CoolFace
Apppublic

strong-tie/inbound-calls

sourceHugging Faceupdated 2y agoView on Hugging Face
0likes
README.md671 linesDownload Raw Back to avvio
1# avvio2 3![CI](https://github.com/fastify/avvio/workflows/CI/badge.svg)4[![NPM version](https://img.shields.io/npm/v/avvio.svg?style=flat)](https://www.npmjs.com/package/avvio)5[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://standardjs.com/)6 7Asynchronous bootstrapping is hard, different things can go wrong, *error handling* and *load order* just to name a few. The aim of this module is to make it simple.8 9`avvio` is fully *reentrant* and *graph-based*. You can load10components/plugins *within* plugins, and be still sure that things will11happen in the right order. At the end of the loading, your application will start.12 13* [Install](#install)14* [Example](#example)15* [API](#api)16* [Acknowledgements](#acknowledgements)17* [License](#license)18 19<a name="install"></a>20## Install21 22To install `avvio`, simply use npm:23 24```25npm i avvio26```27 28<a name="example"></a>29## Example30 31The example below can be found [here][example] and run using `node example.js`.32It demonstrates how to use `avvio` to load functions / plugins in order.33 34 35```js36'use strict'37 38const app = require('avvio')()39 40app41  .use(first, { hello: 'world' })42  .after((err, cb) => {43    console.log('after first and second')44    cb()45  })46 47app.use(third)48 49app.ready(function (err) {50  // the error must be handled somehow51  if (err) {52    throw err53  }54  console.log('application booted!')55})56 57function first (instance, opts, cb) {58  console.log('first loaded', opts)59  instance.use(second)60  cb()61}62 63function second (instance, opts, cb) {64  console.log('second loaded')65  process.nextTick(cb)66}67 68// async/await or Promise support69async function third (instance, opts) {70  console.log('third loaded')71}72```73 74<a name="api"></a>75## API76 77  * <a href="#constructor"><code><b>avvio()</b></code></a>78  * <a href="#use"><code>instance.<b>use()</b></code></a>79  * <a href="#after"><code>instance.<b>after()</b></code></a>80  * <a href="#await-after"><code>await instance.<b>after()</b></code></a>81  * <a href="#ready"><code>instance.<b>ready()</b></code></a>82  * <a href="#start"><code>instance.<b>start()</b></code></a>83  * <a href="#override"><code>instance.<b>override()</b></code></a>84  * <a href="#onClose"><code>instance.<b>onClose()</b></code></a>85  * <a href="#close"><code>instance.<b>close()</b></code></a>86  * <a href="#toJSON"><code>avvio.<b>toJSON()</b></code></a>87  * <a href="#prettyPrint"><code>avvio.<b>prettyPrint()</b></code></a>88 89-------------------------------------------------------90<a name="constructor"></a>91 92### avvio([instance], [options], [started])93 94Starts the avvio sequence.95As the name suggests, `instance` is the object representing your application.96Avvio will add the functions `use`, `after` and `ready` to the instance.97 98```js99const server = {}100 101require('avvio')(server)102 103server.use(function first (s, opts, cb) {104  // s is the same of server105  s.use(function second (s, opts, cb) {106    cb()107  })108  cb()109}).after(function (err, cb) {110  // after first and second are finished111  cb()112})113```114 115Options:116 117* `expose`: a key/value property to change how `use`, `after` and `ready` are exposed.118* `autostart`: do not start loading plugins automatically, but wait for119  a call to [`.start()`](#start)  or [`.ready()`](#ready).120* `timeout`: the number of millis to wait for a plugin to load after which121  it will error with code `ERR_AVVIO_PLUGIN_TIMEOUT`. Default122  `0` (disabled).123 124Events:125 126* `'start'`  when the application starts127* `'preReady'` fired before the ready queue is run128 129The `avvio` function can also be used as a130constructor to inherit from.131```js132function Server () {}133const app = require('avvio')(new Server())134 135app.use(function (s, opts, done) {136  // your code137  done()138})139 140app.on('start', () => {141  // you app can start142})143```144 145-------------------------------------------------------146<a name="use"></a>147 148### app.use(func, [optsOrFunc]) => Thenable149 150Loads one or more functions asynchronously.151 152The function **must** have the signature: `instance, options, done`153 154Plugin example:155```js156function plugin (server, opts, done) {157  done()158}159 160app.use(plugin)161```162`done` should be called only once, when your plugin is ready to go.  Additional calls to `done` are ignored.163 164If your plugin is ready to go immediately after the function is evaluated, you can omit `done` from the signature.165 166If the function returns a `Promise` (i.e. `async`), the above function signature is not required.167 168`use` returns a thenable wrapped instance on which `use` is called, to support a chainable API that can also be awaited.169 170This way, async/await is also supported and `use` can be awaited instead of using `after`.171 172Example using `after`:173 174```js175async function main () {176  console.log('begin')177  app.use(async function (server, opts) {178    await sleep(10)179    console.log('this first')180  })181  app.after(async (err) => {182    if (err) throw err183    console.log('then this')184  })185  await app.ready()186  console.log('ready')187}188main().catch((err) => console.error(err))189```190 191Example using `await after`:192 193 194```js195async function main () {196  console.log('begin')197  app.use(async function (server, opts) {198    await sleep(10)199    console.log('this first')200  })201  await app.after()202  console.log('then this')203  await app.ready()204  console.log('ready')205}206main().catch((err) => console.error(err))207```208 209Example using `await use`:210 211```js212async function main () {213  console.log('begin')214  await app.use(async function (server, opts) {215    await sleep(10)216    console.log('this first')217  })218  console.log('then this')219  await app.ready()220  console.log('ready')221}222main().catch((err) => console.error(err))223```224 225A function that returns the options argument instead of an object is supported as well:226 227```js228function first (server, opts, done) {229  server.foo = 'bar'230  done()231}232 233function second (server, opts, done) {234  console.log(opts.foo === 'bar') // Evaluates to true235  done()236}237 238/**239 * If the options argument is a function, it has access to the parent240 * instance via the first positional variable241 */242const func = parent => {243  return {244    foo: parent.foo245  }246}247 248app.use(first)249app.use(second, func)250```251 252This is useful in cases where an injected variable from a plugin needs to be made available to another.253 254It is also possible to use [esm](https://nodejs.org/api/esm.html) with `import('./file.mjs')`:255 256```js257import boot from 'avvio'258 259const app = boot()260app.use(import('./fixtures/esm.mjs'))261```262 263-------------------------------------------------------264<a name="error-handling"></a>265#### Error handling266 267In order to handle errors in the loading plugins, you must use the268`.ready()` method, like so:269 270```js271app.use(function (instance, opts, done) {272  done(new Error('error'))273}, opts)274 275app.ready(function (err) {276  if (err) throw err277})278```279 280When an error happens, the loading of plugins will stop until there is281an [`after`](#after) callback specified. Otherwise, it will be handled282in [`ready`](#ready).283 284-------------------------------------------------------285<a name="after"></a>286 287### app.after(func(error, [context], [done]))288 289Calls a function after all the previously defined plugins are loaded, including290all their dependencies. The `'start'` event is not emitted yet.291 292Note: `await after` can be used as an awaitable alternative to `after(func)`, or `await use` can be also as a shorthand for `use(plugin); await after()`.293 294The callback changes based on the parameters you give:2951. If no parameter is given to the callback and there is an error, that error will be passed to the next error handler.2962. If one parameter is given to the callback, that parameter will be the `error` object.2973. If two parameters are given to the callback, the first will be the `error` object, the second will be the `done` callback.2984. If three parameters are given to the callback, the first will be the `error` object, the second will be the top level `context` and the third the `done` callback.299 300In the "no parameter" and "one parameter" variants, the callback can return a `Promise`.301 302```js303const server = {}304const app = require('avvio')(server)305 306...307// after with one parameter308app.after(function (err) {309  if (err) throw err310})311 312// after with two parameter313app.after(function (err, done) {314  if (err) throw err315  done()316})317 318// after with three parameters319app.after(function (err, context, done) {320  if (err) throw err321  assert.equal(context, server)322  done()323})324 325// async after with one parameter326app.after(async function (err) {327  await sleep(10)328  if (err) {329    throw err330  }331})332 333// async after with no parameter334app.after(async function () {335  await sleep(10)336})337```338 339`done` must be called only once.340 341If called with a function, it returns the instance on which `after` is called, to support a chainable API.342 343-------------------------------------------------------344<a name="await-after"></a>345 346### await app.after() | app.after() => Promise347 348Calling after with no function argument loads any plugins previously registered via `use` and returns a promise, which resolves when all plugins registered so far have loaded.349 350```js351async function main () {352  app.use(async function (server, opts) {353    await sleep(10)354    console.log('this first')355  })356  app.use(async function (server, opts) {357    await sleep(10)358    console.log('this second')359  })360  console.log('before after')361  await app.after()362  console.log('after after')363  app.use(async function (server, opts) {364    await sleep(10)365    console.log('this third')366  })367  await app.ready()368  console.log('ready')369}370main().catch((err) => console.error(err))371```372 373Unlike `after` and `use`, `await after` is *not* chainable.374 375-------------------------------------------------------376<a name="ready"></a>377 378### app.ready([func(error, [context], [done])])379 380Calls a function after all the plugins and `after` call are completed, but before `'start'` is emitted. `ready` callbacks are executed one at a time.381 382The callback changes based on the parameters you give:3831. If no parameter is given to the callback and there is an error, that error will be passed to the next error handler.3842. If one parameter is given to the callback, that parameter will be the `error` object.3853. If two parameters are given to the callback, the first will be the `error` object, the second will be the `done` callback.3864. If three parameters are given to the callback, the first will be the `error` object, the second will be the top level `context` unless you have specified both server and override, in that case the `context` will be what the override returns, and the third the `done` callback.387 388If no callback is provided `ready` will return a Promise that is resolved or rejected once plugins and `after` calls are completed.  On success `context` is provided to the `.then` callback, if an error occurs it is provided to the `.catch` callback.389 390```js391const server = {}392const app = require('avvio')(server)393...394// ready with one parameter395app.ready(function (err) {396  if (err) throw err397})398 399// ready with two parameter400app.ready(function (err, done) {401  if (err) throw err402  done()403})404 405// ready with three parameters406app.ready(function (err, context, done) {407  if (err) throw err408  assert.equal(context, server)409  done()410})411 412// ready with Promise413app.ready()414  .then(() => console.log('Ready'))415  .catch(err => {416    console.error(err)417    process.exit(1)418  })419 420// await ready from an async function.421async function main () [422  try {423    await app.ready()424    console.log('Ready')425  } catch(err) {426    console.error(err)427    process.exit(1)428  }429}430```431 432`done` must be called only once.433 434The callback form of this function has no return value.435 436If `autostart: false` is passed as an option, calling `.ready()`  will437also start the boot sequence.438 439-------------------------------------------------------440<a name="start"></a>441 442### app.start()443 444Start the boot sequence, if it was not started yet.445Returns the `app` instance.446 447-------------------------------------------------------448<a name="override"></a>449 450### app.override(server, plugin, options)451 452Allows overriding the instance of the server for each loading plugin.453It allows the creation of an inheritance chain for the server instances.454The first parameter is the server instance and the second is the plugin function while the third is the options object that you give to use.455 456```js457const assert = require('node:assert')458const server = { count: 0 }459const app = require('avvio')(server)460 461console.log(app !== server, 'override must be set on the Avvio instance')462 463app.override = function (s, fn, opts) {464  // create a new instance with the465  // server as the prototype466  const res = Object.create(s)467  res.count = res.count + 1468 469  return res470}471 472app.use(function first (s1, opts, cb) {473  assert(s1 !== server)474  assert(Object.prototype.isPrototypeOf.call(server, s1))475  assert(s1.count === 1)476  s1.use(second)477  cb()478 479  function second (s2, opts, cb) {480    assert(s2 !== s1)481    assert(Object.prototype.isPrototypeOf.isPrototypeOf.call(s1, s2))482    assert(s2.count === 2)483    cb()484  }485})486```487-------------------------------------------------------488 489<a name="onClose"></a>490### app.onClose(func([context], [done]))491 492Registers a new callback that will be fired once then `close` api is called.493 494The callback changes basing on the parameters you give:4951. If one parameter is given to the callback, that parameter will be the `context`.4962. If zero or one parameter is given, the callback may return a promise4973. If two parameters are given to the callback, the first will be the top level `context` unless you have specified both server and override, in that case the `context` will be what the override returns, the second will be the `done` callback.498 499```js500const server = {}501const app = require('avvio')(server)502...503// onClose with one parameter504app.onClose(function (context) {505  // ...506})507 508// onClose with one parameter, returning a promise509app.onClose(function (context) {510  return new Promise((resolve, reject) => {511    // ...512  })513})514 515// async onClose with one parameter516app.onClose(async function (context) {517  // ...518  await ...519})520 521 522// onClose with two parameter523app.onClose(function (context, done) {524  // ...525  done()526})527```528 529If the callback returns a promise, the next onClose callback and the close callback will not run until the promise is either resolved or rejected.530 531`done` must be called only once.532Returns the instance on which `onClose` is called, to support a chainable API.533 534-------------------------------------------------------535 536<a name="close"></a>537### app.close(func(error, [context], [done]))538 539Starts the shutdown procedure, the callback is called once all the registered callbacks with `onClose` has been executed.540 541The callback changes based on the parameters you give:5421. If one parameter is given to the callback, that parameter will be the `error` object.5432. If two parameters are given to the callback, the first will be the `error` object, the second will be the `done` callback.5443. If three parameters are given to the callback, the first will be the `error` object, the second will be the top level `context` unless you have specified both server and override, in that case the `context` will be what the override returns, and the third the `done` callback.545 546If no callback is provided `close` will return a Promise.547 548```js549const server = {}550const app = require('avvio')(server)551...552// close with one parameter553app.close(function (err) {554  if (err) throw err555})556 557// close with two parameter558app.close(function (err, done) {559  if (err) throw err560  done()561})562 563// close with three parameters564app.close(function (err, context, done) {565  if (err) throw err566  assert.equal(context, server)567  done()568})569 570// close with Promise571app.close()572  .then(() => console.log('Closed'))573  .catch(err => {574    console.error(err)575    process.exit(1)576  })577 578```579 580`done` must be called only once.581 582-------------------------------------------------------583 584<a name="toJSON"></a>585 586### avvio.toJSON()587 588Return a JSON tree representing the state of the plugins and the loading time.589Call it on `preReady` to get the complete tree.590 591```js592const avvio = require('avvio')()593avvio.on('preReady', () => {594  avvio.toJSON()595})596```597 598The output is like this:599```json600{601  "label": "root",602  "start": 1550245184665,603  "nodes": [604    {605      "parent": "root",606      "start": 1550245184665,607      "label": "first",608      "nodes": [609        {610          "parent": "first",611          "start": 1550245184708,612          "label": "second",613          "nodes": [],614          "stop": 1550245184709,615          "diff": 1616        }617      ],618      "stop": 1550245184709,619      "diff": 44620    },621    {622      "parent": "root",623      "start": 1550245184709,624      "label": "third",625      "nodes": [],626      "stop": 1550245184709,627      "diff": 0628    }629  ],630  "stop": 1550245184709,631  "diff": 44632}633```634 635-------------------------------------------------------636 637<a name="prettyPrint"></a>638 639### avvio.prettyPrint()640 641This method will return a printable string with the tree returned by the `toJSON()` method.642 643```js644const avvio = require('avvio')()645avvio.on('preReady', () => {646  console.log(avvio.prettyPrint())647})648```649 650The output will be like:651 652```653avvio 56 ms654├── first 52 ms655├── second 1 ms656└── third 2 ms657```658 659-------------------------------------------------------660 661## Acknowledgements662 663This project was kindly sponsored by [nearForm](https://nearform.com).664 665## License666 667Copyright Matteo Collina 2016-2020, Licensed under [MIT][].668 669[MIT]: ./LICENSE670[example]: ./examples/example.js671