Projektstart
This commit is contained in:
614
backend/node_modules/avvio/boot.js
generated
vendored
Normal file
614
backend/node_modules/avvio/boot.js
generated
vendored
Normal file
@@ -0,0 +1,614 @@
|
||||
'use strict'
|
||||
|
||||
const fastq = require('fastq')
|
||||
const EE = require('node:events').EventEmitter
|
||||
const inherits = require('node:util').inherits
|
||||
const {
|
||||
AVV_ERR_EXPOSE_ALREADY_DEFINED,
|
||||
AVV_ERR_CALLBACK_NOT_FN,
|
||||
AVV_ERR_ROOT_PLG_BOOTED,
|
||||
AVV_ERR_READY_TIMEOUT,
|
||||
AVV_ERR_ATTRIBUTE_ALREADY_DEFINED
|
||||
} = require('./lib/errors')
|
||||
const {
|
||||
kAvvio,
|
||||
kIsOnCloseHandler
|
||||
} = require('./lib/symbols')
|
||||
const { TimeTree } = require('./lib/time-tree')
|
||||
const { Plugin } = require('./lib/plugin')
|
||||
const { debug } = require('./lib/debug')
|
||||
const { validatePlugin } = require('./lib/validate-plugin')
|
||||
const { isBundledOrTypescriptPlugin } = require('./lib/is-bundled-or-typescript-plugin')
|
||||
const { isPromiseLike } = require('./lib/is-promise-like')
|
||||
const { thenify } = require('./lib/thenify')
|
||||
const { executeWithThenable } = require('./lib/execute-with-thenable')
|
||||
|
||||
function Boot (server, opts, done) {
|
||||
if (typeof server === 'function' && arguments.length === 1) {
|
||||
done = server
|
||||
opts = {}
|
||||
server = null
|
||||
}
|
||||
|
||||
if (typeof opts === 'function') {
|
||||
done = opts
|
||||
opts = {}
|
||||
}
|
||||
|
||||
opts = opts || {}
|
||||
opts.autostart = opts.autostart !== false
|
||||
opts.timeout = Number(opts.timeout) || 0
|
||||
opts.expose = opts.expose || {}
|
||||
|
||||
if (!new.target) {
|
||||
return new Boot(server, opts, done)
|
||||
}
|
||||
|
||||
this._server = server || this
|
||||
this._opts = opts
|
||||
|
||||
if (server) {
|
||||
this._expose()
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Array<Plugin>}
|
||||
*/
|
||||
this._current = []
|
||||
|
||||
this._error = null
|
||||
|
||||
this._lastUsed = null
|
||||
|
||||
this.setMaxListeners(0)
|
||||
|
||||
if (done) {
|
||||
this.once('start', done)
|
||||
}
|
||||
|
||||
this.started = false
|
||||
this.booted = false
|
||||
this.pluginTree = new TimeTree()
|
||||
|
||||
this._readyQ = fastq(this, callWithCbOrNextTick, 1)
|
||||
this._readyQ.pause()
|
||||
this._readyQ.drain = () => {
|
||||
this.emit('start')
|
||||
// nooping this, we want to emit start only once
|
||||
this._readyQ.drain = noop
|
||||
}
|
||||
|
||||
this._closeQ = fastq(this, closeWithCbOrNextTick, 1)
|
||||
this._closeQ.pause()
|
||||
this._closeQ.drain = () => {
|
||||
this.emit('close')
|
||||
// nooping this, we want to emit close only once
|
||||
this._closeQ.drain = noop
|
||||
}
|
||||
|
||||
this._doStart = null
|
||||
|
||||
const instance = this
|
||||
this._root = new Plugin(fastq(this, this._loadPluginNextTick, 1), function root (server, opts, done) {
|
||||
instance._doStart = done
|
||||
opts.autostart && instance.start()
|
||||
}, opts, false, 0)
|
||||
|
||||
this._trackPluginLoading(this._root)
|
||||
|
||||
this._loadPlugin(this._root, (err) => {
|
||||
debug('root plugin ready')
|
||||
try {
|
||||
this.emit('preReady')
|
||||
this._root = null
|
||||
} catch (preReadyError) {
|
||||
err = err || this._error || preReadyError
|
||||
}
|
||||
|
||||
if (err) {
|
||||
this._error = err
|
||||
if (this._readyQ.length() === 0) {
|
||||
throw err
|
||||
}
|
||||
} else {
|
||||
this.booted = true
|
||||
}
|
||||
this._readyQ.resume()
|
||||
})
|
||||
}
|
||||
|
||||
inherits(Boot, EE)
|
||||
|
||||
Boot.prototype.start = function () {
|
||||
this.started = true
|
||||
|
||||
// we need to wait any call to use() to happen
|
||||
process.nextTick(this._doStart)
|
||||
return this
|
||||
}
|
||||
|
||||
// allows to override the instance of a server, given a plugin
|
||||
Boot.prototype.override = function (server, func, opts) {
|
||||
return server
|
||||
}
|
||||
|
||||
Boot.prototype[kAvvio] = true
|
||||
|
||||
// load a plugin
|
||||
Boot.prototype.use = function (plugin, opts) {
|
||||
this._lastUsed = this._addPlugin(plugin, opts, false)
|
||||
return this
|
||||
}
|
||||
|
||||
Boot.prototype._loadRegistered = function () {
|
||||
const plugin = this._current[0]
|
||||
const weNeedToStart = !this.started && !this.booted
|
||||
|
||||
// if the root plugin is not loaded, let's resume that
|
||||
// so one can use after() before calling ready
|
||||
if (weNeedToStart) {
|
||||
process.nextTick(() => this._root.queue.resume())
|
||||
}
|
||||
|
||||
if (!plugin) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
return plugin.loadedSoFar()
|
||||
}
|
||||
|
||||
Object.defineProperty(Boot.prototype, 'then', { get: thenify })
|
||||
|
||||
Boot.prototype._addPlugin = function (pluginFn, opts, isAfter) {
|
||||
if (isBundledOrTypescriptPlugin(pluginFn)) {
|
||||
pluginFn = pluginFn.default
|
||||
}
|
||||
validatePlugin(pluginFn)
|
||||
opts = opts || {}
|
||||
|
||||
if (this.booted) {
|
||||
throw new AVV_ERR_ROOT_PLG_BOOTED()
|
||||
}
|
||||
|
||||
// we always add plugins to load at the current element
|
||||
const current = this._current[0]
|
||||
|
||||
let timeout = this._opts.timeout
|
||||
|
||||
if (!current.loaded && current.timeout > 0) {
|
||||
const delta = Date.now() - current.startTime
|
||||
// We need to decrease it by 3ms to make sure the internal timeout
|
||||
// is triggered earlier than the parent
|
||||
timeout = current.timeout - (delta + 3)
|
||||
}
|
||||
|
||||
const plugin = new Plugin(fastq(this, this._loadPluginNextTick, 1), pluginFn, opts, isAfter, timeout)
|
||||
this._trackPluginLoading(plugin)
|
||||
|
||||
if (current.loaded) {
|
||||
throw new Error(plugin.name, current.name)
|
||||
}
|
||||
|
||||
// we add the plugin to be loaded at the end of the current queue
|
||||
current.enqueue(plugin, (err) => { err && (this._error = err) })
|
||||
|
||||
return plugin
|
||||
}
|
||||
|
||||
Boot.prototype._expose = function _expose () {
|
||||
const instance = this
|
||||
const server = instance._server
|
||||
const {
|
||||
use: useKey = 'use',
|
||||
after: afterKey = 'after',
|
||||
ready: readyKey = 'ready',
|
||||
onClose: onCloseKey = 'onClose',
|
||||
close: closeKey = 'close'
|
||||
} = this._opts.expose
|
||||
|
||||
if (server[useKey]) {
|
||||
throw new AVV_ERR_EXPOSE_ALREADY_DEFINED(useKey, 'use')
|
||||
}
|
||||
server[useKey] = function (fn, opts) {
|
||||
instance.use(fn, opts)
|
||||
return this
|
||||
}
|
||||
|
||||
if (server[afterKey]) {
|
||||
throw new AVV_ERR_EXPOSE_ALREADY_DEFINED(afterKey, 'after')
|
||||
}
|
||||
server[afterKey] = function (func) {
|
||||
if (typeof func !== 'function') {
|
||||
return instance._loadRegistered()
|
||||
}
|
||||
instance.after(encapsulateThreeParam(func, this))
|
||||
return this
|
||||
}
|
||||
|
||||
if (server[readyKey]) {
|
||||
throw new AVV_ERR_EXPOSE_ALREADY_DEFINED(readyKey, 'ready')
|
||||
}
|
||||
server[readyKey] = function (func) {
|
||||
if (func && typeof func !== 'function') {
|
||||
throw new AVV_ERR_CALLBACK_NOT_FN(readyKey, typeof func)
|
||||
}
|
||||
return instance.ready(func ? encapsulateThreeParam(func, this) : undefined)
|
||||
}
|
||||
|
||||
if (server[onCloseKey]) {
|
||||
throw new AVV_ERR_EXPOSE_ALREADY_DEFINED(onCloseKey, 'onClose')
|
||||
}
|
||||
server[onCloseKey] = function (func) {
|
||||
if (typeof func !== 'function') {
|
||||
throw new AVV_ERR_CALLBACK_NOT_FN(onCloseKey, typeof func)
|
||||
}
|
||||
instance.onClose(encapsulateTwoParam(func, this))
|
||||
return this
|
||||
}
|
||||
|
||||
if (server[closeKey]) {
|
||||
throw new AVV_ERR_EXPOSE_ALREADY_DEFINED(closeKey, 'close')
|
||||
}
|
||||
server[closeKey] = function (func) {
|
||||
if (func && typeof func !== 'function') {
|
||||
throw new AVV_ERR_CALLBACK_NOT_FN(closeKey, typeof func)
|
||||
}
|
||||
|
||||
if (func) {
|
||||
instance.close(encapsulateThreeParam(func, this))
|
||||
return this
|
||||
}
|
||||
|
||||
// this is a Promise
|
||||
return instance.close()
|
||||
}
|
||||
|
||||
if (server.then) {
|
||||
throw new AVV_ERR_ATTRIBUTE_ALREADY_DEFINED('then')
|
||||
}
|
||||
Object.defineProperty(server, 'then', { get: thenify.bind(instance) })
|
||||
|
||||
server[kAvvio] = true
|
||||
}
|
||||
|
||||
Boot.prototype.after = function (func) {
|
||||
if (!func) {
|
||||
return this._loadRegistered()
|
||||
}
|
||||
|
||||
this._addPlugin(_after.bind(this), {}, true)
|
||||
|
||||
function _after (s, opts, done) {
|
||||
callWithCbOrNextTick.call(this, func, done)
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
Boot.prototype.onClose = function (func) {
|
||||
// this is used to distinguish between onClose and close handlers
|
||||
// because they share the same queue but must be called with different signatures
|
||||
|
||||
if (typeof func !== 'function') {
|
||||
throw new AVV_ERR_CALLBACK_NOT_FN('onClose', typeof func)
|
||||
}
|
||||
|
||||
func[kIsOnCloseHandler] = true
|
||||
this._closeQ.unshift(func, (err) => { err && (this._error = err) })
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
Boot.prototype.close = function (func) {
|
||||
let promise
|
||||
|
||||
if (func) {
|
||||
if (typeof func !== 'function') {
|
||||
throw new AVV_ERR_CALLBACK_NOT_FN('close', typeof func)
|
||||
}
|
||||
} else {
|
||||
promise = new Promise(function (resolve, reject) {
|
||||
func = function (err) {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.ready(() => {
|
||||
this._error = null
|
||||
this._closeQ.push(func)
|
||||
process.nextTick(this._closeQ.resume.bind(this._closeQ))
|
||||
})
|
||||
|
||||
return promise
|
||||
}
|
||||
|
||||
Boot.prototype.ready = function (func) {
|
||||
if (func) {
|
||||
if (typeof func !== 'function') {
|
||||
throw new AVV_ERR_CALLBACK_NOT_FN('ready', typeof func)
|
||||
}
|
||||
this._readyQ.push(func)
|
||||
queueMicrotask(this.start.bind(this))
|
||||
return
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this._readyQ.push(readyPromiseCB)
|
||||
this.start()
|
||||
|
||||
/**
|
||||
* The `encapsulateThreeParam` let callback function
|
||||
* bind to the right server instance.
|
||||
* In promises we need to track the last server
|
||||
* instance loaded, the first one in the _current queue.
|
||||
*/
|
||||
const relativeContext = this._current[0].server
|
||||
|
||||
function readyPromiseCB (err, context, done) {
|
||||
// the context is always binded to the root server
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve(relativeContext)
|
||||
}
|
||||
process.nextTick(done)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Plugin} plugin
|
||||
* @returns {void}
|
||||
*/
|
||||
Boot.prototype._trackPluginLoading = function (plugin) {
|
||||
const parentName = this._current[0]?.name || null
|
||||
plugin.once('start', (serverName, funcName, time) => {
|
||||
const nodeId = this.pluginTree.start(parentName || null, funcName, time)
|
||||
plugin.once('loaded', (serverName, funcName, time) => {
|
||||
this.pluginTree.stop(nodeId, time)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
Boot.prototype.prettyPrint = function () {
|
||||
return this.pluginTree.prettyPrint()
|
||||
}
|
||||
|
||||
Boot.prototype.toJSON = function () {
|
||||
return this.pluginTree.toJSON()
|
||||
}
|
||||
|
||||
/**
|
||||
* @callback LoadPluginCallback
|
||||
* @param {Error} [err]
|
||||
*/
|
||||
|
||||
/**
|
||||
* Load a plugin
|
||||
*
|
||||
* @param {Plugin} plugin
|
||||
* @param {LoadPluginCallback} callback
|
||||
*/
|
||||
Boot.prototype._loadPlugin = function (plugin, callback) {
|
||||
const instance = this
|
||||
if (isPromiseLike(plugin.func)) {
|
||||
plugin.func.then((fn) => {
|
||||
if (typeof fn.default === 'function') {
|
||||
fn = fn.default
|
||||
}
|
||||
plugin.func = fn
|
||||
this._loadPlugin(plugin, callback)
|
||||
}, callback)
|
||||
return
|
||||
}
|
||||
|
||||
const last = instance._current[0]
|
||||
|
||||
// place the plugin at the top of _current
|
||||
instance._current.unshift(plugin)
|
||||
|
||||
if (instance._error && !plugin.isAfter) {
|
||||
debug('skipping loading of plugin as instance errored and it is not an after', plugin.name)
|
||||
process.nextTick(execCallback)
|
||||
return
|
||||
}
|
||||
|
||||
let server = (last && last.server) || instance._server
|
||||
|
||||
if (!plugin.isAfter) {
|
||||
// Skip override for after
|
||||
try {
|
||||
server = instance.override(server, plugin.func, plugin.options)
|
||||
} catch (overrideErr) {
|
||||
debug('override errored', plugin.name)
|
||||
return execCallback(overrideErr)
|
||||
}
|
||||
}
|
||||
|
||||
plugin.exec(server, execCallback)
|
||||
|
||||
function execCallback (err) {
|
||||
plugin.finish(err, (err) => {
|
||||
instance._current.shift()
|
||||
callback(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delays plugin loading until the next tick to ensure any bound `_after` callbacks have a chance
|
||||
* to run prior to executing the next plugin
|
||||
*/
|
||||
Boot.prototype._loadPluginNextTick = function (plugin, callback) {
|
||||
process.nextTick(this._loadPlugin.bind(this), plugin, callback)
|
||||
}
|
||||
|
||||
function noop () { }
|
||||
|
||||
function callWithCbOrNextTick (func, cb) {
|
||||
const context = this._server
|
||||
const err = this._error
|
||||
|
||||
// with this the error will appear just in the next after/ready callback
|
||||
this._error = null
|
||||
if (func.length === 0) {
|
||||
this._error = err
|
||||
executeWithThenable(func, [], cb)
|
||||
} else if (func.length === 1) {
|
||||
executeWithThenable(func, [err], cb)
|
||||
} else {
|
||||
if (this._opts.timeout === 0) {
|
||||
const wrapCb = (err) => {
|
||||
this._error = err
|
||||
cb(this._error)
|
||||
}
|
||||
|
||||
if (func.length === 2) {
|
||||
func(err, wrapCb)
|
||||
} else {
|
||||
func(err, context, wrapCb)
|
||||
}
|
||||
} else {
|
||||
timeoutCall.call(this, func, err, context, cb)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function timeoutCall (func, rootErr, context, cb) {
|
||||
const name = func.unwrappedName ?? func.name
|
||||
debug('setting up ready timeout', name, this._opts.timeout)
|
||||
let timer = setTimeout(() => {
|
||||
debug('timed out', name)
|
||||
timer = null
|
||||
const toutErr = new AVV_ERR_READY_TIMEOUT(name)
|
||||
toutErr.fn = func
|
||||
this._error = toutErr
|
||||
cb(toutErr)
|
||||
}, this._opts.timeout)
|
||||
|
||||
if (func.length === 2) {
|
||||
func(rootErr, timeoutCb.bind(this))
|
||||
} else {
|
||||
func(rootErr, context, timeoutCb.bind(this))
|
||||
}
|
||||
|
||||
function timeoutCb (err) {
|
||||
if (timer) {
|
||||
clearTimeout(timer)
|
||||
this._error = err
|
||||
cb(this._error)
|
||||
} else {
|
||||
// timeout has been triggered
|
||||
// can not call cb twice
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function closeWithCbOrNextTick (func, cb) {
|
||||
const context = this._server
|
||||
const isOnCloseHandler = func[kIsOnCloseHandler]
|
||||
if (func.length === 0 || func.length === 1) {
|
||||
let promise
|
||||
if (isOnCloseHandler) {
|
||||
promise = func(context)
|
||||
} else {
|
||||
promise = func(this._error)
|
||||
}
|
||||
if (promise && typeof promise.then === 'function') {
|
||||
debug('resolving close/onClose promise')
|
||||
promise.then(
|
||||
() => process.nextTick(cb),
|
||||
(e) => process.nextTick(cb, e))
|
||||
} else {
|
||||
process.nextTick(cb)
|
||||
}
|
||||
} else if (func.length === 2) {
|
||||
if (isOnCloseHandler) {
|
||||
func(context, cb)
|
||||
} else {
|
||||
func(this._error, cb)
|
||||
}
|
||||
} else {
|
||||
if (isOnCloseHandler) {
|
||||
func(context, cb)
|
||||
} else {
|
||||
func(this._error, context, cb)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function encapsulateTwoParam (func, that) {
|
||||
return _encapsulateTwoParam.bind(that)
|
||||
function _encapsulateTwoParam (context, cb) {
|
||||
let res
|
||||
if (func.length === 0) {
|
||||
res = func()
|
||||
if (res && res.then) {
|
||||
res.then(function () {
|
||||
process.nextTick(cb)
|
||||
}, cb)
|
||||
} else {
|
||||
process.nextTick(cb)
|
||||
}
|
||||
} else if (func.length === 1) {
|
||||
res = func(this)
|
||||
|
||||
if (res && res.then) {
|
||||
res.then(function () {
|
||||
process.nextTick(cb)
|
||||
}, cb)
|
||||
} else {
|
||||
process.nextTick(cb)
|
||||
}
|
||||
} else {
|
||||
func(this, cb)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function encapsulateThreeParam (func, that) {
|
||||
const wrapped = _encapsulateThreeParam.bind(that)
|
||||
wrapped.unwrappedName = func.name
|
||||
return wrapped
|
||||
function _encapsulateThreeParam (err, cb) {
|
||||
let res
|
||||
if (!func) {
|
||||
process.nextTick(cb)
|
||||
} else if (func.length === 0) {
|
||||
res = func()
|
||||
if (res && res.then) {
|
||||
res.then(function () {
|
||||
process.nextTick(cb, err)
|
||||
}, cb)
|
||||
} else {
|
||||
process.nextTick(cb, err)
|
||||
}
|
||||
} else if (func.length === 1) {
|
||||
res = func(err)
|
||||
if (res && res.then) {
|
||||
res.then(function () {
|
||||
process.nextTick(cb)
|
||||
}, cb)
|
||||
} else {
|
||||
process.nextTick(cb)
|
||||
}
|
||||
} else if (func.length === 2) {
|
||||
func(err, cb)
|
||||
} else {
|
||||
func(err, this, cb)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Boot
|
||||
module.exports.express = function (app) {
|
||||
return Boot(app, {
|
||||
expose: {
|
||||
use: 'load'
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user