123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 |
- // To do: remove `void`s
- // To do: remove `null` from output of our APIs, allow it as user APIs.
- /**
- * @typedef {(error?: Error | null | undefined, ...output: Array<any>) => void} Callback
- * Callback.
- *
- * @typedef {(...input: Array<any>) => any} Middleware
- * Ware.
- *
- * @typedef Pipeline
- * Pipeline.
- * @property {Run} run
- * Run the pipeline.
- * @property {Use} use
- * Add middleware.
- *
- * @typedef {(...input: Array<any>) => void} Run
- * Call all middleware.
- *
- * Calls `done` on completion with either an error or the output of the
- * last middleware.
- *
- * > 👉 **Note**: as the length of input defines whether async functions get a
- * > `next` function,
- * > it’s recommended to keep `input` at one value normally.
- *
- * @typedef {(fn: Middleware) => Pipeline} Use
- * Add middleware.
- */
- /**
- * Create new middleware.
- *
- * @returns {Pipeline}
- * 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 we’re 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` into a uniform interface.
- *
- * You can pass all input to the resulting function.
- * `callback` is then called with the output of `middleware`.
- *
- * If `middleware` accepts more arguments than the later given in input,
- * an extra `done` function is passed to it after that input,
- * which must be called by `middleware`.
- *
- * The first value in `input` is the main input value.
- * All other input values are the rest input values.
- * The values given to `callback` are the input values,
- * merged with every non-nullish output value.
- *
- * * if `middleware` throws an error,
- * returns a promise that is rejected,
- * or calls the given `done` function with an error,
- * `callback` is called with that error
- * * if `middleware` returns a value or returns a promise that is resolved,
- * that value is the main output value
- * * if `middleware` calls `done`,
- * all non-nullish values except for the first one (the error) overwrite the
- * output values
- *
- * @param {Middleware} middleware
- * Function to wrap.
- * @param {Callback} callback
- * Callback called with the output of `middleware`.
- * @returns {Run}
- * Wrapped middleware.
- */
- 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 && result.then && typeof result.then === 'function') {
- 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)
- }
- }
|