fastify-fusion
Version:
Fastify API framework with `best practices` and `plugins` fused together to make it easy to build and maintain your API.
306 lines (294 loc) • 9.5 kB
JavaScript
var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
// src/index.ts
import process from "process";
// src/fuse.ts
import path2 from "path";
// src/static.ts
import path from "path";
import fastifyStatic from "@fastify/static";
async function fuseStatic(fastify, options) {
for (const staticPath of options) {
let rootPath = staticPath.dir;
if (!path.isAbsolute(rootPath)) {
rootPath = path.resolve(rootPath);
}
await fastify.register(fastifyStatic, {
root: rootPath,
prefix: staticPath.path,
decorateReply: false
});
fastify.log.info(`Static path registered: ${staticPath.path} -> ${rootPath}`);
}
}
__name(fuseStatic, "fuseStatic");
// src/log.ts
import pino from "pino";
var defaultLoggingOptions = {
transport: {
target: "pino-pretty",
options: {
colorize: true,
translateTime: true,
ignore: "pid,hostname",
singleLine: true
}
}
};
async function fuseLog(fastify, options) {
fastify.log = pino(options);
fastify.log.info(`Fasity Logging Registered: ${JSON.stringify(options)}`);
}
__name(fuseLog, "fuseLog");
function logger(options) {
const options_ = options ?? defaultLoggingOptions;
return pino(options_);
}
__name(logger, "logger");
// src/helmet.ts
import { fastifyHelmet } from "@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(fastifyHelmet, options);
fastify.log.info(`Fasity Helment Registered: ${JSON.stringify(options)}`);
}
__name(fuseHelmet, "fuseHelmet");
// src/rate-limit.ts
import { fastifyRateLimit } from "@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(fastifyRateLimit, options);
fastify.log.info(`Fasity Rate Limit Registered: ${JSON.stringify(options)}`);
}
__name(fuseRateLimit, "fuseRateLimit");
// src/open-api.ts
import { fastifySwagger } from "@fastify/swagger";
import { fastifySwaggerUi } from "@fastify/swagger-ui";
import { readPackageUp } from "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 = defaultOpenApiOptions;
const pkg = await readPackageUp();
if (pkg && pkg.packageJson) {
config.title = pkg.packageJson.name ?? defaultOpenApiOptions.title;
config.description = pkg.packageJson.description ?? defaultOpenApiOptions.description;
config.version = pkg.packageJson.version ?? defaultOpenApiOptions.version;
}
if (options) {
config.title = options.title ?? pkg?.packageJson.name ?? defaultOpenApiOptions.title;
config.description = options.description ?? pkg?.packageJson.description ?? defaultOpenApiOptions.description;
config.version = options.version ?? pkg?.packageJson.version ?? defaultOpenApiOptions.version;
config.openApiRoutePrefix = options.openApiRoutePrefix ?? defaultOpenApiOptions.openApiRoutePrefix;
config.docsRoutePath = options.docsRoutePath ?? defaultOpenApiOptions.docsRoutePath;
}
const swaggerConfig = fastifySwaggerConfig;
swaggerConfig.openapi.info.title = config.title;
swaggerConfig.openapi.info.description = config.description;
swaggerConfig.openapi.info.version = config.version;
await fastify.register(fastifySwagger, fastifySwaggerConfig);
await fastify.register(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(`Fasity OpenAPI Registered: ${JSON.stringify(config)}`);
await indexRoute(fastify, config);
fastify.log.info(`Fasity 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 openApiRoutePrefix = options?.openApiRoutePrefix ?? defaultOpenApiOptions.openApiRoutePrefix;
const redocHtml = `
<!doctype html>
<html>
<head>
<title>Hyphen Notifyr</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/fuse.ts
async function fuse(fastify, options) {
options ??= {
static: true,
log: true,
helmet: true,
rateLimit: true,
openApi: 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: path2.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.openApi !== void 0 && typeof options.openApi !== "boolean") {
await fuseOpenApi(fastify, options.openApi);
} else if (options.openApi !== false) {
await fuseOpenApi(fastify);
}
}
__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 = process.env.PORT ?? options.port;
const host = process.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");
export {
defaultFastifyHelmetOptions,
defaultFastifyRateLimitOptions,
defaultLoggingOptions,
defaultStartOptions,
fuse,
fuseHelmet,
fuseLog,
fuseOpenApi,
fuseRateLimit,
fuseStatic,
logger,
start
};