@transformgovsg/zing
Version:
A lightweight web framework.
487 lines (476 loc) • 18.8 kB
TypeScript
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 };