UNPKG

@tsed/platform-fastify

Version:
278 lines (277 loc) 9.54 kB
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 } ]);