UNPKG

fastify-fusion

Version:

Fastify API framework with `best practices` and `plugins` fused together to make it easy to build and maintain your API.

407 lines (393 loc) 13.3 kB
"use strict"; 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 __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; 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 )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { defaultCacheableOptions: () => defaultCacheableOptions, defaultFastifyHelmetOptions: () => defaultFastifyHelmetOptions, defaultFastifyRateLimitOptions: () => defaultFastifyRateLimitOptions, defaultLoggingOptions: () => defaultLoggingOptions, defaultStartOptions: () => defaultStartOptions, fuse: () => fuse, fuseCacheable: () => fuseCacheable, fuseHelmet: () => fuseHelmet, fuseLog: () => fuseLog, fuseOpenApi: () => fuseOpenApi, fuseRateLimit: () => fuseRateLimit, fuseStatic: () => fuseStatic, logger: () => logger, start: () => start }); module.exports = __toCommonJS(index_exports); var import_node_process = __toESM(require("process"), 1); // src/cacheable.ts var import_cacheable = require("cacheable"); var defaultCacheableOptions = { ttl: "1h", stats: true, nonBlocking: true }; async function fuseCacheable(fastify, options = defaultCacheableOptions) { const cacheable = new import_cacheable.Cacheable(options); fastify.decorate("cache", cacheable); fastify.addHook("onClose", async () => { await cacheable.disconnect(); }); fastify.log.info(`Fastify Cacheable Registered: ${JSON.stringify(options)}`); } __name(fuseCacheable, "fuseCacheable"); // src/fuse.ts var import_node_path2 = __toESM(require("path"), 1); // src/cors.ts var import_cors = require("@fastify/cors"); var defaultFastifyCorsOptions = { origin: true, methods: [ "GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS" ], allowedHeaders: [ "Content-Type", "Authorization", "X-Requested-With", "Bearer" ], exposedHeaders: [ "Content-Length", "X-Requested-With" ], credentials: true }; async function fuseCors(fastify, options) { await fastify.register(import_cors.fastifyCors, options); fastify.log.info(`Fasity CORS Registered: ${JSON.stringify(options)}`); } __name(fuseCors, "fuseCors"); // src/helmet.ts var import_helmet = require("@fastify/helmet"); var defaultFastifyHelmetOptions = { // Turn off CSP (mostly for HTML) to avoid overhead contentSecurityPolicy: false, // Remove the X-Power-By header hidePoweredBy: true, // Prevent your API from being framed frameguard: { action: "deny" }, // Disable DNS prefetching dnsPrefetchControl: { allow: false }, // Enable HSTS for one year on HTTPS endpoints hsts: { maxAge: 31536e3, includeSubDomains: true, preload: true }, // Block sniffing of MIME types noSniff: true, // Basic XSS protections xssFilter: true, // Don't send Referer at all referrerPolicy: { policy: "no-referrer" }, // Tighten cross-origin resource loading crossOriginResourcePolicy: { policy: "same-origin" }, // You generally don't need the embedder/policy on an API crossOriginEmbedderPolicy: false, // Leave CSP nonces off // eslint-disable-next-line @typescript-eslint/naming-convention enableCSPNonces: false }; async function fuseHelmet(fastify, options) { await fastify.register(import_helmet.fastifyHelmet, options); fastify.log.info(`Fasity Helment Registered: ${JSON.stringify(options)}`); } __name(fuseHelmet, "fuseHelmet"); // src/log.ts var import_pino = __toESM(require("pino"), 1); var defaultLoggingOptions = { transport: { target: "pino-pretty", options: { colorize: true, translateTime: true, ignore: "pid,hostname", singleLine: true } } }; async function fuseLog(fastify, options) { fastify.log = (0, import_pino.default)(options); fastify.log.info(`Fasity Logging Registered: ${JSON.stringify(options)}`); } __name(fuseLog, "fuseLog"); function logger(options) { const options_ = options ?? defaultLoggingOptions; return (0, import_pino.default)(options_); } __name(logger, "logger"); // src/open-api.ts var import_swagger = require("@fastify/swagger"); var import_swagger_ui = require("@fastify/swagger-ui"); var import_read_package_up = require("read-package-up"); var defaultOpenApiOptions = { title: "Open API Documentation", description: "API Documentation for the Service", version: "0.0.0", openApiRoutePrefix: "/openapi", docsRoutePath: "/" }; var fastifySwaggerConfig = { openapi: { info: { title: "Open API Documentation", description: "API Documentation for the Service", version: "0.0.0" }, consumes: [ "application/json" ], produces: [ "application/json" ] } }; async function fuseOpenApi(fastify, options) { const config = structuredClone(defaultOpenApiOptions); const pkg = await (0, import_read_package_up.readPackageUp)(); config.title = options?.title ?? pkg?.packageJson?.name ?? config.title; config.description = options?.description ?? pkg?.packageJson?.description ?? config.description; config.version = options?.version ?? pkg?.packageJson?.version ?? config.version; config.openApiRoutePrefix = options?.openApiRoutePrefix ?? config.openApiRoutePrefix; config.docsRoutePath = options?.docsRoutePath ?? config.docsRoutePath; const swaggerConfig = structuredClone(fastifySwaggerConfig); swaggerConfig.openapi.info.title = config.title; swaggerConfig.openapi.info.description = config.description; swaggerConfig.openapi.info.version = config.version; await fastify.register(import_swagger.fastifySwagger, swaggerConfig); await fastify.register(import_swagger_ui.fastifySwaggerUi, { routePrefix: config.openApiRoutePrefix, uiConfig: { docExpansion: "none", deepLinking: false }, uiHooks: { /* c8 ignore next 6 */ onRequest(_request, _reply, next) { next(); }, preHandler(_request, _reply, next) { next(); } }, // eslint-disable-next-line @typescript-eslint/naming-convention staticCSP: true, transformSpecification: /* @__PURE__ */ __name((swaggerObject, _request, _reply) => swaggerObject, "transformSpecification"), transformSpecificationClone: true }); fastify.log.info(`Fastify OpenAPI Registered: ${JSON.stringify(config)}`); await indexRoute(fastify, config); fastify.log.info(`Fastify API Docs Registered: ${config.docsRoutePath}`); } __name(fuseOpenApi, "fuseOpenApi"); async function indexRoute(fastify, options) { const indexPath = options?.docsRoutePath ?? defaultOpenApiOptions.docsRoutePath; fastify.get(indexPath, { schema: { hide: true } }, async (_request, reply) => { const title = options?.title ?? defaultOpenApiOptions.title; const openApiRoutePrefix = options?.openApiRoutePrefix ?? defaultOpenApiOptions.openApiRoutePrefix; const redocHtml = ` <!doctype html> <html> <head> <title>${title}</title> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> </head> <body> <script id="api-reference" data-url="${openApiRoutePrefix}/json"></script> <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference@1.31.17/dist/browser/standalone.min.js"></script> </body> </html> `; await reply.type("text/html; charset=utf-8").send(redocHtml); }); } __name(indexRoute, "indexRoute"); // src/rate-limit.ts var import_rate_limit = require("@fastify/rate-limit"); var defaultFastifyRateLimitOptions = { // Enable rate limiting global: true, // Limit to 100 requests per minute max: 500, // Time window for the rate limit timeWindow: 6e4, // allow list for local development and testing allowList: [ "127.0.0.1", "0.0.0.0" ] }; async function fuseRateLimit(fastify, options) { await fastify.register(import_rate_limit.fastifyRateLimit, options); fastify.log.info(`Fasity Rate Limit Registered: ${JSON.stringify(options)}`); } __name(fuseRateLimit, "fuseRateLimit"); // src/static.ts var import_node_path = __toESM(require("path"), 1); var import_static = __toESM(require("@fastify/static"), 1); async function fuseStatic(fastify, options) { for (const staticPath of options) { let rootPath = staticPath.dir; if (!import_node_path.default.isAbsolute(rootPath)) { rootPath = import_node_path.default.resolve(rootPath); } await fastify.register(import_static.default, { root: rootPath, prefix: staticPath.path, decorateReply: false }); fastify.log.info(`Static path registered: ${staticPath.path} -> ${rootPath}`); } } __name(fuseStatic, "fuseStatic"); // src/fuse.ts async function fuse(fastify, options) { options ??= { static: true, log: true, helmet: true, rateLimit: true, cors: true, openApi: true, cache: true }; if (options.log !== void 0 && typeof options.log !== "boolean") { await fuseLog(fastify, options.log); } else if (options.log !== false) { await fuseLog(fastify, defaultLoggingOptions); } if (options.static !== void 0 && typeof options.static !== "boolean") { await fuseStatic(fastify, options.static); } else if (options.static !== false) { const defaultStaticPath = [ { dir: import_node_path2.default.resolve("./public"), path: "/" } ]; await fuseStatic(fastify, defaultStaticPath); } if (options.helmet !== void 0 && typeof options.helmet !== "boolean") { await fuseHelmet(fastify, options.helmet); } else if (options.helmet !== false) { await fuseHelmet(fastify, defaultFastifyHelmetOptions); } if (options.rateLimit !== void 0 && typeof options.rateLimit !== "boolean") { await fuseRateLimit(fastify, options.rateLimit); } else if (options.rateLimit !== false) { await fuseRateLimit(fastify, defaultFastifyRateLimitOptions); } if (options.cors !== void 0 && typeof options.cors !== "boolean") { await fuseCors(fastify, options.cors); } else if (options.cors !== false) { await fuseCors(fastify, defaultFastifyCorsOptions); } if (options.openApi !== void 0 && typeof options.openApi !== "boolean") { await fuseOpenApi(fastify, options.openApi); } else if (options.openApi !== false) { await fuseOpenApi(fastify); } if (options.cache !== void 0 && typeof options.cache !== "boolean") { await fuseCacheable(fastify, options.cache); } else if (options.cache !== false) { await fuseCacheable(fastify, defaultCacheableOptions); } } __name(fuse, "fuse"); // src/index.ts var defaultStartOptions = { port: 3e3, host: "0.0.0.0", message: /* @__PURE__ */ __name((host, port) => `\u{1F30F} started successfully at http://${host}:${port}`, "message") }; async function start(fastify, options = defaultStartOptions) { try { const portString = import_node_process.default.env.PORT ?? options.port; const host = import_node_process.default.env.HOST ?? options.host; if (portString === void 0 || portString === null || Number.isNaN(portString)) { throw new Error("Port is not defined. Please set the PORT environment variable or provide a port in the options."); } const port = Number(portString); if (host === void 0 || host === null || host.trim() === "") { throw new Error("Host is not defined. Please set the HOST environment variable or provide a host in the options."); } await fastify.listen({ port, host }); } catch (error) { fastify.log.error(error); } } __name(start, "start"); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { defaultCacheableOptions, defaultFastifyHelmetOptions, defaultFastifyRateLimitOptions, defaultLoggingOptions, defaultStartOptions, fuse, fuseCacheable, fuseHelmet, fuseLog, fuseOpenApi, fuseRateLimit, fuseStatic, logger, start });