@japa/api-client
Version:
Browser and API testing client for Japa. Built on top of Playwright
272 lines (271 loc) • 8.69 kB
TypeScript
import { type ReadStream } from 'node:fs';
import { type Response } from 'superagent';
import { type EventEmitter } from 'node:events';
import { type ApiRequest } from './request.js';
import { type ApiResponse } from './response.js';
/**
* Represents a file from a multipart response.
* The interface is copied from formidable's PersistentFile type, as superagent
* uses formidable for parsing response files.
*/
export interface SuperAgentResponseFile extends EventEmitter {
open(): void;
toJSON(): {
length: number;
mimetype: string | null;
mtime: Date | null;
size: number;
filepath: string;
originalFilename: string | null;
hash?: string | null;
};
toString(): string;
write(buffer: string, cb: () => void): void;
end(cb: () => void): void;
destroy(): void;
}
/**
* Superagent response parser callback method. The method
* receives an instance of the Node.js readable stream
*/
export type SuperAgentParser = (res: Response, callback: (err: Error | null, body: any) => void) => void;
/**
* Superagent request serializer. The method receives the
* request body object and must serialize it to a string
*/
export type SuperAgentSerializer = (obj: any) => string;
/**
* Allowed multipart values
*/
export type MultipartValue = Blob | Buffer | ReadStream | string | boolean | number;
/**
* Custom cookies serializer for processing request and response cookies.
*/
export type CookiesSerializer = {
/**
* Process a cookie from the response
*
* @param key - The cookie name
* @param value - The cookie value
* @param response - The API response instance
*/
process(key: string, value: any, response: ApiResponse): any;
/**
* Prepare a cookie for the request
*
* @param key - The cookie name
* @param value - The cookie value
* @param request - The API request instance
*/
prepare(key: string, value: any, request: ApiRequest): string;
};
/**
* Configuration accepted by the ApiRequest class.
*/
export type RequestConfig = {
/** The HTTP method */
method: string;
/** The endpoint or URL path */
endpoint: string;
/** Optional base URL to prepend to the endpoint */
baseUrl?: string;
/** Lifecycle hooks for the request */
hooks?: {
setup: SetupHandler[];
teardown: TeardownHandler[];
};
/** Custom serializers */
serializers?: {
cookie?: CookiesSerializer;
};
};
/**
* Represents a parsed cookie from an HTTP response.
*/
export type ResponseCookie = {
/** The cookie name */
name: string;
/** The cookie value */
value: any;
/** The cookie path */
path?: string;
/** The cookie domain */
domain?: string;
/** The cookie expiration date */
expires?: Date;
/** The cookie max age in seconds */
maxAge?: number;
/** Whether the cookie is secure (HTTPS only) */
secure?: boolean;
/** Whether the cookie is HTTP only (not accessible via JavaScript) */
httpOnly?: boolean;
/** The cookie SameSite attribute */
sameSite?: string;
};
/**
* A collection of response cookies indexed by name.
*/
export type ResponseCookies = Record<string, ResponseCookie>;
/**
* Represents a cookie to be sent with a request.
*/
export type RequestCookie = {
/** The cookie name */
name: string;
/** The cookie value */
value: any;
};
/**
* A collection of request cookies indexed by name.
*/
export type RequestCookies = Record<string, RequestCookie>;
/**
* Cleanup handler for setup hooks, called after the setup hook completes or errors.
*/
export type SetupCleanupHandler = (error: any | null, request: ApiRequest) => any | Promise<any>;
/**
* Setup handler called before making an HTTP request.
* Can optionally return a cleanup handler.
*/
export type SetupHandler = (request: ApiRequest) => any | SetupCleanupHandler | Promise<any> | Promise<SetupCleanupHandler>;
/**
* Cleanup handler for teardown hooks, called after the teardown hook completes or errors.
*/
export type TeardownCleanupHandler = (error: any | null, response: ApiResponse) => any | Promise<any>;
/**
* Teardown handler called after receiving an HTTP response.
* Can optionally return a cleanup handler.
*/
export type TeardownHandler = (response: ApiResponse) => any | TeardownCleanupHandler | Promise<any> | Promise<TeardownCleanupHandler>;
/**
* Lifecycle hooks configuration for API requests.
*/
export type ApiRequestHooks = {
setup: [Parameters<SetupHandler>, Parameters<SetupCleanupHandler>];
teardown: [Parameters<TeardownHandler>, Parameters<TeardownCleanupHandler>];
};
/**
* User-augmentable routes registry for type-safe API client.
* Augment this interface to enable type-safe endpoints.
*
* @example
* declare module '@japa/api-client' {
* interface UserRoutesRegistry {
* 'users.show': {
* methods: ['GET', 'HEAD']
* pattern: '/users/:id'
* types: {
* params: { id: string }
* query: {}
* body: {}
* response: { user: { id: string; name: string } }
* }
* }
* }
* }
*/
export interface RoutesRegistry {
}
/**
* Defines the structure of a route in the routes registry.
*/
export interface RouteDefinition {
/** Allowed HTTP methods for this route */
methods: readonly string[];
/** The URL pattern for this route */
pattern: string;
/** Type definitions for the route */
types: {
/** Route parameter types */
params: Record<string, any>;
/** Query string parameter types */
query: Record<string, any>;
/** Request body type */
body: Record<string, any>;
/** Response body type */
response: any;
};
}
/**
* Function that builds a route URL and method from a route name and parameters.
*/
export type RouteBuilder = (name: string, params?: any[] | Record<string, any>) => {
/** The built URL */
url: string;
/** The HTTP method */
method: string;
};
/**
* Check if an object type is empty (has no keys)
*/
export type IsEmptyObject<T> = keyof T extends never ? true : false;
/**
* Check if user has augmented the registry
*/
type HasUserRegistry = keyof RoutesRegistry extends never ? false : true;
/**
* Find a route definition by its pattern
*/
type FindRouteByPattern<P extends string> = {
[K in keyof RoutesRegistry]: RoutesRegistry[K] extends {
pattern: P;
} ? RoutesRegistry[K] : never;
}[keyof RoutesRegistry];
/**
* Extract all patterns from the registry
*/
type AllPatterns = RoutesRegistry[keyof RoutesRegistry] extends {
pattern: infer P;
} ? P extends string ? P : never : never;
/**
* Helper to extract a type from a named route
*/
type InferFromRoute<Name extends keyof RoutesRegistry, Key extends 'params' | 'query' | 'body' | 'response'> = RoutesRegistry[Name] extends {
types: infer Types;
} ? Key extends keyof Types ? Types[Key] : never : never;
/**
* Helper to extract a type from a route pattern
*/
type InferFromPattern<P extends string, Key extends 'body' | 'query' | 'response'> = HasUserRegistry extends true ? [FindRouteByPattern<P>] extends [never] ? any : FindRouteByPattern<P> extends {
types: infer Types;
} ? Key extends keyof Types ? Types[Key] : any : any : any;
export type InferRouteParams<Name extends keyof RoutesRegistry> = InferFromRoute<Name, 'params'>;
/**
* Infer the query type from a named route.
*/
export type InferRouteQuery<Name extends keyof RoutesRegistry> = InferFromRoute<Name, 'query'>;
/**
* Infer the body type from a named route.
*/
export type InferRouteBody<Name extends keyof RoutesRegistry> = InferFromRoute<Name, 'body'>;
/**
* Infer the response type from a named route.
*/
export type InferRouteResponse<Name extends keyof RoutesRegistry> = InferFromRoute<Name, 'response'>;
/**
* Infer the body type from a route pattern.
*/
export type InferBody<P extends string> = InferFromPattern<P, 'body'>;
/**
* Infer the response type from a route pattern.
*/
export type InferResponse<P extends string> = InferFromPattern<P, 'response'>;
/**
* Infer the query type from a route pattern.
*/
export type InferQuery<P extends string> = InferFromPattern<P, 'query'>;
/**
* Valid patterns (restricted to known patterns if registry is configured)
*/
export type ValidPattern = HasUserRegistry extends true ? AllPatterns : string;
/**
* Configuration options for the apiClient plugin.
*/
export interface ApiClientPluginOptions {
/** The base URL for all requests */
baseURL?: string;
}
export {};
/**
* Infer the params type from a named route.
*/