AK-21/Graphite-Industrial-Intelligence
0
1// To do: remove `void`s2// To do: remove `null` from output of our APIs, allow it as user APIs.3 4/**5 * @typedef {(error?: Error | null | undefined, ...output: Array<any>) => void} Callback6 * Callback.7 *8 * @typedef {(...input: Array<any>) => any} Middleware9 * Ware.10 *11 * @typedef Pipeline12 * Pipeline.13 * @property {Run} run14 * Run the pipeline.15 * @property {Use} use16 * Add middleware.17 *18 * @typedef {(...input: Array<any>) => void} Run19 * Call all middleware.20 *21 * Calls `done` on completion with either an error or the output of the22 * last middleware.23 *24 * > 👉 **Note**: as the length of input defines whether async functions get a25 * > `next` function,26 * > it’s recommended to keep `input` at one value normally.27 28 *29 * @typedef {(fn: Middleware) => Pipeline} Use30 * Add middleware.31 */32 33/**34 * Create new middleware.35 *36 * @returns {Pipeline}37 * Pipeline.38 */39export function trough() {40 /** @type {Array<Middleware>} */41 const fns = []42 /** @type {Pipeline} */43 const pipeline = {run, use}44 45 return pipeline46 47 /** @type {Run} */48 function run(...values) {49 let middlewareIndex = -150 /** @type {Callback} */51 const callback = values.pop()52 53 if (typeof callback !== 'function') {54 throw new TypeError('Expected function as last argument, not ' + callback)55 }56 57 next(null, ...values)58 59 /**60 * Run the next `fn`, or we’re done.61 *62 * @param {Error | null | undefined} error63 * @param {Array<any>} output64 */65 function next(error, ...output) {66 const fn = fns[++middlewareIndex]67 let index = -168 69 if (error) {70 callback(error)71 return72 }73 74 // Copy non-nullish input into values.75 while (++index < values.length) {76 if (output[index] === null || output[index] === undefined) {77 output[index] = values[index]78 }79 }80 81 // Save the newly created `output` for the next call.82 values = output83 84 // Next or done.85 if (fn) {86 wrap(fn, next)(...output)87 } else {88 callback(null, ...output)89 }90 }91 }92 93 /** @type {Use} */94 function use(middelware) {95 if (typeof middelware !== 'function') {96 throw new TypeError(97 'Expected `middelware` to be a function, not ' + middelware98 )99 }100 101 fns.push(middelware)102 return pipeline103 }104}105 106/**107 * Wrap `middleware` into a uniform interface.108 *109 * You can pass all input to the resulting function.110 * `callback` is then called with the output of `middleware`.111 *112 * If `middleware` accepts more arguments than the later given in input,113 * an extra `done` function is passed to it after that input,114 * which must be called by `middleware`.115 *116 * The first value in `input` is the main input value.117 * All other input values are the rest input values.118 * The values given to `callback` are the input values,119 * merged with every non-nullish output value.120 *121 * * if `middleware` throws an error,122 * returns a promise that is rejected,123 * or calls the given `done` function with an error,124 * `callback` is called with that error125 * * if `middleware` returns a value or returns a promise that is resolved,126 * that value is the main output value127 * * if `middleware` calls `done`,128 * all non-nullish values except for the first one (the error) overwrite the129 * output values130 *131 * @param {Middleware} middleware132 * Function to wrap.133 * @param {Callback} callback134 * Callback called with the output of `middleware`.135 * @returns {Run}136 * Wrapped middleware.137 */138export function wrap(middleware, callback) {139 /** @type {boolean} */140 let called141 142 return wrapped143 144 /**145 * Call `middleware`.146 * @this {any}147 * @param {Array<any>} parameters148 * @returns {void}149 */150 function wrapped(...parameters) {151 const fnExpectsCallback = middleware.length > parameters.length152 /** @type {any} */153 let result154 155 if (fnExpectsCallback) {156 parameters.push(done)157 }158 159 try {160 result = middleware.apply(this, parameters)161 } catch (error) {162 const exception = /** @type {Error} */ (error)163 164 // Well, this is quite the pickle.165 // `middleware` received a callback and called it synchronously, but that166 // threw an error.167 // The only thing left to do is to throw the thing instead.168 if (fnExpectsCallback && called) {169 throw exception170 }171 172 return done(exception)173 }174 175 if (!fnExpectsCallback) {176 if (result && result.then && typeof result.then === 'function') {177 result.then(then, done)178 } else if (result instanceof Error) {179 done(result)180 } else {181 then(result)182 }183 }184 }185 186 /**187 * Call `callback`, only once.188 *189 * @type {Callback}190 */191 function done(error, ...output) {192 if (!called) {193 called = true194 callback(error, ...output)195 }196 }197 198 /**199 * Call `done` with one value.200 *201 * @param {any} [value]202 */203 function then(value) {204 done(null, value)205 }206}207 