@worker-tools/middleware
Version:
A suite of standalone HTTP server middlewares for Worker Runtimes.
139 lines • 5.39 kB
JavaScript
import { combine } from "./context.js";
import { accepts } from './content-negotiation.js';
import { payloadTooLarge } from '@worker-tools/response-creators';
export const JSON = 'application/json';
export const FORM = 'application/x-www-form-urlencoded';
export const FORM_DATA = 'multipart/form-data';
export const TEXT_HTML = 'text/html';
export const TEXT_PLAIN = 'text/plain';
/** Standard MIME type for binary data */
export const BINARY = 'application/octet-stream';
/** Non-standard MIME type for binary data. Sometimes used, so included anyway. */
export const X_BINARY = 'application/x-binary';
const defaultBody = [
FORM,
FORM_DATA,
JSON,
BINARY,
X_BINARY,
TEXT_HTML,
TEXT_PLAIN,
];
// Not possible to provide a fallback rn: https://github.com/microsoft/TypeScript/issues/48073
const _isString = (x) => !(x[1] instanceof File);
const _isFile = (x) => x[1] instanceof File;
const isBodyTextContext = (nx) => { var _a; return (_a = nx.accepted) === null || _a === void 0 ? void 0 : _a.startsWith('text/'); };
const isBodyVendorJSONContext = (nx) => { var _a; return ((_a = nx.accepted) === null || _a === void 0 ? void 0 : _a.startsWith('application/')) && nx.accepted.endsWith('+json'); };
// const isBodyVendorBinaryContext = <J = any>(nx: BodyContext<J>): nx is BodyVendorBinaryContext =>
// nx.accepted?.startsWith('application/vnd.')
const MB = 1024 ** 2;
async function checkSize(req, maxSize) {
let size = 0;
await req.clone().body.pipeTo(new WritableStream({
write(chunk, ctrl) {
size += chunk.byteLength;
if (size > maxSize) {
ctrl.error(new Error('Payload too large'));
}
}
}));
return size <= maxSize;
}
export const bodyParser = (opts = {}) => async (ax) => {
var _a;
const x = await ax;
const nx = x;
const ok = await checkSize(x.request, (_a = opts.maxSize) !== null && _a !== void 0 ? _a : 1 * MB);
if (!ok)
throw payloadTooLarge();
switch (nx.accepted) {
case JSON: {
nx.body = nx.json = await x.request.json();
return nx;
}
case FORM: {
nx.body = nx.form = new URLSearchParams(await x.request.text());
// FIXME: Multiple values per key??
// nx.form = Object.fromEntries(form);
return nx;
}
case FORM_DATA: {
nx.body = nx.formData = await x.request.formData();
// FIXME: Multiple values per key??
// const tuples = [...formData];
// nx.form = Object.fromEntries(tuples.filter(isString));
// nx.files = Object.fromEntries(tuples.filter(isFile));
return nx;
}
case BINARY:
case X_BINARY: {
nx.body = nx.arrayBuffer = await x.request.arrayBuffer();
nx.blob = new Blob([nx.arrayBuffer]); // TODO: does this copy??
return nx;
}
default: {
if (isBodyTextContext(nx)) {
nx.body = nx.text = await x.request.text();
}
else if (isBodyVendorJSONContext(nx)) {
nx.body = nx.json = await x.request.json();
return nx;
// } else if (isBodyVendorBinaryContext(nx)) {
// nx.body = nx.buffer = await x.request.arrayBuffer();
// nx.blob = new Blob([nx.buffer]) // TODO: does this copy??
// return nx;
}
else {
// Anything else gets the binary treatment (outside of scope of type system)
nx.body = nx.buffer = await x.request.arrayBuffer();
nx.blob = new Blob([nx.buffer]);
return nx;
}
return nx;
}
}
};
export const defaultBodyParser = (options) => combine(accepts(defaultBody), bodyParser(options));
// type ErrorOf<T> = T extends { error?: infer E } ? E : never
// (async () => {
// const ctx: Context = { request: new Request('/'), effects: [], waitUntil: (_f: any) => {}, handled: Promise.resolve(null as any) }
// const z = provides([])(accepts([])(ctx))
// const x = await parseBody()(accepts(['text/x-foo', 'application/vnd.github.v3+json', FORM, FORM_DATA])(ctx))
// if (x.accepted === 'application/vnd.github.v3+json') {
// x.body
// } else if (x.accepted === 'text/x-foo') {
// x.body
// } else if (x.accepted === 'application/x-www-form-urlencoded') {
// x.body
// }
// const y = await bodyParser()(ctx)
// if (y.accepted === 'application/x-www-form-urlencoded') {
// y.bodyParams
// y.body
// }
// if (y.accepted === 'multipart/form-data') {
// y.formData
// y.body
// }
// if (y.accepted === 'application/foobar+json') {
// y.json
// y.body
// }
// // if (x.accepted === 'application/x-www-form-urlencoded') {
// // x.body
// // x.bodyParams
// // x.form
// // }
// // else if (x.accepted === 'multipart/form-data') {
// // x.formData
// // x.form
// // x.files
// // } else if (x.accepted === 'application/octet-stream' || x.accepted === 'application/x-binary') {
// // x.buffer
// // x.blob
// // } else if (x.accepted === 'application/vnd.github.v3+json') {
// // } else if (x.accepted === 'text/foo') {
// // x.text
// // }
// })
//# sourceMappingURL=body-parser.js.map