UNPKG

shelving

Version:

Toolkit for using data in JavaScript.

96 lines (95 loc) 3.9 kB
import { RequiredError } from "../../error/RequiredError.js"; import { UNDEFINED } from "../../schema/Schema.js"; import { isData } from "../../util/data.js"; import { getPlaceholders, matchPathTemplate, renderPathTemplate } from "../../util/template.js"; /** * An abstract API resource definition, used to specify types for e.g. serverless functions. * * @param method The method of the endpoint, e.g. `GET` * @param path Endpoint path, possibly including placeholders e.g. `/users/{id}` * @param payload A `Schema` for the payload of the endpoint. * @param result A `Schema` for the result of the endpoint. */ export class Endpoint { /** Endpoint method. */ method; /** Endpoint path, possibly including placeholders e.g. `/users/{id}` */ path; /** Endpoint path, possibly including placeholders e.g. `/users/{id}` */ placeholders; /** Payload schema. */ payload; /** Result schema. */ result; constructor(method, path, payload, result) { this.method = method; this.path = path; this.placeholders = getPlaceholders(path); this.payload = payload; this.result = result; } /** * Render the path for this endpoint with the given payload. * - Path might contain `{placeholder}` values that are replaced with values from `payload`. * * @returns URL string combining `base` with this endpoint's path, with any `{placeholders}` rendered and `?query` params added. * * @throws {RequiredError} if this endpoint's path has `{placeholders}` but `payload` is not a data object. */ renderPath(payload, caller = this.renderPath) { // Placeholders. if (this.placeholders.length) { assertPlaceholderPayload(payload, this, caller); return renderPathTemplate(this.path, payload, caller); } // No placeholders. return this.path; } /** * Match a method/path pair against this endpoint and return any matched `{placeholder}` params. */ match(method, path, caller = this.match) { if (method !== this.method) return undefined; return matchPathTemplate(this.path, path, caller); } /** * Create an endpoint handler pairing for this endpoint. * @param callback The callback function that implements the logic for this endpoint by receiving the payload and returning the response. * @returns An `EndpointHandler` object combining this endpoint and the callback into a single typed object. */ handler(callback) { return { endpoint: this, callback }; } /** Convert to string, e.g. `GET /user/{id}` */ toString() { return `${this.method} ${this.path}`; } } export function HEAD(path, payload = UNDEFINED, result = UNDEFINED) { return new Endpoint("HEAD", path, payload, result); } export function GET(path, payload = UNDEFINED, result = UNDEFINED) { return new Endpoint("GET", path, payload, result); } export function POST(path, payload = UNDEFINED, result = UNDEFINED) { return new Endpoint("POST", path, payload, result); } export function PUT(path, payload = UNDEFINED, result = UNDEFINED) { return new Endpoint("PUT", path, payload, result); } export function PATCH(path, payload = UNDEFINED, result = UNDEFINED) { return new Endpoint("PATCH", path, payload, result); } export function DELETE(path, payload = UNDEFINED, result = UNDEFINED) { return new Endpoint("DELETE", path, payload, result); } /** Assert that an endpoint with `{placeholders}` only allows data payloads. */ function assertPlaceholderPayload(payload, endpoint, caller = assertPlaceholderPayload) { if (!isData(payload)) throw new RequiredError("Payload for request with URL {placeholders} must be data object", { endpoint, received: payload, caller, }); }