@lpdjs/firestore-repo-service 2.6.8 → 2.6.9

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.
@@ -206,6 +206,52 @@ type AnyServicesContainer = ServicesContainer<Record<string, any>>;
206
206
  type HttpMethod = "get" | "post" | "put" | "patch" | "delete";
207
207
  /** Where the validated payload comes from. */
208
208
  type PayloadSource = "json" | "query" | "form" | "param";
209
+ /**
210
+ * Structured logger injected into every handler / interceptor / error-handler
211
+ * context. Implement it, or extend the package's `BaseLogger` and override its
212
+ * `write` hook to route to your sink (Firebase logger, pino, …).
213
+ *
214
+ * `error()` returns a correlation id so the same value can be both logged and
215
+ * returned to the client.
216
+ */
217
+ interface Logger {
218
+ info(message: string, meta?: unknown): void;
219
+ warn(message: string, meta?: unknown): void;
220
+ /** @returns a correlation id (e.g. to include in the HTTP error response). */
221
+ error(error: unknown, meta?: unknown): string;
222
+ debug(message: string, meta?: unknown): void;
223
+ }
224
+ /** Context passed to {@link ErrorHandler.handle}. */
225
+ interface ErrorHandlerContext<TEnv extends Env = Env, TServices extends AnyServicesContainer = AnyServicesContainer> {
226
+ /** The thrown value (an `AppError`, a Zod {@link ValidationError}, …). */
227
+ error: unknown;
228
+ /** Hono request context — set status, read headers, build the response. */
229
+ c: Context<TEnv>;
230
+ /** Route metadata (read-only). */
231
+ route: AnyRouteDef;
232
+ /** Global DI services container. */
233
+ services: TServices;
234
+ /** Injected {@link Logger} when one was passed to the API, else `undefined`. */
235
+ logger?: Logger;
236
+ }
237
+ /**
238
+ * Cross-cutting error strategy — a class (or object) you pass to the API and
239
+ * that the server injects everywhere **and** applies automatically.
240
+ *
241
+ * Implement {@link ErrorHandler.handle} to map any thrown value to an HTTP
242
+ * `Response` (e.g. your `AppError` → status + localized body, plus structured
243
+ * logging with a correlation id). Return `null` to decline the error and let
244
+ * the built-in fallback (`ValidationError` envelope) / `onError` take over.
245
+ *
246
+ * Prefer extending the package's {@link BaseErrorHandler} (it already maps the
247
+ * built-in errors) and overriding its `mapError` / `logError` hooks. Pass it
248
+ * **per API** via `ApiConfig.errorHandler`, or once via the registry
249
+ * (`{ services, errorHandler }`); it is then available in every handler /
250
+ * interceptor context as `errorHandler` and auto-applied on any uncaught error.
251
+ */
252
+ interface ErrorHandler<TEnv extends Env = Env, TServices extends AnyServicesContainer = AnyServicesContainer> {
253
+ handle(ctx: ErrorHandlerContext<TEnv, TServices>): Response | null | Promise<Response | null>;
254
+ }
209
255
  /** Handler signature — receives a single typed context object. */
210
256
  type RouteHandler<TIn, TOut, TEnv extends Env = Env, TServices extends AnyServicesContainer = AnyServicesContainer> = (ctx: {
211
257
  /** Validated (and typed) request payload. `void` when no `input` schema is defined. */
@@ -218,6 +264,13 @@ type RouteHandler<TIn, TOut, TEnv extends Env = Env, TServices extends AnyServic
218
264
  * `services` option.
219
265
  */
220
266
  services: TServices;
267
+ /**
268
+ * Injected {@link ErrorHandler} when one was passed to the API, else
269
+ * `undefined`. Usually you just `throw` and let it apply automatically.
270
+ */
271
+ errorHandler?: ErrorHandler<TEnv, TServices>;
272
+ /** Injected {@link Logger} when one was passed to the API, else `undefined`. */
273
+ logger?: Logger;
221
274
  }) => Promise<TOut | Response> | TOut | Response;
222
275
  /**
223
276
  * One route declaration. Default-exported by every `routes.ts` file inside the
@@ -264,7 +317,7 @@ interface RouteDef<TIn extends z.ZodTypeAny | undefined = z.ZodTypeAny | undefin
264
317
  /** Mark the operation as deprecated in the generated spec. */
265
318
  deprecated?: boolean;
266
319
  /** Security requirements (operationId-level override). */
267
- security?: Array<Record<string, string[]>>;
320
+ security?: SecurityRequirement[];
268
321
  /** The request handler. */
269
322
  handler: RouteHandler<TIn extends z.ZodTypeAny ? z.infer<TIn> : void, TOut extends z.ZodTypeAny ? z.infer<TOut> : unknown, TEnv>;
270
323
  }
@@ -374,13 +427,137 @@ type RouteInterceptor<TEnv extends Env = Env, TServices extends AnyServicesConta
374
427
  c: Context<TEnv>;
375
428
  /** Global DI services container. See {@link RouteHandler}. */
376
429
  services: TServices;
430
+ /**
431
+ * Injected {@link ErrorHandler} when one was passed to the API. Call
432
+ * `errorHandler?.handle({ error, c, route, services })` in your `catch` to
433
+ * reuse the shared mapping, or simply rethrow to let it apply automatically.
434
+ */
435
+ errorHandler?: ErrorHandler<TEnv, TServices>;
436
+ /** Injected {@link Logger} when one was passed to the API, else `undefined`. */
437
+ logger?: Logger;
377
438
  }) => Promise<Response | unknown> | Response | unknown;
439
+ /**
440
+ * Success-envelope schema declared alongside an interceptor so the generated
441
+ * OpenAPI spec documents what the **wrapper actually returns** (not the raw
442
+ * handler output).
443
+ *
444
+ * - a **static** Zod schema → same envelope for every route (e.g.
445
+ * `z.object({ data: z.any(), intercepted: z.boolean() })`);
446
+ * - a **factory** `(routeOutput) => schema` → wraps each route's own `output`,
447
+ * so `data` is typed per endpoint in the docs.
448
+ */
449
+ type InterceptorOutput = z.ZodTypeAny | ((routeOutput: z.ZodTypeAny | undefined) => z.ZodTypeAny);
450
+ /** A single declared error response for the OpenAPI spec. */
451
+ type InterceptorErrorResponse = z.ZodTypeAny | {
452
+ description?: string;
453
+ schema?: z.ZodTypeAny;
454
+ };
455
+ /**
456
+ * Structured interceptor — pairs the cross-cutting {@link RouteInterceptor}
457
+ * `handler` with the OpenAPI metadata describing what it returns, so the docs
458
+ * reflect the real wrapped responses (success envelope + error shapes).
459
+ *
460
+ * @example
461
+ * ```ts
462
+ * interceptor: {
463
+ * // factory: `data` reflects each route's own output schema
464
+ * output: (routeOutput) =>
465
+ * z.object({ data: routeOutput ?? z.unknown(), intercepted: z.boolean() }),
466
+ * errors: {
467
+ * 400: ValidationErrorSchema,
468
+ * 500: { description: "Internal", schema: ErrorSchema },
469
+ * },
470
+ * handler: async ({ c, next }) => {
471
+ * const data = await next();
472
+ * return c.json({ data, intercepted: true });
473
+ * },
474
+ * }
475
+ * ```
476
+ */
477
+ interface InterceptorConfig<TEnv extends Env = Env, TServices extends AnyServicesContainer = AnyServicesContainer> {
478
+ /** Success-envelope schema (static) or per-route factory. */
479
+ output?: InterceptorOutput;
480
+ /**
481
+ * Error responses applied to **every** operation, keyed by HTTP status. Pass
482
+ * a bare Zod schema or `{ description, schema }`.
483
+ */
484
+ errors?: Record<number, InterceptorErrorResponse>;
485
+ /** The interceptor function. See {@link RouteInterceptor}. */
486
+ handler: RouteInterceptor<TEnv, TServices>;
487
+ }
488
+ /**
489
+ * Interceptor option accepted by the server / registry — either a bare
490
+ * {@link RouteInterceptor} function (legacy) or a structured
491
+ * {@link InterceptorConfig} carrying OpenAPI metadata.
492
+ */
493
+ type InterceptorOption<TEnv extends Env = Env, TServices extends AnyServicesContainer = AnyServicesContainer> = RouteInterceptor<TEnv, TServices> | InterceptorConfig<TEnv, TServices>;
378
494
  /** OpenAPI document info (subset of the spec used by the helper). */
379
495
  interface OpenAPIInfo {
380
496
  title: string;
381
497
  version: string;
382
498
  description?: string;
383
499
  }
500
+ /** Fields shared by every security scheme. */
501
+ interface SecuritySchemeBase {
502
+ /** Human-readable description (CommonMark). */
503
+ description?: string;
504
+ }
505
+ /** API key carried in a header, query param or cookie. */
506
+ interface ApiKeySecurityScheme extends SecuritySchemeBase {
507
+ type: "apiKey";
508
+ /** Name of the header, query parameter or cookie. */
509
+ name: string;
510
+ /** Location of the API key. */
511
+ in: "query" | "header" | "cookie";
512
+ }
513
+ /**
514
+ * HTTP authentication (RFC 7235), e.g. `bearer` (Firebase ID tokens) or
515
+ * `basic`.
516
+ */
517
+ interface HttpSecurityScheme extends SecuritySchemeBase {
518
+ type: "http";
519
+ /** Auth scheme name — `"bearer"`, `"basic"`, `"digest"`, … */
520
+ scheme: "bearer" | "basic" | "digest" | (string & {});
521
+ /** Hint for the bearer token format, e.g. `"JWT"` / `"Firebase JWT"`. */
522
+ bearerFormat?: string;
523
+ }
524
+ /** Mutual TLS authentication. */
525
+ interface MutualTlsSecurityScheme extends SecuritySchemeBase {
526
+ type: "mutualTLS";
527
+ }
528
+ /** A single OAuth2 flow. */
529
+ interface OAuthFlowObject {
530
+ authorizationUrl?: string;
531
+ tokenUrl?: string;
532
+ refreshUrl?: string;
533
+ /** Available scopes → description. */
534
+ scopes: Record<string, string>;
535
+ }
536
+ /** The OAuth2 flows supported by an {@link OAuth2SecurityScheme}. */
537
+ interface OAuthFlowsObject {
538
+ implicit?: OAuthFlowObject;
539
+ password?: OAuthFlowObject;
540
+ clientCredentials?: OAuthFlowObject;
541
+ authorizationCode?: OAuthFlowObject;
542
+ }
543
+ /** OAuth2 authentication. */
544
+ interface OAuth2SecurityScheme extends SecuritySchemeBase {
545
+ type: "oauth2";
546
+ flows: OAuthFlowsObject;
547
+ }
548
+ /** OpenID Connect Discovery. */
549
+ interface OpenIdConnectSecurityScheme extends SecuritySchemeBase {
550
+ type: "openIdConnect";
551
+ openIdConnectUrl: string;
552
+ }
553
+ /** OpenAPI 3.1 Security Scheme Object (discriminated on `type`). */
554
+ type SecurityScheme = ApiKeySecurityScheme | HttpSecurityScheme | MutualTlsSecurityScheme | OAuth2SecurityScheme | OpenIdConnectSecurityScheme;
555
+ /**
556
+ * A single Security Requirement Object: maps a scheme name (a key of
557
+ * {@link OpenAPIConfig.securitySchemes}) to the list of required scopes
558
+ * (empty `[]` for `http` / `apiKey`).
559
+ */
560
+ type SecurityRequirement = Record<string, string[]>;
384
561
  /** OpenAPI configuration on the server. */
385
562
  interface OpenAPIConfig {
386
563
  /** Path served by the JSON spec (e.g. `/openapi.json`). Default: `/openapi.json`. */
@@ -394,10 +571,37 @@ interface OpenAPIConfig {
394
571
  url: string;
395
572
  description?: string;
396
573
  }[];
397
- /** Optional security schemes (e.g. bearer auth). */
398
- securitySchemes?: Record<string, unknown>;
399
- /** Default security requirement applied to every operation. */
400
- security?: Array<Record<string, string[]>>;
574
+ /**
575
+ * Reusable security schemes, keyed by name. The keys are referenced from
576
+ * {@link OpenAPIConfig.security} / per-route `security`.
577
+ *
578
+ * @example
579
+ * ```ts
580
+ * securitySchemes: {
581
+ * bearerAuth: { type: "http", scheme: "bearer", bearerFormat: "Firebase JWT" },
582
+ * }
583
+ * ```
584
+ */
585
+ securitySchemes?: Record<string, SecurityScheme>;
586
+ /**
587
+ * Default security requirement applied to every operation. Each entry maps a
588
+ * scheme name (a key of {@link OpenAPIConfig.securitySchemes}) to its scopes.
589
+ *
590
+ * @example `security: [{ bearerAuth: [] }]`
591
+ */
592
+ security?: SecurityRequirement[];
593
+ /**
594
+ * Hono middleware(s) guarding **only** the docs UI and JSON spec endpoints
595
+ * (not the API routes). Use a raw middleware for a custom flow, or the
596
+ * built-in {@link firebaseBearerAuth} / {@link basicAuth} helpers.
597
+ *
598
+ * @example
599
+ * ```ts
600
+ * import { firebaseBearerAuth } from "@lpdjs/firestore-repo-service/servers/hono";
601
+ * openapi: { info, docsAuth: firebaseBearerAuth({ getAuth }) }
602
+ * ```
603
+ */
604
+ docsAuth?: MiddlewareHandler | MiddlewareHandler[];
401
605
  }
402
606
  /** Options consumed by the {@link HonoServer} constructor. */
403
607
  interface HonoServerOptions<TEnv extends Env = Env> {
@@ -434,9 +638,27 @@ interface HonoServerOptions<TEnv extends Env = Env> {
434
638
  /**
435
639
  * Cross-cutting interceptor wrapping every handler call.
436
640
  * Ideal for response envelopes, business-error mapping, tracing.
437
- * See {@link RouteInterceptor}.
641
+ *
642
+ * Pass a bare {@link RouteInterceptor} function, or an
643
+ * {@link InterceptorConfig} (`{ output?, errors?, handler }`) so the
644
+ * generated OpenAPI spec documents the wrapped responses.
645
+ */
646
+ interceptor?: InterceptorOption<TEnv>;
647
+ /**
648
+ * Cross-cutting error strategy applied to every uncaught error and injected
649
+ * into every handler / interceptor context. See {@link ErrorHandler}.
650
+ *
651
+ * Typically shared across APIs — pass it once to `createApiRegistry` via
652
+ * `{ services, errorHandler }`; this per-API field overrides it.
653
+ */
654
+ errorHandler?: ErrorHandler<TEnv>;
655
+ /**
656
+ * Structured {@link Logger} injected into every handler / interceptor /
657
+ * error-handler context. Extend the package's `BaseLogger` to route to your
658
+ * sink. Typically shared via `createApiRegistry({ services, logger })`; this
659
+ * per-API field overrides it.
438
660
  */
439
- interceptor?: RouteInterceptor<TEnv>;
661
+ logger?: Logger;
440
662
  /**
441
663
  * Global DI services container (see {@link createServices}).
442
664
  *
@@ -645,6 +867,25 @@ type OnRequestFn = (...args: any[]) => any;
645
867
  type ApiConfig<TEnv extends Env = Env> = Omit<HonoServerOptions<TEnv>, "routes" | "api" | "services">;
646
868
  /** Map of API tag → its config. */
647
869
  type ApiConfigMap = Record<string, ApiConfig>;
870
+ /**
871
+ * Per-key excess-property guard.
872
+ *
873
+ * `createApiRegistry` infers its config map generically (`<const TMap …>`),
874
+ * which normally **defeats** TypeScript's excess-property checking — a typo
875
+ * like `openApi` or `middleware` (instead of `openapi` / `middlewares`) would
876
+ * pass silently and the option would be ignored at runtime.
877
+ *
878
+ * This intersects the concrete {@link ApiConfig} shape (so every known key
879
+ * keeps its real type **and JSDoc**, including nested objects such as
880
+ * `openapi`) with a `never` mapping for any key absent from `ApiConfig` — which
881
+ * makes typos a compile error, matching the strictness already enforced on the
882
+ * CRUD server's `openapi`.
883
+ *
884
+ * @internal
885
+ */
886
+ type StrictApiConfig<T, TEnv extends Env = Env> = ApiConfig<TEnv> & {
887
+ [K in keyof T as K extends keyof ApiConfig<TEnv> ? never : K]: never;
888
+ };
648
889
  /**
649
890
  * Options accepted by {@link createApiRegistry} alongside the per-API
650
891
  * configs.
@@ -657,6 +898,18 @@ interface ApiRegistryOptions<TServices extends AnyServicesContainer = AnyService
657
898
  * `services` to every handler / interceptor.
658
899
  */
659
900
  services?: TServices;
901
+ /**
902
+ * Cross-cutting {@link ErrorHandler} shared across every API — injected into
903
+ * every handler / interceptor context and applied automatically on any
904
+ * uncaught error. A per-API `errorHandler` (on the config) overrides it.
905
+ */
906
+ errorHandler?: ErrorHandler;
907
+ /**
908
+ * Structured {@link Logger} shared across every API — injected into every
909
+ * handler / interceptor / error-handler context. A per-API `logger` (on the
910
+ * config) overrides it.
911
+ */
912
+ logger?: Logger;
660
913
  }
661
914
  interface ApiRegistry<TMap extends ApiConfigMap, TServices extends AnyServicesContainer = AnyServicesContainer> {
662
915
  /** The registered configs (read-only). */
@@ -710,10 +963,16 @@ interface ApiRegistry<TMap extends ApiConfigMap, TServices extends AnyServicesCo
710
963
  * Factory — declare every API tag once and get back a typed `defineRoute`
711
964
  * + `toFunctions`. See the file-level example.
712
965
  *
713
- * @param configs API-tag → per-API config.
966
+ * Each per-API config is strictly checked against {@link ApiConfig}: unknown
967
+ * keys (typos like `openApi` / `middleware`) are rejected at compile time,
968
+ * including nested objects such as `openapi` — mirroring the CRUD server.
969
+ *
970
+ * @param configs API-tag → per-API config (see {@link ApiConfig}).
714
971
  * @param options Cross-API options (shared services container, etc).
715
972
  */
716
- declare function createApiRegistry<const TMap extends ApiConfigMap, TServices extends AnyServicesContainer = AnyServicesContainer>(configs: TMap, options?: ApiRegistryOptions<TServices>): ApiRegistry<TMap, TServices>;
973
+ declare function createApiRegistry<const TMap extends ApiConfigMap, TServices extends AnyServicesContainer = AnyServicesContainer>(configs: {
974
+ [K in keyof TMap]: StrictApiConfig<TMap[K]>;
975
+ }, options?: ApiRegistryOptions<TServices>): ApiRegistry<TMap, TServices>;
717
976
 
718
977
  /**
719
978
  * OpenAPI 3.1 spec generator from {@link RouteDef} entries.
@@ -723,13 +982,213 @@ declare function createApiRegistry<const TMap extends ApiConfigMap, TServices ex
723
982
  */
724
983
 
725
984
  /** Build the OpenAPI document from the mounted route registry. */
726
- declare function buildOpenApiDocument(routes: AnyRouteDef[], basePath: string, config: OpenAPIConfig): Record<string, unknown>;
985
+ declare function buildOpenApiDocument(routes: AnyRouteDef[], basePath: string, config: OpenAPIConfig, interceptor?: InterceptorConfig): Record<string, unknown>;
727
986
  /**
728
987
  * Render a self-contained Scalar API Reference HTML page that points to the
729
988
  * generated spec. Loaded from CDN — no build step required.
730
989
  */
731
990
  declare function renderDocsHtml(specUrl: string, title: string): string;
732
991
 
992
+ /**
993
+ * `BaseErrorHandler` — the package's ready-to-use {@link ErrorHandler}.
994
+ *
995
+ * Use it as-is for an API that only needs the built-in error mapping
996
+ * (`ValidationError` / `BadRequestError` / `OutputValidationError`), or extend
997
+ * it and override the two hooks to plug your own domain errors + logger:
998
+ *
999
+ * - {@link BaseErrorHandler.mapError} — map your `AppError` → `Response`
1000
+ * (return `null` to defer to the built-in mapping);
1001
+ * - {@link BaseErrorHandler.logError} — log via your `AppLogger`.
1002
+ *
1003
+ * Pass an instance **per API** (`ApiConfig.errorHandler`) so different APIs can
1004
+ * use different strategies (e.g. one with user-facing localized errors, one
1005
+ * with just the defaults).
1006
+ *
1007
+ * @example
1008
+ * ```ts
1009
+ * class AppErrorHandler extends BaseErrorHandler {
1010
+ * protected mapError({ error, c }) {
1011
+ * if (error instanceof AppError) {
1012
+ * return c.json({ error: error.message, errorId: error.errorId }, error.statusCode);
1013
+ * }
1014
+ * return null; // → built-in mapping
1015
+ * }
1016
+ * protected logError({ error }) {
1017
+ * AppLogger.err(error);
1018
+ * }
1019
+ * }
1020
+ *
1021
+ * // apis.ts
1022
+ * v1: { ..., errorHandler: new AppErrorHandler() }, // user-facing API
1023
+ * v2: { ..., errorHandler: new BaseErrorHandler() }, // defaults only
1024
+ * ```
1025
+ */
1026
+
1027
+ declare class BaseErrorHandler<TEnv extends Env = Env, TServices extends AnyServicesContainer = AnyServicesContainer> implements ErrorHandler<TEnv, TServices> {
1028
+ /**
1029
+ * Orchestration — not meant to be overridden. Tries the user mapping first,
1030
+ * logs it when matched, then falls back to the built-in mapping.
1031
+ */
1032
+ handle(ctx: ErrorHandlerContext<TEnv, TServices>): Promise<Response | null>;
1033
+ /**
1034
+ * Map a domain error (your `AppError`) to a `Response`. Return `null` to let
1035
+ * {@link BaseErrorHandler.handleBuiltin} handle it. Default: `null`.
1036
+ */
1037
+ protected mapError(_ctx: ErrorHandlerContext<TEnv, TServices>): Response | null | Promise<Response | null>;
1038
+ /**
1039
+ * Log a mapped error (e.g. via your `AppLogger`). Called only when
1040
+ * {@link BaseErrorHandler.mapError} produced a response. Default: no-op.
1041
+ */
1042
+ protected logError(_ctx: ErrorHandlerContext<TEnv, TServices>, _response: Response): void;
1043
+ /**
1044
+ * Built-in mapping of the package's own errors (`ValidationError`,
1045
+ * `BadRequestError`, `OutputValidationError`). Returns `null` for unknown
1046
+ * errors so they bubble to `onError` / Hono.
1047
+ */
1048
+ protected handleBuiltin(ctx: ErrorHandlerContext<TEnv, TServices>): Response | null;
1049
+ }
1050
+
1051
+ /**
1052
+ * `BaseLogger` — the package's ready-to-use {@link Logger}.
1053
+ *
1054
+ * Use it as-is (writes structured JSON to `console`), or extend it and override
1055
+ * the single {@link BaseLogger.write} hook to route to your sink (Firebase
1056
+ * `logger`, pino, Datadog, …). Each level funnels through `write`, so one
1057
+ * override covers them all.
1058
+ *
1059
+ * Pass an instance **per API** (`ApiConfig.logger`) or once via the registry
1060
+ * (`createApiRegistry({ services, logger })`); it is then injected into every
1061
+ * handler / interceptor / error-handler context as `logger`.
1062
+ *
1063
+ * @example
1064
+ * ```ts
1065
+ * import { logger as fnLogger } from "firebase-functions/v2";
1066
+ * class AppLogger extends BaseLogger {
1067
+ * protected write(severity, payload) {
1068
+ * fnLogger.write({ severity, ...payload });
1069
+ * }
1070
+ * }
1071
+ * ```
1072
+ */
1073
+
1074
+ type LogSeverity = "DEBUG" | "INFO" | "WARNING" | "ERROR";
1075
+ declare class BaseLogger implements Logger {
1076
+ info(message: string, meta?: unknown): void;
1077
+ warn(message: string, meta?: unknown): void;
1078
+ debug(message: string, meta?: unknown): void;
1079
+ /**
1080
+ * Log an error and return a correlation id. If the error already carries an
1081
+ * `errorId` it is reused, otherwise a fresh one is generated.
1082
+ */
1083
+ error(error: unknown, meta?: unknown): string;
1084
+ /** Build a structured payload from a message + optional metadata. */
1085
+ protected payload(message: string, meta?: unknown): Record<string, unknown>;
1086
+ /**
1087
+ * Sink hook — override to route logs elsewhere. Default: structured
1088
+ * `console` write keyed by severity.
1089
+ */
1090
+ protected write(severity: LogSeverity, payload: Record<string, unknown>): void;
1091
+ /** Reuse an error's `errorId` when present, else generate one. */
1092
+ protected static errorId(error: unknown): string;
1093
+ }
1094
+
1095
+ /**
1096
+ * Built-in error types thrown by the server pipeline and their default
1097
+ * HTTP mapping — shared by the request handler and {@link BaseErrorHandler}.
1098
+ */
1099
+
1100
+ /** Thrown when the request body cannot be read / parsed → HTTP 400. */
1101
+ declare class BadRequestError extends Error {
1102
+ readonly statusCode: 400;
1103
+ constructor(message: string);
1104
+ }
1105
+ /** Thrown when a handler's return value fails the `output` schema → HTTP 500. */
1106
+ declare class OutputValidationError extends Error {
1107
+ readonly zodError: ZodError;
1108
+ readonly statusCode: 500;
1109
+ constructor(zodError: ZodError);
1110
+ }
1111
+ /**
1112
+ * Default JSON mapping for the package's own errors (`ValidationError`,
1113
+ * `BadRequestError`, `OutputValidationError`). Returns `null` for anything
1114
+ * else so the caller can decide (rethrow / custom handler).
1115
+ */
1116
+ declare function defaultErrorResponse(c: any, err: unknown): Response | null;
1117
+
1118
+ /**
1119
+ * Hono-native auth guards for the OpenAPI docs / spec endpoints.
1120
+ *
1121
+ * These return plain Hono `MiddlewareHandler`s, so they slot into
1122
+ * `OpenAPIConfig.docsAuth` (which protects only `/docs` + `/openapi.json`,
1123
+ * never the API routes). For a fully custom flow, pass your own middleware
1124
+ * instead of these helpers.
1125
+ */
1126
+
1127
+ /** Decoded token shape — kept minimal to avoid a hard firebase-admin import. */
1128
+ interface DecodedBearerToken {
1129
+ uid: string;
1130
+ email?: string;
1131
+ [claim: string]: any;
1132
+ }
1133
+ /** Minimal Firebase Admin Auth surface used by {@link firebaseBearerAuth}. */
1134
+ interface FirebaseBearerAuthLike {
1135
+ verifyIdToken(idToken: string, checkRevoked?: boolean): Promise<DecodedBearerToken>;
1136
+ }
1137
+ /** Options for {@link firebaseBearerAuth}. */
1138
+ interface FirebaseBearerAuthOptions {
1139
+ /**
1140
+ * Returns the Firebase Admin Auth instance, e.g. `() => getAuth()`. Called
1141
+ * lazily on each request so `initializeApp()` runs first.
1142
+ */
1143
+ getAuth: () => FirebaseBearerAuthLike;
1144
+ /**
1145
+ * Authorization policy. Return `false` (or throw) to reject the request,
1146
+ * any truthy value to allow. Defaults to allowing any verified token.
1147
+ */
1148
+ allow?: (token: DecodedBearerToken) => boolean | Promise<boolean>;
1149
+ /** Revoke check passed to `verifyIdToken`. Default: `false`. */
1150
+ checkRevoked?: boolean;
1151
+ /**
1152
+ * Context key under which the decoded token is stored (`c.set(key, token)`)
1153
+ * for downstream handlers. Default: `"docsUser"`.
1154
+ */
1155
+ contextKey?: string;
1156
+ }
1157
+ /**
1158
+ * Guard the docs / spec endpoints with a Firebase ID token (Bearer scheme).
1159
+ *
1160
+ * @example
1161
+ * ```ts
1162
+ * import { getAuth } from "firebase-admin/auth";
1163
+ * import { firebaseBearerAuth } from "@lpdjs/firestore-repo-service/servers/hono";
1164
+ *
1165
+ * openapi: {
1166
+ * info,
1167
+ * docsAuth: firebaseBearerAuth({
1168
+ * getAuth: () => getAuth(),
1169
+ * allow: (t) => t.admin === true,
1170
+ * }),
1171
+ * }
1172
+ * ```
1173
+ */
1174
+ declare function firebaseBearerAuth(options: FirebaseBearerAuthOptions): MiddlewareHandler;
1175
+ /** Options for {@link basicAuth}. */
1176
+ interface BasicAuthOptions {
1177
+ username: string;
1178
+ password: string;
1179
+ /** Realm advertised in the `WWW-Authenticate` header. Default: `"Docs"`. */
1180
+ realm?: string;
1181
+ }
1182
+ /**
1183
+ * Guard the docs / spec endpoints with HTTP Basic Auth.
1184
+ *
1185
+ * @example
1186
+ * ```ts
1187
+ * openapi: { info, docsAuth: basicAuth({ username: "admin", password: "secret" }) }
1188
+ * ```
1189
+ */
1190
+ declare function basicAuth(options: BasicAuthOptions): MiddlewareHandler;
1191
+
733
1192
  /**
734
1193
  * URL path inference from filesystem layout.
735
1194
  *
@@ -828,4 +1287,4 @@ declare function generateRoutesManifest(routes: ScannedRoute[], opts: GeneratorO
828
1287
  /** Convenience helper used by the CLI — combines scan + generate in one call. */
829
1288
  declare function generateFromRoot(rootAbs: string, outFileRel: string, derive: PathDeriveOptions, importExtension: string, scan: (root: string) => ScannedRoute[]): GenerationResult;
830
1289
 
831
- export { type AnyRouteDef, type AnyServicesContainer, type ApiConfig, type ApiConfigMap, type ApiRegistry, type ApiRegistryOptions, DEFAULT_DERIVE, DEFAULT_GENERATOR_BANNER, DEFAULT_SCANNER, type GenerationResult, type GeneratorOptions, HonoServer, type HonoServerOptions, type HttpMethod, type OpenAPIConfig, type OpenAPIInfo, type PathDeriveOptions, type PayloadSource, type RequestContext, type RouteDef, type RouteHandler, type RouteInput, type RouteInterceptor, type RouteModuleDefault, type RouteOutput, type RoutesUnion, type ScannedRoute, type ScannerOptions, type ServiceProvider, type ServiceProviderMap, type ServicesContainer, type ServicesOf, UseCase, type UseCaseClass, type UseCaseRouteMeta, ValidationError, buildOpenApiDocument, createApiRegistry, createRequestContextMiddleware, createServices, defineRoutes, derivePath, generateFromRoot, generateRoutesManifest, renderDocsHtml, scanRoutes, toImportSpecifier, useCaseRoute, withRequestContext };
1290
+ export { type AnyRouteDef, type AnyServicesContainer, type ApiConfig, type ApiConfigMap, type ApiKeySecurityScheme, type ApiRegistry, type ApiRegistryOptions, BadRequestError, BaseErrorHandler, BaseLogger, type BasicAuthOptions, DEFAULT_DERIVE, DEFAULT_GENERATOR_BANNER, DEFAULT_SCANNER, type DecodedBearerToken, type ErrorHandler, type ErrorHandlerContext, type FirebaseBearerAuthLike, type FirebaseBearerAuthOptions, type GenerationResult, type GeneratorOptions, HonoServer, type HonoServerOptions, type HttpMethod, type HttpSecurityScheme, type InterceptorConfig, type InterceptorErrorResponse, type InterceptorOption, type InterceptorOutput, type LogSeverity, type Logger, type MutualTlsSecurityScheme, type OAuth2SecurityScheme, type OAuthFlowObject, type OAuthFlowsObject, type OpenAPIConfig, type OpenAPIInfo, type OpenIdConnectSecurityScheme, OutputValidationError, type PathDeriveOptions, type PayloadSource, type RequestContext, type RouteDef, type RouteHandler, type RouteInput, type RouteInterceptor, type RouteModuleDefault, type RouteOutput, type RoutesUnion, type ScannedRoute, type ScannerOptions, type SecurityRequirement, type SecurityScheme, type SecuritySchemeBase, type ServiceProvider, type ServiceProviderMap, type ServicesContainer, type ServicesOf, UseCase, type UseCaseClass, type UseCaseRouteMeta, ValidationError, basicAuth, buildOpenApiDocument, createApiRegistry, createRequestContextMiddleware, createServices, defaultErrorResponse, defineRoutes, derivePath, firebaseBearerAuth, generateFromRoot, generateRoutesManifest, renderDocsHtml, scanRoutes, toImportSpecifier, useCaseRoute, withRequestContext };