Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

first implementation #11

Merged
merged 6 commits into from
Nov 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"extends": "standard"}
194 changes: 178 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,41 +1,203 @@
# template-nodejs
# fastify-overview

Template per Node.js modules
Look for `CHANGE ME` string to spot where adjust this template.
[![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
[![ci](https://github.com/Eomm/fastify-overview/actions/workflows/ci.yml/badge.svg)](https://github.com/Eomm/fastify-overview/actions/workflows/ci.yml)

---
Get a complete overview of your fastify application!
It gives you a tree structure to understand all the relations between your routes and plugins.

CHANGE ME: module description
It tracks:

[![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
- **ALL** the Fastify plugins
- **ALL** the Fastify decorators
- **ALL** the Fastify hooks

```
[![Build Status](https://github.com/Eomm/CHANGE ME/workflows/ci/badge.svg)](https://github.com/Eomm/CHANGE ME/actions)
Doing so you will get a complete overview of your application and you can:

```
- optimize your code
- optimize your application structure
- find out the application structure (expecially if you have joined a new team)
- automate some documentation tasks

This plugin is intended to be run only for _development_ purposes.

## Install

```
npm install <CHANGE ME>
npm install fastify-overview
```


## Usage

This plugin is super simple, just add it to your fastify instance and you will get a `overview()` method that will return a tree structure of your application:

```js
// CHANGE ME: some code
const fastify = require('fastify')

async function run() {
const app = fastify()

// await the plugin registration!
await app.register(require('fastify-overview'))

// create your application as usual
app.addHook('onSend', function hookRoot () {})
app.register(function register1 (instance, opts, next) {
instance.addHook('onRequest', function hook1 () {})
instance.register(function register2 (sub, opts, next) {
sub.addHook('onRequest', function hook2 () {})
next()
})
next()
})

// read your application structure when fastify is ready
app.addHook('onReady', function showStructure (done) {
const appStructure = app.overview()
console.log(JSON.stringify(appStructure, null, 2))
done(null)
})

await app.listen(3000)
}
run()
```

To use this plugin there are 3 things to know:

1. It starts tracking your application after the `await register()` of the plugin:
- what happens before, it is **not** tracked.
- it the `register` is not awaited, the structure will be **not** tracked.
2. The application structure can be accessed **after** the Fastify instance is `ready`. If you try to get it before the `ready` status, you will get an error.
3. The structure tracks hooks' name and decorators' name. If you use arrow functions the structure will be useless/unreadable.

### Structure

The JSON structure returned by the `overview` method is like the following:

```js
{
"name": "pluginName", // the name of the plugin | app.register(function pluginName (){})
"children": [ // the children of the fastify instance | instance.register(function subPlugin (){})
// it contains the same structure we are describing
],
"decorators": { // all the instance decorators | app.decorate('foo-bar', 42)
"decorate": [ "foo-bar" ], // the decorators' name
"decorateRequest": [], // app.decorateRequest('foo-bar', 42)
"decorateReply": [] // app.decorateReply('foo-bar', 42)
},
"hooks": { // all the instance hooks
"onRequest": [ "hook1" ], // app.addHook('onRequest', function hook1 (){})
"preParsing": [],
"preValidation": [],
"preHandler": [],
"preSerialization": [],
"onError": [],
"onSend": [],
"onResponse": [],
"onTimeout": [],
"onReady": [],
"onClose": [],
"onRoute": [],
"onRegister": []
}
}
```

Notice that an hook that appears in the parent node, is inherited by the children but it is not listed in the
children's hooks node.

For example, the previous code returns:

```json
{
"name": "fastify-overview",
"children": [
{
"name": "register1",
"children": [
{
"name": "register2",
"children": [],
"decorators": {
"decorate": [],
"decorateRequest": [],
"decorateReply": []
},
"hooks": {
"onRequest": [
"function hook2 () {}"
],
"preParsing": [],
"preValidation": [],
"preHandler": [],
"preSerialization": [],
"onError": [],
"onSend": [],
"onResponse": [],
"onTimeout": [],
"onReady": [],
"onClose": [],
"onRoute": [],
"onRegister": []
}
}
],
"decorators": {
"decorate": [],
"decorateRequest": [],
"decorateReply": []
},
"hooks": {
"onRequest": [
"function hook1 () {}"
],
"preParsing": [],
"preValidation": [],
"preHandler": [],
"preSerialization": [],
"onError": [],
"onSend": [],
"onResponse": [],
"onTimeout": [],
"onReady": [],
"onClose": [],
"onRoute": [],
"onRegister": []
}
}
],
"decorators": {
"decorate": [],
"decorateRequest": [],
"decorateReply": []
},
"hooks": {
"onRequest": [],
"preParsing": [],
"preValidation": [],
"preHandler": [],
"preSerialization": [],
"onError": [],
"onSend": [ "hookRoot" ],
"onResponse": [],
"onTimeout": [],
"onReady": [],
"onClose": [],
"onRoute": [],
"onRegister": []
}
}
```

### Options
## Roadmap

You can pass the following options during the registration:
What this plugin should track that is missing:

| Option | Default | Description |
|--------|---------|-------------|
|`change`| `me` | What does this opt?
- [ ] routes
- [ ] errorHandler
- [ ] 404 handler


## License
Expand Down
81 changes: 80 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,84 @@
'use strict'

module.exports = function () {
const fp = require('fastify-plugin')
const kTrackerMe = Symbol('fastify-overview.track-me')
const kStructure = Symbol('fastify-overview.structure')

const { getEmptyTree } = require('./lib/utils')

function fastifyOverview (fastify, opts, next) {
const contextMap = new Map()
let structure

fastify.addHook('onRegister', function markInstance (instance) {
const parent = Object.getPrototypeOf(instance)
manInTheMiddle(instance, parent[kTrackerMe])
})

fastify.addHook('onReady', function hook (done) {
const root = contextMap.get(rootToken)
structure = root
contextMap.clear()
done(null)
})

fastify.decorate('overview', function getOverview () {
if (!structure) {
throw new Error('Fastify must be in ready status to access the overview')
}
return structure
})

const rootToken = manInTheMiddle(fastify)
wrapFastify(fastify)

next()

function manInTheMiddle (instance, parentId) {
const trackingToken = Math.random()
instance[kTrackerMe] = trackingToken

const trackStructure = getEmptyTree(instance.pluginName)
if (parentId) {
contextMap.get(parentId).children.push(trackStructure)
}

contextMap.set(trackingToken, trackStructure)
instance[kStructure] = 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) {
wrapDecorator(instance, 'decorate')
wrapDecorator(instance, 'decorateRequest')
wrapDecorator(instance, 'decorateReply')

const originalHook = instance.addHook
instance.addHook = function wrapAddHook (name, hook) {
this[kStructure].hooks[name].push(hook.toString()) // todo get function name
return originalHook.call(this, name, hook)
}
}

function wrapDecorator (instance, type) {
const originalDecorate = instance[type]
instance[type] = function wrapDecorate (name, value) {
this[kStructure].decorators[type].push(name)
return originalDecorate.call(this, name, value)
}
}

module.exports = fp(fastifyOverview, {
name: 'fastify-overview'
})
28 changes: 28 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use strict'

module.exports.getEmptyTree = function getEmptyTree (name) {
return {
name,
children: [],
decorators: {
decorate: [],
decorateRequest: [],
decorateReply: []
},
hooks: {
onRequest: [],
preParsing: [],
preValidation: [],
preHandler: [],
preSerialization: [],
onError: [],
onSend: [],
onResponse: [],
onTimeout: [],
onReady: [],
onClose: [],
onRoute: [],
onRegister: []
}
}
}
23 changes: 16 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "template-nodejs",
"version": "0.0.1",
"description": "<CHANGE ME>",
"name": "fastify-overview",
"version": "1.0.0",
"description": "Get a complete overview of your fastify application",
"main": "index.js",
"scripts": {
"lint": "standard",
Expand All @@ -11,17 +11,26 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/Eomm/<CHANGE ME>.git"
"url": "git+https://github.com/Eomm/fastify-overview.git"
},
"keywords": [],
"keywords": [
"fastify",
"application-schema",
"overview",
"draw"
],
"author": "Manuel Spigolon <behemoth89@gmail.com> (https://github.com/Eomm)",
"license": "MIT",
"bugs": {
"url": "https://github.com/Eomm/<CHANGE ME>/issues"
"url": "https://github.com/Eomm/fastify-overview/issues"
},
"homepage": "https://github.com/Eomm/<CHANGE ME>#readme",
"homepage": "https://github.com/Eomm/fastify-overview#readme",
"devDependencies": {
"fastify": "^3.23.1",
"standard": "^16.0.3",
"tap": "^15.0.9"
},
"dependencies": {
"fastify-plugin": "^3.0.0"
}
}
Loading