shelving
Version:
Toolkit for using data in JavaScript.
96 lines (95 loc) • 3.9 kB
JavaScript
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,
});
}