@lokalise/api-contracts
Version:
135 lines (134 loc) • 7 kB
TypeScript
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;