UNPKG

@transformgovsg/zing

Version:
487 lines (476 loc) 18.8 kB
import { IncomingMessage, ServerResponse } from 'node:http'; interface Options { /** * The maximum size of the request body in bytes. * * @default 1_048_576 */ maxBodySize: number; } /** * Base class for all errors. */ declare class BaseError extends Error { constructor(message: string); } /** * Thrown when the request content is too large. */ declare class ContentTooLargeError extends BaseError { readonly type = "CONTENT_TOO_LARGE_ERROR"; constructor(); } /** * Thrown when the request content type is not supported. */ declare class UnsupportedContentTypeError extends BaseError { readonly type = "UNSUPPORTED_CONTENT_TYPE_ERROR"; constructor(); } /** * Thrown when the request payload is not a valid JSON object. */ declare class MalformedJSONError extends BaseError { readonly type = "MALFORMED_JSON_ERROR"; constructor(); } /** * Thrown when an unexpected error occurs. */ declare class InternalServerError extends BaseError { readonly type = "INTERNAL_SERVER_ERROR"; constructor(cause: unknown); } interface IResult<T, E extends Error> { /** * Returns `true` if the result is an {@link Ok} variant of {@link Result} */ isOk(): this is Ok<T, E>; /** * Returns `true` if the result is an {@link Err} variant of {@link Result} */ isErr(): this is Err<T, E>; /** * Unwraps the {@link Result} and returns the value if it is an {@link Ok} * variant, otherwise throws the error. */ unwrap(): T; /** * Unwraps the {@link Result} and returns the value if it is an {@link Ok} * variant, otherwise returns the provided default value. */ unwrapOr<D>(defaultValue: D): T | D; } declare class Ok<T, E extends Error> implements IResult<T, E> { readonly value: T; constructor(value: T); isOk(): this is Ok<T, E>; isErr(): this is Err<T, E>; unwrap(): T; unwrapOr<D>(_defaultValue: D): T | D; } declare class Err<T, E extends Error> implements IResult<T, E> { readonly error: E; constructor(error: E); isOk(): this is Ok<T, E>; isErr(): this is Err<T, E>; unwrap(): T; unwrapOr<D>(defaultValue: D): T | D; } type Result<T, E extends Error> = Ok<T, E> | Err<T, E>; declare class Request { #private; readonly node: IncomingMessage; constructor(req: IncomingMessage, options: Options); /** * Returns the protocol of the request. */ get protocol(): 'http' | 'https'; /** * Returns the pathname of the request. */ get pathname(): string; /** * Returns the HTTP method of the request. */ get method(): HTTPMethod; /** * Returns the value of the given key from the request-scoped key-value * store. If the key is not found and no default value is provided, `null` * is returned. * * @param key - The key to get the value of. * @param defaultValue - An optional default value to return if the key is not found. */ get<T = unknown>(key: string, defaultValue?: T): T | null; /** * Stores a value in the request-scoped key-value store. The store persists * only for the duration of the current request. If the value is `undefined` * or `null`, the key is removed from the store. * * @param key - The key to store the value under. * @param value - The value to store. */ set(key: string, value: unknown): void; /** * Returns the value of the given cookie name from the request. If the * cookie is not found and no default value is provided, `null` is returned. * * @param name - The name of the cookie to get the value of. * @param defaultValue - An optional default value to return if the cookie is not found. */ cookie(name: string, defaultValue?: string): string | null; /** * Returns the value of the given parameter name from the request. If * the parameter is not found and no default value is provided, `null` is * returned. * * @param name - The name of the parameter to get the value of. * @param defaultValue - An optional default value to return if the parameter is not found. */ param(name: string, defaultValue?: string): string | null; /** * Returns the value of the first occurrence of the given query name from * the request. If the query is not found and no default value is provided, * `null` is returned. * * @param name - The name of the query to get the value of. * @param defaultValue - An optional default value to return if the query is not found. */ query(name: string, defaultValue?: string): string | null; /** * Returns the values of all occurrences of the given query name from the * request. If the query is not found and no default value is provided, `null` * is returned. * * @param name - The name of the query to get the values of. * @param defaultValue - An optional default value to return if the query is not found. */ queries(name: string, defaultValue?: string[]): string[] | null; /** * Returns the value of the given header name from the request. If the * header is not found and no default value is provided, `null` is returned. * * @param name - The name of the header to get the value of. * @param defaultValue - An optional default value to return if the header is not found. */ header(name: string, defaultValue?: string): string | null; /** * Returns a {@link Result} with the body of the request as a {@link Uint8Array} * or `null` if the HTTP method is not `PATCH`, `POST`, or `PUT`. * * Errors that may be returned: * - {@link ContentTooLargeError} - If the body is too large. * - {@link InternalServerError} - If an error occurs while reading the body. */ body(): Promise<Result<Uint8Array | null, ContentTooLargeError | InternalServerError>>; /** * Returns a {@link Result} with the body of the request as a string or * `null` if the HTTP method is not `PATCH`, `POST`, or `PUT`. * * Errors that may be returned: * - {@link ContentTooLargeError} - If the body is too large. * - {@link InternalServerError} - If an error occurs while reading the body. * - {@link UnsupportedContentTypeError} - If the content type is not `text/plain`. */ text(): Promise<Result<string | null, ContentTooLargeError | InternalServerError | UnsupportedContentTypeError>>; /** * Returns a {@link Result} with the body of the request as a JSON object or * `null` if the HTTP method is not `PATCH`, `POST`, or `PUT`. * * Errors that may be returned: * - {@link ContentTooLargeError} - If the body is too large. * - {@link InternalServerError} - If an error occurs while reading the body. * - {@link UnsupportedContentTypeError} - If the content type is not `application/json`. * - {@link MalformedJSONError} - If the body is not valid JSON. */ json<T = unknown>(): Promise<Result<T | null, ContentTooLargeError | InternalServerError | UnsupportedContentTypeError | MalformedJSONError>>; } /** * The options for serialising a cookie. */ interface SerialiseOptions { /** * The path for which the cookie is valid. */ path?: string; /** * The domain for which the cookie is valid. */ domain?: string; /** * The date and time when the cookie will expire. * If `expires` and `maxAge` are both provided, `maxAge` will take precedence. */ expires?: Date; /** * The maximum age of the cookie in seconds. * If `expires` and `maxAge` are both provided, `maxAge` will take precedence. * * - `maxAge` = 0: No `Max-Age` header will be set. * - `maxAge` > 0: The cookie will expire after the given number of seconds. * - `maxAge` < 0: The cookie will expire immediately, equivalent `Max-Age=0`. */ maxAge?: number; /** * Whether the cookie is only accessible via HTTPS. */ secure?: boolean; /** * Whether the cookie is only accessible via HTTP(S) requests and not via * JavaScript. */ httpOnly?: boolean; /** * The `same-site` attribute for the cookie. */ sameSite?: 'strict' | 'lax' | 'none'; } /** * A list of HTTP status codes. Based on RFC 9110. * See https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status */ declare const HTTPStatusCode: { readonly Continue: 100; readonly SwitchingProtocols: 101; readonly Processing: 102; readonly EarlyHints: 103; readonly OK: 200; readonly Created: 201; readonly Accepted: 202; readonly NonAuthoritativeInformation: 203; readonly NoContent: 204; readonly ResetContent: 205; readonly PartialContent: 206; readonly MultiStatus: 207; readonly AlreadyReported: 208; readonly IMUsed: 226; readonly MultipleChoices: 300; readonly MovedPermanently: 301; readonly Found: 302; readonly SeeOther: 303; readonly NotModified: 304; readonly TemporaryRedirect: 307; readonly PermanentRedirect: 308; readonly BadRequest: 400; readonly Unauthorized: 401; readonly PaymentRequired: 402; readonly Forbidden: 403; readonly NotFound: 404; readonly MethodNotAllowed: 405; readonly NotAcceptable: 406; readonly ProxyAuthenticationRequired: 407; readonly RequestTimeout: 408; readonly Conflict: 409; readonly Gone: 410; readonly LengthRequired: 411; readonly PreconditionFailed: 412; readonly ContentTooLarge: 413; readonly URITooLong: 414; readonly UnsupportedMediaType: 415; readonly RangeNotSatisfiable: 416; readonly ExpectationFailed: 417; readonly ImATeapot: 418; readonly MisdirectedRequest: 421; readonly UnprocessableContent: 422; readonly Locked: 423; readonly FailedDependency: 424; readonly TooEarly: 425; readonly UpgradeRequired: 426; readonly PreconditionRequired: 428; readonly TooManyRequests: 429; readonly RequestHeaderFieldsTooLarge: 431; readonly UnavailableForLegalReasons: 451; readonly InternalServerError: 500; readonly NotImplemented: 501; readonly BadGateway: 502; readonly ServiceUnavailable: 503; readonly GatewayTimeout: 504; readonly HTTPVersionNotSupported: 505; readonly VariantAlsoNegotiates: 506; readonly InsufficientStorage: 507; readonly LoopDetected: 508; readonly NotExtended: 510; readonly NetworkAuthenticationRequired: 511; }; declare class Response { readonly node: ServerResponse; constructor(res: ServerResponse); /** * Returns `true` if the response has been sent, otherwise `false`. */ get finished(): boolean; /** * Sends the HTTP response with the specified status code and ends the * response. If the response has already been sent, this method is a no-op * and does nothing. * * @param code - The HTTP status code to send (e.g., 200, 404, 500). */ status(code: (typeof HTTPStatusCode)[keyof typeof HTTPStatusCode]): void; /** * Sends a 200 status code to the response and ends the response. If the * response has already been sent, this method is a no-op and does nothing. * * This method is a shorthand for `status(HTTPStatusCode.OK)`. */ ok(): void; /** * Sets a cookie on the response. If the response has already been sent, * this method does nothing. * * @param name - The name of the cookie. * @param value - The value of the cookie. * @param options - The options for the cookie. */ cookie(name: string, value: string, options?: SerialiseOptions): void; /** * Sets the given header key and value on the response. If the response has * already been sent, this method does nothing. * * @param key - The key of the header to set. * @param value - The value of the header to set. */ header<Key extends Exclude<HTTPHeaderKey, 'set-cookie'>>(key: Key, value: HTTPHeaderValue<Key>): void; /** * Sends a JSON response with the specified status code and ends the response. * If the response has already been sent, this method is a no-op and does * nothing. * * @param code - The HTTP status code to send (e.g., 200, 404, 500). * @param data - The JSON data to send in the response body. */ json(code: (typeof HTTPStatusCode)[keyof typeof HTTPStatusCode], data: JSONObject): void; /** * Sends a text response with the specified status code and ends the response. * If the response has already been sent, this method is a no-op and does * nothing. * * @param code - The HTTP status code to send (e.g., 200, 404, 500). * @param data - The text data to send in the response body. */ text(code: (typeof HTTPStatusCode)[keyof typeof HTTPStatusCode], data: string): void; } type HTTPMethod = 'GET' | 'HEAD' | 'PATCH' | 'POST' | 'PUT' | 'DELETE' | 'OPTIONS'; type MIME = 'application/json; charset=utf-8' | 'text/plain; charset=utf-8'; type HTTPHeaderKey = 'access-control-allow-credentials' | 'access-control-allow-headers' | 'access-control-allow-methods' | 'access-control-allow-origin' | 'access-control-expose-headers' | 'access-control-max-age' | 'age' | 'allow' | 'cache-control' | 'connection' | 'content-length' | 'content-type' | 'cookie' | 'date' | 'etag' | 'expires' | 'last-modified' | 'location' | 'set-cookie' | `x-${string}`; type HTTPHeaderValue<Key extends HTTPHeaderKey> = Key extends 'content-type' ? MIME : string; type HTTPHeaders = { [Key in HTTPHeaderKey]?: HTTPHeaderValue<Key>; }; type JSONPrimitive = string | number | boolean | null | Date; type JSONValue = JSONPrimitive | JSONValue[] | JSONObject; interface JSONObject { [key: string]: JSONValue; } type Handler = (req: Request, res: Response) => Promise<void> | void; type ErrorHandler = (err: unknown, req: Request, res: Response) => Promise<void> | void; type Middleware = (next: Handler) => Handler; /** * A lightweight web framework. */ declare class Zing { #private; constructor(options?: Partial<Options>); /** * Starts listening for incoming connections on the specified port. * * @param port - The port to listen on. Defaults to `8080`. */ listen(port?: number): Promise<void>; /** * Gracefully shuts down the server by stopping it from accepting new * connections and closing all idle connections. After a specified timeout, * all connections will be closed. * * @param timeout - The timeout in milliseconds. Defaults to `10000`. */ shutdown(timeout?: number): Promise<void>; /** * Adds a route with the specified HTTP method, pattern, optional route-level * middleware and handler. * * @param method - The HTTP method to add the route for. * @param pattern - The pattern to match the route against. * @param args - The middleware and handler to call when the route is matched. * * @throws {Error} If the given pattern is invalid. */ route(method: HTTPMethod, pattern: string, ...args: [...Middleware[], Handler]): void; /** * Adds a `GET` route with the specified pattern, optional route-level * middleware and handler. * * @param pattern - The pattern to match the route against. * @param args - The middleware and handler to call when the route is matched. */ get(pattern: string, ...args: [...Middleware[], Handler]): void; /** * Adds a `HEAD` route with the specified pattern, optional route-level * middleware and handler. * * @param pattern - The pattern to match the route against. * @param args - The middleware and handler to call when the route is matched. */ head(pattern: string, ...args: [...Middleware[], Handler]): void; /** * Adds a `PATCH` route with the specified pattern, optional route-level * middleware and handler. * * @param pattern - The pattern to match the route against. * @param args - The middleware and handler to call when the route is matched. */ patch(pattern: string, ...args: [...Middleware[], Handler]): void; /** * Adds a `POST` route with the specified pattern, optional route-level * middleware and handler. * * @param pattern - The pattern to match the route against. * @param args - The middleware and handler to call when the route is matched. */ post(pattern: string, ...args: [...Middleware[], Handler]): void; /** * Adds a `PUT` route with the specified pattern, optional route-level * middleware and handler. * * @param pattern - The pattern to match the route against. * @param args - The middleware and handler to call when the route is matched. */ put(pattern: string, ...args: [...Middleware[], Handler]): void; /** * Adds a `DELETE` route with the specified pattern, optional route-level * middleware and handler. * * @param pattern - The pattern to match the route against. * @param args - The middleware and handler to call when the route is matched. */ delete(pattern: string, ...args: [...Middleware[], Handler]): void; /** * Adds a `OPTIONS` route with the specified pattern, optional route-level * middleware and handler. * * @param pattern - The pattern to match the route against. * @param args - The middleware and handler to call when the route is matched. */ options(pattern: string, ...args: [...Middleware[], Handler]): void; /** * Adds an application-level middleware to be called for each incoming * request regardless of whether it matches a route or not. * * @param middleware - The middleware to be called for each request. */ use(...middleware: Middleware[]): void; /** * Sets the handler to call when a route is not found. * * @param handler - The handler to call when a route is not found. */ set404Handler(handler: Handler): void; /** * Sets the handler to call when an error occurs. * * @param handler - The handler to call when an error occurs. */ setErrorHandler(handler: ErrorHandler): void; } export { BaseError, ContentTooLargeError, type ErrorHandler, type HTTPHeaderKey, type HTTPHeaderValue, type HTTPHeaders, type HTTPMethod, HTTPStatusCode, type Handler, InternalServerError, type JSONObject, type MIME, MalformedJSONError, type Middleware, type Options, Request, Response, UnsupportedContentTypeError, Zing, Zing as default };