@geekmidas/cli 10.0.0-alpha.45 → 10.0.0-alpha.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +144 -183
- package/dist/{OpenApiTsGenerator-B4Kj_qNU.cjs → OpenApiTsGenerator-BN0PIZxd.cjs} +17 -71
- package/dist/OpenApiTsGenerator-BN0PIZxd.cjs.map +1 -0
- package/dist/{OpenApiTsGenerator-CEh3EKN3.mjs → OpenApiTsGenerator-BzIGy8KE.mjs} +17 -71
- package/dist/OpenApiTsGenerator-BzIGy8KE.mjs.map +1 -0
- package/dist/{config-CJ-PAb7M.cjs → config-CsYSyUya.cjs} +3 -3
- package/dist/{config-CJ-PAb7M.cjs.map → config-CsYSyUya.cjs.map} +1 -1
- package/dist/{config-5watB1D0.mjs → config-D8J5rrTb.mjs} +3 -3
- package/dist/{config-5watB1D0.mjs.map → config-D8J5rrTb.mjs.map} +1 -1
- package/dist/config.cjs +2 -2
- package/dist/config.d.cts +2 -2
- package/dist/config.d.mts +2 -2
- package/dist/config.mjs +2 -2
- package/dist/{discover-BLhf7V_J.mjs → discover-Cf2jAcgF.mjs} +4 -56
- package/dist/discover-Cf2jAcgF.mjs.map +1 -0
- package/dist/{discover-B0f_kuNN.cjs → discover-DXNviAh-.cjs} +3 -79
- package/dist/discover-DXNviAh-.cjs.map +1 -0
- package/dist/{emit-Bzq7JsMP.mjs → emit-C1e8qD08.mjs} +12 -43
- package/dist/emit-C1e8qD08.mjs.map +1 -0
- package/dist/{emit-BdoPFjZf.cjs → emit-Dmc1nvdS.cjs} +11 -54
- package/dist/emit-Dmc1nvdS.cjs.map +1 -0
- package/dist/{github-D5MthUlR.mjs → github-BfpadQWQ.mjs} +3 -3
- package/dist/{github-D5MthUlR.mjs.map → github-BfpadQWQ.mjs.map} +1 -1
- package/dist/{github-BAr8WGkR.cjs → github-BrI1tv6u.cjs} +3 -3
- package/dist/{github-BAr8WGkR.cjs.map → github-BrI1tv6u.cjs.map} +1 -1
- package/dist/{index-B3PeGOQp.d.cts → index-BQhJXpaQ.d.mts} +2 -2
- package/dist/{index-B3PeGOQp.d.cts.map → index-BQhJXpaQ.d.mts.map} +1 -1
- package/dist/{index-BOyaGcCm.d.mts → index-DHJePE0y.d.cts} +2 -2
- package/dist/{index-BOyaGcCm.d.mts.map → index-DHJePE0y.d.cts.map} +1 -1
- package/dist/index.cjs +395 -660
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +397 -662
- package/dist/index.mjs.map +1 -1
- package/dist/{migrate-BRklCm7b.mjs → migrate-B3opyC3o.mjs} +5 -5
- package/dist/{migrate-BRklCm7b.mjs.map → migrate-B3opyC3o.mjs.map} +1 -1
- package/dist/{migrate-PJi4b1ga.cjs → migrate-Dp5HFzuQ.cjs} +5 -5
- package/dist/{migrate-PJi4b1ga.cjs.map → migrate-Dp5HFzuQ.cjs.map} +1 -1
- package/dist/openapi.cjs +2 -2
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.mjs +2 -2
- package/dist/{ready-3RfHxdA0.cjs → ready-L7I68yXf.cjs} +67 -8
- package/dist/ready-L7I68yXf.cjs.map +1 -0
- package/dist/{ready-ZqOuNvKD.mjs → ready-V6Ou6-B6.mjs} +49 -8
- package/dist/ready-V6Ou6-B6.mjs.map +1 -0
- package/dist/reconcile/public.cjs +2 -4
- package/dist/reconcile/public.d.cts +6 -10
- package/dist/reconcile/public.d.cts.map +1 -1
- package/dist/reconcile/public.d.mts +6 -10
- package/dist/reconcile/public.d.mts.map +1 -1
- package/dist/reconcile/public.mjs +3 -3
- package/dist/{types-0R124hrY.d.mts → types-BbrNu83u.d.mts} +14 -109
- package/dist/{types-0R124hrY.d.mts.map → types-BbrNu83u.d.mts.map} +1 -1
- package/dist/{types-DfdYe31u.d.cts → types-DNqEsctD.d.cts} +14 -109
- package/dist/{types-DfdYe31u.d.cts.map → types-DNqEsctD.d.cts.map} +1 -1
- package/dist/vitest.cjs +1 -1
- package/dist/vitest.mjs +1 -1
- package/dist/workspace/index.cjs +1 -1
- package/dist/workspace/index.d.cts +2 -2
- package/dist/workspace/index.d.mts +2 -2
- package/dist/workspace/index.mjs +1 -1
- package/dist/{workspace-BKytVCzb.mjs → workspace-CHEC80sp.mjs} +6 -7
- package/dist/workspace-CHEC80sp.mjs.map +1 -0
- package/dist/{workspace-dV6f-3vv.cjs → workspace-D9z2ymBW.cjs} +11 -12
- package/dist/workspace-D9z2ymBW.cjs.map +1 -0
- package/dist/{workspace-RYSYtHP7.cjs → workspace-DEibcoBe.cjs} +11 -25
- package/dist/{workspace-RYSYtHP7.cjs.map → workspace-DEibcoBe.cjs.map} +1 -1
- package/dist/{workspace-DOmIhiau.mjs → workspace-DNzWgM1J.mjs} +11 -25
- package/dist/{workspace-DOmIhiau.mjs.map → workspace-DNzWgM1J.mjs.map} +1 -1
- package/package.json +14 -14
- package/dist/OpenApiTsGenerator-B4Kj_qNU.cjs.map +0 -1
- package/dist/OpenApiTsGenerator-CEh3EKN3.mjs.map +0 -1
- package/dist/discover-B0f_kuNN.cjs.map +0 -1
- package/dist/discover-BLhf7V_J.mjs.map +0 -1
- package/dist/emit-BdoPFjZf.cjs.map +0 -1
- package/dist/emit-Bzq7JsMP.mjs.map +0 -1
- package/dist/ready-3RfHxdA0.cjs.map +0 -1
- package/dist/ready-ZqOuNvKD.mjs.map +0 -1
- package/dist/workspace-BKytVCzb.mjs.map +0 -1
- package/dist/workspace-dV6f-3vv.cjs.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"OpenApiTsGenerator-CEh3EKN3.mjs","names":[],"sources":["../src/build/endpoint-analyzer.ts","../src/build/handler-templates.ts","../src/generators/EndpointGenerator.ts","../src/generators/OpenApiTsGenerator.ts"],"sourcesContent":["/**\n * Endpoint Analyzer for Build-Time Feature Detection\n *\n * Analyzes endpoints at build time to determine their features and assign\n * optimization tiers. This enables generating specialized handler code\n * for maximum performance.\n */\nimport type { Endpoint } from '@geekmidas/constructs/endpoints';\n\n/**\n * Features detected from an endpoint configuration\n */\nexport interface EndpointFeatures {\n\thasAuth: boolean;\n\thasServices: boolean;\n\thasDatabase: boolean;\n\thasBodyValidation: boolean;\n\thasQueryValidation: boolean;\n\thasParamValidation: boolean;\n\thasAudits: boolean;\n\thasEvents: boolean;\n\thasRateLimit: boolean;\n\thasRls: boolean;\n\thasOutputValidation: boolean;\n}\n\n/**\n * Optimization tiers based on endpoint complexity\n *\n * - minimal: No auth, no services, no audits, no events - near-raw-Hono performance\n * - standard: Some features enabled - uses middleware composition\n * - full: Complex endpoints with many features - full handler chain\n */\nexport type EndpointTier = 'minimal' | 'standard' | 'full';\n\n/**\n * Complete analysis of an endpoint for build-time optimization\n */\nexport interface EndpointAnalysis {\n\troute: string;\n\tmethod: string;\n\texportName: string;\n\tfeatures: EndpointFeatures;\n\ttier: EndpointTier;\n\tserviceNames: string[];\n\tdatabaseServiceName?: string;\n}\n\n/**\n * Analyze an endpoint to extract its features\n */\nexport function analyzeEndpointFeatures(\n\tendpoint: Endpoint<\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany\n\t>,\n): EndpointFeatures {\n\treturn {\n\t\thasAuth: !!endpoint.authorizer,\n\t\thasServices: endpoint.services.length > 0,\n\t\thasDatabase: !!endpoint.databaseService,\n\t\thasBodyValidation: !!endpoint.input?.body,\n\t\thasQueryValidation: !!endpoint.input?.query,\n\t\thasParamValidation: !!endpoint.input?.params,\n\t\t// Only declarative audits (.audit([...])) require full tier with transaction wrapping\n\t\t// Having auditorStorageService just makes auditor available to handler (like other services)\n\t\thasAudits: (endpoint.audits?.length ?? 0) > 0,\n\t\thasEvents: (endpoint.events?.length ?? 0) > 0,\n\t\thasRateLimit: !!endpoint.rateLimit,\n\t\thasRls: !!endpoint.rlsConfig && !endpoint.rlsBypass,\n\t\thasOutputValidation: !!endpoint.outputSchema,\n\t};\n}\n\n/**\n * Determine the optimization tier for an endpoint based on its features\n */\nexport function determineEndpointTier(\n\tfeatures: EndpointFeatures,\n): EndpointTier {\n\tconst {\n\t\thasAuth,\n\t\thasServices,\n\t\thasDatabase,\n\t\thasAudits,\n\t\thasEvents,\n\t\thasRateLimit,\n\t\thasRls,\n\t} = features;\n\n\t// Minimal tier: No complex features\n\t// These endpoints can use near-raw-Hono handlers\n\tif (\n\t\t!hasAuth &&\n\t\t!hasServices &&\n\t\t!hasDatabase &&\n\t\t!hasAudits &&\n\t\t!hasEvents &&\n\t\t!hasRateLimit &&\n\t\t!hasRls\n\t) {\n\t\treturn 'minimal';\n\t}\n\n\t// Full tier: Has audits, RLS, or rate limiting (complex state management)\n\tif (hasAudits || hasRls || hasRateLimit) {\n\t\treturn 'full';\n\t}\n\n\t// Standard tier: Auth and/or services, but no complex state\n\treturn 'standard';\n}\n\n/**\n * Perform complete analysis of an endpoint\n */\nexport function analyzeEndpoint(\n\tendpoint: Endpoint<\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany\n\t>,\n\texportName: string,\n): EndpointAnalysis {\n\tconst features = analyzeEndpointFeatures(endpoint);\n\tconst tier = determineEndpointTier(features);\n\n\treturn {\n\t\troute: endpoint.route,\n\t\tmethod: endpoint.method,\n\t\texportName,\n\t\tfeatures,\n\t\ttier,\n\t\tserviceNames: endpoint.services.map(\n\t\t\t(s: { serviceName: string }) => s.serviceName,\n\t\t),\n\t\tdatabaseServiceName: endpoint.databaseService?.serviceName,\n\t};\n}\n\n/**\n * Analyze multiple endpoints and return analysis results\n */\nexport function analyzeEndpoints(\n\tendpoints: Array<{\n\t\tendpoint: Endpoint<\n\t\t\tany,\n\t\t\tany,\n\t\t\tany,\n\t\t\tany,\n\t\t\tany,\n\t\t\tany,\n\t\t\tany,\n\t\t\tany,\n\t\t\tany,\n\t\t\tany,\n\t\t\tany,\n\t\t\tany\n\t\t>;\n\t\texportName: string;\n\t}>,\n): EndpointAnalysis[] {\n\treturn endpoints.map(({ endpoint, exportName }) =>\n\t\tanalyzeEndpoint(endpoint, exportName),\n\t);\n}\n\n/**\n * Generate a summary of endpoint analysis for logging\n */\nexport function summarizeAnalysis(analyses: EndpointAnalysis[]): {\n\ttotal: number;\n\tbyTier: Record<EndpointTier, number>;\n\tbyFeature: Record<keyof EndpointFeatures, number>;\n} {\n\tconst byTier: Record<EndpointTier, number> = {\n\t\tminimal: 0,\n\t\tstandard: 0,\n\t\tfull: 0,\n\t};\n\n\tconst byFeature: Record<keyof EndpointFeatures, number> = {\n\t\thasAuth: 0,\n\t\thasServices: 0,\n\t\thasDatabase: 0,\n\t\thasBodyValidation: 0,\n\t\thasQueryValidation: 0,\n\t\thasParamValidation: 0,\n\t\thasAudits: 0,\n\t\thasEvents: 0,\n\t\thasRateLimit: 0,\n\t\thasRls: 0,\n\t\thasOutputValidation: 0,\n\t};\n\n\tfor (const analysis of analyses) {\n\t\tbyTier[analysis.tier]++;\n\n\t\tfor (const [feature, enabled] of Object.entries(analysis.features)) {\n\t\t\tif (enabled) {\n\t\t\t\tbyFeature[feature as keyof EndpointFeatures]++;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\ttotal: analyses.length,\n\t\tbyTier,\n\t\tbyFeature,\n\t};\n}\n","/**\n * Handler Templates for Build-Time Code Generation\n *\n * Generates optimized handler code based on endpoint tier:\n * - minimal: Near-raw-Hono performance for simple endpoints\n * - standard: Middleware composition for auth/services\n * - full: Complete handler chain for complex endpoints\n *\n * Output structure (split by tier):\n * - endpoints/validators.ts - Shared validator factories\n * - endpoints/minimal.ts - Minimal tier handlers\n * - endpoints/standard.ts - Standard tier handlers\n * - endpoints/full.ts - Full tier handlers\n * - endpoints/index.ts - Main entry point\n */\nimport type { EndpointAnalysis, EndpointFeatures } from './endpoint-analyzer';\n\n/**\n * Multi-file output structure (flat by tier)\n */\nexport interface GeneratedEndpointFiles {\n\t'validators.ts': string;\n\t'minimal.ts': string;\n\t'standard.ts': string;\n\t'full.ts': string;\n\t'index.ts': string;\n}\n\n/**\n * Nested folder structure with per-endpoint files\n *\n * Structure:\n * - endpoints/validators.ts\n * - endpoints/minimal/index.ts\n * - endpoints/minimal/[endpointName].ts\n * - endpoints/standard/index.ts\n * - endpoints/standard/[endpointName].ts\n * - endpoints/full/index.ts\n * - endpoints/full/[endpointName].ts\n * - endpoints/index.ts\n */\nexport interface GeneratedEndpointFilesNested {\n\t'validators.ts': string;\n\t'minimal/index.ts': string;\n\t'standard/index.ts': string;\n\t'full/index.ts': string;\n\t'index.ts': string;\n\t[path: string]: string; // e.g., 'minimal/healthEndpoint.ts'\n}\n\n/**\n * Endpoint import info for generating import statements\n */\nexport interface EndpointImportInfo {\n\texportName: string;\n\timportPath: string;\n}\n\n/**\n * Generate imports needed for optimized endpoints file\n */\nexport function generateOptimizedImports(analyses: EndpointAnalysis[]): string {\n\tconst needsValidator = analyses.some(\n\t\t(a) =>\n\t\t\ta.features.hasBodyValidation ||\n\t\t\ta.features.hasQueryValidation ||\n\t\t\ta.features.hasParamValidation,\n\t);\n\n\tconst needsResponseBuilder = analyses.some(\n\t\t(a) => a.tier === 'standard' || a.tier === 'full',\n\t);\n\n\tconst needsServiceDiscovery = analyses.some(\n\t\t(a) => a.features.hasServices || a.features.hasDatabase,\n\t);\n\n\tconst needsEvents = analyses.some((a) => a.features.hasEvents);\n\n\tconst needsAudits = analyses.some((a) => a.features.hasAudits);\n\n\tconst needsRateLimit = analyses.some((a) => a.features.hasRateLimit);\n\n\tconst needsRls = analyses.some((a) => a.features.hasRls);\n\n\tconst imports: string[] = [\n\t\t`import type { EnvironmentParser } from '@geekmidas/envkit';`,\n\t\t`import type { Logger } from '@geekmidas/logger';`,\n\t\t`import type { Hono } from 'hono';`,\n\t];\n\n\tif (needsValidator) {\n\t\timports.push(`import { validator } from 'hono/validator';`);\n\t}\n\n\timports.push(`import { Endpoint } from '@geekmidas/constructs/endpoints';`);\n\n\tif (needsResponseBuilder) {\n\t\timports.push(\n\t\t\t`import { ResponseBuilder } from '@geekmidas/constructs/endpoints';`,\n\t\t);\n\t}\n\n\tif (needsServiceDiscovery) {\n\t\timports.push(`import { ServiceDiscovery } from '@geekmidas/services';`);\n\t}\n\n\tif (needsEvents) {\n\t\timports.push(\n\t\t\t`import { publishConstructEvents } from '@geekmidas/constructs/endpoints';`,\n\t\t);\n\t}\n\n\tif (needsAudits) {\n\t\timports.push(\n\t\t\t`import { createAuditContext, withAuditableEndpointTransaction } from '@geekmidas/constructs/endpoints';`,\n\t\t);\n\t}\n\n\tif (needsRateLimit) {\n\t\timports.push(`import { createError } from '@geekmidas/errors';`);\n\t}\n\n\tif (needsRls) {\n\t\timports.push(\n\t\t\t`import { withRlsContext, extractRlsContext } from '@geekmidas/constructs/endpoints';`,\n\t\t);\n\t}\n\n\treturn imports.join('\\n');\n}\n\n/**\n * Generate reusable validator middleware factories\n */\nexport function generateValidatorFactories(\n\tanalyses: EndpointAnalysis[],\n): string {\n\tconst needsBody = analyses.some((a) => a.features.hasBodyValidation);\n\tconst needsQuery = analyses.some((a) => a.features.hasQueryValidation);\n\tconst needsParams = analyses.some((a) => a.features.hasParamValidation);\n\n\tif (!needsBody && !needsQuery && !needsParams) {\n\t\treturn '';\n\t}\n\n\tconst factories: string[] = [];\n\n\tif (needsBody) {\n\t\tfactories.push(`\nconst validateBody = (endpoint: any) =>\n validator('json', async (value, c) => {\n if (!endpoint.input?.body) return undefined;\n const parsed = await Endpoint.validate(endpoint.input.body, value);\n if (parsed.issues) return c.json(parsed.issues, 422);\n return parsed.value;\n });`);\n\t}\n\n\tif (needsQuery) {\n\t\tfactories.push(`\nconst validateQuery = (endpoint: any) =>\n validator('query', async (_, c) => {\n if (!endpoint.input?.query) return undefined;\n const rawQuery = Object.fromEntries(new URL(c.req.url).searchParams);\n const parsed = await Endpoint.validate(endpoint.input.query, rawQuery);\n if (parsed.issues) return c.json(parsed.issues, 422);\n return parsed.value;\n });`);\n\t}\n\n\tif (needsParams) {\n\t\tfactories.push(`\nconst validateParams = (endpoint: any) =>\n validator('param', async (params, c) => {\n if (!endpoint.input?.params) return undefined;\n const parsed = await Endpoint.validate(endpoint.input.params, params);\n if (parsed.issues) return c.json(parsed.issues, 422);\n return parsed.value;\n });`);\n\t}\n\n\treturn factories.join('\\n');\n}\n\n/**\n * Generate validator references for an endpoint\n */\nfunction generateValidators(\n\texportName: string,\n\tfeatures: EndpointFeatures,\n): string {\n\tconst validators: string[] = [];\n\n\tif (features.hasBodyValidation) {\n\t\tvalidators.push(`validateBody(${exportName})`);\n\t}\n\n\tif (features.hasQueryValidation) {\n\t\tvalidators.push(`validateQuery(${exportName})`);\n\t}\n\n\tif (features.hasParamValidation) {\n\t\tvalidators.push(`validateParams(${exportName})`);\n\t}\n\n\t// Add trailing comma if there are validators (needed before the handler function)\n\treturn validators.length > 0 ? `\\n ${validators.join(',\\n ')},` : '';\n}\n\n/**\n * Generate a minimal handler (near-raw-Hono performance)\n *\n * Used for: Health checks, public endpoints with no services\n */\nexport function generateMinimalHandler(analysis: EndpointAnalysis): string {\n\tconst { exportName, features } = analysis;\n\tconst method = analysis.method.toLowerCase();\n\n\tconst validators = generateValidators(exportName, features);\n\tconst hasValidators = validators.length > 0;\n\n\t// For truly minimal endpoints (no validation), generate inline handler\n\tif (!hasValidators && !features.hasOutputValidation) {\n\t\treturn `\n // Minimal handler: ${analysis.route} (${analysis.method})\n app.${method}('${analysis.route}', async (c) => {\n const result = await ${exportName}.handler(\n {\n services: {},\n logger,\n body: undefined,\n query: undefined,\n params: undefined,\n session: undefined,\n header: Endpoint.createHeaders(c.req.header()),\n cookie: Endpoint.createCookies(c.req.header().cookie),\n auditor: undefined,\n db: undefined,\n } as any,\n { getMetadata: () => ({}) } as any,\n );\n return c.json(result, ${exportName}.status as any);\n });`;\n\t}\n\n\t// With validation but still minimal\n\treturn `\n // Minimal handler with validation: ${analysis.route} (${analysis.method})\n app.${method}('${analysis.route}',${validators}\n async (c) => {\n const result = await ${exportName}.handler(\n {\n services: {},\n logger,\n body: ${features.hasBodyValidation ? \"(c.req.valid as any)('json')\" : 'undefined'},\n query: ${features.hasQueryValidation ? \"(c.req.valid as any)('query')\" : 'undefined'},\n params: ${features.hasParamValidation ? \"(c.req.valid as any)('param')\" : 'undefined'},\n session: undefined,\n header: Endpoint.createHeaders(c.req.header()),\n cookie: Endpoint.createCookies(c.req.header().cookie),\n auditor: undefined,\n db: undefined,\n } as any,\n { getMetadata: () => ({}) } as any,\n );\n ${\n\t\t\t\tfeatures.hasOutputValidation\n\t\t\t\t\t? `const output = await ${exportName}.parseOutput(result);\n return c.json(output, ${exportName}.status as any);`\n\t\t\t\t\t: `return c.json(result, ${exportName}.status as any);`\n\t\t\t}\n }\n );`;\n}\n\n/**\n * Generate a standard handler (auth and/or services)\n *\n * Used for: Authenticated endpoints, endpoints with services\n */\nexport function generateStandardHandler(analysis: EndpointAnalysis): string {\n\tconst { exportName, features } = analysis;\n\tconst method = analysis.method.toLowerCase();\n\n\tconst validators = generateValidators(exportName, features);\n\n\t// Build service resolution code\n\tlet serviceResolution = '';\n\tif (features.hasServices || features.hasDatabase) {\n\t\tserviceResolution = `\n const services = await serviceDiscovery.register(${exportName}.services);\n ${\n\t\t\t\tfeatures.hasDatabase\n\t\t\t\t\t? `const db = ${exportName}.databaseService\n ? (await serviceDiscovery.register([${exportName}.databaseService]) as any)[${exportName}.databaseService.serviceName]\n : undefined;`\n\t\t\t\t\t: 'const db = undefined;'\n\t\t\t}`;\n\t} else {\n\t\tserviceResolution = `\n const services = {};\n const db = undefined;`;\n\t}\n\n\t// Build auth code\n\tlet authCode = '';\n\tif (features.hasAuth) {\n\t\tauthCode = `\n // Authentication\n const session = await ${exportName}.getSession({\n services,\n logger,\n header,\n cookie,\n ...(db !== undefined && { db }),\n } as any);\n\n const isAuthorized = await ${exportName}.authorize({\n header,\n cookie,\n services,\n logger,\n session,\n } as any);\n\n if (!isAuthorized) {\n return c.json({ error: 'Unauthorized' }, 401);\n }`;\n\t}\n\n\t// Build event publishing code\n\tlet eventCode = '';\n\tif (features.hasEvents) {\n\t\teventCode = `\n // Publish events on success\n if (Endpoint.isSuccessStatus(${exportName}.status)) {\n await (publishConstructEvents as any)(\n ${exportName},\n result,\n serviceDiscovery,\n logger,\n );\n }`;\n\t}\n\n\treturn `\n // Standard handler: ${analysis.route} (${analysis.method})\n app.${method}('${analysis.route}',${validators}\n async (c) => {\n const headerValues = c.req.header();\n const header = Endpoint.createHeaders(headerValues);\n const cookie = Endpoint.createCookies(headerValues.cookie);\n${serviceResolution}\n${authCode}\n\n const responseBuilder = new ResponseBuilder();\n const result = await ${exportName}.handler(\n {\n services,\n logger,\n body: ${features.hasBodyValidation ? \"(c.req.valid as any)('json')\" : 'undefined'},\n query: ${features.hasQueryValidation ? \"(c.req.valid as any)('query')\" : 'undefined'},\n params: ${features.hasParamValidation ? \"(c.req.valid as any)('param')\" : 'undefined'},\n session: ${features.hasAuth ? 'session' : 'undefined'},\n header,\n cookie,\n auditor: undefined,\n db,\n } as any,\n responseBuilder,\n );\n\n let data = result;\n let metadata = responseBuilder.getMetadata();\n\n if (Endpoint.hasMetadata(result)) {\n data = result.data;\n metadata = result.metadata;\n }\n\n ${\n\t\t\t\tfeatures.hasOutputValidation\n\t\t\t\t\t? `const output = ${exportName}.outputSchema\n ? await ${exportName}.parseOutput(data)\n : data;`\n\t\t\t\t\t: 'const output = data;'\n\t\t\t}\n${eventCode}\n\n const status = (metadata.status ?? ${exportName}.status) as any;\n return c.json(output, status);\n }\n );`;\n}\n\n/**\n * Generate setup function that uses HonoEndpoint.addRoutes for full-featured endpoints\n * but generates optimized inline handlers for minimal/standard endpoints\n */\nexport function generateOptimizedSetupFunction(\n\tanalyses: EndpointAnalysis[],\n\t_allExportNames: string[],\n): string {\n\tconst minimalEndpoints = analyses.filter((a) => a.tier === 'minimal');\n\tconst standardEndpoints = analyses.filter((a) => a.tier === 'standard');\n\tconst fullEndpoints = analyses.filter((a) => a.tier === 'full');\n\n\t// Generate inline handlers for minimal and standard endpoints\n\tconst minimalHandlers = minimalEndpoints\n\t\t.map((a) => generateMinimalHandler(a))\n\t\t.join('\\n');\n\n\tconst standardHandlers = standardEndpoints\n\t\t.map((a) => generateStandardHandler(a))\n\t\t.join('\\n');\n\n\t// Full endpoints use HonoEndpoint.addRoutes\n\tconst fullEndpointNames = fullEndpoints.map((a) => a.exportName);\n\n\tconst fullEndpointsSetup =\n\t\tfullEndpointNames.length > 0\n\t\t\t? `\n // Full-featured endpoints use HonoEndpoint.addRoutes\n const fullEndpoints = [${fullEndpointNames.join(', ')}];\n HonoEndpoint.addRoutes(fullEndpoints, serviceDiscovery, app, openApiOptions);`\n\t\t\t: '';\n\n\t// Add HonoEndpoint import only if needed\n\tconst honoEndpointImport =\n\t\tfullEndpointNames.length > 0\n\t\t\t? `import { HonoEndpoint } from '@geekmidas/constructs/hono';`\n\t\t\t: '';\n\n\t// Only generate openApiOptions if we have full endpoints that need it\n\tconst openApiOptionsDecl =\n\t\tfullEndpointNames.length > 0\n\t\t\t? `\n const openApiOptions: any = enableOpenApi ? {\n docsPath: '/__docs',\n openApiOptions: {\n title: 'API Documentation',\n version: '1.0.0',\n description: 'Generated API documentation'\n }\n } : { docsPath: false };`\n\t\t\t: '';\n\n\treturn `${honoEndpointImport}\n\nexport async function setupEndpoints(\n app: Hono,\n envParser: EnvironmentParser<any>,\n logger: Logger,\n enableOpenApi: boolean = false,\n): Promise<void> {\n const serviceDiscovery = ServiceDiscovery.getInstance(envParser);\n${openApiOptionsDecl}\n\n // ============================================\n // Minimal handlers (${minimalEndpoints.length} endpoints)\n // Near-raw-Hono performance\n // ============================================\n${minimalHandlers}\n\n // ============================================\n // Standard handlers (${standardEndpoints.length} endpoints)\n // Auth and/or services\n // ============================================\n${standardHandlers}\n${fullEndpointsSetup}\n\n // Add Swagger UI if OpenAPI is enabled\n if (enableOpenApi) {\n try {\n const { swaggerUI } = await import('@hono/swagger-ui');\n app.get('/__docs/ui', swaggerUI({ url: '/__docs' }));\n } catch {\n // @hono/swagger-ui not installed, skip Swagger UI\n }\n }\n}`;\n}\n\n/**\n * Generate complete optimized endpoints file\n */\nexport function generateOptimizedEndpointsFile(\n\tanalyses: EndpointAnalysis[],\n\tendpointImports: string,\n\tallExportNames: string[],\n): string {\n\tconst imports = generateOptimizedImports(analyses);\n\tconst validatorFactories = generateValidatorFactories(analyses);\n\tconst setupFunction = generateOptimizedSetupFunction(\n\t\tanalyses,\n\t\tallExportNames,\n\t);\n\n\treturn `/**\n * Generated optimized endpoints file\n *\n * Build-time optimization tiers:\n * - minimal: ${analyses.filter((a) => a.tier === 'minimal').length} endpoints (near-raw-Hono)\n * - standard: ${analyses.filter((a) => a.tier === 'standard').length} endpoints (auth/services)\n * - full: ${analyses.filter((a) => a.tier === 'full').length} endpoints (audits/rls/rate-limit)\n */\n${imports}\n${endpointImports}\n${validatorFactories}\n\n${setupFunction}\n`;\n}\n\n// ============================================================================\n// Multi-File Generation (Split by Tier)\n// ============================================================================\n\n/**\n * Generate validators.ts - Shared validator middleware factories\n */\nfunction generateValidatorsFile(analyses: EndpointAnalysis[]): string {\n\tconst needsBody = analyses.some((a) => a.features.hasBodyValidation);\n\tconst needsQuery = analyses.some((a) => a.features.hasQueryValidation);\n\tconst needsParams = analyses.some((a) => a.features.hasParamValidation);\n\n\tif (!needsBody && !needsQuery && !needsParams) {\n\t\treturn `// No validators needed for this build\\nexport {};\\n`;\n\t}\n\n\tconst exports: string[] = [];\n\tconst factories: string[] = [];\n\n\tif (needsBody) {\n\t\texports.push('validateBody');\n\t\tfactories.push(`\nexport const validateBody = (endpoint: any) =>\n validator('json', async (value, c) => {\n if (!endpoint.input?.body) return undefined;\n const parsed = await Endpoint.validate(endpoint.input.body, value);\n if (parsed.issues) return c.json(parsed.issues, 422);\n return parsed.value;\n });`);\n\t}\n\n\tif (needsQuery) {\n\t\texports.push('validateQuery');\n\t\tfactories.push(`\nexport const validateQuery = (endpoint: any) =>\n validator('query', async (_, c) => {\n if (!endpoint.input?.query) return undefined;\n const rawQuery = Object.fromEntries(new URL(c.req.url).searchParams);\n const parsed = await Endpoint.validate(endpoint.input.query, rawQuery);\n if (parsed.issues) return c.json(parsed.issues, 422);\n return parsed.value;\n });`);\n\t}\n\n\tif (needsParams) {\n\t\texports.push('validateParams');\n\t\tfactories.push(`\nexport const validateParams = (endpoint: any) =>\n validator('param', async (params, c) => {\n if (!endpoint.input?.params) return undefined;\n const parsed = await Endpoint.validate(endpoint.input.params, params);\n if (parsed.issues) return c.json(parsed.issues, 422);\n return parsed.value;\n });`);\n\t}\n\n\treturn `/**\n * Generated validator middleware factories\n * Shared across all endpoint tiers that need validation\n */\nimport { validator } from 'hono/validator';\nimport { Endpoint } from '@geekmidas/constructs/endpoints';\n${factories.join('\\n')}\n`;\n}\n\n/**\n * Generate minimal.ts - Minimal tier handlers\n */\nfunction generateMinimalFile(\n\tanalyses: EndpointAnalysis[],\n\tendpointImports: EndpointImportInfo[],\n): string {\n\tconst minimalEndpoints = analyses.filter((a) => a.tier === 'minimal');\n\n\tif (minimalEndpoints.length === 0) {\n\t\treturn `// No minimal-tier endpoints in this build\nimport type { Hono } from 'hono';\nimport type { Logger } from '@geekmidas/logger';\n\nexport function setupMinimalEndpoints(\n _app: Hono,\n _logger: Logger,\n): void {\n // No minimal endpoints\n}\n`;\n\t}\n\n\tconst minimalExportNames = minimalEndpoints.map((a) => a.exportName);\n\tconst relevantImports = endpointImports.filter((i) =>\n\t\tminimalExportNames.includes(i.exportName),\n\t);\n\n\tconst importStatements = relevantImports\n\t\t.map((i) => `import { ${i.exportName} } from '${i.importPath}';`)\n\t\t.join('\\n');\n\n\tconst needsValidators = minimalEndpoints.some(\n\t\t(a) =>\n\t\t\ta.features.hasBodyValidation ||\n\t\t\ta.features.hasQueryValidation ||\n\t\t\ta.features.hasParamValidation,\n\t);\n\n\tconst validatorImport = needsValidators\n\t\t? generateValidatorImports(minimalEndpoints)\n\t\t: '';\n\n\tconst handlers = minimalEndpoints\n\t\t.map((a) => generateMinimalHandler(a))\n\t\t.join('\\n');\n\n\treturn `/**\n * Minimal-tier endpoint handlers (${minimalEndpoints.length} endpoints)\n * Near-raw-Hono performance - no auth, no services, no complex features\n */\nimport type { Hono } from 'hono';\nimport type { Logger } from '@geekmidas/logger';\nimport { Endpoint } from '@geekmidas/constructs/endpoints';\n${validatorImport}\n${importStatements}\n\nexport function setupMinimalEndpoints(\n app: Hono,\n logger: Logger,\n): void {\n${handlers}\n}\n`;\n}\n\n/**\n * Generate standard.ts - Standard tier handlers\n */\nfunction generateStandardFile(\n\tanalyses: EndpointAnalysis[],\n\tendpointImports: EndpointImportInfo[],\n): string {\n\tconst standardEndpoints = analyses.filter((a) => a.tier === 'standard');\n\n\tif (standardEndpoints.length === 0) {\n\t\treturn `// No standard-tier endpoints in this build\nimport type { Hono } from 'hono';\nimport type { Logger } from '@geekmidas/logger';\nimport type { ServiceDiscovery } from '@geekmidas/services';\n\nexport function setupStandardEndpoints(\n _app: Hono,\n _serviceDiscovery: ServiceDiscovery<any, any>,\n _logger: Logger,\n): void {\n // No standard endpoints\n}\n`;\n\t}\n\n\tconst standardExportNames = standardEndpoints.map((a) => a.exportName);\n\tconst relevantImports = endpointImports.filter((i) =>\n\t\tstandardExportNames.includes(i.exportName),\n\t);\n\n\tconst importStatements = relevantImports\n\t\t.map((i) => `import { ${i.exportName} } from '${i.importPath}';`)\n\t\t.join('\\n');\n\n\tconst needsValidators = standardEndpoints.some(\n\t\t(a) =>\n\t\t\ta.features.hasBodyValidation ||\n\t\t\ta.features.hasQueryValidation ||\n\t\t\ta.features.hasParamValidation,\n\t);\n\n\tconst validatorImport = needsValidators\n\t\t? generateValidatorImports(standardEndpoints)\n\t\t: '';\n\n\tconst needsEvents = standardEndpoints.some((a) => a.features.hasEvents);\n\tconst eventsImport = needsEvents\n\t\t? `import { publishConstructEvents } from '@geekmidas/constructs/endpoints';`\n\t\t: '';\n\n\tconst handlers = standardEndpoints\n\t\t.map((a) => generateStandardHandler(a))\n\t\t.join('\\n');\n\n\treturn `/**\n * Standard-tier endpoint handlers (${standardEndpoints.length} endpoints)\n * Auth and/or services enabled, but no complex features like audits/RLS\n */\nimport type { Hono } from 'hono';\nimport type { Logger } from '@geekmidas/logger';\nimport type { ServiceDiscovery } from '@geekmidas/services';\nimport { Endpoint, ResponseBuilder } from '@geekmidas/constructs/endpoints';\n${eventsImport}\n${validatorImport}\n${importStatements}\n\nexport function setupStandardEndpoints(\n app: Hono,\n serviceDiscovery: ServiceDiscovery<any, any>,\n logger: Logger,\n): void {\n${handlers}\n}\n`;\n}\n\n/**\n * Generate full.ts - Full tier handlers (uses HonoEndpoint.addRoutes)\n */\nfunction generateFullFile(\n\tanalyses: EndpointAnalysis[],\n\tendpointImports: EndpointImportInfo[],\n): string {\n\tconst fullEndpoints = analyses.filter((a) => a.tier === 'full');\n\n\tif (fullEndpoints.length === 0) {\n\t\treturn `// No full-tier endpoints in this build\nimport type { Hono } from 'hono';\nimport type { ServiceDiscovery } from '@geekmidas/services';\n\nexport function setupFullEndpoints(\n _app: Hono,\n _serviceDiscovery: ServiceDiscovery<any, any>,\n _enableOpenApi: boolean,\n): void {\n // No full endpoints\n}\n`;\n\t}\n\n\tconst fullExportNames = fullEndpoints.map((a) => a.exportName);\n\tconst relevantImports = endpointImports.filter((i) =>\n\t\tfullExportNames.includes(i.exportName),\n\t);\n\n\tconst importStatements = relevantImports\n\t\t.map((i) => `import { ${i.exportName} } from '${i.importPath}';`)\n\t\t.join('\\n');\n\n\treturn `/**\n * Full-tier endpoint handlers (${fullEndpoints.length} endpoints)\n * Complex features: audits, RLS, rate limiting\n * Uses HonoEndpoint.addRoutes for full feature support\n */\nimport type { Hono } from 'hono';\nimport type { ServiceDiscovery } from '@geekmidas/services';\nimport { HonoEndpoint } from '@geekmidas/constructs/hono';\n${importStatements}\n\nconst fullEndpoints = [${fullExportNames.join(', ')}];\n\nexport function setupFullEndpoints(\n app: Hono,\n serviceDiscovery: ServiceDiscovery<any, any>,\n enableOpenApi: boolean,\n): void {\n const openApiOptions: any = enableOpenApi ? {\n docsPath: '/__docs',\n openApiOptions: {\n title: 'API Documentation',\n version: '1.0.0',\n description: 'Generated API documentation'\n }\n } : { docsPath: false };\n\n HonoEndpoint.addRoutes(fullEndpoints as any, serviceDiscovery, app, openApiOptions);\n}\n`;\n}\n\n/**\n * Generate index.ts - Main entry point\n */\nfunction generateIndexFile(analyses: EndpointAnalysis[]): string {\n\tconst minimalCount = analyses.filter((a) => a.tier === 'minimal').length;\n\tconst standardCount = analyses.filter((a) => a.tier === 'standard').length;\n\tconst fullCount = analyses.filter((a) => a.tier === 'full').length;\n\n\treturn `/**\n * Generated optimized endpoints\n *\n * Build-time optimization tiers:\n * - minimal: ${minimalCount} endpoints (near-raw-Hono)\n * - standard: ${standardCount} endpoints (auth/services)\n * - full: ${fullCount} endpoints (audits/rls/rate-limit)\n */\nimport type { EnvironmentParser } from '@geekmidas/envkit';\nimport type { Logger } from '@geekmidas/logger';\nimport type { Hono } from 'hono';\nimport { ServiceDiscovery } from '@geekmidas/services';\nimport { setupMinimalEndpoints } from './minimal.js';\nimport { setupStandardEndpoints } from './standard.js';\nimport { setupFullEndpoints } from './full.js';\n\nexport async function setupEndpoints(\n app: Hono,\n envParser: EnvironmentParser<any>,\n logger: Logger,\n enableOpenApi: boolean = false,\n): Promise<void> {\n const serviceDiscovery = ServiceDiscovery.getInstance(envParser);\n\n // Minimal handlers (${minimalCount} endpoints) - near-raw-Hono performance\n setupMinimalEndpoints(app, logger);\n\n // Standard handlers (${standardCount} endpoints) - auth/services\n setupStandardEndpoints(app, serviceDiscovery, logger);\n\n // Full handlers (${fullCount} endpoints) - audits/rls/rate-limit\n setupFullEndpoints(app, serviceDiscovery, enableOpenApi);\n\n // Add Swagger UI if OpenAPI is enabled\n if (enableOpenApi) {\n try {\n const { swaggerUI } = await import('@hono/swagger-ui');\n app.get('/__docs/ui', swaggerUI({ url: '/__docs' }));\n } catch {\n // @hono/swagger-ui not installed, skip Swagger UI\n }\n }\n}\n`;\n}\n\n/**\n * Generate validator imports based on what's needed\n */\nfunction generateValidatorImports(analyses: EndpointAnalysis[]): string {\n\tconst needsBody = analyses.some((a) => a.features.hasBodyValidation);\n\tconst needsQuery = analyses.some((a) => a.features.hasQueryValidation);\n\tconst needsParams = analyses.some((a) => a.features.hasParamValidation);\n\n\tconst imports: string[] = [];\n\tif (needsBody) imports.push('validateBody');\n\tif (needsQuery) imports.push('validateQuery');\n\tif (needsParams) imports.push('validateParams');\n\n\tif (imports.length === 0) return '';\n\treturn `import { ${imports.join(', ')} } from './validators.js';`;\n}\n\n/**\n * Generate all endpoint files split by tier (flat structure)\n */\nexport function generateEndpointFilesByTier(\n\tanalyses: EndpointAnalysis[],\n\tendpointImports: EndpointImportInfo[],\n): GeneratedEndpointFiles {\n\treturn {\n\t\t'validators.ts': generateValidatorsFile(analyses),\n\t\t'minimal.ts': generateMinimalFile(analyses, endpointImports),\n\t\t'standard.ts': generateStandardFile(analyses, endpointImports),\n\t\t'full.ts': generateFullFile(analyses, endpointImports),\n\t\t'index.ts': generateIndexFile(analyses),\n\t};\n}\n\n// ============================================================================\n// Per-Endpoint File Generation (Nested Folder Structure)\n// ============================================================================\n\n/**\n * Generate a standalone minimal endpoint file\n */\nfunction generateMinimalEndpointFile(\n\tanalysis: EndpointAnalysis,\n\tendpointImport: EndpointImportInfo,\n): string {\n\tconst { exportName, features } = analysis;\n\n\tconst needsValidators =\n\t\tfeatures.hasBodyValidation ||\n\t\tfeatures.hasQueryValidation ||\n\t\tfeatures.hasParamValidation;\n\n\tconst validatorImport = needsValidators\n\t\t? generateValidatorImportsForEndpoint(analysis)\n\t\t: '';\n\n\tconst handler = generateMinimalHandler(analysis);\n\n\treturn `/**\n * Minimal endpoint: ${analysis.route} (${analysis.method})\n * Near-raw-Hono performance\n */\nimport type { Hono } from 'hono';\nimport type { Logger } from '@geekmidas/logger';\nimport { Endpoint } from '@geekmidas/constructs/endpoints';\n${validatorImport}\nimport { ${exportName} } from '${endpointImport.importPath}';\n\nexport function setup${capitalize(exportName)}(\n app: Hono,\n logger: Logger,\n): void {\n${handler}\n}\n`;\n}\n\n/**\n * Generate a standalone standard endpoint file\n */\nfunction generateStandardEndpointFile(\n\tanalysis: EndpointAnalysis,\n\tendpointImport: EndpointImportInfo,\n): string {\n\tconst { exportName, features } = analysis;\n\n\tconst needsValidators =\n\t\tfeatures.hasBodyValidation ||\n\t\tfeatures.hasQueryValidation ||\n\t\tfeatures.hasParamValidation;\n\n\tconst validatorImport = needsValidators\n\t\t? generateValidatorImportsForEndpoint(analysis)\n\t\t: '';\n\n\tconst eventsImport = features.hasEvents\n\t\t? `import { publishConstructEvents } from '@geekmidas/constructs/endpoints';`\n\t\t: '';\n\n\tconst handler = generateStandardHandler(analysis);\n\n\treturn `/**\n * Standard endpoint: ${analysis.route} (${analysis.method})\n * Auth and/or services enabled\n */\nimport type { Hono } from 'hono';\nimport type { Logger } from '@geekmidas/logger';\nimport type { ServiceDiscovery } from '@geekmidas/services';\nimport { Endpoint, ResponseBuilder } from '@geekmidas/constructs/endpoints';\n${eventsImport}\n${validatorImport}\nimport { ${exportName} } from '${endpointImport.importPath}';\n\nexport function setup${capitalize(exportName)}(\n app: Hono,\n serviceDiscovery: ServiceDiscovery<any, any>,\n logger: Logger,\n): void {\n${handler}\n}\n`;\n}\n\n/**\n * Generate a standalone full endpoint file\n */\nfunction generateFullEndpointFile(\n\tanalysis: EndpointAnalysis,\n\tendpointImport: EndpointImportInfo,\n): string {\n\tconst { exportName } = analysis;\n\n\treturn `/**\n * Full endpoint: ${analysis.route} (${analysis.method})\n * Complex features: audits, RLS, rate limiting\n */\nimport type { Hono } from 'hono';\nimport type { ServiceDiscovery } from '@geekmidas/services';\nimport { HonoEndpoint } from '@geekmidas/constructs/hono';\nimport { ${exportName} } from '${endpointImport.importPath}';\n\nexport function setup${capitalize(exportName)}(\n app: Hono,\n serviceDiscovery: ServiceDiscovery<any, any>,\n openApiOptions: any,\n): void {\n HonoEndpoint.addRoutes([${exportName}] as any, serviceDiscovery, app, openApiOptions);\n}\n`;\n}\n\n/**\n * Generate tier index file that imports and calls all endpoint setup functions\n */\nfunction generateTierIndexFile(\n\ttier: 'minimal' | 'standard' | 'full',\n\tanalyses: EndpointAnalysis[],\n): string {\n\tconst tierEndpoints = analyses.filter((a) => a.tier === tier);\n\n\tif (tierEndpoints.length === 0) {\n\t\tif (tier === 'minimal') {\n\t\t\treturn `// No minimal-tier endpoints in this build\nimport type { Hono } from 'hono';\nimport type { Logger } from '@geekmidas/logger';\n\nexport function setupMinimalEndpoints(\n _app: Hono,\n _logger: Logger,\n): void {\n // No minimal endpoints\n}\n`;\n\t\t}\n\t\tif (tier === 'standard') {\n\t\t\treturn `// No standard-tier endpoints in this build\nimport type { Hono } from 'hono';\nimport type { Logger } from '@geekmidas/logger';\nimport type { ServiceDiscovery } from '@geekmidas/services';\n\nexport function setupStandardEndpoints(\n _app: Hono,\n _serviceDiscovery: ServiceDiscovery<any, any>,\n _logger: Logger,\n): void {\n // No standard endpoints\n}\n`;\n\t\t}\n\t\t// full\n\t\treturn `// No full-tier endpoints in this build\nimport type { Hono } from 'hono';\nimport type { ServiceDiscovery } from '@geekmidas/services';\n\nexport function setupFullEndpoints(\n _app: Hono,\n _serviceDiscovery: ServiceDiscovery<any, any>,\n _enableOpenApi: boolean,\n): void {\n // No full endpoints\n}\n`;\n\t}\n\n\tconst imports = tierEndpoints\n\t\t.map(\n\t\t\t(a) =>\n\t\t\t\t`import { setup${capitalize(a.exportName)} } from './${a.exportName}.js';`,\n\t\t)\n\t\t.join('\\n');\n\n\tif (tier === 'minimal') {\n\t\tconst calls = tierEndpoints\n\t\t\t.map((a) => ` setup${capitalize(a.exportName)}(app, logger);`)\n\t\t\t.join('\\n');\n\n\t\treturn `/**\n * Minimal-tier endpoint index (${tierEndpoints.length} endpoints)\n * Near-raw-Hono performance\n */\nimport type { Hono } from 'hono';\nimport type { Logger } from '@geekmidas/logger';\n${imports}\n\nexport function setupMinimalEndpoints(\n app: Hono,\n logger: Logger,\n): void {\n${calls}\n}\n`;\n\t}\n\n\tif (tier === 'standard') {\n\t\tconst calls = tierEndpoints\n\t\t\t.map(\n\t\t\t\t(a) =>\n\t\t\t\t\t` setup${capitalize(a.exportName)}(app, serviceDiscovery, logger);`,\n\t\t\t)\n\t\t\t.join('\\n');\n\n\t\treturn `/**\n * Standard-tier endpoint index (${tierEndpoints.length} endpoints)\n * Auth and/or services enabled\n */\nimport type { Hono } from 'hono';\nimport type { Logger } from '@geekmidas/logger';\nimport type { ServiceDiscovery } from '@geekmidas/services';\n${imports}\n\nexport function setupStandardEndpoints(\n app: Hono,\n serviceDiscovery: ServiceDiscovery<any, any>,\n logger: Logger,\n): void {\n${calls}\n}\n`;\n\t}\n\n\t// full\n\tconst calls = tierEndpoints\n\t\t.map(\n\t\t\t(a) =>\n\t\t\t\t` setup${capitalize(a.exportName)}(app, serviceDiscovery, openApiOptions);`,\n\t\t)\n\t\t.join('\\n');\n\n\treturn `/**\n * Full-tier endpoint index (${tierEndpoints.length} endpoints)\n * Complex features: audits, RLS, rate limiting\n */\nimport type { Hono } from 'hono';\nimport type { ServiceDiscovery } from '@geekmidas/services';\n${imports}\n\nexport function setupFullEndpoints(\n app: Hono,\n serviceDiscovery: ServiceDiscovery<any, any>,\n enableOpenApi: boolean,\n): void {\n const openApiOptions: any = enableOpenApi ? {\n docsPath: '/__docs',\n openApiOptions: {\n title: 'API Documentation',\n version: '1.0.0',\n description: 'Generated API documentation'\n }\n } : { docsPath: false };\n\n${calls}\n}\n`;\n}\n\n/**\n * Generate validator imports for a single endpoint\n */\nfunction generateValidatorImportsForEndpoint(\n\tanalysis: EndpointAnalysis,\n): string {\n\tconst imports: string[] = [];\n\tif (analysis.features.hasBodyValidation) imports.push('validateBody');\n\tif (analysis.features.hasQueryValidation) imports.push('validateQuery');\n\tif (analysis.features.hasParamValidation) imports.push('validateParams');\n\n\tif (imports.length === 0) return '';\n\treturn `import { ${imports.join(', ')} } from '../validators.js';`;\n}\n\n/**\n * Capitalize first letter\n */\nfunction capitalize(str: string): string {\n\treturn str.charAt(0).toUpperCase() + str.slice(1);\n}\n\n/**\n * Generate all endpoint files with nested folder structure (per-endpoint files)\n */\nexport function generateEndpointFilesNested(\n\tanalyses: EndpointAnalysis[],\n\tendpointImports: EndpointImportInfo[],\n): GeneratedEndpointFilesNested {\n\tconst files: GeneratedEndpointFilesNested = {\n\t\t'validators.ts': generateValidatorsFile(analyses),\n\t\t'minimal/index.ts': generateTierIndexFile('minimal', analyses),\n\t\t'standard/index.ts': generateTierIndexFile('standard', analyses),\n\t\t'full/index.ts': generateTierIndexFile('full', analyses),\n\t\t'index.ts': generateNestedIndexFile(analyses),\n\t};\n\n\t// Generate individual endpoint files\n\tfor (const analysis of analyses) {\n\t\tconst endpointImport = endpointImports.find(\n\t\t\t(i) => i.exportName === analysis.exportName,\n\t\t);\n\t\tif (!endpointImport) continue;\n\n\t\tconst fileName = `${analysis.tier}/${analysis.exportName}.ts`;\n\n\t\tswitch (analysis.tier) {\n\t\t\tcase 'minimal':\n\t\t\t\tfiles[fileName] = generateMinimalEndpointFile(analysis, endpointImport);\n\t\t\t\tbreak;\n\t\t\tcase 'standard':\n\t\t\t\tfiles[fileName] = generateStandardEndpointFile(\n\t\t\t\t\tanalysis,\n\t\t\t\t\tendpointImport,\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase 'full':\n\t\t\t\tfiles[fileName] = generateFullEndpointFile(analysis, endpointImport);\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn files;\n}\n\n/**\n * Generate index.ts for nested structure\n */\nfunction generateNestedIndexFile(analyses: EndpointAnalysis[]): string {\n\tconst minimalCount = analyses.filter((a) => a.tier === 'minimal').length;\n\tconst standardCount = analyses.filter((a) => a.tier === 'standard').length;\n\tconst fullCount = analyses.filter((a) => a.tier === 'full').length;\n\n\treturn `/**\n * Generated optimized endpoints\n *\n * Build-time optimization tiers:\n * - minimal: ${minimalCount} endpoints (near-raw-Hono)\n * - standard: ${standardCount} endpoints (auth/services)\n * - full: ${fullCount} endpoints (audits/rls/rate-limit)\n */\nimport type { EnvironmentParser } from '@geekmidas/envkit';\nimport type { Logger } from '@geekmidas/logger';\nimport type { Hono } from 'hono';\nimport { ServiceDiscovery } from '@geekmidas/services';\nimport { setupMinimalEndpoints } from './minimal/index.js';\nimport { setupStandardEndpoints } from './standard/index.js';\nimport { setupFullEndpoints } from './full/index.js';\n\nexport async function setupEndpoints(\n app: Hono,\n envParser: EnvironmentParser<any>,\n logger: Logger,\n enableOpenApi: boolean = false,\n): Promise<void> {\n const serviceDiscovery = ServiceDiscovery.getInstance(envParser);\n\n // Minimal handlers (${minimalCount} endpoints) - near-raw-Hono performance\n setupMinimalEndpoints(app, logger);\n\n // Standard handlers (${standardCount} endpoints) - auth/services\n setupStandardEndpoints(app, serviceDiscovery, logger);\n\n // Full handlers (${fullCount} endpoints) - audits/rls/rate-limit\n setupFullEndpoints(app, serviceDiscovery, enableOpenApi);\n\n // Add Swagger UI if OpenAPI is enabled\n if (enableOpenApi) {\n try {\n const { swaggerUI } = await import('@hono/swagger-ui');\n app.get('/__docs/ui', swaggerUI({ url: '/__docs' }));\n } catch {\n // @hono/swagger-ui not installed, skip Swagger UI\n }\n }\n}\n`;\n}\n","import { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join, relative } from 'node:path';\nimport { Endpoint } from '@geekmidas/constructs/endpoints';\nimport {\n\tanalyzeEndpoint,\n\ttype EndpointAnalysis,\n\tsummarizeAnalysis,\n} from '../build/endpoint-analyzer';\nimport {\n\ttype EndpointImportInfo,\n\tgenerateEndpointFilesNested,\n} from '../build/handler-templates';\nimport type { BuildContext } from '../build/types';\nimport type { LegacyProvider, RouteInfo } from '../types';\nimport type { StorageDrivers } from './drivers';\nimport {\n\tConstructGenerator,\n\ttype GeneratedConstruct,\n\ttype GeneratorOptions,\n} from './Generator';\n\n/**\n * How a generated entry gets its logger and environment parser.\n *\n * From the surface it serves, when there is one: the surface holds both as\n * *objects*, and discovery already recorded which module declares it, so the\n * entry imports that module and reads them off it.\n *\n * The alternative — the shape this replaces — was naming both in config as\n * module paths, `envParser: './config/env#envParser'`, and printing an import\n * for each. Two strings per app, checked by nothing, describing files the\n * surface was already holding the contents of.\n *\n * Falls back to the configured paths for an app that declares no surface.\n */\nexport function runtimeFor(\n\tcontext: BuildContext,\n\tfromDir: string,\n\t/**\n\t * The construct that owns the thing being generated.\n\t *\n\t * An endpoint's surface, or the worker that handed out the cron factory.\n\t * Omitted for the app entry itself, which belongs to the primary surface.\n\t */\n\towner?: string,\n): { imports: string; bindings: string } {\n\tconst module = owner\n\t\t? (context.owners?.[owner] ?? context.surface?.module)\n\t\t: context.surface?.module;\n\n\tif (module) {\n\t\tconst specifier = importSpecifier(fromDir, module.specifier);\n\n\t\t// Bound to the same names the rest of the entry already uses, so what\n\t\t// changes is where they come from and nothing else.\n\t\treturn {\n\t\t\timports: `import { ${module.exportName} as __surface } from '${specifier}';`,\n\t\t\tbindings: [\n\t\t\t\t'// What this construct was declared with. Objects, not module paths:',\n\t\t\t\t'// the entry imports the owner, so there is nothing to print.',\n\t\t\t\t'const envParser = __surface.envParser;',\n\t\t\t\t'const logger = __surface.logger;',\n\t\t\t].join('\\n'),\n\t\t};\n\t}\n\n\t// No fallback. Every runnable comes from a factory a `RestApi` or a `Worker`\n\t// handed out, and that construct holds the objects — so there is no longer a\n\t// case where the only thing we know about a logger is a module path somebody\n\t// typed into config.\n\tthrow new Error(\n\t\t`No owner to take a logger and an environment parser from${\n\t\t\towner ? ` for \"${owner}\"` : ''\n\t\t}. Build it from a \\`RestApi\\` or a \\`Worker\\`.`,\n\t);\n}\n\n/** A relative specifier ESM will accept: always prefixed, never bare. */\nfunction importSpecifier(fromDir: string, target: string): string {\n\tconst rel = relative(fromDir, target).replace(/\\.ts$/, '.js');\n\n\treturn rel.startsWith('.') ? rel : `./${rel}`;\n}\n\n/**\n * CORS for a surface, derived rather than hand-written.\n *\n * Who may call a surface is already in the graph — every construct that\n * declared an edge to it — and reaches the process as one comma-separated\n * value. An application that maintained this by hand maintained a list that was\n * wrong in two directions at once: it trusted origins nothing was serving, and\n * it could not name hosts a deploy had not chosen yet.\n *\n * Only the parts a graph cannot answer are configurable, and every one has a\n * default: a surface that says nothing about CORS still gets it.\n */\nexport function corsFor(surface: BuildContext['surface']): {\n\timports: string;\n\tsetup: string;\n} {\n\tif (!surface) return { imports: '', setup: '' };\n\n\tconst cors = surface.cors ?? {};\n\tconst allowHeaders = [\n\t\t'content-type',\n\t\t'authorization',\n\t\t...(cors.allowHeaders ?? []),\n\t];\n\n\treturn {\n\t\timports: `import { cors } from 'hono/cors';`,\n\t\tsetup: `\n // Who may call ${surface.id}, read off the graph rather than listed here.\n {\n const { origins } = envParser\n .create((get) => ({\n origins: get('${surface.trustedOriginsKey}')\n .string()\n .default('')\n .transform((value: string) =>\n value.split(',').map((origin) => origin.trim()).filter(Boolean),\n ),\n }))\n .parse();\n\n honoApp.use('*', cors({\n origin: origins,\n allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],\n allowHeaders: ${JSON.stringify(allowHeaders)},${\n\t\t\t\tcors.exposeHeaders\n\t\t\t\t\t? `\\n exposeHeaders: ${JSON.stringify(cors.exposeHeaders)},`\n\t\t\t\t\t: ''\n\t\t\t}\n credentials: ${cors.credentials ?? true},\n maxAge: ${cors.maxAge ?? 86400},\n }));\n }\n`,\n\t};\n}\n\nexport class EndpointGenerator extends ConstructGenerator<\n\tEndpoint<any, any, any, any, any, any, any, any, any, any, any, any>,\n\tRouteInfo[]\n> {\n\tisConstruct(\n\t\tvalue: any,\n\t): value is Endpoint<\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany\n\t> {\n\t\treturn Endpoint.isEndpoint(value);\n\t}\n\n\tasync build(\n\t\tcontext: BuildContext,\n\t\tconstructs: GeneratedConstruct<\n\t\t\tEndpoint<any, any, any, any, any, any, any, any, any, any, any, any>\n\t\t>[],\n\t\toutputDir: string,\n\t\toptions?: GeneratorOptions,\n\t): Promise<RouteInfo[]> {\n\t\tconst provider = options?.provider || 'aws-apigatewayv2';\n\t\tconst enableOpenApi = options?.enableOpenApi || false;\n\t\tconst logger = console;\n\t\tconst routes: RouteInfo[] = [];\n\n\t\tif (constructs.length === 0) {\n\t\t\treturn routes;\n\t\t}\n\n\t\tif (provider === 'server') {\n\t\t\t// Generate endpoints.ts and app.ts\n\t\t\tawait this.generateEndpointsFile(outputDir, constructs, context);\n\t\t\tconst appFile = await this.generateAppFile(outputDir, context);\n\n\t\t\troutes.push({\n\t\t\t\tpath: '*',\n\t\t\t\tmethod: 'ALL',\n\t\t\t\thandler: relative(process.cwd(), appFile),\n\t\t\t\tauthorizer: 'none',\n\t\t\t});\n\n\t\t\tlogger.log(\n\t\t\t\t`Generated server with ${constructs.length} endpoints${enableOpenApi ? ' (OpenAPI enabled)' : ''}`,\n\t\t\t);\n\t\t} else if (provider === 'aws-lambda') {\n\t\t\t// For aws-lambda, create routes subdirectory\n\t\t\tconst routesDir = join(outputDir, 'routes');\n\t\t\tawait mkdir(routesDir, { recursive: true });\n\n\t\t\t// Generate individual handlers for API Gateway routes\n\t\t\tfor (const { key, construct, path } of constructs) {\n\t\t\t\tconst handlerFile = await this.generateHandlerFile(\n\t\t\t\t\troutesDir,\n\t\t\t\t\tpath.relative,\n\t\t\t\t\tkey,\n\t\t\t\t\t'aws-apigatewayv2',\n\t\t\t\t\tconstruct,\n\t\t\t\t\tcontext,\n\t\t\t\t);\n\n\t\t\t\tconst routeInfo: RouteInfo = {\n\t\t\t\t\tpath: construct._path,\n\t\t\t\t\tmethod: construct.method,\n\t\t\t\t\thandler: relative(process.cwd(), handlerFile).replace(\n\t\t\t\t\t\t/\\.ts$/,\n\t\t\t\t\t\t'.handler',\n\t\t\t\t\t),\n\t\t\t\t\ttimeout: construct.timeout,\n\t\t\t\t\tmemorySize: construct.memorySize,\n\t\t\t\t\tenvironment: await construct.getEnvironment({\n\t\t\t\t\t\tmarkOptional: context.markOptional,\n\t\t\t\t\t}),\n\t\t\t\t\tdependencies: construct.constructs,\n\t\t\t\t\tauthorizer: construct.authorizer?.name ?? 'none',\n\t\t\t\t};\n\n\t\t\t\troutes.push(routeInfo);\n\t\t\t\tlogger.log(\n\t\t\t\t\t`Generated handler for ${routeInfo.method} ${routeInfo.path}`,\n\t\t\t\t);\n\t\t\t}\n\t\t} else {\n\t\t\t// Generate individual handler files for AWS API Gateway providers\n\t\t\tfor (const { key, construct, path } of constructs) {\n\t\t\t\tconst handlerFile = await this.generateHandlerFile(\n\t\t\t\t\toutputDir,\n\t\t\t\t\tpath.relative,\n\t\t\t\t\tkey,\n\t\t\t\t\tprovider,\n\t\t\t\t\tconstruct,\n\t\t\t\t\tcontext,\n\t\t\t\t);\n\n\t\t\t\tconst routeInfo: RouteInfo = {\n\t\t\t\t\tpath: construct._path,\n\t\t\t\t\tmethod: construct.method,\n\t\t\t\t\thandler: relative(process.cwd(), handlerFile).replace(\n\t\t\t\t\t\t/\\.ts$/,\n\t\t\t\t\t\t'.handler',\n\t\t\t\t\t),\n\t\t\t\t\ttimeout: construct.timeout,\n\t\t\t\t\tmemorySize: construct.memorySize,\n\t\t\t\t\tenvironment: await construct.getEnvironment({\n\t\t\t\t\t\tmarkOptional: context.markOptional,\n\t\t\t\t\t}),\n\t\t\t\t\tdependencies: construct.constructs,\n\t\t\t\t\tauthorizer: construct.authorizer?.name ?? 'none',\n\t\t\t\t};\n\n\t\t\t\troutes.push(routeInfo);\n\t\t\t\tlogger.log(\n\t\t\t\t\t`Generated handler for ${routeInfo.method} ${routeInfo.path}`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn routes;\n\t}\n\n\tprivate async generateHandlerFile(\n\t\toutputDir: string,\n\t\tsourceFile: string,\n\t\texportName: string,\n\t\tprovider: LegacyProvider,\n\t\t_endpoint: Endpoint<\n\t\t\tany,\n\t\t\tany,\n\t\t\tany,\n\t\t\tany,\n\t\t\tany,\n\t\t\tany,\n\t\t\tany,\n\t\t\tany,\n\t\t\tany,\n\t\t\tany,\n\t\t\tany,\n\t\t\tany\n\t\t>,\n\t\tcontext: BuildContext,\n\t): Promise<string> {\n\t\tconst handlerFileName = `${exportName}.ts`;\n\t\tconst handlerPath = join(outputDir, handlerFileName);\n\n\t\tconst relativePath = relative(dirname(handlerPath), sourceFile);\n\t\tconst importPath = relativePath.replace(/\\.ts$/, '.js');\n\n\t\tlet content: string;\n\n\t\tswitch (provider) {\n\t\t\tcase 'aws-apigatewayv1':\n\t\t\t\tcontent = this.generateAWSApiGatewayV1Handler(\n\t\t\t\t\timportPath,\n\t\t\t\t\texportName,\n\t\t\t\t\tcontext.storageDrivers,\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase 'aws-apigatewayv2':\n\t\t\t\tcontent = this.generateAWSApiGatewayV2Handler(\n\t\t\t\t\timportPath,\n\t\t\t\t\texportName,\n\t\t\t\t\tcontext.storageDrivers,\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase 'server':\n\t\t\t\tcontent = this.generateServerHandler(importPath, exportName);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tthrow new Error(`Unsupported provider: ${provider}`);\n\t\t}\n\n\t\tawait writeFile(handlerPath, content);\n\t\treturn handlerPath;\n\t}\n\n\tprivate async generateEndpointsFile(\n\t\toutputDir: string,\n\t\tendpoints: GeneratedConstruct<\n\t\t\tEndpoint<any, any, any, any, any, any, any, any, any, any, any, any>\n\t\t>[],\n\t\tcontext: BuildContext,\n\t): Promise<string> {\n\t\tconst endpointsFileName = 'endpoints.ts';\n\t\tconst endpointsPath = join(outputDir, endpointsFileName);\n\n\t\t// Group imports by file\n\t\tconst importsByFile = new Map<string, string[]>();\n\n\t\tfor (const { path, key } of endpoints) {\n\t\t\tconst relativePath = relative(dirname(endpointsPath), path.relative);\n\t\t\tconst importPath = relativePath.replace(/\\.ts$/, '.js');\n\n\t\t\tif (!importsByFile.has(importPath)) {\n\t\t\t\timportsByFile.set(importPath, []);\n\t\t\t}\n\t\t\timportsByFile.get(importPath)?.push(key);\n\t\t}\n\n\t\t// Generate import statements for endpoints\n\t\tconst endpointImports = Array.from(importsByFile.entries())\n\t\t\t.map(\n\t\t\t\t([importPath, exports]) =>\n\t\t\t\t\t`import { ${exports.join(', ')} } from '${importPath}';`,\n\t\t\t)\n\t\t\t.join('\\n');\n\n\t\tconst allExportNames = endpoints.map(({ key }) => key);\n\n\t\t// Check if we should use optimized handler generation\n\t\tif (context.production?.enabled && context.production.optimizedHandlers) {\n\t\t\treturn this.generateOptimizedEndpointsFile(\n\t\t\t\tendpointsPath,\n\t\t\t\tendpoints,\n\t\t\t\tendpointImports,\n\t\t\t\tallExportNames,\n\t\t\t);\n\t\t}\n\n\t\t// Standard generation (development or optimizedHandlers: false)\n\t\tconst content = `import type { EnvironmentParser } from '@geekmidas/envkit';\nimport type { Logger } from '@geekmidas/logger';\nimport { HonoEndpoint } from '@geekmidas/constructs/hono';\nimport { Endpoint } from '@geekmidas/constructs/endpoints';\nimport { ServiceDiscovery } from '@geekmidas/services';\nimport type { Hono } from 'hono';\n${endpointImports}\n\nexport const endpoints: Endpoint<any, any, any, any, any, any, any, any, any, any, any, any>[] = [\n ${allExportNames.join(',\\n ')}\n];\n\nexport async function setupEndpoints(\n app: Hono,\n envParser: EnvironmentParser<any>,\n logger: Logger,\n enableOpenApi: boolean = true,\n): Promise<void> {\n const serviceDiscovery = ServiceDiscovery.getInstance(envParser);\n\n // Configure OpenAPI options based on enableOpenApi flag\n const openApiOptions: any = enableOpenApi ? {\n docsPath: '/__docs',\n openApiOptions: {\n title: 'API Documentation',\n version: '1.0.0',\n description: 'Generated API documentation'\n }\n } : { docsPath: false };\n\n HonoEndpoint.addRoutes(endpoints, serviceDiscovery, app, openApiOptions);\n\n // Add Swagger UI if OpenAPI is enabled\n if (enableOpenApi) {\n try {\n const { swaggerUI } = await import('@hono/swagger-ui');\n app.get('/__docs/ui', swaggerUI({ url: '/__docs' }));\n } catch {\n // @hono/swagger-ui not installed, skip Swagger UI\n }\n }\n}\n`;\n\n\t\tawait writeFile(endpointsPath, content);\n\n\t\treturn endpointsPath;\n\t}\n\n\t/**\n\t * Generate optimized endpoints files with nested folder structure (per-endpoint files)\n\t */\n\tprivate async generateOptimizedEndpointsFile(\n\t\tendpointsPath: string,\n\t\tendpoints: GeneratedConstruct<\n\t\t\tEndpoint<any, any, any, any, any, any, any, any, any, any, any, any>\n\t\t>[],\n\t\t_endpointImports: string,\n\t\t_allExportNames: string[],\n\t): Promise<string> {\n\t\tconst logger = console;\n\t\tconst outputDir = dirname(endpointsPath);\n\n\t\t// Create endpoints subdirectory with tier folders\n\t\tconst endpointsDir = join(outputDir, 'endpoints');\n\t\tawait mkdir(join(endpointsDir, 'minimal'), { recursive: true });\n\t\tawait mkdir(join(endpointsDir, 'standard'), { recursive: true });\n\t\tawait mkdir(join(endpointsDir, 'full'), { recursive: true });\n\n\t\t// Analyze each endpoint\n\t\tconst analyses: EndpointAnalysis[] = endpoints.map(({ key, construct }) =>\n\t\t\tanalyzeEndpoint(construct, key),\n\t\t);\n\n\t\t// Build endpoint import info with correct relative paths from each tier folder\n\t\t// Use paths relative to the tier folder (e.g., endpoints/standard/)\n\t\tconst endpointImports: EndpointImportInfo[] = endpoints.map(\n\t\t\t({ key, path }) => {\n\t\t\t\t// Calculate relative path from tier folder (one level deeper than endpointsDir)\n\t\t\t\tconst tierDir = join(endpointsDir, 'standard'); // Use any tier as reference - same depth\n\t\t\t\tconst relativePath = relative(tierDir, path.relative);\n\t\t\t\tconst importPath = relativePath.replace(/\\.ts$/, '.js');\n\t\t\t\treturn { exportName: key, importPath };\n\t\t\t},\n\t\t);\n\n\t\t// Log analysis summary\n\t\tconst summary = summarizeAnalysis(analyses);\n\t\tlogger.log(`\\n📊 Endpoint Analysis:`);\n\t\tlogger.log(` Total: ${summary.total} endpoints`);\n\t\tlogger.log(\n\t\t\t` - Minimal (near-raw-Hono): ${summary.byTier.minimal} endpoints`,\n\t\t);\n\t\tlogger.log(\n\t\t\t` - Standard (auth/services): ${summary.byTier.standard} endpoints`,\n\t\t);\n\t\tlogger.log(\n\t\t\t` - Full (audits/rls/rate-limit): ${summary.byTier.full} endpoints`,\n\t\t);\n\n\t\t// Generate files with nested structure (per-endpoint files)\n\t\tconst files = generateEndpointFilesNested(analyses, endpointImports);\n\n\t\t// Write each file, creating directories as needed\n\t\tfor (const [filename, content] of Object.entries(files)) {\n\t\t\tconst filePath = join(endpointsDir, filename);\n\t\t\tawait mkdir(dirname(filePath), { recursive: true });\n\t\t\tawait writeFile(filePath, content);\n\t\t}\n\n\t\t// Count files by type\n\t\tconst endpointFiles = Object.keys(files).filter(\n\t\t\t(f) => !f.endsWith('index.ts') && !f.endsWith('validators.ts'),\n\t\t).length;\n\t\tconst indexFiles = Object.keys(files).filter((f) =>\n\t\t\tf.endsWith('index.ts'),\n\t\t).length;\n\n\t\tlogger.log(\n\t\t\t` Generated ${endpointFiles} endpoint files + ${indexFiles} index files + validators.ts`,\n\t\t);\n\n\t\t// Return path to index file\n\t\treturn join(endpointsDir, 'index.ts');\n\t}\n\n\tprivate async generateAppFile(\n\t\toutputDir: string,\n\t\tcontext: BuildContext,\n\t): Promise<string> {\n\t\t// Use production generator if in production mode\n\t\tif (context.production?.enabled) {\n\t\t\treturn this.generateProductionAppFile(outputDir, context);\n\t\t}\n\n\t\tconst appFileName = 'app.ts';\n\t\tconst appPath = join(outputDir, appFileName);\n\n\t\t// The logger and the env parser, from the surface that already holds them.\n\t\tconst runtime = runtimeFor(context, dirname(appPath));\n\n\t\t// Generate telescope imports and setup if enabled\n\t\tconst telescopeEnabled = context.telescope?.enabled;\n\t\tconst telescopeWebSocketEnabled = context.telescope?.websocket;\n\t\tconst usesExternalTelescope = !!context.telescope?.telescopePath;\n\n\t\t// Generate studio imports and setup if enabled\n\t\tconst studioEnabled = context.studio?.enabled;\n\t\tconst usesExternalStudio = !!context.studio?.studioPath;\n\t\t// Studio from the declared database rather than from a module someone\n\t\t// wrote to resolve one. Every input it takes is derivable: the client\n\t\t// from the construct, the rest defaults.\n\t\tconst studioFromDatabase =\n\t\t\t!usesExternalStudio && !!context.studio?.database;\n\n\t\t// Generate imports based on whether telescope is external or inline\n\t\tconst telescopeFromSurface =\n\t\t\t!!context.surface?.module && !usesExternalTelescope;\n\n\t\tlet telescopeImports = '';\n\t\tif (telescopeEnabled) {\n\t\t\tif (telescopeFromSurface) {\n\t\t\t\t// The entry already imports the surface, which may or may not have\n\t\t\t\t// been given a Telescope — an object, so only the running entry can\n\t\t\t\t// tell. The inline one stands in when it was not.\n\t\t\t\ttelescopeImports = `import { Telescope, InMemoryStorage } from '@geekmidas/telescope';\nimport { createMiddleware, createUI } from '@geekmidas/telescope/hono';`;\n\t\t\t} else if (usesExternalTelescope) {\n\t\t\t\tconst relativeTelescopePath = relative(\n\t\t\t\t\tdirname(appPath),\n\t\t\t\t\tcontext.telescope?.telescopePath!,\n\t\t\t\t);\n\t\t\t\ttelescopeImports = `import ${context.telescope?.telescopeImportPattern} from '${relativeTelescopePath}';\nimport { createMiddleware, createUI } from '@geekmidas/telescope/hono';`;\n\t\t\t} else {\n\t\t\t\ttelescopeImports = `import { Telescope, InMemoryStorage } from '@geekmidas/telescope';\nimport { createMiddleware, createUI } from '@geekmidas/telescope/hono';`;\n\t\t\t}\n\t\t}\n\n\t\t// Generate imports for studio\n\t\tlet studioImports = '';\n\t\tif (studioEnabled) {\n\t\t\tif (usesExternalStudio) {\n\t\t\t\tconst relativeStudioPath = relative(\n\t\t\t\t\tdirname(appPath),\n\t\t\t\t\tcontext.studio?.studioPath!,\n\t\t\t\t);\n\t\t\t\tstudioImports = `import ${context.studio?.studioImportPattern} from '${relativeStudioPath}';\nimport { createStudioApp } from '@geekmidas/studio/server/hono';`;\n\t\t\t} else if (studioFromDatabase) {\n\t\t\t\tconst dbSpecifier = importSpecifier(\n\t\t\t\t\tdirname(appPath),\n\t\t\t\t\tcontext.studio!.database!.specifier,\n\t\t\t\t);\n\t\t\t\tstudioImports = `import { snifferContext } from '@geekmidas/constructs';\nimport { Direction, InMemoryMonitoringStorage, Studio } from '@geekmidas/studio';\nimport { ${context.studio!.database!.exportName} as __studioDb } from '${dbSpecifier}';\nimport { createStudioApp } from '@geekmidas/studio/server/hono';`;\n\t\t\t} else {\n\t\t\t\tstudioImports = '';\n\t\t\t}\n\t\t}\n\n\t\t// Generate imports for server hooks\n\t\tconst cors = corsFor(context.surface);\n\t\tlet hooksImports = '';\n\t\tlet beforeSetupCall = '';\n\t\tlet afterSetupCall = '';\n\t\tif (context.hooks?.serverHooksPath) {\n\t\t\tconst relativeHooksPath = relative(\n\t\t\t\tdirname(appPath),\n\t\t\t\tcontext.hooks.serverHooksPath,\n\t\t\t);\n\t\t\thooksImports = `import * as serverHooks from '${relativeHooksPath}';`;\n\t\t\tbeforeSetupCall = `\n // Call beforeSetup hook if defined\n if (typeof serverHooks.beforeSetup === 'function') {\n await serverHooks.beforeSetup(honoApp, { envParser, logger });\n }\n`;\n\t\t\tafterSetupCall = `\n // Call afterSetup hook if defined\n if (typeof serverHooks.afterSetup === 'function') {\n await serverHooks.afterSetup(honoApp, { envParser, logger });\n }\n`;\n\t\t}\n\n\t\tconst telescopeWebSocketSetupCode = telescopeWebSocketEnabled\n\t\t\t? `\n // Setup WebSocket for real-time telescope updates\n try {\n const { createNodeWebSocket } = await import('@hono/node-ws');\n const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app: honoApp });\n // Add WebSocket route directly to main app (sub-app routes don't support WS upgrade)\n honoApp.get('${context.telescope?.path}/ws', upgradeWebSocket(() => ({\n onOpen: (_event: Event, ws: any) => {\n telescope.addWsClient(ws);\n },\n onClose: (_event: Event, ws: any) => {\n telescope.removeWsClient(ws);\n },\n onMessage: (event: MessageEvent, ws: any) => {\n try {\n const data = JSON.parse(event.data);\n if (data.type === 'ping') {\n ws.send(JSON.stringify({ type: 'pong' }));\n }\n } catch {\n // Ignore invalid messages\n }\n },\n })));\n // Store injectWebSocket for server entry to call after serve()\n (honoApp as any).__injectWebSocket = injectWebSocket;\n logger.info('Telescope WebSocket enabled');\n } catch (e) {\n logger.warn({ error: e }, 'WebSocket support not available - install @hono/node-ws for real-time updates');\n }\n`\n\t\t\t: '';\n\n\t\t// Generate telescope setup - either use external instance or create inline\n\t\tlet telescopeSetup = '';\n\t\tif (telescopeEnabled) {\n\t\t\tif (telescopeFromSurface || usesExternalTelescope) {\n\t\t\t\t// Use external telescope instance - no need to create one\n\t\t\t\ttelescopeSetup = `\n${telescopeWebSocketSetupCode}\n // Add telescope middleware (before endpoints to capture all requests)\n honoApp.use('*', createMiddleware(telescope));\n\n // Mount telescope UI\n const telescopeUI = createUI(telescope);\n honoApp.route('${context.telescope?.path}', telescopeUI);\n`;\n\t\t\t} else {\n\t\t\t\t// Create inline telescope instance\n\t\t\t\ttelescopeSetup = `\n // Setup Telescope for debugging/monitoring\n const telescopeStorage = new InMemoryStorage({ maxEntries: ${context.telescope?.maxEntries} });\n const telescope = new Telescope({\n enabled: true,\n path: '${context.telescope?.path}',\n ignorePatterns: ${JSON.stringify(context.telescope?.ignore)},\n recordBody: ${context.telescope?.recordBody},\n storage: telescopeStorage,\n });\n${telescopeWebSocketSetupCode}\n // Add telescope middleware (before endpoints to capture all requests)\n honoApp.use('*', createMiddleware(telescope));\n\n // Mount telescope UI\n const telescopeUI = createUI(telescope);\n honoApp.route('${context.telescope?.path}', telescopeUI);\n`;\n\t\t\t}\n\t\t}\n\n\t\t// Generate studio setup - requires external instance\n\t\tlet studioSetup = '';\n\t\tif (studioEnabled && studioFromDatabase) {\n\t\t\tstudioSetup = `\n // Studio, built from the declared database. The client comes from the\n // construct, so what you inspect is by definition what the handlers write to.\n const studio = new Studio({\n monitoring: { storage: new InMemoryMonitoringStorage({ maxEntries: 100 }) },\n data: {\n db: await __studioDb.service.register({ envParser, context: snifferContext }),\n cursor: { field: 'id', direction: Direction.Desc },\n },\n enabled: true,\n });\n\n const studioApp = createStudioApp(studio);\n honoApp.route('${context.studio?.path}', studioApp);\n`;\n\t\t} else if (studioEnabled && usesExternalStudio) {\n\t\t\tstudioSetup = `\n // Mount Studio data browser UI\n const studioApp = createStudioApp(studio);\n honoApp.route('${context.studio?.path}', studioApp);\n`;\n\t\t}\n\n\t\tconst content = `/**\n * Generated server application\n *\n * ⚠️ WARNING: This is for LOCAL DEVELOPMENT ONLY\n * The subscriber polling mechanism is not production-ready.\n * For production, use AWS Lambda with SQS/SNS event sources.\n */\nimport { Hono } from 'hono';\nimport type { Hono as HonoType } from 'hono';\nimport { setupEndpoints } from './endpoints.js';\nimport { setupSubscribers } from './subscribers.js';\nimport { setupQueues } from './queues.js';\nimport { setupCrons } from './crons.js';\n${runtime.imports}\n${telescopeImports}\n${studioImports}\n${hooksImports}\n${cors.imports}\n${context.storageDrivers?.imports ?? ''}\n\n${runtime.bindings}\n${\n\ttelescopeEnabled && telescopeFromSurface\n\t\t? `// The surface's own Telescope, or the one \\`telescope\\` config describes.\nconst telescope =\n __surface.telescope ??\n new Telescope({\n enabled: true,\n path: '${context.telescope?.path}',\n ignorePatterns: ${JSON.stringify(context.telescope?.ignore)},\n recordBody: ${context.telescope?.recordBody},\n storage: new InMemoryStorage({ maxEntries: ${context.telescope?.maxEntries} }),\n });`\n\t\t: ''\n}\n\n${\n\tcontext.storageDrivers?.setup\n\t\t? `// Which storage drivers exist is the entry point's decision: the scheme in\n// each injected URL picks one, so no construct and no application module names\n// a provider.\n${context.storageDrivers.setup}\n`\n\t\t: ''\n}\nexport interface ServerApp {\n app: HonoType;\n start: (options?: {\n port?: number;\n serve: (app: HonoType, port: number) => void | Promise<void>;\n }) => Promise<void>;\n}\n\n/**\n * Create and configure the Hono application\n *\n * @param app - Optional Hono app instance to configure (creates new one if not provided)\n * @param enableOpenApi - Enable OpenAPI documentation (default: true)\n * @returns Server app with configured Hono app and start function\n *\n * @example\n * // With Bun\n * import { createApp } from './.gkm/server/app.js';\n *\n * const { app, start } = await createApp();\n *\n * await start({\n * port: 3000,\n * serve: (app, port) => {\n * Bun.serve({ port, fetch: app.fetch });\n * }\n * });\n *\n * @example\n * // With Node.js (using @hono/node-server)\n * import { serve } from '@hono/node-server';\n * import { createApp } from './.gkm/server/app.js';\n *\n * const { app, start } = await createApp();\n *\n * await start({\n * port: 3000,\n * serve: (app, port) => {\n * serve({ fetch: app.fetch, port });\n * }\n * });\n */\nexport async function createApp(app?: HonoType, enableOpenApi: boolean = true): Promise<ServerApp> {\n const honoApp = app || new Hono();\n${telescopeSetup}${cors.setup}${beforeSetupCall}${studioSetup}\n // Setup HTTP endpoints\n await setupEndpoints(honoApp, envParser, logger, enableOpenApi);\n${afterSetupCall}\n\n return {\n app: honoApp,\n async start(options) {\n if (!options?.serve) {\n throw new Error(\n 'serve function is required. Pass a serve function for your runtime:\\\\n' +\n ' - Bun: (app, port) => Bun.serve({ port, fetch: app.fetch })\\\\n' +\n ' - Node: (app, port) => serve({ fetch: app.fetch, port })'\n );\n }\n\n const port = options.port ?? 3000;\n\n // Mount pushed subscribers' routes and start polled ones. What comes\n // back subscribes the routes, once the server can answer them.\n const subscribeForPush = await setupSubscribers(honoApp, envParser, logger).catch((error) => {\n logger.error({ error }, 'Failed to start subscribers');\n return async (_port: number) => {};\n });\n\n // Start queue workers in background (non-blocking, local development only)\n await setupQueues(envParser, logger).catch((error) => {\n logger.error({ error }, 'Failed to start queue workers');\n });\n\n // Schedule this app's crons. Caught like the others: a scheduler that\n // cannot start is not a reason for the HTTP server not to.\n await setupCrons(envParser, logger).catch((error) => {\n logger.error({ error }, 'Failed to schedule crons');\n });\n\n logger.info({ port }, 'Starting server');\n\n // Start HTTP server using provided serve function\n await options.serve(honoApp, port);\n\n logger.info({ port }, 'Server started');\n\n await subscribeForPush(port);\n }\n };\n}\n\n// Default export for convenience\nexport default createApp;\n`;\n\n\t\tawait writeFile(appPath, content);\n\n\t\treturn appPath;\n\t}\n\n\t/**\n\t * One Lambda, one endpoint — and no environment import.\n\t *\n\t * The endpoint was built from its surface, so it carries the parser the\n\t * adaptor needs. This used to read\n\t * `import { envParser } from '../../config/env'`, a line the build wrote\n\t * because a generator can print a module specifier and cannot print an\n\t * object. Nothing has to be printed now, which is also why nothing has to be\n\t * named in config.\n\t */\n\tprivate generateAWSApiGatewayV1Handler(\n\t\timportPath: string,\n\t\texportName: string,\n\t\tdrivers?: StorageDrivers,\n\t): string {\n\t\treturn `import { AmazonApiGatewayV1Endpoint } from '@geekmidas/constructs/aws';\nimport { ${exportName} } from '${importPath}';\n${drivers?.imports ?? ''}\n${drivers?.setup ? `\\n// The handler registers the drivers its target needs.\\n${drivers.setup}\\n` : ''}\nconst adapter = new AmazonApiGatewayV1Endpoint(${exportName});\n\nexport const handler = adapter.handler;\n`;\n\t}\n\n\t/**\n\t * One Lambda, one endpoint — and no environment import.\n\t *\n\t * The endpoint was built from its surface, so it carries the parser the\n\t * adaptor needs. This used to read\n\t * `import { envParser } from '../../config/env'`, a line the build wrote\n\t * because a generator can print a module specifier and cannot print an\n\t * object. Nothing has to be printed now, which is also why nothing has to be\n\t * named in config.\n\t */\n\tprivate generateAWSApiGatewayV2Handler(\n\t\timportPath: string,\n\t\texportName: string,\n\t\tdrivers?: StorageDrivers,\n\t): string {\n\t\treturn `import { AmazonApiGatewayV2Endpoint } from '@geekmidas/constructs/aws';\nimport { ${exportName} } from '${importPath}';\n${drivers?.imports ?? ''}\n${drivers?.setup ? `\\n// The handler registers the drivers its target needs.\\n${drivers.setup}\\n` : ''}\nconst adapter = new AmazonApiGatewayV2Endpoint(${exportName});\n\nexport const handler = adapter.handler;\n`;\n\t}\n\n\tprivate generateServerHandler(\n\t\timportPath: string,\n\t\texportName: string,\n\t): string {\n\t\treturn `import { ${exportName} } from '${importPath}';\n\n// Server handler - implement based on your server framework\nexport const handler = ${exportName};\n`;\n\t}\n\n\t/**\n\t * Generate a production-optimized app.ts file\n\t * No dev tools (Telescope, Studio, WebSocket), includes health checks and graceful shutdown\n\t */\n\tprivate async generateProductionAppFile(\n\t\toutputDir: string,\n\t\tcontext: BuildContext,\n\t): Promise<string> {\n\t\tconst appFileName = 'app.ts';\n\t\tconst appPath = join(outputDir, appFileName);\n\n\t\t// The app entry belongs to the primary surface, so no owner is named.\n\t\tconst runtime = runtimeFor(context, dirname(appPath));\n\n\t\tconst production = context.production!;\n\t\tconst healthCheckPath = production.healthCheck;\n\t\tconst enableGracefulShutdown = production.gracefulShutdown;\n\t\tconst enableOpenApi = production.openapi;\n\t\tconst includeSubscribers = production.subscribers === 'include';\n\n\t\t// Generate imports for server hooks\n\t\tconst cors = corsFor(context.surface);\n\t\tlet hooksImports = '';\n\t\tlet beforeSetupCall = '';\n\t\tlet afterSetupCall = '';\n\t\tif (context.hooks?.serverHooksPath) {\n\t\t\tconst relativeHooksPath = relative(\n\t\t\t\tdirname(appPath),\n\t\t\t\tcontext.hooks.serverHooksPath,\n\t\t\t);\n\t\t\thooksImports = `import * as serverHooks from '${relativeHooksPath}';`;\n\t\t\tbeforeSetupCall = `\n // Call beforeSetup hook if defined\n if (typeof serverHooks.beforeSetup === 'function') {\n await serverHooks.beforeSetup(honoApp, { envParser, logger });\n }\n`;\n\t\t\tafterSetupCall = `\n // Call afterSetup hook if defined\n if (typeof serverHooks.afterSetup === 'function') {\n await serverHooks.afterSetup(honoApp, { envParser, logger });\n }\n`;\n\t\t}\n\n\t\t// Subscriber + queue setup code (background workers share the same flag)\n\t\tconst subscriberSetup = includeSubscribers\n\t\t\t? `\n // Mount pushed subscribers' routes and start polled ones. What comes\n // back subscribes the routes, once the server can answer them.\n const subscribeForPush = await setupSubscribers(honoApp, envParser, logger).catch((error) => {\n logger.error({ error }, 'Failed to start subscribers');\n return async (_port: number) => {};\n });\n\n // Start queue workers in background\n await setupQueues(envParser, logger).catch((error) => {\n logger.error({ error }, 'Failed to start queue workers');\n });\n\n // Schedule this app's crons. Caught like the others: a scheduler that\n // cannot start is not a reason for the HTTP server not to.\n await setupCrons(envParser, logger).catch((error) => {\n logger.error({ error }, 'Failed to schedule crons');\n });\n`\n\t\t\t: '';\n\n\t\tconst subscriberImport = includeSubscribers\n\t\t\t? `import { setupSubscribers } from './subscribers.js';\nimport { setupQueues } from './queues.js';\nimport { setupCrons } from './crons.js';`\n\t\t\t: '';\n\n\t\t// Graceful shutdown code\n\t\tconst gracefulShutdownCode = enableGracefulShutdown\n\t\t\t? `\n // Graceful shutdown handling\n let isShuttingDown = false;\n\n const shutdown = async () => {\n if (isShuttingDown) return;\n isShuttingDown = true;\n logger.info('Graceful shutdown initiated');\n // Allow in-flight requests to complete (30s timeout)\n setTimeout(() => process.exit(0), 30000);\n };\n\n process.on('SIGTERM', shutdown);\n process.on('SIGINT', shutdown);\n`\n\t\t\t: '';\n\n\t\t// Use endpoints/index.js for optimized builds, endpoints.js otherwise\n\t\tconst endpointsImportPath = production.optimizedHandlers\n\t\t\t? './endpoints/index.js'\n\t\t\t: './endpoints.js';\n\n\t\tconst content = `/**\n * Generated production server application\n *\n * This is a production-optimized build without dev tools.\n * - No Telescope debugging dashboard\n * - No Studio database browser\n * - No WebSocket updates\n * - Includes health checks and graceful shutdown\n */\nimport { Hono } from 'hono';\nimport type { Hono as HonoType } from 'hono';\nimport { setupEndpoints } from '${endpointsImportPath}';\n${subscriberImport}\n${runtime.imports}\n${runtime.bindings}\n${hooksImports}\n${cors.imports}\n${context.storageDrivers?.imports ?? ''}\n\n${\n\tcontext.storageDrivers?.setup\n\t\t? `// The entry point registers the drivers its target needs.\n${context.storageDrivers.setup}\n`\n\t\t: ''\n}\nexport interface ServerApp {\n app: HonoType;\n start: (options?: {\n port?: number;\n serve: (app: HonoType, port: number) => void | Promise<void>;\n }) => Promise<void>;\n}\n\n/**\n * Create and configure the production Hono application\n */\nexport async function createApp(app?: HonoType): Promise<ServerApp> {\n const honoApp = app || new Hono();\n\n // Health check endpoint (always first)\n honoApp.get('${healthCheckPath}', (c) => c.json({ status: 'ok', timestamp: Date.now() }));\n honoApp.get('/ready', (c) => c.json({ ready: true }));\n${cors.setup}${beforeSetupCall}\n // Setup HTTP endpoints (OpenAPI: ${enableOpenApi})\n await setupEndpoints(honoApp, envParser, logger, ${enableOpenApi});\n${afterSetupCall}\n return {\n app: honoApp,\n async start(options) {\n if (!options?.serve) {\n throw new Error(\n 'serve function is required. Pass a serve function for your runtime:\\\\n' +\n ' - Bun: (app, port) => Bun.serve({ port, fetch: app.fetch })\\\\n' +\n ' - Node: (app, port) => serve({ fetch: app.fetch, port })'\n );\n }\n\n const port = options.port ?? Number(process.env.PORT) ?? 3000;\n${gracefulShutdownCode}${subscriberSetup}\n logger.info({ port }, 'Starting production server');\n\n // Start HTTP server using provided serve function\n await options.serve(honoApp, port);\n\n logger.info({ port }, 'Production server started');\n${includeSubscribers ? '\\n await subscribeForPush(port);' : ''}\n }\n };\n}\n\n// Default export for convenience\nexport default createApp;\n`;\n\n\t\tawait writeFile(appPath, content);\n\n\t\t// Also generate the production server entry point\n\t\tawait this.generateProductionServerEntry(outputDir);\n\n\t\treturn appPath;\n\t}\n\n\t/**\n\t * Generate production server.ts entry point\n\t */\n\tprivate async generateProductionServerEntry(\n\t\toutputDir: string,\n\t): Promise<void> {\n\t\tconst serverPath = join(outputDir, 'server.ts');\n\n\t\tconst content = `#!/usr/bin/env node\n/**\n * Production server entry point\n * Generated by 'gkm build --provider server --production'\n */\nimport { serve } from '@hono/node-server';\nimport { createApp } from './app.js';\n\nconst port = Number(process.env.PORT) || 3000;\n\nconst { start } = await createApp();\n\nawait start({\n port,\n serve: (app, port) => { serve({ fetch: app.fetch, port }); },\n});\n`;\n\n\t\tawait writeFile(serverPath, content);\n\t}\n}\n","import type { Endpoint } from '@geekmidas/constructs/endpoints';\nimport {\n\tgetRegisteredZodJsonSchemas,\n\tgetSchemaMetadata,\n\tStandardSchemaJsonSchema,\n} from '@geekmidas/schema/conversion';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\n\ninterface OpenApiTsOptions {\n\ttitle?: string;\n\tversion?: string;\n\tdescription?: string;\n}\n\n// JSON Schema type definition\ninterface JsonSchema {\n\t// An array since Zod 4.6, which writes a union of primitives — a nullable\n\t// string among them — as `type: ['string', 'null']` rather than `anyOf`.\n\ttype?: string | string[];\n\t/** A literal — `z.literal('open')` is `{ type: 'string', const: 'open' }`. */\n\tconst?: unknown;\n\tproperties?: Record<string, JsonSchema>;\n\titems?: JsonSchema;\n\trequired?: string[];\n\tenum?: string[];\n\t$ref?: string;\n\tanyOf?: JsonSchema[];\n\toneOf?: JsonSchema[];\n\tallOf?: JsonSchema[];\n\tadditionalProperties?: boolean | JsonSchema;\n\t[key: string]: unknown;\n}\n\n// Security scheme type (OpenAPI 3.1)\ninterface SecuritySchemeObject {\n\ttype: 'apiKey' | 'http' | 'mutualTLS' | 'oauth2' | 'openIdConnect';\n\tdescription?: string;\n\tname?: string;\n\tin?: 'query' | 'header' | 'cookie';\n\tscheme?: string;\n\tbearerFormat?: string;\n\tflows?: Record<string, unknown>;\n\topenIdConnectUrl?: string;\n\t[key: string]: unknown;\n}\n\ninterface EndpointInfo {\n\tendpoint: string;\n\troute: string;\n\tmethod: string;\n\tauthorizerName: string | null;\n\t/** @deprecated Use securityScheme instead */\n\tauthorizerType: string | null;\n\t/** The OpenAPI security scheme definition for this endpoint's authorizer */\n\tsecurityScheme: SecuritySchemeObject | null;\n\tinput?: {\n\t\tbody?: StandardSchemaV1;\n\t\tquery?: StandardSchemaV1;\n\t\tparams?: StandardSchemaV1;\n\t};\n\toutput?: StandardSchemaV1;\n\tdescription?: string;\n\ttags?: string[];\n\toperationId?: string;\n\tresponseType: string;\n}\n\ninterface SecuritySchemeInfo {\n\tname: string;\n\ttype: string;\n\tscheme: SecuritySchemeObject;\n}\n\n/**\n * Generates TypeScript OpenAPI module from endpoints.\n * Outputs:\n * - securitySchemes: typed security scheme definitions\n * - endpointAuth: runtime map of endpoints to auth requirements\n * - paths: TypeScript interface for type-safe fetcher\n * - schema interfaces: reusable TypeScript types from Zod/Valibot schemas\n */\nexport class OpenApiTsGenerator {\n\tasync generate(\n\t\tendpoints: Endpoint<any, any, any, any, any, any>[],\n\t\toptions: OpenApiTsOptions = {},\n\t): Promise<string> {\n\t\tconst { title = 'API', version = '1.0.0', description } = options;\n\n\t\t// Extract endpoint info\n\t\tconst endpointInfos = await this.extractEndpointInfos(endpoints);\n\n\t\t// Collect unique security schemes\n\t\tconst securitySchemes = this.collectSecuritySchemes(endpointInfos);\n\n\t\t// Build endpoint auth map\n\t\tconst endpointAuth = this.buildEndpointAuthMap(endpointInfos);\n\n\t\t// Generate schema interfaces\n\t\tconst schemaInterfaces = await this.generateSchemaInterfaces(endpointInfos);\n\n\t\t// Generate paths interface\n\t\tconst pathsInterface = await this.generatePathsInterface(endpointInfos);\n\n\t\t// Build the final TypeScript module\n\t\treturn this.buildModule({\n\t\t\ttitle,\n\t\t\tversion,\n\t\t\tdescription,\n\t\t\tsecuritySchemes,\n\t\t\tendpointAuth,\n\t\t\tschemaInterfaces,\n\t\t\tpathsInterface,\n\t\t});\n\t}\n\n\tprivate async extractEndpointInfos(\n\t\tendpoints: Endpoint<any, any, any, any, any, any>[],\n\t): Promise<EndpointInfo[]> {\n\t\treturn endpoints.map((ep) => {\n\t\t\tconst route = ep.route.replace(/:(\\w+)/g, '{$1}');\n\t\t\tconst method = ep.method.toUpperCase();\n\n\t\t\t// Get security scheme from authorizer (if available)\n\t\t\t// This is the preferred way - the scheme is stored directly on the authorizer\n\t\t\tconst securityScheme = ep.authorizer?.securityScheme as\n\t\t\t\t| SecuritySchemeObject\n\t\t\t\t| undefined;\n\n\t\t\treturn {\n\t\t\t\tendpoint: `${method} ${route}`,\n\t\t\t\troute,\n\t\t\t\tmethod,\n\t\t\t\tauthorizerName: ep.authorizer?.name ?? null,\n\t\t\t\tauthorizerType: ep.authorizer?.type ?? null,\n\t\t\t\tsecurityScheme: securityScheme ?? null,\n\t\t\t\tinput: ep.input,\n\t\t\t\toutput: ep.outputSchema,\n\t\t\t\tdescription: ep.description,\n\t\t\t\ttags: ep.tags,\n\t\t\t\toperationId: ep.operationId,\n\t\t\t\tresponseType: ep.responseType ?? 'application/json',\n\t\t\t};\n\t\t});\n\t}\n\n\tprivate collectSecuritySchemes(\n\t\tendpointInfos: EndpointInfo[],\n\t): SecuritySchemeInfo[] {\n\t\tconst schemes = new Map<string, SecuritySchemeInfo>();\n\n\t\tfor (const info of endpointInfos) {\n\t\t\tif (info.authorizerName && !schemes.has(info.authorizerName)) {\n\t\t\t\t// Prefer the stored security scheme (from .securitySchemes() or built-ins)\n\t\t\t\t// Fall back to inference from authorizerType for backward compatibility\n\t\t\t\tconst scheme =\n\t\t\t\t\tinfo.securityScheme ??\n\t\t\t\t\t(info.authorizerType\n\t\t\t\t\t\t? this.mapAuthorizerToSecurityScheme(\n\t\t\t\t\t\t\t\tinfo.authorizerType,\n\t\t\t\t\t\t\t\tinfo.authorizerName,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t: null);\n\n\t\t\t\tif (scheme) {\n\t\t\t\t\tschemes.set(info.authorizerName, {\n\t\t\t\t\t\tname: info.authorizerName,\n\t\t\t\t\t\ttype: scheme.type,\n\t\t\t\t\t\tscheme,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn Array.from(schemes.values());\n\t}\n\n\tprivate mapAuthorizerToSecurityScheme(\n\t\ttype: string,\n\t\t_name: string,\n\t): SecuritySchemeObject {\n\t\tswitch (type.toLowerCase()) {\n\t\t\tcase 'jwt':\n\t\t\tcase 'bearer':\n\t\t\t\treturn {\n\t\t\t\t\ttype: 'http',\n\t\t\t\t\tscheme: 'bearer',\n\t\t\t\t\tbearerFormat: 'JWT',\n\t\t\t\t};\n\t\t\tcase 'iam':\n\t\t\tcase 'aws-sigv4':\n\t\t\tcase 'sigv4':\n\t\t\t\treturn {\n\t\t\t\t\ttype: 'apiKey',\n\t\t\t\t\tin: 'header',\n\t\t\t\t\tname: 'Authorization',\n\t\t\t\t\t'x-amazon-apigateway-authtype': 'awsSigv4',\n\t\t\t\t};\n\t\t\tcase 'apikey':\n\t\t\tcase 'api-key':\n\t\t\t\treturn {\n\t\t\t\t\ttype: 'apiKey',\n\t\t\t\t\tin: 'header',\n\t\t\t\t\tname: 'X-API-Key',\n\t\t\t\t};\n\t\t\tcase 'oauth2':\n\t\t\t\treturn {\n\t\t\t\t\ttype: 'oauth2',\n\t\t\t\t\tflows: {},\n\t\t\t\t};\n\t\t\tcase 'oidc':\n\t\t\tcase 'openidconnect':\n\t\t\t\treturn {\n\t\t\t\t\ttype: 'openIdConnect',\n\t\t\t\t\topenIdConnectUrl: '',\n\t\t\t\t};\n\t\t\tdefault:\n\t\t\t\treturn {\n\t\t\t\t\ttype: 'http',\n\t\t\t\t\tscheme: 'bearer',\n\t\t\t\t};\n\t\t}\n\t}\n\n\tprivate buildEndpointAuthMap(\n\t\tendpointInfos: EndpointInfo[],\n\t): Record<string, string | null> {\n\t\tconst authMap: Record<string, string | null> = {};\n\n\t\tfor (const info of endpointInfos) {\n\t\t\tauthMap[info.endpoint] = info.authorizerName;\n\t\t}\n\n\t\treturn authMap;\n\t}\n\n\tprivate async generateSchemaInterfaces(\n\t\tendpointInfos: EndpointInfo[],\n\t): Promise<string> {\n\t\tconst interfaces: string[] = [];\n\t\tconst generatedNames = new Set<string>();\n\t\t// Collect nested schemas with $defs (from .meta({ id: 'X' }))\n\t\tconst collectedDefs = new Map<string, JsonSchema>();\n\n\t\tfor (const info of endpointInfos) {\n\t\t\tconst baseName = this.getSchemaBaseName(info);\n\n\t\t\t// Input body schema\n\t\t\tif (info.input?.body) {\n\t\t\t\tconst name = await this.getSchemaName(\n\t\t\t\t\tinfo.input.body,\n\t\t\t\t\t`${baseName}Input`,\n\t\t\t\t);\n\t\t\t\tif (!generatedNames.has(name)) {\n\t\t\t\t\tconst schema = await this.schemaToInterfaceWithDefs(\n\t\t\t\t\t\tinfo.input.body,\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tcollectedDefs,\n\t\t\t\t\t);\n\t\t\t\t\tif (schema) {\n\t\t\t\t\t\tinterfaces.push(schema);\n\t\t\t\t\t\tgeneratedNames.add(name);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Input params schema\n\t\t\tif (info.input?.params) {\n\t\t\t\tconst name = await this.getSchemaName(\n\t\t\t\t\tinfo.input.params,\n\t\t\t\t\t`${baseName}Params`,\n\t\t\t\t);\n\t\t\t\tif (!generatedNames.has(name)) {\n\t\t\t\t\tconst schema = await this.schemaToInterfaceWithDefs(\n\t\t\t\t\t\tinfo.input.params,\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tcollectedDefs,\n\t\t\t\t\t);\n\t\t\t\t\tif (schema) {\n\t\t\t\t\t\tinterfaces.push(schema);\n\t\t\t\t\t\tgeneratedNames.add(name);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Input query schema\n\t\t\tif (info.input?.query) {\n\t\t\t\tconst name = await this.getSchemaName(\n\t\t\t\t\tinfo.input.query,\n\t\t\t\t\t`${baseName}Query`,\n\t\t\t\t);\n\t\t\t\tif (!generatedNames.has(name)) {\n\t\t\t\t\tconst schema = await this.schemaToInterfaceWithDefs(\n\t\t\t\t\t\tinfo.input.query,\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tcollectedDefs,\n\t\t\t\t\t);\n\t\t\t\t\tif (schema) {\n\t\t\t\t\t\tinterfaces.push(schema);\n\t\t\t\t\t\tgeneratedNames.add(name);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Output schema\n\t\t\tif (info.output) {\n\t\t\t\tconst name = await this.getSchemaName(info.output, `${baseName}Output`);\n\t\t\t\tif (!generatedNames.has(name)) {\n\t\t\t\t\tconst schema = await this.schemaToInterfaceWithDefs(\n\t\t\t\t\t\tinfo.output,\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tcollectedDefs,\n\t\t\t\t\t);\n\t\t\t\t\tif (schema) {\n\t\t\t\t\t\tinterfaces.push(schema);\n\t\t\t\t\t\tgeneratedNames.add(name);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Pull in every schema registered via `.meta({ id })` in zod's global\n\t\t// registry, even when no endpoint references it directly. This makes\n\t\t// derived schemas like `UserSchema.pick(...)` discoverable through the\n\t\t// named parent type without requiring the parent to be wired into an\n\t\t// endpoint.\n\t\tconst registrySchemas = await getRegisteredZodJsonSchemas();\n\t\tfor (const [rawName, rawSchema] of Object.entries(registrySchemas)) {\n\t\t\tconst defName = this.pascalCase(rawName);\n\t\t\tif (collectedDefs.has(defName) || generatedNames.has(defName)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst {\n\t\t\t\tid: _id,\n\t\t\t\t$defs: _defs,\n\t\t\t\t...rest\n\t\t\t} = rawSchema as JsonSchema & {\n\t\t\t\tid?: string;\n\t\t\t};\n\t\t\tcollectedDefs.set(defName, rest as JsonSchema);\n\t\t}\n\n\t\t// Generate interfaces for collected $defs (nested schemas with .meta({ id: 'X' }))\n\t\tfor (const [defName, defSchema] of collectedDefs) {\n\t\t\tif (!generatedNames.has(defName)) {\n\t\t\t\tconst interfaceStr = this.jsonSchemaToInterface(defSchema, defName);\n\t\t\t\tinterfaces.push(interfaceStr);\n\t\t\t\tgeneratedNames.add(defName);\n\t\t\t}\n\t\t}\n\n\t\treturn interfaces.join('\\n\\n');\n\t}\n\n\t/**\n\t * Get the name for a schema, using metadata `id` if available,\n\t * otherwise falling back to the provided default name.\n\t */\n\tprivate async getSchemaName(\n\t\tschema: StandardSchemaV1,\n\t\tdefaultName: string,\n\t): Promise<string> {\n\t\ttry {\n\t\t\tconst metadata = await getSchemaMetadata(schema);\n\t\t\tif (metadata?.id) {\n\t\t\t\treturn this.pascalCase(metadata.id);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore metadata extraction errors\n\t\t}\n\t\treturn defaultName;\n\t}\n\n\tprivate getSchemaBaseName(info: EndpointInfo): string {\n\t\tif (info.operationId) {\n\t\t\treturn this.pascalCase(info.operationId);\n\t\t}\n\n\t\t// Generate name from method + route\n\t\tconst routeParts = info.route\n\t\t\t.replace(/[{}]/g, '')\n\t\t\t.split('/')\n\t\t\t.filter(Boolean)\n\t\t\t.map((part) => this.pascalCase(part));\n\n\t\treturn `${this.pascalCase(info.method.toLowerCase())}${routeParts.join('')}`;\n\t}\n\n\tprivate pascalCase(str: string): string {\n\t\treturn str\n\t\t\t.replace(/[-_](.)/g, (_, c) => c.toUpperCase())\n\t\t\t.replace(/^./, (c) => c.toUpperCase());\n\t}\n\n\t/**\n\t * Convert schema to interface while collecting $defs for nested schemas\n\t * with .meta({ id: 'X' }).\n\t */\n\tprivate async schemaToInterfaceWithDefs(\n\t\tschema: StandardSchemaV1,\n\t\tname: string,\n\t\tcollectedDefs: Map<string, JsonSchema>,\n\t): Promise<string | null> {\n\t\ttry {\n\t\t\t// Get raw JSON schema with $defs intact (don't use convertStandardSchemaToJsonSchema\n\t\t\t// which strips $defs)\n\t\t\tconst vendor = schema['~standard']?.vendor;\n\t\t\tif (!vendor || !(vendor in StandardSchemaJsonSchema)) {\n\t\t\t\treturn unknownType(name);\n\t\t\t}\n\n\t\t\tconst toJsonSchema =\n\t\t\t\tStandardSchemaJsonSchema[\n\t\t\t\t\tvendor as keyof typeof StandardSchemaJsonSchema\n\t\t\t\t];\n\t\t\tconst jsonSchema = await toJsonSchema(schema);\n\t\t\tif (!jsonSchema) return unknownType(name);\n\n\t\t\t// Extract $defs from the JSON schema (these come from .meta({ id: 'X' }))\n\t\t\tif (jsonSchema.$defs && typeof jsonSchema.$defs === 'object') {\n\t\t\t\tfor (const [defName, defSchema] of Object.entries(jsonSchema.$defs)) {\n\t\t\t\t\tif (!collectedDefs.has(defName)) {\n\t\t\t\t\t\t// Remove the 'id' field from the schema as it's just metadata\n\t\t\t\t\t\tconst { id, ...schemaWithoutId } = defSchema as JsonSchema & {\n\t\t\t\t\t\t\tid?: string;\n\t\t\t\t\t\t};\n\t\t\t\t\t\tcollectedDefs.set(defName, schemaWithoutId as JsonSchema);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Remove $defs from the schema before converting to interface\n\t\t\tconst { $defs, ...schemaWithoutDefs } = jsonSchema;\n\n\t\t\t// A schema that *is* a registered schema converts to nothing but a\n\t\t\t// `$ref` at it, and the def it points at has already been collected\n\t\t\t// above — so emitting a declaration here as well produces\n\t\t\t// `export type User = User`, a circular alias that is not a type.\n\t\t\t//\n\t\t\t// Zod used to inline this case and only started emitting the `$ref` in\n\t\t\t// 4.6, which is how the bug arrived without the generator changing.\n\t\t\tconst ref = (schemaWithoutDefs as { $ref?: string }).$ref;\n\t\t\tif (\n\t\t\t\tref &&\n\t\t\t\tObject.keys(schemaWithoutDefs).every(\n\t\t\t\t\t(k) => k === '$ref' || k === '$schema',\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tconst target = ref.split('/').pop();\n\t\t\t\t// The def's own interface is the declaration. Only alias it when the\n\t\t\t\t// names actually differ, which is when the alias says something.\n\t\t\t\treturn target === name ? null : `export type ${name} = ${target};`;\n\t\t\t}\n\n\t\t\treturn this.jsonSchemaToInterface(schemaWithoutDefs, name);\n\t\t} catch {\n\t\t\treturn unknownType(name);\n\t\t}\n\t}\n\n\tprivate jsonSchemaToInterface(schema: JsonSchema, name: string): string {\n\t\tif (schema.type !== 'object' || !schema.properties) {\n\t\t\t// For non-object types, create a type alias\n\t\t\tconst typeStr = this.jsonSchemaTypeToTs(schema);\n\t\t\treturn `export type ${name} = ${typeStr};`;\n\t\t}\n\n\t\tconst props: string[] = [];\n\t\tconst required = new Set(schema.required || []);\n\n\t\tfor (const [propName, propSchema] of Object.entries(schema.properties)) {\n\t\t\tconst isRequired = required.has(propName);\n\t\t\tconst typeStr = this.jsonSchemaTypeToTs(propSchema as JsonSchema);\n\t\t\tconst optionalMark = isRequired ? '' : '?';\n\t\t\tprops.push(` ${propName}${optionalMark}: ${typeStr};`);\n\t\t}\n\n\t\treturn `export interface ${name} {\\n${props.join('\\n')}\\n}`;\n\t}\n\n\tprivate jsonSchemaTypeToTs(schema: JsonSchema): string {\n\t\tif (!schema) return 'unknown';\n\n\t\tif (schema.$ref) {\n\t\t\t// Extract name from $ref\n\t\t\tconst refName = schema.$ref.split('/').pop() || 'unknown';\n\t\t\treturn refName;\n\t\t}\n\n\t\t// Before the type: a literal is narrower than the type it belongs to, and\n\t\t// reading only the type turned `'open' | 'closed'` into `string | string`.\n\t\tif (schema.const !== undefined) {\n\t\t\treturn typeof schema.const === 'string'\n\t\t\t\t? `'${schema.const}'`\n\t\t\t\t: String(schema.const);\n\t\t}\n\n\t\tif (schema.anyOf) {\n\t\t\treturn schema.anyOf\n\t\t\t\t.map((s: JsonSchema) => this.jsonSchemaTypeToTs(s))\n\t\t\t\t.join(' | ');\n\t\t}\n\n\t\tif (schema.oneOf) {\n\t\t\treturn schema.oneOf\n\t\t\t\t.map((s: JsonSchema) => this.jsonSchemaTypeToTs(s))\n\t\t\t\t.join(' | ');\n\t\t}\n\n\t\tif (schema.allOf) {\n\t\t\treturn schema.allOf\n\t\t\t\t.map((s: JsonSchema) => this.jsonSchemaTypeToTs(s))\n\t\t\t\t.join(' & ');\n\t\t}\n\n\t\tif (Array.isArray(schema.type)) {\n\t\t\treturn schema.type\n\t\t\t\t.map((type) => this.jsonSchemaTypeToTs({ ...schema, type }))\n\t\t\t\t.join(' | ');\n\t\t}\n\n\t\tswitch (schema.type) {\n\t\t\tcase 'string':\n\t\t\t\tif (schema.enum) {\n\t\t\t\t\treturn schema.enum.map((e: string) => `'${e}'`).join(' | ');\n\t\t\t\t}\n\t\t\t\treturn 'string';\n\t\t\tcase 'number':\n\t\t\tcase 'integer':\n\t\t\t\treturn 'number';\n\t\t\tcase 'boolean':\n\t\t\t\treturn 'boolean';\n\t\t\tcase 'null':\n\t\t\t\treturn 'null';\n\t\t\tcase 'array':\n\t\t\t\tif (schema.items) {\n\t\t\t\t\treturn `Array<${this.jsonSchemaTypeToTs(schema.items as JsonSchema)}>`;\n\t\t\t\t}\n\t\t\t\treturn 'Array<unknown>';\n\t\t\tcase 'object':\n\t\t\t\tif (schema.properties) {\n\t\t\t\t\tconst props: string[] = [];\n\t\t\t\t\tconst required = new Set(schema.required || []);\n\t\t\t\t\tfor (const [propName, propSchema] of Object.entries(\n\t\t\t\t\t\tschema.properties,\n\t\t\t\t\t)) {\n\t\t\t\t\t\tconst isRequired = required.has(propName);\n\t\t\t\t\t\tconst typeStr = this.jsonSchemaTypeToTs(propSchema as JsonSchema);\n\t\t\t\t\t\tconst optionalMark = isRequired ? '' : '?';\n\t\t\t\t\t\tprops.push(`${propName}${optionalMark}: ${typeStr}`);\n\t\t\t\t\t}\n\t\t\t\t\treturn `{ ${props.join('; ')} }`;\n\t\t\t\t}\n\t\t\t\tif (schema.additionalProperties) {\n\t\t\t\t\tconst valueType = this.jsonSchemaTypeToTs(\n\t\t\t\t\t\tschema.additionalProperties as JsonSchema,\n\t\t\t\t\t);\n\t\t\t\t\treturn `Record<string, ${valueType}>`;\n\t\t\t\t}\n\t\t\t\treturn 'Record<string, unknown>';\n\t\t\tdefault:\n\t\t\t\treturn 'unknown';\n\t\t}\n\t}\n\n\tprivate async generatePathsInterface(\n\t\tendpointInfos: EndpointInfo[],\n\t): Promise<string> {\n\t\tconst pathGroups = new Map<string, EndpointInfo[]>();\n\n\t\t// Group endpoints by route\n\t\tfor (const info of endpointInfos) {\n\t\t\tconst existing = pathGroups.get(info.route) || [];\n\t\t\texisting.push(info);\n\t\t\tpathGroups.set(info.route, existing);\n\t\t}\n\n\t\tconst pathEntries: string[] = [];\n\n\t\tfor (const [route, infos] of pathGroups) {\n\t\t\tconst methodEntries: string[] = [];\n\n\t\t\tfor (const info of infos) {\n\t\t\t\tconst methodDef = await this.generateMethodDefinition(info);\n\t\t\t\tmethodEntries.push(` ${info.method.toLowerCase()}: ${methodDef};`);\n\t\t\t}\n\n\t\t\t// Add path parameters if present\n\t\t\tconst firstWithParams = infos.find((i) => i.input?.params);\n\t\t\tlet paramsEntry = '';\n\t\t\tif (firstWithParams?.input?.params) {\n\t\t\t\tconst paramsName = await this.getSchemaName(\n\t\t\t\t\tfirstWithParams.input.params,\n\t\t\t\t\t`${this.getSchemaBaseName(firstWithParams)}Params`,\n\t\t\t\t);\n\t\t\t\tparamsEntry = `\\n parameters: {\\n path: ${paramsName};\\n };`;\n\t\t\t}\n\n\t\t\tpathEntries.push(\n\t\t\t\t` '${route}': {${paramsEntry}\\n${methodEntries.join('\\n')}\\n };`,\n\t\t\t);\n\t\t}\n\n\t\treturn `export interface paths {\\n${pathEntries.join('\\n')}\\n}`;\n\t}\n\n\tprivate async generateMethodDefinition(info: EndpointInfo): Promise<string> {\n\t\tconst parts: string[] = [];\n\t\tconst baseName = this.getSchemaBaseName(info);\n\n\t\t// Request body\n\t\tif (info.input?.body) {\n\t\t\tconst bodyName = await this.getSchemaName(\n\t\t\t\tinfo.input.body,\n\t\t\t\t`${baseName}Input`,\n\t\t\t);\n\t\t\tparts.push(`requestBody: {\n content: {\n 'application/json': ${bodyName};\n };\n }`);\n\t\t}\n\n\t\t// Query parameters\n\t\tif (info.input?.query) {\n\t\t\tconst queryName = await this.getSchemaName(\n\t\t\t\tinfo.input.query,\n\t\t\t\t`${baseName}Query`,\n\t\t\t);\n\t\t\tparts.push(`parameters: {\n query: ${queryName};\n }`);\n\t\t}\n\n\t\t// Responses\n\t\tconst outputName = info.output\n\t\t\t? await this.getSchemaName(info.output, `${baseName}Output`)\n\t\t\t: 'unknown';\n\t\tparts.push(`responses: {\n 200: {\n content: {\n '${info.responseType}': ${outputName};\n };\n };\n }`);\n\n\t\treturn `{\\n ${parts.join(';\\n ')};\\n }`;\n\t}\n\n\tprivate buildModule(params: {\n\t\ttitle: string;\n\t\tversion: string;\n\t\tdescription?: string;\n\t\tsecuritySchemes: SecuritySchemeInfo[];\n\t\tendpointAuth: Record<string, string | null>;\n\t\tschemaInterfaces: string;\n\t\tpathsInterface: string;\n\t}): string {\n\t\tconst {\n\t\t\ttitle,\n\t\t\tversion,\n\t\t\tdescription,\n\t\t\tsecuritySchemes,\n\t\t\tendpointAuth,\n\t\t\tschemaInterfaces,\n\t\t\tpathsInterface,\n\t\t} = params;\n\n\t\tconst securitySchemesObj = securitySchemes.reduce(\n\t\t\t(acc, s) => {\n\t\t\t\tacc[s.name] = s.scheme;\n\t\t\t\treturn acc;\n\t\t\t},\n\t\t\t{} as Record<string, SecuritySchemeObject>,\n\t\t);\n\n\t\tconst schemeNames = securitySchemes.map((s) => `'${s.name}'`).join(' | ');\n\n\t\t// Generate createApi only if there are security schemes\n\t\tconst hasSecuritySchemes = schemeNames.length > 0;\n\n\t\tconst createApiSection = hasSecuritySchemes\n\t\t\t? `\n// ============================================================\n// API Client Factory\n// ============================================================\n\nimport {\n createAuthAwareFetcher,\n type AuthStrategy,\n} from '@geekmidas/client/auth-fetcher';\nimport { createEndpointHooks } from '@geekmidas/client/endpoint-hooks';\nimport type { QueryClient } from '@tanstack/react-query';\n\n/**\n * Options for creating the API client.\n */\nexport interface CreateApiOptions {\n /** Base URL for all API requests (required) */\n baseURL: string;\n /** Auth strategies for each security scheme used in this API */\n authStrategies: Record<SecuritySchemeId, AuthStrategy>;\n /** Optional React Query client instance */\n queryClient?: QueryClient;\n /** Optional request interceptor */\n onRequest?: (config: RequestInit) => RequestInit | Promise<RequestInit>;\n /** The \\`fetch\\` requests go out on — a test browser's, for one */\n fetch?: typeof fetch;\n}\n\n/**\n * Create a type-safe API client with authentication and React Query hooks.\n *\n * @example\n * \\`\\`\\`typescript\n * const api = createApi({\n * baseURL: 'https://api.example.com',\n * authStrategies: {\n * jwt: { type: 'bearer', tokenProvider },\n * },\n * });\n *\n * // Imperative fetch\n * const user = await api('GET /users/{id}', { params: { id: '123' } });\n *\n * // React Query hooks\n * const { data } = api.useQuery('GET /users/{id}', { params: { id: '123' } });\n * const mutation = api.useMutation('POST /users');\n * \\`\\`\\`\n */\nexport function createApi(options: CreateApiOptions) {\n const fetcher = createAuthAwareFetcher<paths, typeof endpointAuth, typeof securitySchemes>({\n baseURL: options.baseURL,\n endpointAuth,\n securitySchemes,\n authStrategies: options.authStrategies,\n onRequest: options.onRequest,\n ...(options.fetch ? { fetch: options.fetch } : {}),\n });\n\n const hooks = createEndpointHooks<paths>(fetcher, { queryClient: options.queryClient });\n\n return Object.assign(fetcher, hooks);\n}\n`\n\t\t\t: `\n// ============================================================\n// API Client Factory\n// ============================================================\n\nimport { createTypedFetcher, type FetcherOptions } from '@geekmidas/client/fetcher';\nimport { createEndpointHooks } from '@geekmidas/client/endpoint-hooks';\nimport type { QueryClient } from '@tanstack/react-query';\n\n/**\n * Options for creating the API client.\n */\nexport interface CreateApiOptions extends Omit<FetcherOptions, 'baseURL'> {\n /** Base URL for all API requests (required) */\n baseURL: string;\n /** Optional React Query client instance */\n queryClient?: QueryClient;\n}\n\n/**\n * Create a type-safe API client with React Query hooks.\n *\n * @example\n * \\`\\`\\`typescript\n * const api = createApi({\n * baseURL: 'https://api.example.com',\n * });\n *\n * // Imperative fetch\n * const data = await api('GET /health');\n *\n * // React Query hooks\n * const { data } = api.useQuery('GET /health');\n * \\`\\`\\`\n */\nexport function createApi(options: CreateApiOptions) {\n const { queryClient, ...fetcherOptions } = options;\n const request = createTypedFetcher<paths>(fetcherOptions);\n\n const hooks = createEndpointHooks<paths>(request, { queryClient });\n\n return Object.assign(request, hooks);\n}\n`;\n\n\t\treturn `// Auto-generated by @geekmidas/cli - DO NOT EDIT\n\n// ============================================================\n// Security Scheme Type\n// ============================================================\n\ninterface SecuritySchemeObject {\n type: 'apiKey' | 'http' | 'mutualTLS' | 'oauth2' | 'openIdConnect';\n description?: string;\n name?: string;\n in?: 'query' | 'header' | 'cookie';\n scheme?: string;\n bearerFormat?: string;\n flows?: Record<string, unknown>;\n openIdConnectUrl?: string;\n [key: string]: unknown;\n}\n\n// ============================================================\n// API Info\n// ============================================================\n\nexport const apiInfo = {\n title: '${title}',\n version: '${version}',${description ? `\\n description: '${description.replace(/'/g, \"\\\\'\")}',` : ''}\n} as const;\n\n// ============================================================\n// Security Schemes\n// ============================================================\n\n/**\n * Available security schemes for this API.\n * Maps authorizer names to OpenAPI security scheme definitions.\n */\nexport const securitySchemes = ${JSON.stringify(securitySchemesObj, null, 2).replace(/\"([a-zA-Z_$][a-zA-Z0-9_$]*)\":/g, '$1:')} as const satisfies Record<string, SecuritySchemeObject>;\n\nexport type SecuritySchemeId = ${schemeNames || 'never'};\n\n// ============================================================\n// Endpoint Authentication Map\n// ============================================================\n\n/**\n * Runtime map of endpoints to their required authentication scheme.\n * \\`null\\` indicates a public endpoint (no auth required).\n */\nexport const endpointAuth = ${JSON.stringify(endpointAuth, null, 2).replace(/\"([^\"]+)\":/g, \"'$1':\")} as const satisfies Record<string, SecuritySchemeId | null>;\n\nexport type EndpointString = keyof typeof endpointAuth;\n\nexport type AuthenticatedEndpoint = {\n [K in EndpointString]: typeof endpointAuth[K] extends null ? never : K;\n}[EndpointString];\n\nexport type PublicEndpoint = {\n [K in EndpointString]: typeof endpointAuth[K] extends null ? K : never;\n}[EndpointString];\n\n// ============================================================\n// Schema Definitions\n// ============================================================\n\n${schemaInterfaces}\n\n// ============================================================\n// OpenAPI Paths\n// ============================================================\n\n${pathsInterface}\n${createApiSection}\n`;\n\t}\n}\n\n/**\n * The declaration for a schema that cannot be converted — a vendor with no\n * converter, or one that fails. The paths interface names it either way, so\n * leaving it undeclared made the generated client fail to compile.\n */\nfunction unknownType(name: string): string {\n\treturn `export type ${name} = unknown;`;\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"discover-B0f_kuNN.cjs","names":["fg","kebabCase","relative","fg","canonicalId","relative"],"sources":["../src/types.ts","../src/generators/Generator.ts","../src/reconcile/discover.ts"],"sourcesContent":["import type {\n\tSecretsConfig,\n\tStagesConfig,\n\tTestConfig,\n} from './workspace/types.js';\n\nexport type MainProvider = 'aws' | 'server';\nexport type LegacyProvider =\n\t| 'server'\n\t| 'aws-apigatewayv1'\n\t| 'aws-apigatewayv2'\n\t| 'aws-lambda';\n\nexport interface PartitionedRoutes {\n\tpaths: string | string[];\n\tpartition: (filepath: string) => string;\n}\n\nexport type Routes = string | string[] | PartitionedRoutes;\n\nexport function isPartitionedRoutes(\n\troutes: Routes | undefined,\n): routes is PartitionedRoutes {\n\treturn (\n\t\ttypeof routes === 'object' &&\n\t\troutes !== null &&\n\t\t!Array.isArray(routes) &&\n\t\t'paths' in routes\n\t);\n}\n\nexport interface ProviderConfig {\n\tenabled?: boolean;\n\toutputDir?: string;\n}\n\nexport interface AWSApiGatewayConfig extends ProviderConfig {\n\t// Additional AWS API Gateway specific options\n}\n\nexport interface AWSLambdaConfig extends ProviderConfig {\n\t// Additional AWS Lambda specific options\n}\n\nexport interface ProductionConfig {\n\t/** Enable production mode (default: false) */\n\tenabled?: boolean;\n\t/** Bundle server into single file (default: true) */\n\tbundle?: boolean;\n\t/** Minify bundled output (default: true) */\n\tminify?: boolean;\n\t/** Health check endpoint path (default: '/health') */\n\thealthCheck?: string;\n\t/** Enable graceful shutdown handling (default: true) */\n\tgracefulShutdown?: boolean;\n\t/** Packages to exclude from bundling (default: []) */\n\texternal?: string[];\n\t/** Include subscribers in production build (default: 'exclude' for serverless) */\n\tsubscribers?: 'include' | 'exclude';\n\t/** Include OpenAPI spec in production (default: false) */\n\topenapi?: boolean;\n\t/**\n\t * Enable build-time optimized handler generation (default: true)\n\t * Generates specialized handlers based on endpoint tier:\n\t * - minimal: Near-raw-Hono performance for simple endpoints\n\t * - standard: Optimized handlers for auth/services\n\t * - full: Uses HonoEndpoint.addRoutes for complex endpoints\n\t */\n\toptimizedHandlers?: boolean;\n}\n\n/**\n * Supported event backend types.\n *\n * - `pgboss`: Reuses PostgreSQL with a dedicated user/schema. Auto-enables db service.\n * - `sns`: Adds the AWS emulator (floci) container; each topic and queue is\n * created on it and addressed by its own string.\n * - `rabbitmq`: Adds RabbitMQ container.\n *\n * Each topic and queue resolves its own `<ID>_PUBLISHER_CONNECTION_STRING`,\n * which its producers and its consumers both read. pg-boss and RabbitMQ also\n * resolve `EVENT_PUBLISHER_CONNECTION_STRING`, the one broker crons schedule\n * through.\n */\nexport type EventsBackend = 'pgboss' | 'sns' | 'rabbitmq';\n\n/**\n * Where a declared cache actually lives.\n *\n * A deployment choice, not an application one — the same code caches into any\n * of these — so it is config here beside `events` rather than a field on the\n * construct. Each backend is consistent between local and deployed, which is\n * the property worth having: a cache that behaves differently in the two places\n * is worse than a slower one.\n *\n * - `upstash` (default) — HTTP with a token. Reachable from a Lambda with no\n * VPC and no connection pool, which is why it is the default; locally the\n * `serverless-redis-http` proxy in front of Redis speaks the same protocol.\n * - `elasticache` — the Redis wire protocol, inside a VPC. Locally, plain Redis.\n * - `db` — a table in the database the app already declared. No infrastructure\n * at all, and the same relationship pg-boss has to Postgres: it creates its\n * own table on first use and needs a declared database to live in.\n */\nexport type CacheBackend = 'upstash' | 'elasticache' | 'db';\n\n/**\n * Where a declared bucket actually lives.\n *\n * The fourth of these selectors and the last to arrive, which is why\n * `UPLOADS_URL` had no answer on a container target: there was nothing to\n * choose with. Like the others it changes no application code — the `s3://`\n * scheme picks the driver, and every one of these speaks it.\n *\n * - `s3` — a real bucket. Nothing is created on the host; the URL is composed\n * against credentials you already hold, the same way `resend` is an API key\n * rather than infrastructure.\n * - `r2` — Cloudflare's, reached through the same S3 API and differing only in\n * its endpoint.\n * - `minio` — a container. Everything the app declares lives on the box, which\n * is what makes a single-host deploy self-contained, and what makes it a\n * single disk with no replication. Locally this is what every backend is.\n */\nexport type StorageBackend = 'minio' | 's3' | 'r2';\n\n/**\n * The backend a project gets when it says nothing, by where it deploys.\n *\n * Flat defaults could not be right for both: `upstash` on a single box means a\n * SaaS round-trip for a cache the database could have held, and `minio` on AWS\n * means running a bucket beside S3. So the default is a function of the target,\n * and an app that says nothing gets the obvious answer in both places — which\n * is the whole of what \"zero config\" can mean once there is more than one\n * target.\n *\n * `server` covers Dokploy and a bare host alike: the question these answer is\n * whether the app is next to a managed cloud or next to its own containers, and\n * on that question the two are the same.\n *\n * A per-construct, per-stage override is the layer above this — see\n * `constructs-outstanding.md` — so two buckets in one app can differ. This is\n * only what happens when nobody has said anything at all.\n */\nexport const DEFAULT_CACHE: Record<MainProvider, CacheBackend> = {\n\t// Reachable from a Lambda with no VPC and no connection pool.\n\taws: 'upstash',\n\t// The database is already there and already has a connection pool open to\n\t// it. Anything else is a second service to run for a table.\n\tserver: 'db',\n};\n\n/**\n * Which broker carries a declared queue or topic, by target.\n *\n * Whether there are events at all is the manifest's answer — a declared\n * `Topic` or `Queue` — so this only ever answers the second question. AWS\n * deploys them to SNS and SQS, and locally the AWS emulator stands in, so the\n * broker a developer runs is the one production has. A server has a Postgres\n * already, and pg-boss keeps its queues there.\n */\nexport const DEFAULT_EVENTS: Record<MainProvider, EventsBackend> = {\n\taws: 'sns',\n\tserver: 'pgboss',\n};\n\n/** Where a bucket lives when nobody said — see {@link DEFAULT_CACHE}. */\nexport const DEFAULT_STORAGE: Record<MainProvider, StorageBackend> = {\n\taws: 's3',\n\tserver: 'minio',\n};\n\n/**\n * Who delivers a declared app's mail.\n *\n * Unlike a cache, this changes *nothing* about the client. Every backend speaks\n * SMTP, so the declaration's `smtp://` URL is true of all of them and only the\n * provisioner differs — which is the same reason the declaration has no\n * `provider` field and there is no `ses://` scheme.\n *\n * - `ses` (default) — the only backend that can be a *chain* of resources: an\n * identity, a user, an access key, and an SMTP password derived from it,\n * because SES issues IAM credentials rather than SMTP ones. Hand it a URL and\n * it uses that instead, which is what you want when the credentials already\n * exist — provisioning a second set of sending credentials for an identity\n * that already has them is not a thing to do by default.\n * - `resend` — an API key you hold, composed into an SMTP URL. Nothing to\n * provision: a SaaS account is not infrastructure.\n * - `smtp` — a URL supplied whole, for a relay somebody else runs.\n *\n * Locally every one of them is Mailpit, because locally none of it matters.\n */\nexport type EmailBackend = 'resend' | 'ses' | 'smtp';\n\n/**\n * Who delivers mail when nobody said.\n *\n * Flat, unlike the others, and deliberately: mail is a SaaS on every target.\n * There is no self-hosted preset to default a container deploy to — Mailpit is\n * a development inbox, not a sender — so `server` has no answer of its own to\n * give and would only be `ses` written twice.\n */\nexport const DEFAULT_EMAIL: EmailBackend = 'ses';\n\n/** Supported docker-compose service names */\nexport type ComposeServiceName =\n\t| 'postgres'\n\t| 'redis'\n\t| 'rabbitmq'\n\t| 'minio'\n\t| 'mailpit'\n\t| 'localstack';\n\nexport interface DockerConfig {\n\t/** Container registry URL (e.g., 'ghcr.io/myorg') */\n\tregistry?: string;\n\t/** Docker image name (default: derived from package.json name) */\n\timageName?: string;\n\t/** Base Docker image (default: 'node:22-alpine') */\n\tbaseImage?: string;\n\t/** Container port (default: 3000) */\n\tport?: number;\n}\n\nexport interface ServerConfig extends ProviderConfig {\n\tenableOpenApi?: boolean;\n\tport?: number;\n\t/** Production build configuration */\n\tproduction?: ProductionConfig;\n}\n\nexport type Runtime = 'node' | 'bun';\n\nexport interface TelescopeConfig {\n\t/** Enable/disable telescope (default: true in development) */\n\tenabled?: boolean;\n\t/** Port for telescope to use (defaults to server port) */\n\tport?: number;\n\t/** Path prefix for telescope UI (default: /__telescope) */\n\tpath?: string;\n\t/** Ignore patterns for telescope (e.g., ['/health', '/metrics']) */\n\tignore?: string[];\n\t/** Record request/response bodies (default: true) */\n\trecordBody?: boolean;\n\t/** Maximum entries to keep in memory (default: 1000) */\n\tmaxEntries?: number;\n\t/** Enable WebSocket for real-time updates (default: true, requires @hono/node-ws for Node.js) */\n\twebsocket?: boolean;\n}\n\nexport interface StudioConfig {\n\t/** Enable/disable studio (default: true in development) */\n\tenabled?: boolean;\n\t/** Path prefix for studio UI (default: /__studio) */\n\tpath?: string;\n\t/** Schema to introspect (default: 'public') */\n\tschema?: string;\n}\n\nexport interface OpenApiConfig {\n\t/** Enable OpenAPI generation (default: true) */\n\tenabled?: boolean;\n\t/** API title */\n\ttitle?: string;\n\t/** API version */\n\tversion?: string;\n\t/** API description */\n\tdescription?: string;\n}\n\nexport interface HooksConfig {\n\t/**\n\t * Path to a module exporting server lifecycle hooks.\n\t * The module should export `beforeSetup` and/or `afterSetup` functions.\n\t *\n\t * @example\n\t * ```typescript\n\t * // src/config/hooks.ts\n\t * import type { Hono } from 'hono';\n\t * import type { Logger } from '@geekmidas/logger';\n\t * import type { EnvironmentParser } from '@geekmidas/envkit';\n\t *\n\t * // Called BEFORE gkm endpoints are registered\n\t * export function beforeSetup(app: Hono, ctx: { envParser: EnvironmentParser; logger: Logger }) {\n\t * app.use('*', cors());\n\t * app.get('/custom/health', (c) => c.json({ status: 'ok' }));\n\t * }\n\t *\n\t * // Called AFTER gkm endpoints are registered\n\t * export function afterSetup(app: Hono, ctx: { envParser: EnvironmentParser; logger: Logger }) {\n\t * app.notFound((c) => c.json({ error: 'Not found' }, 404));\n\t * app.onError((err, c) => c.json({ error: err.message }, 500));\n\t * }\n\t * ```\n\t */\n\tserver?: string;\n}\n\n/** Dokploy deployment configuration */\nexport interface DokployProviderConfig {\n\t/** Dokploy API endpoint (e.g., 'https://dokploy.example.com') */\n\tendpoint: string;\n\t/** Project ID in Dokploy */\n\tprojectId: string;\n\t/** Application ID in Dokploy */\n\tapplicationId: string;\n\t/** Container registry (overrides docker.registry if set) */\n\tregistry?: string;\n\t/** Registry ID in Dokploy (recommended for private registries) */\n\tregistryId?: string;\n}\n\nexport interface ProvidersConfig {\n\taws?: {\n\t\tapiGateway?: {\n\t\t\tv1?: boolean | AWSApiGatewayConfig;\n\t\t\tv2?: boolean | AWSApiGatewayConfig;\n\t\t};\n\t\tlambda?: {\n\t\t\tfunctions?: boolean | AWSLambdaConfig;\n\t\t\tcrons?: boolean | AWSLambdaConfig;\n\t\t};\n\t};\n\tserver?: boolean | ServerConfig;\n\t/** Dokploy deployment configuration */\n\tdokploy?: boolean | DokployProviderConfig;\n}\n\nexport interface GkmConfig {\n\t/**\n\t * What this app is called, and therefore how everything it declares is named.\n\t *\n\t * The same field `sst.config.ts` carries, doing the same job: every physical\n\t * name is `{stage}-{name}-{construct}`, so `Database` in the `production`\n\t * stage of `kitchen-sink` is `production-kitchen-sink-database` on AWS and on\n\t * Dokploy alike. One statement, in the config, rather than a rule each\n\t * provider restates.\n\t *\n\t * `defineWorkspace` has always had this. A single-app config did not, so its\n\t * names fell back to whatever the directory happened to be called — the\n\t * monorepo's package name for the project and the app's for the application,\n\t * two accidents of layout standing in for a decision.\n\t *\n\t * Defaults to the package.json name with any scope stripped, then the\n\t * directory name.\n\t */\n\tname?: string;\n\t/** The project's stages: which one is local, which deploy */\n\tstages: StagesConfig;\n\t/** Encrypted secrets, and where deployed stages' secrets are stored */\n\tsecrets?: SecretsConfig;\n\t/** What `gkm test` hands a feature test: where the factories are. */\n\ttest?: TestConfig;\n\t/**\n\t * Constructs glob pattern — one glob, every kind.\n\t *\n\t * What reconcile reads to derive the containers a stage needs: a database\n\t * implies Postgres, objects imply MinIO, mail implies Mailpit. A glob per\n\t * kind cannot find a resource, because a declared `ObjectStorage` has no\n\t * kind to be listed under.\n\t *\n\t * @example './src/**\\/*.ts'\n\t */\n\tconstructs: Routes;\n\tproviders?: ProvidersConfig;\n\t/**\n\t * Server lifecycle hooks for customizing the Hono app.\n\t * Allows adding custom routes, middleware, error handlers, etc.\n\t *\n\t * @example\n\t * hooks: {\n\t * server: './src/config/hooks'\n\t * }\n\t */\n\thooks?: HooksConfig;\n\t/**\n\t * Telescope configuration for debugging/monitoring.\n\t * Can be:\n\t * - A string path to a module that exports a Telescope instance (recommended)\n\t * - A boolean to enable/disable with defaults\n\t * - A TelescopeConfig object for inline configuration\n\t */\n\ttelescope?: string | boolean | TelescopeConfig;\n\t/**\n\t * Studio configuration for database browsing.\n\t * Can be:\n\t * - A string path to a module that exports a Studio instance (recommended)\n\t * - A boolean to enable/disable with defaults\n\t * - A StudioConfig object for inline configuration\n\t *\n\t * Requires a database connection configured via services.\n\t */\n\tstudio?: string | boolean | StudioConfig;\n\t/**\n\t * OpenAPI generation configuration.\n\t * Can be:\n\t * - A boolean to enable/disable with defaults (output: ./src/api/openapi.ts)\n\t * - An OpenApiConfig object for customization\n\t *\n\t * When enabled, OpenAPI spec is generated on startup and regenerated on route changes.\n\t *\n\t * @example\n\t * openapi: true\n\t *\n\t * @example\n\t * openapi: {\n\t * output: './src/api/openapi.ts',\n\t * title: 'My API',\n\t * version: '1.0.0',\n\t * }\n\t */\n\topenapi?: boolean | OpenApiConfig;\n\t/** Runtime to use for dev server (default: 'node') */\n\truntime?: Runtime;\n\t/**\n\t * Environment file(s) to load for development.\n\t * Can be:\n\t * - A string path to a single env file (e.g., '.env.local')\n\t * - An array of paths to load in order (later files override earlier)\n\t * - Defaults to '.env' if not specified\n\t *\n\t * @example\n\t * env: '.env.local'\n\t *\n\t * @example\n\t * env: ['.env', '.env.local']\n\t */\n\tenv?: string | string[];\n\t/**\n\t * Docker deployment configuration.\n\t * Used by `gkm docker` and `gkm prepack` commands.\n\t *\n\t * @example\n\t * docker: {\n\t * registry: 'ghcr.io/myorg',\n\t * imageName: 'my-api',\n\t * compose: {\n\t * services: ['postgres', 'redis']\n\t * }\n\t * }\n\t */\n\tdocker?: DockerConfig;\n\t/**\n\t * Where this app deploys, and what the target needs to know.\n\t *\n\t * Absent until now, which meant a single-app project could not configure a\n\t * Dokploy deploy at all: the wrap that turns one into a workspace hardcoded\n\t * `{ default: 'dokploy' }` and dropped everything else, so there was no\n\t * endpoint, no registry and no domain — and `resolveHost` failed with \"no\n\t * domain configured for stage\" on a config that had nowhere to put one.\n\t *\n\t * The same shape a workspace config uses, so moving from one to the other is\n\t * a rename rather than a rewrite.\n\t */\n\tdeploy?: import('./workspace/types').DeployConfig;\n}\n\nexport interface BuildOptions {\n\tprovider?: MainProvider;\n\t// Legacy support - will be deprecated\n\tproviders?: LegacyProvider[];\n\tenableOpenApi?: boolean;\n\t/** Build for production (no dev tools, bundled output) */\n\tproduction?: boolean;\n\t/** Skip bundling step in production build */\n\tskipBundle?: boolean;\n\t/** Stage for secrets injection (e.g., 'production', 'staging') */\n\tstage?: string;\n\t/**\n\t * When true, optional environment variables (those with `.optional()` or\n\t * `.default()` in the envParser) are suffixed with `?` within each\n\t * construct's `environment` array in the manifest (e.g. `PORT?`).\n\t * Defaults to false.\n\t */\n\tmarkOptional?: boolean;\n}\n\n/** Result from build command when secrets are injected */\nexport interface BuildResult {\n\t/** Ephemeral master key for deployment (only if stage was provided) */\n\tmasterKey?: string;\n}\n\n// The deployment manifest types are a shared, dependency-free data contract\n// in `@geekmidas/manifest`, re-exported here for back-compat with existing\n// `@geekmidas/cli` imports.\nexport type {\n\tCronInfo,\n\tFunctionInfo,\n\tManifest,\n\tQueueInfo,\n\tRouteInfo,\n\tSubscriberInfo,\n\tTopicInfo,\n} from '@geekmidas/manifest';\n","import { relative } from 'node:path';\nimport type { Construct } from '@geekmidas/constructs';\nimport fg from 'fast-glob';\nimport kebabCase from 'lodash.kebabcase';\nimport type { BuildContext } from '../build/types';\nimport {\n\tisPartitionedRoutes,\n\ttype LegacyProvider,\n\ttype Routes,\n} from '../types';\n\n/**\n * Zod v4 maintains a process-wide registry of schemas registered via\n * `.meta({ id })`. When we cache-bust user module imports (by appending a\n * `?t=timestamp` query), those modules re-execute and try to register the\n * same ids again, which throws `ID X already exists in the registry`.\n *\n * Clearing the registry in the CLI process is safe: the user's running\n * server lives in a subprocess and has its own isolated registry.\n *\n * Exported for tests.\n */\nexport function clearZodGlobalRegistry(): void {\n\tconst registry = (\n\t\tglobalThis as { __zod_globalRegistry?: { clear?: () => void } }\n\t).__zod_globalRegistry;\n\tif (registry && typeof registry.clear === 'function') {\n\t\tregistry.clear();\n\t}\n}\n\nexport interface GeneratorOptions {\n\tprovider?: LegacyProvider;\n\t[key: string]: any;\n}\n\nexport abstract class ConstructGenerator<T extends Construct, R = void> {\n\tabstract isConstruct(value: any): value is T;\n\n\tstatic async build<T extends Construct, R = void>(\n\t\tcontext: BuildContext,\n\t\toutputDir: string,\n\t\tgenerator: ConstructGenerator<T, R>,\n\t\tpatterns?: Routes,\n\t\toptions?: GeneratorOptions,\n\t): Promise<R> {\n\t\tconst constructs = await generator.load(patterns);\n\t\treturn generator.build(context, constructs, outputDir, options);\n\t}\n\n\tabstract build(\n\t\tcontext: BuildContext,\n\t\tconstructs: GeneratedConstruct<T>[],\n\t\toutputDir: string,\n\t\toptions?: GeneratorOptions,\n\t): Promise<R>;\n\n\tasync load(\n\t\tpatterns?: Routes,\n\t\tcwd = process.cwd(),\n\t\tbustCache = false,\n\t): Promise<GeneratedConstruct<T>[]> {\n\t\tconst logger = console;\n\n\t\t// Extract glob patterns and optional partition function\n\t\tlet globPatterns: string[];\n\t\tlet partitionFn: ((filepath: string) => string) | undefined;\n\n\t\tif (isPartitionedRoutes(patterns)) {\n\t\t\tglobPatterns = Array.isArray(patterns.paths)\n\t\t\t\t? patterns.paths\n\t\t\t\t: [patterns.paths];\n\t\t\tpartitionFn = patterns.partition;\n\t\t} else {\n\t\t\tglobPatterns = Array.isArray(patterns)\n\t\t\t\t? patterns\n\t\t\t\t: patterns\n\t\t\t\t\t? [patterns]\n\t\t\t\t\t: [];\n\t\t}\n\n\t\t// Find all files\n\t\tconst files = fg.stream(globPatterns, {\n\t\t\tcwd,\n\t\t\tabsolute: true,\n\t\t});\n\n\t\t// Cache-busted re-imports re-execute user modules, which would try to\n\t\t// re-register Zod schemas with the same `.meta({ id })`. Clear the\n\t\t// global registry once per load to prevent duplicate-id errors.\n\t\tif (bustCache) {\n\t\t\tclearZodGlobalRegistry();\n\t\t}\n\n\t\t// Load constructs\n\t\tconst constructs: GeneratedConstruct<T>[] = [];\n\n\t\tfor await (const f of files) {\n\t\t\ttry {\n\t\t\t\tconst file = f.toString();\n\t\t\t\t// Append cache-busting query param to force re-import of changed modules\n\t\t\t\tconst importPath = bustCache ? `${file}?t=${Date.now()}` : file;\n\t\t\t\tconst module = await import(importPath);\n\n\t\t\t\t// Compute partition name for this file (if partition function provided)\n\t\t\t\tconst partition = partitionFn ? partitionFn(file) : undefined;\n\n\t\t\t\t// Check all exports for constructs\n\t\t\t\tfor (const [key, construct] of Object.entries(module)) {\n\t\t\t\t\tif (this.isConstruct(construct)) {\n\t\t\t\t\t\tconstructs.push({\n\t\t\t\t\t\t\tkey,\n\t\t\t\t\t\t\tname: kebabCase(key),\n\t\t\t\t\t\t\tconstruct,\n\t\t\t\t\t\t\tpath: {\n\t\t\t\t\t\t\t\tabsolute: file,\n\t\t\t\t\t\t\t\trelative: relative(process.cwd(), file),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tpartition,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconst err = error as Error;\n\t\t\t\tlogger.error(`Failed to load ${f}: ${err.message}`);\n\t\t\t\tif (err.stack) {\n\t\t\t\t\tlogger.error(err.stack);\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\treturn constructs;\n\t}\n}\n\nexport interface GeneratedConstruct<T extends Construct> {\n\tkey: string;\n\tname: string;\n\tconstruct: T;\n\tpath: {\n\t\tabsolute: string;\n\t\trelative: string;\n\t};\n\t/** Partition name assigned by the partition function, if configured. */\n\tpartition?: string;\n}\n","/**\n * Discovery — one glob, one structural test.\n *\n * A glob per kind is the same specialness the construct model removes, and\n * resources have no kind to be listed under: a declared `ObjectStorage` would\n * never be found by `routes:` or `crons:`. So there is one `constructs` glob,\n * every export of every matching module is inspected, and the per-kind\n * `isConstruct` checks collapse into a single question — does it have an id and\n * can it declare?\n *\n * Structural rather than `instanceof` on purpose. A construct from a different\n * copy of `@geekmidas/constructs` — a linked workspace, two versions in a\n * lockfile — is still a construct, and `instanceof` is exactly the check that\n * says otherwise.\n */\n\nimport { relative } from 'node:path';\nimport {\n\tassertDerivations,\n\ttype ConstructManifest,\n\tcanonicalId,\n\ttype Declaration,\n} from '@geekmidas/manifest';\nimport fg from 'fast-glob';\nimport { clearZodGlobalRegistry } from '../generators/Generator';\n\n/** Matches the rest of the CLI, which logs through `console` directly. */\nconst logger = console;\n\n/** The construct face discovery needs: an id, and the ability to declare. */\ninterface Declarable {\n\tid: string;\n\tdeclare(): Declaration[];\n}\n\n/**\n * Whether a module export is a construct.\n *\n * Deliberately loose about what else it carries — a construct that also has a\n * `service` is still a construct, and requiring one would exclude the kinds that\n * own no address.\n */\nexport function isDeclarable(value: unknown): value is Declarable {\n\tif (typeof value !== 'object' || value === null) return false;\n\n\tconst candidate = value as Partial<Declarable>;\n\n\treturn (\n\t\ttypeof candidate.id === 'string' &&\n\t\tcandidate.id.length > 0 &&\n\t\ttypeof candidate.declare === 'function'\n\t);\n}\n\nexport interface DiscoverOptions {\n\t/** The glob(s) to search. One pattern, not one per kind. */\n\tpatterns: string | readonly string[];\n\tcwd?: string;\n\t/**\n\t * Re-import changed modules, for the dev watcher.\n\t *\n\t * A single glob makes discovery more central, not less: a new construct file\n\t * has to be picked up on `add`, not only on `change`.\n\t */\n\tbustCache?: boolean;\n\t/**\n\t * Filled in with where each construct was found, when provided.\n\t *\n\t * Discovery is the only thing that knows: it globbed the files and imported\n\t * them. Everything downstream that needs to *write* an import — the build,\n\t * generating a server entry that reads a surface's logger — would otherwise\n\t * have to be told the path in config, which is the string this removes.\n\t *\n\t * An out-parameter rather than a second return value so that every existing\n\t * caller, which wants the manifest and nothing else, is unchanged.\n\t */\n\tsources?: Record<string, ConstructSource>;\n\t/**\n\t * Filled in, when provided, with the ids each owner's runnables declared an\n\t * edge to — keyed by the surface an endpoint was built from, or the worker\n\t * a cron or subscriber was.\n\t *\n\t * A runnable is not a declaration: a surface's endpoints are found by the\n\t * glob, not listed by the surface, so its node says `endpoints: []` until a\n\t * build folds the routes in. Anything deciding what an app's process reaches\n\t * — which keys its container is given — needs those edges before any build,\n\t * and discovery is already importing the files they are in.\n\t */\n\trunnables?: Record<string, string[]>;\n}\n\n/** Where a construct was declared, and under what name. */\nexport interface ConstructSource {\n\t/** Absolute path to the module that exported it. */\n\tfile: string;\n\t/** The name it was exported as — what an import statement has to say. */\n\texportName: string;\n\t/**\n\t * The exported construct itself.\n\t *\n\t * What a declaration cannot say — whether it serves itself — the object can\n\t * be asked, and discovery already imported it.\n\t */\n\tconstruct: unknown;\n}\n\n/**\n * Build the construct manifest for a project.\n *\n * Runs the manifest's own validation before returning, so an app is wrong here\n * — where the error names a file — rather than at deploy.\n *\n * @throws {DuplicateConstruct} when two exports claim one id.\n */\nexport async function discover(\n\toptions: DiscoverOptions,\n): Promise<ConstructManifest> {\n\tconst {\n\t\tpatterns,\n\t\tcwd = process.cwd(),\n\t\tbustCache = false,\n\t\tsources: out,\n\t} = options;\n\n\t// Re-importing user modules re-executes them, and Zod v4 throws on a\n\t// re-registered `.meta({ id })`. Same reason the generators clear it.\n\tif (bustCache) clearZodGlobalRegistry();\n\n\tconst manifest: Record<string, Declaration> = {};\n\t/** Which file declared each id, for the error when two of them do. */\n\tconst sources: Record<string, string> = {};\n\t/**\n\t * Constructs already seen, by identity.\n\t *\n\t * A re-export is the *same object* reached through a second file — ESM\n\t * bindings are live, so `export * from './database.js'` hands back the\n\t * binding rather than a copy. Without this, a barrel file made every\n\t * construct in it appear declared twice, and `constructs/index.ts` is the\n\t * first thing anyone writes in a shared folder.\n\t *\n\t * Identity rather than file is also the more honest rule: what may not\n\t * happen twice is two *different* constructs claiming one id, and that is\n\t * still an error below.\n\t */\n\tconst seen = new WeakSet<object>();\n\n\tconst globs = Array.isArray(patterns) ? [...patterns] : [patterns as string];\n\tconst files = fg.stream(globs, { cwd, absolute: true });\n\n\t/** Matched files, for the diagnostic below. */\n\tlet matched = 0;\n\n\tfor await (const found of files) {\n\t\tmatched++;\n\t\tconst file = found.toString();\n\t\tconst module = await import(bustCache ? `${file}?t=${Date.now()}` : file);\n\n\t\tfor (const [exportName, exported] of Object.entries(module)) {\n\t\t\tif (!isDeclarable(exported)) {\n\t\t\t\tif (options.runnables) recordRunnable(options.runnables, exported);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// The same construct, reached again through a re-export. Skipped\n\t\t\t// rather than re-declared: it has already claimed its id, from the\n\t\t\t// file that defined it.\n\t\t\tif (seen.has(exported as object)) continue;\n\t\t\tseen.add(exported as object);\n\n\t\t\t// A construct that is also something a process runs — a queue and its\n\t\t\t// consumer, built from a worker — reaches what it depends on through\n\t\t\t// that process, exactly as an endpoint or a cron does.\n\t\t\tif (\n\t\t\t\toptions.runnables &&\n\t\t\t\ttypeof (exported as { owner?: unknown }).owner === 'string'\n\t\t\t) {\n\t\t\t\trecordRunnable(options.runnables, exported);\n\t\t\t}\n\n\t\t\tfor (const declaration of exported.declare()) {\n\t\t\t\t// Canonicalise here too: a construct built by hand rather than through\n\t\t\t\t// a constructor is still subject to the same identity rule.\n\t\t\t\tconst id = canonicalId(declaration.id);\n\t\t\t\tconst source = relative(cwd, file);\n\n\t\t\t\tconst claimed = sources[id];\n\t\t\t\tif (claimed !== undefined && claimed !== source) {\n\t\t\t\t\tthrow new DuplicateConstruct(id, [claimed, source]);\n\t\t\t\t}\n\n\t\t\t\tmanifest[id] = { ...declaration, id };\n\t\t\t\tsources[id] = source;\n\t\t\t\tif (out) out[id] = { file, exportName, construct: exported };\n\t\t\t}\n\t\t}\n\t}\n\n\t// Reference integrity before anyone reads it — a schema tenant naming a\n\t// database that was deleted is a manifest error, not a reconcile failure.\n\tassertDerivations(manifest);\n\n\twarnIfNothingFound(globs, matched, Object.keys(manifest).length);\n\n\treturn manifest;\n}\n\n/**\n * Record a runnable's edges under the construct that owns it — the surface an\n * endpoint was built from, or the worker a cron or subscriber was. Anything\n * with no owner, or that is not a runnable at all, is left alone.\n */\nfunction recordRunnable(\n\trunnables: Record<string, string[]>,\n\texported: unknown,\n): void {\n\tif (typeof exported !== 'object' || exported === null) return;\n\n\tconst runnable = exported as {\n\t\tsurface?: { id?: unknown };\n\t\towner?: unknown;\n\t\tconstructs?: unknown;\n\t};\n\tconst owner =\n\t\ttypeof runnable.surface?.id === 'string'\n\t\t\t? runnable.surface.id\n\t\t\t: typeof runnable.owner === 'string'\n\t\t\t\t? runnable.owner\n\t\t\t\t: undefined;\n\tif (!owner || !Array.isArray(runnable.constructs)) return;\n\n\tconst key = canonicalId(owner);\n\tconst edges = runnables[key] ?? [];\n\trunnables[key] = edges;\n\tfor (const id of runnable.constructs) {\n\t\tif (typeof id !== 'string') continue;\n\t\tconst target = canonicalId(id);\n\t\tif (!edges.includes(target)) edges.push(target);\n\t}\n}\n\n/**\n * Say so when a glob was configured and found nothing.\n *\n * The glob is not a filter over declarations already known — it is *how they\n * are found*, by importing what it matches. So a file it misses is a\n * declaration that does not exist, and every symptom of that appears somewhere\n * other than the cause: no container starts, no env key is written, and the\n * application fails on first use against a resource it can see in its own\n * source.\n *\n * Nothing reported it before, because an unmatched file is indistinguishable\n * from a file nobody wrote. These two cases are the ones that are *not*\n * ambiguous — a glob was configured, so something was expected.\n *\n * Silent when no glob is configured at all: that is a project which has not\n * adopted constructs, and has nothing to be missing.\n */\nfunction warnIfNothingFound(\n\tglobs: readonly string[],\n\tmatched: number,\n\tdeclared: number,\n): void {\n\tif (globs.length === 0) return;\n\n\tif (matched === 0) {\n\t\tlogger.warn(\n\t\t\t`\\n⚠️ The constructs glob matched no files, so nothing was declared.\\n` +\n\t\t\t\t` Patterns: ${globs.join(', ')}\\n` +\n\t\t\t\t` Nothing will be provisioned — no containers, no env keys, no ` +\n\t\t\t\t`resources.\\n` +\n\t\t\t\t` A common cause is depth: '*.ts' matches one level, '**/*.ts' ` +\n\t\t\t\t`matches any.`,\n\t\t);\n\t\treturn;\n\t}\n\n\tif (declared === 0) {\n\t\tlogger.warn(\n\t\t\t`\\n⚠️ The constructs glob matched ${matched} file(s) but none declared ` +\n\t\t\t\t`anything.\\n` +\n\t\t\t\t` Patterns: ${globs.join(', ')}\\n` +\n\t\t\t\t` Discovery keeps exports with an 'id' that can 'declare()'. A ` +\n\t\t\t\t`construct that is\\n` +\n\t\t\t\t` built but never exported is invisible to it.`,\n\t\t);\n\t}\n}\n\n/** Two constructs claiming one id. */\nexport class DuplicateConstruct extends Error {\n\t/** The id claimed twice. */\n\treadonly id: string;\n\t/** The files that claimed it. */\n\treadonly sources: readonly string[];\n\n\tconstructor(id: string, sources: readonly string[]) {\n\t\tsuper('Two constructs declare the same id');\n\t\tthis.name = 'DuplicateConstruct';\n\t\tthis.id = id;\n\t\tthis.sources = sources;\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"discover-BLhf7V_J.mjs","names":["kebabCase"],"sources":["../src/types.ts","../src/generators/Generator.ts","../src/reconcile/discover.ts"],"sourcesContent":["import type {\n\tSecretsConfig,\n\tStagesConfig,\n\tTestConfig,\n} from './workspace/types.js';\n\nexport type MainProvider = 'aws' | 'server';\nexport type LegacyProvider =\n\t| 'server'\n\t| 'aws-apigatewayv1'\n\t| 'aws-apigatewayv2'\n\t| 'aws-lambda';\n\nexport interface PartitionedRoutes {\n\tpaths: string | string[];\n\tpartition: (filepath: string) => string;\n}\n\nexport type Routes = string | string[] | PartitionedRoutes;\n\nexport function isPartitionedRoutes(\n\troutes: Routes | undefined,\n): routes is PartitionedRoutes {\n\treturn (\n\t\ttypeof routes === 'object' &&\n\t\troutes !== null &&\n\t\t!Array.isArray(routes) &&\n\t\t'paths' in routes\n\t);\n}\n\nexport interface ProviderConfig {\n\tenabled?: boolean;\n\toutputDir?: string;\n}\n\nexport interface AWSApiGatewayConfig extends ProviderConfig {\n\t// Additional AWS API Gateway specific options\n}\n\nexport interface AWSLambdaConfig extends ProviderConfig {\n\t// Additional AWS Lambda specific options\n}\n\nexport interface ProductionConfig {\n\t/** Enable production mode (default: false) */\n\tenabled?: boolean;\n\t/** Bundle server into single file (default: true) */\n\tbundle?: boolean;\n\t/** Minify bundled output (default: true) */\n\tminify?: boolean;\n\t/** Health check endpoint path (default: '/health') */\n\thealthCheck?: string;\n\t/** Enable graceful shutdown handling (default: true) */\n\tgracefulShutdown?: boolean;\n\t/** Packages to exclude from bundling (default: []) */\n\texternal?: string[];\n\t/** Include subscribers in production build (default: 'exclude' for serverless) */\n\tsubscribers?: 'include' | 'exclude';\n\t/** Include OpenAPI spec in production (default: false) */\n\topenapi?: boolean;\n\t/**\n\t * Enable build-time optimized handler generation (default: true)\n\t * Generates specialized handlers based on endpoint tier:\n\t * - minimal: Near-raw-Hono performance for simple endpoints\n\t * - standard: Optimized handlers for auth/services\n\t * - full: Uses HonoEndpoint.addRoutes for complex endpoints\n\t */\n\toptimizedHandlers?: boolean;\n}\n\n/**\n * Supported event backend types.\n *\n * - `pgboss`: Reuses PostgreSQL with a dedicated user/schema. Auto-enables db service.\n * - `sns`: Adds the AWS emulator (floci) container; each topic and queue is\n * created on it and addressed by its own string.\n * - `rabbitmq`: Adds RabbitMQ container.\n *\n * Each topic and queue resolves its own `<ID>_PUBLISHER_CONNECTION_STRING`,\n * which its producers and its consumers both read. pg-boss and RabbitMQ also\n * resolve `EVENT_PUBLISHER_CONNECTION_STRING`, the one broker crons schedule\n * through.\n */\nexport type EventsBackend = 'pgboss' | 'sns' | 'rabbitmq';\n\n/**\n * Where a declared cache actually lives.\n *\n * A deployment choice, not an application one — the same code caches into any\n * of these — so it is config here beside `events` rather than a field on the\n * construct. Each backend is consistent between local and deployed, which is\n * the property worth having: a cache that behaves differently in the two places\n * is worse than a slower one.\n *\n * - `upstash` (default) — HTTP with a token. Reachable from a Lambda with no\n * VPC and no connection pool, which is why it is the default; locally the\n * `serverless-redis-http` proxy in front of Redis speaks the same protocol.\n * - `elasticache` — the Redis wire protocol, inside a VPC. Locally, plain Redis.\n * - `db` — a table in the database the app already declared. No infrastructure\n * at all, and the same relationship pg-boss has to Postgres: it creates its\n * own table on first use and needs a declared database to live in.\n */\nexport type CacheBackend = 'upstash' | 'elasticache' | 'db';\n\n/**\n * Where a declared bucket actually lives.\n *\n * The fourth of these selectors and the last to arrive, which is why\n * `UPLOADS_URL` had no answer on a container target: there was nothing to\n * choose with. Like the others it changes no application code — the `s3://`\n * scheme picks the driver, and every one of these speaks it.\n *\n * - `s3` — a real bucket. Nothing is created on the host; the URL is composed\n * against credentials you already hold, the same way `resend` is an API key\n * rather than infrastructure.\n * - `r2` — Cloudflare's, reached through the same S3 API and differing only in\n * its endpoint.\n * - `minio` — a container. Everything the app declares lives on the box, which\n * is what makes a single-host deploy self-contained, and what makes it a\n * single disk with no replication. Locally this is what every backend is.\n */\nexport type StorageBackend = 'minio' | 's3' | 'r2';\n\n/**\n * The backend a project gets when it says nothing, by where it deploys.\n *\n * Flat defaults could not be right for both: `upstash` on a single box means a\n * SaaS round-trip for a cache the database could have held, and `minio` on AWS\n * means running a bucket beside S3. So the default is a function of the target,\n * and an app that says nothing gets the obvious answer in both places — which\n * is the whole of what \"zero config\" can mean once there is more than one\n * target.\n *\n * `server` covers Dokploy and a bare host alike: the question these answer is\n * whether the app is next to a managed cloud or next to its own containers, and\n * on that question the two are the same.\n *\n * A per-construct, per-stage override is the layer above this — see\n * `constructs-outstanding.md` — so two buckets in one app can differ. This is\n * only what happens when nobody has said anything at all.\n */\nexport const DEFAULT_CACHE: Record<MainProvider, CacheBackend> = {\n\t// Reachable from a Lambda with no VPC and no connection pool.\n\taws: 'upstash',\n\t// The database is already there and already has a connection pool open to\n\t// it. Anything else is a second service to run for a table.\n\tserver: 'db',\n};\n\n/**\n * Which broker carries a declared queue or topic, by target.\n *\n * Whether there are events at all is the manifest's answer — a declared\n * `Topic` or `Queue` — so this only ever answers the second question. AWS\n * deploys them to SNS and SQS, and locally the AWS emulator stands in, so the\n * broker a developer runs is the one production has. A server has a Postgres\n * already, and pg-boss keeps its queues there.\n */\nexport const DEFAULT_EVENTS: Record<MainProvider, EventsBackend> = {\n\taws: 'sns',\n\tserver: 'pgboss',\n};\n\n/** Where a bucket lives when nobody said — see {@link DEFAULT_CACHE}. */\nexport const DEFAULT_STORAGE: Record<MainProvider, StorageBackend> = {\n\taws: 's3',\n\tserver: 'minio',\n};\n\n/**\n * Who delivers a declared app's mail.\n *\n * Unlike a cache, this changes *nothing* about the client. Every backend speaks\n * SMTP, so the declaration's `smtp://` URL is true of all of them and only the\n * provisioner differs — which is the same reason the declaration has no\n * `provider` field and there is no `ses://` scheme.\n *\n * - `ses` (default) — the only backend that can be a *chain* of resources: an\n * identity, a user, an access key, and an SMTP password derived from it,\n * because SES issues IAM credentials rather than SMTP ones. Hand it a URL and\n * it uses that instead, which is what you want when the credentials already\n * exist — provisioning a second set of sending credentials for an identity\n * that already has them is not a thing to do by default.\n * - `resend` — an API key you hold, composed into an SMTP URL. Nothing to\n * provision: a SaaS account is not infrastructure.\n * - `smtp` — a URL supplied whole, for a relay somebody else runs.\n *\n * Locally every one of them is Mailpit, because locally none of it matters.\n */\nexport type EmailBackend = 'resend' | 'ses' | 'smtp';\n\n/**\n * Who delivers mail when nobody said.\n *\n * Flat, unlike the others, and deliberately: mail is a SaaS on every target.\n * There is no self-hosted preset to default a container deploy to — Mailpit is\n * a development inbox, not a sender — so `server` has no answer of its own to\n * give and would only be `ses` written twice.\n */\nexport const DEFAULT_EMAIL: EmailBackend = 'ses';\n\n/** Supported docker-compose service names */\nexport type ComposeServiceName =\n\t| 'postgres'\n\t| 'redis'\n\t| 'rabbitmq'\n\t| 'minio'\n\t| 'mailpit'\n\t| 'localstack';\n\nexport interface DockerConfig {\n\t/** Container registry URL (e.g., 'ghcr.io/myorg') */\n\tregistry?: string;\n\t/** Docker image name (default: derived from package.json name) */\n\timageName?: string;\n\t/** Base Docker image (default: 'node:22-alpine') */\n\tbaseImage?: string;\n\t/** Container port (default: 3000) */\n\tport?: number;\n}\n\nexport interface ServerConfig extends ProviderConfig {\n\tenableOpenApi?: boolean;\n\tport?: number;\n\t/** Production build configuration */\n\tproduction?: ProductionConfig;\n}\n\nexport type Runtime = 'node' | 'bun';\n\nexport interface TelescopeConfig {\n\t/** Enable/disable telescope (default: true in development) */\n\tenabled?: boolean;\n\t/** Port for telescope to use (defaults to server port) */\n\tport?: number;\n\t/** Path prefix for telescope UI (default: /__telescope) */\n\tpath?: string;\n\t/** Ignore patterns for telescope (e.g., ['/health', '/metrics']) */\n\tignore?: string[];\n\t/** Record request/response bodies (default: true) */\n\trecordBody?: boolean;\n\t/** Maximum entries to keep in memory (default: 1000) */\n\tmaxEntries?: number;\n\t/** Enable WebSocket for real-time updates (default: true, requires @hono/node-ws for Node.js) */\n\twebsocket?: boolean;\n}\n\nexport interface StudioConfig {\n\t/** Enable/disable studio (default: true in development) */\n\tenabled?: boolean;\n\t/** Path prefix for studio UI (default: /__studio) */\n\tpath?: string;\n\t/** Schema to introspect (default: 'public') */\n\tschema?: string;\n}\n\nexport interface OpenApiConfig {\n\t/** Enable OpenAPI generation (default: true) */\n\tenabled?: boolean;\n\t/** API title */\n\ttitle?: string;\n\t/** API version */\n\tversion?: string;\n\t/** API description */\n\tdescription?: string;\n}\n\nexport interface HooksConfig {\n\t/**\n\t * Path to a module exporting server lifecycle hooks.\n\t * The module should export `beforeSetup` and/or `afterSetup` functions.\n\t *\n\t * @example\n\t * ```typescript\n\t * // src/config/hooks.ts\n\t * import type { Hono } from 'hono';\n\t * import type { Logger } from '@geekmidas/logger';\n\t * import type { EnvironmentParser } from '@geekmidas/envkit';\n\t *\n\t * // Called BEFORE gkm endpoints are registered\n\t * export function beforeSetup(app: Hono, ctx: { envParser: EnvironmentParser; logger: Logger }) {\n\t * app.use('*', cors());\n\t * app.get('/custom/health', (c) => c.json({ status: 'ok' }));\n\t * }\n\t *\n\t * // Called AFTER gkm endpoints are registered\n\t * export function afterSetup(app: Hono, ctx: { envParser: EnvironmentParser; logger: Logger }) {\n\t * app.notFound((c) => c.json({ error: 'Not found' }, 404));\n\t * app.onError((err, c) => c.json({ error: err.message }, 500));\n\t * }\n\t * ```\n\t */\n\tserver?: string;\n}\n\n/** Dokploy deployment configuration */\nexport interface DokployProviderConfig {\n\t/** Dokploy API endpoint (e.g., 'https://dokploy.example.com') */\n\tendpoint: string;\n\t/** Project ID in Dokploy */\n\tprojectId: string;\n\t/** Application ID in Dokploy */\n\tapplicationId: string;\n\t/** Container registry (overrides docker.registry if set) */\n\tregistry?: string;\n\t/** Registry ID in Dokploy (recommended for private registries) */\n\tregistryId?: string;\n}\n\nexport interface ProvidersConfig {\n\taws?: {\n\t\tapiGateway?: {\n\t\t\tv1?: boolean | AWSApiGatewayConfig;\n\t\t\tv2?: boolean | AWSApiGatewayConfig;\n\t\t};\n\t\tlambda?: {\n\t\t\tfunctions?: boolean | AWSLambdaConfig;\n\t\t\tcrons?: boolean | AWSLambdaConfig;\n\t\t};\n\t};\n\tserver?: boolean | ServerConfig;\n\t/** Dokploy deployment configuration */\n\tdokploy?: boolean | DokployProviderConfig;\n}\n\nexport interface GkmConfig {\n\t/**\n\t * What this app is called, and therefore how everything it declares is named.\n\t *\n\t * The same field `sst.config.ts` carries, doing the same job: every physical\n\t * name is `{stage}-{name}-{construct}`, so `Database` in the `production`\n\t * stage of `kitchen-sink` is `production-kitchen-sink-database` on AWS and on\n\t * Dokploy alike. One statement, in the config, rather than a rule each\n\t * provider restates.\n\t *\n\t * `defineWorkspace` has always had this. A single-app config did not, so its\n\t * names fell back to whatever the directory happened to be called — the\n\t * monorepo's package name for the project and the app's for the application,\n\t * two accidents of layout standing in for a decision.\n\t *\n\t * Defaults to the package.json name with any scope stripped, then the\n\t * directory name.\n\t */\n\tname?: string;\n\t/** The project's stages: which one is local, which deploy */\n\tstages: StagesConfig;\n\t/** Encrypted secrets, and where deployed stages' secrets are stored */\n\tsecrets?: SecretsConfig;\n\t/** What `gkm test` hands a feature test: where the factories are. */\n\ttest?: TestConfig;\n\t/**\n\t * Constructs glob pattern — one glob, every kind.\n\t *\n\t * What reconcile reads to derive the containers a stage needs: a database\n\t * implies Postgres, objects imply MinIO, mail implies Mailpit. A glob per\n\t * kind cannot find a resource, because a declared `ObjectStorage` has no\n\t * kind to be listed under.\n\t *\n\t * @example './src/**\\/*.ts'\n\t */\n\tconstructs: Routes;\n\tproviders?: ProvidersConfig;\n\t/**\n\t * Server lifecycle hooks for customizing the Hono app.\n\t * Allows adding custom routes, middleware, error handlers, etc.\n\t *\n\t * @example\n\t * hooks: {\n\t * server: './src/config/hooks'\n\t * }\n\t */\n\thooks?: HooksConfig;\n\t/**\n\t * Telescope configuration for debugging/monitoring.\n\t * Can be:\n\t * - A string path to a module that exports a Telescope instance (recommended)\n\t * - A boolean to enable/disable with defaults\n\t * - A TelescopeConfig object for inline configuration\n\t */\n\ttelescope?: string | boolean | TelescopeConfig;\n\t/**\n\t * Studio configuration for database browsing.\n\t * Can be:\n\t * - A string path to a module that exports a Studio instance (recommended)\n\t * - A boolean to enable/disable with defaults\n\t * - A StudioConfig object for inline configuration\n\t *\n\t * Requires a database connection configured via services.\n\t */\n\tstudio?: string | boolean | StudioConfig;\n\t/**\n\t * OpenAPI generation configuration.\n\t * Can be:\n\t * - A boolean to enable/disable with defaults (output: ./src/api/openapi.ts)\n\t * - An OpenApiConfig object for customization\n\t *\n\t * When enabled, OpenAPI spec is generated on startup and regenerated on route changes.\n\t *\n\t * @example\n\t * openapi: true\n\t *\n\t * @example\n\t * openapi: {\n\t * output: './src/api/openapi.ts',\n\t * title: 'My API',\n\t * version: '1.0.0',\n\t * }\n\t */\n\topenapi?: boolean | OpenApiConfig;\n\t/** Runtime to use for dev server (default: 'node') */\n\truntime?: Runtime;\n\t/**\n\t * Environment file(s) to load for development.\n\t * Can be:\n\t * - A string path to a single env file (e.g., '.env.local')\n\t * - An array of paths to load in order (later files override earlier)\n\t * - Defaults to '.env' if not specified\n\t *\n\t * @example\n\t * env: '.env.local'\n\t *\n\t * @example\n\t * env: ['.env', '.env.local']\n\t */\n\tenv?: string | string[];\n\t/**\n\t * Docker deployment configuration.\n\t * Used by `gkm docker` and `gkm prepack` commands.\n\t *\n\t * @example\n\t * docker: {\n\t * registry: 'ghcr.io/myorg',\n\t * imageName: 'my-api',\n\t * compose: {\n\t * services: ['postgres', 'redis']\n\t * }\n\t * }\n\t */\n\tdocker?: DockerConfig;\n\t/**\n\t * Where this app deploys, and what the target needs to know.\n\t *\n\t * Absent until now, which meant a single-app project could not configure a\n\t * Dokploy deploy at all: the wrap that turns one into a workspace hardcoded\n\t * `{ default: 'dokploy' }` and dropped everything else, so there was no\n\t * endpoint, no registry and no domain — and `resolveHost` failed with \"no\n\t * domain configured for stage\" on a config that had nowhere to put one.\n\t *\n\t * The same shape a workspace config uses, so moving from one to the other is\n\t * a rename rather than a rewrite.\n\t */\n\tdeploy?: import('./workspace/types').DeployConfig;\n}\n\nexport interface BuildOptions {\n\tprovider?: MainProvider;\n\t// Legacy support - will be deprecated\n\tproviders?: LegacyProvider[];\n\tenableOpenApi?: boolean;\n\t/** Build for production (no dev tools, bundled output) */\n\tproduction?: boolean;\n\t/** Skip bundling step in production build */\n\tskipBundle?: boolean;\n\t/** Stage for secrets injection (e.g., 'production', 'staging') */\n\tstage?: string;\n\t/**\n\t * When true, optional environment variables (those with `.optional()` or\n\t * `.default()` in the envParser) are suffixed with `?` within each\n\t * construct's `environment` array in the manifest (e.g. `PORT?`).\n\t * Defaults to false.\n\t */\n\tmarkOptional?: boolean;\n}\n\n/** Result from build command when secrets are injected */\nexport interface BuildResult {\n\t/** Ephemeral master key for deployment (only if stage was provided) */\n\tmasterKey?: string;\n}\n\n// The deployment manifest types are a shared, dependency-free data contract\n// in `@geekmidas/manifest`, re-exported here for back-compat with existing\n// `@geekmidas/cli` imports.\nexport type {\n\tCronInfo,\n\tFunctionInfo,\n\tManifest,\n\tQueueInfo,\n\tRouteInfo,\n\tSubscriberInfo,\n\tTopicInfo,\n} from '@geekmidas/manifest';\n","import { relative } from 'node:path';\nimport type { Construct } from '@geekmidas/constructs';\nimport fg from 'fast-glob';\nimport kebabCase from 'lodash.kebabcase';\nimport type { BuildContext } from '../build/types';\nimport {\n\tisPartitionedRoutes,\n\ttype LegacyProvider,\n\ttype Routes,\n} from '../types';\n\n/**\n * Zod v4 maintains a process-wide registry of schemas registered via\n * `.meta({ id })`. When we cache-bust user module imports (by appending a\n * `?t=timestamp` query), those modules re-execute and try to register the\n * same ids again, which throws `ID X already exists in the registry`.\n *\n * Clearing the registry in the CLI process is safe: the user's running\n * server lives in a subprocess and has its own isolated registry.\n *\n * Exported for tests.\n */\nexport function clearZodGlobalRegistry(): void {\n\tconst registry = (\n\t\tglobalThis as { __zod_globalRegistry?: { clear?: () => void } }\n\t).__zod_globalRegistry;\n\tif (registry && typeof registry.clear === 'function') {\n\t\tregistry.clear();\n\t}\n}\n\nexport interface GeneratorOptions {\n\tprovider?: LegacyProvider;\n\t[key: string]: any;\n}\n\nexport abstract class ConstructGenerator<T extends Construct, R = void> {\n\tabstract isConstruct(value: any): value is T;\n\n\tstatic async build<T extends Construct, R = void>(\n\t\tcontext: BuildContext,\n\t\toutputDir: string,\n\t\tgenerator: ConstructGenerator<T, R>,\n\t\tpatterns?: Routes,\n\t\toptions?: GeneratorOptions,\n\t): Promise<R> {\n\t\tconst constructs = await generator.load(patterns);\n\t\treturn generator.build(context, constructs, outputDir, options);\n\t}\n\n\tabstract build(\n\t\tcontext: BuildContext,\n\t\tconstructs: GeneratedConstruct<T>[],\n\t\toutputDir: string,\n\t\toptions?: GeneratorOptions,\n\t): Promise<R>;\n\n\tasync load(\n\t\tpatterns?: Routes,\n\t\tcwd = process.cwd(),\n\t\tbustCache = false,\n\t): Promise<GeneratedConstruct<T>[]> {\n\t\tconst logger = console;\n\n\t\t// Extract glob patterns and optional partition function\n\t\tlet globPatterns: string[];\n\t\tlet partitionFn: ((filepath: string) => string) | undefined;\n\n\t\tif (isPartitionedRoutes(patterns)) {\n\t\t\tglobPatterns = Array.isArray(patterns.paths)\n\t\t\t\t? patterns.paths\n\t\t\t\t: [patterns.paths];\n\t\t\tpartitionFn = patterns.partition;\n\t\t} else {\n\t\t\tglobPatterns = Array.isArray(patterns)\n\t\t\t\t? patterns\n\t\t\t\t: patterns\n\t\t\t\t\t? [patterns]\n\t\t\t\t\t: [];\n\t\t}\n\n\t\t// Find all files\n\t\tconst files = fg.stream(globPatterns, {\n\t\t\tcwd,\n\t\t\tabsolute: true,\n\t\t});\n\n\t\t// Cache-busted re-imports re-execute user modules, which would try to\n\t\t// re-register Zod schemas with the same `.meta({ id })`. Clear the\n\t\t// global registry once per load to prevent duplicate-id errors.\n\t\tif (bustCache) {\n\t\t\tclearZodGlobalRegistry();\n\t\t}\n\n\t\t// Load constructs\n\t\tconst constructs: GeneratedConstruct<T>[] = [];\n\n\t\tfor await (const f of files) {\n\t\t\ttry {\n\t\t\t\tconst file = f.toString();\n\t\t\t\t// Append cache-busting query param to force re-import of changed modules\n\t\t\t\tconst importPath = bustCache ? `${file}?t=${Date.now()}` : file;\n\t\t\t\tconst module = await import(importPath);\n\n\t\t\t\t// Compute partition name for this file (if partition function provided)\n\t\t\t\tconst partition = partitionFn ? partitionFn(file) : undefined;\n\n\t\t\t\t// Check all exports for constructs\n\t\t\t\tfor (const [key, construct] of Object.entries(module)) {\n\t\t\t\t\tif (this.isConstruct(construct)) {\n\t\t\t\t\t\tconstructs.push({\n\t\t\t\t\t\t\tkey,\n\t\t\t\t\t\t\tname: kebabCase(key),\n\t\t\t\t\t\t\tconstruct,\n\t\t\t\t\t\t\tpath: {\n\t\t\t\t\t\t\t\tabsolute: file,\n\t\t\t\t\t\t\t\trelative: relative(process.cwd(), file),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tpartition,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconst err = error as Error;\n\t\t\t\tlogger.error(`Failed to load ${f}: ${err.message}`);\n\t\t\t\tif (err.stack) {\n\t\t\t\t\tlogger.error(err.stack);\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\treturn constructs;\n\t}\n}\n\nexport interface GeneratedConstruct<T extends Construct> {\n\tkey: string;\n\tname: string;\n\tconstruct: T;\n\tpath: {\n\t\tabsolute: string;\n\t\trelative: string;\n\t};\n\t/** Partition name assigned by the partition function, if configured. */\n\tpartition?: string;\n}\n","/**\n * Discovery — one glob, one structural test.\n *\n * A glob per kind is the same specialness the construct model removes, and\n * resources have no kind to be listed under: a declared `ObjectStorage` would\n * never be found by `routes:` or `crons:`. So there is one `constructs` glob,\n * every export of every matching module is inspected, and the per-kind\n * `isConstruct` checks collapse into a single question — does it have an id and\n * can it declare?\n *\n * Structural rather than `instanceof` on purpose. A construct from a different\n * copy of `@geekmidas/constructs` — a linked workspace, two versions in a\n * lockfile — is still a construct, and `instanceof` is exactly the check that\n * says otherwise.\n */\n\nimport { relative } from 'node:path';\nimport {\n\tassertDerivations,\n\ttype ConstructManifest,\n\tcanonicalId,\n\ttype Declaration,\n} from '@geekmidas/manifest';\nimport fg from 'fast-glob';\nimport { clearZodGlobalRegistry } from '../generators/Generator';\n\n/** Matches the rest of the CLI, which logs through `console` directly. */\nconst logger = console;\n\n/** The construct face discovery needs: an id, and the ability to declare. */\ninterface Declarable {\n\tid: string;\n\tdeclare(): Declaration[];\n}\n\n/**\n * Whether a module export is a construct.\n *\n * Deliberately loose about what else it carries — a construct that also has a\n * `service` is still a construct, and requiring one would exclude the kinds that\n * own no address.\n */\nexport function isDeclarable(value: unknown): value is Declarable {\n\tif (typeof value !== 'object' || value === null) return false;\n\n\tconst candidate = value as Partial<Declarable>;\n\n\treturn (\n\t\ttypeof candidate.id === 'string' &&\n\t\tcandidate.id.length > 0 &&\n\t\ttypeof candidate.declare === 'function'\n\t);\n}\n\nexport interface DiscoverOptions {\n\t/** The glob(s) to search. One pattern, not one per kind. */\n\tpatterns: string | readonly string[];\n\tcwd?: string;\n\t/**\n\t * Re-import changed modules, for the dev watcher.\n\t *\n\t * A single glob makes discovery more central, not less: a new construct file\n\t * has to be picked up on `add`, not only on `change`.\n\t */\n\tbustCache?: boolean;\n\t/**\n\t * Filled in with where each construct was found, when provided.\n\t *\n\t * Discovery is the only thing that knows: it globbed the files and imported\n\t * them. Everything downstream that needs to *write* an import — the build,\n\t * generating a server entry that reads a surface's logger — would otherwise\n\t * have to be told the path in config, which is the string this removes.\n\t *\n\t * An out-parameter rather than a second return value so that every existing\n\t * caller, which wants the manifest and nothing else, is unchanged.\n\t */\n\tsources?: Record<string, ConstructSource>;\n\t/**\n\t * Filled in, when provided, with the ids each owner's runnables declared an\n\t * edge to — keyed by the surface an endpoint was built from, or the worker\n\t * a cron or subscriber was.\n\t *\n\t * A runnable is not a declaration: a surface's endpoints are found by the\n\t * glob, not listed by the surface, so its node says `endpoints: []` until a\n\t * build folds the routes in. Anything deciding what an app's process reaches\n\t * — which keys its container is given — needs those edges before any build,\n\t * and discovery is already importing the files they are in.\n\t */\n\trunnables?: Record<string, string[]>;\n}\n\n/** Where a construct was declared, and under what name. */\nexport interface ConstructSource {\n\t/** Absolute path to the module that exported it. */\n\tfile: string;\n\t/** The name it was exported as — what an import statement has to say. */\n\texportName: string;\n\t/**\n\t * The exported construct itself.\n\t *\n\t * What a declaration cannot say — whether it serves itself — the object can\n\t * be asked, and discovery already imported it.\n\t */\n\tconstruct: unknown;\n}\n\n/**\n * Build the construct manifest for a project.\n *\n * Runs the manifest's own validation before returning, so an app is wrong here\n * — where the error names a file — rather than at deploy.\n *\n * @throws {DuplicateConstruct} when two exports claim one id.\n */\nexport async function discover(\n\toptions: DiscoverOptions,\n): Promise<ConstructManifest> {\n\tconst {\n\t\tpatterns,\n\t\tcwd = process.cwd(),\n\t\tbustCache = false,\n\t\tsources: out,\n\t} = options;\n\n\t// Re-importing user modules re-executes them, and Zod v4 throws on a\n\t// re-registered `.meta({ id })`. Same reason the generators clear it.\n\tif (bustCache) clearZodGlobalRegistry();\n\n\tconst manifest: Record<string, Declaration> = {};\n\t/** Which file declared each id, for the error when two of them do. */\n\tconst sources: Record<string, string> = {};\n\t/**\n\t * Constructs already seen, by identity.\n\t *\n\t * A re-export is the *same object* reached through a second file — ESM\n\t * bindings are live, so `export * from './database.js'` hands back the\n\t * binding rather than a copy. Without this, a barrel file made every\n\t * construct in it appear declared twice, and `constructs/index.ts` is the\n\t * first thing anyone writes in a shared folder.\n\t *\n\t * Identity rather than file is also the more honest rule: what may not\n\t * happen twice is two *different* constructs claiming one id, and that is\n\t * still an error below.\n\t */\n\tconst seen = new WeakSet<object>();\n\n\tconst globs = Array.isArray(patterns) ? [...patterns] : [patterns as string];\n\tconst files = fg.stream(globs, { cwd, absolute: true });\n\n\t/** Matched files, for the diagnostic below. */\n\tlet matched = 0;\n\n\tfor await (const found of files) {\n\t\tmatched++;\n\t\tconst file = found.toString();\n\t\tconst module = await import(bustCache ? `${file}?t=${Date.now()}` : file);\n\n\t\tfor (const [exportName, exported] of Object.entries(module)) {\n\t\t\tif (!isDeclarable(exported)) {\n\t\t\t\tif (options.runnables) recordRunnable(options.runnables, exported);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// The same construct, reached again through a re-export. Skipped\n\t\t\t// rather than re-declared: it has already claimed its id, from the\n\t\t\t// file that defined it.\n\t\t\tif (seen.has(exported as object)) continue;\n\t\t\tseen.add(exported as object);\n\n\t\t\t// A construct that is also something a process runs — a queue and its\n\t\t\t// consumer, built from a worker — reaches what it depends on through\n\t\t\t// that process, exactly as an endpoint or a cron does.\n\t\t\tif (\n\t\t\t\toptions.runnables &&\n\t\t\t\ttypeof (exported as { owner?: unknown }).owner === 'string'\n\t\t\t) {\n\t\t\t\trecordRunnable(options.runnables, exported);\n\t\t\t}\n\n\t\t\tfor (const declaration of exported.declare()) {\n\t\t\t\t// Canonicalise here too: a construct built by hand rather than through\n\t\t\t\t// a constructor is still subject to the same identity rule.\n\t\t\t\tconst id = canonicalId(declaration.id);\n\t\t\t\tconst source = relative(cwd, file);\n\n\t\t\t\tconst claimed = sources[id];\n\t\t\t\tif (claimed !== undefined && claimed !== source) {\n\t\t\t\t\tthrow new DuplicateConstruct(id, [claimed, source]);\n\t\t\t\t}\n\n\t\t\t\tmanifest[id] = { ...declaration, id };\n\t\t\t\tsources[id] = source;\n\t\t\t\tif (out) out[id] = { file, exportName, construct: exported };\n\t\t\t}\n\t\t}\n\t}\n\n\t// Reference integrity before anyone reads it — a schema tenant naming a\n\t// database that was deleted is a manifest error, not a reconcile failure.\n\tassertDerivations(manifest);\n\n\twarnIfNothingFound(globs, matched, Object.keys(manifest).length);\n\n\treturn manifest;\n}\n\n/**\n * Record a runnable's edges under the construct that owns it — the surface an\n * endpoint was built from, or the worker a cron or subscriber was. Anything\n * with no owner, or that is not a runnable at all, is left alone.\n */\nfunction recordRunnable(\n\trunnables: Record<string, string[]>,\n\texported: unknown,\n): void {\n\tif (typeof exported !== 'object' || exported === null) return;\n\n\tconst runnable = exported as {\n\t\tsurface?: { id?: unknown };\n\t\towner?: unknown;\n\t\tconstructs?: unknown;\n\t};\n\tconst owner =\n\t\ttypeof runnable.surface?.id === 'string'\n\t\t\t? runnable.surface.id\n\t\t\t: typeof runnable.owner === 'string'\n\t\t\t\t? runnable.owner\n\t\t\t\t: undefined;\n\tif (!owner || !Array.isArray(runnable.constructs)) return;\n\n\tconst key = canonicalId(owner);\n\tconst edges = runnables[key] ?? [];\n\trunnables[key] = edges;\n\tfor (const id of runnable.constructs) {\n\t\tif (typeof id !== 'string') continue;\n\t\tconst target = canonicalId(id);\n\t\tif (!edges.includes(target)) edges.push(target);\n\t}\n}\n\n/**\n * Say so when a glob was configured and found nothing.\n *\n * The glob is not a filter over declarations already known — it is *how they\n * are found*, by importing what it matches. So a file it misses is a\n * declaration that does not exist, and every symptom of that appears somewhere\n * other than the cause: no container starts, no env key is written, and the\n * application fails on first use against a resource it can see in its own\n * source.\n *\n * Nothing reported it before, because an unmatched file is indistinguishable\n * from a file nobody wrote. These two cases are the ones that are *not*\n * ambiguous — a glob was configured, so something was expected.\n *\n * Silent when no glob is configured at all: that is a project which has not\n * adopted constructs, and has nothing to be missing.\n */\nfunction warnIfNothingFound(\n\tglobs: readonly string[],\n\tmatched: number,\n\tdeclared: number,\n): void {\n\tif (globs.length === 0) return;\n\n\tif (matched === 0) {\n\t\tlogger.warn(\n\t\t\t`\\n⚠️ The constructs glob matched no files, so nothing was declared.\\n` +\n\t\t\t\t` Patterns: ${globs.join(', ')}\\n` +\n\t\t\t\t` Nothing will be provisioned — no containers, no env keys, no ` +\n\t\t\t\t`resources.\\n` +\n\t\t\t\t` A common cause is depth: '*.ts' matches one level, '**/*.ts' ` +\n\t\t\t\t`matches any.`,\n\t\t);\n\t\treturn;\n\t}\n\n\tif (declared === 0) {\n\t\tlogger.warn(\n\t\t\t`\\n⚠️ The constructs glob matched ${matched} file(s) but none declared ` +\n\t\t\t\t`anything.\\n` +\n\t\t\t\t` Patterns: ${globs.join(', ')}\\n` +\n\t\t\t\t` Discovery keeps exports with an 'id' that can 'declare()'. A ` +\n\t\t\t\t`construct that is\\n` +\n\t\t\t\t` built but never exported is invisible to it.`,\n\t\t);\n\t}\n}\n\n/** Two constructs claiming one id. */\nexport class DuplicateConstruct extends Error {\n\t/** The id claimed twice. */\n\treadonly id: string;\n\t/** The files that claimed it. */\n\treadonly sources: readonly string[];\n\n\tconstructor(id: string, sources: readonly string[]) {\n\t\tsuper('Two constructs declare the same id');\n\t\tthis.name = 'DuplicateConstruct';\n\t\tthis.id = id;\n\t\tthis.sources = sources;\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"emit-BdoPFjZf.cjs","names":["provideKey","canonicalId","join","mkdir","dirname","writeFile"],"sources":["../src/reconcile/emit.ts"],"sourcesContent":["/**\n * The manifest, written out as a module.\n *\n * Discovery imports the application's own code — that is how it finds\n * constructs — so anything calling it evaluates the whole runtime graph: React\n * email templates, database clients, modules that read env at import time. That\n * is fine inside `gkm dev`, which is already running the app. It is not fine\n * inside a deploy config, where it fails on the first thing the deploy toolchain\n * does not expect and would keep failing on the next.\n *\n * So discovery happens once, in the build, and everything downstream reads what\n * it wrote. The same split `RestApi` makes by naming a routes glob rather than\n * importing route modules.\n *\n * A module rather than JSON, because a manifest is not just data to this\n * codebase — `as const satisfies ConstructManifest` is what keeps every id,\n * kind and provided key a *literal*, so `IdsOf`, `ProvidedKeys` and the rest\n * can select out of it. `JSON.parse` returns `any` and gives all of that up.\n */\n\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport type {\n\tConstructManifest,\n\tCronInfo,\n\tDeclaration,\n\tDependency,\n\tFunctionInfo,\n\tQueueInfo,\n\tRestApiEndpoint,\n\tRouteInfo,\n\tSubscriberInfo,\n} from '@geekmidas/manifest';\nimport { canonicalId, provideKey } from '@geekmidas/manifest';\nimport type { CacheBackend, EmailBackend } from '../types.js';\n\n/** Where the build writes it, relative to the app root. */\nexport const MANIFEST_PATH = '.gkm/manifest.ts';\n\n/**\n * Fold the build's generated routes into the surface that serves them.\n *\n * The routes already exist: `gkm build` generates one handler per endpoint and\n * records its path, method and environment. What was missing is that the\n * *construct* manifest never learned about them, so a `rest-api` node said\n * `endpoints: []` while five handlers sat in `.gkm/`. Two documents describing\n * the same routes, one of them wrong.\n *\n * Merging here rather than making the construct name a glob or import its\n * endpoints: the build is the thing that already walked the filesystem and\n * already knows where it wrote each handler. Asking the declaration to restate\n * any of that is how the two come to disagree.\n *\n * Attribution is by exclusion — routes go to the surface that declared none of\n * its own. A surface with static endpoints (an auth server's single wildcard)\n * keeps them. That is exact while an app has one API of its own, and needs a\n * real answer the day it has two; the alternative today would be inventing one\n * before anything needs it.\n *\n * **Only from a provider that generates one handler per route.** The `server`\n * provider generates a single catch-all — `ALL *` pointing at the Hono app that\n * routes in-process — which is a true description of that build and a false\n * description of the application. Merging it wrote `endpoints: [{ method:\n * 'ALL', path: '*' }]` into a manifest for an app with five routes, and made the\n * document's contents depend on which `--provider` ran last. A manifest must not\n * do that.\n */\nexport function withRoutes(\n\tmanifest: ConstructManifest,\n\troutes: readonly RouteInfo[],\n\toptions: { perRoute: boolean },\n): ConstructManifest {\n\tif (routes.length === 0 || !options.perRoute) return manifest;\n\n\tconst target = Object.entries(manifest).find(\n\t\t([, declaration]) =>\n\t\t\tdeclaration.kind === 'rest-api' && declaration.endpoints.length === 0,\n\t);\n\n\tif (!target) return manifest;\n\n\tconst [id, declaration] = target;\n\tif (declaration.kind !== 'rest-api') return manifest;\n\n\treturn {\n\t\t...manifest,\n\t\t[id]: {\n\t\t\t...declaration,\n\t\t\tendpoints: routes.map((route) => asEndpoint(manifest, id, route)),\n\t\t},\n\t};\n}\n\n/**\n * Fold in the compute the build generated: functions, crons, queue workers and\n * topic subscribers.\n *\n * All four are named by their *export*, the same way an endpoint's handler is —\n * `CronGenerator` uses the export key as the name — so none of them can declare\n * themselves and all of them are the build's to add. That is why a `Function`\n * has no `declare()` and never will.\n *\n * A worker and a subscriber **nest** inside the queue or topic that triggers\n * them, because position carries the trigger. A function and a cron are\n * top-level, because nothing triggers them but a caller or a clock.\n */\nexport function withCompute(\n\tmanifest: ConstructManifest,\n\tcompute: {\n\t\tfunctions?: readonly FunctionInfo[];\n\t\tcrons?: readonly CronInfo[];\n\t\tqueues?: readonly QueueInfo[];\n\t\tsubscribers?: readonly SubscriberInfo[];\n\t},\n): ConstructManifest {\n\tconst next: Record<string, Declaration> = { ...manifest };\n\n\tfor (const fn of compute.functions ?? []) {\n\t\tnext[fn.name] = {\n\t\t\tkind: 'function',\n\t\t\tid: fn.name,\n\t\t\thandler: fn.handler,\n\t\t\tdependencies: edgesTo(manifest, fn.dependencies),\n\t\t\tprovides: [provideKey(fn.name, 'url')],\n\t\t};\n\t}\n\n\tfor (const cron of compute.crons ?? []) {\n\t\tnext[cron.name] = {\n\t\t\tkind: 'cron',\n\t\t\tid: cron.name,\n\t\t\thandler: cron.handler,\n\t\t\tschedule: cron.schedule,\n\t\t\tdependencies: edgesTo(manifest, cron.dependencies),\n\t\t};\n\t}\n\n\t// A worker drains exactly one queue, so it is matched by the queue it names\n\t// rather than by anything about the handler.\n\tfor (const worker of compute.queues ?? []) {\n\t\tconst queue = next[canonicalId(worker.name)];\n\t\tif (queue?.kind !== 'queue') continue;\n\n\t\tnext[queue.id] = {\n\t\t\t...queue,\n\t\t\tworker: {\n\t\t\t\tid: `${queue.id}Worker`,\n\t\t\t\thandler: worker.handler,\n\t\t\t\tdependencies: edgesTo(manifest, worker.dependencies),\n\t\t\t},\n\t\t};\n\t}\n\n\tfor (const subscriber of compute.subscribers ?? []) {\n\t\tconst topicId = subscriber.topic && canonicalId(subscriber.topic);\n\t\tconst topic = topicId ? next[topicId] : undefined;\n\t\tif (topic?.kind !== 'topic') continue;\n\n\t\tnext[topic.id] = {\n\t\t\t...topic,\n\t\t\tsubscribers: [\n\t\t\t\t...topic.subscribers,\n\t\t\t\t{\n\t\t\t\t\tid: subscriber.name,\n\t\t\t\t\thandler: subscriber.handler,\n\t\t\t\t\tevents: subscriber.subscribedEvents,\n\t\t\t\t\tdependencies: edgesTo(manifest, subscriber.dependencies),\n\t\t\t\t},\n\t\t\t],\n\t\t};\n\t}\n\n\treturn next;\n}\n\n/**\n * Construct ids as manifest edges.\n *\n * A `Dependency` carries the target's kind as well as its id, and the kind is\n * read off the manifest rather than recorded beside the edge — the same rule\n * `edgeTo` follows, for the same reason: a hand-written kind is one fact stated\n * twice, and the copy is what goes stale.\n *\n * An id that names nothing is dropped rather than thrown on. A handler can\n * depend on a construct that declares no node of its own — a lifted `Service`\n * is exactly that — and a build is the wrong place to refuse it; what a target\n * needs from this list is the subset it can actually grant.\n */\nfunction edgesTo(\n\tmanifest: ConstructManifest,\n\tids: readonly string[] = [],\n): Dependency[] {\n\treturn ids.flatMap((target) => {\n\t\tconst declaration = manifest[target];\n\n\t\treturn declaration ? [{ target, kind: declaration.kind }] : [];\n\t});\n}\n\n/** One generated route as the manifest's shape. */\nfunction asEndpoint(\n\tmanifest: ConstructManifest,\n\tsurface: string,\n\troute: RouteInfo,\n): RestApiEndpoint {\n\treturn {\n\t\t// The handler path is already unique per route and is what a target needs\n\t\t// to point a function at, so it is also the natural id.\n\t\tid: `${surface}${route.method}${route.path}`,\n\t\thandler: route.handler,\n\t\tmethod: route.method,\n\t\tpath: route.path,\n\t\t// The edges the handler declared, which is what a target reads to grant\n\t\t// this one function access to exactly those constructs. `environment` is\n\t\t// the same edges' shadow — the keys read — and cannot be turned back into\n\t\t// them, which is why the ids are carried rather than inferred.\n\t\tdependencies: edgesTo(manifest, route.dependencies),\n\t\t...(route.authorizer ? { authorizer: route.authorizer } : {}),\n\t};\n}\n\n/**\n * The manifest as TypeScript source.\n *\n * Pure, so what gets written can be asserted without touching a filesystem.\n */\nexport function manifestModule(\n\tmanifest: ConstructManifest,\n\tbackends: Backends = {},\n): string {\n\treturn `${HEADER}\nexport const manifest = ${serialise(manifest)} as const satisfies ConstructManifest;\n\n/**\n * Where the backends that are *config* rather than declaration resolved to.\n *\n * Recorded because they were being answered twice: once by the build, which\n * registers exactly one cache driver, and once by a deploy config, which told\n * the provisioner something else. The result was a deployed URL the running code\n * had no driver for — two documents disagreeing about one fact, which is what\n * this manifest exists to stop.\n */\nexport const backends = ${serialise(backends)} as const;\n\n// Derived types\nexport type Ids = IdsOf<typeof manifest>;\nexport type Construct<Id extends Ids> = DeclarationOf<typeof manifest, Id>;\nexport type Kind = Construct<Ids>['kind'];\n\n// Useful union types\nexport type ProvidedKeys = AllProvidedKeys<typeof manifest>;\nexport type Surfaces = IdsOfKind<typeof manifest, 'rest-api'>;\nexport type CacheBackend = (typeof backends)['cache'];\nexport type EmailBackend = (typeof backends)['email'];\n`;\n}\n\n/** Write it where the deploy config expects to import it from. */\nexport async function writeManifestModule(\n\tmanifest: ConstructManifest,\n\tappRoot: string,\n\tbackends: Backends = {},\n): Promise<string> {\n\tconst path = join(appRoot, MANIFEST_PATH);\n\n\tawait mkdir(dirname(path), { recursive: true });\n\tawait writeFile(path, manifestModule(manifest, backends), 'utf8');\n\n\treturn path;\n}\n\n/** The choices that are deployment config rather than declaration. */\nexport interface Backends {\n\tcache?: CacheBackend;\n\temail?: EmailBackend;\n}\n\nconst HEADER = `// Generated by \\`gkm build\\`. Do not edit.\n//\n// Discovery imports application code, so it runs once here rather than in every\n// consumer — a deploy config that discovered for itself would evaluate the whole\n// runtime graph inside its own toolchain. This is the result, as literals.\n\nimport type {\n\tAllProvidedKeys,\n\tConstructManifest,\n\tDeclarationOf,\n\tIdsOf,\n\tIdsOfKind,\n} from '@geekmidas/manifest';\n`;\n\n/**\n * JSON, re-indented with tabs and with keys left unquoted where they can be.\n *\n * Cosmetic, and worth it: this file is read by people trying to work out what\n * their app declares, and a wall of quoted keys reads as output rather than as\n * something anyone is expected to understand.\n */\nfunction serialise(value: object): string {\n\treturn JSON.stringify(value, null, '\\t')\n\t\t.replace(/^(\\t*)\"([A-Za-z_$][\\w$]*)\":/gm, '$1$2:')\n\t\t.replace(/\"/g, \"'\");\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"emit-Bzq7JsMP.mjs","names":[],"sources":["../src/reconcile/emit.ts"],"sourcesContent":["/**\n * The manifest, written out as a module.\n *\n * Discovery imports the application's own code — that is how it finds\n * constructs — so anything calling it evaluates the whole runtime graph: React\n * email templates, database clients, modules that read env at import time. That\n * is fine inside `gkm dev`, which is already running the app. It is not fine\n * inside a deploy config, where it fails on the first thing the deploy toolchain\n * does not expect and would keep failing on the next.\n *\n * So discovery happens once, in the build, and everything downstream reads what\n * it wrote. The same split `RestApi` makes by naming a routes glob rather than\n * importing route modules.\n *\n * A module rather than JSON, because a manifest is not just data to this\n * codebase — `as const satisfies ConstructManifest` is what keeps every id,\n * kind and provided key a *literal*, so `IdsOf`, `ProvidedKeys` and the rest\n * can select out of it. `JSON.parse` returns `any` and gives all of that up.\n */\n\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport type {\n\tConstructManifest,\n\tCronInfo,\n\tDeclaration,\n\tDependency,\n\tFunctionInfo,\n\tQueueInfo,\n\tRestApiEndpoint,\n\tRouteInfo,\n\tSubscriberInfo,\n} from '@geekmidas/manifest';\nimport { canonicalId, provideKey } from '@geekmidas/manifest';\nimport type { CacheBackend, EmailBackend } from '../types.js';\n\n/** Where the build writes it, relative to the app root. */\nexport const MANIFEST_PATH = '.gkm/manifest.ts';\n\n/**\n * Fold the build's generated routes into the surface that serves them.\n *\n * The routes already exist: `gkm build` generates one handler per endpoint and\n * records its path, method and environment. What was missing is that the\n * *construct* manifest never learned about them, so a `rest-api` node said\n * `endpoints: []` while five handlers sat in `.gkm/`. Two documents describing\n * the same routes, one of them wrong.\n *\n * Merging here rather than making the construct name a glob or import its\n * endpoints: the build is the thing that already walked the filesystem and\n * already knows where it wrote each handler. Asking the declaration to restate\n * any of that is how the two come to disagree.\n *\n * Attribution is by exclusion — routes go to the surface that declared none of\n * its own. A surface with static endpoints (an auth server's single wildcard)\n * keeps them. That is exact while an app has one API of its own, and needs a\n * real answer the day it has two; the alternative today would be inventing one\n * before anything needs it.\n *\n * **Only from a provider that generates one handler per route.** The `server`\n * provider generates a single catch-all — `ALL *` pointing at the Hono app that\n * routes in-process — which is a true description of that build and a false\n * description of the application. Merging it wrote `endpoints: [{ method:\n * 'ALL', path: '*' }]` into a manifest for an app with five routes, and made the\n * document's contents depend on which `--provider` ran last. A manifest must not\n * do that.\n */\nexport function withRoutes(\n\tmanifest: ConstructManifest,\n\troutes: readonly RouteInfo[],\n\toptions: { perRoute: boolean },\n): ConstructManifest {\n\tif (routes.length === 0 || !options.perRoute) return manifest;\n\n\tconst target = Object.entries(manifest).find(\n\t\t([, declaration]) =>\n\t\t\tdeclaration.kind === 'rest-api' && declaration.endpoints.length === 0,\n\t);\n\n\tif (!target) return manifest;\n\n\tconst [id, declaration] = target;\n\tif (declaration.kind !== 'rest-api') return manifest;\n\n\treturn {\n\t\t...manifest,\n\t\t[id]: {\n\t\t\t...declaration,\n\t\t\tendpoints: routes.map((route) => asEndpoint(manifest, id, route)),\n\t\t},\n\t};\n}\n\n/**\n * Fold in the compute the build generated: functions, crons, queue workers and\n * topic subscribers.\n *\n * All four are named by their *export*, the same way an endpoint's handler is —\n * `CronGenerator` uses the export key as the name — so none of them can declare\n * themselves and all of them are the build's to add. That is why a `Function`\n * has no `declare()` and never will.\n *\n * A worker and a subscriber **nest** inside the queue or topic that triggers\n * them, because position carries the trigger. A function and a cron are\n * top-level, because nothing triggers them but a caller or a clock.\n */\nexport function withCompute(\n\tmanifest: ConstructManifest,\n\tcompute: {\n\t\tfunctions?: readonly FunctionInfo[];\n\t\tcrons?: readonly CronInfo[];\n\t\tqueues?: readonly QueueInfo[];\n\t\tsubscribers?: readonly SubscriberInfo[];\n\t},\n): ConstructManifest {\n\tconst next: Record<string, Declaration> = { ...manifest };\n\n\tfor (const fn of compute.functions ?? []) {\n\t\tnext[fn.name] = {\n\t\t\tkind: 'function',\n\t\t\tid: fn.name,\n\t\t\thandler: fn.handler,\n\t\t\tdependencies: edgesTo(manifest, fn.dependencies),\n\t\t\tprovides: [provideKey(fn.name, 'url')],\n\t\t};\n\t}\n\n\tfor (const cron of compute.crons ?? []) {\n\t\tnext[cron.name] = {\n\t\t\tkind: 'cron',\n\t\t\tid: cron.name,\n\t\t\thandler: cron.handler,\n\t\t\tschedule: cron.schedule,\n\t\t\tdependencies: edgesTo(manifest, cron.dependencies),\n\t\t};\n\t}\n\n\t// A worker drains exactly one queue, so it is matched by the queue it names\n\t// rather than by anything about the handler.\n\tfor (const worker of compute.queues ?? []) {\n\t\tconst queue = next[canonicalId(worker.name)];\n\t\tif (queue?.kind !== 'queue') continue;\n\n\t\tnext[queue.id] = {\n\t\t\t...queue,\n\t\t\tworker: {\n\t\t\t\tid: `${queue.id}Worker`,\n\t\t\t\thandler: worker.handler,\n\t\t\t\tdependencies: edgesTo(manifest, worker.dependencies),\n\t\t\t},\n\t\t};\n\t}\n\n\tfor (const subscriber of compute.subscribers ?? []) {\n\t\tconst topicId = subscriber.topic && canonicalId(subscriber.topic);\n\t\tconst topic = topicId ? next[topicId] : undefined;\n\t\tif (topic?.kind !== 'topic') continue;\n\n\t\tnext[topic.id] = {\n\t\t\t...topic,\n\t\t\tsubscribers: [\n\t\t\t\t...topic.subscribers,\n\t\t\t\t{\n\t\t\t\t\tid: subscriber.name,\n\t\t\t\t\thandler: subscriber.handler,\n\t\t\t\t\tevents: subscriber.subscribedEvents,\n\t\t\t\t\tdependencies: edgesTo(manifest, subscriber.dependencies),\n\t\t\t\t},\n\t\t\t],\n\t\t};\n\t}\n\n\treturn next;\n}\n\n/**\n * Construct ids as manifest edges.\n *\n * A `Dependency` carries the target's kind as well as its id, and the kind is\n * read off the manifest rather than recorded beside the edge — the same rule\n * `edgeTo` follows, for the same reason: a hand-written kind is one fact stated\n * twice, and the copy is what goes stale.\n *\n * An id that names nothing is dropped rather than thrown on. A handler can\n * depend on a construct that declares no node of its own — a lifted `Service`\n * is exactly that — and a build is the wrong place to refuse it; what a target\n * needs from this list is the subset it can actually grant.\n */\nfunction edgesTo(\n\tmanifest: ConstructManifest,\n\tids: readonly string[] = [],\n): Dependency[] {\n\treturn ids.flatMap((target) => {\n\t\tconst declaration = manifest[target];\n\n\t\treturn declaration ? [{ target, kind: declaration.kind }] : [];\n\t});\n}\n\n/** One generated route as the manifest's shape. */\nfunction asEndpoint(\n\tmanifest: ConstructManifest,\n\tsurface: string,\n\troute: RouteInfo,\n): RestApiEndpoint {\n\treturn {\n\t\t// The handler path is already unique per route and is what a target needs\n\t\t// to point a function at, so it is also the natural id.\n\t\tid: `${surface}${route.method}${route.path}`,\n\t\thandler: route.handler,\n\t\tmethod: route.method,\n\t\tpath: route.path,\n\t\t// The edges the handler declared, which is what a target reads to grant\n\t\t// this one function access to exactly those constructs. `environment` is\n\t\t// the same edges' shadow — the keys read — and cannot be turned back into\n\t\t// them, which is why the ids are carried rather than inferred.\n\t\tdependencies: edgesTo(manifest, route.dependencies),\n\t\t...(route.authorizer ? { authorizer: route.authorizer } : {}),\n\t};\n}\n\n/**\n * The manifest as TypeScript source.\n *\n * Pure, so what gets written can be asserted without touching a filesystem.\n */\nexport function manifestModule(\n\tmanifest: ConstructManifest,\n\tbackends: Backends = {},\n): string {\n\treturn `${HEADER}\nexport const manifest = ${serialise(manifest)} as const satisfies ConstructManifest;\n\n/**\n * Where the backends that are *config* rather than declaration resolved to.\n *\n * Recorded because they were being answered twice: once by the build, which\n * registers exactly one cache driver, and once by a deploy config, which told\n * the provisioner something else. The result was a deployed URL the running code\n * had no driver for — two documents disagreeing about one fact, which is what\n * this manifest exists to stop.\n */\nexport const backends = ${serialise(backends)} as const;\n\n// Derived types\nexport type Ids = IdsOf<typeof manifest>;\nexport type Construct<Id extends Ids> = DeclarationOf<typeof manifest, Id>;\nexport type Kind = Construct<Ids>['kind'];\n\n// Useful union types\nexport type ProvidedKeys = AllProvidedKeys<typeof manifest>;\nexport type Surfaces = IdsOfKind<typeof manifest, 'rest-api'>;\nexport type CacheBackend = (typeof backends)['cache'];\nexport type EmailBackend = (typeof backends)['email'];\n`;\n}\n\n/** Write it where the deploy config expects to import it from. */\nexport async function writeManifestModule(\n\tmanifest: ConstructManifest,\n\tappRoot: string,\n\tbackends: Backends = {},\n): Promise<string> {\n\tconst path = join(appRoot, MANIFEST_PATH);\n\n\tawait mkdir(dirname(path), { recursive: true });\n\tawait writeFile(path, manifestModule(manifest, backends), 'utf8');\n\n\treturn path;\n}\n\n/** The choices that are deployment config rather than declaration. */\nexport interface Backends {\n\tcache?: CacheBackend;\n\temail?: EmailBackend;\n}\n\nconst HEADER = `// Generated by \\`gkm build\\`. Do not edit.\n//\n// Discovery imports application code, so it runs once here rather than in every\n// consumer — a deploy config that discovered for itself would evaluate the whole\n// runtime graph inside its own toolchain. This is the result, as literals.\n\nimport type {\n\tAllProvidedKeys,\n\tConstructManifest,\n\tDeclarationOf,\n\tIdsOf,\n\tIdsOfKind,\n} from '@geekmidas/manifest';\n`;\n\n/**\n * JSON, re-indented with tabs and with keys left unquoted where they can be.\n *\n * Cosmetic, and worth it: this file is read by people trying to work out what\n * their app declares, and a wall of quoted keys reads as output rather than as\n * something anyone is expected to understand.\n */\nfunction serialise(value: object): string {\n\treturn JSON.stringify(value, null, '\\t')\n\t\t.replace(/^(\\t*)\"([A-Za-z_$][\\w$]*)\":/gm, '$1$2:')\n\t\t.replace(/\"/g, \"'\");\n}\n"],"mappings":"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"}
|