feat: add additional processing tools
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import type { RelayAdapter, RelayRequestInput, RelayResponse } from "../libraries/adapter.ts";
|
import type { RelayAdapter, RelayRequestInput, RelayResponse } from "../libraries/adapter.ts";
|
||||||
|
import { RelayError, UnprocessableContentError } from "../libraries/errors.ts";
|
||||||
|
|
||||||
export class HttpAdapter implements RelayAdapter {
|
export class HttpAdapter implements RelayAdapter {
|
||||||
#id: number = 0;
|
#id: number = 0;
|
||||||
@@ -6,10 +7,28 @@ export class HttpAdapter implements RelayAdapter {
|
|||||||
constructor(readonly url: string) {}
|
constructor(readonly url: string) {}
|
||||||
|
|
||||||
async send({ method, params }: RelayRequestInput): Promise<RelayResponse> {
|
async send({ method, params }: RelayRequestInput): Promise<RelayResponse> {
|
||||||
const res = await fetch(this.url, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ method, params, id: this.#id++ }) });
|
const id = this.#id++;
|
||||||
if (res.headers.get("content-type")?.includes("application/json") === false) {
|
const res = await fetch(this.url, {
|
||||||
throw new Error("Unexpected return type");
|
method: "POST",
|
||||||
|
headers: { "content-type": "application/json" },
|
||||||
|
body: JSON.stringify({ relay: "1.0", method, params, id }),
|
||||||
|
});
|
||||||
|
const contentType = res.headers.get("content-type");
|
||||||
|
if (contentType !== "application/json") {
|
||||||
|
return {
|
||||||
|
relay: "1.0",
|
||||||
|
error: new UnprocessableContentError(`Invalid 'content-type' in header header, expected 'application/json', received '${contentType}'`),
|
||||||
|
id,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return res.json();
|
const json = await res.json();
|
||||||
|
if ("error" in json) {
|
||||||
|
return {
|
||||||
|
relay: "1.0",
|
||||||
|
error: RelayError.fromJSON(json.error),
|
||||||
|
id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return json;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@valkyr/relay",
|
"name": "@valkyr/relay",
|
||||||
"version": "0.3.0",
|
"version": "0.3.1",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./mod.ts",
|
".": "./mod.ts",
|
||||||
"./http": "./adapters/http.ts"
|
"./http": "./adapters/http.ts"
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { RelayError } from "./errors.ts";
|
||||||
|
|
||||||
export type RelayAdapter = {
|
export type RelayAdapter = {
|
||||||
send(input: RelayRequestInput): Promise<RelayResponse>;
|
send(input: RelayRequestInput): Promise<RelayResponse>;
|
||||||
};
|
};
|
||||||
@@ -9,12 +11,12 @@ export type RelayRequestInput = {
|
|||||||
|
|
||||||
export type RelayResponse =
|
export type RelayResponse =
|
||||||
| {
|
| {
|
||||||
|
relay: "1.0";
|
||||||
result: unknown;
|
result: unknown;
|
||||||
id: string;
|
id: string | number;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
error: {
|
relay: "1.0";
|
||||||
message: string;
|
error: RelayError;
|
||||||
};
|
id: string | number;
|
||||||
id: string;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import z from "zod";
|
|||||||
|
|
||||||
import { BadRequestError, InternalServerError, NotFoundError, RelayError } from "./errors.ts";
|
import { BadRequestError, InternalServerError, NotFoundError, RelayError } from "./errors.ts";
|
||||||
import { Procedure } from "./procedure.ts";
|
import { Procedure } from "./procedure.ts";
|
||||||
|
import { RelayRequest, request } from "./request.ts";
|
||||||
|
|
||||||
export class RelayApi<TProcedures extends Procedure[]> {
|
export class RelayApi<TProcedures extends Procedure[]> {
|
||||||
/**
|
/**
|
||||||
@@ -21,6 +22,15 @@ export class RelayApi<TProcedures extends Procedure[]> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a request candidate and parses its json body.
|
||||||
|
*
|
||||||
|
* @param candidate - Request candidate to parse.
|
||||||
|
*/
|
||||||
|
async parse(candidate: Request): Promise<RelayRequest> {
|
||||||
|
return request.parseAsync(await candidate.json());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle a incoming fetch request.
|
* Handle a incoming fetch request.
|
||||||
*
|
*
|
||||||
@@ -28,7 +38,7 @@ export class RelayApi<TProcedures extends Procedure[]> {
|
|||||||
* @param params - Parameters provided with the method request.
|
* @param params - Parameters provided with the method request.
|
||||||
* @param id - Request id used for response identification.
|
* @param id - Request id used for response identification.
|
||||||
*/
|
*/
|
||||||
async call(method: string, params: unknown, id: string): Promise<Response> {
|
async call({ method, params, id }: RelayRequest): Promise<Response> {
|
||||||
const procedure = this.#index.get(method);
|
const procedure = this.#index.get(method);
|
||||||
if (procedure === undefined) {
|
if (procedure === undefined) {
|
||||||
return toResponse(new NotFoundError(`Method '' does not exist`), id);
|
return toResponse(new NotFoundError(`Method '' does not exist`), id);
|
||||||
@@ -112,10 +122,11 @@ export class RelayApi<TProcedures extends Procedure[]> {
|
|||||||
* @param result - Result to send back as a Response.
|
* @param result - Result to send back as a Response.
|
||||||
* @param id - Request id which can be used to identify the response.
|
* @param id - Request id which can be used to identify the response.
|
||||||
*/
|
*/
|
||||||
function toResponse(result: object | RelayError | Response | void, id: string): Response {
|
export function toResponse(result: object | RelayError | Response | void, id: string | number): Response {
|
||||||
if (result === undefined) {
|
if (result === undefined) {
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
relay: "1.0",
|
||||||
result: null,
|
result: null,
|
||||||
id,
|
id,
|
||||||
}),
|
}),
|
||||||
@@ -133,6 +144,7 @@ function toResponse(result: object | RelayError | Response | void, id: string):
|
|||||||
if (result instanceof RelayError) {
|
if (result instanceof RelayError) {
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
relay: "1.0",
|
||||||
error: result,
|
error: result,
|
||||||
id,
|
id,
|
||||||
}),
|
}),
|
||||||
@@ -146,6 +158,7 @@ function toResponse(result: object | RelayError | Response | void, id: string):
|
|||||||
}
|
}
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
relay: "1.0",
|
||||||
result,
|
result,
|
||||||
id,
|
id,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,17 +1,50 @@
|
|||||||
export abstract class RelayError<D = unknown> extends Error {
|
export abstract class RelayError<TData = unknown> extends Error {
|
||||||
constructor(
|
constructor(
|
||||||
message: string,
|
message: string,
|
||||||
readonly status: number,
|
readonly status: number,
|
||||||
readonly data?: D,
|
readonly data?: TData,
|
||||||
) {
|
) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON(): {
|
/**
|
||||||
status: number;
|
* Converts a server delivered JSON error to its native instance.
|
||||||
message: string;
|
*
|
||||||
data: any;
|
* @param value - Error JSON.
|
||||||
} {
|
*/
|
||||||
|
static fromJSON(value: RelayErrorJSON): RelayErrorType {
|
||||||
|
switch (value.status) {
|
||||||
|
case 400:
|
||||||
|
return new BadRequestError(value.message, value.data);
|
||||||
|
case 401:
|
||||||
|
return new UnauthorizedError(value.message, value.data);
|
||||||
|
case 403:
|
||||||
|
return new ForbiddenError(value.message, value.data);
|
||||||
|
case 404:
|
||||||
|
return new NotFoundError(value.message, value.data);
|
||||||
|
case 405:
|
||||||
|
return new MethodNotAllowedError(value.message, value.data);
|
||||||
|
case 406:
|
||||||
|
return new NotAcceptableError(value.message, value.data);
|
||||||
|
case 409:
|
||||||
|
return new ConflictError(value.message, value.data);
|
||||||
|
case 410:
|
||||||
|
return new GoneError(value.message, value.data);
|
||||||
|
case 415:
|
||||||
|
return new UnsupportedMediaTypeError(value.message, value.data);
|
||||||
|
case 422:
|
||||||
|
return new UnprocessableContentError(value.message, value.data);
|
||||||
|
case 503:
|
||||||
|
return new ServiceUnavailableError(value.message, value.data);
|
||||||
|
default:
|
||||||
|
return new InternalServerError(value.message, value.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert error instance to a JSON object.
|
||||||
|
*/
|
||||||
|
toJSON(): RelayErrorJSON {
|
||||||
return {
|
return {
|
||||||
status: this.status,
|
status: this.status,
|
||||||
message: this.message,
|
message: this.message,
|
||||||
@@ -20,7 +53,7 @@ export abstract class RelayError<D = unknown> extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BadRequestError<D = unknown> extends RelayError<D> {
|
export class BadRequestError<TData = unknown> extends RelayError<TData> {
|
||||||
/**
|
/**
|
||||||
* Instantiate a new BadRequestError.
|
* Instantiate a new BadRequestError.
|
||||||
*
|
*
|
||||||
@@ -30,12 +63,12 @@ export class BadRequestError<D = unknown> extends RelayError<D> {
|
|||||||
*
|
*
|
||||||
* @param data - Optional data to send with the error.
|
* @param data - Optional data to send with the error.
|
||||||
*/
|
*/
|
||||||
constructor(message = "Bad Request", data?: D) {
|
constructor(message = "Bad Request", data?: TData) {
|
||||||
super(message, 400, data);
|
super(message, 400, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UnauthorizedError<D = unknown> extends RelayError<D> {
|
export class UnauthorizedError<TData = unknown> extends RelayError<TData> {
|
||||||
/**
|
/**
|
||||||
* Instantiate a new UnauthorizedError.
|
* Instantiate a new UnauthorizedError.
|
||||||
*
|
*
|
||||||
@@ -56,12 +89,12 @@ export class UnauthorizedError<D = unknown> extends RelayError<D> {
|
|||||||
* @param message - Optional message to send with the error. Default: "Unauthorized".
|
* @param message - Optional message to send with the error. Default: "Unauthorized".
|
||||||
* @param data - Optional data to send with the error.
|
* @param data - Optional data to send with the error.
|
||||||
*/
|
*/
|
||||||
constructor(message = "Unauthorized", data?: D) {
|
constructor(message = "Unauthorized", data?: TData) {
|
||||||
super(message, 401, data);
|
super(message, 401, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ForbiddenError<D = unknown> extends RelayError<D> {
|
export class ForbiddenError<TData = unknown> extends RelayError<TData> {
|
||||||
/**
|
/**
|
||||||
* Instantiate a new ForbiddenError.
|
* Instantiate a new ForbiddenError.
|
||||||
*
|
*
|
||||||
@@ -77,12 +110,12 @@ export class ForbiddenError<D = unknown> extends RelayError<D> {
|
|||||||
* @param message - Optional message to send with the error. Default: "Forbidden".
|
* @param message - Optional message to send with the error. Default: "Forbidden".
|
||||||
* @param data - Optional data to send with the error.
|
* @param data - Optional data to send with the error.
|
||||||
*/
|
*/
|
||||||
constructor(message = "Forbidden", data?: D) {
|
constructor(message = "Forbidden", data?: TData) {
|
||||||
super(message, 403, data);
|
super(message, 403, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NotFoundError<D = unknown> extends RelayError<D> {
|
export class NotFoundError<TData = unknown> extends RelayError<TData> {
|
||||||
/**
|
/**
|
||||||
* Instantiate a new NotFoundError.
|
* Instantiate a new NotFoundError.
|
||||||
*
|
*
|
||||||
@@ -99,12 +132,12 @@ export class NotFoundError<D = unknown> extends RelayError<D> {
|
|||||||
* @param message - Optional message to send with the error. Default: "Not Found".
|
* @param message - Optional message to send with the error. Default: "Not Found".
|
||||||
* @param data - Optional data to send with the error.
|
* @param data - Optional data to send with the error.
|
||||||
*/
|
*/
|
||||||
constructor(message = "Not Found", data?: D) {
|
constructor(message = "Not Found", data?: TData) {
|
||||||
super(message, 404, data);
|
super(message, 404, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MethodNotAllowedError<D = unknown> extends RelayError<D> {
|
export class MethodNotAllowedError<TData = unknown> extends RelayError<TData> {
|
||||||
/**
|
/**
|
||||||
* Instantiate a new MethodNotAllowedError.
|
* Instantiate a new MethodNotAllowedError.
|
||||||
*
|
*
|
||||||
@@ -116,12 +149,12 @@ export class MethodNotAllowedError<D = unknown> extends RelayError<D> {
|
|||||||
* @param message - Optional message to send with the error. Default: "Method Not Allowed".
|
* @param message - Optional message to send with the error. Default: "Method Not Allowed".
|
||||||
* @param data - Optional data to send with the error.
|
* @param data - Optional data to send with the error.
|
||||||
*/
|
*/
|
||||||
constructor(message = "Method Not Allowed", data?: D) {
|
constructor(message = "Method Not Allowed", data?: TData) {
|
||||||
super(message, 405, data);
|
super(message, 405, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NotAcceptableError<D = unknown> extends RelayError<D> {
|
export class NotAcceptableError<TData = unknown> extends RelayError<TData> {
|
||||||
/**
|
/**
|
||||||
* Instantiate a new NotAcceptableError.
|
* Instantiate a new NotAcceptableError.
|
||||||
*
|
*
|
||||||
@@ -133,12 +166,12 @@ export class NotAcceptableError<D = unknown> extends RelayError<D> {
|
|||||||
* @param message - Optional message to send with the error. Default: "Not Acceptable".
|
* @param message - Optional message to send with the error. Default: "Not Acceptable".
|
||||||
* @param data - Optional data to send with the error.
|
* @param data - Optional data to send with the error.
|
||||||
*/
|
*/
|
||||||
constructor(message = "Not Acceptable", data?: D) {
|
constructor(message = "Not Acceptable", data?: TData) {
|
||||||
super(message, 406, data);
|
super(message, 406, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ConflictError<D = unknown> extends RelayError<D> {
|
export class ConflictError<TData = unknown> extends RelayError<TData> {
|
||||||
/**
|
/**
|
||||||
* Instantiate a new ConflictError.
|
* Instantiate a new ConflictError.
|
||||||
*
|
*
|
||||||
@@ -154,12 +187,12 @@ export class ConflictError<D = unknown> extends RelayError<D> {
|
|||||||
* @param message - Optional message to send with the error. Default: "Conflict".
|
* @param message - Optional message to send with the error. Default: "Conflict".
|
||||||
* @param data - Optional data to send with the error.
|
* @param data - Optional data to send with the error.
|
||||||
*/
|
*/
|
||||||
constructor(message = "Conflict", data?: D) {
|
constructor(message = "Conflict", data?: TData) {
|
||||||
super(message, 409, data);
|
super(message, 409, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GoneError<D = unknown> extends RelayError<D> {
|
export class GoneError<TData = unknown> extends RelayError<TData> {
|
||||||
/**
|
/**
|
||||||
* Instantiate a new GoneError.
|
* Instantiate a new GoneError.
|
||||||
*
|
*
|
||||||
@@ -177,12 +210,12 @@ export class GoneError<D = unknown> extends RelayError<D> {
|
|||||||
* @param message - Optional message to send with the error. Default: "Gone".
|
* @param message - Optional message to send with the error. Default: "Gone".
|
||||||
* @param data - Optional data to send with the error.
|
* @param data - Optional data to send with the error.
|
||||||
*/
|
*/
|
||||||
constructor(message = "Gone", data?: D) {
|
constructor(message = "Gone", data?: TData) {
|
||||||
super(message, 410, data);
|
super(message, 410, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UnsupportedMediaTypeError<D = unknown> extends RelayError<D> {
|
export class UnsupportedMediaTypeError<TData = unknown> extends RelayError<TData> {
|
||||||
/**
|
/**
|
||||||
* Instantiate a new UnsupportedMediaTypeError.
|
* Instantiate a new UnsupportedMediaTypeError.
|
||||||
*
|
*
|
||||||
@@ -194,12 +227,12 @@ export class UnsupportedMediaTypeError<D = unknown> extends RelayError<D> {
|
|||||||
* @param message - Optional message to send with the error. Default: "Unsupported Media Type".
|
* @param message - Optional message to send with the error. Default: "Unsupported Media Type".
|
||||||
* @param data - Optional data to send with the error.
|
* @param data - Optional data to send with the error.
|
||||||
*/
|
*/
|
||||||
constructor(message = "Unsupported Media Type", data?: D) {
|
constructor(message = "Unsupported Media Type", data?: TData) {
|
||||||
super(message, 415, data);
|
super(message, 415, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UnprocessableContentError<D = unknown> extends RelayError<D> {
|
export class UnprocessableContentError<TData = unknown> extends RelayError<TData> {
|
||||||
/**
|
/**
|
||||||
* Instantiate a new UnprocessableContentError.
|
* Instantiate a new UnprocessableContentError.
|
||||||
*
|
*
|
||||||
@@ -216,12 +249,12 @@ export class UnprocessableContentError<D = unknown> extends RelayError<D> {
|
|||||||
* @param message - Optional message to send with the error. Default: "Unprocessable Content".
|
* @param message - Optional message to send with the error. Default: "Unprocessable Content".
|
||||||
* @param data - Optional data to send with the error.
|
* @param data - Optional data to send with the error.
|
||||||
*/
|
*/
|
||||||
constructor(message = "Unprocessable Content", data?: D) {
|
constructor(message = "Unprocessable Content", data?: TData) {
|
||||||
super(message, 422, data);
|
super(message, 422, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class InternalServerError<D = unknown> extends RelayError<D> {
|
export class InternalServerError<TData = unknown> extends RelayError<TData> {
|
||||||
/**
|
/**
|
||||||
* Instantiate a new InternalServerError.
|
* Instantiate a new InternalServerError.
|
||||||
*
|
*
|
||||||
@@ -239,12 +272,12 @@ export class InternalServerError<D = unknown> extends RelayError<D> {
|
|||||||
* @param message - Optional message to send with the error. Default: "Internal Server Error".
|
* @param message - Optional message to send with the error. Default: "Internal Server Error".
|
||||||
* @param data - Optional data to send with the error.
|
* @param data - Optional data to send with the error.
|
||||||
*/
|
*/
|
||||||
constructor(message = "Internal Server Error", data?: D) {
|
constructor(message = "Internal Server Error", data?: TData) {
|
||||||
super(message, 500, data);
|
super(message, 500, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ServiceUnavailableError<D = unknown> extends RelayError<D> {
|
export class ServiceUnavailableError<TData = unknown> extends RelayError<TData> {
|
||||||
/**
|
/**
|
||||||
* Instantiate a new ServiceUnavailableError.
|
* Instantiate a new ServiceUnavailableError.
|
||||||
*
|
*
|
||||||
@@ -259,7 +292,27 @@ export class ServiceUnavailableError<D = unknown> extends RelayError<D> {
|
|||||||
* @param message - Optional message to send with the error. Default: "Service Unavailable".
|
* @param message - Optional message to send with the error. Default: "Service Unavailable".
|
||||||
* @param data - Optional data to send with the error.
|
* @param data - Optional data to send with the error.
|
||||||
*/
|
*/
|
||||||
constructor(message = "Service Unavailable", data?: D) {
|
constructor(message = "Service Unavailable", data?: TData) {
|
||||||
super(message, 503, data);
|
super(message, 503, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type RelayErrorJSON = {
|
||||||
|
status: number;
|
||||||
|
message: string;
|
||||||
|
data: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RelayErrorType =
|
||||||
|
| BadRequestError
|
||||||
|
| UnauthorizedError
|
||||||
|
| ForbiddenError
|
||||||
|
| NotFoundError
|
||||||
|
| MethodNotAllowedError
|
||||||
|
| NotAcceptableError
|
||||||
|
| ConflictError
|
||||||
|
| GoneError
|
||||||
|
| UnsupportedMediaTypeError
|
||||||
|
| UnprocessableContentError
|
||||||
|
| ServiceUnavailableError
|
||||||
|
| InternalServerError;
|
||||||
|
|||||||
10
libraries/request.ts
Normal file
10
libraries/request.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
export const request = z.object({
|
||||||
|
relay: z.literal("1.0"),
|
||||||
|
method: z.string(),
|
||||||
|
params: z.unknown(),
|
||||||
|
id: z.string().or(z.number()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type RelayRequest = z.infer<typeof request>;
|
||||||
1
mod.ts
1
mod.ts
@@ -4,3 +4,4 @@ export * from "./libraries/api.ts";
|
|||||||
export * from "./libraries/errors.ts";
|
export * from "./libraries/errors.ts";
|
||||||
export * from "./libraries/procedure.ts";
|
export * from "./libraries/procedure.ts";
|
||||||
export * from "./libraries/relay.ts";
|
export * from "./libraries/relay.ts";
|
||||||
|
export * from "./libraries/request.ts";
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ describe("Procedure", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
async (request) => {
|
async (request) => {
|
||||||
const { method, params, id } = await request.json();
|
return api.call(await api.parse(request));
|
||||||
return api.call(method, params, id);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
client = relay.client({
|
client = relay.client({
|
||||||
|
|||||||
Reference in New Issue
Block a user