@universal-middleware/fastify
Version:
fastify adapter for universal middlewares
353 lines (347 loc) • 11.7 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
// ../../node_modules/.pnpm/fastify-plugin@5.0.1/node_modules/fastify-plugin/lib/getPluginName.js
var require_getPluginName = __commonJS({
"../../node_modules/.pnpm/fastify-plugin@5.0.1/node_modules/fastify-plugin/lib/getPluginName.js"(exports, module) {
"use strict";
var fpStackTracePattern = /at\s{1}(?:.*\.)?plugin\s{1}.*\n\s*(.*)/;
var fileNamePattern = /(\w*(\.\w*)*)\..*/;
module.exports = function getPluginName(fn) {
if (fn.name.length > 0) return fn.name;
const stackTraceLimit = Error.stackTraceLimit;
Error.stackTraceLimit = 10;
try {
throw new Error("anonymous function");
} catch (e) {
Error.stackTraceLimit = stackTraceLimit;
return extractPluginName(e.stack);
}
};
function extractPluginName(stack) {
const m = stack.match(fpStackTracePattern);
return m ? m[1].split(/[/\\]/).slice(-1)[0].match(fileNamePattern)[1] : "anonymous";
}
module.exports.extractPluginName = extractPluginName;
}
});
// ../../node_modules/.pnpm/fastify-plugin@5.0.1/node_modules/fastify-plugin/lib/toCamelCase.js
var require_toCamelCase = __commonJS({
"../../node_modules/.pnpm/fastify-plugin@5.0.1/node_modules/fastify-plugin/lib/toCamelCase.js"(exports, module) {
"use strict";
module.exports = function toCamelCase(name) {
if (name[0] === "@") {
name = name.slice(1).replace("/", "-");
}
return name.replace(/-(.)/g, function(match, g1) {
return g1.toUpperCase();
});
};
}
});
// ../../node_modules/.pnpm/fastify-plugin@5.0.1/node_modules/fastify-plugin/plugin.js
var require_plugin = __commonJS({
"../../node_modules/.pnpm/fastify-plugin@5.0.1/node_modules/fastify-plugin/plugin.js"(exports, module) {
"use strict";
var getPluginName = require_getPluginName();
var toCamelCase = require_toCamelCase();
var count = 0;
function plugin(fn, options = {}) {
let autoName = false;
if (fn.default !== void 0) {
fn = fn.default;
}
if (typeof fn !== "function") {
throw new TypeError(
`fastify-plugin expects a function, instead got a '${typeof fn}'`
);
}
if (typeof options === "string") {
options = {
fastify: options
};
}
if (typeof options !== "object" || Array.isArray(options) || options === null) {
throw new TypeError("The options object should be an object");
}
if (options.fastify !== void 0 && typeof options.fastify !== "string") {
throw new TypeError(`fastify-plugin expects a version string, instead got '${typeof options.fastify}'`);
}
if (!options.name) {
autoName = true;
options.name = getPluginName(fn) + "-auto-" + count++;
}
fn[Symbol.for("skip-override")] = options.encapsulate !== true;
fn[Symbol.for("fastify.display-name")] = options.name;
fn[Symbol.for("plugin-meta")] = options;
if (!fn.default) {
fn.default = fn;
}
const camelCase = toCamelCase(options.name);
if (!autoName && !fn[camelCase]) {
fn[camelCase] = fn;
}
return fn;
}
module.exports = plugin;
module.exports.default = plugin;
module.exports.fastifyPlugin = plugin;
}
});
// src/common.ts
var import_fastify_plugin = __toESM(require_plugin(), 1);
import {
attachUniversal,
bindUniversal,
contextSymbol,
getAdapterRuntime,
isBodyInit,
mergeHeadersInto,
universalSymbol
} from "@universal-middleware/core";
import { createRequestAdapter } from "@universal-middleware/express";
var pendingMiddlewaresSymbol = Symbol.for("unPendingMiddlewares");
var wrappedResponseSymbol = Symbol.for("unWrappedResponse");
function patchBody(response) {
Object.defineProperty(response, "body", {
value: new ReadableStream({
start(controller) {
controller.close();
}
}),
writable: false,
configurable: true
});
return response;
}
function getHeaders(reply) {
const ret = new Headers();
const headers = reply.getHeaders();
let setCookie = reply.getHeader("set-cookie");
if (typeof setCookie === "string") {
setCookie = [setCookie];
}
if (Array.isArray(setCookie)) {
for (const cookie of setCookie) {
ret.append("set-cookie", cookie);
}
}
for (const [key, value] of Object.entries(headers)) {
if (key === "set-cookie") continue;
if (typeof value === "string") {
ret.set(key, value);
} else if (typeof value === "number") {
ret.set(key, String(value));
} else if (Array.isArray(value)) {
if (value.length === 1) {
ret.set(key, value[0]);
} else if (value.length > 1) {
console.warn(`Header "${key}" should not be an array. Only last value will be sent`);
ret.set(key, value.at(-1));
}
}
}
return ret;
}
function getRawRequest(req) {
if (!req.body || "rawBody" in req.raw) return req.raw;
if ("rawBody" in req) {
Object.defineProperty(req.raw, "rawBody", {
get() {
return req.rawBody;
},
configurable: true,
enumerable: true
});
} else {
Object.defineProperty(req.raw, "rawBody", {
get() {
throw new Error(
"rawBody not Found. Please install fastify-raw-body plugin: https://github.com/Eomm/fastify-raw-body"
);
},
configurable: true,
enumerable: true
});
}
return req.raw;
}
function createHandler(handlerFactory) {
const requestAdapter = createRequestAdapter();
return (...args) => {
const handler = handlerFactory(...args);
return bindUniversal(handler, async function universalHandlerFastify(request, reply) {
const ctx = initContext(request);
const response = await this[universalSymbol](
requestAdapter(getRawRequest(request)),
ctx,
getRuntime(request, reply)
);
if (response) {
if (!response.body) {
patchBody(response);
}
return reply.send(response);
}
reply.callNotFound();
});
};
}
function createMiddleware(middlewareFactory) {
const requestAdapter = createRequestAdapter();
return (...args) => {
const middleware = middlewareFactory(...args);
return attachUniversal(
middleware,
(0, import_fastify_plugin.default)(async (instance) => {
instance.addHook(
"preHandler",
bindUniversal(
middleware,
async function universalMiddlewareFastify(request, reply) {
const ctx = initContext(request);
const response = await this[universalSymbol](
requestAdapter(getRawRequest(request)),
ctx,
getRuntime(request, reply)
);
if (!response) {
return;
}
if (typeof response === "function") {
if (reply.sent) {
throw new Error(
"Universal Middleware called after headers have been sent. Please open an issue at https://github.com/magne4000/universal-middleware"
);
}
request[pendingMiddlewaresSymbol] ??= [];
request[wrappedResponseSymbol] = false;
request[pendingMiddlewaresSymbol].push(response);
} else if (response instanceof Response) {
if (!response.body) {
patchBody(response);
}
await reply.send(response);
} else {
setContext(request, response);
}
}
)
);
instance.addHook("onSend", async (request, reply, payload) => {
if (request[wrappedResponseSymbol] !== false) return payload;
request[wrappedResponseSymbol] = true;
if (payload instanceof Response) {
mergeHeadersInto(payload.headers, getHeaders(reply));
} else if (isBodyInit(payload)) {
payload = new Response(payload, {
headers: new Headers(getHeaders(reply)),
status: reply.statusCode
});
} else if (payload !== void 0) {
throw new TypeError("Payload is not a Response or BodyInit compatible");
}
const middlewares = request[pendingMiddlewaresSymbol];
delete request[pendingMiddlewaresSymbol];
if (!payload) {
reply.callNotFound();
}
const newResponse = await middlewares?.reduce(
async (prev, curr) => {
const p = await prev;
const newR = await curr(p);
return newR ?? p;
},
Promise.resolve(payload)
);
const r = newResponse ?? payload;
if (!r) {
reply.callNotFound();
} else {
return r;
}
});
})
);
};
}
function initContext(req) {
const config = req.routeOptions.config;
config[contextSymbol] ??= {};
return config[contextSymbol];
}
function getContext(req) {
const config = req.routeOptions.config;
return config[contextSymbol];
}
function setContext(req, newContext) {
const config = req.routeOptions.config;
config[contextSymbol] = newContext;
}
function getRuntime(request, reply) {
return getAdapterRuntime("fastify", {
params: request.params,
req: request.raw,
res: reply.raw,
fastify: Object.freeze({
request,
reply
})
});
}
// src/router.ts
import {
applyAsync as applyAsyncCore,
getUniversal,
UniversalRouter,
universalSymbol as universalSymbol2
} from "@universal-middleware/core";
var UniversalFastifyRouter = class extends UniversalRouter {
#app;
constructor(app) {
super(false);
this.#app = app;
}
// @ts-expect-error ReturnType mismatch with UniversalRouter
async use(middleware) {
this.#app.register(createMiddleware(() => getUniversal(middleware))());
return this;
}
// @ts-expect-error ReturnType mismatch with UniversalRouter
async applyCatchAll() {
this.#app.all("/*", createHandler(() => this[universalSymbol2])());
return this;
}
};
function apply(app, middlewares) {
const router = new UniversalFastifyRouter(app);
return applyAsyncCore(router, middlewares);
}
export {
apply,
createHandler,
createMiddleware,
getContext
};