import type { StandardSchemaV1 } from "../schema/standard-schema.ts"; import { isStandardJSONSchema, materializeStandardJSONSchema, type CombinedSchemaV1, type StandardJSONSchemaV1, } from "../schema/standard-json-schema.ts"; export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; export interface ContractSchemas { body?: StandardSchemaV1; query?: StandardSchemaV1; params?: StandardSchemaV1; headers?: StandardSchemaV1; response?: StandardSchemaV1; } export interface Contract< Method extends HttpMethod = HttpMethod, Path extends string = string, Schemas extends ContractSchemas = ContractSchemas, > { readonly method: Method; readonly path: Path; readonly schemas: Schemas; } export type AnyContract = Contract; export type ContractSchemaKey = keyof ContractSchemas; export type RequestContractSchemaKey = Exclude; export type SerializableContractSchema = CombinedSchemaV1< Input, Output >; export interface SerializableContractSchemas { body?: SerializableContractSchema; query?: SerializableContractSchema; params?: SerializableContractSchema; headers?: SerializableContractSchema; response?: SerializableContractSchema; } export interface MaterializedContractSchemas { body?: Record; query?: Record; params?: Record; headers?: Record; response?: Record; } type InferSchemaInput = Schema extends StandardSchemaV1 ? Input : Fallback; type InferSchemaOutput = Schema extends StandardSchemaV1 ? Output : Fallback; type ContractSchema< TContract extends AnyContract, TKey extends keyof ContractSchemas, > = TContract["schemas"][TKey]; export type InferContractBodyInput = InferSchemaInput< ContractSchema >; export type InferContractQueryInput = InferSchemaInput< ContractSchema >; export type InferContractParamsInput = InferSchemaInput< ContractSchema >; export type InferContractHeadersInput = InferSchemaInput< ContractSchema >; export type InferContractResponseOutput = InferSchemaOutput< ContractSchema >; function getSchemaMaterializeDirection(key: ContractSchemaKey): "input" | "output" { return key === "response" ? "output" : "input"; } export function isSerializableContractSchema( schema: unknown, ): schema is SerializableContractSchema { return isStandardJSONSchema(schema); } export function materializeContractSchema( schema: StandardSchemaV1, key: ContractSchemaKey, options: StandardJSONSchemaV1.Options, ): Record { if (!isSerializableContractSchema(schema)) { throw new Error( `Contract schema '${key}' does not implement StandardJSONSchemaV1 and cannot be serialized`, ); } return materializeStandardJSONSchema(schema, getSchemaMaterializeDirection(key), options); } export function materializeContractSchemas( schemas: ContractSchemas, options: StandardJSONSchemaV1.Options, ): MaterializedContractSchemas { const materialized: MaterializedContractSchemas = {}; for (const key of ["body", "query", "params", "headers", "response"] as const) { const schema = schemas[key]; if (!schema) { continue; } materialized[key] = materializeContractSchema(schema, key, options); } return materialized; } export function defineContract< const Method extends HttpMethod, const Path extends string, const Schemas extends ContractSchemas, >(contract: { method: Method; path: Path; schemas: Schemas }): Contract { return contract; }