@tsed/platform-fastify
Version:
Fastify package for Ts.ED framework
278 lines (277 loc) • 9.54 kB
JavaScript
import "@tsed/platform-multer/fastify";
import * as Http from "node:http";
import * as Https from "node:https";
import fastifyMiddie from "@fastify/middie";
import fastifyStatics from "@fastify/static";
import { isFunction, isString } from "@tsed/core";
import { constant, inject, logger, runInContext } from "@tsed/di";
import { NotFound } from "@tsed/exceptions";
import { $alter } from "@tsed/hooks";
import { PlatformExceptions } from "@tsed/platform-exceptions";
import { adapter, createContext, createServer, PlatformAdapter, PlatformBuilder, PlatformRequest, PlatformResponse } from "@tsed/platform-http";
import { PlatformHandlerType } from "@tsed/platform-router";
import Fastify from "fastify";
import { PlatformFastifyRequest } from "../services/PlatformFastifyRequest.js";
import { PlatformFastifyResponse } from "../services/PlatformFastifyResponse.js";
import { convertPath } from "../utils/convertPath.js";
export class PlatformFastify extends PlatformAdapter {
constructor() {
super(...arguments);
this.NAME = "fastify";
this.staticsDecorated = false;
}
/**
* Create new serverless application. In this mode, the component scan are disabled.
* @param module
* @param settings
*/
static create(module, settings = {}) {
return PlatformBuilder.create(module, {
...settings,
adapter: PlatformFastify
});
}
/**
* Bootstrap a server application
* @param module
* @param settings
*/
static bootstrap(module, settings = {}) {
return PlatformBuilder.bootstrap(module, {
...settings,
adapter: PlatformFastify
});
}
mapLayers(layers) {
const { app } = this;
const rawApp = app.getApp();
layers.forEach((layer) => {
const { path, wildcard } = convertPath(layer.path);
const handlers = layer.getArgs(false);
switch (layer.method) {
case "use":
if (rawApp.use) {
rawApp.use(path, handlers);
}
return;
case "statics":
this.statics(path, layer.opts);
// rawApp.register();
return;
}
try {
rawApp.route({
method: layer.method.toUpperCase(),
url: path,
handler: this.compose(layer, wildcard),
config: {
rawBody: layer.handlers.some((handler) => handler.opts?.paramsTypes?.RAW_BODY)
}
});
}
catch (er) {
logger().warn({
error_name: er.code,
error_message: er.message
});
}
});
}
mapHandler(handler, metadata) {
if (metadata.isRawMiddleware()) {
return handler;
}
switch (metadata.type) {
case PlatformHandlerType.MIDDLEWARE:
return (request, _, done) => {
const { $ctx } = request;
$ctx.next = done;
return runInContext($ctx, () => handler($ctx));
};
default:
return async (request, _, done) => {
const { $ctx } = request;
$ctx.next = done;
await runInContext($ctx, () => handler($ctx));
if (metadata.type === PlatformHandlerType.CTX_FN) {
done();
}
};
}
}
async useContext() {
const { app } = this;
const invoke = createContext();
const rawApp = app.getApp();
rawApp.addHook("onRequest", async (request, reply) => {
const $ctx = invoke({
request: request,
response: reply
});
$ctx.request.getReq().$ctx = $ctx;
await $ctx.start();
});
rawApp.addHook("onResponse", async (request, reply) => {
const { $ctx } = request;
$ctx.request.getReq().$ctx = undefined;
await $ctx.finish();
});
await rawApp.register(fastifyMiddie, {
hook: "onRequest"
});
const plugins = await this.resolvePlugins();
for (const plugin of plugins) {
await rawApp.register(plugin.use, plugin.options);
}
}
createApp() {
const { app, ...props } = constant("fastify") || {};
const httpPort = constant("httpPort");
const httpOptions = constant("httpOptions");
const httpsPort = constant("httpsPort");
const httpsOptions = constant("httpsOptions");
const opts = {
...props,
ignoreTrailingSlash: true,
http: httpPort !== false
? {
...httpOptions
}
: null,
https: httpsPort !== false
? {
...httpsOptions
}
: null
};
const instance = app || Fastify(opts);
instance.decorateRequest("$ctx", null);
instance.decorateReply("locals", null);
return {
app: instance,
callback: () => {
return async (request, response) => {
await instance.ready();
instance.server.emit("request", request, response);
};
}
};
}
afterLoadRoutes() {
const rawApp = this.app.getApp();
rawApp.setErrorHandler((error, request, reply) => {
const { $ctx } = request;
$ctx.error = $ctx.error || error;
return inject(PlatformExceptions)?.catch(error, $ctx);
});
rawApp.setNotFoundHandler((request, reply) => {
const { $ctx } = request;
return inject(PlatformExceptions)?.catch(new NotFound(`Resource "${request.originalUrl}" not found`), $ctx);
});
return Promise.resolve();
}
getServers() {
const httpsPort = constant("httpsPort");
const httpPort = constant("httpPort");
const listen = (hostinfo) => this.app.getApp().listen({
host: hostinfo.address,
port: hostinfo.port
});
const server = () => this.app.getApp().server;
return [
createServer({
port: httpsPort,
type: "https",
token: Https.Server,
server,
listen
}),
createServer({
port: httpPort,
type: "http",
token: Http.Server,
server,
listen
})
];
}
bodyParser(type, opts) {
return null;
}
statics(endpoint, options) {
this.app.getApp().register(fastifyStatics, {
root: options.root,
prefix: endpoint,
decorateReply: !this.staticsDecorated
});
this.staticsDecorated = true;
return null;
}
compose(layer, wildcard) {
return (req, _) => {
const params = req.params;
if (wildcard && params["*"] && !params[wildcard]) {
params[wildcard] = params["*"];
}
return runInContext(req.$ctx, async () => {
const $ctx = req.$ctx;
$ctx.next = null;
for (const metadata of layer.handlers) {
try {
if (!req.$ctx.isDone()) {
if (($ctx.error && metadata.type === PlatformHandlerType.ERR_MIDDLEWARE) ||
(!$ctx.error && metadata.type !== PlatformHandlerType.ERR_MIDDLEWARE)) {
await metadata.compiledHandler($ctx);
}
}
}
catch (er) {
$ctx.error = er;
}
}
if (req.$ctx.error) {
// TODO maybe we can use platform exception here?
throw req.$ctx.error;
}
return $ctx.response.raw;
});
};
}
async resolvePlugins() {
let plugins = constant("plugins", []);
const env = constant("env");
const promises = plugins.map(async (plugin) => {
if (isFunction(plugin)) {
return {
env,
use: plugin
};
}
if (isString(plugin)) {
plugin = { env, use: plugin };
}
let { use } = plugin;
if (isString(use)) {
const mod = await import(use);
use = mod.default || mod;
}
return {
env,
...plugin,
use
};
});
plugins = await Promise.all(promises);
return $alter("$afterPlugins", plugins.filter((plugin) => plugin.use).filter((plugin) => plugin.env === env));
}
}
adapter(PlatformFastify, [
{
token: PlatformResponse,
useClass: PlatformFastifyResponse
},
{
token: PlatformRequest,
useClass: PlatformFastifyRequest
}
]);