|
| 1 | +// 🙏 shoutout to -> https://github.com/gvergnaud/ts-pattern |
| 2 | + |
| 3 | +type Union<a, b> = [b] extends [a] ? a : [a] extends [b] ? b : a | b |
| 4 | + |
| 5 | +type Match<Value, InferredOutput = never> = { |
| 6 | + /** |
| 7 | + * if the `predicate` func returns true, the value returned by the `handler` will be the one returned when calling `else`. |
| 8 | + * |
| 9 | + * The matching `handler` func will be invoked only after `else` is called. |
| 10 | + */ |
| 11 | + if: <Output>( |
| 12 | + predicate: (value: NonNullable<Value>) => boolean, |
| 13 | + handler: (value: NonNullable<Value>) => Output |
| 14 | + ) => Match<Value, Union<InferredOutput, Output>> |
| 15 | + /** |
| 16 | + * takes a function allowing one to return a fallback value in case no match were found |
| 17 | + */ |
| 18 | + else: <O>(fallback: () => O) => Union<InferredOutput, O> |
| 19 | +} |
| 20 | + |
| 21 | +/** |
| 22 | + * Entry point to create some sort of a matching expression |
| 23 | + * |
| 24 | + * It returns a Match builder, on which you can chain several |
| 25 | + * `.if(predicate, handler)` clauses |
| 26 | + * |
| 27 | + * @example |
| 28 | + * |
| 29 | + * import { match, __ } from 'match' |
| 30 | + * |
| 31 | + * type Status = 'idle' | 'loading' | 'success' | 'error' |
| 32 | + * |
| 33 | + * const predicate: boolean |
| 34 | + * const status: Status |
| 35 | + * |
| 36 | + * const someExample = match(status) |
| 37 | + * .if(__.isEqual<Status>('idle'), s => <p>is {s}</p>) // s === idle |
| 38 | + * .if(__.isEqual<Status>('loading'), s => <p>is {s}</p>) // s === loading |
| 39 | + * .else(() => <p>no matches were found</p>) |
| 40 | + * |
| 41 | + * const otherExample = match({ status, predicate }) |
| 42 | + * .if( |
| 43 | + * ({ status, predicate }) => status === 'success' && predicate, |
| 44 | + * () => <p>ok</p> |
| 45 | + * ) |
| 46 | + * .if( |
| 47 | + * ({ status, predicate }) => status === 'error' && !predicate, |
| 48 | + * () => <p>great</p> |
| 49 | + * ) |
| 50 | + * .else(() => <p>no matches were found</p>) |
| 51 | + */ |
| 52 | +/* eslint-disable fp/no-mutation, fp/no-let */ |
| 53 | +function match<Value, InferredOutput = never>(value?: Value) { |
| 54 | + let getMatchingHandlerValue: () => any = () => undefined |
| 55 | + let hasFoundMatch = false |
| 56 | + let hasPredicatePassed = false |
| 57 | + |
| 58 | + function makeMatchBuilder(passedValue: NonNullable<Value>) { |
| 59 | + const matchBuilder = {} as Match<Value, InferredOutput> |
| 60 | + |
| 61 | + function if_<Output>( |
| 62 | + predicate: (value: NonNullable<Value>) => boolean, |
| 63 | + handler: (value: NonNullable<Value>) => Output |
| 64 | + ): Match<Value, Union<InferredOutput, Output>> { |
| 65 | + try { |
| 66 | + if (predicate(passedValue)) hasPredicatePassed = true |
| 67 | + } catch (error) {} |
| 68 | + |
| 69 | + if (!hasFoundMatch && hasPredicatePassed) { |
| 70 | + hasFoundMatch = true |
| 71 | + getMatchingHandlerValue = () => handler(passedValue) |
| 72 | + } |
| 73 | + |
| 74 | + return makeMatchBuilder(passedValue) as unknown as Match< |
| 75 | + Value, |
| 76 | + Union<InferredOutput, Output> |
| 77 | + > |
| 78 | + } |
| 79 | + |
| 80 | + function else_<E>(fallback: () => E) { |
| 81 | + let handlerValue |
| 82 | + let hasError = false |
| 83 | + try { |
| 84 | + handlerValue = getMatchingHandlerValue() |
| 85 | + } catch (error) { |
| 86 | + hasError = true |
| 87 | + console.error('Match: There was a problem inside your handler function 👇') |
| 88 | + console.error(error) |
| 89 | + } |
| 90 | + |
| 91 | + return hasError || !hasFoundMatch ? fallback() : handlerValue |
| 92 | + } |
| 93 | + |
| 94 | + matchBuilder.if = if_ |
| 95 | + matchBuilder.else = else_ |
| 96 | + |
| 97 | + return matchBuilder |
| 98 | + } |
| 99 | + |
| 100 | + return makeMatchBuilder(value as NonNullable<Value>) |
| 101 | +} |
| 102 | + |
| 103 | +export { match } |
| 104 | +export { __ } from './helpers' |
0 commit comments