|
| 1 | +type Awaitable<T> = T | Promise<T>; |
| 2 | +export type Pipe<Output, Input> = (input: Input) => Awaitable<Output>; |
| 3 | +export type Step<Output, T> = <Input extends T>(input: Input) => Awaitable<Output>; |
| 4 | + |
| 5 | +/** |
| 6 | + * Special step that assign the input and output. |
| 7 | + * |
| 8 | + * @example |
| 9 | + * ``` |
| 10 | + * const addHobby = middleware(() => ({hobby: 'Coding'})); |
| 11 | + * |
| 12 | + * await addHobby({name: 'John Smith'}); |
| 13 | + * //=> {name: 'John Smith', hobby: 'Coding'} |
| 14 | + * ``` |
| 15 | + */ |
| 16 | +export function middleware<Output, T>(step: Pipe<Output, T>) { |
| 17 | + return async <Input extends T>(input: Input) => ({...input, ...(await step(input))}); |
| 18 | +} |
| 19 | + |
| 20 | +/** |
| 21 | + * Compose multiple steps. |
| 22 | + * |
| 23 | + * @example |
| 24 | + * ``` |
| 25 | + * const getUser = async () => ({name: 'John Smith'}); |
| 26 | + * const addHobby = middleware(() => ({hobby: 'Coding'})); |
| 27 | + * |
| 28 | + * const pipeline = pipe(getUser, addHobby); |
| 29 | + * |
| 30 | + * await pipeline(); |
| 31 | + * //=> {name: 'John Smith', hobby: 'Coding'} |
| 32 | + * ``` |
| 33 | + */ |
| 34 | +export function pipe<Output, A, Input>( |
| 35 | + s1: Pipe<A, Input>, |
| 36 | + s2: Pipe<Output, A> |
| 37 | +): Pipe<Output, Input>; |
| 38 | +export function pipe<Output, B, A, Input>( |
| 39 | + s1: Pipe<A, Input>, |
| 40 | + s2: Pipe<B, A>, |
| 41 | + s3: Pipe<Output, B> |
| 42 | +): Pipe<Output, Input>; |
| 43 | +export function pipe<Output, C, B, A, Input>( |
| 44 | + s1: Pipe<A, Input>, |
| 45 | + s2: Pipe<B, A>, |
| 46 | + s3: Pipe<C, B>, |
| 47 | + s4: Pipe<Output, C> |
| 48 | +): Pipe<Output, Input>; |
| 49 | +export function pipe<Output, D, C, B, A, Input>( |
| 50 | + s1: Pipe<A, Input>, |
| 51 | + s2: Pipe<B, A>, |
| 52 | + s3: Pipe<C, B>, |
| 53 | + s4: Pipe<D, C>, |
| 54 | + s5: Pipe<Output, D> |
| 55 | +): Pipe<Output, Input>; |
| 56 | +export function pipe<Output, E, D, C, B, A, Input>( |
| 57 | + s1: Pipe<A, Input>, |
| 58 | + s2: Pipe<B, A>, |
| 59 | + s3: Pipe<C, B>, |
| 60 | + s4: Pipe<D, C>, |
| 61 | + s5: Pipe<E, D>, |
| 62 | + s6: Pipe<Output, E> |
| 63 | +): Pipe<Output, Input>; |
| 64 | +export function pipe<Output, F, E, D, C, B, A, Input>( |
| 65 | + s1: Pipe<A, Input>, |
| 66 | + s2: Pipe<B, A>, |
| 67 | + s3: Pipe<C, B>, |
| 68 | + s4: Pipe<D, C>, |
| 69 | + s5: Pipe<E, D>, |
| 70 | + s6: Pipe<F, E>, |
| 71 | + s7: Pipe<Output, F> |
| 72 | +): Pipe<Output, Input>; |
| 73 | +export function pipe<Input>( |
| 74 | + init: Pipe<unknown, Input>, |
| 75 | + ...steps: Array<Pipe<unknown, unknown>> |
| 76 | +): Pipe<unknown, Input> { |
| 77 | + // eslint-disable-next-line unicorn/no-array-reduce |
| 78 | + return input => steps.reduce(async (promise, step) => step(await promise), init(input)); |
| 79 | +} |
| 80 | + |
| 81 | +/** |
| 82 | + * Execute steps in parallel. |
| 83 | + * |
| 84 | + * @example |
| 85 | + * ``` |
| 86 | + * const getUser = async () => ({name: 'John Smith'}); |
| 87 | + * const getSession = async () => ({id: '4a53'}); |
| 88 | + * |
| 89 | + * const pipeline = parallel(getUser, getSession); |
| 90 | + * |
| 91 | + * await pipeline(); |
| 92 | + * //=> [{name: 'John Smith'}, {id: '4a53'}] |
| 93 | + * ``` |
| 94 | + */ |
| 95 | +export function parallel<D, C, B, A>( |
| 96 | + s1: Pipe<B, A>, |
| 97 | + s2: Pipe<D, C> |
| 98 | +): Pipe<[B, D], A & C>; |
| 99 | +export function parallel<F, E, D, C, B, A>( |
| 100 | + s1: Pipe<B, A>, |
| 101 | + s2: Pipe<D, C>, |
| 102 | + s3: Pipe<F, E> |
| 103 | +): Pipe<[B, D, F], A & C & E>; |
| 104 | +export function parallel<H, G, F, E, D, C, B, A>( |
| 105 | + s1: Pipe<B, A>, |
| 106 | + s2: Pipe<D, C>, |
| 107 | + s3: Pipe<F, E>, |
| 108 | + s4: Pipe<H, G> |
| 109 | +): Pipe<[B, D, F, H], A & C & E & G>; |
| 110 | +export function parallel<J, I, H, G, F, E, D, C, B, A>( |
| 111 | + s1: Pipe<B, A>, |
| 112 | + s2: Pipe<D, C>, |
| 113 | + s3: Pipe<F, E>, |
| 114 | + s4: Pipe<H, G>, |
| 115 | + s5: Pipe<J, I> |
| 116 | +): Pipe<[B, D, F, H, J], A & C & E & G & I>; |
| 117 | +export function parallel<L, K, J, I, H, G, F, E, D, C, B, A>( |
| 118 | + s1: Pipe<B, A>, |
| 119 | + s2: Pipe<D, C>, |
| 120 | + s3: Pipe<F, E>, |
| 121 | + s4: Pipe<H, G>, |
| 122 | + s5: Pipe<J, I>, |
| 123 | + s6: Pipe<L, K> |
| 124 | +): Pipe<[B, D, F, H, J, L], A & C & E & G & I & K>; |
| 125 | +export function parallel<N, M, L, K, J, I, H, G, F, E, D, C, B, A>( |
| 126 | + s1: Pipe<B, A>, |
| 127 | + s2: Pipe<D, C>, |
| 128 | + s3: Pipe<F, E>, |
| 129 | + s4: Pipe<H, G>, |
| 130 | + s5: Pipe<J, I>, |
| 131 | + s6: Pipe<L, K>, |
| 132 | + s7: Pipe<N, M> |
| 133 | +): Pipe<[B, D, F, H, J, L, N], A & C & E & G & I & K & M>; |
| 134 | +export function parallel<Input>(...steps: Array<Pipe<unknown, Input>>): Pipe<unknown[], Input> { |
| 135 | + return async input => Promise.all(steps.map(step => step(input))); |
| 136 | +} |
| 137 | + |
| 138 | +type Assign<T extends unknown[]> = T extends [infer Head, ...infer Tail] |
| 139 | + ? Head & Assign<Tail> |
| 140 | + : unknown; |
| 141 | + |
| 142 | +/** |
| 143 | + * Assign the output of a parallel step. |
| 144 | + * |
| 145 | + * @example |
| 146 | + * ``` |
| 147 | + * const getUser = async () => ({name: 'John Smith'}); |
| 148 | + * const addHobby = middleware(() => ({hobby: 'Coding'})); |
| 149 | + * |
| 150 | + * const pipeline = assign(parallel(getUser, addHobby)); |
| 151 | + * |
| 152 | + * await pipeline(); |
| 153 | + * //=> {name: 'John Smith', hobby: 'Coding'} |
| 154 | + * ``` |
| 155 | + */ |
| 156 | +export function assign<Output extends unknown[], Input>( |
| 157 | + step: Step<Output, Input>, |
| 158 | +): Pipe<Assign<Output>, Input> { |
| 159 | + // eslint-disable-next-line @typescript-eslint/no-unsafe-return |
| 160 | + return async input => Object.assign({}, ...(await step(input))); |
| 161 | +} |
| 162 | + |
| 163 | +/** |
| 164 | + * Special step that passthrough the input. |
| 165 | + * |
| 166 | + * @example |
| 167 | + * ``` |
| 168 | + * type User = {name: string}; |
| 169 | + * |
| 170 | + * const notifyLogin = (user: User) => console.log(`User ${user.name} logged in`); |
| 171 | + * |
| 172 | + * const pipeline = passthrough(notifyLogin); |
| 173 | + * |
| 174 | + * await pipeline({name: 'John Smith'}); |
| 175 | + * //=> {name: 'John Smith'} |
| 176 | + * ``` |
| 177 | + */ |
| 178 | +export function passthrough<T>(step: Pipe<unknown, T>) { |
| 179 | + // eslint-disable-next-line no-sequences |
| 180 | + return async <Input extends T>(input: Input) => (await step(input), input); |
| 181 | +} |
| 182 | + |
| 183 | +/** |
| 184 | + * Placeholder for step without input. |
| 185 | + * |
| 186 | + * @example |
| 187 | + * ``` |
| 188 | + * const pipeline = pipe(() => ({name: 'John Smith'}), user => user.name); |
| 189 | + * |
| 190 | + * await pipeline(_); |
| 191 | + * //=> 'John Smith' |
| 192 | + * ``` |
| 193 | + */ |
| 194 | +export const _ = {}; |
0 commit comments