UNPKG

@lokalise/api-contracts

Version:
135 lines (134 loc) 7 kB
import type { z } from 'zod/v4'; import type { HttpStatusCode, WildcardStatusCodeKey } from '../HttpStatusCodes.ts'; export type ResponseOptions = { readonly description?: string; }; export type SseSchemaByEventName = Record<string, z.ZodType>; export type TypedJsonResponse = z.ZodType; export declare const isJsonResponse: (value: ApiContractResponse | ResponseEntry) => value is TypedJsonResponse; export type TypedApiContractResponse = TypedJsonResponse; export type ApiContractResponse = TypedApiContractResponse; /** Opaque binary body; the media type is supplied by the content-map key. */ export type BlobBody = { readonly _tag: 'BlobBody'; }; export declare const blobBody: () => BlobBody; /** * Lazy, single-consume accessor over a `blobResponse()` body — the client-side value a blob * response resolves to. Mirrors the accessor surface of Fetch's `Response`/`Blob`. * * The underlying body is a one-shot stream: the first accessor you call consumes it; calling a * second throws. Pick one. Draining the body (any accessor except a lazy `stream()`, or `cancel()`) * is also what releases the connection — a handle you never touch keeps it open. */ export interface BlobResponseHandle { /** Raw stream, for piping/backpressure. You own draining or cancelling it. */ stream(): ReadableStream<Uint8Array>; /** Buffer the whole body into a `Blob`. The common case; echoes `blobResponse()`. */ blob(): Promise<Blob>; /** Buffer the whole body and decode it as UTF-8 text. */ text(): Promise<string>; /** Buffer the whole body into an `ArrayBuffer`. */ arrayBuffer(): Promise<ArrayBuffer>; /** Discard the body without materializing it, releasing the connection. */ cancel(): Promise<void>; } export declare const isBlobBody: (value: BodyDescriptor) => value is BlobBody; /** Server-Sent Events body; the media type is supplied by the content-map key. */ export type SseBody<T extends SseSchemaByEventName = SseSchemaByEventName> = { readonly _tag: 'SseBody'; readonly schemaByEventName: T; }; export declare const sseBody: <T extends SseSchemaByEventName>(schemaByEventName: T) => SseBody<T>; export declare const isSseBody: (value: BodyDescriptor) => value is SseBody; export declare const isJsonBody: (value: BodyDescriptor) => value is z.ZodType; /** * A value in a {@link ResponseContentMap}; the media type is the map key, so a * descriptor never carries a content type itself. A bare Zod schema is JSON. */ export type BodyDescriptor = z.ZodType | BlobBody | SseBody; /** Commonly used response media types, offered as autocomplete suggestions. */ export type CommonResponseContentType = 'application/json' | 'application/octet-stream' | 'application/pdf' | 'application/x-ndjson' | 'application/xml' | 'application/zip' | 'audio/mpeg' | 'audio/ogg' | 'image/gif' | 'image/jpeg' | 'image/png' | 'image/svg+xml' | 'image/webp' | 'text/csv' | 'text/event-stream' | 'text/html' | 'text/plain' | 'video/mp4' | 'video/webm'; /** * A response media type. Common values are autocompleted; any other string * (e.g. a vendored variant like `application/json+01`) is accepted too. */ export type ResponseContentType = CommonResponseContentType | (string & {}); /** * Maps a response media type (e.g. `application/json`) to the body it carries. * {@link CommonResponseContentType} keys are autocompleted; any other media type is accepted too. */ export type ResponseContentMap = Partial<Record<CommonResponseContentType, BodyDescriptor>> & Record<string, BodyDescriptor>; /** A content-map response carrying a body for one or more media types. */ export type BodyContentResponseEntry = { readonly description?: string; readonly content: ResponseContentMap; readonly allowNoBody?: boolean; }; /** A content-map response that never carries a body. */ export type NoBodyContentResponseEntry = { readonly description?: string; readonly content?: never; readonly allowNoBody: true; }; /** * A content-map response entry. Either a body response (`content` required, * optionally `allowNoBody`) or a no-body response (`allowNoBody: true`, no * `content`). The union forces at least one of `content` / `allowNoBody`. */ export type ResponseEntry = BodyContentResponseEntry | NoBodyContentResponseEntry; export declare const isContentResponseEntry: (value: ApiContractResponse | ResponseEntry) => value is ResponseEntry; /** * Declares a no-body response (e.g. `204`). */ export declare const noBodyResponse: (options?: ResponseOptions) => NoBodyContentResponseEntry; /** * Declares a binary/opaque response for a single media type. */ export declare const blobResponse: <TContentType extends ResponseContentType>(contentType: TContentType, options?: ResponseOptions) => { readonly content: { readonly [K in TContentType]: BlobBody; }; readonly description?: string | undefined; }; /** * Declares a Server-Sent Events response. */ export declare const sseResponse: <T extends SseSchemaByEventName>(schemaByEventName: T, options?: ResponseOptions) => { readonly content: { readonly 'text/event-stream': SseBody<T>; }; readonly description?: string | undefined; }; export type ResponsesByStatusCode = Partial<Record<HttpStatusCode | WildcardStatusCodeKey, ApiContractResponse | ResponseEntry>>; export type ResponseKind = { kind: 'noContent'; } | { kind: 'blob'; } | { kind: 'json'; schema: z.ZodType; } | { kind: 'sse'; schemaByEventName: SseSchemaByEventName; }; /** * Resolves a contract's response entry for a given status code into a concrete `ResponseKind`, * taking the response `content-type` into account. * * Returns `null` when the content-type cannot be matched to any entry in the contract, * indicating the response is unexpected and should be treated as an error by the caller. * * @param schemaEntry - The contract entry for the matched status code (a Zod schema, * `noBodyResponse`, or a content-map entry). * @param contentType - The `content-type` header value from the actual HTTP response, * or `undefined` when the header is absent. * @param strict - When `true` (default), returns `null` if the `content-type` is absent or does * not match the contract entry. When `false`, falls back to the entry's declared kind instead of * returning `null` — only applies to single-entry responses. */ export declare const resolveContractResponse: (schemaEntry: ApiContractResponse | ResponseEntry, contentType: string | undefined, strict?: boolean) => ResponseKind | null; /** * Combines status-code lookup and content-type resolution into a single call. * Lookup precedence: exact code → range key (e.g. `'4xx'`) → `'default'`. * Returns `null` when no entry matches or the content-type cannot be matched. */ export declare function resolveResponseEntry(responsesByStatusCode: ResponsesByStatusCode, statusCode: number, contentType: string | undefined, strictContentType: boolean): ResponseKind | null;