import { z } from 'zod'; import { ServiceOperationSample, ServiceDependencyDescriptor, ObservabilityCapability, ServiceObservabilityDescribe, ServiceHealthStatus, ObservabilityCheck, ServiceObservabilityState, ServiceObservabilityHealth } from '@kb-labs/core-contracts'; import { ILogger, IContextLogger } from '@kb-labs/core-platform'; import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; export { ZodTypeProvider, jsonSchemaTransform, serializerCompiler, validatorCompiler } from 'fastify-type-provider-zod'; /** * Registers @fastify/swagger + @fastify/swagger-ui on a Fastify instance. * * Routes without `tags:` are excluded from the spec (hideUntagged: true). * Use this as the visibility toggle — internal/undocumented routes simply * omit `tags:` and they won't appear in /docs or /openapi.json. * * Plain JSON Schema and Zod schemas are both supported natively by * @fastify/swagger without any custom transform. * * @example * ```ts * import { registerOpenAPI } from '@kb-labs/shared-http'; * * await registerOpenAPI(server, { * title: 'My Service', * version: '1.0.0', * servers: [{ url: 'http://localhost:3000', description: 'Local dev' }], * ui: process.env.NODE_ENV !== 'production', * }); * ``` */ /** Minimal structural interface to accept any Fastify instance regardless of version. */ interface FastifyLike { register(plugin: unknown, opts?: unknown): unknown; get(path: string, opts: unknown, handler: (req: unknown, reply: unknown) => unknown): void; swagger?(): unknown; } interface OpenAPIOptions { title: string; description?: string; version?: string; /** Route prefix for Swagger UI. Default: '/docs' */ docsPath?: string; /** Route for the raw OpenAPI JSON spec. Default: '/openapi.json' */ specPath?: string; servers?: Array<{ url: string; description?: string; }>; /** * Set to false to skip Swagger UI registration (e.g. in production). * The spec endpoint (/openapi.json) is still registered. * Default: true */ ui?: boolean; } declare function registerOpenAPI(server: FastifyLike, options: OpenAPIOptions): Promise; interface ZodSchemaRef { /** Reference in format "package-name#ExportedSchemaName" */ zod: string; } interface JsonSchemaRef { $ref: string; } type SchemaRef = ZodSchemaRef | JsonSchemaRef; /** * Resolves a SchemaRef from a plugin manifest to a plain JSON Schema object. * * Supports two formats: * - `{ $ref: "..." }` — returned as-is (already a JSON Schema reference) * - `{ zod: "pkg#ExportName" }` — dynamically imports the package, grabs the * named export, and converts the ZodType to JSON Schema via zod-to-json-schema * * Fail-open: if the module cannot be imported or the export is missing/not a * ZodType, returns null and logs a warning. The route will still mount — it just * won't have schema documentation. * * @example * ```ts * const schema = await resolveSchemaRef({ * zod: '@kb-labs/commit-contracts#GenerateRequestSchema' * }); * // → { type: 'object', properties: { ... }, required: [...] } * ``` */ declare function resolveSchemaRef(ref: SchemaRef): Promise | null>; /** * Standard error response schema. * Use in route `response:` blocks for error status codes. */ declare const ErrorResponseSchema: z.ZodObject<{ ok: z.ZodLiteral; error: z.ZodString; code: z.ZodOptional; }, "strip", z.ZodTypeAny, { ok: false; error: string; code?: string | undefined; }, { ok: false; error: string; code?: string | undefined; }>; /** * Wraps a data schema in a standard success envelope. * * @example * ```ts * response: { * 200: OkResponseSchema(z.object({ jobs: z.array(JobSchema) })) * } * ``` */ declare const OkResponseSchema: (data: T) => z.ZodObject<{ ok: z.ZodLiteral; data: T; }, "strip", z.ZodTypeAny, z.objectUtil.addQuestionMarks; data: T; }>, any> extends infer T_1 ? { [k in keyof T_1]: T_1[k]; } : never, z.baseObjectInputType<{ ok: z.ZodLiteral; data: T; }> extends infer T_2 ? { [k_1 in keyof T_2]: T_2[k_1]; } : never>; type ErrorResponse = z.infer; interface VersionedObservabilityShape { schema: string; contractVersion: string; } interface ServiceReadyResponse { schema: 'kb.ready/1'; ts: string; ready: boolean; status: 'ready' | 'degraded' | 'initializing'; reason: string; components: Record; } /** * Shared builder for versioned service describe payloads. * Keeps services on a single construction path instead of ad hoc objects. */ declare function createServiceObservabilityDescribe(options: T): T; /** * Shared builder for versioned service health payloads. * Callers provide the contract-specific shape while keeping a common entry point. */ declare function createServiceObservabilityHealth(options: T): T; declare function createServiceReadyResponse(options: { ready: boolean; status?: ServiceReadyResponse['status']; reason?: string; components?: Record; ts?: string; }): ServiceReadyResponse; type OperationStatus = 'ok' | 'error'; interface OperationObserver { recordOperation(operation: string, durationMs?: number, status?: OperationStatus, count?: number): void; observeOperation(operation: string, work: () => T | Promise): Promise; } declare class OperationMetricsTracker implements OperationObserver { private readonly stats; reset(): void; recordOperation(operation: string, durationMs?: number, status?: OperationStatus, count?: number): void; observeOperation(operation: string, work: () => T | Promise): Promise; getTopOperations(limit?: number): ServiceOperationSample[]; getMetricLines(): string[]; } declare module 'fastify' { interface FastifyRequest { kbObservabilityStart?: number; } } type HookableFastifyServer = { addHook: (event: string, handler: (...args: any[]) => void) => void; }; interface HttpObservabilityCollectorOptions { serviceId: string; serviceType: string; version: string; metricsEndpoint?: string; healthEndpoint?: string; logsSource?: string; environment?: string; dependencies?: ServiceDependencyDescriptor[]; capabilities?: ObservabilityCapability[]; startedAtMs?: number; } declare function normalizeObservabilityRoute(route: string | undefined): string; declare function metricLine(name: string, value: number, labels?: Record): string; declare class HttpObservabilityCollector { private readonly options; private readonly instanceId; private readonly startedAtMs; private readonly metricsEndpoint; private readonly healthEndpoint; private readonly logsSource; private readonly environment; private readonly dependencies; private readonly capabilities; private readonly eventLoop; private readonly routeStats; private readonly operationMetrics; private lastCpuUsage; private lastCpuTime; private intervalId; private activeOperations; private requestsTotal; private errorsTotal; private runtimeSnapshotCollectedAt; private lastSnapshot; constructor(options: HttpObservabilityCollectorOptions); register(server: HookableFastifyServer): void; buildDescribe(): ServiceObservabilityDescribe; buildHealth(input: { status: ServiceHealthStatus; checks: ObservabilityCheck[]; observedAt?: string; state?: ServiceObservabilityState; topOperations?: ServiceOperationSample[]; meta?: Record; }): ServiceObservabilityHealth; getTopOperations(limit?: number): ServiceOperationSample[]; recordOperation(operation: string, durationMs?: number, status?: OperationStatus, count?: number): void; observeOperation(operation: string, work: () => T | Promise): Promise; renderPrometheusMetrics(healthStatus: ServiceHealthStatus, extraLines?: string[]): string; private captureRuntimeSnapshot; } interface HttpLogContextInput { applicationId?: string; serviceId: string; instanceId?: string; layer?: string; component?: string; requestId?: string; traceId?: string; operation?: string; method?: string; url?: string; fields?: Record; } declare function resolveObservabilityInstanceId(): string; /** * Creates the canonical request logger. HTTP attributes are deliberately * namespaced so unrelated domains cannot collide with them. */ declare function createHttpLogger(baseLogger: ILogger, input: HttpLogContextInput): IContextLogger; /** * Returns Fastify listen options for the current process. * * When KB_SOCKET_PATH is set (injected by kb-dev), returns a unix socket * path instead of TCP — eliminating an extra port per service in solo/dev mode. * Cleans up a stale socket file from a previous crash before binding. */ declare function getListenOptions(port: number, host?: string): { port: number; host: string; } | { path: string; }; interface ObservabilityCollectorLike { register(server: FastifyInstance): void; buildDescribe(): ServiceObservabilityDescribe; buildHealth(params?: Record): ServiceObservabilityHealth; renderPrometheusMetrics(status: string, extra?: string[]): string; } interface DaemonServerOptions { serviceId: string; logger: ILogger; observability: ObservabilityCollectorLike; bodyLimit?: number; trustProxy?: boolean; /** Optional CORS config — requires @fastify/cors peer dep */ cors?: Record; openapi?: Pick; setErrorHandler?: (server: FastifyInstance) => void; registerRoutes?: (server: FastifyInstance) => Promise; /** Called AFTER correlation hook — request.kbLogger is already set */ onRequest?: (req: FastifyRequest, reply: FastifyReply) => Promise; /** * When true, skip registering the 5 standard observability routes * (/health, /ready, /metrics, /observability/describe, /observability/health). * Use when the service needs fully custom implementations of these routes. */ skipStandardRoutes?: boolean; /** * Optional custom readiness check. When provided, /ready returns 200 if the * check passes and 503 otherwise. When omitted, /ready delegates to buildHealth(). */ readyCheck?: () => ServiceReadyResponse | { ready: boolean; [key: string]: unknown; }; /** * Optional custom /health payload. When provided, /health returns it verbatim * (200). When omitted, /health delegates to observability.buildHealth(). Use * to preserve a service's legacy health contract (e.g. { status, service, ts }). */ healthResponse?: () => unknown; } declare function createDaemonServer(opts: DaemonServerOptions): Promise; export { type DaemonServerOptions, type ErrorResponse, ErrorResponseSchema, type HttpLogContextInput, HttpObservabilityCollector, type HttpObservabilityCollectorOptions, type JsonSchemaRef, type ObservabilityCollectorLike, OkResponseSchema, type OpenAPIOptions, OperationMetricsTracker, type OperationObserver, type OperationStatus, type SchemaRef, type ServiceReadyResponse, type VersionedObservabilityShape, type ZodSchemaRef, createDaemonServer, createHttpLogger, createServiceObservabilityDescribe, createServiceObservabilityHealth, createServiceReadyResponse, getListenOptions, metricLine, normalizeObservabilityRoute, registerOpenAPI, resolveObservabilityInstanceId, resolveSchemaRef };