UNPKG

midwinter

Version:

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

261 lines (251 loc) 7.45 kB
'use strict'; var radix3 = require('radix3'); // 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/routing/routers/util.ts var WILDCARD_METHOD_KEY = "*"; // src/plugins/routing/routers/radix.ts var createRouter = (initialRoutes) => { const routes = {}; for (const route of initialRoutes) { add(route); } const router = radix3.createRouter({ routes }); const matcher = radix3.toRouteMatcher(router); function add(route) { routes[route.path] ??= {}; for (const _method of route.methods) { const method = _method.toUpperCase(); routes[route.path][method] ??= []; routes[route.path][method].push(route.payload); } } function match(request) { const path = new URL(request.url).pathname; const method = request.method; const match2 = router.lookup(path); if (!match2) return; const { params: _, ...methods } = match2; if (!methods) return; const handlers = methods[method] ?? methods[WILDCARD_METHOD_KEY]; return handlers?.[0]; } function matchAll(request) { const path = new URL(request.url).pathname; const method = request.method; const matches = matcher.matchAll(path); const result = []; for (const match2 of matches) { const { params: _, ...methods } = match2; if (!methods) continue; const specificHandlers = methods[method] ?? []; const wildcardHandlers = methods[WILDCARD_METHOD_KEY] ?? []; result.push(...specificHandlers, ...wildcardHandlers); } return result; } return { match, matchAll }; }; // src/plugins/routing/util.ts var parsePathParams = (path) => { const parts = path.split("/"); const params = []; for (const part of parts) { if (part.startsWith(":")) { params.push(part.replace(/^:/, "")); } } return params; }; // src/plugins/routing/index.ts var init = (opts = {}) => { const { router = createRouter } = opts; const route = (config) => { if (config.path == null) { return new Midwinter(config); } return new Midwinter({ ...config, params: parsePathParams(config.path) }); }; return { route, prefixed(prefix) { return (config) => route({ ...config, path: `${prefix}${config.path ?? ""}` }); }, createRouter(routes, opts2 = {}) { const { onNotFound = () => { return Response.json({ code: "NOT_FOUND" }, { status: 404 }); }, onError = () => { return Response.json({ code: "SERVER_EXCEPTION" }, { status: 500 }); }, keepTrailingSlashes = false } = opts2; const _routes = (Array.isArray(routes) ? routes : Object.values(routes)).map( (route2) => { const { method, path } = route2.meta ?? {}; const _path = String(path); const _method = method == null ? WILDCARD_METHOD_KEY : method; return { methods: Array.isArray(_method) ? _method : [String(_method)], path: keepTrailingSlashes ? _path : _path.replace(/\/+$/, ""), payload: route2 }; } ); const _router = router(_routes); return async (request) => { try { const handler = _router.match(request); if (handler) { const response = await handler(request); if (response) return response; } return onNotFound(request); } catch (e) { return onError(e); } }; } }; }; var RadixRouter = createRouter; exports.RadixRouter = RadixRouter; exports.init = init;