UNPKG

midwinter

Version:

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

165 lines (159 loc) 4.81 kB
'use strict'; var api = require('@opentelemetry/api'); var semanticConventions = require('@opentelemetry/semantic-conventions'); // 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 }); } }; var init = (opts) => { const { name = "midwinter" } = opts ?? {}; const otel = () => { const tracer = api.trace.getTracer(name); return new Midwinter().use((req, _, meta) => { return tracer.startActiveSpan("request", (span) => { return (res) => { span.setAttributes({ [semanticConventions.SEMATTRS_HTTP_URL]: res.url, [semanticConventions.ATTR_HTTP_REQUEST_METHOD]: req.method, [semanticConventions.ATTR_HTTP_RESPONSE_STATUS_CODE]: res.status }); if (typeof meta.path === "string") { span.setAttribute(semanticConventions.ATTR_HTTP_ROUTE, meta.path); } span.end(); }; }); }); }; return { otel }; }; exports.init = init;