site/node_modules/trough/index.js

161 lines
3.4 KiB
JavaScript
Raw Permalink Normal View History

2024-10-14 06:09:33 +00:00
/**
* @typedef {(error?: Error|null|undefined, ...output: Array<any>) => void} Callback
* @typedef {(...input: Array<any>) => any} Middleware
*
* @typedef {(...input: Array<any>) => void} Run
* Call all middleware.
* @typedef {(fn: Middleware) => Pipeline} Use
* Add `fn` (middleware) to the list.
* @typedef {{run: Run, use: Use}} Pipeline
* Middleware.
*/
/**
* Create new middleware.
*
* @returns {Pipeline}
*/
export function trough() {
/** @type {Array<Middleware>} */
const fns = []
/** @type {Pipeline} */
const pipeline = {run, use}
return pipeline
/** @type {Run} */
function run(...values) {
let middlewareIndex = -1
/** @type {Callback} */
const callback = values.pop()
if (typeof callback !== 'function') {
throw new TypeError('Expected function as last argument, not ' + callback)
}
next(null, ...values)
/**
* Run the next `fn`, or were done.
*
* @param {Error|null|undefined} error
* @param {Array<any>} output
*/
function next(error, ...output) {
const fn = fns[++middlewareIndex]
let index = -1
if (error) {
callback(error)
return
}
// Copy non-nullish input into values.
while (++index < values.length) {
if (output[index] === null || output[index] === undefined) {
output[index] = values[index]
}
}
// Save the newly created `output` for the next call.
values = output
// Next or done.
if (fn) {
wrap(fn, next)(...output)
} else {
callback(null, ...output)
}
}
}
/** @type {Use} */
function use(middelware) {
if (typeof middelware !== 'function') {
throw new TypeError(
'Expected `middelware` to be a function, not ' + middelware
)
}
fns.push(middelware)
return pipeline
}
}
/**
* Wrap `middleware`.
* Can be sync or async; return a promise, receive a callback, or return new
* values and errors.
*
* @param {Middleware} middleware
* @param {Callback} callback
*/
export function wrap(middleware, callback) {
/** @type {boolean} */
let called
return wrapped
/**
* Call `middleware`.
* @this {any}
* @param {Array<any>} parameters
* @returns {void}
*/
function wrapped(...parameters) {
const fnExpectsCallback = middleware.length > parameters.length
/** @type {any} */
let result
if (fnExpectsCallback) {
parameters.push(done)
}
try {
result = middleware.apply(this, parameters)
} catch (error) {
const exception = /** @type {Error} */ (error)
// Well, this is quite the pickle.
// `middleware` received a callback and called it synchronously, but that
// threw an error.
// The only thing left to do is to throw the thing instead.
if (fnExpectsCallback && called) {
throw exception
}
return done(exception)
}
if (!fnExpectsCallback) {
if (result instanceof Promise) {
result.then(then, done)
} else if (result instanceof Error) {
done(result)
} else {
then(result)
}
}
}
/**
* Call `callback`, only once.
* @type {Callback}
*/
function done(error, ...output) {
if (!called) {
called = true
callback(error, ...output)
}
}
/**
* Call `done` with one value.
*
* @param {any} [value]
*/
function then(value) {
done(null, value)
}
}