import type { DataClassification } from "../classification.js"; import { type Method } from "../router/router.js"; import type { InferInput, InferOutput, StandardSchemaV1 } from "../schema/standard.js"; import type { Context, IdempotencyConfig, Params, RouteSchema } from "./context.js"; import type { EmptyRegistry, OutputOf, Registry, RouteInfoFor } from "./registry.js"; import { Server } from "./server.js"; type EmptyResponseMap = NonNullable; /** An additional (non-success) response a contract operation can document, e.g. a `404`. */ export interface ResponseDef { readonly description?: string; /** Response body schema (any Standard Schema; a `t` schema yields full JSON Schema in OpenAPI). */ readonly schema?: StandardSchemaV1; /** Content type. Default `application/json`. */ readonly contentType?: string; } /** * One operation in a contract. Input schemas are any Standard Schema; `response` is optional. * * The fields below `response` are **optional OpenAPI metadata** - they don't affect runtime * validation or the inferred handler types, they enrich the document `toOpenAPI` emits. A contract is * the natural home for them: it's the versionable description of the API, decoupled from the impl. */ export interface OperationDef { readonly method: Method; readonly path: string; /** Optional path-params schema (validated + coercible at the boundary; see {@link RouteSchema.params}). */ readonly params?: StandardSchemaV1; /** Optional request-header schema (header names are normalized to lower-case). */ readonly headers?: StandardSchemaV1; readonly body?: StandardSchemaV1; readonly query?: StandardSchemaV1; readonly response?: StandardSchemaV1; /** Declared effect tokens, carried into route reflection and capability assurance. */ readonly capabilities?: readonly string[]; /** Dedupe retries of this operation on an `Idempotency-Key` header (see {@link RouteSchema.idempotency}). */ readonly idempotency?: IdempotencyConfig; /** Per-route transport body cap; see RouteSchema.bodyLimit. */ readonly bodyLimit?: number | "unlimited"; readonly bodyLimitReason?: string; /** Highest data-sensitivity the response carries (see {@link RouteSchema.classification}). */ readonly classification?: DataClassification; /** Inline route-assurance evidence this operation carries (see {@link RouteSchema.assurance}). */ readonly assurance?: readonly string[]; /** Mark this operation a dynamic route family (see {@link RouteSchema.family}). */ readonly family?: boolean; /** Short summary (OpenAPI `summary`). */ readonly summary?: string; /** Longer description (OpenAPI `description`, CommonMark). */ readonly description?: string; /** Grouping tags (OpenAPI `tags`). */ readonly tags?: readonly string[]; /** Mark the operation deprecated. */ readonly deprecated?: boolean; /** Security requirements (names ref `securitySchemes`); `[]` = explicitly public. Omit ⇒ inherit the document default. */ readonly security?: ReadonlyArray>>; /** Request body content type. Default `application/json`. */ readonly requestContentType?: string; /** Content type of the success (`200`) response. Default `application/json`. */ readonly responseContentType?: string; /** Additional responses by status code, e.g. `{ "404": { description: "Not found" } }`. */ readonly responses?: Readonly>; } /** A contract: named operations. Names are the handler keys and OpenAPI operationIds. */ export type ContractShape = Record; /** Client-visible request inputs use the schema's INPUT side (what a caller sends pre-validation, * so a defaulted field stays optional on the wire) - mirroring the inline registry's * `RegistryBody`/`RegistryQuery`. Handler-facing types (ContextForOp) still use the output side. */ type OpBody = O extends { body: infer B extends StandardSchemaV1; } ? InferInput : never; type OpQuery = O extends { query: infer Q extends StandardSchemaV1; } ? InferInput : never; type OpResponse = O extends { response: infer R extends StandardSchemaV1; } ? InferOutput : unknown; /** The body types of a contract op's declared **non-2xx** `responses` (schema-bearing ones), keyed by status. */ type OpErrorBodies> = { [K in keyof Rs as K extends `2${string}` ? never : K extends `${infer N extends number}` ? Rs[K] extends { schema: StandardSchemaV1; } ? N : never : never]: Rs[K] extends { schema: infer S extends StandardSchemaV1; } ? InferOutput : never; }; /** The client-visible error bodies for a contract op as a status-keyed record (`{ 404: Body }`), or * `unknown` when it declares none (mirrors an inline route with no `errors`). */ type OpErrors = O extends { responses: infer Rs extends Record; } ? [keyof OpErrorBodies] extends [never] ? unknown : OpErrorBodies : unknown; /** The schema-bearing status map a contract consumer can use for success and failure narrowing. */ type OpAdditionalResponseMap = O extends { responses: infer Rs extends Record; } ? { [K in keyof Rs as K extends `${infer N extends number}` ? Rs[K] extends { schema: StandardSchemaV1; } ? N : never : never]: Rs[K] extends { schema: infer S extends StandardSchemaV1; } ? InferOutput : never; } : EmptyResponseMap; type OpResponseMap = Omit, 200> & (O extends { response: infer R extends StandardSchemaV1; } ? { 200: InferOutput; } : EmptyResponseMap); /** * RouteInfo as a *decoupled consumer* sees it from the contract alone: the * `output` is the declared `response` schema's type, or `unknown` when none is * declared (the consumer can't know the response without the server). Path/method * levels are mutable and fields are `readonly` - matching the inline registry, so * the two are mutually assignable. */ type RouteInfoForOp = { readonly params: Params; readonly query: OpQuery; readonly body: OpBody; readonly output: OpResponse; readonly responses: OpResponseMap; readonly errors: OpErrors; readonly sse: never; }; /** Re-key the name-keyed ops into the `path → method → RouteInfo` registry. */ export type RegistryFor = { [P in C[keyof C]["path"]]: { [K in keyof C as C[K]["path"] extends P ? C[K]["method"] : never]: RouteInfoForOp; }; }; /** The schema shape an op contributes to its handler context (mirrors inline `RouteSchema`). */ type SchemaForOp = (O extends { body: infer B extends StandardSchemaV1; } ? { body: B; } : Record) & (O extends { query: infer Q extends StandardSchemaV1; } ? { query: Q; } : Record) & (O extends { headers: infer H extends StandardSchemaV1; } ? { headers: H; } : Record) & (O extends { params: infer P extends StandardSchemaV1; } ? { params: P; } : Record); type OpResponseSchemas = O extends { responses: infer Rs extends Record; } ? { [K in keyof Rs as K extends `2${string}` ? never : Rs[K] extends { schema: StandardSchemaV1; } ? K extends `${infer N extends number}` ? N : never : never]: Rs[K] extends { schema: infer S extends StandardSchemaV1; } ? S : never; } : EmptyResponseMap; type RouteSchemaForOp = SchemaForOp & RouteSchema & (O extends { response: infer R extends StandardSchemaV1; } ? { response: R; } : EmptyResponseMap) & (keyof OpResponseSchemas extends never ? EmptyResponseMap : { errors: OpResponseSchemas; }); /** * The handler context for an op - identical to the inline `Context`, so a * handler written for an inline route type-checks unchanged under `implement` * (the graduation guarantee). */ export type ContextForOp = Context & RouteSchema>; type MaybePromise = T | Promise; /** * What a handler may return for an op. When the op declares a `response`, the return is constrained to * that contract shape (or a raw `Response`) - so an `implement`ed backend can't drift from the response * the contract's client was built against. With no `response` it's unconstrained (`unknown`), identical * to before. Purely type-level - erased at compile time, zero runtime cost - and mirrors the inline * route's `ResponseOf` constraint, so a handler graduates inline↔contract unchanged. */ type HandlerReturnForOp = O extends { response: infer R extends StandardSchemaV1; } ? Response | InferOutput : unknown; /** * The handlers `implement` requires: one per operation, typed from the op's input + response contract, * intersected with the host app's accumulated `derive`/`decorate` context - the same * `Context & Ctx` an inline {@link Handler} receives, so a handler graduates either way unchanged. */ export type HandlersFor> = { [K in keyof C]: (context: ContextForOp & Ctx) => MaybePromise>; }; /** * Define a standalone, versionable contract. Identity at runtime (it returns the * contract for type inference via the `const` type parameter, which preserves the * path/method literals) plus boot-time (L2) validation: each operation must use a * known method, a path starting with `/`, and no two operations may share a * `(method, path)`. Deeper path validation (param names, wildcard position) runs * when the contract is `implement`ed. */ export declare function defineContract(contract: C): C; type AnyFn = (...args: never[]) => unknown; /** * The registry produced by `implement`: input from the contract op; `output` is the declared `response` * contract when present (it wins - exactly as in the inline path), else the bound HANDLER's return - so * the implemented server stays route-for-route identical to the equivalent inline server (the * mode-conformance guarantee), and a contract-typed client and a `typeof app`-typed client agree. */ export type RegistryFromImpl, Ctx = NonNullable, HookOutput = never> = { [P in C[keyof C]["path"]]: { [K in keyof C as C[K]["path"] extends P ? C[K]["method"] : never]: H[K] extends AnyFn ? RouteInfoFor, OutputOf, HookOutput> : RouteInfoFor, unknown, HookOutput>; }; }; /** * Bind handlers to a contract, producing a real {@link Server} you can `.listen()` * or `.fetch()`. Each op is registered through the same path as the inline * builder, so the result is identical to writing the routes inline - handlers * lift over **unchanged** ("graduation"), and body/query schemas validate at the * request boundary exactly as in inline mode. * * Pass a pre-configured `app` to give the contract's routes a middleware chain. A route captures the * server's `derive`/`decorate`/assurance chain **at registration**, so anything applied to the * returned server afterwards reaches the contract's routes not at all - the app must already carry it: * * ```ts * const app = implement(contract, handlers, server().use(auth).derive(sessionOf)) * ``` * * That is also the seam that lets `nifra assure` prove rather than merely classify a contract-first * app: the plugin that installs the enforcement is what declares the evidence * ({@link withRouteAssurance}), and only a plugin installed *before* registration is captured. Handlers * then see the app's `Ctx`, and the returned server keeps any routes the app already had. */ export declare function implement, R extends Registry = EmptyRegistry, Ctx = NonNullable, HookOutput = never>(contract: C, handlers: H, app?: Server): Server, Ctx, HookOutput>; export {}; //# sourceMappingURL=contract.d.ts.map