UNPKG

fastify-overview

Version:

Get a complete overview of your fastify application

198 lines (170 loc) 5.79 kB
'use strict' const fp = require('fastify-plugin') const getSource = require('./lib/source-code') const kTrackerMe = Symbol('fastify-overview.track-me') const kStructure = Symbol('fastify-overview.structure') const kSourceRegister = Symbol('fastify-overview.source.register') const kSourceRoute = Symbol('fastify-overview.source.route') const { transformRoute, getDecoratorNode, getPluginNode, getHookNode, filterStructure } = require('./lib/utils') function fastifyOverview (fastify, options, next) { const opts = Object.assign({ addSource: false }, options) const contextMap = new Map() let structure fastify.addHook('onRegister', function markInstance (instance) { const parent = Object.getPrototypeOf(instance) // this is the `avvio` instance manInTheMiddle.call(this, instance, parent[kTrackerMe]) }) fastify.addHook('onRoute', function markRoute (routeOpts) { const routeNode = Object.assign(transformRoute(routeOpts), opts.onRouteDefinition?.(routeOpts)) if (opts.addSource) { routeNode.source = routeOpts.handler[kSourceRoute] // the hooks added using the route options, does not have the `source` property // so we can use the same as the route handler const hooksKey = Object.keys(routeNode.hooks) for (const hookKey of hooksKey) { routeNode.hooks[hookKey].forEach(hookNode => { hookNode.source = routeNode.source }) } } this[kStructure].routes.push(routeNode) }) fastify.addHook('onReady', function hook (done) { const root = contextMap.get(rootToken) structure = root contextMap.clear() done(null) }) fastify.decorate('overview', function getOverview (opts) { if (!structure) { throw new Error('Fastify must be in ready status to access the overview') } if (opts?.hideEmpty || opts?.routesFilter) { return filterStructure(structure, opts) } return structure }) const rootToken = manInTheMiddle(fastify) wrapFastify(fastify, opts) if (opts.exposeRoute === true) { const routeConfig = Object.assign( { method: 'GET', exposeHeadRoute: false, url: '/json-overview' }, opts.exposeRouteOptions, { handler: getJsonOverview }) fastify.route(routeConfig) } next() function manInTheMiddle (instance, parentId) { const trackingToken = Math.random() instance[kTrackerMe] = trackingToken const trackStructure = getPluginNode(trackingToken, instance.pluginName) contextMap.set(trackingToken, trackStructure) instance[kStructure] = trackStructure if (parentId) { contextMap.get(parentId).children.push(trackStructure) } return trackingToken } } /** * this function is executed only once: when the plugin is registered. * if it is executed more than once, the output structure will have duplicated * entries. * this is caused by the fact that the wrapDecorate will call wrapDecorate again and so on. * Running the code only the first time relies on the Fastify prototype chain. * * The key here is to use the this[kStructure] property to get the right structure to update. */ function wrapFastify (instance, pluginOpts) { // *** decorators wrapDecorator(instance, 'decorate', pluginOpts) wrapDecorator(instance, 'decorateRequest', pluginOpts) wrapDecorator(instance, 'decorateReply', pluginOpts) // *** register const originalRegister = instance.register instance.register = function wrapRegister (pluginFn, opts) { if (pluginOpts.addSource) { // this Symbol is processed by the `onRegister` hook if necessary pluginFn[kSourceRegister] = getSource()[0] } return originalRegister.call(this, pluginFn, opts) } // *** routes ;[ 'delete', 'get', 'head', 'patch', 'post', 'put', 'options', 'all' ].forEach(shortcut => { const originalMethod = instance[shortcut] instance[shortcut] = function wrapRoute (url, opts, handler) { if (pluginOpts.addSource) { // this Symbol is processed by the `onRoute` hook getRouteHandler(url, opts, handler)[kSourceRoute] = getSource()[0] } return originalMethod.call(this, url, opts, handler) } }) const originalRoute = instance.route instance.route = function wrapRoute (routeOpts) { if (pluginOpts.addSource) { // this Symbol is processed by the `onRoute` hook routeOpts.handler[kSourceRoute] = getSource()[0] } return originalRoute.call(this, routeOpts) } // *** hooks const originalHook = instance.addHook instance.addHook = function wrapAddHook (name, hook) { const hookNode = getHookNode(hook) if (pluginOpts.addSource) { hookNode.source = getSource()[0] } this[kStructure].hooks[name].push(hookNode) return originalHook.call(this, name, hook) } } function wrapDecorator (instance, type, { addSource, onDecorateDefinition }) { const originalDecorate = instance[type] instance[type] = function wrapDecorate (name, value) { const decoratorNode = Object.assign(getDecoratorNode(name, value), onDecorateDefinition?.(type, name, value)) if (addSource) { decoratorNode.source = getSource()[0] } this[kStructure].decorators[type].push(decoratorNode) return originalDecorate.call(this, name, value) } } function getRouteHandler (url, options, handler) { if (!handler && typeof options === 'function') { handler = options } return handler || (options && options.handler) } function getJsonOverview (request, reply) { return this.overview() } const plugin = fp(fastifyOverview, { name: 'fastify-overview', fastify: '5.x' }) module.exports = plugin module.exports.default = plugin module.exports.fastifyOverview = plugin