@lpdjs/firestore-repo-service 2.6.11 → 2.6.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/servers/hono/types.ts","../../../src/servers/hono/errors.ts","../../../src/servers/hono/openapi.ts","../../../src/servers/auth/login-page.tsx","../../../src/servers/auth/session.ts","../../../src/servers/hono/docs-auth.ts","../../../src/servers/hono/services.ts","../../../src/servers/hono/server.ts","../../../src/servers/hono/usecase.ts","../../../src/servers/hono/api-registry.ts","../../../src/servers/hono/gcp-logs.ts","../../../src/servers/hono/error-handler.ts","../../../src/servers/hono/logger.ts","../../../src/servers/hono/codegen/path-utils.ts","../../../src/servers/hono/codegen/scanner.ts","../../../src/servers/hono/codegen/generator.ts"],"names":["defineRoutes","routes","ValidationError","zodError","source","BadRequestError","message","OutputValidationError","formatZodIssues","error","i","defaultErrorResponse","c","err","extendZodWithOpenApi","z","DEFAULT_RESPONSE_DESCRIPTION","DEFAULT_ERROR_DESCRIPTION","defaultSource","method","resolveSuccessSchema","routeOutput","interceptor","out","normalizeErrorResponse","entry","cfg","buildOpenApiDocument","basePath","config","registry","OpenAPIRegistry","name","scheme","route","fullPath","joinPath","status","requestBody","buildRequestBody","requestQuery","buildQueryOrParam","requestParams","operationId","makeOperationId","successSchema","responses","code","description","schema","convertExpressPathToOpenApi","OpenApiGeneratorV31","target","path","base","left","right","merged","cleaned","renderDocsHtml","specUrl","title","safeUrl","emulatorUrl","host","htmlEscape","value","jsonEscape","renderLoginPage","opts","showPassword","showGoogle","initialError","parseCookies","header","part","eq","key","firebaseBearerAuth","options","getAuth","allow","checkRevoked","contextKey","next","match","decoded","permitted","basicAuth","username","password","realm","expected","timingSafeEqual","a","b","diff","isDocsAuthExtension","LOGIN_NAME","SESSION_NAME","LOGOUT_NAME","lastSegment","segs","buildSetCookie","segments","sanitizeNext","raw","fallback","firebaseDocsAuth","apiKey","authDomain","mode","providers","cookieName","sessionTtlDays","secureCookie","sameSite","onUnauthenticated","authEmulatorHost","passesAllow","token","auth","session","accept","isBrowserGet","html","idToken","body","expiresInMs","authTimeRaw","authTime","sessionCookie","cookie","als","AsyncLocalStorage","requestContextSingleton","store","createRequestContextMiddleware","withRequestContext","ctx","fn","CTX_KEY","createServices","cache","inProgress","proxy","_target","prop","provider","isClassConstructor","_t","EMPTY_SERVICES","HonoServer","Hono","filterRoutes","globalMws","mw","getRequestListener","onRequest","httpsOptions","handler","interceptorConfig","validateOutput","verbose","middlewares","makeRouteHandler","interceptorFn","httpMethod","specPath","docsPath","fullSpecPath","fullDocsPath","guards","ext","authDir","dirOf","relativeSpecUrl","relativeUrlFromTo","isInterceptorConfig","api","r","idx","from","to","fromSegs","toSegs","common","ups","services","errorHandler","logger","inputSchema","outputSchema","servicesArg","applyErrorHandler","handled","callNext","payload","readPayload","parsed","result","checked","text","UseCase","useCaseRoute","useCaseClass","meta","input","createApiRegistry","configs","sharedServices","sharedErrorHandler","sharedLogger","def","httpsOpts","server","resolveGcpProjectId","explicit","gcpLogsUrl","errorId","projectId","query","params","BaseErrorHandler","mapped","_ctx","_response","BaseLogger","_BaseLogger","severity","line","DEFAULT_DERIVE","derivePath","relativeDir","skip","p","kebab","s","toImportSpecifier","fromDir","toFile","fromParts","splitAbs","toParts","up","down","stripped","finalLast","DEFAULT_SCANNER","scanRoutes","rootAbs","found","walk","root","dir","entries","readdirSync","abs","join","st","statSync","relPath","relative","sep","relDir","DEFAULT_GENERATOR_BANNER","generateRoutesManifest","outDir","dirname","mkdirSync","banner","now","importLines","entryLines","derivedPaths","importPath","url","writeFileSync","generateFromRoot","outFileRel","derive","importExtension","scan","outFile"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Public types for the Hono file-based API server.\n *\n * Designed to be:\n * - **fully typed** end-to-end (Zod input/output → handler payload type),\n * - **runtime-safe** via Zod parsing on every request,\n * - **bundle-friendly** for Firebase Cloud Functions v2 cold-start (codegen\n * emits static imports — no `fs`/`import()` at runtime).\n */\n\nimport type { z, ZodError } from \"zod\";\nimport type { Context, Env, MiddlewareHandler } from \"hono\";\nimport type { AnyServicesContainer } from \"./services\";\nimport type { DocsAuthExtension } from \"./docs-auth\";\n\nexport type HttpMethod = \"get\" | \"post\" | \"put\" | \"patch\" | \"delete\";\n\n/** Where the validated payload comes from. */\nexport type PayloadSource = \"json\" | \"query\" | \"form\" | \"param\";\n\n/**\n * Structured logger injected into every handler / interceptor / error-handler\n * context. Implement it, or extend the package's `BaseLogger` and override its\n * `write` hook to route to your sink (Firebase logger, pino, …).\n *\n * `error()` returns a correlation id so the same value can be both logged and\n * returned to the client.\n */\nexport interface Logger {\n info(message: string, meta?: unknown): void;\n warn(message: string, meta?: unknown): void;\n /** @returns a correlation id (e.g. to include in the HTTP error response). */\n error(error: unknown, meta?: unknown): string;\n debug(message: string, meta?: unknown): void;\n}\n\n/** Context passed to {@link ErrorHandler.handle}. */\nexport interface ErrorHandlerContext<\n TEnv extends Env = Env,\n TServices extends AnyServicesContainer = AnyServicesContainer,\n> {\n /** The thrown value (an `AppError`, a Zod {@link ValidationError}, …). */\n error: unknown;\n /** Hono request context — set status, read headers, build the response. */\n c: Context<TEnv>;\n /** Route metadata (read-only). */\n route: AnyRouteDef;\n /** Global DI services container. */\n services: TServices;\n /** Injected {@link Logger} when one was passed to the API, else `undefined`. */\n logger?: Logger;\n}\n\n/**\n * Cross-cutting error strategy — a class (or object) you pass to the API and\n * that the server injects everywhere **and** applies automatically.\n *\n * Implement {@link ErrorHandler.handle} to map any thrown value to an HTTP\n * `Response` (e.g. your `AppError` → status + localized body, plus structured\n * logging with a correlation id). Return `null` to decline the error and let\n * the built-in fallback (`ValidationError` envelope) / `onError` take over.\n *\n * Prefer extending the package's {@link BaseErrorHandler} (it already maps the\n * built-in errors) and overriding its `mapError` / `logError` hooks. Pass it\n * **per API** via `ApiConfig.errorHandler`, or once via the registry\n * (`{ services, errorHandler }`); it is then available in every handler /\n * interceptor context as `errorHandler` and auto-applied on any uncaught error.\n */\nexport interface ErrorHandler<\n TEnv extends Env = Env,\n TServices extends AnyServicesContainer = AnyServicesContainer,\n> {\n handle(\n ctx: ErrorHandlerContext<TEnv, TServices>,\n ): Response | null | Promise<Response | null>;\n}\n\n/** Handler signature — receives a single typed context object. */\nexport type RouteHandler<\n TIn,\n TOut,\n TEnv extends Env = Env,\n TServices extends AnyServicesContainer = AnyServicesContainer,\n> = (ctx: {\n /** Validated (and typed) request payload. `void` when no `input` schema is defined. */\n input: TIn;\n /** Raw Hono `Context` for headers, set status, redirect, etc. */\n c: Context<TEnv>;\n /**\n * Global DI services container — same instance shared by every handler.\n * Empty `ServicesContainer` when the server was started without a\n * `services` option.\n */\n services: TServices;\n /**\n * Injected {@link ErrorHandler} when one was passed to the API, else\n * `undefined`. Usually you just `throw` and let it apply automatically.\n */\n errorHandler?: ErrorHandler<TEnv, TServices>;\n /** Injected {@link Logger} when one was passed to the API, else `undefined`. */\n logger?: Logger;\n}) => Promise<TOut | Response> | TOut | Response;\n\n/**\n * One route declaration. Default-exported by every `routes.ts` file inside the\n * domain tree. Use {@link defineRoute} for full type inference.\n */\nexport interface RouteDef<\n TIn extends z.ZodTypeAny | undefined = z.ZodTypeAny | undefined,\n TOut extends z.ZodTypeAny | undefined = z.ZodTypeAny | undefined,\n TEnv extends Env = Env,\n> {\n /**\n * Logical API tag — routes sharing the same `api` are mounted on the same\n * `HonoServer` (typically one Cloud Function per `api`).\n *\n * To expose the **same logic** under several APIs with different\n * inputs/outputs, export multiple `defineRoute({...})` from the same file\n * (default + named exports are both picked up by the codegen).\n */\n api: string;\n\n /** HTTP method. */\n method: HttpMethod;\n\n /**\n * URL path appended to the server `basePath`. If omitted, the codegen will\n * derive it from the file location (e.g. `domains/activities/useCases/createCustom/routes.ts`\n * → `/activities/createCustom`).\n */\n path?: string;\n\n /**\n * Where the request payload comes from.\n * Default: `\"json\"` for body methods (POST/PUT/PATCH/DELETE), `\"query\"` for GET.\n */\n source?: PayloadSource;\n\n /** Zod schema validating the payload. Failures yield a 400 response. */\n input?: TIn;\n\n /**\n * Zod schema for the success response. Used to populate the OpenAPI spec\n * and (when `validateOutput` is enabled on the server) to assert the\n * runtime payload returned by the handler.\n */\n output?: TOut;\n\n /** Status code for the success response. Default: 200. */\n status?: number;\n\n /** Hono middlewares applied to this route only (after global middlewares). */\n middlewares?: MiddlewareHandler<TEnv>[];\n\n // ── OpenAPI metadata ─────────────────────────────────────────────────\n summary?: string;\n description?: string;\n tags?: string[];\n /** Mark the operation as deprecated in the generated spec. */\n deprecated?: boolean;\n /** Security requirements (operationId-level override). */\n security?: SecurityRequirement[];\n\n /** The request handler. */\n handler: RouteHandler<\n TIn extends z.ZodTypeAny ? z.infer<TIn> : void,\n TOut extends z.ZodTypeAny ? z.infer<TOut> : unknown,\n TEnv\n >;\n}\n\n/** Erased `RouteDef` used by registry/codegen — handler signature is opaque. */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyRouteDef = RouteDef<any, any>;\n\n/**\n * What `routes.ts` can default-export:\n * - a single {@link RouteDef} (most common case),\n * - or an array of {@link RouteDef} (e.g. expose the same useCase on\n * multiple `api` tags or under multiple paths). `readonly` arrays are\n * accepted so that tuple-preserving helpers like {@link defineRoutes} (or a\n * plain `as const` array) can be default-exported directly.\n */\nexport type RouteModuleDefault = AnyRouteDef | readonly AnyRouteDef[];\n\n// ── useCase ⇆ routes type bridge ──────────────────────────────────────────\n// A useCase should never re-declare its input/output by hand. Instead it\n// derives them from the Zod schemas of the route(s) that drive it, so the two\n// can never drift apart. Pass `typeof import(\"./routes.js\").default` to the\n// helpers below.\n\n/**\n * Identity helper that **preserves the tuple type** of a `routes.ts` default\n * export. A plain array literal (`export default [a, b]`) collapses every\n * element to a single common type, which would lose each route's individual\n * schema. Wrapping the array with `defineRoutes([...])` keeps the per-route\n * types intact so {@link RouteInput} / {@link RouteOutput} can aggregate them\n * into a union.\n *\n * @example\n * export default defineRoutes([\n * defineRoute({ ... }),\n * defineRoute({ ... }),\n * ]);\n */\nexport function defineRoutes<const T extends readonly AnyRouteDef[]>(\n routes: T,\n): T {\n return routes;\n}\n\n/**\n * Normalizes a `routes.ts` default export — a single {@link RouteDef} or an\n * array of them — to a *union* of its individual route members.\n */\nexport type RoutesUnion<T> = T extends readonly (infer R)[] ? R : T;\n\ntype InferRouteInput<R> = R extends { input?: infer I }\n ? I extends z.ZodTypeAny\n ? z.infer<I>\n : void\n : void;\n\ntype InferRouteOutput<R> = R extends { output?: infer O }\n ? O extends z.ZodTypeAny\n ? z.infer<O>\n : unknown\n : unknown;\n\n/**\n * Union of validated request payloads across every route in a `routes.ts`\n * module. Use it to type a useCase input without duplicating the Zod schema:\n *\n * @example\n * type Routes = typeof import(\"./routes.js\").default;\n * export type CreatePostUseCaseInput = RouteInput<Routes>;\n */\nexport type RouteInput<T> = InferRouteInput<RoutesUnion<T>>;\n\n/**\n * Union of success-response payloads across every route in a `routes.ts`\n * module. Mirror of {@link RouteInput} for the useCase output type.\n */\nexport type RouteOutput<T> = InferRouteOutput<RoutesUnion<T>>;\n\n/**\n * Thrown by the server when the incoming request fails Zod validation.\n * Caught by the {@link RouteInterceptor} (if any) so users can shape the\n * response envelope however they like; otherwise yields a default 400.\n */\nexport class ValidationError extends Error {\n readonly statusCode = 400 as const;\n constructor(\n /** Original Zod error — `error.issues` to enumerate field-level problems. */\n readonly zodError: ZodError,\n /** Where the offending payload came from. */\n readonly source: PayloadSource,\n ) {\n super(\"Request validation failed\");\n this.name = \"ValidationError\";\n }\n}\n\n/**\n * Cross-cutting interceptor applied around every handler.\n * Use it for response envelopes, business-error → HTTP mapping, structured\n * logging, tracing spans, etc.\n *\n * Wrap `next()` in `try/catch` to intercept BOTH Zod {@link ValidationError}s\n * (thrown before the handler runs) AND business errors thrown by the handler.\n *\n * @example\n * ```ts\n * interceptor: async ({ next, route, c }) => {\n * try {\n * const data = await next();\n * return c.json({ success: true, data, error: null });\n * } catch (err) {\n * if (err instanceof ValidationError) {\n * return c.json({ success: false, error: \"validation\", issues: err.zodError.issues }, 400);\n * }\n * if (err instanceof DomainError) {\n * return c.json({ success: false, error: err.code }, err.statusCode);\n * }\n * throw err; // → falls back to onError or Hono's default 500\n * }\n * }\n * ```\n */\nexport type RouteInterceptor<\n TEnv extends Env = Env,\n TServices extends AnyServicesContainer = AnyServicesContainer,\n> = (ctx: {\n /**\n * Calls validation + handler and returns the raw value.\n * Throws {@link ValidationError} on Zod failure or any error thrown by the handler.\n */\n next: () => Promise<unknown>;\n /** Route metadata (read-only). */\n route: AnyRouteDef;\n /** Hono request context. */\n c: Context<TEnv>;\n /** Global DI services container. See {@link RouteHandler}. */\n services: TServices;\n /**\n * Injected {@link ErrorHandler} when one was passed to the API. Call\n * `errorHandler?.handle({ error, c, route, services })` in your `catch` to\n * reuse the shared mapping, or simply rethrow to let it apply automatically.\n */\n errorHandler?: ErrorHandler<TEnv, TServices>;\n /** Injected {@link Logger} when one was passed to the API, else `undefined`. */\n logger?: Logger;\n}) => Promise<Response | unknown> | Response | unknown;\n\n/**\n * Success-envelope schema declared alongside an interceptor so the generated\n * OpenAPI spec documents what the **wrapper actually returns** (not the raw\n * handler output).\n *\n * - a **static** Zod schema → same envelope for every route (e.g.\n * `z.object({ data: z.any(), intercepted: z.boolean() })`);\n * - a **factory** `(routeOutput) => schema` → wraps each route's own `output`,\n * so `data` is typed per endpoint in the docs.\n */\nexport type InterceptorOutput =\n | z.ZodTypeAny\n | ((routeOutput: z.ZodTypeAny | undefined) => z.ZodTypeAny);\n\n/** A single declared error response for the OpenAPI spec. */\nexport type InterceptorErrorResponse =\n | z.ZodTypeAny\n | { description?: string; schema?: z.ZodTypeAny };\n\n/**\n * Structured interceptor — pairs the cross-cutting {@link RouteInterceptor}\n * `handler` with the OpenAPI metadata describing what it returns, so the docs\n * reflect the real wrapped responses (success envelope + error shapes).\n *\n * @example\n * ```ts\n * interceptor: {\n * // factory: `data` reflects each route's own output schema\n * output: (routeOutput) =>\n * z.object({ data: routeOutput ?? z.unknown(), intercepted: z.boolean() }),\n * errors: {\n * 400: ValidationErrorSchema,\n * 500: { description: \"Internal\", schema: ErrorSchema },\n * },\n * handler: async ({ c, next }) => {\n * const data = await next();\n * return c.json({ data, intercepted: true });\n * },\n * }\n * ```\n */\nexport interface InterceptorConfig<\n TEnv extends Env = Env,\n TServices extends AnyServicesContainer = AnyServicesContainer,\n> {\n /** Success-envelope schema (static) or per-route factory. */\n output?: InterceptorOutput;\n /**\n * Error responses applied to **every** operation, keyed by HTTP status. Pass\n * a bare Zod schema or `{ description, schema }`.\n */\n errors?: Record<number, InterceptorErrorResponse>;\n /** The interceptor function. See {@link RouteInterceptor}. */\n handler: RouteInterceptor<TEnv, TServices>;\n}\n\n/**\n * Interceptor option accepted by the server / registry — either a bare\n * {@link RouteInterceptor} function (legacy) or a structured\n * {@link InterceptorConfig} carrying OpenAPI metadata.\n */\nexport type InterceptorOption<\n TEnv extends Env = Env,\n TServices extends AnyServicesContainer = AnyServicesContainer,\n> = RouteInterceptor<TEnv, TServices> | InterceptorConfig<TEnv, TServices>;\n\n/** OpenAPI document info (subset of the spec used by the helper). */\nexport interface OpenAPIInfo {\n title: string;\n version: string;\n description?: string;\n}\n\n// ── OpenAPI 3.1 Security Scheme Object ──────────────────────────────────────\n\n/** Fields shared by every security scheme. */\nexport interface SecuritySchemeBase {\n /** Human-readable description (CommonMark). */\n description?: string;\n}\n\n/** API key carried in a header, query param or cookie. */\nexport interface ApiKeySecurityScheme extends SecuritySchemeBase {\n type: \"apiKey\";\n /** Name of the header, query parameter or cookie. */\n name: string;\n /** Location of the API key. */\n in: \"query\" | \"header\" | \"cookie\";\n}\n\n/**\n * HTTP authentication (RFC 7235), e.g. `bearer` (Firebase ID tokens) or\n * `basic`.\n */\nexport interface HttpSecurityScheme extends SecuritySchemeBase {\n type: \"http\";\n /** Auth scheme name — `\"bearer\"`, `\"basic\"`, `\"digest\"`, … */\n // eslint-disable-next-line @typescript-eslint/ban-types\n scheme: \"bearer\" | \"basic\" | \"digest\" | (string & {});\n /** Hint for the bearer token format, e.g. `\"JWT\"` / `\"Firebase JWT\"`. */\n bearerFormat?: string;\n}\n\n/** Mutual TLS authentication. */\nexport interface MutualTlsSecurityScheme extends SecuritySchemeBase {\n type: \"mutualTLS\";\n}\n\n/** A single OAuth2 flow. */\nexport interface OAuthFlowObject {\n authorizationUrl?: string;\n tokenUrl?: string;\n refreshUrl?: string;\n /** Available scopes → description. */\n scopes: Record<string, string>;\n}\n\n/** The OAuth2 flows supported by an {@link OAuth2SecurityScheme}. */\nexport interface OAuthFlowsObject {\n implicit?: OAuthFlowObject;\n password?: OAuthFlowObject;\n clientCredentials?: OAuthFlowObject;\n authorizationCode?: OAuthFlowObject;\n}\n\n/** OAuth2 authentication. */\nexport interface OAuth2SecurityScheme extends SecuritySchemeBase {\n type: \"oauth2\";\n flows: OAuthFlowsObject;\n}\n\n/** OpenID Connect Discovery. */\nexport interface OpenIdConnectSecurityScheme extends SecuritySchemeBase {\n type: \"openIdConnect\";\n openIdConnectUrl: string;\n}\n\n/** OpenAPI 3.1 Security Scheme Object (discriminated on `type`). */\nexport type SecurityScheme =\n | ApiKeySecurityScheme\n | HttpSecurityScheme\n | MutualTlsSecurityScheme\n | OAuth2SecurityScheme\n | OpenIdConnectSecurityScheme;\n\n/**\n * A single Security Requirement Object: maps a scheme name (a key of\n * {@link OpenAPIConfig.securitySchemes}) to the list of required scopes\n * (empty `[]` for `http` / `apiKey`).\n */\nexport type SecurityRequirement = Record<string, string[]>;\n\n/** OpenAPI configuration on the server. */\nexport interface OpenAPIConfig {\n /** Path served by the JSON spec (e.g. `/openapi.json`). Default: `/openapi.json`. */\n path?: string;\n /** Path serving the documentation UI. Set to `false` to disable. Default: `/docs`. */\n docsPath?: string | false;\n /** OpenAPI document info. */\n info: OpenAPIInfo;\n /** Optional servers list for the spec. */\n servers?: { url: string; description?: string }[];\n /**\n * Reusable security schemes, keyed by name. The keys are referenced from\n * {@link OpenAPIConfig.security} / per-route `security`.\n *\n * @example\n * ```ts\n * securitySchemes: {\n * bearerAuth: { type: \"http\", scheme: \"bearer\", bearerFormat: \"Firebase JWT\" },\n * }\n * ```\n */\n securitySchemes?: Record<string, SecurityScheme>;\n /**\n * Default security requirement applied to every operation. Each entry maps a\n * scheme name (a key of {@link OpenAPIConfig.securitySchemes}) to its scopes.\n *\n * @example `security: [{ bearerAuth: [] }]`\n */\n security?: SecurityRequirement[];\n /**\n * Hono middleware(s) guarding **only** the docs UI and JSON spec endpoints\n * (not the API routes). Use a raw middleware for a custom flow, or the\n * built-in {@link firebaseBearerAuth} / {@link basicAuth} helpers.\n *\n * @example\n * ```ts\n * import { firebaseBearerAuth } from \"@lpdjs/firestore-repo-service/servers/hono\";\n * openapi: { info, docsAuth: firebaseBearerAuth({ getAuth }) }\n * ```\n *\n * For a full login form + session cookie (like the admin server), pass the\n * {@link DocsAuthExtension} returned by `firebaseDocsAuth({ ... })` instead.\n */\n docsAuth?: MiddlewareHandler | MiddlewareHandler[] | DocsAuthExtension;\n}\n\n/** Options consumed by the {@link HonoServer} constructor. */\nexport interface HonoServerOptions<TEnv extends Env = Env> {\n /**\n * API tag — only routes whose `api` matches this value are mounted.\n * If omitted, every route in the registry is mounted.\n */\n api?: string;\n\n /** Pre-resolved route registry (typically the codegen output). */\n routes: AnyRouteDef[];\n\n /** URL prefix mounted before every route path. Default: `\"\"`. */\n basePath?: string;\n\n /** Hono middlewares applied to every route (after the built-ins). */\n middlewares?: MiddlewareHandler<TEnv>[];\n\n /**\n * Alias for `middlewares` — global middlewares applied to every route.\n * If both are provided, `globalMiddlewares` is appended after `middlewares`.\n */\n globalMiddlewares?: MiddlewareHandler<TEnv>[];\n\n /**\n * If `true`, the server validates the value returned by every handler\n * against the route's `output` schema and rejects mismatches with a 500\n * response. Useful in dev / staging. Default: `false`.\n */\n validateOutput?: boolean;\n\n /** Enable verbose logging of mounted routes at startup. Default: `false`. */\n verbose?: boolean;\n\n /** OpenAPI configuration. Omit to disable. */\n openapi?: OpenAPIConfig;\n\n /** Custom 404 handler. */\n notFound?: (c: Context<TEnv>) => Response | Promise<Response>;\n\n /** Custom error handler. */\n onError?: (err: unknown, c: Context<TEnv>) => Response | Promise<Response>;\n\n /**\n * Cross-cutting interceptor wrapping every handler call.\n * Ideal for response envelopes, business-error mapping, tracing.\n *\n * Pass a bare {@link RouteInterceptor} function, or an\n * {@link InterceptorConfig} (`{ output?, errors?, handler }`) so the\n * generated OpenAPI spec documents the wrapped responses.\n */\n interceptor?: InterceptorOption<TEnv>;\n\n /**\n * Cross-cutting error strategy applied to every uncaught error and injected\n * into every handler / interceptor context. See {@link ErrorHandler}.\n *\n * Typically shared across APIs — pass it once to `createApiRegistry` via\n * `{ services, errorHandler }`; this per-API field overrides it.\n */\n errorHandler?: ErrorHandler<TEnv>;\n\n /**\n * Structured {@link Logger} injected into every handler / interceptor /\n * error-handler context. Extend the package's `BaseLogger` to route to your\n * sink. Typically shared via `createApiRegistry({ services, logger })`; this\n * per-API field overrides it.\n */\n logger?: Logger;\n\n /**\n * Global DI services container (see {@link createServices}).\n *\n * When provided:\n * - a middleware is installed automatically so the built-in `ctx`\n * service resolves to the current request via `AsyncLocalStorage`;\n * - `services` is passed into every handler and the interceptor.\n *\n * The same container instance should be shared across every API of your\n * project — declare it once and pass it to {@link createApiRegistry}.\n */\n services?: AnyServicesContainer;\n}\n","/**\n * Built-in error types thrown by the server pipeline and their default\n * HTTP mapping — shared by the request handler and {@link BaseErrorHandler}.\n */\n\nimport type { ZodError } from \"zod\";\nimport { ValidationError } from \"./types\";\n\n/** Thrown when the request body cannot be read / parsed → HTTP 400. */\nexport class BadRequestError extends Error {\n readonly statusCode = 400 as const;\n constructor(message: string) {\n super(message);\n this.name = \"BadRequestError\";\n }\n}\n\n/** Thrown when a handler's return value fails the `output` schema → HTTP 500. */\nexport class OutputValidationError extends Error {\n readonly statusCode = 500 as const;\n constructor(readonly zodError: ZodError) {\n super(\"Output validation failed\");\n this.name = \"OutputValidationError\";\n }\n}\n\n/** Flatten a `ZodError` into a compact `{ path, code, message }[]`. */\nexport function formatZodIssues(error: ZodError): unknown {\n return error.issues.map((i) => ({\n path: i.path.join(\".\"),\n code: i.code,\n message: i.message,\n }));\n}\n\n/**\n * Default JSON mapping for the package's own errors (`ValidationError`,\n * `BadRequestError`, `OutputValidationError`). Returns `null` for anything\n * else so the caller can decide (rethrow / custom handler).\n */\nexport function defaultErrorResponse(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n c: any,\n err: unknown,\n): Response | null {\n if (err instanceof ValidationError) {\n return c.json(\n {\n success: false,\n error: \"Validation failed\",\n issues: formatZodIssues(err.zodError),\n },\n 400,\n );\n }\n if (err instanceof BadRequestError) {\n return c.json(\n { success: false, error: \"Bad Request\", message: err.message },\n 400,\n );\n }\n if (err instanceof OutputValidationError) {\n return c.json(\n {\n success: false,\n error: \"Output validation failed\",\n issues: formatZodIssues(err.zodError),\n },\n 500,\n );\n }\n return null;\n}\n","/**\n * OpenAPI 3.1 spec generator from {@link RouteDef} entries.\n *\n * Uses `@asteasolutions/zod-to-openapi` directly so users keep importing the\n * vanilla `zod` package (no opinionated `z` re-export required).\n */\n\nimport {\n OpenAPIRegistry,\n OpenApiGeneratorV31,\n extendZodWithOpenApi,\n} from \"@asteasolutions/zod-to-openapi\";\nimport { z } from \"zod\";\nimport type {\n AnyRouteDef,\n HttpMethod,\n InterceptorConfig,\n InterceptorErrorResponse,\n OpenAPIConfig,\n PayloadSource,\n} from \"./types\";\n\n// Patches Zod prototype with `.openapi()` and enables schema → OpenAPI\n// conversion for vanilla zod schemas. Idempotent — safe to call multiple times.\nextendZodWithOpenApi(z);\n\nconst DEFAULT_RESPONSE_DESCRIPTION = \"Successful response\";\nconst DEFAULT_ERROR_DESCRIPTION = \"Error response\";\n\nfunction defaultSource(method: HttpMethod): PayloadSource {\n return method === \"get\" ? \"query\" : \"json\";\n}\n\n/**\n * Resolve the success-envelope schema for a route, honouring an interceptor's\n * static schema or per-route factory. Falls back to the raw `route.output`.\n */\nfunction resolveSuccessSchema(\n routeOutput: z.ZodTypeAny | undefined,\n interceptor: InterceptorConfig | undefined,\n): z.ZodTypeAny | undefined {\n const out = interceptor?.output;\n if (!out) return routeOutput;\n return typeof out === \"function\" ? out(routeOutput) : out;\n}\n\n/** Normalise a declared error response to `{ description, schema? }`. */\nfunction normalizeErrorResponse(\n entry: InterceptorErrorResponse,\n): { description: string; schema?: z.ZodTypeAny } {\n // A Zod schema exposes `safeParse`; a plain config object does not.\n if (typeof (entry as { safeParse?: unknown }).safeParse === \"function\") {\n return { description: DEFAULT_ERROR_DESCRIPTION, schema: entry as z.ZodTypeAny };\n }\n const cfg = entry as { description?: string; schema?: z.ZodTypeAny };\n return { description: cfg.description ?? DEFAULT_ERROR_DESCRIPTION, schema: cfg.schema };\n}\n\n/** Build the OpenAPI document from the mounted route registry. */\nexport function buildOpenApiDocument(\n routes: AnyRouteDef[],\n basePath: string,\n config: OpenAPIConfig,\n interceptor?: InterceptorConfig,\n): Record<string, unknown> {\n const registry = new OpenAPIRegistry();\n\n if (config.securitySchemes) {\n for (const [name, scheme] of Object.entries(config.securitySchemes)) {\n // The registry's runtime accepts any spec-shaped object; cast through\n // `unknown` to satisfy zod-to-openapi's stricter typings.\n registry.registerComponent(\n \"securitySchemes\",\n name,\n scheme as unknown as Parameters<\n typeof registry.registerComponent\n >[2],\n );\n }\n }\n\n for (const route of routes) {\n const method = route.method;\n const source = route.source ?? defaultSource(method);\n const fullPath = joinPath(basePath, route.path ?? \"/\");\n const status = route.status ?? 200;\n\n const requestBody = buildRequestBody(method, source, route.input);\n const requestQuery = buildQueryOrParam(source, route.input, \"query\");\n const requestParams = buildQueryOrParam(source, route.input, \"param\");\n const operationId = makeOperationId(method, fullPath);\n\n // Success response — wrapped by the interceptor envelope when declared.\n const successSchema = resolveSuccessSchema(route.output, interceptor);\n const responses: Record<string, unknown> = {\n [status]: successSchema\n ? {\n description: DEFAULT_RESPONSE_DESCRIPTION,\n content: { \"application/json\": { schema: successSchema } },\n }\n : { description: DEFAULT_RESPONSE_DESCRIPTION },\n };\n\n // Declared error responses (interceptor.errors) applied to every operation.\n if (interceptor?.errors) {\n for (const [code, entry] of Object.entries(interceptor.errors)) {\n const { description, schema } = normalizeErrorResponse(entry);\n responses[code] = schema\n ? { description, content: { \"application/json\": { schema } } }\n : { description };\n }\n }\n\n registry.registerPath({\n method,\n path: convertExpressPathToOpenApi(fullPath),\n operationId,\n summary: route.summary,\n description: route.description,\n tags: route.tags,\n deprecated: route.deprecated,\n security: route.security,\n // Cast: registerPath types narrow query/params to ZodObject — we accept\n // any ZodTypeAny at runtime and let users pass plain objects via z.object.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n request: {\n ...(requestQuery ? { query: requestQuery } : {}),\n ...(requestParams ? { params: requestParams } : {}),\n ...(requestBody ? { body: requestBody } : {}),\n } as any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n responses: responses as any,\n });\n }\n\n const generator = new OpenApiGeneratorV31(registry.definitions);\n const document = generator.generateDocument({\n openapi: \"3.1.0\",\n // OpenAPIInfo is structurally compatible; cast to satisfy the `x-*` index.\n info: config.info as Parameters<\n typeof generator.generateDocument\n >[0][\"info\"],\n servers: config.servers,\n security: config.security,\n });\n return document as unknown as Record<string, unknown>;\n}\n\nfunction buildRequestBody(\n method: HttpMethod,\n source: PayloadSource,\n schema: z.ZodTypeAny | undefined,\n): { content: Record<string, { schema: z.ZodTypeAny }> } | null {\n if (!schema) return null;\n if (method === \"get\") return null;\n if (source === \"json\") {\n return { content: { \"application/json\": { schema } } };\n }\n if (source === \"form\") {\n return {\n content: { \"application/x-www-form-urlencoded\": { schema } },\n };\n }\n return null;\n}\n\nfunction buildQueryOrParam(\n source: PayloadSource,\n schema: z.ZodTypeAny | undefined,\n target: \"query\" | \"param\",\n): z.ZodTypeAny | undefined {\n if (!schema) return undefined;\n if (target === \"query\" && source === \"query\") return schema;\n if (target === \"param\" && source === \"param\") return schema;\n return undefined;\n}\n\n/** Convert `:foo` style express params to `{foo}` OpenAPI placeholders. */\nfunction convertExpressPathToOpenApi(path: string): string {\n return path.replace(/:([A-Za-z0-9_]+)/g, \"{$1}\");\n}\n\nfunction joinPath(base: string, path: string): string {\n const left = base.endsWith(\"/\") ? base.slice(0, -1) : base;\n const right = path.startsWith(\"/\") ? path : `/${path}`;\n const merged = `${left}${right}`;\n return merged === \"\" ? \"/\" : merged;\n}\n\nfunction makeOperationId(method: HttpMethod, path: string): string {\n const cleaned = path\n .replace(/[{}]/g, \"\")\n .replace(/\\/+/g, \"_\")\n .replace(/[^A-Za-z0-9_]/g, \"\")\n .replace(/^_+|_+$/g, \"\");\n return `${method}_${cleaned || \"root\"}`;\n}\n\n/**\n * Render a self-contained Scalar API Reference HTML page that points to the\n * generated spec. Loaded from CDN — no build step required.\n */\nexport function renderDocsHtml(specUrl: string, title: string): string {\n const safeUrl = specUrl.replace(/\"/g, \"&quot;\");\n const safeTitle = title\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\");\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n<title>${safeTitle}</title>\n</head>\n<body>\n<script id=\"api-reference\" data-url=\"${safeUrl}\"></script>\n<script src=\"https://cdn.jsdelivr.net/npm/@scalar/api-reference\"></script>\n</body>\n</html>`;\n}\n","/**\n * Login page renderer for `firebaseAuth`.\n * Standalone HTML — no JSX. Embeds the Firebase JS SDK from the official CDN\n * (modular v10) so users don't need a frontend build step.\n *\n * Flow:\n * 1. User signs in client-side (email/password or Google popup).\n * 2. We call `user.getIdToken(true)` and `POST` it to `{sessionPath}`.\n * 3. The server mints a session cookie and we redirect to `next`.\n */\n\ninterface LoginPageOptions {\n title: string;\n providers: (\"password\" | \"google\")[];\n apiKey: string;\n authDomain: string;\n sessionPath: string;\n next: string;\n error: string | null;\n /**\n * Firebase Auth emulator host (e.g. `127.0.0.1:9099`). When set, the client\n * SDK is pointed at the emulator via `connectAuthEmulator` so local sign-ins\n * match the server side (which the Admin SDK already routes to the emulator\n * through `FIREBASE_AUTH_EMULATOR_HOST`). A bare `host:port` is upgraded to\n * an `http://` URL.\n */\n authEmulatorHost?: string;\n}\n\n/** Normalise an emulator host (`host:port`) into a full `http://` URL. */\nfunction emulatorUrl(host: string | undefined): string {\n if (!host) return \"\";\n return /^https?:\\/\\//.test(host) ? host : `http://${host}`;\n}\n\nfunction htmlEscape(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#39;\");\n}\n\nfunction jsonEscape(value: string): string {\n // Safe for embedding inside a <script> string literal.\n return JSON.stringify(value).slice(1, -1);\n}\n\nexport function renderLoginPage(opts: LoginPageOptions): string {\n const showPassword = opts.providers.includes(\"password\");\n const showGoogle = opts.providers.includes(\"google\");\n const initialError = opts.error ? htmlEscape(opts.error) : \"\";\n\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n <title>${htmlEscape(opts.title)}</title>\n <style>\n :root { color-scheme: light dark; }\n * { box-sizing: border-box; }\n body {\n margin: 0;\n min-height: 100vh;\n display: grid;\n place-items: center;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n background: #f5f5f7;\n color: #1d1d1f;\n }\n @media (prefers-color-scheme: dark) {\n body { background: #1d1d1f; color: #f5f5f7; }\n .card { background: #2c2c2e !important; }\n input { background: #1d1d1f; color: #f5f5f7; border-color: #444; }\n input::placeholder { color: #888; }\n input:-webkit-autofill,\n input:-webkit-autofill:hover,\n input:-webkit-autofill:focus,\n input:-webkit-autofill:active {\n -webkit-text-fill-color: #f5f5f7 !important;\n -webkit-box-shadow: 0 0 0 1000px #1d1d1f inset !important;\n caret-color: #f5f5f7;\n }\n .divider { color: #888; }\n .divider::before, .divider::after { background: #444; }\n }\n .card {\n width: min(420px, 92vw);\n padding: 32px;\n background: #fff;\n border-radius: 14px;\n box-shadow: 0 20px 50px rgba(0,0,0,.08);\n }\n h1 { font-size: 22px; margin: 0 0 6px; font-weight: 600; }\n p.sub { margin: 0 0 24px; opacity: .7; font-size: 14px; }\n label { display: block; font-size: 13px; margin-bottom: 6px; opacity: .8; }\n input {\n width: 100%; padding: 11px 12px;\n border: 1px solid #d2d2d7; border-radius: 8px;\n font-size: 15px; outline: none; background: #fff; color: #1d1d1f;\n margin-bottom: 14px;\n }\n input::placeholder { color: #86868b; }\n input:focus { border-color: #0071e3; box-shadow: 0 0 0 3px rgba(0,113,227,.15); }\n /* Force readable text on Chrome's autofill (otherwise the input keeps\n the autofill's white background but inherits the page's dark-mode text\n colour, producing white-on-white). */\n input:-webkit-autofill,\n input:-webkit-autofill:hover,\n input:-webkit-autofill:focus,\n input:-webkit-autofill:active {\n -webkit-text-fill-color: #1d1d1f !important;\n -webkit-box-shadow: 0 0 0 1000px #fff inset !important;\n caret-color: #1d1d1f;\n transition: background-color 9999s ease-out 0s;\n }\n button {\n width: 100%; padding: 11px 12px; border: none; border-radius: 8px;\n font-size: 15px; font-weight: 500; cursor: pointer;\n transition: opacity .15s, transform .05s;\n }\n button:active { transform: scale(.98); }\n button:disabled { opacity: .55; cursor: progress; }\n .btn-primary { background: #0071e3; color: #fff; }\n .btn-google {\n background: #fff; color: #1d1d1f; border: 1px solid #d2d2d7;\n display: flex; align-items: center; justify-content: center; gap: 8px;\n }\n @media (prefers-color-scheme: dark) {\n .btn-google { background: #2c2c2e; color: #f5f5f7; border-color: #444; }\n }\n .divider {\n display: flex; align-items: center; gap: 12px;\n margin: 16px 0; font-size: 12px; opacity: .55; text-transform: uppercase;\n }\n .divider::before, .divider::after {\n content: \"\"; flex: 1; height: 1px; background: #d2d2d7;\n }\n .err {\n margin: 0 0 14px; padding: 10px 12px;\n background: rgba(255,59,48,.12); color: #ff3b30;\n border-radius: 8px; font-size: 13px;\n display: ${initialError ? \"block\" : \"none\"};\n }\n .ok {\n margin: 0 0 14px; padding: 10px 12px;\n background: rgba(52,199,89,.12); color: #34c759;\n border-radius: 8px; font-size: 13px; display: none;\n }\n </style>\n</head>\n<body>\n <main class=\"card\">\n <h1>${htmlEscape(opts.title)}</h1>\n <p class=\"sub\">Sign in to continue.</p>\n <div id=\"err\" class=\"err\">${initialError}</div>\n <div id=\"ok\" class=\"ok\"></div>\n\n ${\n showPassword\n ? `<form id=\"pwd-form\" autocomplete=\"on\">\n <label for=\"email\">Email</label>\n <input id=\"email\" type=\"email\" name=\"email\" autocomplete=\"username\" required />\n <label for=\"password\">Password</label>\n <input id=\"password\" type=\"password\" name=\"password\" autocomplete=\"current-password\" required />\n <button class=\"btn-primary\" type=\"submit\" id=\"pwd-submit\">Sign in</button>\n </form>`\n : \"\"\n }\n\n ${showPassword && showGoogle ? `<div class=\"divider\">or</div>` : \"\"}\n\n ${\n showGoogle\n ? `<button class=\"btn-google\" type=\"button\" id=\"google-btn\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 18 18\" aria-hidden=\"true\">\n <path fill=\"#4285F4\" d=\"M17.64 9.205c0-.638-.057-1.252-.164-1.841H9v3.481h4.844a4.14 4.14 0 0 1-1.796 2.716v2.259h2.908c1.702-1.567 2.684-3.875 2.684-6.615z\"/>\n <path fill=\"#34A853\" d=\"M9 18c2.43 0 4.467-.806 5.956-2.18l-2.908-2.259c-.806.54-1.837.86-3.048.86-2.344 0-4.328-1.584-5.036-3.711H.957v2.332A8.997 8.997 0 0 0 9 18z\"/>\n <path fill=\"#FBBC05\" d=\"M3.964 10.71A5.41 5.41 0 0 1 3.682 9c0-.593.102-1.17.282-1.71V4.958H.957A8.996 8.996 0 0 0 0 9c0 1.452.348 2.827.957 4.042l3.007-2.332z\"/>\n <path fill=\"#EA4335\" d=\"M9 3.58c1.321 0 2.508.454 3.44 1.345l2.582-2.58C13.463.891 11.426 0 9 0A8.997 8.997 0 0 0 .957 4.958L3.964 7.29C4.672 5.163 6.656 3.58 9 3.58z\"/>\n </svg>\n Continue with Google\n </button>`\n : \"\"\n }\n </main>\n\n <script type=\"module\">\n import { initializeApp } from \"https://www.gstatic.com/firebasejs/10.13.2/firebase-app.js\";\n import {\n getAuth,\n connectAuthEmulator,\n signInWithEmailAndPassword,\n signInWithPopup,\n GoogleAuthProvider,\n setPersistence,\n browserSessionPersistence,\n } from \"https://www.gstatic.com/firebasejs/10.13.2/firebase-auth.js\";\n\n const app = initializeApp({\n apiKey: \"${jsonEscape(opts.apiKey)}\",\n authDomain: \"${jsonEscape(opts.authDomain)}\",\n });\n const auth = getAuth(app);\n ${\n opts.authEmulatorHost\n ? `connectAuthEmulator(auth, ${JSON.stringify(\n emulatorUrl(opts.authEmulatorHost),\n )}, { disableWarnings: true });`\n : \"\"\n }\n // Don't persist client-side — the server-side session cookie is the source of truth.\n await setPersistence(auth, browserSessionPersistence).catch(() => {});\n\n const SESSION_PATH = \"${jsonEscape(opts.sessionPath)}\";\n const NEXT = ${JSON.stringify(opts.next)};\n\n // Defense-in-depth against open redirect (issue #07): only ever navigate\n // to a same-origin path, even though the server already sanitizes next.\n // Resolve against the current page URL (not just the origin) so a relative\n // \"next\" works behind any Cloud Functions / reverse-proxy path prefix.\n function safeNext(raw) {\n try {\n const u = new URL(raw, window.location.href);\n if (u.origin !== window.location.origin) return \"/\";\n return u.pathname + u.search + u.hash;\n } catch {\n return \"/\";\n }\n }\n\n const errEl = document.getElementById(\"err\");\n const okEl = document.getElementById(\"ok\");\n function showError(msg) {\n errEl.textContent = msg;\n errEl.style.display = \"block\";\n okEl.style.display = \"none\";\n }\n function showOk(msg) {\n okEl.textContent = msg;\n okEl.style.display = \"block\";\n errEl.style.display = \"none\";\n }\n\n async function exchangeForSession(user) {\n const idToken = await user.getIdToken(true);\n const res = await fetch(SESSION_PATH, {\n method: \"POST\",\n credentials: \"same-origin\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ idToken }),\n });\n if (!res.ok) {\n const data = await res.json().catch(() => ({}));\n throw new Error(data.error || \"Session exchange failed (\" + res.status + \")\");\n }\n // Sign out client-side immediately — we only needed the id token.\n try { await auth.signOut(); } catch {}\n showOk(\"Signed in. Redirecting…\");\n window.location.replace(safeNext(NEXT));\n }\n\n const pwdForm = document.getElementById(\"pwd-form\");\n if (pwdForm) {\n pwdForm.addEventListener(\"submit\", async (ev) => {\n ev.preventDefault();\n const submit = document.getElementById(\"pwd-submit\");\n submit.disabled = true;\n try {\n const email = document.getElementById(\"email\").value.trim();\n const password = document.getElementById(\"password\").value;\n const cred = await signInWithEmailAndPassword(auth, email, password);\n await exchangeForSession(cred.user);\n } catch (err) {\n showError(err && err.message ? err.message : String(err));\n submit.disabled = false;\n }\n });\n }\n\n const googleBtn = document.getElementById(\"google-btn\");\n if (googleBtn) {\n googleBtn.addEventListener(\"click\", async () => {\n googleBtn.disabled = true;\n try {\n const provider = new GoogleAuthProvider();\n const cred = await signInWithPopup(auth, provider);\n await exchangeForSession(cred.user);\n } catch (err) {\n showError(err && err.message ? err.message : String(err));\n googleBtn.disabled = false;\n }\n });\n }\n </script>\n</body>\n</html>`;\n}\n","/**\n * Session cookie + logout handlers for `firebaseAuth`.\n * Exchanges a Firebase ID token for an HttpOnly session cookie via the\n * Firebase Admin SDK (`createSessionCookie`), and clears it on logout.\n */\n\nimport type { RouteHandler } from \"../admin/router\";\nimport type { FirebaseAdminAuthLike } from \"./firebase-auth\";\n\nexport const SESSION_COOKIE_DEFAULT = \"__admin_session\";\n\ninterface SessionHandlerConfig {\n getAuth: () => FirebaseAdminAuthLike;\n cookieName: string;\n ttlDays: number;\n secure: boolean;\n sameSite: \"Strict\" | \"Lax\" | \"None\";\n}\n\ninterface LogoutHandlerConfig {\n getAuth: () => FirebaseAdminAuthLike;\n cookieName: string;\n secure: boolean;\n sameSite: \"Strict\" | \"Lax\" | \"None\";\n}\n\n// ---------------------------------------------------------------------------\n// Cookie utilities\n// ---------------------------------------------------------------------------\n\n/** Parse a `Cookie` header into a flat key→value map. Tolerant of malformed pairs. */\nexport function parseCookies(header: string): Record<string, string> {\n const out: Record<string, string> = {};\n if (!header) return out;\n for (const part of header.split(\";\")) {\n const eq = part.indexOf(\"=\");\n if (eq === -1) continue;\n const key = part.slice(0, eq).trim();\n if (!key) continue;\n let value = part.slice(eq + 1).trim();\n if (value.startsWith('\"') && value.endsWith('\"')) {\n value = value.slice(1, -1);\n }\n try {\n out[key] = decodeURIComponent(value);\n } catch {\n out[key] = value;\n }\n }\n return out;\n}\n\nfunction buildSetCookie(\n name: string,\n value: string,\n opts: {\n maxAgeSeconds: number;\n secure: boolean;\n sameSite: \"Strict\" | \"Lax\" | \"None\";\n path?: string;\n },\n): string {\n const segments = [\n `${name}=${value}`,\n `Path=${opts.path ?? \"/\"}`,\n `Max-Age=${opts.maxAgeSeconds}`,\n \"HttpOnly\",\n `SameSite=${opts.sameSite}`,\n ];\n if (opts.secure) segments.push(\"Secure\");\n return segments.join(\"; \");\n}\n\n/** Pull JSON body out of any Express-like request (works with `parseBody` already done by the host). */\nfunction readJsonBody(req: { body?: unknown }): Record<string, unknown> {\n const body = req.body;\n if (!body) return {};\n if (typeof body === \"string\") {\n try {\n return JSON.parse(body) as Record<string, unknown>;\n } catch {\n return {};\n }\n }\n if (typeof body === \"object\") return body as Record<string, unknown>;\n return {};\n}\n\n// ---------------------------------------------------------------------------\n// Handlers\n// ---------------------------------------------------------------------------\n\n/**\n * `POST /__session` — receives `{ idToken }`, verifies it via the Admin SDK,\n * mints a session cookie, and sets it on the response.\n */\nexport function createSessionHandler(cfg: SessionHandlerConfig): RouteHandler {\n return async (req, res) => {\n const body = readJsonBody(req);\n const idToken = typeof body.idToken === \"string\" ? body.idToken : \"\";\n if (!idToken) {\n res\n .status(400)\n .set(\"Content-Type\", \"application/json; charset=utf-8\")\n .send(JSON.stringify({ success: false, error: \"Missing idToken\" }));\n return;\n }\n\n const expiresInMs = cfg.ttlDays * 24 * 60 * 60 * 1000;\n try {\n const auth = cfg.getAuth();\n // Verify first so we surface auth errors before minting the cookie.\n const decoded = await auth.verifyIdToken(idToken, true);\n // Reject very old sign-ins to encourage fresh re-auth (Google guidance: < 5 min).\n const authTimeRaw = (decoded as { auth_time?: number }).auth_time;\n const authTime =\n typeof authTimeRaw === \"number\" ? authTimeRaw * 1000 : Date.now();\n if (Date.now() - authTime > 5 * 60 * 1000) {\n res\n .status(401)\n .set(\"Content-Type\", \"application/json; charset=utf-8\")\n .send(\n JSON.stringify({\n success: false,\n error: \"Recent sign-in required\",\n }),\n );\n return;\n }\n const sessionCookie = await auth.createSessionCookie(idToken, {\n expiresIn: expiresInMs,\n });\n const cookie = buildSetCookie(cfg.cookieName, encodeURIComponent(sessionCookie), {\n maxAgeSeconds: Math.floor(expiresInMs / 1000),\n secure: cfg.secure,\n sameSite: cfg.sameSite,\n });\n res\n .status(200)\n .set(\"Set-Cookie\", cookie)\n .set(\"Content-Type\", \"application/json; charset=utf-8\")\n .send(JSON.stringify({ success: true }));\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Invalid idToken\";\n res\n .status(401)\n .set(\"Content-Type\", \"application/json; charset=utf-8\")\n .send(JSON.stringify({ success: false, error: message }));\n }\n };\n}\n\n/**\n * `POST /__logout` — clears the session cookie and revokes the user's refresh\n * tokens (best-effort; failure to revoke does not block the logout).\n */\nexport function createLogoutHandler(cfg: LogoutHandlerConfig): RouteHandler {\n return async (req, res) => {\n try {\n const cookieHeader = req.headers?.cookie;\n const raw = Array.isArray(cookieHeader) ? cookieHeader.join(\"; \") : cookieHeader;\n const cookies = parseCookies(typeof raw === \"string\" ? raw : \"\");\n const session = cookies[cfg.cookieName];\n if (session) {\n try {\n const auth = cfg.getAuth();\n const decoded = await auth.verifySessionCookie(session, false);\n await auth.revokeRefreshTokens(decoded.uid);\n } catch {\n /* best-effort */\n }\n }\n } finally {\n const expired = buildSetCookie(cfg.cookieName, \"\", {\n maxAgeSeconds: 0,\n secure: cfg.secure,\n sameSite: cfg.sameSite,\n });\n res\n .status(200)\n .set(\"Set-Cookie\", expired)\n .set(\"Content-Type\", \"application/json; charset=utf-8\")\n .send(JSON.stringify({ success: true }));\n }\n };\n}\n","/**\n * Hono-native auth guards for the OpenAPI docs / spec endpoints.\n *\n * These return plain Hono `MiddlewareHandler`s, so they slot into\n * `OpenAPIConfig.docsAuth` (which protects only `/docs` + `/openapi.json`,\n * never the API routes). For a fully custom flow, pass your own middleware\n * instead of these helpers.\n */\n\nimport type { MiddlewareHandler } from \"hono\";\nimport { renderLoginPage } from \"../auth/login-page\";\nimport { parseCookies } from \"../auth/session\";\nimport type {\n DecodedIdTokenLike,\n FirebaseAdminAuthLike,\n} from \"../auth/firebase-auth\";\n\n/** Decoded token shape — kept minimal to avoid a hard firebase-admin import. */\nexport interface DecodedBearerToken {\n uid: string;\n email?: string;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n [claim: string]: any;\n}\n\n/** Minimal Firebase Admin Auth surface used by {@link firebaseBearerAuth}. */\nexport interface FirebaseBearerAuthLike {\n verifyIdToken(\n idToken: string,\n checkRevoked?: boolean,\n ): Promise<DecodedBearerToken>;\n}\n\n/** Options for {@link firebaseBearerAuth}. */\nexport interface FirebaseBearerAuthOptions {\n /**\n * Returns the Firebase Admin Auth instance, e.g. `() => getAuth()`. Called\n * lazily on each request so `initializeApp()` runs first.\n */\n getAuth: () => FirebaseBearerAuthLike;\n /**\n * Authorization policy. Return `false` (or throw) to reject the request,\n * any truthy value to allow. Defaults to allowing any verified token.\n */\n allow?: (token: DecodedBearerToken) => boolean | Promise<boolean>;\n /** Revoke check passed to `verifyIdToken`. Default: `false`. */\n checkRevoked?: boolean;\n /**\n * Context key under which the decoded token is stored (`c.set(key, token)`)\n * for downstream handlers. Default: `\"docsUser\"`.\n */\n contextKey?: string;\n}\n\n/**\n * Guard the docs / spec endpoints with a Firebase ID token (Bearer scheme).\n *\n * @example\n * ```ts\n * import { getAuth } from \"firebase-admin/auth\";\n * import { firebaseBearerAuth } from \"@lpdjs/firestore-repo-service/servers/hono\";\n *\n * openapi: {\n * info,\n * docsAuth: firebaseBearerAuth({\n * getAuth: () => getAuth(),\n * allow: (t) => t.admin === true,\n * }),\n * }\n * ```\n */\nexport function firebaseBearerAuth(\n options: FirebaseBearerAuthOptions,\n): MiddlewareHandler {\n const {\n getAuth,\n allow,\n checkRevoked = false,\n contextKey = \"docsUser\",\n } = options;\n\n return async (c, next) => {\n const header = c.req.header(\"authorization\") ?? c.req.header(\"Authorization\");\n const match = header?.match(/^Bearer\\s+(.+)$/i);\n if (!match) {\n return c.json({ error: \"Unauthorized\" }, 401, {\n \"WWW-Authenticate\": \"Bearer\",\n });\n }\n\n let decoded: DecodedBearerToken;\n try {\n decoded = await getAuth().verifyIdToken(match[1]!, checkRevoked);\n } catch {\n return c.json({ error: \"Unauthorized\" }, 401, {\n \"WWW-Authenticate\": \"Bearer\",\n });\n }\n\n if (allow) {\n let permitted = false;\n try {\n permitted = await allow(decoded);\n } catch {\n permitted = false;\n }\n if (!permitted) return c.json({ error: \"Forbidden\" }, 403);\n }\n\n c.set(contextKey, decoded);\n return next();\n };\n}\n\n/** Options for {@link basicAuth}. */\nexport interface BasicAuthOptions {\n username: string;\n password: string;\n /** Realm advertised in the `WWW-Authenticate` header. Default: `\"Docs\"`. */\n realm?: string;\n}\n\n/**\n * Guard the docs / spec endpoints with HTTP Basic Auth.\n *\n * @example\n * ```ts\n * openapi: { info, docsAuth: basicAuth({ username: \"admin\", password: \"secret\" }) }\n * ```\n */\nexport function basicAuth(options: BasicAuthOptions): MiddlewareHandler {\n const { username, password, realm = \"Docs\" } = options;\n const expected = `Basic ${btoa(`${username}:${password}`)}`;\n\n return async (c, next) => {\n const header =\n c.req.header(\"authorization\") ?? c.req.header(\"Authorization\") ?? \"\";\n if (!timingSafeEqual(header, expected)) {\n return c.text(\"Unauthorized\", 401, {\n \"WWW-Authenticate\": `Basic realm=\"${realm}\"`,\n });\n }\n return next();\n };\n}\n\n/** Constant-time string comparison to avoid leaking length/contents via timing. */\nfunction timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n let diff = 0;\n for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);\n return diff === 0;\n}\n\n// ===========================================================================\n// Firebase login-form auth (session cookie) — mirrors the admin server flow\n// ===========================================================================\n\n/** A bare auxiliary route name (relative to the docs directory). */\nexport interface DocsAuthRoute {\n method: \"GET\" | \"POST\";\n /** Bare path segment relative to the docs directory, e.g. `\"__login\"`. */\n name: string;\n handler: MiddlewareHandler;\n}\n\n/**\n * Richer `docsAuth` value (vs. a bare `MiddlewareHandler`): a guard middleware\n * **plus** auxiliary routes (login page, session exchange, logout) that the\n * {@link HonoServer} mounts next to the docs/spec endpoints. Mirrors the admin\n * server's `AuthExtension`. Produced by {@link firebaseDocsAuth}.\n */\nexport interface DocsAuthExtension {\n readonly __docsAuthExtension: true;\n /** Guard applied to the docs UI + JSON spec endpoints. */\n middleware: MiddlewareHandler;\n /** Auxiliary routes mounted (unguarded) in the docs directory. */\n routes: DocsAuthRoute[];\n /** Bare login route name, used to redirect unauthenticated browsers. */\n loginName: string;\n}\n\n/** Type guard distinguishing a {@link DocsAuthExtension} from a raw middleware. */\nexport function isDocsAuthExtension(value: unknown): value is DocsAuthExtension {\n return (\n typeof value === \"object\" &&\n value !== null &&\n (value as { __docsAuthExtension?: unknown }).__docsAuthExtension === true\n );\n}\n\n/** Options for {@link firebaseDocsAuth}. */\nexport interface FirebaseDocsAuthOptions {\n /**\n * Returns the Firebase Admin Auth instance, e.g. `() => getAuth()`. Called\n * lazily on each request so `initializeApp()` runs first. Must expose\n * `verifyIdToken`, `verifySessionCookie`, `createSessionCookie`,\n * `revokeRefreshTokens`.\n */\n getAuth: () => FirebaseAdminAuthLike;\n /** Firebase Web API key — required by the JS SDK on the login page. */\n apiKey: string;\n /** Firebase Auth domain (e.g. `my-project.firebaseapp.com`). */\n authDomain: string;\n /**\n * Transport mode. `\"cookie\"` (default) gates the docs behind the bundled\n * login form + a server-side session cookie. `\"both\"` additionally accepts a\n * `Bearer` ID token (handy to embed the docs in an authenticated iframe).\n */\n mode?: \"cookie\" | \"both\";\n /**\n * Authorization policy. Return `false` (or throw) to reject, any truthy value\n * to allow. Defaults to allowing any verified user. Receives the decoded\n * ID-token / session-cookie claims.\n */\n allow?: (token: DecodedIdTokenLike) => boolean | Promise<boolean>;\n /** Providers shown on the login page. Default: `[\"password\", \"google\"]`. */\n providers?: (\"password\" | \"google\")[];\n /** Login page title. Default: `\"Docs sign-in\"`. */\n title?: string;\n /** Session cookie name. Default: `\"__docs_session\"`. */\n cookieName?: string;\n /** Session cookie TTL in days. Default: `5` (Firebase max is 14). */\n sessionTtlDays?: number;\n /** Cookie `Secure` flag. Default: `true` (set `false` only for local HTTP). */\n secureCookie?: boolean;\n /** Cookie `SameSite`. Default: `\"Lax\"`. */\n sameSite?: \"Strict\" | \"Lax\" | \"None\";\n /**\n * Context key under which the decoded token is stored (`c.set(key, token)`)\n * for downstream handlers. Default: `\"docsUser\"`.\n */\n contextKey?: string;\n /**\n * Behaviour when authentication fails. `\"redirect\"` (default) sends browser\n * `GET`s to the login page; `\"401\"` always returns a JSON 401.\n */\n onUnauthenticated?: \"redirect\" | \"401\";\n /**\n * Firebase Auth emulator host (e.g. `127.0.0.1:9099`). When set, the login\n * page's client SDK targets the emulator via `connectAuthEmulator`, matching\n * the Admin SDK (which already routes to the emulator when\n * `FIREBASE_AUTH_EMULATOR_HOST` is set). Defaults to that env var; pass `\"\"`\n * to force it off.\n */\n authEmulatorHost?: string;\n}\n\nconst LOGIN_NAME = \"__login\";\nconst SESSION_NAME = \"__session\";\nconst LOGOUT_NAME = \"__logout\";\n\n/** Last path segment, e.g. `/v1/docs` → `docs`. Empty string for `/`. */\nfunction lastSegment(path: string): string {\n const segs = path.split(\"?\")[0]!.split(\"/\").filter(Boolean);\n return segs[segs.length - 1] ?? \"\";\n}\n\n/** Build a `Set-Cookie` header value (HttpOnly, path-scoped). */\nfunction buildSetCookie(\n name: string,\n value: string,\n opts: {\n maxAgeSeconds: number;\n secure: boolean;\n sameSite: \"Strict\" | \"Lax\" | \"None\";\n },\n): string {\n const segments = [\n `${name}=${value}`,\n \"Path=/\",\n `Max-Age=${opts.maxAgeSeconds}`,\n \"HttpOnly\",\n `SameSite=${opts.sameSite}`,\n ];\n if (opts.secure) segments.push(\"Secure\");\n return segments.join(\"; \");\n}\n\n/** Restrict a `next` target to a simple same-origin relative token. */\nfunction sanitizeNext(raw: string | undefined, fallback: string): string {\n if (!raw) return fallback;\n if (raw.startsWith(\"/\") || raw.includes(\"://\") || raw.includes(\"\\\\\")) {\n return fallback;\n }\n return raw;\n}\n\n/**\n * Guard the docs / spec endpoints with a Firebase **login form + session\n * cookie**, the same flow as the admin server — instead of forcing callers to\n * craft a `Bearer` token by hand.\n *\n * Returns a {@link DocsAuthExtension}: pass it straight to\n * `OpenAPIConfig.docsAuth`. The {@link HonoServer} mounts the bundled\n * `__login` / `__session` / `__logout` routes next to the docs and applies the\n * guard. Unauthenticated browsers are redirected to the login page; once signed\n * in, an HttpOnly session cookie keeps them authenticated.\n *\n * @example\n * ```ts\n * import { getAuth } from \"firebase-admin/auth\";\n * import { firebaseDocsAuth } from \"@lpdjs/firestore-repo-service/servers/hono\";\n *\n * openapi: {\n * info,\n * docsAuth: firebaseDocsAuth({\n * getAuth: () => getAuth(),\n * apiKey: process.env.FIREBASE_API_KEY!,\n * authDomain: process.env.FIREBASE_AUTH_DOMAIN!,\n * allow: (t) => t.admin === true, // optional policy\n * }),\n * }\n * ```\n */\nexport function firebaseDocsAuth(\n options: FirebaseDocsAuthOptions,\n): DocsAuthExtension {\n const {\n getAuth,\n apiKey,\n authDomain,\n mode = \"cookie\",\n allow,\n providers = [\"password\", \"google\"],\n title = \"Docs sign-in\",\n cookieName = \"__docs_session\",\n sessionTtlDays = 5,\n secureCookie = true,\n sameSite = \"Lax\",\n contextKey = \"docsUser\",\n onUnauthenticated = \"redirect\",\n authEmulatorHost = process.env[\"FIREBASE_AUTH_EMULATOR_HOST\"],\n } = options;\n\n if (!apiKey || !authDomain) {\n throw new Error(\n \"[firebaseDocsAuth] `apiKey` and `authDomain` are required for the login \" +\n \"page. Find both in the Firebase Console → Project Settings → General → \" +\n \"Web app config.\",\n );\n }\n\n async function passesAllow(token: DecodedIdTokenLike): Promise<boolean> {\n if (!allow) return true;\n try {\n return Boolean(await allow(token));\n } catch {\n return false;\n }\n }\n\n // ── Login page (GET __login) ──────────────────────────────────────────────\n const loginHandler: MiddlewareHandler = async (c) => {\n const next = sanitizeNext(c.req.query(\"next\"), \"docs\");\n const error = c.req.query(\"error\") ?? null;\n const html = renderLoginPage({\n title,\n providers,\n apiKey,\n authDomain,\n // Relative to the login page URL (resolved client-side), so it works\n // behind any Cloud Functions / reverse-proxy prefix.\n sessionPath: SESSION_NAME,\n next,\n error,\n authEmulatorHost,\n });\n return c.html(html, 200, { \"Cache-Control\": \"no-store\" });\n };\n\n // ── Session exchange (POST __session) ─────────────────────────────────────\n const sessionHandler: MiddlewareHandler = async (c) => {\n let idToken = \"\";\n try {\n const body = (await c.req.json()) as { idToken?: unknown };\n idToken = typeof body.idToken === \"string\" ? body.idToken : \"\";\n } catch {\n idToken = \"\";\n }\n if (!idToken) {\n return c.json({ success: false, error: \"Missing idToken\" }, 400);\n }\n\n const expiresInMs = sessionTtlDays * 24 * 60 * 60 * 1000;\n try {\n const auth = getAuth();\n const decoded = await auth.verifyIdToken(idToken, true);\n if (!(await passesAllow(decoded))) {\n return c.json({ success: false, error: \"Forbidden\" }, 403);\n }\n // Reject stale sign-ins (Google guidance: require a recent auth, < 5 min).\n const authTimeRaw = (decoded as { auth_time?: number }).auth_time;\n const authTime =\n typeof authTimeRaw === \"number\" ? authTimeRaw * 1000 : Date.now();\n if (Date.now() - authTime > 5 * 60 * 1000) {\n return c.json(\n { success: false, error: \"Recent sign-in required\" },\n 401,\n );\n }\n const sessionCookie = await auth.createSessionCookie(idToken, {\n expiresIn: expiresInMs,\n });\n const cookie = buildSetCookie(\n cookieName,\n encodeURIComponent(sessionCookie),\n {\n maxAgeSeconds: Math.floor(expiresInMs / 1000),\n secure: secureCookie,\n sameSite,\n },\n );\n c.header(\"Set-Cookie\", cookie);\n return c.json({ success: true });\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Invalid idToken\";\n return c.json({ success: false, error: message }, 401);\n }\n };\n\n // ── Logout (POST __logout) ────────────────────────────────────────────────\n const logoutHandler: MiddlewareHandler = async (c) => {\n const cookies = parseCookies(c.req.header(\"cookie\") ?? \"\");\n const session = cookies[cookieName];\n if (session) {\n try {\n const auth = getAuth();\n const decoded = await auth.verifySessionCookie(session, false);\n await auth.revokeRefreshTokens(decoded.uid);\n } catch {\n /* best-effort */\n }\n }\n c.header(\n \"Set-Cookie\",\n buildSetCookie(cookieName, \"\", {\n maxAgeSeconds: 0,\n secure: secureCookie,\n sameSite,\n }),\n );\n return c.json({ success: true });\n };\n\n // ── Guard middleware (docs + spec) ────────────────────────────────────────\n const middleware: MiddlewareHandler = async (c, next) => {\n const auth = getAuth();\n\n // mode \"both\": accept a Bearer ID token first (e.g. iframe embedding).\n if (mode === \"both\") {\n const header =\n c.req.header(\"authorization\") ?? c.req.header(\"Authorization\");\n const match = header?.match(/^Bearer\\s+(.+)$/i);\n if (match) {\n try {\n const decoded = await auth.verifyIdToken(match[1]!, false);\n if (await passesAllow(decoded)) {\n c.set(contextKey, decoded);\n return next();\n }\n } catch {\n /* fall through to cookie / unauthenticated */\n }\n }\n }\n\n // Session cookie.\n const cookies = parseCookies(c.req.header(\"cookie\") ?? \"\");\n const session = cookies[cookieName];\n if (session) {\n try {\n const decoded = await auth.verifySessionCookie(session, false);\n if (await passesAllow(decoded)) {\n c.set(contextKey, decoded);\n return next();\n }\n } catch {\n /* unauthenticated */\n }\n }\n\n // Unauthenticated.\n const accept = c.req.header(\"accept\") ?? \"\";\n const isBrowserGet =\n c.req.method === \"GET\" && accept.includes(\"text/html\");\n if (onUnauthenticated === \"redirect\" && isBrowserGet) {\n const target = encodeURIComponent(lastSegment(c.req.path) || \"docs\");\n // Relative to the requested page (login route is a sibling), so the\n // external prefix is preserved by the browser.\n return c.redirect(`${LOGIN_NAME}?next=${target}`, 302);\n }\n return c.json({ error: \"Unauthorized\" }, 401);\n };\n\n return {\n __docsAuthExtension: true,\n middleware,\n loginName: LOGIN_NAME,\n routes: [\n { method: \"GET\", name: LOGIN_NAME, handler: loginHandler },\n { method: \"POST\", name: SESSION_NAME, handler: sessionHandler },\n { method: \"POST\", name: LOGOUT_NAME, handler: logoutHandler },\n ],\n };\n}\n","/**\n * Global DI services container for the Hono server.\n *\n * Lets you declare all your singletons (repositories, SDK clients, useCases,\n * loggers…) **once**, then inject them anywhere — routes, interceptors,\n * cron jobs, Firestore triggers, tests.\n *\n * ## How it works\n *\n * - Each service is constructed **lazily** on first access and cached for\n * the process lifetime (perfect for Cloud Functions cold-start).\n * - Providers may be **classes** (auto-injected: `new Class(services)`)\n * or **factories** (`(services) => instance`). Mix both freely.\n * - Inter-service dependencies are inferred either by destructuring the\n * factory argument (`postRepo: ({ db }) => new PostRepo(db)`) or by\n * reading `this.services.*` inside a class.\n * - A built-in `ctx` service exposes the **current request's** Hono\n * `Context` via `AsyncLocalStorage`. UseCases access\n * `this.services.ctx.c.get(\"user\")` without any plumbing.\n * - Cycles are detected at first access with a clear error.\n *\n * @example\n * ```ts\n * // src/services.ts — infrastructure singletons only (no useCases here)\n * import { createServices } from \"@lpdjs/firestore-repo-service/servers/hono\";\n * import { PostRepo } from \"./domains/posts/PostRepo.js\";\n * import { Mailer } from \"./services/Mailer.js\";\n *\n * export const services = createServices({\n * postRepo: PostRepo, // class form (auto-injected)\n * mailer: ({ ctx }) => new Mailer(ctx), // factory form\n * });\n *\n * export type Services = typeof services;\n * ```\n *\n * @example\n * ```ts\n * // src/apis.ts\n * import { services } from \"./services.js\";\n * export const apis = createApiRegistry(\n * { v1: { basePath: \"/v1\", ... } },\n * { services },\n * );\n * ```\n *\n * @example\n * ```ts\n * // Inside a useCase — extends the UseCase base, owns its Zod schemas\n * import { z } from \"zod\";\n * import { UseCase } from \"@lpdjs/firestore-repo-service/servers/hono\";\n * import type { Services } from \"../../services.js\";\n *\n * const input = z.object({ title: z.string() });\n * const output = z.object({ id: z.string() });\n *\n * export class CreatePostUseCase extends UseCase<typeof input, typeof output, Services> {\n * static readonly input = input;\n * static readonly output = output;\n *\n * async execute(payload: z.infer<typeof input>): Promise<z.infer<typeof output>> {\n * const user = this.services.ctx.c.get(\"user\");\n * return this.services.postRepo.create({ ...payload, authorId: user.id });\n * }\n * }\n * ```\n */\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport type { Context, Env, MiddlewareHandler } from \"hono\";\n\n// ---------------------------------------------------------------------------\n// Request-scoped context (AsyncLocalStorage backed)\n// ---------------------------------------------------------------------------\n\n/**\n * Per-request context exposed to every service / useCase.\n * The instance is a **stable singleton** but its `c` getter resolves to the\n * Hono `Context` of the currently-handled request via `AsyncLocalStorage`.\n *\n * Outside of a request (cron, manual scripts, tests), wrap your call in\n * {@link withRequestContext} to supply a context, otherwise accessing `c`\n * will throw.\n */\nexport interface RequestContext<TEnv extends Env = Env> {\n /** Hono `Context` of the currently-handled request. */\n readonly c: Context<TEnv>;\n /**\n * Same as `c` but returns `undefined` instead of throwing when called\n * outside a request scope. Useful for opportunistic logging.\n */\n readonly maybeC: Context<TEnv> | undefined;\n}\n\ninterface RequestStore {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n c: Context<any>;\n}\n\nconst als = new AsyncLocalStorage<RequestStore>();\n\nconst requestContextSingleton: RequestContext = Object.freeze({\n get c() {\n const store = als.getStore();\n if (!store) {\n throw new Error(\n \"[services] requestContext.c was accessed outside of a request. \" +\n \"Wrap non-HTTP code paths (cron, triggers, scripts, tests) in \" +\n \"`withRequestContext({ c }, () => ...)` to supply a Hono Context.\",\n );\n }\n return store.c;\n },\n get maybeC() {\n return als.getStore()?.c;\n },\n});\n\n/**\n * Hono middleware installed automatically by `HonoServer` when a `services`\n * container is provided. Populates the AsyncLocalStorage so the built-in\n * `ctx` service resolves to the current request.\n *\n * Exported for advanced cases (custom server / non-Hono adapter); you do not\n * need to call this manually in the standard flow.\n */\nexport function createRequestContextMiddleware(): MiddlewareHandler {\n return async (c, next) => {\n await als.run({ c }, async () => {\n await next();\n });\n };\n}\n\n/**\n * Run `fn` with a synthetic request context — required when invoking\n * services outside an HTTP handler (cron jobs, Firestore triggers, scripts,\n * unit tests). Inside `fn`, `services.ctx.c` resolves to the supplied `c`.\n *\n * @example\n * ```ts\n * // A cron that reuses a useCase\n * export const dailyTask = onSchedule(\"every 24 hours\", async () => {\n * await withRequestContext({ c: fakeContext() }, async () => {\n * await services.createPostUseCase.execute({ ... });\n * });\n * });\n * ```\n */\nexport function withRequestContext<T>(\n ctx: { c: Context },\n fn: () => Promise<T> | T,\n): Promise<T> {\n return als.run({ c: ctx.c }, async () => fn());\n}\n\n// ---------------------------------------------------------------------------\n// createServices\n// ---------------------------------------------------------------------------\n\n/**\n * Reserved service name — the built-in request context. User factories\n * cannot override it.\n */\nconst CTX_KEY = \"ctx\" as const;\n\n/**\n * Helper that derives the public services type from an output map.\n * Each entry in `TMap` is the instance type returned by its provider.\n * The built-in `ctx` is always present.\n */\nexport type ServicesOf<TMap> = { readonly ctx: RequestContext } & {\n readonly [K in keyof TMap]: TMap[K];\n};\n\n/**\n * A single provider entry — either a factory `(deps) => R` or a class\n * `new (deps) => R`. `deps` is the *complete* services proxy\n * (siblings + built-in `ctx`).\n */\nexport type ServiceProvider<TMap, R> =\n | ((deps: ServicesOf<TMap>) => R)\n | (new (deps: ServicesOf<TMap>) => R);\n\n/**\n * A provider map — each value is either a factory or a class constructor.\n */\nexport type ServiceProviderMap<TMap> = {\n [K in keyof TMap]: K extends typeof CTX_KEY\n ? never\n : ServiceProvider<TMap, TMap[K]>;\n};\n\n/**\n * Extract the instance/return type from a single provider value.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type ProviderReturn<P> = P extends new (...args: any) => infer R\n ? R\n : // eslint-disable-next-line @typescript-eslint/no-explicit-any\n P extends (...args: any) => infer R\n ? R\n : never;\n\n/**\n * Compute the output service map from an inferred provider map.\n */\nexport type MapFromProviders<P> = {\n [K in keyof P]: ProviderReturn<P[K]>;\n};\n\n/**\n * Container returned by {@link createServices}. Behaves like a plain object\n * keyed by service name — accessing a key triggers lazy instantiation.\n * Use `type Services = typeof services` to derive the public type.\n */\nexport type ServicesContainer<TMap> = TMap;\n\n/**\n * Build a lazy singleton DI container.\n *\n * @param providers A map of service name → factory function **or** class\n * constructor. The single argument (factory deps / first\n * ctor param) receives a deps proxy typed as the full\n * services map (siblings + the built-in `ctx`).\n *\n * @example Factory form\n * ```ts\n * db: () => getFirestore(),\n * postRepo: ({ db }) => new PostRepo(db),\n * ```\n *\n * @example Class form (zero-boilerplate auto-injection)\n * ```ts\n * class RepositoryService {\n * constructor(private readonly services: Services) {}\n * get posts() { return this.services.db.posts; }\n * }\n *\n * createServices({\n * db: () => getFirestore(), // ← factory\n * repository: RepositoryService, // ← bare class (deps auto-injected)\n * });\n * ```\n */\nexport function createServices<\n P extends Record<\n string,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ((deps: any) => unknown) | (new (deps: any) => unknown)\n >,\n>(providers: P): ServicesContainer<ServicesOf<MapFromProviders<P>>> {\n const cache = new Map<string, unknown>();\n const inProgress: string[] = [];\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const proxy = new Proxy({} as any, {\n get(_target, prop) {\n if (typeof prop !== \"string\") return undefined;\n if (prop === CTX_KEY) return requestContextSingleton;\n\n if (cache.has(prop)) return cache.get(prop);\n\n const provider = (providers as Record<string, unknown>)[prop];\n if (typeof provider !== \"function\") {\n throw new Error(\n `[services] unknown service \"${prop}\". Registered: ${\n [CTX_KEY, ...Object.keys(providers)].join(\", \")\n }`,\n );\n }\n\n if (inProgress.includes(prop)) {\n throw new Error(\n `[services] circular dependency detected: ${[\n ...inProgress,\n prop,\n ].join(\" → \")}`,\n );\n }\n\n inProgress.push(prop);\n try {\n const value = isClassConstructor(provider)\n ? new (provider as new (deps: unknown) => unknown)(proxy)\n : (provider as (deps: unknown) => unknown)(proxy);\n cache.set(prop, value);\n return value;\n } finally {\n inProgress.pop();\n }\n },\n has(_target, prop) {\n if (typeof prop !== \"string\") return false;\n return prop === CTX_KEY || prop in providers;\n },\n ownKeys() {\n return [CTX_KEY, ...Object.keys(providers)];\n },\n getOwnPropertyDescriptor(_t, prop) {\n if (typeof prop !== \"string\") return undefined;\n if (prop === CTX_KEY || prop in providers) {\n return { enumerable: true, configurable: true };\n }\n return undefined;\n },\n });\n\n return proxy as ServicesContainer<ServicesOf<MapFromProviders<P>>>;\n}\n\n/**\n * Detect whether a function value should be invoked with `new` (class\n * constructor) or called directly (plain factory). Relies on the `class`\n * keyword being preserved in `Function.prototype.toString`, which is the\n * case for TypeScript / esbuild output targeting modern JS (ES2015+).\n */\n// eslint-disable-next-line @typescript-eslint/ban-types\nfunction isClassConstructor(fn: Function): boolean {\n return /^class[\\s{]/.test(Function.prototype.toString.call(fn));\n}\n\n/**\n * Opaque container type used by registry / server signatures that don't\n * need to know the concrete services map.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyServicesContainer = ServicesContainer<Record<string, any>>;\n","/**\n * `HonoServer` — high-performance, fully-typed file-based API server for\n * Firebase Cloud Functions v2 (`onRequest`).\n *\n * Designed to:\n * - rely on **prebuild codegen** (`hono:gen` CLI) for static imports → zero\n * runtime filesystem scan, optimal cold-start;\n * - expose handlers receiving a Zod-parsed payload typed end-to-end;\n * - generate the OpenAPI 3.1 spec automatically from the same Zod schemas;\n * - bridge Hono's Web Fetch API to Cloud Functions' Express-style\n * `(req, res)` via `@hono/node-server`'s request listener.\n */\n\nimport { Hono } from \"hono\";\nimport { getRequestListener } from \"@hono/node-server\";\nimport { z } from \"zod\";\nimport type { Env, MiddlewareHandler } from \"hono\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type { HttpsOptions } from \"firebase-functions/v2/https\";\n\nimport type {\n AnyRouteDef,\n ErrorHandler,\n HonoServerOptions,\n HttpMethod,\n InterceptorConfig,\n InterceptorOption,\n Logger,\n PayloadSource,\n RouteInterceptor,\n} from \"./types\";\nimport {\n BadRequestError,\n defaultErrorResponse,\n OutputValidationError,\n} from \"./errors\";\nimport { ValidationError } from \"./types\";\nimport { buildOpenApiDocument, renderDocsHtml } from \"./openapi\";\nimport { isDocsAuthExtension } from \"./docs-auth\";\nimport {\n createRequestContextMiddleware,\n type AnyServicesContainer,\n} from \"./services\";\n\n/**\n * Minimal shape of `firebase-functions/v2/https` `onRequest` so the package\n * stays decoupled from a specific firebase-functions version. We import the\n * real type only when users pass `onRequest` to `toFunction(...)`.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype OnRequestFn = (...args: any[]) => any;\n\n/**\n * Sentinel passed to handlers / interceptors when the server is started\n * without a `services` container. Frozen so accidental writes throw.\n */\nconst EMPTY_SERVICES: AnyServicesContainer = Object.freeze(\n {},\n) as unknown as AnyServicesContainer;\n\nexport class HonoServer<TEnv extends Env = Env> {\n private readonly app: Hono<TEnv>;\n private readonly options: HonoServerOptions<TEnv>;\n private readonly mountedRoutes: AnyRouteDef[];\n private cachedSpec: Record<string, unknown> | null = null;\n\n constructor(options: HonoServerOptions<TEnv>) {\n this.options = options;\n this.app = new Hono<TEnv>();\n this.mountedRoutes = filterRoutes(options.routes, options.api);\n\n // Install the request-context middleware FIRST so the AsyncLocalStorage\n // is populated before any user middleware / handler runs.\n if (options.services) {\n this.app.use(\"*\", createRequestContextMiddleware());\n }\n\n const globalMws = [\n ...(options.middlewares ?? []),\n ...(options.globalMiddlewares ?? []),\n ];\n for (const mw of globalMws) this.app.use(\"*\", mw);\n\n this.mountRoutes();\n this.mountOpenApi();\n\n if (options.notFound) this.app.notFound(options.notFound);\n if (options.onError) this.app.onError(options.onError);\n }\n\n /** Underlying Hono instance — useful for advanced composition / tests. */\n get hono(): Hono<TEnv> {\n return this.app;\n }\n\n /** Raw `(req, res)` handler suitable for `onRequest()` / `http.createServer`. */\n get nodeHandler(): (req: IncomingMessage, res: ServerResponse) => void {\n return getRequestListener(this.app.fetch, {\n overrideGlobalObjects: false,\n });\n }\n\n /**\n * Wrap the server as a Cloud Functions v2 HTTP function.\n *\n * @param onRequest The `onRequest` factory imported from\n * `firebase-functions/v2/https` (or `firebase-functions/https`).\n * @param httpsOptions Options forwarded as the first argument to\n * `onRequest()` (region, memory, invoker, etc.).\n */\n toFunction(onRequest: OnRequestFn, httpsOptions?: HttpsOptions) {\n const handler = this.nodeHandler;\n if (httpsOptions) {\n return onRequest(httpsOptions, handler);\n }\n return onRequest(handler);\n }\n\n /** Generate (and cache) the OpenAPI 3.1 spec for the mounted routes. */\n buildOpenApiSpec(): Record<string, unknown> {\n if (this.cachedSpec) return this.cachedSpec;\n if (!this.options.openapi) {\n throw new Error(\"[HonoServer] openapi config not set\");\n }\n this.cachedSpec = buildOpenApiDocument(\n this.mountedRoutes,\n this.options.basePath ?? \"\",\n this.options.openapi,\n interceptorConfig(this.options.interceptor as InterceptorOption | undefined),\n );\n return this.cachedSpec;\n }\n\n // ── Internals ─────────────────────────────────────────────────────────\n\n private mountRoutes(): void {\n const basePath = this.options.basePath ?? \"\";\n const validateOutput = this.options.validateOutput ?? false;\n const verbose = this.options.verbose ?? false;\n\n for (const route of this.mountedRoutes) {\n if (!route.path) {\n throw new Error(\n `[HonoServer] route \"${route.method.toUpperCase()} (no path)\" — missing \\`path\\`. ` +\n \"Run the codegen so the path is derived from the file location, or set it explicitly.\",\n );\n }\n\n const fullPath = joinPath(basePath, route.path);\n const middlewares = route.middlewares ?? [];\n const source: PayloadSource =\n route.source ?? (route.method === \"get\" ? \"query\" : \"json\");\n\n const handler = makeRouteHandler(\n route,\n source,\n validateOutput,\n interceptorFn(this.options.interceptor as InterceptorOption | undefined),\n this.options.services,\n this.options.errorHandler as ErrorHandler | undefined,\n this.options.logger as Logger | undefined,\n );\n const httpMethod = route.method.toUpperCase() as\n | \"GET\"\n | \"POST\"\n | \"PUT\"\n | \"PATCH\"\n | \"DELETE\";\n // `app.on(method, path, handlers[])` accepts a variadic array of\n // handlers/middlewares — the typed `.get/.post/...` overloads don't\n // accept a spread of generic `MiddlewareHandler[]`.\n this.app.on(\n httpMethod,\n [fullPath],\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ...([...middlewares, handler] as any[]),\n );\n\n if (verbose) {\n // eslint-disable-next-line no-console\n console.log(\n `[HonoServer] ${route.method.toUpperCase().padEnd(6)} ${fullPath}`,\n );\n }\n }\n }\n\n private mountOpenApi(): void {\n const cfg = this.options.openapi;\n if (!cfg) return;\n const specPath = cfg.path ?? \"/openapi.json\";\n const docsPath = cfg.docsPath === undefined ? \"/docs\" : cfg.docsPath;\n const fullSpecPath = joinPath(this.options.basePath ?? \"\", specPath);\n const fullDocsPath =\n docsPath === false ? null : joinPath(this.options.basePath ?? \"\", docsPath);\n\n // Auth guards applied ONLY to the spec + docs endpoints (not API routes).\n // A DocsAuthExtension (e.g. firebaseDocsAuth) also contributes auxiliary\n // routes (login page / session / logout) mounted next to the docs.\n let guards: MiddlewareHandler[];\n if (isDocsAuthExtension(cfg.docsAuth)) {\n const ext = cfg.docsAuth;\n guards = [ext.middleware];\n // Mount the aux routes (unguarded) as siblings of the docs/spec page so\n // bare-name relative links/redirects stay correct behind any prefix.\n const authDir = dirOf(fullDocsPath ?? fullSpecPath);\n for (const route of ext.routes) {\n this.app.on(\n route.method,\n [joinPath(authDir, route.name)],\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n route.handler as any,\n );\n }\n } else {\n guards = cfg.docsAuth\n ? Array.isArray(cfg.docsAuth)\n ? cfg.docsAuth\n : [cfg.docsAuth]\n : [];\n }\n\n this.app.on(\n \"GET\",\n [fullSpecPath],\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ...([...guards, (c: any) => c.json(this.buildOpenApiSpec())] as any[]),\n );\n\n if (fullDocsPath) {\n // Resolve the spec URL relative to the docs page so it works whether the\n // server is mounted at `/`, behind a Firebase Functions prefix\n // (`/<project>/<region>/<funcName>/...`), or behind any reverse proxy.\n const relativeSpecUrl = relativeUrlFromTo(fullDocsPath, fullSpecPath);\n this.app.on(\n \"GET\",\n [fullDocsPath],\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ...([\n ...guards,\n (c: any) => c.html(renderDocsHtml(relativeSpecUrl, cfg.info.title)),\n ] as any[]),\n );\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Type guard distinguishing a structured interceptor from a bare function. */\nfunction isInterceptorConfig(\n i: InterceptorOption | undefined,\n): i is InterceptorConfig {\n return typeof i === \"object\" && i !== null && typeof i.handler === \"function\";\n}\n\n/** Extract the runtime interceptor function from either form. */\nfunction interceptorFn(\n i: InterceptorOption | undefined,\n): RouteInterceptor | undefined {\n if (!i) return undefined;\n return isInterceptorConfig(i) ? i.handler : i;\n}\n\n/** Extract the OpenAPI metadata (output/errors) from the structured form. */\nfunction interceptorConfig(\n i: InterceptorOption | undefined,\n): InterceptorConfig | undefined {\n return isInterceptorConfig(i) ? i : undefined;\n}\n\nfunction filterRoutes(\n routes: AnyRouteDef[],\n api: string | undefined,\n): AnyRouteDef[] {\n if (!api) return routes.slice();\n return routes.filter((r) =>\n Array.isArray(r.api) ? r.api.includes(api) : r.api === api,\n );\n}\n\nfunction joinPath(base: string, path: string): string {\n const left = base.endsWith(\"/\") ? base.slice(0, -1) : base;\n const right = path.startsWith(\"/\") ? path : `/${path}`;\n const merged = `${left}${right}`;\n return merged === \"\" ? \"/\" : merged;\n}\n\n/** Directory of an absolute pathname, e.g. `/v1/docs` → `/v1`, `/docs` → ``. */\nfunction dirOf(path: string): string {\n const idx = path.lastIndexOf(\"/\");\n return idx <= 0 ? \"\" : path.slice(0, idx);\n}\n\n/**\n * Compute a URL relative to `from` that points to `to`, both being absolute\n * pathnames (e.g. `/v1/docs` → `/v1/openapi.json` becomes `openapi.json`).\n * Lets the OpenAPI UI fetch the spec without knowing the upstream prefix\n * added by Firebase Functions / reverse proxies.\n */\nfunction relativeUrlFromTo(from: string, to: string): string {\n const fromSegs = from.split(\"/\").filter(Boolean);\n const toSegs = to.split(\"/\").filter(Boolean);\n // Drop the docs page filename so we resolve relative to its directory.\n fromSegs.pop();\n let common = 0;\n while (\n common < fromSegs.length &&\n common < toSegs.length &&\n fromSegs[common] === toSegs[common]\n ) {\n common++;\n }\n const ups = fromSegs.length - common;\n const rel = [\n ...Array(ups).fill(\"..\"),\n ...toSegs.slice(common),\n ].join(\"/\");\n return rel || \"./\";\n}\n\n/**\n * Build the actual Hono handler with input validation, output validation\n * (optional), and error normalisation.\n */\nfunction makeRouteHandler(\n route: AnyRouteDef,\n source: PayloadSource,\n validateOutput: boolean,\n interceptor: RouteInterceptor | undefined,\n services: AnyServicesContainer | undefined,\n errorHandler: ErrorHandler | undefined,\n logger: Logger | undefined,\n) {\n const inputSchema = route.input as z.ZodTypeAny | undefined;\n const outputSchema = route.output as z.ZodTypeAny | undefined;\n const status = route.status ?? 200;\n // Empty fallback so handlers/interceptors always see a `services` field\n // even when the server was started without a container.\n const servicesArg = (services ?? (EMPTY_SERVICES as AnyServicesContainer));\n\n return async (\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n c: any,\n ): Promise<Response> => {\n // Apply the injected ErrorHandler, falling back to the built-in envelope.\n // Returns a Response when handled, or null to let the error propagate.\n const applyErrorHandler = async (err: unknown): Promise<Response | null> => {\n if (errorHandler) {\n const handled = await errorHandler.handle({\n error: err,\n c,\n route,\n services: servicesArg,\n logger,\n });\n if (handled) return handled;\n }\n return defaultErrorResponse(c, err);\n };\n\n // `next()` runs validation + handler. Any Zod failure throws\n // `ValidationError` so the interceptor (or default catcher) can shape it.\n const callNext = async (): Promise<unknown> => {\n let payload: unknown = undefined;\n\n if (inputSchema) {\n let raw: unknown;\n try {\n raw = await readPayload(c, source, route.method);\n } catch (err) {\n // Body parse failure → wrap as a generic Error so the interceptor\n // can decide. Use a 400-shaped Error subclass.\n throw new BadRequestError(\n err instanceof Error ? err.message : String(err),\n );\n }\n const parsed = inputSchema.safeParse(raw);\n if (!parsed.success) {\n throw new ValidationError(parsed.error, source);\n }\n payload = parsed.data;\n }\n\n const result = await (route.handler as (ctx: {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n input: any;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n c: any;\n services: AnyServicesContainer;\n errorHandler: ErrorHandler | undefined;\n logger: Logger | undefined;\n }) => unknown)({\n input: payload,\n c,\n services: servicesArg,\n errorHandler,\n logger,\n });\n\n if (validateOutput && outputSchema && !(result instanceof Response)) {\n const checked = outputSchema.safeParse(result);\n if (!checked.success) {\n throw new OutputValidationError(checked.error);\n }\n return checked.data;\n }\n return result;\n };\n\n let result: unknown;\n if (interceptor) {\n // Interceptor owns the response shape — including validation errors.\n // If it rethrows, the injected ErrorHandler / default catcher applies.\n try {\n result = await interceptor({\n next: callNext,\n route,\n c,\n services: servicesArg,\n errorHandler,\n logger,\n });\n } catch (err) {\n const handled = await applyErrorHandler(err);\n if (handled) return handled;\n throw err;\n }\n } else {\n // Default behaviour — ErrorHandler first, then the built-in\n // ValidationError envelope, else bubble to onError / Hono.\n try {\n result = await callNext();\n } catch (err) {\n const handled = await applyErrorHandler(err);\n if (handled) return handled;\n throw err;\n }\n }\n\n if (result instanceof Response) return result;\n return c.json(result, status);\n };\n}\n\nasync function readPayload(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n c: any,\n source: PayloadSource,\n method: HttpMethod,\n): Promise<unknown> {\n switch (source) {\n case \"json\": {\n if (method === \"get\") return c.req.query();\n const text = await c.req.text();\n if (!text) return {};\n try {\n return JSON.parse(text);\n } catch (err) {\n throw new Error(\n `Invalid JSON body: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n case \"query\":\n return c.req.query();\n case \"form\": {\n const form = await c.req.parseBody();\n return form;\n }\n case \"param\":\n return c.req.param();\n default:\n return {};\n }\n}\n","/**\n * useCase ⇆ route bridge.\n *\n * A useCase owns its Zod `input` / `output` schemas (declared as `static`\n * members) and the business logic in {@link UseCase.execute}. Routes never\n * re-declare those schemas: they wire a useCase into an HTTP endpoint with the\n * one-liner {@link ApiRegistry.useCaseRoute} (or the standalone\n * {@link useCaseRoute}), keeping `routes.ts` flat and readable while the\n * types can never drift from the schemas.\n *\n * @example\n * ```ts\n * // useCase.ts — single source of truth for the I/O shape\n * import { z } from \"zod\";\n * import { UseCase } from \"@lpdjs/firestore-repo-service/servers/hono\";\n * import type { Services } from \"../../../../services.js\";\n *\n * const input = z.object({ example: z.string() });\n * const output = z.object({ id: z.string(), warning: z.string().nullable() });\n *\n * export class CreatePostUseCase extends UseCase<typeof input, typeof output, Services> {\n * static readonly input = input;\n * static readonly output = output;\n *\n * async execute(payload: z.infer<typeof input>): Promise<z.infer<typeof output>> {\n * return { id: payload.example, warning: null };\n * }\n * }\n *\n * // routes.ts — one line per route, `api` stays typed\n * export default defineRoutes([\n * useCaseRoute(CreatePostUseCase, { api: \"v1\", method: \"post\", tags: [\"posts\"] }),\n * ]);\n * ```\n */\n\nimport type { z } from \"zod\";\n\nimport type {\n HttpMethod,\n PayloadSource,\n RouteDef,\n} from \"./types\";\nimport type { AnyServicesContainer } from \"./services\";\n\n/**\n * Base class for every useCase — pure business logic, no HTTP awareness.\n * The shared {@link AnyServicesContainer} is injected via the constructor; the\n * `input` / `output` Zod schemas are declared as `static` members on the\n * concrete subclass (see {@link UseCaseClass}).\n *\n * @typeParam TInput Zod schema of the validated request payload.\n * @typeParam TOutput Zod schema of the success response.\n * @typeParam TServices Concrete services container injected into the useCase.\n */\nexport abstract class UseCase<\n TInput extends z.ZodTypeAny = z.ZodTypeAny,\n TOutput extends z.ZodTypeAny = z.ZodTypeAny,\n TServices extends AnyServicesContainer = AnyServicesContainer,\n> {\n constructor(protected readonly services: TServices) {}\n\n /** Run the business logic. Input is already validated against the schema. */\n abstract execute(input: z.infer<TInput>): Promise<z.infer<TOutput>>;\n}\n\n/**\n * Structural type of a concrete {@link UseCase} subclass — i.e. a constructor\n * that also exposes the `static input` / `static output` schemas. Consumed by\n * {@link useCaseRoute} to derive the route's Zod schemas and the handler types.\n */\nexport interface UseCaseClass<\n TInput extends z.ZodTypeAny,\n TOutput extends z.ZodTypeAny,\n TServices extends AnyServicesContainer = AnyServicesContainer,\n> {\n readonly input: TInput;\n readonly output: TOutput;\n new (services: TServices): UseCase<TInput, TOutput, TServices>;\n}\n\n/**\n * HTTP metadata for {@link useCaseRoute} — everything a route needs **except**\n * the input/output schemas and the handler, which are derived from the useCase.\n */\nexport interface UseCaseRouteMeta<TApi extends string = string> {\n /** API tag the route is mounted under. */\n api: TApi;\n /** HTTP method. */\n method: HttpMethod;\n /** URL path. Defaults to the codegen-derived path when omitted. */\n path?: string;\n /** Where the payload comes from. Defaults per method (see {@link RouteDef}). */\n source?: PayloadSource;\n /** Success status code. Default: 200. */\n status?: number;\n\n // ── OpenAPI metadata ─────────────────────────────────────────────────\n summary?: string;\n description?: string;\n tags?: string[];\n deprecated?: boolean;\n security?: Array<Record<string, string[]>>;\n}\n\n/**\n * Build a {@link RouteDef} from a useCase class and HTTP metadata. The route's\n * `input` / `output` schemas are read from the useCase's `static` members and\n * the handler instantiates the useCase with the request `services` and runs\n * {@link UseCase.execute}.\n *\n * Prefer the registry-bound `apis.useCaseRoute` (returned by\n * `createApiRegistry`) so that `meta.api` is narrowed to the registered tags.\n */\nexport function useCaseRoute<\n TInput extends z.ZodTypeAny,\n TOutput extends z.ZodTypeAny,\n TServices extends AnyServicesContainer,\n TApi extends string = string,\n>(\n useCaseClass: UseCaseClass<TInput, TOutput, TServices>,\n meta: UseCaseRouteMeta<TApi>,\n): RouteDef<TInput, TOutput> & { api: TApi } {\n return {\n ...meta,\n input: useCaseClass.input,\n output: useCaseClass.output,\n handler: ({ input, services }) =>\n new useCaseClass(services as TServices).execute(\n input as z.infer<TInput>,\n ),\n } as RouteDef<TInput, TOutput> & { api: TApi };\n}\n","/**\n * Typed multi-API registry.\n *\n * Lets you declare every API tag (= every Cloud Function) in **one place**,\n * with full TypeScript safety: the `api` field of {@link defineRoute} is\n * narrowed to the registered tags, and {@link toFunctions} returns one\n * `onRequest` Cloud Function per tag, named after its key.\n *\n * @example\n * ```ts\n * // apis.ts\n * import { createApiRegistry } from \"@lpdjs/firestore-repo-service/servers/hono\";\n * import { enrichUser } from \"./middlewares/enrich-user.js\";\n *\n * export const apis = createApiRegistry({\n * v1: {\n * basePath: \"/v1\",\n * middlewares: [enrichUser],\n * openapi: { info: { title: \"Public API\", version: \"1.0.0\" } },\n * },\n * webhooks: {\n * basePath: \"/hooks\",\n * openapi: { info: { title: \"Webhooks\", version: \"1.0.0\" } },\n * },\n * });\n *\n * // Use in routes — `api` is now typed \"v1\" | \"webhooks\".\n * export const defineRoute = apis.defineRoute;\n * export const useCaseRoute = apis.useCaseRoute;\n *\n * // index.ts (Cloud Functions entrypoint)\n * import { onRequest } from \"firebase-functions/v2/https\";\n * import { apis } from \"./apis.js\";\n * import { routes } from \"./domains/__generated__/routes.js\";\n *\n * export const { v1, webhooks } = apis.toFunctions(routes, onRequest, {\n * defaults: { region: \"us-central1\", invoker: \"public\" },\n * per: { v1: { memory: \"512MiB\" } },\n * });\n * // → URLs: https://<region>-<project>.cloudfunctions.net/v1/posts\n * // https://<region>-<project>.cloudfunctions.net/webhooks/...\n * ```\n */\n\nimport type { Env } from \"hono\";\nimport type { z } from \"zod\";\nimport type { HttpsOptions } from \"firebase-functions/v2/https\";\n\nimport type {\n AnyRouteDef,\n ErrorHandler,\n HonoServerOptions,\n Logger,\n RouteDef,\n RouteHandler,\n} from \"./types\";\nimport { HonoServer } from \"./server\";\nimport type { AnyServicesContainer } from \"./services\";\nimport {\n useCaseRoute as buildUseCaseRoute,\n type UseCaseClass,\n type UseCaseRouteMeta,\n} from \"./usecase\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype OnRequestFn = (...args: any[]) => any;\n\n/**\n * Per-API configuration. Same shape as {@link HonoServerOptions} minus the\n * `routes` (resolved by the registry), `api` (the registry key) and\n * `services` (set globally on the registry — see\n * {@link createApiRegistry}).\n */\nexport type ApiConfig<TEnv extends Env = Env> = Omit<\n HonoServerOptions<TEnv>,\n \"routes\" | \"api\" | \"services\"\n>;\n\n/** Map of API tag → its config. */\nexport type ApiConfigMap = Record<string, ApiConfig>;\n\n/**\n * Per-key excess-property guard.\n *\n * `createApiRegistry` infers its config map generically (`<const TMap …>`),\n * which normally **defeats** TypeScript's excess-property checking — a typo\n * like `openApi` or `middleware` (instead of `openapi` / `middlewares`) would\n * pass silently and the option would be ignored at runtime.\n *\n * This intersects the concrete {@link ApiConfig} shape (so every known key\n * keeps its real type **and JSDoc**, including nested objects such as\n * `openapi`) with a `never` mapping for any key absent from `ApiConfig` — which\n * makes typos a compile error, matching the strictness already enforced on the\n * CRUD server's `openapi`.\n *\n * @internal\n */\nexport type StrictApiConfig<T, TEnv extends Env = Env> = ApiConfig<TEnv> & {\n [K in keyof T as K extends keyof ApiConfig<TEnv> ? never : K]: never;\n};\n\n/**\n * Options accepted by {@link createApiRegistry} alongside the per-API\n * configs.\n */\nexport interface ApiRegistryOptions<\n TServices extends AnyServicesContainer = AnyServicesContainer,\n> {\n /**\n * Global DI services container shared across every API. See\n * {@link ServicesContainer} / `createServices`. When provided, every\n * `HonoServer` mounted by the registry receives it and exposes\n * `services` to every handler / interceptor.\n */\n services?: TServices;\n\n /**\n * Cross-cutting {@link ErrorHandler} shared across every API — injected into\n * every handler / interceptor context and applied automatically on any\n * uncaught error. A per-API `errorHandler` (on the config) overrides it.\n */\n errorHandler?: ErrorHandler;\n\n /**\n * Structured {@link Logger} shared across every API — injected into every\n * handler / interceptor / error-handler context. A per-API `logger` (on the\n * config) overrides it.\n */\n logger?: Logger;\n}\n\nexport interface ApiRegistry<\n TMap extends ApiConfigMap,\n TServices extends AnyServicesContainer = AnyServicesContainer,\n> {\n /** The registered configs (read-only). */\n readonly configs: TMap;\n\n /**\n * Typed `defineRoute` — the `api` field is constrained to `keyof TMap`,\n * and `handler({ services })` is typed with the concrete services\n * container passed to {@link createApiRegistry}.\n */\n defineRoute<\n TIn extends z.ZodTypeAny | undefined = undefined,\n TOut extends z.ZodTypeAny | undefined = undefined,\n >(\n def: Omit<RouteDef<TIn, TOut>, \"api\" | \"handler\"> & {\n api: keyof TMap & string;\n handler: RouteHandler<\n TIn extends z.ZodTypeAny ? z.infer<TIn> : void,\n TOut extends z.ZodTypeAny ? z.infer<TOut> : unknown,\n Env,\n TServices\n >;\n },\n ): RouteDef<TIn, TOut> & { api: keyof TMap & string };\n\n /**\n * Typed `useCaseRoute` — wires a {@link UseCase} class into a route in one\n * line. The route's `input` / `output` schemas are read from the useCase's\n * `static` members and the handler instantiates it with the request\n * `services`. The `api` field is constrained to `keyof TMap`.\n *\n * @example\n * export default defineRoutes([\n * useCaseRoute(CreatePostUseCase, { api: \"v1\", method: \"post\", tags: [\"posts\"] }),\n * ]);\n */\n useCaseRoute<TIn extends z.ZodTypeAny, TOut extends z.ZodTypeAny>(\n useCaseClass: UseCaseClass<TIn, TOut, TServices>,\n meta: UseCaseRouteMeta<keyof TMap & string>,\n ): RouteDef<TIn, TOut> & { api: keyof TMap & string };\n\n /**\n * Build one Cloud Function per registered API and return them as a map\n * keyed by API tag — spread it directly into your `index.ts` exports.\n *\n * @param routes Pre-resolved route registry (typically the codegen output).\n * @param onRequest The `onRequest` factory imported from\n * `firebase-functions/v2/https`.\n * @param opts Optional defaults and per-API overrides for `httpsOptions`.\n */\n toFunctions(\n routes: AnyRouteDef[],\n onRequest: OnRequestFn,\n opts?: {\n /** Shared `HttpsOptions` applied to every generated function. */\n defaults?: HttpsOptions;\n /** Per-API overrides — merged on top of {@link defaults}. */\n per?: Partial<Record<keyof TMap & string, HttpsOptions>>;\n },\n ): { [K in keyof TMap & string]: ReturnType<OnRequestFn> };\n\n /** Build the underlying {@link HonoServer} for a given API (escape hatch). */\n serverFor<K extends keyof TMap & string>(\n api: K,\n routes: AnyRouteDef[],\n ): HonoServer;\n}\n\n/**\n * Factory — declare every API tag once and get back a typed `defineRoute`\n * + `toFunctions`. See the file-level example.\n *\n * Each per-API config is strictly checked against {@link ApiConfig}: unknown\n * keys (typos like `openApi` / `middleware`) are rejected at compile time,\n * including nested objects such as `openapi` — mirroring the CRUD server.\n *\n * @param configs API-tag → per-API config (see {@link ApiConfig}).\n * @param options Cross-API options (shared services container, etc).\n */\nexport function createApiRegistry<\n const TMap extends ApiConfigMap,\n TServices extends AnyServicesContainer = AnyServicesContainer,\n>(\n configs: { [K in keyof TMap]: StrictApiConfig<TMap[K]> },\n options?: ApiRegistryOptions<TServices>,\n): ApiRegistry<TMap, TServices> {\n const sharedServices = options?.services;\n const sharedErrorHandler = options?.errorHandler;\n const sharedLogger = options?.logger;\n\n return {\n configs: configs as unknown as TMap,\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n defineRoute(def: any) {\n return def;\n },\n\n useCaseRoute(useCaseClass, meta) {\n return buildUseCaseRoute(useCaseClass, meta);\n },\n\n serverFor(api, routes) {\n const cfg = configs[api];\n if (!cfg) {\n throw new Error(\n `[ApiRegistry] unknown api \"${api}\". Registered: ${Object.keys(configs).join(\", \")}`,\n );\n }\n return new HonoServer({\n ...cfg,\n api,\n routes,\n services: sharedServices,\n // Per-API errorHandler / logger (on the config) win over the shared ones.\n errorHandler: (cfg as ApiConfig).errorHandler ?? sharedErrorHandler,\n logger: (cfg as ApiConfig).logger ?? sharedLogger,\n });\n },\n\n toFunctions(routes, onRequest, opts) {\n const out = {} as { [K in keyof TMap & string]: ReturnType<OnRequestFn> };\n for (const api of Object.keys(configs) as Array<keyof TMap & string>) {\n const httpsOpts = {\n ...(opts?.defaults ?? {}),\n ...(opts?.per?.[api] ?? {}),\n };\n const server = new HonoServer({\n ...configs[api],\n api,\n routes,\n services: sharedServices,\n errorHandler:\n (configs[api] as ApiConfig).errorHandler ?? sharedErrorHandler,\n logger: (configs[api] as ApiConfig).logger ?? sharedLogger,\n });\n out[api] = Object.keys(httpsOpts).length\n ? server.toFunction(onRequest, httpsOpts)\n : server.toFunction(onRequest);\n }\n return out;\n },\n };\n}\n","/**\n * Build a deep link to the GCP Cloud Logging \"Logs Explorer\", pre-filtered on a\n * correlation id, so a developer can jump straight from an HTTP error response\n * to the matching structured log.\n *\n * Designed as a dev ergonomic: keep it **disabled in production** (the link is\n * meant for engineers, not end users) and enable it locally / in staging. The\n * `errorId` returned by {@link BaseLogger.error} and carried by your `AppError`\n * is the same value used both in the response body and in the link's query.\n *\n * @example\n * ```ts\n * // package-level helper\n * const url = gcpLogsUrl(error.errorId, {\n * enabled: process.env.NODE_ENV !== \"production\",\n * projectId: \"my-gcp-project\", // or omit to read it from the environment\n * });\n *\n * // inside BaseErrorHandler.mapError\n * return c.json({ error: msg, errorId, ...(url ? { logsUrl: url } : {}) }, 412);\n * ```\n */\n\n/** Options controlling {@link gcpLogsUrl}. */\nexport interface GcpLogsLinkOptions {\n /**\n * Master switch — when falsy, {@link gcpLogsUrl} returns `undefined` and no\n * link is produced. Default: `false` (opt-in). Wire it to e.g.\n * `process.env.NODE_ENV !== \"production\"`.\n */\n enabled?: boolean;\n /**\n * GCP project id. Defaults to the first non-empty of\n * `GOOGLE_CLOUD_PROJECT`, `GCLOUD_PROJECT`, `GCP_PROJECT` (all auto-set on\n * Cloud Functions / Cloud Run). When none is resolvable, no link is built.\n */\n projectId?: string;\n /**\n * Structured-log field that carries the correlation id. Must match what your\n * logger writes (the package's {@link BaseLogger} writes `errorId`).\n * Default: `\"errorId\"`.\n */\n field?: string;\n /**\n * Optional lookback window appended to the query as an ISO-8601 duration\n * (e.g. `\"PT1H\"`, `\"PT30M\"`, `\"P1D\"`). Omit to let the Logs Explorer use its\n * default range.\n */\n duration?: string;\n}\n\n/**\n * Resolve the GCP project id from an explicit value, falling back to the\n * standard environment variables. Returns `undefined` when none is set.\n */\nexport function resolveGcpProjectId(explicit?: string): string | undefined {\n return (\n explicit ||\n process.env[\"GOOGLE_CLOUD_PROJECT\"] ||\n process.env[\"GCLOUD_PROJECT\"] ||\n process.env[\"GCP_PROJECT\"] ||\n undefined\n );\n}\n\n/**\n * Build the Logs Explorer URL filtered on `<field>=\"<errorId>\"`, or return\n * `undefined` when the feature is disabled, the `errorId` is missing, or no\n * project id can be resolved (so callers can spread it safely).\n */\nexport function gcpLogsUrl(\n errorId: string | undefined,\n options: GcpLogsLinkOptions = {},\n): string | undefined {\n if (!options.enabled || !errorId) return undefined;\n\n const projectId = resolveGcpProjectId(options.projectId);\n if (!projectId) return undefined;\n\n const field = options.field ?? \"errorId\";\n const query = `jsonPayload.${field}=\"${errorId}\"`;\n\n const params = [`query=${encodeURIComponent(query)}`];\n if (options.duration) {\n params.push(`duration=${encodeURIComponent(options.duration)}`);\n }\n\n return `https://console.cloud.google.com/logs/query;${params.join(\n \";\",\n )}?project=${encodeURIComponent(projectId)}`;\n}\n","/**\n * `BaseErrorHandler` — the package's ready-to-use {@link ErrorHandler}.\n *\n * Use it as-is for an API that only needs the built-in error mapping\n * (`ValidationError` / `BadRequestError` / `OutputValidationError`), or extend\n * it and override the two hooks to plug your own domain errors + logger:\n *\n * - {@link BaseErrorHandler.mapError} — map your `AppError` → `Response`\n * (return `null` to defer to the built-in mapping);\n * - {@link BaseErrorHandler.logError} — log via your `AppLogger`.\n *\n * Pass an instance **per API** (`ApiConfig.errorHandler`) so different APIs can\n * use different strategies (e.g. one with user-facing localized errors, one\n * with just the defaults).\n *\n * @example\n * ```ts\n * class AppErrorHandler extends BaseErrorHandler {\n * protected mapError({ error, c }) {\n * if (error instanceof AppError) {\n * return c.json({ error: error.message, errorId: error.errorId }, error.statusCode);\n * }\n * return null; // → built-in mapping\n * }\n * protected logError({ error }) {\n * AppLogger.err(error);\n * }\n * }\n *\n * // apis.ts\n * v1: { ..., errorHandler: new AppErrorHandler() }, // user-facing API\n * v2: { ..., errorHandler: new BaseErrorHandler() }, // defaults only\n * ```\n */\n\nimport type { Env } from \"hono\";\nimport { defaultErrorResponse } from \"./errors\";\nimport {\n gcpLogsUrl,\n type GcpLogsLinkOptions,\n} from \"./gcp-logs\";\nimport type { AnyServicesContainer } from \"./services\";\nimport type { ErrorHandler, ErrorHandlerContext } from \"./types\";\n\n/** Construction options shared by every {@link BaseErrorHandler}. */\nexport interface BaseErrorHandlerOptions {\n /**\n * Enable building a GCP Logs Explorer deep link from an error's correlation\n * id (see {@link BaseErrorHandler.gcpLogsUrl}). Disabled by default — turn it\n * on in dev/staging to let engineers jump from a response to its log.\n */\n gcpLogs?: GcpLogsLinkOptions;\n}\n\nexport class BaseErrorHandler<\n TEnv extends Env = Env,\n TServices extends AnyServicesContainer = AnyServicesContainer,\n> implements ErrorHandler<TEnv, TServices>\n{\n constructor(protected readonly options: BaseErrorHandlerOptions = {}) {}\n\n /**\n * Build a GCP Logs Explorer link for `errorId`, or `undefined` when the\n * `gcpLogs` option is disabled / unresolved. Spread it into a mapped\n * response to give developers a one-click jump to the matching log:\n *\n * ```ts\n * const logsUrl = this.gcpLogsUrl(error.errorId);\n * return c.json({ error, errorId, ...(logsUrl ? { logsUrl } : {}) }, status);\n * ```\n */\n protected gcpLogsUrl(errorId?: string): string | undefined {\n return gcpLogsUrl(errorId, this.options.gcpLogs);\n }\n\n /**\n * Orchestration — not meant to be overridden. Tries the user mapping first,\n * logs it when matched, then falls back to the built-in mapping.\n */\n async handle(\n ctx: ErrorHandlerContext<TEnv, TServices>,\n ): Promise<Response | null> {\n const mapped = await this.mapError(ctx);\n if (mapped) {\n this.logError(ctx, mapped);\n return mapped;\n }\n return this.handleBuiltin(ctx);\n }\n\n /**\n * Map a domain error (your `AppError`) to a `Response`. Return `null` to let\n * {@link BaseErrorHandler.handleBuiltin} handle it. Default: `null`.\n */\n protected mapError(\n _ctx: ErrorHandlerContext<TEnv, TServices>,\n ): Response | null | Promise<Response | null> {\n return null;\n }\n\n /**\n * Log a mapped error (e.g. via your `AppLogger`). Called only when\n * {@link BaseErrorHandler.mapError} produced a response. Default: no-op.\n */\n protected logError(\n _ctx: ErrorHandlerContext<TEnv, TServices>,\n _response: Response,\n ): void {}\n\n /**\n * Built-in mapping of the package's own errors (`ValidationError`,\n * `BadRequestError`, `OutputValidationError`). Returns `null` for unknown\n * errors so they bubble to `onError` / Hono.\n */\n protected handleBuiltin(\n ctx: ErrorHandlerContext<TEnv, TServices>,\n ): Response | null {\n return defaultErrorResponse(ctx.c, ctx.error);\n }\n}\n","/**\n * `BaseLogger` — the package's ready-to-use {@link Logger}.\n *\n * Use it as-is (writes structured JSON to `console`), or extend it and override\n * the single {@link BaseLogger.write} hook to route to your sink (Firebase\n * `logger`, pino, Datadog, …). Each level funnels through `write`, so one\n * override covers them all.\n *\n * Pass an instance **per API** (`ApiConfig.logger`) or once via the registry\n * (`createApiRegistry({ services, logger })`); it is then injected into every\n * handler / interceptor / error-handler context as `logger`.\n *\n * @example\n * ```ts\n * import { logger as fnLogger } from \"firebase-functions/v2\";\n * class AppLogger extends BaseLogger {\n * protected write(severity, payload) {\n * fnLogger.write({ severity, ...payload });\n * }\n * }\n * ```\n */\n\nimport type { Logger } from \"./types\";\n\nexport type LogSeverity = \"DEBUG\" | \"INFO\" | \"WARNING\" | \"ERROR\";\n\nexport class BaseLogger implements Logger {\n info(message: string, meta?: unknown): void {\n this.write(\"INFO\", this.payload(message, meta));\n }\n\n warn(message: string, meta?: unknown): void {\n this.write(\"WARNING\", this.payload(message, meta));\n }\n\n debug(message: string, meta?: unknown): void {\n this.write(\"DEBUG\", this.payload(message, meta));\n }\n\n /**\n * Log an error and return a correlation id. If the error already carries an\n * `errorId` it is reused, otherwise a fresh one is generated.\n */\n error(error: unknown, meta?: unknown): string {\n const errorId = BaseLogger.errorId(error);\n this.write(\"ERROR\", {\n errorId,\n message: error instanceof Error ? error.message : String(error),\n ...(error instanceof Error && error.stack ? { stack: error.stack } : {}),\n ...(meta !== undefined ? { meta } : {}),\n });\n return errorId;\n }\n\n /** Build a structured payload from a message + optional metadata. */\n protected payload(message: string, meta?: unknown): Record<string, unknown> {\n return meta !== undefined ? { message, meta } : { message };\n }\n\n /**\n * Sink hook — override to route logs elsewhere. Default: structured\n * `console` write keyed by severity.\n */\n protected write(severity: LogSeverity, payload: Record<string, unknown>): void {\n const line = { severity, ...payload };\n // eslint-disable-next-line no-console\n if (severity === \"ERROR\") console.error(line);\n // eslint-disable-next-line no-console\n else if (severity === \"WARNING\") console.warn(line);\n // eslint-disable-next-line no-console\n else console.log(line);\n }\n\n /** Reuse an error's `errorId` when present, else generate one. */\n protected static errorId(error: unknown): string {\n if (\n error &&\n typeof error === \"object\" &&\n \"errorId\" in error &&\n typeof (error as { errorId: unknown }).errorId === \"string\"\n ) {\n return (error as { errorId: string }).errorId;\n }\n return Math.random().toString(36).slice(2, 12);\n }\n}\n","/**\n * URL path inference from filesystem layout.\n *\n * Convention: every `routes.ts` file under the configured root contributes\n * one route. The URL path is derived from its directory chain, optionally\n * skipping segments such as `useCases` so that\n *\n * domains/activities/useCases/createOrUpdateCustom/routes.ts\n *\n * becomes\n *\n * /activities/createOrUpdateCustom\n */\n\nexport interface PathDeriveOptions {\n /** Segments to drop from the derived path (case-insensitive). */\n skipSegments: string[];\n /**\n * Casing convention applied to each remaining segment.\n * - `\"preserve\"` — keep the directory name as-is (default),\n * - `\"kebab\"` — convert camelCase / PascalCase to kebab-case.\n */\n casing: \"preserve\" | \"kebab\";\n}\n\nexport const DEFAULT_DERIVE: PathDeriveOptions = {\n skipSegments: [\"useCases\", \"useCase\", \"use-cases\", \"use-case\"],\n casing: \"preserve\",\n};\n\n/**\n * @param relativeDir POSIX-style directory path of the routes file relative\n * to the codegen root (no leading slash, no `routes.ts`).\n */\nexport function derivePath(\n relativeDir: string,\n options: PathDeriveOptions = DEFAULT_DERIVE,\n): string {\n const skip = new Set(options.skipSegments.map((s) => s.toLowerCase()));\n const parts = relativeDir\n .split(\"/\")\n .filter(Boolean)\n .filter((p) => !skip.has(p.toLowerCase()))\n .map((p) => (options.casing === \"kebab\" ? kebab(p) : p));\n return \"/\" + parts.join(\"/\");\n}\n\nfunction kebab(s: string): string {\n return s\n .replace(/([a-z0-9])([A-Z])/g, \"$1-$2\")\n .replace(/[\\s_]+/g, \"-\")\n .toLowerCase();\n}\n\n/**\n * Convert an absolute filesystem path to a POSIX-style import specifier\n * relative to a directory (the generated file's directory). Always uses\n * forward slashes and prefixes with `./` or `../` as needed.\n */\nexport function toImportSpecifier(\n fromDir: string,\n toFile: string,\n ext: string,\n): string {\n // Both paths are absolute POSIX (the CLI normalises them).\n const fromParts = splitAbs(fromDir);\n const toParts = splitAbs(toFile);\n let common = 0;\n while (\n common < fromParts.length &&\n common < toParts.length &&\n fromParts[common] === toParts[common]\n ) {\n common++;\n }\n const up = fromParts.length - common;\n const down = toParts.slice(common);\n const last = down[down.length - 1] ?? \"\";\n const stripped = last.replace(/\\.[mc]?[tj]sx?$/i, \"\");\n const finalLast = ext === \"\" ? stripped : `${stripped}${ext}`;\n down[down.length - 1] = finalLast;\n const prefix = up === 0 ? \"./\" : \"../\".repeat(up);\n return prefix + down.join(\"/\");\n}\n\nfunction splitAbs(p: string): string[] {\n const norm = p.replace(/\\\\/g, \"/\").replace(/\\/+$/, \"\");\n return norm.split(\"/\").filter((part, i) => !(i === 0 && part === \"\"));\n}\n","/**\n * Filesystem scanner — walks the configured root and yields every route file.\n * Synchronous and dependency-free (no `fast-glob` etc.) for a tiny CLI footprint.\n */\n\nimport { readdirSync, statSync } from \"node:fs\";\nimport { join, relative, sep } from \"node:path\";\n\nexport interface ScannerOptions {\n /** Filename to look for (default: `routes.ts`). */\n routesFile: string;\n /** Glob-like exclude segments (matched against any path part). */\n excludeSegments: string[];\n}\n\nexport const DEFAULT_SCANNER: ScannerOptions = {\n routesFile: \"routes.ts\",\n excludeSegments: [\n \"node_modules\",\n \"__generated__\",\n \"tests\",\n \"__tests__\",\n \".turbo\",\n \"dist\",\n \"build\",\n \".next\",\n ],\n};\n\nexport interface ScannedRoute {\n /** Absolute path to the routes file. */\n absPath: string;\n /** Path relative to the scan root (POSIX style). */\n relPath: string;\n /** Directory portion of `relPath` (what {@link derivePath} consumes). */\n relDir: string;\n}\n\nexport function scanRoutes(\n rootAbs: string,\n options: ScannerOptions = DEFAULT_SCANNER,\n): ScannedRoute[] {\n const found: ScannedRoute[] = [];\n walk(rootAbs, rootAbs, options, found);\n // Stable, deterministic order — important for reproducible builds.\n found.sort((a, b) => a.relPath.localeCompare(b.relPath));\n return found;\n}\n\nfunction walk(\n root: string,\n dir: string,\n opts: ScannerOptions,\n out: ScannedRoute[],\n): void {\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return;\n }\n for (const name of entries) {\n if (opts.excludeSegments.includes(name)) continue;\n const abs = join(dir, name);\n let st;\n try {\n st = statSync(abs);\n } catch {\n continue;\n }\n if (st.isDirectory()) {\n walk(root, abs, opts, out);\n } else if (st.isFile() && name === opts.routesFile) {\n const relPath = relative(root, abs).split(sep).join(\"/\");\n const relDir = relPath.replace(/\\/?[^/]+$/, \"\");\n out.push({ absPath: abs, relPath, relDir });\n }\n }\n}\n","/**\n * Generator — emits `__generated__/routes.ts` from a list of {@link ScannedRoute}s.\n *\n * The generated module statically imports every `routes.ts` so that bundlers\n * (esbuild/tsup) can tree-shake unused dependencies and Cloud Functions get\n * the smallest possible cold-start footprint.\n */\n\nimport { dirname, join } from \"node:path\";\nimport { mkdirSync, writeFileSync } from \"node:fs\";\n\nimport {\n derivePath,\n toImportSpecifier,\n type PathDeriveOptions,\n} from \"./path-utils\";\nimport type { ScannedRoute } from \"./scanner\";\n\nexport interface GeneratorOptions {\n /** Absolute path of the file to write (e.g. `…/__generated__/routes.ts`). */\n outFile: string;\n /** Path-derivation options (skipSegments, casing). */\n derive: PathDeriveOptions;\n /**\n * Import extension used in the generated file:\n * - `\".js\"` (default) — required for ESM Node.js with `\"type\": \"module\"`,\n * - `\"\"` — bundler-friendly (Vite, no extension),\n * - `\".ts\"` — for TS-ESM runners (`tsx`, `bun`).\n */\n importExtension: string;\n /** Friendly banner placed at the top of the generated file. */\n banner?: string;\n /** Generation timestamp included in the banner. Default: `new Date()`. */\n now?: Date;\n}\n\nexport const DEFAULT_GENERATOR_BANNER =\n \"/**\\n\" +\n \" * AUTO-GENERATED by `@lpdjs/firestore-repo-service` Hono codegen.\\n\" +\n \" * Do not edit by hand — re-run `hono:gen` after adding / removing route files.\\n\" +\n \" */\\n\";\n\nexport interface GenerationResult {\n /** Absolute path of the file written. */\n outFile: string;\n /** Number of routes captured in the manifest. */\n routeCount: number;\n /** Human-readable summary of the derived URLs. */\n derivedPaths: { source: string; url: string }[];\n}\n\nexport function generateRoutesManifest(\n routes: ScannedRoute[],\n opts: GeneratorOptions,\n): GenerationResult {\n const outDir = dirname(opts.outFile);\n mkdirSync(outDir, { recursive: true });\n\n const banner = opts.banner ?? DEFAULT_GENERATOR_BANNER;\n const now = (opts.now ?? new Date()).toISOString();\n const ext = opts.importExtension;\n\n const importLines: string[] = [];\n const entryLines: string[] = [];\n const derivedPaths: GenerationResult[\"derivedPaths\"] = [];\n\n routes.forEach((r, i) => {\n const importPath = toImportSpecifier(outDir, r.absPath, ext);\n const url = derivePath(r.relDir, opts.derive);\n importLines.push(\n `import mod${i} from ${JSON.stringify(importPath)};`,\n );\n entryLines.push(` { __derivedPath: ${JSON.stringify(url)}, mod: mod${i} },`);\n derivedPaths.push({ source: r.relPath, url });\n });\n\n const body =\n `${banner}` +\n `// Generated at ${now} — ${routes.length} route file${routes.length === 1 ? \"\" : \"s\"}.\\n` +\n `\\n` +\n `import type { AnyRouteDef, RouteModuleDefault } from \"@lpdjs/firestore-repo-service/servers/hono\";\\n` +\n `\\n` +\n importLines.join(\"\\n\") +\n (importLines.length ? \"\\n\\n\" : \"\\n\") +\n `const __defs: { __derivedPath: string; mod: RouteModuleDefault }[] = [\\n` +\n entryLines.join(\"\\n\") +\n (entryLines.length ? \"\\n\" : \"\") +\n `];\\n\\n` +\n `export const routes: AnyRouteDef[] = __defs.flatMap(({ __derivedPath, mod }) => {\\n` +\n ` const list = Array.isArray(mod) ? mod : [mod];\\n` +\n ` return list.map((route) => ({ ...route, path: route.path ?? __derivedPath }));\\n` +\n `});\\n`;\n\n writeFileSync(opts.outFile, body, \"utf8\");\n return {\n outFile: opts.outFile,\n routeCount: routes.length,\n derivedPaths,\n };\n}\n\n/** Convenience helper used by the CLI — combines scan + generate in one call. */\nexport function generateFromRoot(\n rootAbs: string,\n outFileRel: string,\n derive: PathDeriveOptions,\n importExtension: string,\n scan: (root: string) => ScannedRoute[],\n): GenerationResult {\n const routes = scan(rootAbs);\n const outFile = join(rootAbs, outFileRel);\n return generateRoutesManifest(routes, {\n outFile,\n derive,\n importExtension,\n });\n}\n"]}
1
+ {"version":3,"sources":["../../../src/servers/hono/types.ts","../../../src/servers/hono/errors.ts","../../../src/servers/hono/openapi.ts","../../../src/servers/auth/login-page.tsx","../../../src/servers/auth/session.ts","../../../src/servers/hono/docs-auth.ts","../../../src/servers/hono/services.ts","../../../src/servers/hono/server.ts","../../../src/servers/hono/usecase.ts","../../../src/servers/hono/api-registry.ts","../../../src/servers/hono/gcp-logs.ts","../../../src/servers/hono/error-handler.ts","../../../src/servers/hono/logger.ts","../../../src/servers/hono/codegen/path-utils.ts","../../../src/servers/hono/codegen/scanner.ts","../../../src/servers/hono/codegen/generator.ts"],"names":["defineRoutes","routes","ValidationError","zodError","source","BadRequestError","message","OutputValidationError","formatZodIssues","error","i","defaultErrorResponse","c","err","extendZodWithOpenApi","z","DEFAULT_RESPONSE_DESCRIPTION","DEFAULT_ERROR_DESCRIPTION","defaultSource","method","resolveSuccessSchema","routeOutput","interceptor","out","normalizeErrorResponse","entry","cfg","buildOpenApiDocument","basePath","config","registry","OpenAPIRegistry","name","scheme","route","fullPath","joinPath","status","requestBody","buildRequestBody","requestQuery","buildQueryOrParam","requestParams","operationId","makeOperationId","successSchema","responses","code","description","schema","convertExpressPathToOpenApi","OpenApiGeneratorV31","target","path","base","left","right","merged","cleaned","renderDocsHtml","specUrl","title","safeUrl","emulatorUrl","host","htmlEscape","value","jsonEscape","renderLoginPage","opts","showPassword","showGoogle","initialError","parseCookies","header","part","eq","key","firebaseBearerAuth","options","getAuth","allow","checkRevoked","contextKey","next","match","decoded","permitted","basicAuth","username","password","realm","expected","timingSafeEqual","a","b","diff","isDocsAuthExtension","LOGIN_NAME","SESSION_NAME","LOGOUT_NAME","lastSegment","segs","buildSetCookie","segments","sanitizeNext","raw","fallback","firebaseDocsAuth","apiKey","authDomain","mode","providers","cookieName","sessionTtlDays","secureCookie","sameSite","onUnauthenticated","authEmulatorHost","passesAllow","token","auth","session","accept","isBrowserGet","html","idToken","body","expiresInMs","authTimeRaw","authTime","sessionCookie","cookie","als","AsyncLocalStorage","requestContextSingleton","store","createRequestContextMiddleware","withRequestContext","ctx","fn","CTX_KEY","createServices","cache","inProgress","proxy","_target","prop","provider","isClassConstructor","_t","EMPTY_SERVICES","HonoServer","Hono","filterRoutes","globalMws","mw","getRequestListener","onRequest","httpsOptions","handler","interceptorConfig","validateOutput","verbose","middlewares","makeRouteHandler","interceptorFn","httpMethod","specPath","docsPath","fullSpecPath","fullDocsPath","guards","ext","authDir","dirOf","relativeSpecUrl","relativeUrlFromTo","isInterceptorConfig","api","r","idx","from","to","fromSegs","toSegs","common","ups","services","errorHandler","logger","inputSchema","outputSchema","servicesArg","applyErrorHandler","handled","callNext","payload","readPayload","parsed","result","checked","text","UseCase","useCaseRoute","useCaseClass","meta","input","createApiRegistry","configs","sharedServices","sharedErrorHandler","sharedLogger","def","httpsOpts","server","resolveGcpProjectId","explicit","gcpLogsUrl","errorId","projectId","query","params","BaseErrorHandler","mapped","_ctx","_response","BaseLogger","_BaseLogger","severity","line","DEFAULT_DERIVE","derivePath","relativeDir","skip","p","kebab","s","toImportSpecifier","fromDir","toFile","fromParts","splitAbs","toParts","up","down","stripped","finalLast","DEFAULT_SCANNER","scanRoutes","rootAbs","found","walk","root","dir","entries","readdirSync","abs","join","st","statSync","relPath","relative","sep","relDir","DEFAULT_GENERATOR_BANNER","generateRoutesManifest","outDir","dirname","mkdirSync","banner","now","importLines","entryLines","derivedPaths","importPath","url","writeFileSync","generateFromRoot","outFileRel","derive","importExtension","scan","outFile"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Public types for the Hono file-based API server.\n *\n * Designed to be:\n * - **fully typed** end-to-end (Zod input/output → handler payload type),\n * - **runtime-safe** via Zod parsing on every request,\n * - **bundle-friendly** for Firebase Cloud Functions v2 cold-start (codegen\n * emits static imports — no `fs`/`import()` at runtime).\n */\n\nimport type { z, ZodError } from \"zod\";\nimport type { Context, Env, MiddlewareHandler } from \"hono\";\nimport type { AnyServicesContainer } from \"./services\";\nimport type { DocsAuthExtension } from \"./docs-auth\";\n\nexport type HttpMethod = \"get\" | \"post\" | \"put\" | \"patch\" | \"delete\";\n\n/** Where the validated payload comes from. */\nexport type PayloadSource = \"json\" | \"query\" | \"form\" | \"param\";\n\n/**\n * Structured logger injected into every handler / interceptor / error-handler\n * context. Implement it, or extend the package's `BaseLogger` and override its\n * `write` hook to route to your sink (Firebase logger, pino, …).\n *\n * `error()` returns a correlation id so the same value can be both logged and\n * returned to the client.\n */\nexport interface Logger {\n info(message: string, meta?: unknown): void;\n warn(message: string, meta?: unknown): void;\n /** @returns a correlation id (e.g. to include in the HTTP error response). */\n error(error: unknown, meta?: unknown): string;\n debug(message: string, meta?: unknown): void;\n}\n\n/** Context passed to {@link ErrorHandler.handle}. */\nexport interface ErrorHandlerContext<\n TEnv extends Env = Env,\n TServices extends AnyServicesContainer = AnyServicesContainer,\n> {\n /** The thrown value (an `AppError`, a Zod {@link ValidationError}, …). */\n error: unknown;\n /** Hono request context — set status, read headers, build the response. */\n c: Context<TEnv>;\n /** Route metadata (read-only). */\n route: AnyRouteDef;\n /** Global DI services container. */\n services: TServices;\n /** Injected {@link Logger} when one was passed to the API, else `undefined`. */\n logger?: Logger;\n}\n\n/**\n * Cross-cutting error strategy — a class (or object) you pass to the API and\n * that the server injects everywhere **and** applies automatically.\n *\n * Implement {@link ErrorHandler.handle} to map any thrown value to an HTTP\n * `Response` (e.g. your `AppError` → status + localized body, plus structured\n * logging with a correlation id). Return `null` to decline the error and let\n * the built-in fallback (`ValidationError` envelope) / `onError` take over.\n *\n * Prefer extending the package's {@link BaseErrorHandler} (it already maps the\n * built-in errors) and overriding its `mapError` / `logError` hooks. Pass it\n * **per API** via `ApiConfig.errorHandler`, or once via the registry\n * (`{ services, errorHandler }`); it is then available in every handler /\n * interceptor context as `errorHandler` and auto-applied on any uncaught error.\n */\nexport interface ErrorHandler<\n TEnv extends Env = Env,\n TServices extends AnyServicesContainer = AnyServicesContainer,\n> {\n handle(\n ctx: ErrorHandlerContext<TEnv, TServices>,\n ): Response | null | Promise<Response | null>;\n}\n\n/** Handler signature — receives a single typed context object. */\nexport type RouteHandler<\n TIn,\n TOut,\n TEnv extends Env = Env,\n TServices extends AnyServicesContainer = AnyServicesContainer,\n> = (ctx: {\n /** Validated (and typed) request payload. `void` when no `input` schema is defined. */\n input: TIn;\n /** Raw Hono `Context` for headers, set status, redirect, etc. */\n c: Context<TEnv>;\n /**\n * Global DI services container — same instance shared by every handler.\n * Empty `ServicesContainer` when the server was started without a\n * `services` option.\n */\n services: TServices;\n /**\n * Injected {@link ErrorHandler} when one was passed to the API, else\n * `undefined`. Usually you just `throw` and let it apply automatically.\n */\n errorHandler?: ErrorHandler<TEnv, TServices>;\n /** Injected {@link Logger} when one was passed to the API, else `undefined`. */\n logger?: Logger;\n}) => Promise<TOut | Response> | TOut | Response;\n\n/**\n * One route declaration. Default-exported by every `routes.ts` file inside the\n * domain tree. Use {@link defineRoute} for full type inference.\n */\nexport interface RouteDef<\n TIn extends z.ZodTypeAny | undefined = z.ZodTypeAny | undefined,\n TOut extends z.ZodTypeAny | undefined = z.ZodTypeAny | undefined,\n TEnv extends Env = Env,\n> {\n /**\n * Logical API tag — routes sharing the same `api` are mounted on the same\n * `HonoServer` (typically one Cloud Function per `api`).\n *\n * To expose the **same logic** under several APIs with different\n * inputs/outputs, export multiple `defineRoute({...})` from the same file\n * (default + named exports are both picked up by the codegen).\n */\n api: string;\n\n /** HTTP method. */\n method: HttpMethod;\n\n /**\n * URL path appended to the server `basePath`. If omitted, the codegen will\n * derive it from the file location (e.g. `domains/activities/useCases/createCustom/routes.ts`\n * → `/activities/createCustom`).\n */\n path?: string;\n\n /**\n * Where the request payload comes from.\n * Default: `\"json\"` for body methods (POST/PUT/PATCH/DELETE), `\"query\"` for GET.\n */\n source?: PayloadSource;\n\n /** Zod schema validating the payload. Failures yield a 400 response. */\n input?: TIn;\n\n /**\n * Zod schema for the success response. Used to populate the OpenAPI spec\n * and (when `validateOutput` is enabled on the server) to assert the\n * runtime payload returned by the handler.\n */\n output?: TOut;\n\n /** Status code for the success response. Default: 200. */\n status?: number;\n\n /** Hono middlewares applied to this route only (after global middlewares). */\n middlewares?: MiddlewareHandler<TEnv>[];\n\n // ── OpenAPI metadata ─────────────────────────────────────────────────\n summary?: string;\n description?: string;\n tags?: string[];\n /** Mark the operation as deprecated in the generated spec. */\n deprecated?: boolean;\n /** Security requirements (operationId-level override). */\n security?: SecurityRequirement[];\n\n /** The request handler. */\n handler: RouteHandler<\n TIn extends z.ZodTypeAny ? z.infer<TIn> : void,\n TOut extends z.ZodTypeAny ? z.infer<TOut> : unknown,\n TEnv\n >;\n}\n\n/** Erased `RouteDef` used by registry/codegen — handler signature is opaque. */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyRouteDef = RouteDef<any, any>;\n\n/**\n * What `routes.ts` can default-export:\n * - a single {@link RouteDef} (most common case),\n * - or an array of {@link RouteDef} (e.g. expose the same useCase on\n * multiple `api` tags or under multiple paths). `readonly` arrays are\n * accepted so that tuple-preserving helpers like {@link defineRoutes} (or a\n * plain `as const` array) can be default-exported directly.\n */\nexport type RouteModuleDefault = AnyRouteDef | readonly AnyRouteDef[];\n\n// ── useCase ⇆ routes type bridge ──────────────────────────────────────────\n// A useCase should never re-declare its input/output by hand. Instead it\n// derives them from the Zod schemas of the route(s) that drive it, so the two\n// can never drift apart. Pass `typeof import(\"./routes.js\").default` to the\n// helpers below.\n\n/**\n * Identity helper that **preserves the tuple type** of a `routes.ts` default\n * export. A plain array literal (`export default [a, b]`) collapses every\n * element to a single common type, which would lose each route's individual\n * schema. Wrapping the array with `defineRoutes([...])` keeps the per-route\n * types intact so {@link RouteInput} / {@link RouteOutput} can aggregate them\n * into a union.\n *\n * @example\n * export default defineRoutes([\n * defineRoute({ ... }),\n * defineRoute({ ... }),\n * ]);\n */\nexport function defineRoutes<const T extends readonly AnyRouteDef[]>(\n routes: T,\n): T {\n return routes;\n}\n\n/**\n * Normalizes a `routes.ts` default export — a single {@link RouteDef} or an\n * array of them — to a *union* of its individual route members.\n */\nexport type RoutesUnion<T> = T extends readonly (infer R)[] ? R : T;\n\ntype InferRouteInput<R> = R extends { input?: infer I }\n ? I extends z.ZodTypeAny\n ? z.infer<I>\n : void\n : void;\n\ntype InferRouteOutput<R> = R extends { output?: infer O }\n ? O extends z.ZodTypeAny\n ? z.infer<O>\n : unknown\n : unknown;\n\n/**\n * Union of validated request payloads across every route in a `routes.ts`\n * module. Use it to type a useCase input without duplicating the Zod schema:\n *\n * @example\n * type Routes = typeof import(\"./routes.js\").default;\n * export type CreatePostUseCaseInput = RouteInput<Routes>;\n */\nexport type RouteInput<T> = InferRouteInput<RoutesUnion<T>>;\n\n/**\n * Union of success-response payloads across every route in a `routes.ts`\n * module. Mirror of {@link RouteInput} for the useCase output type.\n */\nexport type RouteOutput<T> = InferRouteOutput<RoutesUnion<T>>;\n\n/**\n * Thrown by the server when the incoming request fails Zod validation.\n * Caught by the {@link RouteInterceptor} (if any) so users can shape the\n * response envelope however they like; otherwise yields a default 400.\n */\nexport class ValidationError extends Error {\n readonly statusCode = 400 as const;\n constructor(\n /** Original Zod error — `error.issues` to enumerate field-level problems. */\n readonly zodError: ZodError,\n /** Where the offending payload came from. */\n readonly source: PayloadSource,\n ) {\n super(\"Request validation failed\");\n this.name = \"ValidationError\";\n }\n}\n\n/**\n * Cross-cutting interceptor applied around every handler.\n * Use it for response envelopes, business-error → HTTP mapping, structured\n * logging, tracing spans, etc.\n *\n * Wrap `next()` in `try/catch` to intercept BOTH Zod {@link ValidationError}s\n * (thrown before the handler runs) AND business errors thrown by the handler.\n *\n * @example\n * ```ts\n * interceptor: async ({ next, route, c }) => {\n * try {\n * const data = await next();\n * return c.json({ success: true, data, error: null });\n * } catch (err) {\n * if (err instanceof ValidationError) {\n * return c.json({ success: false, error: \"validation\", issues: err.zodError.issues }, 400);\n * }\n * if (err instanceof DomainError) {\n * return c.json({ success: false, error: err.code }, err.statusCode);\n * }\n * throw err; // → falls back to onError or Hono's default 500\n * }\n * }\n * ```\n */\nexport type RouteInterceptor<\n TEnv extends Env = Env,\n TServices extends AnyServicesContainer = AnyServicesContainer,\n> = (ctx: {\n /**\n * Calls validation + handler and returns the raw value.\n * Throws {@link ValidationError} on Zod failure or any error thrown by the handler.\n */\n next: () => Promise<unknown>;\n /** Route metadata (read-only). */\n route: AnyRouteDef;\n /** Hono request context. */\n c: Context<TEnv>;\n /** Global DI services container. See {@link RouteHandler}. */\n services: TServices;\n /**\n * Injected {@link ErrorHandler} when one was passed to the API. Call\n * `errorHandler?.handle({ error, c, route, services })` in your `catch` to\n * reuse the shared mapping, or simply rethrow to let it apply automatically.\n */\n errorHandler?: ErrorHandler<TEnv, TServices>;\n /** Injected {@link Logger} when one was passed to the API, else `undefined`. */\n logger?: Logger;\n}) => Promise<Response | unknown> | Response | unknown;\n\n/**\n * Success-envelope schema declared alongside an interceptor so the generated\n * OpenAPI spec documents what the **wrapper actually returns** (not the raw\n * handler output).\n *\n * - a **static** Zod schema → same envelope for every route (e.g.\n * `z.object({ data: z.any(), intercepted: z.boolean() })`);\n * - a **factory** `(routeOutput) => schema` → wraps each route's own `output`,\n * so `data` is typed per endpoint in the docs.\n */\nexport type InterceptorOutput =\n | z.ZodTypeAny\n | ((routeOutput: z.ZodTypeAny | undefined) => z.ZodTypeAny);\n\n/** A single declared error response for the OpenAPI spec. */\nexport type InterceptorErrorResponse =\n | z.ZodTypeAny\n | { description?: string; schema?: z.ZodTypeAny };\n\n/**\n * Structured interceptor — pairs the cross-cutting {@link RouteInterceptor}\n * `handler` with the OpenAPI metadata describing what it returns, so the docs\n * reflect the real wrapped responses (success envelope + error shapes).\n *\n * @example\n * ```ts\n * interceptor: {\n * // factory: `data` reflects each route's own output schema\n * output: (routeOutput) =>\n * z.object({ data: routeOutput ?? z.unknown(), intercepted: z.boolean() }),\n * errors: {\n * 400: ValidationErrorSchema,\n * 500: { description: \"Internal\", schema: ErrorSchema },\n * },\n * handler: async ({ c, next }) => {\n * const data = await next();\n * return c.json({ data, intercepted: true });\n * },\n * }\n * ```\n */\nexport interface InterceptorConfig<\n TEnv extends Env = Env,\n TServices extends AnyServicesContainer = AnyServicesContainer,\n> {\n /** Success-envelope schema (static) or per-route factory. */\n output?: InterceptorOutput;\n /**\n * Error responses applied to **every** operation, keyed by HTTP status. Pass\n * a bare Zod schema or `{ description, schema }`.\n */\n errors?: Record<number, InterceptorErrorResponse>;\n /** The interceptor function. See {@link RouteInterceptor}. */\n handler: RouteInterceptor<TEnv, TServices>;\n}\n\n/**\n * Interceptor option accepted by the server / registry — either a bare\n * {@link RouteInterceptor} function (legacy) or a structured\n * {@link InterceptorConfig} carrying OpenAPI metadata.\n */\nexport type InterceptorOption<\n TEnv extends Env = Env,\n TServices extends AnyServicesContainer = AnyServicesContainer,\n> = RouteInterceptor<TEnv, TServices> | InterceptorConfig<TEnv, TServices>;\n\n/** OpenAPI document info (subset of the spec used by the helper). */\nexport interface OpenAPIInfo {\n title: string;\n version: string;\n description?: string;\n}\n\n// ── OpenAPI 3.1 Security Scheme Object ──────────────────────────────────────\n\n/** Fields shared by every security scheme. */\nexport interface SecuritySchemeBase {\n /** Human-readable description (CommonMark). */\n description?: string;\n}\n\n/** API key carried in a header, query param or cookie. */\nexport interface ApiKeySecurityScheme extends SecuritySchemeBase {\n type: \"apiKey\";\n /** Name of the header, query parameter or cookie. */\n name: string;\n /** Location of the API key. */\n in: \"query\" | \"header\" | \"cookie\";\n}\n\n/**\n * HTTP authentication (RFC 7235), e.g. `bearer` (Firebase ID tokens) or\n * `basic`.\n */\nexport interface HttpSecurityScheme extends SecuritySchemeBase {\n type: \"http\";\n /** Auth scheme name — `\"bearer\"`, `\"basic\"`, `\"digest\"`, … */\n // eslint-disable-next-line @typescript-eslint/ban-types\n scheme: \"bearer\" | \"basic\" | \"digest\" | (string & {});\n /** Hint for the bearer token format, e.g. `\"JWT\"` / `\"Firebase JWT\"`. */\n bearerFormat?: string;\n}\n\n/** Mutual TLS authentication. */\nexport interface MutualTlsSecurityScheme extends SecuritySchemeBase {\n type: \"mutualTLS\";\n}\n\n/** A single OAuth2 flow. */\nexport interface OAuthFlowObject {\n authorizationUrl?: string;\n tokenUrl?: string;\n refreshUrl?: string;\n /** Available scopes → description. */\n scopes: Record<string, string>;\n}\n\n/** The OAuth2 flows supported by an {@link OAuth2SecurityScheme}. */\nexport interface OAuthFlowsObject {\n implicit?: OAuthFlowObject;\n password?: OAuthFlowObject;\n clientCredentials?: OAuthFlowObject;\n authorizationCode?: OAuthFlowObject;\n}\n\n/** OAuth2 authentication. */\nexport interface OAuth2SecurityScheme extends SecuritySchemeBase {\n type: \"oauth2\";\n flows: OAuthFlowsObject;\n}\n\n/** OpenID Connect Discovery. */\nexport interface OpenIdConnectSecurityScheme extends SecuritySchemeBase {\n type: \"openIdConnect\";\n openIdConnectUrl: string;\n}\n\n/** OpenAPI 3.1 Security Scheme Object (discriminated on `type`). */\nexport type SecurityScheme =\n | ApiKeySecurityScheme\n | HttpSecurityScheme\n | MutualTlsSecurityScheme\n | OAuth2SecurityScheme\n | OpenIdConnectSecurityScheme;\n\n/**\n * A single Security Requirement Object: maps a scheme name (a key of\n * {@link OpenAPIConfig.securitySchemes}) to the list of required scopes\n * (empty `[]` for `http` / `apiKey`).\n */\nexport type SecurityRequirement = Record<string, string[]>;\n\n/** OpenAPI configuration on the server. */\nexport interface OpenAPIConfig {\n /** Path served by the JSON spec (e.g. `/openapi.json`). Default: `/openapi.json`. */\n path?: string;\n /** Path serving the documentation UI. Set to `false` to disable. Default: `/docs`. */\n docsPath?: string | false;\n /** OpenAPI document info. */\n info: OpenAPIInfo;\n /** Optional servers list for the spec. */\n servers?: { url: string; description?: string }[];\n /**\n * Reusable security schemes, keyed by name. The keys are referenced from\n * {@link OpenAPIConfig.security} / per-route `security`.\n *\n * @example\n * ```ts\n * securitySchemes: {\n * bearerAuth: { type: \"http\", scheme: \"bearer\", bearerFormat: \"Firebase JWT\" },\n * }\n * ```\n */\n securitySchemes?: Record<string, SecurityScheme>;\n /**\n * Default security requirement applied to every operation. Each entry maps a\n * scheme name (a key of {@link OpenAPIConfig.securitySchemes}) to its scopes.\n *\n * @example `security: [{ bearerAuth: [] }]`\n */\n security?: SecurityRequirement[];\n /**\n * Hono middleware(s) guarding **only** the docs UI and JSON spec endpoints\n * (not the API routes). Use a raw middleware for a custom flow, or the\n * built-in {@link firebaseBearerAuth} / {@link basicAuth} helpers.\n *\n * @example\n * ```ts\n * import { firebaseBearerAuth } from \"@lpdjs/firestore-repo-service/servers/hono\";\n * openapi: { info, docsAuth: firebaseBearerAuth({ getAuth }) }\n * ```\n *\n * For a full login form + session cookie (like the admin server), pass the\n * {@link DocsAuthExtension} returned by `firebaseDocsAuth({ ... })` instead.\n */\n docsAuth?: MiddlewareHandler | MiddlewareHandler[] | DocsAuthExtension;\n}\n\n/** Options consumed by the {@link HonoServer} constructor. */\nexport interface HonoServerOptions<TEnv extends Env = Env> {\n /**\n * API tag — only routes whose `api` matches this value are mounted.\n * If omitted, every route in the registry is mounted.\n */\n api?: string;\n\n /** Pre-resolved route registry (typically the codegen output). */\n routes: AnyRouteDef[];\n\n /** URL prefix mounted before every route path. Default: `\"\"`. */\n basePath?: string;\n\n /** Hono middlewares applied to every route (after the built-ins). */\n middlewares?: MiddlewareHandler<TEnv>[];\n\n /**\n * Alias for `middlewares` — global middlewares applied to every route.\n * If both are provided, `globalMiddlewares` is appended after `middlewares`.\n */\n globalMiddlewares?: MiddlewareHandler<TEnv>[];\n\n /**\n * If `true`, the server validates the value returned by every handler\n * against the route's `output` schema and rejects mismatches with a 500\n * response. Useful in dev / staging. Default: `false`.\n */\n validateOutput?: boolean;\n\n /** Enable verbose logging of mounted routes at startup. Default: `false`. */\n verbose?: boolean;\n\n /** OpenAPI configuration. Omit to disable. */\n openapi?: OpenAPIConfig;\n\n /** Custom 404 handler. */\n notFound?: (c: Context<TEnv>) => Response | Promise<Response>;\n\n /** Custom error handler. */\n onError?: (err: unknown, c: Context<TEnv>) => Response | Promise<Response>;\n\n /**\n * Cross-cutting interceptor wrapping every handler call.\n * Ideal for response envelopes, business-error mapping, tracing.\n *\n * Pass a bare {@link RouteInterceptor} function, or an\n * {@link InterceptorConfig} (`{ output?, errors?, handler }`) so the\n * generated OpenAPI spec documents the wrapped responses.\n */\n interceptor?: InterceptorOption<TEnv>;\n\n /**\n * Cross-cutting error strategy applied to every uncaught error and injected\n * into every handler / interceptor context. See {@link ErrorHandler}.\n *\n * Typically shared across APIs — pass it once to `createApiRegistry` via\n * `{ services, errorHandler }`; this per-API field overrides it.\n */\n errorHandler?: ErrorHandler<TEnv>;\n\n /**\n * Structured {@link Logger} injected into every handler / interceptor /\n * error-handler context. Extend the package's `BaseLogger` to route to your\n * sink. Typically shared via `createApiRegistry({ services, logger })`; this\n * per-API field overrides it.\n */\n logger?: Logger;\n\n /**\n * Global DI services container (see {@link createServices}).\n *\n * When provided:\n * - a middleware is installed automatically so the built-in `ctx`\n * service resolves to the current request via `AsyncLocalStorage`;\n * - `services` is passed into every handler and the interceptor.\n *\n * The same container instance should be shared across every API of your\n * project — declare it once and pass it to {@link createApiRegistry}.\n */\n services?: AnyServicesContainer;\n}\n","/**\n * Built-in error types thrown by the server pipeline and their default\n * HTTP mapping — shared by the request handler and {@link BaseErrorHandler}.\n */\n\nimport type { ZodError } from \"zod\";\nimport { ValidationError } from \"./types\";\n\n/** Thrown when the request body cannot be read / parsed → HTTP 400. */\nexport class BadRequestError extends Error {\n readonly statusCode = 400 as const;\n constructor(message: string) {\n super(message);\n this.name = \"BadRequestError\";\n }\n}\n\n/** Thrown when a handler's return value fails the `output` schema → HTTP 500. */\nexport class OutputValidationError extends Error {\n readonly statusCode = 500 as const;\n constructor(readonly zodError: ZodError) {\n super(\"Output validation failed\");\n this.name = \"OutputValidationError\";\n }\n}\n\n/** Flatten a `ZodError` into a compact `{ path, code, message }[]`. */\nexport function formatZodIssues(error: ZodError): unknown {\n return error.issues.map((i) => ({\n path: i.path.join(\".\"),\n code: i.code,\n message: i.message,\n }));\n}\n\n/**\n * Default JSON mapping for the package's own errors (`ValidationError`,\n * `BadRequestError`, `OutputValidationError`). Returns `null` for anything\n * else so the caller can decide (rethrow / custom handler).\n */\nexport function defaultErrorResponse(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n c: any,\n err: unknown,\n): Response | null {\n if (err instanceof ValidationError) {\n return c.json(\n {\n success: false,\n error: \"Validation failed\",\n issues: formatZodIssues(err.zodError),\n },\n 400,\n );\n }\n if (err instanceof BadRequestError) {\n return c.json(\n { success: false, error: \"Bad Request\", message: err.message },\n 400,\n );\n }\n if (err instanceof OutputValidationError) {\n return c.json(\n {\n success: false,\n error: \"Output validation failed\",\n issues: formatZodIssues(err.zodError),\n },\n 500,\n );\n }\n return null;\n}\n","/**\n * OpenAPI 3.1 spec generator from {@link RouteDef} entries.\n *\n * Uses `@asteasolutions/zod-to-openapi` directly so users keep importing the\n * vanilla `zod` package (no opinionated `z` re-export required).\n */\n\nimport {\n OpenAPIRegistry,\n OpenApiGeneratorV31,\n extendZodWithOpenApi,\n} from \"@asteasolutions/zod-to-openapi\";\nimport { z } from \"zod\";\nimport type {\n AnyRouteDef,\n HttpMethod,\n InterceptorConfig,\n InterceptorErrorResponse,\n OpenAPIConfig,\n PayloadSource,\n} from \"./types\";\n\n// Patches Zod prototype with `.openapi()` and enables schema → OpenAPI\n// conversion for vanilla zod schemas. Idempotent — safe to call multiple times.\nextendZodWithOpenApi(z);\n\nconst DEFAULT_RESPONSE_DESCRIPTION = \"Successful response\";\nconst DEFAULT_ERROR_DESCRIPTION = \"Error response\";\n\nfunction defaultSource(method: HttpMethod): PayloadSource {\n return method === \"get\" ? \"query\" : \"json\";\n}\n\n/**\n * Resolve the success-envelope schema for a route, honouring an interceptor's\n * static schema or per-route factory. Falls back to the raw `route.output`.\n */\nfunction resolveSuccessSchema(\n routeOutput: z.ZodTypeAny | undefined,\n interceptor: InterceptorConfig | undefined,\n): z.ZodTypeAny | undefined {\n const out = interceptor?.output;\n if (!out) return routeOutput;\n return typeof out === \"function\" ? out(routeOutput) : out;\n}\n\n/** Normalise a declared error response to `{ description, schema? }`. */\nfunction normalizeErrorResponse(\n entry: InterceptorErrorResponse,\n): { description: string; schema?: z.ZodTypeAny } {\n // A Zod schema exposes `safeParse`; a plain config object does not.\n if (typeof (entry as { safeParse?: unknown }).safeParse === \"function\") {\n return { description: DEFAULT_ERROR_DESCRIPTION, schema: entry as z.ZodTypeAny };\n }\n const cfg = entry as { description?: string; schema?: z.ZodTypeAny };\n return { description: cfg.description ?? DEFAULT_ERROR_DESCRIPTION, schema: cfg.schema };\n}\n\n/** Build the OpenAPI document from the mounted route registry. */\nexport function buildOpenApiDocument(\n routes: AnyRouteDef[],\n basePath: string,\n config: OpenAPIConfig,\n interceptor?: InterceptorConfig,\n): Record<string, unknown> {\n const registry = new OpenAPIRegistry();\n\n if (config.securitySchemes) {\n for (const [name, scheme] of Object.entries(config.securitySchemes)) {\n // The registry's runtime accepts any spec-shaped object; cast through\n // `unknown` to satisfy zod-to-openapi's stricter typings.\n registry.registerComponent(\n \"securitySchemes\",\n name,\n scheme as unknown as Parameters<\n typeof registry.registerComponent\n >[2],\n );\n }\n }\n\n for (const route of routes) {\n const method = route.method;\n const source = route.source ?? defaultSource(method);\n const fullPath = joinPath(basePath, route.path ?? \"/\");\n const status = route.status ?? 200;\n\n const requestBody = buildRequestBody(method, source, route.input);\n const requestQuery = buildQueryOrParam(source, route.input, \"query\");\n const requestParams = buildQueryOrParam(source, route.input, \"param\");\n const operationId = makeOperationId(method, fullPath);\n\n // Success response — wrapped by the interceptor envelope when declared.\n const successSchema = resolveSuccessSchema(route.output, interceptor);\n const responses: Record<string, unknown> = {\n [status]: successSchema\n ? {\n description: DEFAULT_RESPONSE_DESCRIPTION,\n content: { \"application/json\": { schema: successSchema } },\n }\n : { description: DEFAULT_RESPONSE_DESCRIPTION },\n };\n\n // Declared error responses (interceptor.errors) applied to every operation.\n if (interceptor?.errors) {\n for (const [code, entry] of Object.entries(interceptor.errors)) {\n const { description, schema } = normalizeErrorResponse(entry);\n responses[code] = schema\n ? { description, content: { \"application/json\": { schema } } }\n : { description };\n }\n }\n\n registry.registerPath({\n method,\n path: convertExpressPathToOpenApi(fullPath),\n operationId,\n summary: route.summary,\n description: route.description,\n tags: route.tags,\n deprecated: route.deprecated,\n security: route.security,\n // Cast: registerPath types narrow query/params to ZodObject — we accept\n // any ZodTypeAny at runtime and let users pass plain objects via z.object.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n request: {\n ...(requestQuery ? { query: requestQuery } : {}),\n ...(requestParams ? { params: requestParams } : {}),\n ...(requestBody ? { body: requestBody } : {}),\n } as any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n responses: responses as any,\n });\n }\n\n const generator = new OpenApiGeneratorV31(registry.definitions);\n const document = generator.generateDocument({\n openapi: \"3.1.0\",\n // OpenAPIInfo is structurally compatible; cast to satisfy the `x-*` index.\n info: config.info as Parameters<\n typeof generator.generateDocument\n >[0][\"info\"],\n servers: config.servers,\n security: config.security,\n });\n return document as unknown as Record<string, unknown>;\n}\n\nfunction buildRequestBody(\n method: HttpMethod,\n source: PayloadSource,\n schema: z.ZodTypeAny | undefined,\n): { content: Record<string, { schema: z.ZodTypeAny }> } | null {\n if (!schema) return null;\n if (method === \"get\") return null;\n if (source === \"json\") {\n return { content: { \"application/json\": { schema } } };\n }\n if (source === \"form\") {\n return {\n content: { \"application/x-www-form-urlencoded\": { schema } },\n };\n }\n return null;\n}\n\nfunction buildQueryOrParam(\n source: PayloadSource,\n schema: z.ZodTypeAny | undefined,\n target: \"query\" | \"param\",\n): z.ZodTypeAny | undefined {\n if (!schema) return undefined;\n if (target === \"query\" && source === \"query\") return schema;\n if (target === \"param\" && source === \"param\") return schema;\n return undefined;\n}\n\n/** Convert `:foo` style express params to `{foo}` OpenAPI placeholders. */\nfunction convertExpressPathToOpenApi(path: string): string {\n return path.replace(/:([A-Za-z0-9_]+)/g, \"{$1}\");\n}\n\nfunction joinPath(base: string, path: string): string {\n const left = base.endsWith(\"/\") ? base.slice(0, -1) : base;\n const right = path.startsWith(\"/\") ? path : `/${path}`;\n const merged = `${left}${right}`;\n return merged === \"\" ? \"/\" : merged;\n}\n\nfunction makeOperationId(method: HttpMethod, path: string): string {\n const cleaned = path\n .replace(/[{}]/g, \"\")\n .replace(/\\/+/g, \"_\")\n .replace(/[^A-Za-z0-9_]/g, \"\")\n .replace(/^_+|_+$/g, \"\");\n return `${method}_${cleaned || \"root\"}`;\n}\n\n/**\n * Render a self-contained Scalar API Reference HTML page that points to the\n * generated spec. Loaded from CDN — no build step required.\n */\nexport function renderDocsHtml(specUrl: string, title: string): string {\n const safeUrl = specUrl.replace(/\"/g, \"&quot;\");\n const safeTitle = title\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\");\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n<title>${safeTitle}</title>\n</head>\n<body>\n<script id=\"api-reference\" data-url=\"${safeUrl}\"></script>\n<script src=\"https://cdn.jsdelivr.net/npm/@scalar/api-reference\"></script>\n</body>\n</html>`;\n}\n","/**\n * Login page renderer for `firebaseAuth`.\n * Standalone HTML — no JSX. Embeds the Firebase JS SDK from the official CDN\n * (modular v10) so users don't need a frontend build step.\n *\n * Flow:\n * 1. User signs in client-side (email/password or Google popup).\n * 2. We call `user.getIdToken(true)` and `POST` it to `{sessionPath}`.\n * 3. The server mints a session cookie and we redirect to `next`.\n */\n\ninterface LoginPageOptions {\n title: string;\n providers: (\"password\" | \"google\")[];\n apiKey: string;\n authDomain: string;\n sessionPath: string;\n next: string;\n error: string | null;\n /**\n * Firebase Auth emulator host (e.g. `127.0.0.1:9099`). When set, the client\n * SDK is pointed at the emulator via `connectAuthEmulator` so local sign-ins\n * match the server side (which the Admin SDK already routes to the emulator\n * through `FIREBASE_AUTH_EMULATOR_HOST`). A bare `host:port` is upgraded to\n * an `http://` URL.\n */\n authEmulatorHost?: string;\n}\n\n/** Normalise an emulator host (`host:port`) into a full `http://` URL. */\nfunction emulatorUrl(host: string | undefined): string {\n if (!host) return \"\";\n return /^https?:\\/\\//.test(host) ? host : `http://${host}`;\n}\n\nfunction htmlEscape(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#39;\");\n}\n\nfunction jsonEscape(value: string): string {\n // Safe for embedding inside a <script> string literal.\n return JSON.stringify(value).slice(1, -1);\n}\n\nexport function renderLoginPage(opts: LoginPageOptions): string {\n const showPassword = opts.providers.includes(\"password\");\n const showGoogle = opts.providers.includes(\"google\");\n const initialError = opts.error ? htmlEscape(opts.error) : \"\";\n\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n <title>${htmlEscape(opts.title)}</title>\n <style>\n :root { color-scheme: light dark; }\n * { box-sizing: border-box; }\n body {\n margin: 0;\n min-height: 100vh;\n display: grid;\n place-items: center;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n background: #f5f5f7;\n color: #1d1d1f;\n }\n @media (prefers-color-scheme: dark) {\n body { background: #1d1d1f; color: #f5f5f7; }\n .card { background: #2c2c2e !important; }\n input { background: #1d1d1f; color: #f5f5f7; border-color: #444; }\n input::placeholder { color: #888; }\n input:-webkit-autofill,\n input:-webkit-autofill:hover,\n input:-webkit-autofill:focus,\n input:-webkit-autofill:active {\n -webkit-text-fill-color: #f5f5f7 !important;\n -webkit-box-shadow: 0 0 0 1000px #1d1d1f inset !important;\n caret-color: #f5f5f7;\n }\n .divider { color: #888; }\n .divider::before, .divider::after { background: #444; }\n }\n .card {\n width: min(420px, 92vw);\n padding: 32px;\n background: #fff;\n border-radius: 14px;\n box-shadow: 0 20px 50px rgba(0,0,0,.08);\n }\n h1 { font-size: 22px; margin: 0 0 6px; font-weight: 600; }\n p.sub { margin: 0 0 24px; opacity: .7; font-size: 14px; }\n label { display: block; font-size: 13px; margin-bottom: 6px; opacity: .8; }\n input {\n width: 100%; padding: 11px 12px;\n border: 1px solid #d2d2d7; border-radius: 8px;\n font-size: 15px; outline: none; background: #fff; color: #1d1d1f;\n margin-bottom: 14px;\n }\n input::placeholder { color: #86868b; }\n input:focus { border-color: #0071e3; box-shadow: 0 0 0 3px rgba(0,113,227,.15); }\n /* Force readable text on Chrome's autofill (otherwise the input keeps\n the autofill's white background but inherits the page's dark-mode text\n colour, producing white-on-white). */\n input:-webkit-autofill,\n input:-webkit-autofill:hover,\n input:-webkit-autofill:focus,\n input:-webkit-autofill:active {\n -webkit-text-fill-color: #1d1d1f !important;\n -webkit-box-shadow: 0 0 0 1000px #fff inset !important;\n caret-color: #1d1d1f;\n transition: background-color 9999s ease-out 0s;\n }\n button {\n width: 100%; padding: 11px 12px; border: none; border-radius: 8px;\n font-size: 15px; font-weight: 500; cursor: pointer;\n transition: opacity .15s, transform .05s;\n }\n button:active { transform: scale(.98); }\n button:disabled { opacity: .55; cursor: progress; }\n .btn-primary { background: #0071e3; color: #fff; }\n .btn-google {\n background: #fff; color: #1d1d1f; border: 1px solid #d2d2d7;\n display: flex; align-items: center; justify-content: center; gap: 8px;\n }\n @media (prefers-color-scheme: dark) {\n .btn-google { background: #2c2c2e; color: #f5f5f7; border-color: #444; }\n }\n .divider {\n display: flex; align-items: center; gap: 12px;\n margin: 16px 0; font-size: 12px; opacity: .55; text-transform: uppercase;\n }\n .divider::before, .divider::after {\n content: \"\"; flex: 1; height: 1px; background: #d2d2d7;\n }\n .err {\n margin: 0 0 14px; padding: 10px 12px;\n background: rgba(255,59,48,.12); color: #ff3b30;\n border-radius: 8px; font-size: 13px;\n display: ${initialError ? \"block\" : \"none\"};\n }\n .ok {\n margin: 0 0 14px; padding: 10px 12px;\n background: rgba(52,199,89,.12); color: #34c759;\n border-radius: 8px; font-size: 13px; display: none;\n }\n </style>\n</head>\n<body>\n <main class=\"card\">\n <h1>${htmlEscape(opts.title)}</h1>\n <p class=\"sub\">Sign in to continue.</p>\n <div id=\"err\" class=\"err\">${initialError}</div>\n <div id=\"ok\" class=\"ok\"></div>\n\n ${\n showPassword\n ? `<form id=\"pwd-form\" autocomplete=\"on\">\n <label for=\"email\">Email</label>\n <input id=\"email\" type=\"email\" name=\"email\" autocomplete=\"username\" required />\n <label for=\"password\">Password</label>\n <input id=\"password\" type=\"password\" name=\"password\" autocomplete=\"current-password\" required />\n <button class=\"btn-primary\" type=\"submit\" id=\"pwd-submit\">Sign in</button>\n </form>`\n : \"\"\n }\n\n ${showPassword && showGoogle ? `<div class=\"divider\">or</div>` : \"\"}\n\n ${\n showGoogle\n ? `<button class=\"btn-google\" type=\"button\" id=\"google-btn\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 18 18\" aria-hidden=\"true\">\n <path fill=\"#4285F4\" d=\"M17.64 9.205c0-.638-.057-1.252-.164-1.841H9v3.481h4.844a4.14 4.14 0 0 1-1.796 2.716v2.259h2.908c1.702-1.567 2.684-3.875 2.684-6.615z\"/>\n <path fill=\"#34A853\" d=\"M9 18c2.43 0 4.467-.806 5.956-2.18l-2.908-2.259c-.806.54-1.837.86-3.048.86-2.344 0-4.328-1.584-5.036-3.711H.957v2.332A8.997 8.997 0 0 0 9 18z\"/>\n <path fill=\"#FBBC05\" d=\"M3.964 10.71A5.41 5.41 0 0 1 3.682 9c0-.593.102-1.17.282-1.71V4.958H.957A8.996 8.996 0 0 0 0 9c0 1.452.348 2.827.957 4.042l3.007-2.332z\"/>\n <path fill=\"#EA4335\" d=\"M9 3.58c1.321 0 2.508.454 3.44 1.345l2.582-2.58C13.463.891 11.426 0 9 0A8.997 8.997 0 0 0 .957 4.958L3.964 7.29C4.672 5.163 6.656 3.58 9 3.58z\"/>\n </svg>\n Continue with Google\n </button>`\n : \"\"\n }\n </main>\n\n <script type=\"module\">\n import { initializeApp } from \"https://www.gstatic.com/firebasejs/10.13.2/firebase-app.js\";\n import {\n getAuth,\n connectAuthEmulator,\n signInWithEmailAndPassword,\n signInWithPopup,\n GoogleAuthProvider,\n setPersistence,\n browserSessionPersistence,\n } from \"https://www.gstatic.com/firebasejs/10.13.2/firebase-auth.js\";\n\n const app = initializeApp({\n apiKey: \"${jsonEscape(opts.apiKey)}\",\n authDomain: \"${jsonEscape(opts.authDomain)}\",\n });\n const auth = getAuth(app);\n ${\n opts.authEmulatorHost\n ? `connectAuthEmulator(auth, ${JSON.stringify(\n emulatorUrl(opts.authEmulatorHost),\n )}, { disableWarnings: true });`\n : \"\"\n }\n // Don't persist client-side — the server-side session cookie is the source of truth.\n await setPersistence(auth, browserSessionPersistence).catch(() => {});\n\n const SESSION_PATH = \"${jsonEscape(opts.sessionPath)}\";\n const NEXT = ${JSON.stringify(opts.next)};\n\n // Defense-in-depth against open redirect (issue #07): only ever navigate\n // to a same-origin path, even though the server already sanitizes next.\n // Resolve against the current page URL (not just the origin) so a relative\n // \"next\" works behind any Cloud Functions / reverse-proxy path prefix.\n function safeNext(raw) {\n try {\n const u = new URL(raw, window.location.href);\n if (u.origin !== window.location.origin) return \"/\";\n return u.pathname + u.search + u.hash;\n } catch {\n return \"/\";\n }\n }\n\n const errEl = document.getElementById(\"err\");\n const okEl = document.getElementById(\"ok\");\n function showError(msg) {\n errEl.textContent = msg;\n errEl.style.display = \"block\";\n okEl.style.display = \"none\";\n }\n function showOk(msg) {\n okEl.textContent = msg;\n okEl.style.display = \"block\";\n errEl.style.display = \"none\";\n }\n\n async function exchangeForSession(user) {\n const idToken = await user.getIdToken(true);\n const res = await fetch(SESSION_PATH, {\n method: \"POST\",\n credentials: \"same-origin\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ idToken }),\n });\n if (!res.ok) {\n const data = await res.json().catch(() => ({}));\n throw new Error(data.error || \"Session exchange failed (\" + res.status + \")\");\n }\n // Sign out client-side immediately — we only needed the id token.\n try { await auth.signOut(); } catch {}\n showOk(\"Signed in. Redirecting…\");\n window.location.replace(safeNext(NEXT));\n }\n\n const pwdForm = document.getElementById(\"pwd-form\");\n if (pwdForm) {\n pwdForm.addEventListener(\"submit\", async (ev) => {\n ev.preventDefault();\n const submit = document.getElementById(\"pwd-submit\");\n submit.disabled = true;\n try {\n const email = document.getElementById(\"email\").value.trim();\n const password = document.getElementById(\"password\").value;\n const cred = await signInWithEmailAndPassword(auth, email, password);\n await exchangeForSession(cred.user);\n } catch (err) {\n showError(err && err.message ? err.message : String(err));\n submit.disabled = false;\n }\n });\n }\n\n const googleBtn = document.getElementById(\"google-btn\");\n if (googleBtn) {\n googleBtn.addEventListener(\"click\", async () => {\n googleBtn.disabled = true;\n try {\n const provider = new GoogleAuthProvider();\n const cred = await signInWithPopup(auth, provider);\n await exchangeForSession(cred.user);\n } catch (err) {\n showError(err && err.message ? err.message : String(err));\n googleBtn.disabled = false;\n }\n });\n }\n </script>\n</body>\n</html>`;\n}\n","/**\n * Session cookie + logout handlers for `firebaseAuth`.\n * Exchanges a Firebase ID token for an HttpOnly session cookie via the\n * Firebase Admin SDK (`createSessionCookie`), and clears it on logout.\n */\n\nimport type { RouteHandler } from \"../admin/router\";\nimport type { FirebaseAdminAuthLike } from \"./firebase-auth\";\n\nexport const SESSION_COOKIE_DEFAULT = \"__admin_session\";\n\ninterface SessionHandlerConfig {\n getAuth: () => FirebaseAdminAuthLike;\n cookieName: string;\n ttlDays: number;\n secure: boolean;\n sameSite: \"Strict\" | \"Lax\" | \"None\";\n}\n\ninterface LogoutHandlerConfig {\n getAuth: () => FirebaseAdminAuthLike;\n cookieName: string;\n secure: boolean;\n sameSite: \"Strict\" | \"Lax\" | \"None\";\n}\n\n// ---------------------------------------------------------------------------\n// Cookie utilities\n// ---------------------------------------------------------------------------\n\n/** Parse a `Cookie` header into a flat key→value map. Tolerant of malformed pairs. */\nexport function parseCookies(header: string): Record<string, string> {\n const out: Record<string, string> = {};\n if (!header) return out;\n for (const part of header.split(\";\")) {\n const eq = part.indexOf(\"=\");\n if (eq === -1) continue;\n const key = part.slice(0, eq).trim();\n if (!key) continue;\n let value = part.slice(eq + 1).trim();\n if (value.startsWith('\"') && value.endsWith('\"')) {\n value = value.slice(1, -1);\n }\n try {\n out[key] = decodeURIComponent(value);\n } catch {\n out[key] = value;\n }\n }\n return out;\n}\n\nfunction buildSetCookie(\n name: string,\n value: string,\n opts: {\n maxAgeSeconds: number;\n secure: boolean;\n sameSite: \"Strict\" | \"Lax\" | \"None\";\n path?: string;\n },\n): string {\n const segments = [\n `${name}=${value}`,\n `Path=${opts.path ?? \"/\"}`,\n `Max-Age=${opts.maxAgeSeconds}`,\n \"HttpOnly\",\n `SameSite=${opts.sameSite}`,\n ];\n if (opts.secure) segments.push(\"Secure\");\n return segments.join(\"; \");\n}\n\n/** Pull JSON body out of any Express-like request (works with `parseBody` already done by the host). */\nfunction readJsonBody(req: { body?: unknown }): Record<string, unknown> {\n const body = req.body;\n if (!body) return {};\n if (typeof body === \"string\") {\n try {\n return JSON.parse(body) as Record<string, unknown>;\n } catch {\n return {};\n }\n }\n if (typeof body === \"object\") return body as Record<string, unknown>;\n return {};\n}\n\n// ---------------------------------------------------------------------------\n// Handlers\n// ---------------------------------------------------------------------------\n\n/**\n * `POST /__session` — receives `{ idToken }`, verifies it via the Admin SDK,\n * mints a session cookie, and sets it on the response.\n */\nexport function createSessionHandler(cfg: SessionHandlerConfig): RouteHandler {\n return async (req, res) => {\n const body = readJsonBody(req);\n const idToken = typeof body.idToken === \"string\" ? body.idToken : \"\";\n if (!idToken) {\n res\n .status(400)\n .set(\"Content-Type\", \"application/json; charset=utf-8\")\n .send(JSON.stringify({ success: false, error: \"Missing idToken\" }));\n return;\n }\n\n const expiresInMs = cfg.ttlDays * 24 * 60 * 60 * 1000;\n try {\n const auth = cfg.getAuth();\n // Verify first so we surface auth errors before minting the cookie.\n const decoded = await auth.verifyIdToken(idToken, true);\n // Reject very old sign-ins to encourage fresh re-auth (Google guidance: < 5 min).\n const authTimeRaw = (decoded as { auth_time?: number }).auth_time;\n const authTime =\n typeof authTimeRaw === \"number\" ? authTimeRaw * 1000 : Date.now();\n if (Date.now() - authTime > 5 * 60 * 1000) {\n res\n .status(401)\n .set(\"Content-Type\", \"application/json; charset=utf-8\")\n .send(\n JSON.stringify({\n success: false,\n error: \"Recent sign-in required\",\n }),\n );\n return;\n }\n const sessionCookie = await auth.createSessionCookie(idToken, {\n expiresIn: expiresInMs,\n });\n const cookie = buildSetCookie(cfg.cookieName, encodeURIComponent(sessionCookie), {\n maxAgeSeconds: Math.floor(expiresInMs / 1000),\n secure: cfg.secure,\n sameSite: cfg.sameSite,\n });\n res\n .status(200)\n .set(\"Set-Cookie\", cookie)\n .set(\"Content-Type\", \"application/json; charset=utf-8\")\n .send(JSON.stringify({ success: true }));\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Invalid idToken\";\n res\n .status(401)\n .set(\"Content-Type\", \"application/json; charset=utf-8\")\n .send(JSON.stringify({ success: false, error: message }));\n }\n };\n}\n\n/**\n * `POST /__logout` — clears the session cookie and revokes the user's refresh\n * tokens (best-effort; failure to revoke does not block the logout).\n */\nexport function createLogoutHandler(cfg: LogoutHandlerConfig): RouteHandler {\n return async (req, res) => {\n try {\n const cookieHeader = req.headers?.cookie;\n const raw = Array.isArray(cookieHeader) ? cookieHeader.join(\"; \") : cookieHeader;\n const cookies = parseCookies(typeof raw === \"string\" ? raw : \"\");\n const session = cookies[cfg.cookieName];\n if (session) {\n try {\n const auth = cfg.getAuth();\n const decoded = await auth.verifySessionCookie(session, false);\n await auth.revokeRefreshTokens(decoded.uid);\n } catch {\n /* best-effort */\n }\n }\n } finally {\n const expired = buildSetCookie(cfg.cookieName, \"\", {\n maxAgeSeconds: 0,\n secure: cfg.secure,\n sameSite: cfg.sameSite,\n });\n res\n .status(200)\n .set(\"Set-Cookie\", expired)\n .set(\"Content-Type\", \"application/json; charset=utf-8\")\n .send(JSON.stringify({ success: true }));\n }\n };\n}\n","/**\n * Hono-native auth guards for the OpenAPI docs / spec endpoints.\n *\n * These return plain Hono `MiddlewareHandler`s, so they slot into\n * `OpenAPIConfig.docsAuth` (which protects only `/docs` + `/openapi.json`,\n * never the API routes). For a fully custom flow, pass your own middleware\n * instead of these helpers.\n */\n\nimport type { MiddlewareHandler } from \"hono\";\nimport { renderLoginPage } from \"../auth/login-page\";\nimport { parseCookies } from \"../auth/session\";\nimport type {\n DecodedIdTokenLike,\n FirebaseAdminAuthLike,\n} from \"../auth/firebase-auth\";\n\n/** Decoded token shape — kept minimal to avoid a hard firebase-admin import. */\nexport interface DecodedBearerToken {\n uid: string;\n email?: string;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n [claim: string]: any;\n}\n\n/** Minimal Firebase Admin Auth surface used by {@link firebaseBearerAuth}. */\nexport interface FirebaseBearerAuthLike {\n verifyIdToken(\n idToken: string,\n checkRevoked?: boolean,\n ): Promise<DecodedBearerToken>;\n}\n\n/** Options for {@link firebaseBearerAuth}. */\nexport interface FirebaseBearerAuthOptions {\n /**\n * Returns the Firebase Admin Auth instance, e.g. `() => getAuth()`. Called\n * lazily on each request so `initializeApp()` runs first.\n */\n getAuth: () => FirebaseBearerAuthLike;\n /**\n * Authorization policy. Return `false` (or throw) to reject the request,\n * any truthy value to allow. Defaults to allowing any verified token.\n */\n allow?: (token: DecodedBearerToken) => boolean | Promise<boolean>;\n /** Revoke check passed to `verifyIdToken`. Default: `false`. */\n checkRevoked?: boolean;\n /**\n * Context key under which the decoded token is stored (`c.set(key, token)`)\n * for downstream handlers. Default: `\"docsUser\"`.\n */\n contextKey?: string;\n}\n\n/**\n * Guard the docs / spec endpoints with a Firebase ID token (Bearer scheme).\n *\n * @example\n * ```ts\n * import { getAuth } from \"firebase-admin/auth\";\n * import { firebaseBearerAuth } from \"@lpdjs/firestore-repo-service/servers/hono\";\n *\n * openapi: {\n * info,\n * docsAuth: firebaseBearerAuth({\n * getAuth: () => getAuth(),\n * allow: (t) => t.admin === true,\n * }),\n * }\n * ```\n */\nexport function firebaseBearerAuth(\n options: FirebaseBearerAuthOptions,\n): MiddlewareHandler {\n const {\n getAuth,\n allow,\n checkRevoked = false,\n contextKey = \"docsUser\",\n } = options;\n\n return async (c, next) => {\n const header = c.req.header(\"authorization\") ?? c.req.header(\"Authorization\");\n const match = header?.match(/^Bearer\\s+(.+)$/i);\n if (!match) {\n return c.json({ error: \"Unauthorized\" }, 401, {\n \"WWW-Authenticate\": \"Bearer\",\n });\n }\n\n let decoded: DecodedBearerToken;\n try {\n decoded = await getAuth().verifyIdToken(match[1]!, checkRevoked);\n } catch {\n return c.json({ error: \"Unauthorized\" }, 401, {\n \"WWW-Authenticate\": \"Bearer\",\n });\n }\n\n if (allow) {\n let permitted = false;\n try {\n permitted = await allow(decoded);\n } catch {\n permitted = false;\n }\n if (!permitted) return c.json({ error: \"Forbidden\" }, 403);\n }\n\n c.set(contextKey, decoded);\n return next();\n };\n}\n\n/** Options for {@link basicAuth}. */\nexport interface BasicAuthOptions {\n username: string;\n password: string;\n /** Realm advertised in the `WWW-Authenticate` header. Default: `\"Docs\"`. */\n realm?: string;\n}\n\n/**\n * Guard the docs / spec endpoints with HTTP Basic Auth.\n *\n * @example\n * ```ts\n * openapi: { info, docsAuth: basicAuth({ username: \"admin\", password: \"secret\" }) }\n * ```\n */\nexport function basicAuth(options: BasicAuthOptions): MiddlewareHandler {\n const { username, password, realm = \"Docs\" } = options;\n const expected = `Basic ${btoa(`${username}:${password}`)}`;\n\n return async (c, next) => {\n const header =\n c.req.header(\"authorization\") ?? c.req.header(\"Authorization\") ?? \"\";\n if (!timingSafeEqual(header, expected)) {\n return c.text(\"Unauthorized\", 401, {\n \"WWW-Authenticate\": `Basic realm=\"${realm}\"`,\n });\n }\n return next();\n };\n}\n\n/** Constant-time string comparison to avoid leaking length/contents via timing. */\nfunction timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n let diff = 0;\n for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);\n return diff === 0;\n}\n\n// ===========================================================================\n// Firebase login-form auth (session cookie) — mirrors the admin server flow\n// ===========================================================================\n\n/** A bare auxiliary route name (relative to the docs directory). */\nexport interface DocsAuthRoute {\n method: \"GET\" | \"POST\";\n /** Bare path segment relative to the docs directory, e.g. `\"__login\"`. */\n name: string;\n handler: MiddlewareHandler;\n}\n\n/**\n * Richer `docsAuth` value (vs. a bare `MiddlewareHandler`): a guard middleware\n * **plus** auxiliary routes (login page, session exchange, logout) that the\n * {@link HonoServer} mounts next to the docs/spec endpoints. Mirrors the admin\n * server's `AuthExtension`. Produced by {@link firebaseDocsAuth}.\n */\nexport interface DocsAuthExtension {\n readonly __docsAuthExtension: true;\n /** Guard applied to the docs UI + JSON spec endpoints. */\n middleware: MiddlewareHandler;\n /** Auxiliary routes mounted (unguarded) in the docs directory. */\n routes: DocsAuthRoute[];\n /** Bare login route name, used to redirect unauthenticated browsers. */\n loginName: string;\n}\n\n/** Type guard distinguishing a {@link DocsAuthExtension} from a raw middleware. */\nexport function isDocsAuthExtension(value: unknown): value is DocsAuthExtension {\n return (\n typeof value === \"object\" &&\n value !== null &&\n (value as { __docsAuthExtension?: unknown }).__docsAuthExtension === true\n );\n}\n\n/** Options for {@link firebaseDocsAuth}. */\nexport interface FirebaseDocsAuthOptions {\n /**\n * Returns the Firebase Admin Auth instance, e.g. `() => getAuth()`. Called\n * lazily on each request so `initializeApp()` runs first. Must expose\n * `verifyIdToken`, `verifySessionCookie`, `createSessionCookie`,\n * `revokeRefreshTokens`.\n */\n getAuth: () => FirebaseAdminAuthLike;\n /** Firebase Web API key — required by the JS SDK on the login page. */\n apiKey: string;\n /** Firebase Auth domain (e.g. `my-project.firebaseapp.com`). */\n authDomain: string;\n /**\n * Transport mode. `\"cookie\"` (default) gates the docs behind the bundled\n * login form + a server-side session cookie. `\"both\"` additionally accepts a\n * `Bearer` ID token (handy to embed the docs in an authenticated iframe).\n */\n mode?: \"cookie\" | \"both\";\n /**\n * Authorization policy. Return `false` (or throw) to reject, any truthy value\n * to allow. Defaults to allowing any verified user. Receives the decoded\n * ID-token / session-cookie claims.\n */\n allow?: (token: DecodedIdTokenLike) => boolean | Promise<boolean>;\n /** Providers shown on the login page. Default: `[\"password\", \"google\"]`. */\n providers?: (\"password\" | \"google\")[];\n /** Login page title. Default: `\"Docs sign-in\"`. */\n title?: string;\n /** Session cookie name. Default: `\"__docs_session\"`. */\n cookieName?: string;\n /** Session cookie TTL in days. Default: `5` (Firebase max is 14). */\n sessionTtlDays?: number;\n /** Cookie `Secure` flag. Default: `true` (set `false` only for local HTTP). */\n secureCookie?: boolean;\n /** Cookie `SameSite`. Default: `\"Lax\"`. */\n sameSite?: \"Strict\" | \"Lax\" | \"None\";\n /**\n * Context key under which the decoded token is stored (`c.set(key, token)`)\n * for downstream handlers. Default: `\"docsUser\"`.\n */\n contextKey?: string;\n /**\n * Behaviour when authentication fails. `\"redirect\"` (default) sends browser\n * `GET`s to the login page; `\"401\"` always returns a JSON 401.\n */\n onUnauthenticated?: \"redirect\" | \"401\";\n /**\n * Firebase Auth emulator host (e.g. `127.0.0.1:9099`). When set, the login\n * page's client SDK targets the emulator via `connectAuthEmulator`, matching\n * the Admin SDK (which already routes to the emulator when\n * `FIREBASE_AUTH_EMULATOR_HOST` is set). Defaults to that env var; pass `\"\"`\n * to force it off.\n */\n authEmulatorHost?: string;\n}\n\nconst LOGIN_NAME = \"__login\";\nconst SESSION_NAME = \"__session\";\nconst LOGOUT_NAME = \"__logout\";\n\n/** Last path segment, e.g. `/v1/docs` → `docs`. Empty string for `/`. */\nfunction lastSegment(path: string): string {\n const segs = path.split(\"?\")[0]!.split(\"/\").filter(Boolean);\n return segs[segs.length - 1] ?? \"\";\n}\n\n/** Build a `Set-Cookie` header value (HttpOnly, path-scoped). */\nfunction buildSetCookie(\n name: string,\n value: string,\n opts: {\n maxAgeSeconds: number;\n secure: boolean;\n sameSite: \"Strict\" | \"Lax\" | \"None\";\n },\n): string {\n const segments = [\n `${name}=${value}`,\n \"Path=/\",\n `Max-Age=${opts.maxAgeSeconds}`,\n \"HttpOnly\",\n `SameSite=${opts.sameSite}`,\n ];\n if (opts.secure) segments.push(\"Secure\");\n return segments.join(\"; \");\n}\n\n/** Restrict a `next` target to a simple same-origin relative token. */\nfunction sanitizeNext(raw: string | undefined, fallback: string): string {\n if (!raw) return fallback;\n if (raw.startsWith(\"/\") || raw.includes(\"://\") || raw.includes(\"\\\\\")) {\n return fallback;\n }\n return raw;\n}\n\n/**\n * Guard the docs / spec endpoints with a Firebase **login form + session\n * cookie**, the same flow as the admin server — instead of forcing callers to\n * craft a `Bearer` token by hand.\n *\n * Returns a {@link DocsAuthExtension}: pass it straight to\n * `OpenAPIConfig.docsAuth`. The {@link HonoServer} mounts the bundled\n * `__login` / `__session` / `__logout` routes next to the docs and applies the\n * guard. Unauthenticated browsers are redirected to the login page; once signed\n * in, an HttpOnly session cookie keeps them authenticated.\n *\n * @example\n * ```ts\n * import { getAuth } from \"firebase-admin/auth\";\n * import { firebaseDocsAuth } from \"@lpdjs/firestore-repo-service/servers/hono\";\n *\n * openapi: {\n * info,\n * docsAuth: firebaseDocsAuth({\n * getAuth: () => getAuth(),\n * apiKey: process.env.FIREBASE_API_KEY!,\n * authDomain: process.env.FIREBASE_AUTH_DOMAIN!,\n * allow: (t) => t.admin === true, // optional policy\n * }),\n * }\n * ```\n */\nexport function firebaseDocsAuth(\n options: FirebaseDocsAuthOptions,\n): DocsAuthExtension {\n const {\n getAuth,\n apiKey,\n authDomain,\n mode = \"cookie\",\n allow,\n providers = [\"password\", \"google\"],\n title = \"Docs sign-in\",\n cookieName = \"__docs_session\",\n sessionTtlDays = 5,\n secureCookie = true,\n sameSite = \"Lax\",\n contextKey = \"docsUser\",\n onUnauthenticated = \"redirect\",\n authEmulatorHost = process.env[\"FIREBASE_AUTH_EMULATOR_HOST\"],\n } = options;\n\n async function passesAllow(token: DecodedIdTokenLike): Promise<boolean> {\n if (!allow) return true;\n try {\n return Boolean(await allow(token));\n } catch {\n return false;\n }\n }\n\n // ── Login page (GET __login) ──────────────────────────────────────────────\n const loginHandler: MiddlewareHandler = async (c) => {\n // Validate lazily (at request time) so importing this module during the\n // Firebase CLI analysis / emulator load doesn't throw before env vars are\n // injected — same approach as the admin `firebaseAuth`.\n if (!apiKey || !authDomain) {\n throw new Error(\n \"[firebaseDocsAuth] `apiKey` and `authDomain` are required for the login \" +\n \"page. Find both in the Firebase Console → Project Settings → General → \" +\n \"Web app config.\",\n );\n }\n const next = sanitizeNext(c.req.query(\"next\"), \"docs\");\n const error = c.req.query(\"error\") ?? null;\n const html = renderLoginPage({\n title,\n providers,\n apiKey,\n authDomain,\n // Relative to the login page URL (resolved client-side), so it works\n // behind any Cloud Functions / reverse-proxy prefix.\n sessionPath: SESSION_NAME,\n next,\n error,\n authEmulatorHost,\n });\n return c.html(html, 200, { \"Cache-Control\": \"no-store\" });\n };\n\n // ── Session exchange (POST __session) ─────────────────────────────────────\n const sessionHandler: MiddlewareHandler = async (c) => {\n let idToken = \"\";\n try {\n const body = (await c.req.json()) as { idToken?: unknown };\n idToken = typeof body.idToken === \"string\" ? body.idToken : \"\";\n } catch {\n idToken = \"\";\n }\n if (!idToken) {\n return c.json({ success: false, error: \"Missing idToken\" }, 400);\n }\n\n const expiresInMs = sessionTtlDays * 24 * 60 * 60 * 1000;\n try {\n const auth = getAuth();\n const decoded = await auth.verifyIdToken(idToken, true);\n if (!(await passesAllow(decoded))) {\n return c.json({ success: false, error: \"Forbidden\" }, 403);\n }\n // Reject stale sign-ins (Google guidance: require a recent auth, < 5 min).\n const authTimeRaw = (decoded as { auth_time?: number }).auth_time;\n const authTime =\n typeof authTimeRaw === \"number\" ? authTimeRaw * 1000 : Date.now();\n if (Date.now() - authTime > 5 * 60 * 1000) {\n return c.json(\n { success: false, error: \"Recent sign-in required\" },\n 401,\n );\n }\n const sessionCookie = await auth.createSessionCookie(idToken, {\n expiresIn: expiresInMs,\n });\n const cookie = buildSetCookie(\n cookieName,\n encodeURIComponent(sessionCookie),\n {\n maxAgeSeconds: Math.floor(expiresInMs / 1000),\n secure: secureCookie,\n sameSite,\n },\n );\n c.header(\"Set-Cookie\", cookie);\n return c.json({ success: true });\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Invalid idToken\";\n return c.json({ success: false, error: message }, 401);\n }\n };\n\n // ── Logout (POST __logout) ────────────────────────────────────────────────\n const logoutHandler: MiddlewareHandler = async (c) => {\n const cookies = parseCookies(c.req.header(\"cookie\") ?? \"\");\n const session = cookies[cookieName];\n if (session) {\n try {\n const auth = getAuth();\n const decoded = await auth.verifySessionCookie(session, false);\n await auth.revokeRefreshTokens(decoded.uid);\n } catch {\n /* best-effort */\n }\n }\n c.header(\n \"Set-Cookie\",\n buildSetCookie(cookieName, \"\", {\n maxAgeSeconds: 0,\n secure: secureCookie,\n sameSite,\n }),\n );\n return c.json({ success: true });\n };\n\n // ── Guard middleware (docs + spec) ────────────────────────────────────────\n const middleware: MiddlewareHandler = async (c, next) => {\n const auth = getAuth();\n\n // mode \"both\": accept a Bearer ID token first (e.g. iframe embedding).\n if (mode === \"both\") {\n const header =\n c.req.header(\"authorization\") ?? c.req.header(\"Authorization\");\n const match = header?.match(/^Bearer\\s+(.+)$/i);\n if (match) {\n try {\n const decoded = await auth.verifyIdToken(match[1]!, false);\n if (await passesAllow(decoded)) {\n c.set(contextKey, decoded);\n return next();\n }\n } catch {\n /* fall through to cookie / unauthenticated */\n }\n }\n }\n\n // Session cookie.\n const cookies = parseCookies(c.req.header(\"cookie\") ?? \"\");\n const session = cookies[cookieName];\n if (session) {\n try {\n const decoded = await auth.verifySessionCookie(session, false);\n if (await passesAllow(decoded)) {\n c.set(contextKey, decoded);\n return next();\n }\n } catch {\n /* unauthenticated */\n }\n }\n\n // Unauthenticated.\n const accept = c.req.header(\"accept\") ?? \"\";\n const isBrowserGet =\n c.req.method === \"GET\" && accept.includes(\"text/html\");\n if (onUnauthenticated === \"redirect\" && isBrowserGet) {\n const target = encodeURIComponent(lastSegment(c.req.path) || \"docs\");\n // Relative to the requested page (login route is a sibling), so the\n // external prefix is preserved by the browser.\n return c.redirect(`${LOGIN_NAME}?next=${target}`, 302);\n }\n return c.json({ error: \"Unauthorized\" }, 401);\n };\n\n return {\n __docsAuthExtension: true,\n middleware,\n loginName: LOGIN_NAME,\n routes: [\n { method: \"GET\", name: LOGIN_NAME, handler: loginHandler },\n { method: \"POST\", name: SESSION_NAME, handler: sessionHandler },\n { method: \"POST\", name: LOGOUT_NAME, handler: logoutHandler },\n ],\n };\n}\n","/**\n * Global DI services container for the Hono server.\n *\n * Lets you declare all your singletons (repositories, SDK clients, useCases,\n * loggers…) **once**, then inject them anywhere — routes, interceptors,\n * cron jobs, Firestore triggers, tests.\n *\n * ## How it works\n *\n * - Each service is constructed **lazily** on first access and cached for\n * the process lifetime (perfect for Cloud Functions cold-start).\n * - Providers may be **classes** (auto-injected: `new Class(services)`)\n * or **factories** (`(services) => instance`). Mix both freely.\n * - Inter-service dependencies are inferred either by destructuring the\n * factory argument (`postRepo: ({ db }) => new PostRepo(db)`) or by\n * reading `this.services.*` inside a class.\n * - A built-in `ctx` service exposes the **current request's** Hono\n * `Context` via `AsyncLocalStorage`. UseCases access\n * `this.services.ctx.c.get(\"user\")` without any plumbing.\n * - Cycles are detected at first access with a clear error.\n *\n * @example\n * ```ts\n * // src/services.ts — infrastructure singletons only (no useCases here)\n * import { createServices } from \"@lpdjs/firestore-repo-service/servers/hono\";\n * import { PostRepo } from \"./domains/posts/PostRepo.js\";\n * import { Mailer } from \"./services/Mailer.js\";\n *\n * export const services = createServices({\n * postRepo: PostRepo, // class form (auto-injected)\n * mailer: ({ ctx }) => new Mailer(ctx), // factory form\n * });\n *\n * export type Services = typeof services;\n * ```\n *\n * @example\n * ```ts\n * // src/apis.ts\n * import { services } from \"./services.js\";\n * export const apis = createApiRegistry(\n * { v1: { basePath: \"/v1\", ... } },\n * { services },\n * );\n * ```\n *\n * @example\n * ```ts\n * // Inside a useCase — extends the UseCase base, owns its Zod schemas\n * import { z } from \"zod\";\n * import { UseCase } from \"@lpdjs/firestore-repo-service/servers/hono\";\n * import type { Services } from \"../../services.js\";\n *\n * const input = z.object({ title: z.string() });\n * const output = z.object({ id: z.string() });\n *\n * export class CreatePostUseCase extends UseCase<typeof input, typeof output, Services> {\n * static readonly input = input;\n * static readonly output = output;\n *\n * async execute(payload: z.infer<typeof input>): Promise<z.infer<typeof output>> {\n * const user = this.services.ctx.c.get(\"user\");\n * return this.services.postRepo.create({ ...payload, authorId: user.id });\n * }\n * }\n * ```\n */\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport type { Context, Env, MiddlewareHandler } from \"hono\";\n\n// ---------------------------------------------------------------------------\n// Request-scoped context (AsyncLocalStorage backed)\n// ---------------------------------------------------------------------------\n\n/**\n * Per-request context exposed to every service / useCase.\n * The instance is a **stable singleton** but its `c` getter resolves to the\n * Hono `Context` of the currently-handled request via `AsyncLocalStorage`.\n *\n * Outside of a request (cron, manual scripts, tests), wrap your call in\n * {@link withRequestContext} to supply a context, otherwise accessing `c`\n * will throw.\n */\nexport interface RequestContext<TEnv extends Env = Env> {\n /** Hono `Context` of the currently-handled request. */\n readonly c: Context<TEnv>;\n /**\n * Same as `c` but returns `undefined` instead of throwing when called\n * outside a request scope. Useful for opportunistic logging.\n */\n readonly maybeC: Context<TEnv> | undefined;\n}\n\ninterface RequestStore {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n c: Context<any>;\n}\n\nconst als = new AsyncLocalStorage<RequestStore>();\n\nconst requestContextSingleton: RequestContext = Object.freeze({\n get c() {\n const store = als.getStore();\n if (!store) {\n throw new Error(\n \"[services] requestContext.c was accessed outside of a request. \" +\n \"Wrap non-HTTP code paths (cron, triggers, scripts, tests) in \" +\n \"`withRequestContext({ c }, () => ...)` to supply a Hono Context.\",\n );\n }\n return store.c;\n },\n get maybeC() {\n return als.getStore()?.c;\n },\n});\n\n/**\n * Hono middleware installed automatically by `HonoServer` when a `services`\n * container is provided. Populates the AsyncLocalStorage so the built-in\n * `ctx` service resolves to the current request.\n *\n * Exported for advanced cases (custom server / non-Hono adapter); you do not\n * need to call this manually in the standard flow.\n */\nexport function createRequestContextMiddleware(): MiddlewareHandler {\n return async (c, next) => {\n await als.run({ c }, async () => {\n await next();\n });\n };\n}\n\n/**\n * Run `fn` with a synthetic request context — required when invoking\n * services outside an HTTP handler (cron jobs, Firestore triggers, scripts,\n * unit tests). Inside `fn`, `services.ctx.c` resolves to the supplied `c`.\n *\n * @example\n * ```ts\n * // A cron that reuses a useCase\n * export const dailyTask = onSchedule(\"every 24 hours\", async () => {\n * await withRequestContext({ c: fakeContext() }, async () => {\n * await services.createPostUseCase.execute({ ... });\n * });\n * });\n * ```\n */\nexport function withRequestContext<T>(\n ctx: { c: Context },\n fn: () => Promise<T> | T,\n): Promise<T> {\n return als.run({ c: ctx.c }, async () => fn());\n}\n\n// ---------------------------------------------------------------------------\n// createServices\n// ---------------------------------------------------------------------------\n\n/**\n * Reserved service name — the built-in request context. User factories\n * cannot override it.\n */\nconst CTX_KEY = \"ctx\" as const;\n\n/**\n * Helper that derives the public services type from an output map.\n * Each entry in `TMap` is the instance type returned by its provider.\n * The built-in `ctx` is always present.\n */\nexport type ServicesOf<TMap> = { readonly ctx: RequestContext } & {\n readonly [K in keyof TMap]: TMap[K];\n};\n\n/**\n * A single provider entry — either a factory `(deps) => R` or a class\n * `new (deps) => R`. `deps` is the *complete* services proxy\n * (siblings + built-in `ctx`).\n */\nexport type ServiceProvider<TMap, R> =\n | ((deps: ServicesOf<TMap>) => R)\n | (new (deps: ServicesOf<TMap>) => R);\n\n/**\n * A provider map — each value is either a factory or a class constructor.\n */\nexport type ServiceProviderMap<TMap> = {\n [K in keyof TMap]: K extends typeof CTX_KEY\n ? never\n : ServiceProvider<TMap, TMap[K]>;\n};\n\n/**\n * Extract the instance/return type from a single provider value.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type ProviderReturn<P> = P extends new (...args: any) => infer R\n ? R\n : // eslint-disable-next-line @typescript-eslint/no-explicit-any\n P extends (...args: any) => infer R\n ? R\n : never;\n\n/**\n * Compute the output service map from an inferred provider map.\n */\nexport type MapFromProviders<P> = {\n [K in keyof P]: ProviderReturn<P[K]>;\n};\n\n/**\n * Container returned by {@link createServices}. Behaves like a plain object\n * keyed by service name — accessing a key triggers lazy instantiation.\n * Use `type Services = typeof services` to derive the public type.\n */\nexport type ServicesContainer<TMap> = TMap;\n\n/**\n * Build a lazy singleton DI container.\n *\n * @param providers A map of service name → factory function **or** class\n * constructor. The single argument (factory deps / first\n * ctor param) receives a deps proxy typed as the full\n * services map (siblings + the built-in `ctx`).\n *\n * @example Factory form\n * ```ts\n * db: () => getFirestore(),\n * postRepo: ({ db }) => new PostRepo(db),\n * ```\n *\n * @example Class form (zero-boilerplate auto-injection)\n * ```ts\n * class RepositoryService {\n * constructor(private readonly services: Services) {}\n * get posts() { return this.services.db.posts; }\n * }\n *\n * createServices({\n * db: () => getFirestore(), // ← factory\n * repository: RepositoryService, // ← bare class (deps auto-injected)\n * });\n * ```\n */\nexport function createServices<\n P extends Record<\n string,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ((deps: any) => unknown) | (new (deps: any) => unknown)\n >,\n>(providers: P): ServicesContainer<ServicesOf<MapFromProviders<P>>> {\n const cache = new Map<string, unknown>();\n const inProgress: string[] = [];\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const proxy = new Proxy({} as any, {\n get(_target, prop) {\n if (typeof prop !== \"string\") return undefined;\n if (prop === CTX_KEY) return requestContextSingleton;\n\n if (cache.has(prop)) return cache.get(prop);\n\n const provider = (providers as Record<string, unknown>)[prop];\n if (typeof provider !== \"function\") {\n throw new Error(\n `[services] unknown service \"${prop}\". Registered: ${\n [CTX_KEY, ...Object.keys(providers)].join(\", \")\n }`,\n );\n }\n\n if (inProgress.includes(prop)) {\n throw new Error(\n `[services] circular dependency detected: ${[\n ...inProgress,\n prop,\n ].join(\" → \")}`,\n );\n }\n\n inProgress.push(prop);\n try {\n const value = isClassConstructor(provider)\n ? new (provider as new (deps: unknown) => unknown)(proxy)\n : (provider as (deps: unknown) => unknown)(proxy);\n cache.set(prop, value);\n return value;\n } finally {\n inProgress.pop();\n }\n },\n has(_target, prop) {\n if (typeof prop !== \"string\") return false;\n return prop === CTX_KEY || prop in providers;\n },\n ownKeys() {\n return [CTX_KEY, ...Object.keys(providers)];\n },\n getOwnPropertyDescriptor(_t, prop) {\n if (typeof prop !== \"string\") return undefined;\n if (prop === CTX_KEY || prop in providers) {\n return { enumerable: true, configurable: true };\n }\n return undefined;\n },\n });\n\n return proxy as ServicesContainer<ServicesOf<MapFromProviders<P>>>;\n}\n\n/**\n * Detect whether a function value should be invoked with `new` (class\n * constructor) or called directly (plain factory). Relies on the `class`\n * keyword being preserved in `Function.prototype.toString`, which is the\n * case for TypeScript / esbuild output targeting modern JS (ES2015+).\n */\n// eslint-disable-next-line @typescript-eslint/ban-types\nfunction isClassConstructor(fn: Function): boolean {\n return /^class[\\s{]/.test(Function.prototype.toString.call(fn));\n}\n\n/**\n * Opaque container type used by registry / server signatures that don't\n * need to know the concrete services map.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyServicesContainer = ServicesContainer<Record<string, any>>;\n","/**\n * `HonoServer` — high-performance, fully-typed file-based API server for\n * Firebase Cloud Functions v2 (`onRequest`).\n *\n * Designed to:\n * - rely on **prebuild codegen** (`hono:gen` CLI) for static imports → zero\n * runtime filesystem scan, optimal cold-start;\n * - expose handlers receiving a Zod-parsed payload typed end-to-end;\n * - generate the OpenAPI 3.1 spec automatically from the same Zod schemas;\n * - bridge Hono's Web Fetch API to Cloud Functions' Express-style\n * `(req, res)` via `@hono/node-server`'s request listener.\n */\n\nimport { Hono } from \"hono\";\nimport { getRequestListener } from \"@hono/node-server\";\nimport { z } from \"zod\";\nimport type { Env, MiddlewareHandler } from \"hono\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type { HttpsOptions } from \"firebase-functions/v2/https\";\n\nimport type {\n AnyRouteDef,\n ErrorHandler,\n HonoServerOptions,\n HttpMethod,\n InterceptorConfig,\n InterceptorOption,\n Logger,\n PayloadSource,\n RouteInterceptor,\n} from \"./types\";\nimport {\n BadRequestError,\n defaultErrorResponse,\n OutputValidationError,\n} from \"./errors\";\nimport { ValidationError } from \"./types\";\nimport { buildOpenApiDocument, renderDocsHtml } from \"./openapi\";\nimport { isDocsAuthExtension } from \"./docs-auth\";\nimport {\n createRequestContextMiddleware,\n type AnyServicesContainer,\n} from \"./services\";\n\n/**\n * Minimal shape of `firebase-functions/v2/https` `onRequest` so the package\n * stays decoupled from a specific firebase-functions version. We import the\n * real type only when users pass `onRequest` to `toFunction(...)`.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype OnRequestFn = (...args: any[]) => any;\n\n/**\n * Sentinel passed to handlers / interceptors when the server is started\n * without a `services` container. Frozen so accidental writes throw.\n */\nconst EMPTY_SERVICES: AnyServicesContainer = Object.freeze(\n {},\n) as unknown as AnyServicesContainer;\n\nexport class HonoServer<TEnv extends Env = Env> {\n private readonly app: Hono<TEnv>;\n private readonly options: HonoServerOptions<TEnv>;\n private readonly mountedRoutes: AnyRouteDef[];\n private cachedSpec: Record<string, unknown> | null = null;\n\n constructor(options: HonoServerOptions<TEnv>) {\n this.options = options;\n this.app = new Hono<TEnv>();\n this.mountedRoutes = filterRoutes(options.routes, options.api);\n\n // Install the request-context middleware FIRST so the AsyncLocalStorage\n // is populated before any user middleware / handler runs.\n if (options.services) {\n this.app.use(\"*\", createRequestContextMiddleware());\n }\n\n const globalMws = [\n ...(options.middlewares ?? []),\n ...(options.globalMiddlewares ?? []),\n ];\n for (const mw of globalMws) this.app.use(\"*\", mw);\n\n this.mountRoutes();\n this.mountOpenApi();\n\n if (options.notFound) this.app.notFound(options.notFound);\n if (options.onError) this.app.onError(options.onError);\n }\n\n /** Underlying Hono instance — useful for advanced composition / tests. */\n get hono(): Hono<TEnv> {\n return this.app;\n }\n\n /** Raw `(req, res)` handler suitable for `onRequest()` / `http.createServer`. */\n get nodeHandler(): (req: IncomingMessage, res: ServerResponse) => void {\n return getRequestListener(this.app.fetch, {\n overrideGlobalObjects: false,\n });\n }\n\n /**\n * Wrap the server as a Cloud Functions v2 HTTP function.\n *\n * @param onRequest The `onRequest` factory imported from\n * `firebase-functions/v2/https` (or `firebase-functions/https`).\n * @param httpsOptions Options forwarded as the first argument to\n * `onRequest()` (region, memory, invoker, etc.).\n */\n toFunction(onRequest: OnRequestFn, httpsOptions?: HttpsOptions) {\n const handler = this.nodeHandler;\n if (httpsOptions) {\n return onRequest(httpsOptions, handler);\n }\n return onRequest(handler);\n }\n\n /** Generate (and cache) the OpenAPI 3.1 spec for the mounted routes. */\n buildOpenApiSpec(): Record<string, unknown> {\n if (this.cachedSpec) return this.cachedSpec;\n if (!this.options.openapi) {\n throw new Error(\"[HonoServer] openapi config not set\");\n }\n this.cachedSpec = buildOpenApiDocument(\n this.mountedRoutes,\n this.options.basePath ?? \"\",\n this.options.openapi,\n interceptorConfig(this.options.interceptor as InterceptorOption | undefined),\n );\n return this.cachedSpec;\n }\n\n // ── Internals ─────────────────────────────────────────────────────────\n\n private mountRoutes(): void {\n const basePath = this.options.basePath ?? \"\";\n const validateOutput = this.options.validateOutput ?? false;\n const verbose = this.options.verbose ?? false;\n\n for (const route of this.mountedRoutes) {\n if (!route.path) {\n throw new Error(\n `[HonoServer] route \"${route.method.toUpperCase()} (no path)\" — missing \\`path\\`. ` +\n \"Run the codegen so the path is derived from the file location, or set it explicitly.\",\n );\n }\n\n const fullPath = joinPath(basePath, route.path);\n const middlewares = route.middlewares ?? [];\n const source: PayloadSource =\n route.source ?? (route.method === \"get\" ? \"query\" : \"json\");\n\n const handler = makeRouteHandler(\n route,\n source,\n validateOutput,\n interceptorFn(this.options.interceptor as InterceptorOption | undefined),\n this.options.services,\n this.options.errorHandler as ErrorHandler | undefined,\n this.options.logger as Logger | undefined,\n );\n const httpMethod = route.method.toUpperCase() as\n | \"GET\"\n | \"POST\"\n | \"PUT\"\n | \"PATCH\"\n | \"DELETE\";\n // `app.on(method, path, handlers[])` accepts a variadic array of\n // handlers/middlewares — the typed `.get/.post/...` overloads don't\n // accept a spread of generic `MiddlewareHandler[]`.\n this.app.on(\n httpMethod,\n [fullPath],\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ...([...middlewares, handler] as any[]),\n );\n\n if (verbose) {\n // eslint-disable-next-line no-console\n console.log(\n `[HonoServer] ${route.method.toUpperCase().padEnd(6)} ${fullPath}`,\n );\n }\n }\n }\n\n private mountOpenApi(): void {\n const cfg = this.options.openapi;\n if (!cfg) return;\n const specPath = cfg.path ?? \"/openapi.json\";\n const docsPath = cfg.docsPath === undefined ? \"/docs\" : cfg.docsPath;\n const fullSpecPath = joinPath(this.options.basePath ?? \"\", specPath);\n const fullDocsPath =\n docsPath === false ? null : joinPath(this.options.basePath ?? \"\", docsPath);\n\n // Auth guards applied ONLY to the spec + docs endpoints (not API routes).\n // A DocsAuthExtension (e.g. firebaseDocsAuth) also contributes auxiliary\n // routes (login page / session / logout) mounted next to the docs.\n let guards: MiddlewareHandler[];\n if (isDocsAuthExtension(cfg.docsAuth)) {\n const ext = cfg.docsAuth;\n guards = [ext.middleware];\n // Mount the aux routes (unguarded) as siblings of the docs/spec page so\n // bare-name relative links/redirects stay correct behind any prefix.\n const authDir = dirOf(fullDocsPath ?? fullSpecPath);\n for (const route of ext.routes) {\n this.app.on(\n route.method,\n [joinPath(authDir, route.name)],\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n route.handler as any,\n );\n }\n } else {\n guards = cfg.docsAuth\n ? Array.isArray(cfg.docsAuth)\n ? cfg.docsAuth\n : [cfg.docsAuth]\n : [];\n }\n\n this.app.on(\n \"GET\",\n [fullSpecPath],\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ...([...guards, (c: any) => c.json(this.buildOpenApiSpec())] as any[]),\n );\n\n if (fullDocsPath) {\n // Resolve the spec URL relative to the docs page so it works whether the\n // server is mounted at `/`, behind a Firebase Functions prefix\n // (`/<project>/<region>/<funcName>/...`), or behind any reverse proxy.\n const relativeSpecUrl = relativeUrlFromTo(fullDocsPath, fullSpecPath);\n this.app.on(\n \"GET\",\n [fullDocsPath],\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ...([\n ...guards,\n (c: any) => c.html(renderDocsHtml(relativeSpecUrl, cfg.info.title)),\n ] as any[]),\n );\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Type guard distinguishing a structured interceptor from a bare function. */\nfunction isInterceptorConfig(\n i: InterceptorOption | undefined,\n): i is InterceptorConfig {\n return typeof i === \"object\" && i !== null && typeof i.handler === \"function\";\n}\n\n/** Extract the runtime interceptor function from either form. */\nfunction interceptorFn(\n i: InterceptorOption | undefined,\n): RouteInterceptor | undefined {\n if (!i) return undefined;\n return isInterceptorConfig(i) ? i.handler : i;\n}\n\n/** Extract the OpenAPI metadata (output/errors) from the structured form. */\nfunction interceptorConfig(\n i: InterceptorOption | undefined,\n): InterceptorConfig | undefined {\n return isInterceptorConfig(i) ? i : undefined;\n}\n\nfunction filterRoutes(\n routes: AnyRouteDef[],\n api: string | undefined,\n): AnyRouteDef[] {\n if (!api) return routes.slice();\n return routes.filter((r) =>\n Array.isArray(r.api) ? r.api.includes(api) : r.api === api,\n );\n}\n\nfunction joinPath(base: string, path: string): string {\n const left = base.endsWith(\"/\") ? base.slice(0, -1) : base;\n const right = path.startsWith(\"/\") ? path : `/${path}`;\n const merged = `${left}${right}`;\n return merged === \"\" ? \"/\" : merged;\n}\n\n/** Directory of an absolute pathname, e.g. `/v1/docs` → `/v1`, `/docs` → ``. */\nfunction dirOf(path: string): string {\n const idx = path.lastIndexOf(\"/\");\n return idx <= 0 ? \"\" : path.slice(0, idx);\n}\n\n/**\n * Compute a URL relative to `from` that points to `to`, both being absolute\n * pathnames (e.g. `/v1/docs` → `/v1/openapi.json` becomes `openapi.json`).\n * Lets the OpenAPI UI fetch the spec without knowing the upstream prefix\n * added by Firebase Functions / reverse proxies.\n */\nfunction relativeUrlFromTo(from: string, to: string): string {\n const fromSegs = from.split(\"/\").filter(Boolean);\n const toSegs = to.split(\"/\").filter(Boolean);\n // Drop the docs page filename so we resolve relative to its directory.\n fromSegs.pop();\n let common = 0;\n while (\n common < fromSegs.length &&\n common < toSegs.length &&\n fromSegs[common] === toSegs[common]\n ) {\n common++;\n }\n const ups = fromSegs.length - common;\n const rel = [\n ...Array(ups).fill(\"..\"),\n ...toSegs.slice(common),\n ].join(\"/\");\n return rel || \"./\";\n}\n\n/**\n * Build the actual Hono handler with input validation, output validation\n * (optional), and error normalisation.\n */\nfunction makeRouteHandler(\n route: AnyRouteDef,\n source: PayloadSource,\n validateOutput: boolean,\n interceptor: RouteInterceptor | undefined,\n services: AnyServicesContainer | undefined,\n errorHandler: ErrorHandler | undefined,\n logger: Logger | undefined,\n) {\n const inputSchema = route.input as z.ZodTypeAny | undefined;\n const outputSchema = route.output as z.ZodTypeAny | undefined;\n const status = route.status ?? 200;\n // Empty fallback so handlers/interceptors always see a `services` field\n // even when the server was started without a container.\n const servicesArg = (services ?? (EMPTY_SERVICES as AnyServicesContainer));\n\n return async (\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n c: any,\n ): Promise<Response> => {\n // Apply the injected ErrorHandler, falling back to the built-in envelope.\n // Returns a Response when handled, or null to let the error propagate.\n const applyErrorHandler = async (err: unknown): Promise<Response | null> => {\n if (errorHandler) {\n const handled = await errorHandler.handle({\n error: err,\n c,\n route,\n services: servicesArg,\n logger,\n });\n if (handled) return handled;\n }\n return defaultErrorResponse(c, err);\n };\n\n // `next()` runs validation + handler. Any Zod failure throws\n // `ValidationError` so the interceptor (or default catcher) can shape it.\n const callNext = async (): Promise<unknown> => {\n let payload: unknown = undefined;\n\n if (inputSchema) {\n let raw: unknown;\n try {\n raw = await readPayload(c, source, route.method);\n } catch (err) {\n // Body parse failure → wrap as a generic Error so the interceptor\n // can decide. Use a 400-shaped Error subclass.\n throw new BadRequestError(\n err instanceof Error ? err.message : String(err),\n );\n }\n const parsed = inputSchema.safeParse(raw);\n if (!parsed.success) {\n throw new ValidationError(parsed.error, source);\n }\n payload = parsed.data;\n }\n\n const result = await (route.handler as (ctx: {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n input: any;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n c: any;\n services: AnyServicesContainer;\n errorHandler: ErrorHandler | undefined;\n logger: Logger | undefined;\n }) => unknown)({\n input: payload,\n c,\n services: servicesArg,\n errorHandler,\n logger,\n });\n\n if (validateOutput && outputSchema && !(result instanceof Response)) {\n const checked = outputSchema.safeParse(result);\n if (!checked.success) {\n throw new OutputValidationError(checked.error);\n }\n return checked.data;\n }\n return result;\n };\n\n let result: unknown;\n if (interceptor) {\n // Interceptor owns the response shape — including validation errors.\n // If it rethrows, the injected ErrorHandler / default catcher applies.\n try {\n result = await interceptor({\n next: callNext,\n route,\n c,\n services: servicesArg,\n errorHandler,\n logger,\n });\n } catch (err) {\n const handled = await applyErrorHandler(err);\n if (handled) return handled;\n throw err;\n }\n } else {\n // Default behaviour — ErrorHandler first, then the built-in\n // ValidationError envelope, else bubble to onError / Hono.\n try {\n result = await callNext();\n } catch (err) {\n const handled = await applyErrorHandler(err);\n if (handled) return handled;\n throw err;\n }\n }\n\n if (result instanceof Response) return result;\n return c.json(result, status);\n };\n}\n\nasync function readPayload(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n c: any,\n source: PayloadSource,\n method: HttpMethod,\n): Promise<unknown> {\n switch (source) {\n case \"json\": {\n if (method === \"get\") return c.req.query();\n const text = await c.req.text();\n if (!text) return {};\n try {\n return JSON.parse(text);\n } catch (err) {\n throw new Error(\n `Invalid JSON body: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n case \"query\":\n return c.req.query();\n case \"form\": {\n const form = await c.req.parseBody();\n return form;\n }\n case \"param\":\n return c.req.param();\n default:\n return {};\n }\n}\n","/**\n * useCase ⇆ route bridge.\n *\n * A useCase owns its Zod `input` / `output` schemas (declared as `static`\n * members) and the business logic in {@link UseCase.execute}. Routes never\n * re-declare those schemas: they wire a useCase into an HTTP endpoint with the\n * one-liner {@link ApiRegistry.useCaseRoute} (or the standalone\n * {@link useCaseRoute}), keeping `routes.ts` flat and readable while the\n * types can never drift from the schemas.\n *\n * @example\n * ```ts\n * // useCase.ts — single source of truth for the I/O shape\n * import { z } from \"zod\";\n * import { UseCase } from \"@lpdjs/firestore-repo-service/servers/hono\";\n * import type { Services } from \"../../../../services.js\";\n *\n * const input = z.object({ example: z.string() });\n * const output = z.object({ id: z.string(), warning: z.string().nullable() });\n *\n * export class CreatePostUseCase extends UseCase<typeof input, typeof output, Services> {\n * static readonly input = input;\n * static readonly output = output;\n *\n * async execute(payload: z.infer<typeof input>): Promise<z.infer<typeof output>> {\n * return { id: payload.example, warning: null };\n * }\n * }\n *\n * // routes.ts — one line per route, `api` stays typed\n * export default defineRoutes([\n * useCaseRoute(CreatePostUseCase, { api: \"v1\", method: \"post\", tags: [\"posts\"] }),\n * ]);\n * ```\n */\n\nimport type { z } from \"zod\";\n\nimport type {\n HttpMethod,\n PayloadSource,\n RouteDef,\n} from \"./types\";\nimport type { AnyServicesContainer } from \"./services\";\n\n/**\n * Base class for every useCase — pure business logic, no HTTP awareness.\n * The shared {@link AnyServicesContainer} is injected via the constructor; the\n * `input` / `output` Zod schemas are declared as `static` members on the\n * concrete subclass (see {@link UseCaseClass}).\n *\n * @typeParam TInput Zod schema of the validated request payload.\n * @typeParam TOutput Zod schema of the success response.\n * @typeParam TServices Concrete services container injected into the useCase.\n */\nexport abstract class UseCase<\n TInput extends z.ZodTypeAny = z.ZodTypeAny,\n TOutput extends z.ZodTypeAny = z.ZodTypeAny,\n TServices extends AnyServicesContainer = AnyServicesContainer,\n> {\n constructor(protected readonly services: TServices) {}\n\n /** Run the business logic. Input is already validated against the schema. */\n abstract execute(input: z.infer<TInput>): Promise<z.infer<TOutput>>;\n}\n\n/**\n * Structural type of a concrete {@link UseCase} subclass — i.e. a constructor\n * that also exposes the `static input` / `static output` schemas. Consumed by\n * {@link useCaseRoute} to derive the route's Zod schemas and the handler types.\n */\nexport interface UseCaseClass<\n TInput extends z.ZodTypeAny,\n TOutput extends z.ZodTypeAny,\n TServices extends AnyServicesContainer = AnyServicesContainer,\n> {\n readonly input: TInput;\n readonly output: TOutput;\n new (services: TServices): UseCase<TInput, TOutput, TServices>;\n}\n\n/**\n * HTTP metadata for {@link useCaseRoute} — everything a route needs **except**\n * the input/output schemas and the handler, which are derived from the useCase.\n */\nexport interface UseCaseRouteMeta<TApi extends string = string> {\n /** API tag the route is mounted under. */\n api: TApi;\n /** HTTP method. */\n method: HttpMethod;\n /** URL path. Defaults to the codegen-derived path when omitted. */\n path?: string;\n /** Where the payload comes from. Defaults per method (see {@link RouteDef}). */\n source?: PayloadSource;\n /** Success status code. Default: 200. */\n status?: number;\n\n // ── OpenAPI metadata ─────────────────────────────────────────────────\n summary?: string;\n description?: string;\n tags?: string[];\n deprecated?: boolean;\n security?: Array<Record<string, string[]>>;\n}\n\n/**\n * Build a {@link RouteDef} from a useCase class and HTTP metadata. The route's\n * `input` / `output` schemas are read from the useCase's `static` members and\n * the handler instantiates the useCase with the request `services` and runs\n * {@link UseCase.execute}.\n *\n * Prefer the registry-bound `apis.useCaseRoute` (returned by\n * `createApiRegistry`) so that `meta.api` is narrowed to the registered tags.\n */\nexport function useCaseRoute<\n TInput extends z.ZodTypeAny,\n TOutput extends z.ZodTypeAny,\n TServices extends AnyServicesContainer,\n TApi extends string = string,\n>(\n useCaseClass: UseCaseClass<TInput, TOutput, TServices>,\n meta: UseCaseRouteMeta<TApi>,\n): RouteDef<TInput, TOutput> & { api: TApi } {\n return {\n ...meta,\n input: useCaseClass.input,\n output: useCaseClass.output,\n handler: ({ input, services }) =>\n new useCaseClass(services as TServices).execute(\n input as z.infer<TInput>,\n ),\n } as RouteDef<TInput, TOutput> & { api: TApi };\n}\n","/**\n * Typed multi-API registry.\n *\n * Lets you declare every API tag (= every Cloud Function) in **one place**,\n * with full TypeScript safety: the `api` field of {@link defineRoute} is\n * narrowed to the registered tags, and {@link toFunctions} returns one\n * `onRequest` Cloud Function per tag, named after its key.\n *\n * @example\n * ```ts\n * // apis.ts\n * import { createApiRegistry } from \"@lpdjs/firestore-repo-service/servers/hono\";\n * import { enrichUser } from \"./middlewares/enrich-user.js\";\n *\n * export const apis = createApiRegistry({\n * v1: {\n * basePath: \"/v1\",\n * middlewares: [enrichUser],\n * openapi: { info: { title: \"Public API\", version: \"1.0.0\" } },\n * },\n * webhooks: {\n * basePath: \"/hooks\",\n * openapi: { info: { title: \"Webhooks\", version: \"1.0.0\" } },\n * },\n * });\n *\n * // Use in routes — `api` is now typed \"v1\" | \"webhooks\".\n * export const defineRoute = apis.defineRoute;\n * export const useCaseRoute = apis.useCaseRoute;\n *\n * // index.ts (Cloud Functions entrypoint)\n * import { onRequest } from \"firebase-functions/v2/https\";\n * import { apis } from \"./apis.js\";\n * import { routes } from \"./domains/__generated__/routes.js\";\n *\n * export const { v1, webhooks } = apis.toFunctions(routes, onRequest, {\n * defaults: { region: \"us-central1\", invoker: \"public\" },\n * per: { v1: { memory: \"512MiB\" } },\n * });\n * // → URLs: https://<region>-<project>.cloudfunctions.net/v1/posts\n * // https://<region>-<project>.cloudfunctions.net/webhooks/...\n * ```\n */\n\nimport type { Env } from \"hono\";\nimport type { z } from \"zod\";\nimport type { HttpsOptions } from \"firebase-functions/v2/https\";\n\nimport type {\n AnyRouteDef,\n ErrorHandler,\n HonoServerOptions,\n Logger,\n RouteDef,\n RouteHandler,\n} from \"./types\";\nimport { HonoServer } from \"./server\";\nimport type { AnyServicesContainer } from \"./services\";\nimport {\n useCaseRoute as buildUseCaseRoute,\n type UseCaseClass,\n type UseCaseRouteMeta,\n} from \"./usecase\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype OnRequestFn = (...args: any[]) => any;\n\n/**\n * Per-API configuration. Same shape as {@link HonoServerOptions} minus the\n * `routes` (resolved by the registry), `api` (the registry key) and\n * `services` (set globally on the registry — see\n * {@link createApiRegistry}).\n */\nexport type ApiConfig<TEnv extends Env = Env> = Omit<\n HonoServerOptions<TEnv>,\n \"routes\" | \"api\" | \"services\"\n>;\n\n/** Map of API tag → its config. */\nexport type ApiConfigMap = Record<string, ApiConfig>;\n\n/**\n * Per-key excess-property guard.\n *\n * `createApiRegistry` infers its config map generically (`<const TMap …>`),\n * which normally **defeats** TypeScript's excess-property checking — a typo\n * like `openApi` or `middleware` (instead of `openapi` / `middlewares`) would\n * pass silently and the option would be ignored at runtime.\n *\n * This intersects the concrete {@link ApiConfig} shape (so every known key\n * keeps its real type **and JSDoc**, including nested objects such as\n * `openapi`) with a `never` mapping for any key absent from `ApiConfig` — which\n * makes typos a compile error, matching the strictness already enforced on the\n * CRUD server's `openapi`.\n *\n * @internal\n */\nexport type StrictApiConfig<T, TEnv extends Env = Env> = ApiConfig<TEnv> & {\n [K in keyof T as K extends keyof ApiConfig<TEnv> ? never : K]: never;\n};\n\n/**\n * Options accepted by {@link createApiRegistry} alongside the per-API\n * configs.\n */\nexport interface ApiRegistryOptions<\n TServices extends AnyServicesContainer = AnyServicesContainer,\n> {\n /**\n * Global DI services container shared across every API. See\n * {@link ServicesContainer} / `createServices`. When provided, every\n * `HonoServer` mounted by the registry receives it and exposes\n * `services` to every handler / interceptor.\n */\n services?: TServices;\n\n /**\n * Cross-cutting {@link ErrorHandler} shared across every API — injected into\n * every handler / interceptor context and applied automatically on any\n * uncaught error. A per-API `errorHandler` (on the config) overrides it.\n */\n errorHandler?: ErrorHandler;\n\n /**\n * Structured {@link Logger} shared across every API — injected into every\n * handler / interceptor / error-handler context. A per-API `logger` (on the\n * config) overrides it.\n */\n logger?: Logger;\n}\n\nexport interface ApiRegistry<\n TMap extends ApiConfigMap,\n TServices extends AnyServicesContainer = AnyServicesContainer,\n> {\n /** The registered configs (read-only). */\n readonly configs: TMap;\n\n /**\n * Typed `defineRoute` — the `api` field is constrained to `keyof TMap`,\n * and `handler({ services })` is typed with the concrete services\n * container passed to {@link createApiRegistry}.\n */\n defineRoute<\n TIn extends z.ZodTypeAny | undefined = undefined,\n TOut extends z.ZodTypeAny | undefined = undefined,\n >(\n def: Omit<RouteDef<TIn, TOut>, \"api\" | \"handler\"> & {\n api: keyof TMap & string;\n handler: RouteHandler<\n TIn extends z.ZodTypeAny ? z.infer<TIn> : void,\n TOut extends z.ZodTypeAny ? z.infer<TOut> : unknown,\n Env,\n TServices\n >;\n },\n ): RouteDef<TIn, TOut> & { api: keyof TMap & string };\n\n /**\n * Typed `useCaseRoute` — wires a {@link UseCase} class into a route in one\n * line. The route's `input` / `output` schemas are read from the useCase's\n * `static` members and the handler instantiates it with the request\n * `services`. The `api` field is constrained to `keyof TMap`.\n *\n * @example\n * export default defineRoutes([\n * useCaseRoute(CreatePostUseCase, { api: \"v1\", method: \"post\", tags: [\"posts\"] }),\n * ]);\n */\n useCaseRoute<TIn extends z.ZodTypeAny, TOut extends z.ZodTypeAny>(\n useCaseClass: UseCaseClass<TIn, TOut, TServices>,\n meta: UseCaseRouteMeta<keyof TMap & string>,\n ): RouteDef<TIn, TOut> & { api: keyof TMap & string };\n\n /**\n * Build one Cloud Function per registered API and return them as a map\n * keyed by API tag — spread it directly into your `index.ts` exports.\n *\n * @param routes Pre-resolved route registry (typically the codegen output).\n * @param onRequest The `onRequest` factory imported from\n * `firebase-functions/v2/https`.\n * @param opts Optional defaults and per-API overrides for `httpsOptions`.\n */\n toFunctions(\n routes: AnyRouteDef[],\n onRequest: OnRequestFn,\n opts?: {\n /** Shared `HttpsOptions` applied to every generated function. */\n defaults?: HttpsOptions;\n /** Per-API overrides — merged on top of {@link defaults}. */\n per?: Partial<Record<keyof TMap & string, HttpsOptions>>;\n },\n ): { [K in keyof TMap & string]: ReturnType<OnRequestFn> };\n\n /** Build the underlying {@link HonoServer} for a given API (escape hatch). */\n serverFor<K extends keyof TMap & string>(\n api: K,\n routes: AnyRouteDef[],\n ): HonoServer;\n}\n\n/**\n * Factory — declare every API tag once and get back a typed `defineRoute`\n * + `toFunctions`. See the file-level example.\n *\n * Each per-API config is strictly checked against {@link ApiConfig}: unknown\n * keys (typos like `openApi` / `middleware`) are rejected at compile time,\n * including nested objects such as `openapi` — mirroring the CRUD server.\n *\n * @param configs API-tag → per-API config (see {@link ApiConfig}).\n * @param options Cross-API options (shared services container, etc).\n */\nexport function createApiRegistry<\n const TMap extends ApiConfigMap,\n TServices extends AnyServicesContainer = AnyServicesContainer,\n>(\n configs: { [K in keyof TMap]: StrictApiConfig<TMap[K]> },\n options?: ApiRegistryOptions<TServices>,\n): ApiRegistry<TMap, TServices> {\n const sharedServices = options?.services;\n const sharedErrorHandler = options?.errorHandler;\n const sharedLogger = options?.logger;\n\n return {\n configs: configs as unknown as TMap,\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n defineRoute(def: any) {\n return def;\n },\n\n useCaseRoute(useCaseClass, meta) {\n return buildUseCaseRoute(useCaseClass, meta);\n },\n\n serverFor(api, routes) {\n const cfg = configs[api];\n if (!cfg) {\n throw new Error(\n `[ApiRegistry] unknown api \"${api}\". Registered: ${Object.keys(configs).join(\", \")}`,\n );\n }\n return new HonoServer({\n ...cfg,\n api,\n routes,\n services: sharedServices,\n // Per-API errorHandler / logger (on the config) win over the shared ones.\n errorHandler: (cfg as ApiConfig).errorHandler ?? sharedErrorHandler,\n logger: (cfg as ApiConfig).logger ?? sharedLogger,\n });\n },\n\n toFunctions(routes, onRequest, opts) {\n const out = {} as { [K in keyof TMap & string]: ReturnType<OnRequestFn> };\n for (const api of Object.keys(configs) as Array<keyof TMap & string>) {\n const httpsOpts = {\n ...(opts?.defaults ?? {}),\n ...(opts?.per?.[api] ?? {}),\n };\n const server = new HonoServer({\n ...configs[api],\n api,\n routes,\n services: sharedServices,\n errorHandler:\n (configs[api] as ApiConfig).errorHandler ?? sharedErrorHandler,\n logger: (configs[api] as ApiConfig).logger ?? sharedLogger,\n });\n out[api] = Object.keys(httpsOpts).length\n ? server.toFunction(onRequest, httpsOpts)\n : server.toFunction(onRequest);\n }\n return out;\n },\n };\n}\n","/**\n * Build a deep link to the GCP Cloud Logging \"Logs Explorer\", pre-filtered on a\n * correlation id, so a developer can jump straight from an HTTP error response\n * to the matching structured log.\n *\n * Designed as a dev ergonomic: keep it **disabled in production** (the link is\n * meant for engineers, not end users) and enable it locally / in staging. The\n * `errorId` returned by {@link BaseLogger.error} and carried by your `AppError`\n * is the same value used both in the response body and in the link's query.\n *\n * @example\n * ```ts\n * // package-level helper\n * const url = gcpLogsUrl(error.errorId, {\n * enabled: process.env.NODE_ENV !== \"production\",\n * projectId: \"my-gcp-project\", // or omit to read it from the environment\n * });\n *\n * // inside BaseErrorHandler.mapError\n * return c.json({ error: msg, errorId, ...(url ? { logsUrl: url } : {}) }, 412);\n * ```\n */\n\n/** Options controlling {@link gcpLogsUrl}. */\nexport interface GcpLogsLinkOptions {\n /**\n * Master switch — when falsy, {@link gcpLogsUrl} returns `undefined` and no\n * link is produced. Default: `false` (opt-in). Wire it to e.g.\n * `process.env.NODE_ENV !== \"production\"`.\n */\n enabled?: boolean;\n /**\n * GCP project id. Defaults to the first non-empty of\n * `GOOGLE_CLOUD_PROJECT`, `GCLOUD_PROJECT`, `GCP_PROJECT` (all auto-set on\n * Cloud Functions / Cloud Run). When none is resolvable, no link is built.\n */\n projectId?: string;\n /**\n * Structured-log field that carries the correlation id. Must match what your\n * logger writes (the package's {@link BaseLogger} writes `errorId`).\n * Default: `\"errorId\"`.\n */\n field?: string;\n /**\n * Optional lookback window appended to the query as an ISO-8601 duration\n * (e.g. `\"PT1H\"`, `\"PT30M\"`, `\"P1D\"`). Omit to let the Logs Explorer use its\n * default range.\n */\n duration?: string;\n}\n\n/**\n * Resolve the GCP project id from an explicit value, falling back to the\n * standard environment variables. Returns `undefined` when none is set.\n */\nexport function resolveGcpProjectId(explicit?: string): string | undefined {\n return (\n explicit ||\n process.env[\"GOOGLE_CLOUD_PROJECT\"] ||\n process.env[\"GCLOUD_PROJECT\"] ||\n process.env[\"GCP_PROJECT\"] ||\n undefined\n );\n}\n\n/**\n * Build the Logs Explorer URL filtered on `<field>=\"<errorId>\"`, or return\n * `undefined` when the feature is disabled, the `errorId` is missing, or no\n * project id can be resolved (so callers can spread it safely).\n */\nexport function gcpLogsUrl(\n errorId: string | undefined,\n options: GcpLogsLinkOptions = {},\n): string | undefined {\n if (!options.enabled || !errorId) return undefined;\n\n const projectId = resolveGcpProjectId(options.projectId);\n if (!projectId) return undefined;\n\n const field = options.field ?? \"errorId\";\n const query = `jsonPayload.${field}=\"${errorId}\"`;\n\n const params = [`query=${encodeURIComponent(query)}`];\n if (options.duration) {\n params.push(`duration=${encodeURIComponent(options.duration)}`);\n }\n\n return `https://console.cloud.google.com/logs/query;${params.join(\n \";\",\n )}?project=${encodeURIComponent(projectId)}`;\n}\n","/**\n * `BaseErrorHandler` — the package's ready-to-use {@link ErrorHandler}.\n *\n * Use it as-is for an API that only needs the built-in error mapping\n * (`ValidationError` / `BadRequestError` / `OutputValidationError`), or extend\n * it and override the two hooks to plug your own domain errors + logger:\n *\n * - {@link BaseErrorHandler.mapError} — map your `AppError` → `Response`\n * (return `null` to defer to the built-in mapping);\n * - {@link BaseErrorHandler.logError} — log via your `AppLogger`.\n *\n * Pass an instance **per API** (`ApiConfig.errorHandler`) so different APIs can\n * use different strategies (e.g. one with user-facing localized errors, one\n * with just the defaults).\n *\n * @example\n * ```ts\n * class AppErrorHandler extends BaseErrorHandler {\n * protected mapError({ error, c }) {\n * if (error instanceof AppError) {\n * return c.json({ error: error.message, errorId: error.errorId }, error.statusCode);\n * }\n * return null; // → built-in mapping\n * }\n * protected logError({ error }) {\n * AppLogger.err(error);\n * }\n * }\n *\n * // apis.ts\n * v1: { ..., errorHandler: new AppErrorHandler() }, // user-facing API\n * v2: { ..., errorHandler: new BaseErrorHandler() }, // defaults only\n * ```\n */\n\nimport type { Env } from \"hono\";\nimport { defaultErrorResponse } from \"./errors\";\nimport {\n gcpLogsUrl,\n type GcpLogsLinkOptions,\n} from \"./gcp-logs\";\nimport type { AnyServicesContainer } from \"./services\";\nimport type { ErrorHandler, ErrorHandlerContext } from \"./types\";\n\n/** Construction options shared by every {@link BaseErrorHandler}. */\nexport interface BaseErrorHandlerOptions {\n /**\n * Enable building a GCP Logs Explorer deep link from an error's correlation\n * id (see {@link BaseErrorHandler.gcpLogsUrl}). Disabled by default — turn it\n * on in dev/staging to let engineers jump from a response to its log.\n */\n gcpLogs?: GcpLogsLinkOptions;\n}\n\nexport class BaseErrorHandler<\n TEnv extends Env = Env,\n TServices extends AnyServicesContainer = AnyServicesContainer,\n> implements ErrorHandler<TEnv, TServices>\n{\n constructor(protected readonly options: BaseErrorHandlerOptions = {}) {}\n\n /**\n * Build a GCP Logs Explorer link for `errorId`, or `undefined` when the\n * `gcpLogs` option is disabled / unresolved. Spread it into a mapped\n * response to give developers a one-click jump to the matching log:\n *\n * ```ts\n * const logsUrl = this.gcpLogsUrl(error.errorId);\n * return c.json({ error, errorId, ...(logsUrl ? { logsUrl } : {}) }, status);\n * ```\n */\n protected gcpLogsUrl(errorId?: string): string | undefined {\n return gcpLogsUrl(errorId, this.options.gcpLogs);\n }\n\n /**\n * Orchestration — not meant to be overridden. Tries the user mapping first,\n * logs it when matched, then falls back to the built-in mapping.\n */\n async handle(\n ctx: ErrorHandlerContext<TEnv, TServices>,\n ): Promise<Response | null> {\n const mapped = await this.mapError(ctx);\n if (mapped) {\n this.logError(ctx, mapped);\n return mapped;\n }\n return this.handleBuiltin(ctx);\n }\n\n /**\n * Map a domain error (your `AppError`) to a `Response`. Return `null` to let\n * {@link BaseErrorHandler.handleBuiltin} handle it. Default: `null`.\n */\n protected mapError(\n _ctx: ErrorHandlerContext<TEnv, TServices>,\n ): Response | null | Promise<Response | null> {\n return null;\n }\n\n /**\n * Log a mapped error (e.g. via your `AppLogger`). Called only when\n * {@link BaseErrorHandler.mapError} produced a response. Default: no-op.\n */\n protected logError(\n _ctx: ErrorHandlerContext<TEnv, TServices>,\n _response: Response,\n ): void {}\n\n /**\n * Built-in mapping of the package's own errors (`ValidationError`,\n * `BadRequestError`, `OutputValidationError`). Returns `null` for unknown\n * errors so they bubble to `onError` / Hono.\n */\n protected handleBuiltin(\n ctx: ErrorHandlerContext<TEnv, TServices>,\n ): Response | null {\n return defaultErrorResponse(ctx.c, ctx.error);\n }\n}\n","/**\n * `BaseLogger` — the package's ready-to-use {@link Logger}.\n *\n * Use it as-is (writes structured JSON to `console`), or extend it and override\n * the single {@link BaseLogger.write} hook to route to your sink (Firebase\n * `logger`, pino, Datadog, …). Each level funnels through `write`, so one\n * override covers them all.\n *\n * Pass an instance **per API** (`ApiConfig.logger`) or once via the registry\n * (`createApiRegistry({ services, logger })`); it is then injected into every\n * handler / interceptor / error-handler context as `logger`.\n *\n * @example\n * ```ts\n * import { logger as fnLogger } from \"firebase-functions/v2\";\n * class AppLogger extends BaseLogger {\n * protected write(severity, payload) {\n * fnLogger.write({ severity, ...payload });\n * }\n * }\n * ```\n */\n\nimport type { Logger } from \"./types\";\n\nexport type LogSeverity = \"DEBUG\" | \"INFO\" | \"WARNING\" | \"ERROR\";\n\nexport class BaseLogger implements Logger {\n info(message: string, meta?: unknown): void {\n this.write(\"INFO\", this.payload(message, meta));\n }\n\n warn(message: string, meta?: unknown): void {\n this.write(\"WARNING\", this.payload(message, meta));\n }\n\n debug(message: string, meta?: unknown): void {\n this.write(\"DEBUG\", this.payload(message, meta));\n }\n\n /**\n * Log an error and return a correlation id. If the error already carries an\n * `errorId` it is reused, otherwise a fresh one is generated.\n */\n error(error: unknown, meta?: unknown): string {\n const errorId = BaseLogger.errorId(error);\n this.write(\"ERROR\", {\n errorId,\n message: error instanceof Error ? error.message : String(error),\n ...(error instanceof Error && error.stack ? { stack: error.stack } : {}),\n ...(meta !== undefined ? { meta } : {}),\n });\n return errorId;\n }\n\n /** Build a structured payload from a message + optional metadata. */\n protected payload(message: string, meta?: unknown): Record<string, unknown> {\n return meta !== undefined ? { message, meta } : { message };\n }\n\n /**\n * Sink hook — override to route logs elsewhere. Default: structured\n * `console` write keyed by severity.\n */\n protected write(severity: LogSeverity, payload: Record<string, unknown>): void {\n const line = { severity, ...payload };\n // eslint-disable-next-line no-console\n if (severity === \"ERROR\") console.error(line);\n // eslint-disable-next-line no-console\n else if (severity === \"WARNING\") console.warn(line);\n // eslint-disable-next-line no-console\n else console.log(line);\n }\n\n /** Reuse an error's `errorId` when present, else generate one. */\n protected static errorId(error: unknown): string {\n if (\n error &&\n typeof error === \"object\" &&\n \"errorId\" in error &&\n typeof (error as { errorId: unknown }).errorId === \"string\"\n ) {\n return (error as { errorId: string }).errorId;\n }\n return Math.random().toString(36).slice(2, 12);\n }\n}\n","/**\n * URL path inference from filesystem layout.\n *\n * Convention: every `routes.ts` file under the configured root contributes\n * one route. The URL path is derived from its directory chain, optionally\n * skipping segments such as `useCases` so that\n *\n * domains/activities/useCases/createOrUpdateCustom/routes.ts\n *\n * becomes\n *\n * /activities/createOrUpdateCustom\n */\n\nexport interface PathDeriveOptions {\n /** Segments to drop from the derived path (case-insensitive). */\n skipSegments: string[];\n /**\n * Casing convention applied to each remaining segment.\n * - `\"preserve\"` — keep the directory name as-is (default),\n * - `\"kebab\"` — convert camelCase / PascalCase to kebab-case.\n */\n casing: \"preserve\" | \"kebab\";\n}\n\nexport const DEFAULT_DERIVE: PathDeriveOptions = {\n skipSegments: [\"useCases\", \"useCase\", \"use-cases\", \"use-case\"],\n casing: \"preserve\",\n};\n\n/**\n * @param relativeDir POSIX-style directory path of the routes file relative\n * to the codegen root (no leading slash, no `routes.ts`).\n */\nexport function derivePath(\n relativeDir: string,\n options: PathDeriveOptions = DEFAULT_DERIVE,\n): string {\n const skip = new Set(options.skipSegments.map((s) => s.toLowerCase()));\n const parts = relativeDir\n .split(\"/\")\n .filter(Boolean)\n .filter((p) => !skip.has(p.toLowerCase()))\n .map((p) => (options.casing === \"kebab\" ? kebab(p) : p));\n return \"/\" + parts.join(\"/\");\n}\n\nfunction kebab(s: string): string {\n return s\n .replace(/([a-z0-9])([A-Z])/g, \"$1-$2\")\n .replace(/[\\s_]+/g, \"-\")\n .toLowerCase();\n}\n\n/**\n * Convert an absolute filesystem path to a POSIX-style import specifier\n * relative to a directory (the generated file's directory). Always uses\n * forward slashes and prefixes with `./` or `../` as needed.\n */\nexport function toImportSpecifier(\n fromDir: string,\n toFile: string,\n ext: string,\n): string {\n // Both paths are absolute POSIX (the CLI normalises them).\n const fromParts = splitAbs(fromDir);\n const toParts = splitAbs(toFile);\n let common = 0;\n while (\n common < fromParts.length &&\n common < toParts.length &&\n fromParts[common] === toParts[common]\n ) {\n common++;\n }\n const up = fromParts.length - common;\n const down = toParts.slice(common);\n const last = down[down.length - 1] ?? \"\";\n const stripped = last.replace(/\\.[mc]?[tj]sx?$/i, \"\");\n const finalLast = ext === \"\" ? stripped : `${stripped}${ext}`;\n down[down.length - 1] = finalLast;\n const prefix = up === 0 ? \"./\" : \"../\".repeat(up);\n return prefix + down.join(\"/\");\n}\n\nfunction splitAbs(p: string): string[] {\n const norm = p.replace(/\\\\/g, \"/\").replace(/\\/+$/, \"\");\n return norm.split(\"/\").filter((part, i) => !(i === 0 && part === \"\"));\n}\n","/**\n * Filesystem scanner — walks the configured root and yields every route file.\n * Synchronous and dependency-free (no `fast-glob` etc.) for a tiny CLI footprint.\n */\n\nimport { readdirSync, statSync } from \"node:fs\";\nimport { join, relative, sep } from \"node:path\";\n\nexport interface ScannerOptions {\n /** Filename to look for (default: `routes.ts`). */\n routesFile: string;\n /** Glob-like exclude segments (matched against any path part). */\n excludeSegments: string[];\n}\n\nexport const DEFAULT_SCANNER: ScannerOptions = {\n routesFile: \"routes.ts\",\n excludeSegments: [\n \"node_modules\",\n \"__generated__\",\n \"tests\",\n \"__tests__\",\n \".turbo\",\n \"dist\",\n \"build\",\n \".next\",\n ],\n};\n\nexport interface ScannedRoute {\n /** Absolute path to the routes file. */\n absPath: string;\n /** Path relative to the scan root (POSIX style). */\n relPath: string;\n /** Directory portion of `relPath` (what {@link derivePath} consumes). */\n relDir: string;\n}\n\nexport function scanRoutes(\n rootAbs: string,\n options: ScannerOptions = DEFAULT_SCANNER,\n): ScannedRoute[] {\n const found: ScannedRoute[] = [];\n walk(rootAbs, rootAbs, options, found);\n // Stable, deterministic order — important for reproducible builds.\n found.sort((a, b) => a.relPath.localeCompare(b.relPath));\n return found;\n}\n\nfunction walk(\n root: string,\n dir: string,\n opts: ScannerOptions,\n out: ScannedRoute[],\n): void {\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return;\n }\n for (const name of entries) {\n if (opts.excludeSegments.includes(name)) continue;\n const abs = join(dir, name);\n let st;\n try {\n st = statSync(abs);\n } catch {\n continue;\n }\n if (st.isDirectory()) {\n walk(root, abs, opts, out);\n } else if (st.isFile() && name === opts.routesFile) {\n const relPath = relative(root, abs).split(sep).join(\"/\");\n const relDir = relPath.replace(/\\/?[^/]+$/, \"\");\n out.push({ absPath: abs, relPath, relDir });\n }\n }\n}\n","/**\n * Generator — emits `__generated__/routes.ts` from a list of {@link ScannedRoute}s.\n *\n * The generated module statically imports every `routes.ts` so that bundlers\n * (esbuild/tsup) can tree-shake unused dependencies and Cloud Functions get\n * the smallest possible cold-start footprint.\n */\n\nimport { dirname, join } from \"node:path\";\nimport { mkdirSync, writeFileSync } from \"node:fs\";\n\nimport {\n derivePath,\n toImportSpecifier,\n type PathDeriveOptions,\n} from \"./path-utils\";\nimport type { ScannedRoute } from \"./scanner\";\n\nexport interface GeneratorOptions {\n /** Absolute path of the file to write (e.g. `…/__generated__/routes.ts`). */\n outFile: string;\n /** Path-derivation options (skipSegments, casing). */\n derive: PathDeriveOptions;\n /**\n * Import extension used in the generated file:\n * - `\".js\"` (default) — required for ESM Node.js with `\"type\": \"module\"`,\n * - `\"\"` — bundler-friendly (Vite, no extension),\n * - `\".ts\"` — for TS-ESM runners (`tsx`, `bun`).\n */\n importExtension: string;\n /** Friendly banner placed at the top of the generated file. */\n banner?: string;\n /** Generation timestamp included in the banner. Default: `new Date()`. */\n now?: Date;\n}\n\nexport const DEFAULT_GENERATOR_BANNER =\n \"/**\\n\" +\n \" * AUTO-GENERATED by `@lpdjs/firestore-repo-service` Hono codegen.\\n\" +\n \" * Do not edit by hand — re-run `hono:gen` after adding / removing route files.\\n\" +\n \" */\\n\";\n\nexport interface GenerationResult {\n /** Absolute path of the file written. */\n outFile: string;\n /** Number of routes captured in the manifest. */\n routeCount: number;\n /** Human-readable summary of the derived URLs. */\n derivedPaths: { source: string; url: string }[];\n}\n\nexport function generateRoutesManifest(\n routes: ScannedRoute[],\n opts: GeneratorOptions,\n): GenerationResult {\n const outDir = dirname(opts.outFile);\n mkdirSync(outDir, { recursive: true });\n\n const banner = opts.banner ?? DEFAULT_GENERATOR_BANNER;\n const now = (opts.now ?? new Date()).toISOString();\n const ext = opts.importExtension;\n\n const importLines: string[] = [];\n const entryLines: string[] = [];\n const derivedPaths: GenerationResult[\"derivedPaths\"] = [];\n\n routes.forEach((r, i) => {\n const importPath = toImportSpecifier(outDir, r.absPath, ext);\n const url = derivePath(r.relDir, opts.derive);\n importLines.push(\n `import mod${i} from ${JSON.stringify(importPath)};`,\n );\n entryLines.push(` { __derivedPath: ${JSON.stringify(url)}, mod: mod${i} },`);\n derivedPaths.push({ source: r.relPath, url });\n });\n\n const body =\n `${banner}` +\n `// Generated at ${now} — ${routes.length} route file${routes.length === 1 ? \"\" : \"s\"}.\\n` +\n `\\n` +\n `import type { AnyRouteDef, RouteModuleDefault } from \"@lpdjs/firestore-repo-service/servers/hono\";\\n` +\n `\\n` +\n importLines.join(\"\\n\") +\n (importLines.length ? \"\\n\\n\" : \"\\n\") +\n `const __defs: { __derivedPath: string; mod: RouteModuleDefault }[] = [\\n` +\n entryLines.join(\"\\n\") +\n (entryLines.length ? \"\\n\" : \"\") +\n `];\\n\\n` +\n `export const routes: AnyRouteDef[] = __defs.flatMap(({ __derivedPath, mod }) => {\\n` +\n ` const list = Array.isArray(mod) ? mod : [mod];\\n` +\n ` return list.map((route) => ({ ...route, path: route.path ?? __derivedPath }));\\n` +\n `});\\n`;\n\n writeFileSync(opts.outFile, body, \"utf8\");\n return {\n outFile: opts.outFile,\n routeCount: routes.length,\n derivedPaths,\n };\n}\n\n/** Convenience helper used by the CLI — combines scan + generate in one call. */\nexport function generateFromRoot(\n rootAbs: string,\n outFileRel: string,\n derive: PathDeriveOptions,\n importExtension: string,\n scan: (root: string) => ScannedRoute[],\n): GenerationResult {\n const routes = scan(rootAbs);\n const outFile = join(rootAbs, outFileRel);\n return generateRoutesManifest(routes, {\n outFile,\n derive,\n importExtension,\n });\n}\n"]}