UNPKG

midwinter

Version:

A next-gen middleware engine built for the WinterCG environments.

284 lines (276 loc) 7.78 kB
'use strict'; var schemaShift = require('schema-shift'); // src/plugins/validation/parsing.ts var cached = (fn) => { let called = false; let result; return async () => { if (called) return result; result = await fn(); called = true; return result; }; }; var parseQuery = (req, opts) => { const url = new URL(req.url); if (opts.parseQueryString) { const result = opts.parseQueryString(url); return opts.Query ? schemaShift.parse(opts.Query, result) : result; } const map = {}; for (const [key, value] of url.searchParams.entries()) { map[key] = value; } return opts.Query ? schemaShift.parse(opts.Query, map) : map; }; var parseParams = (req, opts) => { const url = new URL(req.url); const map = {}; if (opts.path == null) { if (opts.Params) { console.warn( "Unable to parse params: could not find a `path` for this request handler." ); } return map; } const patternParts = opts.path?.split("/"); const PathParts = url.pathname.split("/"); for (let i = 0; i <= patternParts.length; i++) { const pattern = patternParts[i]; const Path = PathParts[i]; if (pattern == null || Path == null) break; if (pattern.startsWith(":")) { map[pattern.slice(1)] = Path; } } return opts.Params ? schemaShift.parse(opts.Params, map) : map; }; var parseBody = async (_req, opts) => { const req = _req.clone(); const contentType = req.headers.get("content-type"); if (opts.Body) { const data = await req.json(); return schemaShift.parse(opts.Body, data); } if (contentType === "application/json") { return req.json(); } if (contentType?.startsWith("text/")) { return req.text(); } return req.body; }; var parseHeaders = (req, opts) => { const map = {}; for (const [key, value] of req.headers.entries()) { map[key] = value; } return opts.Headers ? schemaShift.parse(opts.Headers, map) : map; }; var getCachedParsers = (req, opts) => { return { query: cached(() => parseQuery(req, opts)), params: cached(() => parseParams(req, opts)), headers: cached(() => parseHeaders(req, opts)), body: cached(() => parseBody(req, opts)) }; }; var makeParseFn = (req, opts) => { const parsers = getCachedParsers(req, opts); const parse3 = async (key) => { if (key) { return parsers[key](); } const [_query, _params, _headers, _body] = await Promise.all([ parsers.query(), parsers.params(), parsers.headers(), parsers.body() ]); return { query: _query, params: _params, headers: _headers, body: _body }; }; return parse3; }; // src/util/request.ts function isResponse(value) { if (typeof value !== "object" || value === null) { return false; } return typeof value.status === "number" && typeof value.ok === "boolean" && typeof value.json === "function" && typeof value.headers === "object"; } // src/executor/executor.ts var MiddlewareExecutor = class { constructor(middlewares = []) { this.middlewares = middlewares; } responseHandlers = []; append(...middlewares) { this.middlewares.push(...middlewares); } /** * Invokes the the pre-handler middleware and registers any response handlers. * * Note: this method must be invoked for response/outbound middleware to be registered for `.post()`. * * @returns An async generator which is used to execute and * optionally "listen" to and respond to middleware events as they occur. * * @example * ```ts * for await (const result of mid.pre(req)) { * // Optionally handle middleware events as they occur: * switch(result.type) { * case "update": { * ctx = { ...ctx, ...result.updates } * } * case "response": { * return next(result.response) * } * } * } * ``` */ async *pre(request, ctx, meta) { for (const middleware of this.middlewares) { const result = await middleware(request, ctx, meta); if (result instanceof Response || isResponse(result)) { yield { type: "response", response: result }; } else if (typeof result === "function") { this.responseHandlers.push(result); continue; } else if (typeof result === "object") { yield { type: "update", update: result }; } } } /** * Invokes the post-handler middleware in reverse-order to that which the middleware was registered. * * Note: this method requires running `.pre` before hand. * * @returns The final `response` object. * * @example * ```ts * const finalResponse = await mid.post(response) * * return finalResponse * ``` */ async post(response) { let _response = response; for (let i = this.responseHandlers.length - 1; i >= 0; i--) { const handler = this.responseHandlers[i]; const result = await handler(response); if (result) { _response = result; } } return _response; } }; // src/midwinter/midwinter.ts var Midwinter = class _Midwinter { constructor(meta = {}, middlewares = []) { this.meta = meta; this.middlewares = middlewares; } use(value) { let meta = { ...this.meta }; if (value instanceof _Midwinter) { return new _Midwinter( { ...meta, ...value.meta }, [...this.middlewares, ...value.middlewares] ); } if ("meta" in value && value.meta != null) { meta = { ...meta, ...value.meta }; } if (typeof value === "function") { return new _Midwinter(meta, [...this.middlewares, value]); } return new _Midwinter(meta, [...this.middlewares]); } end(middleware) { const meta = Object.freeze(this.meta); const handler = async (request) => { const ctx = {}; const executor = new MiddlewareExecutor(this.middlewares); const runMiddleware = async () => { for await (const result of executor.pre(request, ctx, meta)) { switch (result.type) { case "response": { return result.response; } case "update": { for (const key in result.update) { ctx[key] = result.update[key]; } } } } }; let response = await runMiddleware(); if (response == null) { response = await middleware?.(request, ctx, meta); } if (response != null) { return executor.post(response); } }; return Object.assign(handler, { meta }); } }; // src/plugins/validation/index.ts var init = (opts) => { const { parseQueryString } = opts ?? {}; return { valid(opts2) { return new Midwinter(opts2).use( async (req, _, meta) => { const parse3 = makeParseFn(req, { ...opts2, path: meta.path ? String(meta.path) : void 0, parseQueryString }); return { ...await parse3() }; } ); }, validLazy(opts2) { return new Midwinter(opts2).use( (req, _, meta) => { return { parse: makeParseFn(req, { ...opts2, path: meta.path ? String(meta.path) : void 0, parseQueryString }) }; } ); }, // Can't be bothered fixing this // @ts-expect-error output(handler, opts2) { return async (req, ctx, meta) => { const data = await handler(req, ctx, meta); if (data instanceof Response) { return data; } const outData = meta.Output ? await schemaShift.parse(meta.Output, data) : data; return Response.json(outData ?? null, opts2); }; } }; }; exports.init = init;