Open
Description
What’s missing?
The lib should handle/integrate more http frameworks/toolkits. (like node, express, deno, sunder) Or at least it should allow the users to write integrations easily to it.
Why?
To use and deploy in multiple cloud infrastructures (like cloudflare workers, aws lambda, etc.)
Alternatives you tried
We started to discuss this in #693 .
I started to play with my idea described here, it would look sth like this;
//neccess boilerplate for later compat.
enum HttpMethod {
GET = "GET",
POST = "POST",
DELETE = "DELETE",
OPTION = "OPTION",
PUT = "PUT",
UNKNOWN = "UNKNOWN",
}
type HttpMethodStrings = keyof typeof HttpMethod;
//typeclass definition (RRP as request-response-pair)
interface MiddlewareIOHelper<RRP> {
getHeader(i: RRP, name: string): string;
getBodyAsJson(i: RRP): Promise<unknown>;
getUrlPath(i: RRP): string;
getMethod(i: RRP): HttpMethodStrings;
respondWith(o: RRP, body?: string, status?: number, headers?: Record<string, string>): void;
handleUnknownRoute(o: RRP): void;
}
//this handles the application logic
export async function middleware<RRP>(
webhooks: Webhooks<RRP>,
options: Required<MiddlewareOptions>,
handler: NodeMiddlewareIOHelper<RRP>,
rrp: RRP
) {
...
}
Implemented to node;
type RRP<I, O> = {
request: I,
response: O,
next?: Function,
}
type NodeRRP = RRP<IncomingMessage, ServerResponse>
//typeclass instance for node middleware
function NodeMiddlewareIOHelper(onUnhandledRequest: (request: IncomingMessage, response: ServerResponse) => void = onUnhandledRequestDefault): MiddlewareIOHelper<NodeRRP> {
return {
getBodyAsJson(i: NodeRRP): Promise<unknown> {
return new Promise((resolve, reject) => {
let data = "";
i.request.setEncoding("utf8");
// istanbul ignore next
i.request.on("error", (error: Error) => reject(new AggregateError([error])));
i.request.on("data", (chunk: string) => (data += chunk));
i.request.on("end", () => {
try {
resolve(JSON.parse(data));
} catch (error: any) {
error.message = "Invalid JSON";
error.status = 400;
reject(new AggregateError([error]));
}
});
});
},
getHeader(i: NodeRRP, name: string): string {
return i.request.headers[name] as string;
},
getMethod(i: NodeRRP): HttpMethodStrings {
const indexOf = Object.values(HttpMethod).indexOf((i.request.method || "") as unknown as HttpMethod);
const key = (indexOf >= 0) ? Object.keys(HttpMethod)[indexOf] : "UNKNOWN";
return key as HttpMethodStrings;
},
getUrlPath(i: NodeRRP): string {
return new URL(i.request.url as string, "http://localhost").pathname;
},
respondWith(o: NodeRRP, body: string | undefined, status: number | undefined, headers: Record<string, string> | undefined): void {
if (!headers && status) {
o.response.statusCode = status;
} else if (status) {
o.response.writeHead(status, headers);
}
o.response.end(body || "");
},
handleUnknownRoute(rrp: NodeRRP) {
const isExpressMiddleware = typeof rrp.next === "function";
if (isExpressMiddleware) {
rrp.next!();
} else {
onUnhandledRequest(rrp.request, rrp.response);
}
}
};
}
//this connects the dots for node
export function createNodeMiddleware(
webhooks: Webhooks<IncomingMessage>,
{
path = "/api/github/webhooks",
onUnhandledRequest = onUnhandledRequestDefault,
log = createLogger(),
}: MiddlewareOptions = {}
) {
const handler = NodeMiddlewareIOHelper(onUnhandledRequest)
const fun = (request: IncomingMessage, response: ServerResponse, next?: Function) =>
middleware<NodeRRP>(
webhooks,
{
path,
log,
} as Required<MiddlewareOptions>,
handler,
{request, response, next}
)
}
Implement to sunder would be sth like;
type SunderRRP = Context
function SunderMiddlewareIOHelper(onUnhandledRequest: (context: Context) => void = onUnhandledRequestDefaultSunder): MiddlewareIOHelper<SunderRRP> {
...
}
//this connects the dots for node
export function createSunderMiddleware(
webhooks: Webhooks<IncomingMessage>,
{
path = "/api/github/webhooks",
onUnhandledRequest = onUnhandledRequestDefaultSunder,
log = createLogger(),
}: MiddlewareOptions = {}
) {
const handler = SunderMiddlewareIOHelper(onUnhandledRequest)
const fun = (context: Context, next?: Function) =>
middleware<SunderRRP>(
webhooks,
{
path,
log,
} as Required<MiddlewareOptions>,
handler,
{context, next}
)
}