All files / mapper/src mapper.ts

100% Statements 13/13
100% Branches 4/4
100% Functions 5/5
100% Lines 12/12

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 601x 1x                                                                                   5x 1x 19x 19x 11x 8x 7x   4x     7x 7x        
import { isArray } from "@benneq/array";
import { isFunction, Mapper } from "@benneq/function";
 
type Primitive = string | null | number | boolean | undefined | bigint | symbol;
type DefFunction<I, T> = (value: I) => T;
type DefArray<I, T> = ReadonlyArray<MapperDefinition<I, T>>;
type DefObject<I, T> = {
  [key in keyof T]: MapperDefinition<I, T[key]>;
};
 
type MapperDefinition<I, T> = T extends Primitive
  ? DefFunction<I, T>
  : T extends ReadonlyArray<infer R>
  ? DefArray<I, R>
  : DefObject<I, T>;
 
/**
 *
 * @example
 * ```
 * const valueMapper = mapper({
 *   a: [
 *     value => value.a.length,
 *     {
 *       a: value => value.a[0],
 *       z: value => value.a.at(-1)
 *     }
 *   ],
 *   B: value => value.b.toUpperCase()
 * });
 *
 * const value = {
 *   a: [1,2,3],
 *   b: "b"
 * };
 *
 * const mappedValue = valueMapper(value);
 * console.log(mappedValue); // { a:[3,{a:1,z:3}], B: "B" }
 * ```
 *
 * @param def
 * @returns
 */
export const mapper =
  <I, T>(def: MapperDefinition<I, T>): Mapper<I, T> =>
  (initialValue) => {
    if (isFunction(def)) {
      return def(initialValue);
    } else if (isArray(def)) {
      return def.map((e) => mapper(e)(initialValue)) as T;
    } else {
      return (
        Object.entries(def) as [keyof T, MapperDefinition<I, T[keyof T]>][]
      ).reduce((acc, [key, value]) => {
        acc[key] = mapper(value)(initialValue);
        return acc;
      }, {} as T);
    }
  };