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
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 __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
});
;