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