midwinter
Version:
A next-gen middleware engine built for the WinterCG environments.
261 lines (251 loc) • 7.45 kB
JavaScript
;
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;