@beignet/core
Version:
Core framework primitives for Beignet
304 lines • 12.9 kB
TypeScript
import { type ContractLike, type HttpContractConfig, type ResolveContract, type StandardErrorResponseBody, type StandardSchemaV1 } from "../contracts/index.js";
import type { CallArgs, ClientConfig, EndpointResult, InferEndpointErrorResponse, InferEndpointErrorResponseByStatus, InferEndpointErrorStatus, InferSuccessResponse } from "./types.js";
/**
* Source category for a `ContractError`.
*/
export type ContractErrorSource = "http" | "client" | "network" | "contract";
/**
* Narrow a contract error union by source.
*/
export type ContractErrorWithSource<TError, TSource extends ContractErrorSource> = Extract<TError, {
readonly source: TSource;
}>;
/**
* Narrow a contract error union by HTTP status.
*/
export type ContractErrorWithStatus<TError, TStatus extends number> = Extract<TError, {
readonly status: TStatus;
}>;
/**
* Narrow a contract error union by Beignet error code.
*/
export type ContractErrorWithCode<TError, TCode extends string> = Extract<TError, {
readonly code: TCode;
}>;
/**
* Error for a non-2xx HTTP response.
*/
export type HttpContractError<TBody = unknown, TStatus extends number = number> = ContractError<TBody, TStatus, "http"> & {
readonly source: "http";
readonly status: TStatus;
readonly response: Response;
};
/**
* Error created by the client before a network request is made.
*/
export type ClientContractError = ContractError<undefined, undefined, "client"> & {
readonly source: "client";
readonly status: undefined;
readonly response: undefined;
};
/**
* Error created when the network request itself fails.
*/
export type NetworkContractError = ContractError<undefined, undefined, "network"> & {
readonly source: "network";
readonly status: undefined;
readonly response: undefined;
};
/**
* Error created when a response violates the contract.
*/
export type ResponseContractError<TBody = unknown, TStatus extends number | undefined = number | undefined> = ContractError<TBody, TStatus, "contract"> & {
readonly source: "contract";
readonly status: TStatus;
};
/**
* Union of all Beignet client error variants.
*/
export type AnyContractError = HttpContractError | ClientContractError | NetworkContractError | ResponseContractError;
type EndpointCatalogErrorDefinition<TContract extends HttpContractConfig> = TContract["metadata"] extends {
errors: infer TErrors;
} ? TErrors extends Record<string, {
code: string;
status: number;
message: string;
}> ? TErrors[keyof TErrors] : never : never;
type InferErrorDefinitionDetails<TDef> = TDef extends {
details: StandardSchemaV1;
} ? StandardSchemaV1.InferOutput<TDef["details"]> : unknown;
type StandardErrorBodyForDefinition<TDef extends {
code: string;
}> = StandardErrorResponseBody & {
code: TDef["code"];
details?: InferErrorDefinitionDetails<TDef>;
};
type EndpointCatalogContractError<TContract extends HttpContractConfig> = EndpointCatalogErrorDefinition<TContract> extends infer TDef ? TDef extends {
code: string;
status: number;
} ? HttpContractError<StandardErrorBodyForDefinition<TDef>, TDef["status"]> & {
readonly code: TDef["code"];
readonly details?: InferErrorDefinitionDetails<TDef>;
} : never : never;
/**
* Infer route-owned error catalog codes declared by a contract.
*/
export type InferEndpointErrorCode<TContract extends HttpContractConfig> = EndpointCatalogErrorDefinition<TContract>["code"];
/**
* Infer the full typed error union for a contract endpoint.
*/
export type InferEndpointContractError<TContract extends HttpContractConfig> = EndpointCatalogContractError<TContract> | {
[TStatus in InferEndpointErrorStatus<TContract>]: HttpContractError<InferEndpointErrorResponseByStatus<TContract, TStatus>, TStatus>;
}[InferEndpointErrorStatus<TContract>] | HttpContractError<InferEndpointErrorResponse<TContract>, number> | ClientContractError | NetworkContractError | ResponseContractError<InferEndpointErrorResponse<TContract>, number | undefined>;
/**
* Error thrown by Beignet contract clients.
*
* `source` distinguishes HTTP error responses, client-side request mistakes,
* network failures, and contract drift such as response validation failures.
*/
export declare class ContractError<TBody = unknown, TStatus extends number | undefined = number | undefined, TSource extends ContractErrorSource = ContractErrorSource> extends Error {
/**
* Error source category.
*/
readonly source: TSource;
/**
* HTTP status when a response was available.
*/
readonly status: TStatus;
/**
* Stable error code.
*/
readonly code?: string;
/**
* Parsed response body when available.
*/
readonly body?: TBody;
/**
* Structured error details when available.
*/
readonly details?: unknown;
/**
* Native fetch response when available.
*/
readonly response?: Response;
cause?: unknown;
constructor(args: {
source: TSource;
status?: TStatus;
code?: string;
message: string;
body?: TBody;
details?: unknown;
response?: Response;
cause?: unknown;
});
/**
* Check whether this error has a specific HTTP status code.
*/
hasStatus<S extends number>(status: S): this is this & {
readonly status: S;
};
/**
* Check whether this error came from a specific source.
*/
hasSource<S extends ContractErrorSource>(source: S): this is this & {
readonly source: S;
};
/**
* Check whether this error has a specific error code.
*/
hasCode<C extends string>(code: C): this is this & {
code: C;
};
}
/**
* Type guard to check if an unknown error is a ContractError,
* optionally narrowing by HTTP status code.
*
* @example
* ```ts
* try { await endpoint.call(...) }
* catch (err) {
* if (isContractError(err, 404)) {
* // err.status is 404
* }
* if (isContractError(err)) {
* // err is ContractError
* }
* }
* ```
*/
export declare function isContractError(err: unknown): err is AnyContractError;
export declare function isContractError<S extends number>(err: unknown, status: S): err is HttpContractError<unknown, S>;
export declare function isContractError<TError extends AnyContractError, S extends number>(err: TError, criteria: {
status: S;
}): err is ContractErrorWithStatus<TError, S>;
export declare function isContractError<TError extends AnyContractError, S extends ContractErrorSource>(err: TError, criteria: {
source: S;
}): err is ContractErrorWithSource<TError, S>;
export declare function isContractError<TError extends ContractError, Status extends number, Source extends ContractErrorSource>(err: TError, criteria: {
status: Status;
source: Source;
}): err is ContractErrorWithSource<ContractErrorWithStatus<TError, Status>, Source>;
export declare function isContractError<TError extends ContractError, Code extends string>(err: TError, criteria: {
code: Code;
}): err is ContractErrorWithCode<TError, Code>;
export declare function isContractError<TError extends ContractError, Status extends number, Code extends string>(err: TError, criteria: {
status: Status;
code: Code;
}): err is ContractErrorWithCode<ContractErrorWithStatus<TError, Status>, Code>;
export declare function isContractError<TError extends ContractError, Source extends ContractErrorSource, Code extends string>(err: TError, criteria: {
source: Source;
code: Code;
}): err is ContractErrorWithCode<ContractErrorWithSource<TError, Source>, Code>;
export declare function isContractError<TError extends ContractError, Status extends number, Source extends ContractErrorSource, Code extends string>(err: TError, criteria: {
status: Status;
source: Source;
code: Code;
}): err is ContractErrorWithCode<ContractErrorWithSource<ContractErrorWithStatus<TError, Status>, Source>, Code>;
export declare function isContractError<S extends number>(err: unknown, criteria: {
status: S;
}): err is ContractError<unknown, S>;
export declare function isContractError<S extends ContractErrorSource>(err: unknown, criteria: {
source: S;
}): err is ContractError<unknown, number | undefined, S>;
export declare function isContractError<Status extends number, Source extends ContractErrorSource>(err: unknown, criteria: {
status: Status;
source: Source;
}): err is ContractErrorWithSource<HttpContractError<unknown, Status>, Source>;
export declare function isContractError<Code extends string>(err: unknown, criteria: {
code: Code;
}): err is ContractError<unknown, number | undefined> & {
readonly code: Code;
};
/**
* Typed client endpoint for one contract.
*/
export declare class Endpoint<TContract extends HttpContractConfig, TProvidedHeaders extends string = never> {
private contract;
private config;
constructor(contract: TContract, config: ClientConfig<TProvidedHeaders>);
/**
* Check whether an unknown error is a `ContractError` for this endpoint.
*/
isError(err: unknown): err is InferEndpointContractError<TContract>;
isError<S extends InferEndpointErrorStatus<TContract>>(err: unknown, status: S): err is ContractErrorWithStatus<InferEndpointContractError<TContract>, S>;
isError<S extends number>(err: unknown, status: S): err is HttpContractError<unknown, S>;
isError<S extends InferEndpointErrorStatus<TContract>>(err: unknown, criteria: {
status: S;
}): err is ContractErrorWithStatus<InferEndpointContractError<TContract>, S>;
isError<S extends ContractErrorSource>(err: unknown, criteria: {
source: S;
}): err is ContractErrorWithSource<InferEndpointContractError<TContract>, S>;
isError<C extends InferEndpointErrorCode<TContract>>(err: unknown, criteria: {
code: C;
}): err is ContractErrorWithCode<InferEndpointContractError<TContract>, C>;
isError<C extends string>(err: unknown, criteria: {
code: C;
}): err is InferEndpointContractError<TContract> & {
readonly code: C;
};
isError<Status extends InferEndpointErrorStatus<TContract>, Source extends ContractErrorSource>(err: unknown, criteria: {
status: Status;
source: Source;
}): err is ContractErrorWithSource<ContractErrorWithStatus<InferEndpointContractError<TContract>, Status>, Source>;
isError<Status extends InferEndpointErrorStatus<TContract>, C extends InferEndpointErrorCode<TContract>>(err: unknown, criteria: {
status: Status;
code: C;
}): err is ContractErrorWithCode<ContractErrorWithStatus<InferEndpointContractError<TContract>, Status>, C>;
isError<Source extends ContractErrorSource, C extends InferEndpointErrorCode<TContract>>(err: unknown, criteria: {
source: Source;
code: C;
}): err is ContractErrorWithCode<ContractErrorWithSource<InferEndpointContractError<TContract>, Source>, C>;
isError<Status extends InferEndpointErrorStatus<TContract>, Source extends ContractErrorSource, C extends InferEndpointErrorCode<TContract>>(err: unknown, criteria: {
status: Status;
source: Source;
code: C;
}): err is ContractErrorWithCode<ContractErrorWithSource<ContractErrorWithStatus<InferEndpointContractError<TContract>, Status>, Source>, C>;
/**
* Call the endpoint and return the parsed success body.
*
* Throws `ContractError` when the request fails or the response violates the
* contract.
*/
call(...callArgs: CallArgs<TContract, TProvidedHeaders>): Promise<InferSuccessResponse<TContract>>;
/**
* Call the endpoint and return a typed result instead of throwing
* `ContractError`.
*/
safeCall(...callArgs: CallArgs<TContract, TProvidedHeaders>): Promise<EndpointResult<TContract, InferEndpointContractError<TContract>>>;
/**
* Build the full URL with path and query parameters
*/
private buildUrl;
private serializeQueryParam;
private invalidQueryParam;
/**
* Build request headers
*/
private buildHeaders;
/**
* Create a contract error
*/
private createError;
}
/**
* Client for making contract-based requests.
*/
export declare class Client<TProvidedHeaders extends string = never> {
private config;
constructor(config: ClientConfig<TProvidedHeaders>);
/**
* Create an endpoint wrapper for a contract.
*
* Accepts either a plain `HttpContractConfig` or a builder with a `.config`
* property.
*/
endpoint<TContractLike extends ContractLike>(contract: TContractLike): Endpoint<ResolveContract<TContractLike>, TProvidedHeaders>;
}
/**
* Create a configured Beignet client.
*/
export declare function createClient<const TProvidedHeaders extends string = never>(config?: ClientConfig<TProvidedHeaders>): Client<TProvidedHeaders>;
export {};
//# sourceMappingURL=client.d.ts.map