UNPKG

@thisisagile/easy

Version:

Straightforward library for building domain-driven microservice architectures

143 lines (127 loc) • 5.77 kB
import { traverse } from './Traverse'; import { ifDefined } from './If'; import { Primitive } from '../types/Primitive'; import { Constructor, isConstructor, use } from '../types/Constructor'; import { asJson, json as typesJson } from '../types/Json'; import { choose } from '../types/Case'; import { isArray, isBoolean, isFunction, isString } from '../types/Is'; import { meta } from '../types/Meta'; import { isPageList, PageList, toPageList } from '../types/PageList'; import { List } from '../types/List'; import { isEqual } from '../types/IsEqual'; import { Get, ofGet } from '../types/Get'; import { DontInfer } from '../types/Types'; import { AnyKey } from '../types/AnyKey'; import { EnumConstructor, isEnumConstructor } from '../types/Enum'; import { Id } from '../types/Id'; type Func<T = unknown> = (a: any, key?: string) => T; type FuncOn<S> = (a: S, key?: string) => unknown; type Viewer = { key: string; f: Func }; type ViewType<V = any> = Primitive | EnumConstructor | Constructor | Func | View<V> | undefined; type ViewValue<S = never> = [S] extends [never] ? ViewType : Exclude<ViewType, string | Func> | AnyKey<S> | FuncOn<S>; type ViewRecord<V = any, S = never> = [S] extends [never] ? Partial<Record<keyof V | typeof spread, ViewValue<S>>> : Partial<Record<keyof V | typeof spread, ViewValue<S>>> & Partial<Record<Exclude<Extract<keyof S, string>, Extract<keyof V, string>>, typeof ignore>>; const ignore = Symbol('view.ignore'); const keep = Symbol('view.keep'); const json = Symbol('view.json'); const spread = 'view.spread'; export const toViewer = (key: string, value: ViewType): Viewer => choose(value) .is.not.defined(v => v, { key, f: () => undefined } as Viewer) .type(isBoolean, b => ({ key, f: () => b })) .equals(ignore, { key, f: () => undefined }) .equals(keep, { key, f: (a: any) => traverse(a, key) }) .equals(json, { key, f: (a: any) => asJson(traverse(a, key)) }) .type(isString, s => ({ key, f: (a: any) => traverse(a, s) })) .type(isEnumConstructor, c => ({ key, f: (a, key) => use(traverse(a, key), v => (isArray(v) ? c.byIds(v) : c.byId(v as Id))), })) .type(isConstructor, c => ({ key, f: (a, key) => use(traverse(a, key), v => (isArray(v) ? v.map(i => optional(c, i)) : optional(c, v))), })) .type(isSimpleView, f => ({ key, f: (a: any) => ifDefined(traverse(a, key), v => f.from(v)) })) .type(isFunction, f => ({ key, f })) .else(v => ({ key, f: () => v })); const optional = (c: Constructor, v: any) => ifDefined( v, i => new c(i), () => v ); const toViewers = (views: ViewRecord): Viewer[] => meta(views) .entries<ViewType>() .map(([k, v]) => toViewer(k, v)); export class View<V = any, S = never> { constructor( private views = {} as ViewRecord<V, S>, readonly startsFrom: 'scratch' | 'source' = 'scratch', readonly viewers: Viewer[] = toViewers(views), private spreads: Get<V>[] = [] ) {} get fromSource(): View<V, S> { return new View(this.views, 'source', this.viewers, this.spreads); } spread(f: Get<V>): View<V, S> { return new View(this.views, this.startsFrom, this.viewers, [...this.spreads, f]); } from<T = unknown>(source: PageList<T>): PageList<V>; from<T = unknown>(source: List<T>): List<V>; from<T = unknown>(source: T[]): V[]; from<T = unknown>(source: T): V; from<T = unknown>(source: PageList<T> | List<T> | T[] | T): PageList<V> | List<V> | V[] | V { if (isPageList(source)) return toPageList( source.map(s => this.reduce(s)), source.options ); if (isArray(source)) return source.map(s => this.reduce(s)); return this.reduce(source); } same = (one?: unknown, another?: unknown): boolean => isEqual(this.from(one), this.from(another)); private reduce = (source: any): any => use(asJson(source), src => { const mapped = this.viewers.reduce( (acc, v) => (v.key === spread ? { ...acc, ...asJson(v.f(src, v.key)) } : typesJson.set(acc, v.key, v.f(src, v.key))), this.startsFrom === 'scratch' ? {} : src ); return this.spreads.reduce((acc, f) => ({ ...acc, ...asJson(ofGet(f, src)) }), mapped); }); } export const isSimpleView = (a: unknown): a is View => a instanceof View; export const view = <V = any, S = never>(views: ViewRecord<DontInfer<V>, DontInfer<S>>): View<V, S> => new View<V, S>(views); export const views = { ignore, keep, json, spread, skip: ignore, equals: (key: string, value: unknown) => (a: unknown) => isEqual(traverse(a, key), value), value: (value: unknown) => () => value, coalesce: (...keys: string[]) => (a: unknown) => keys.reduce<unknown>((result, k) => result ?? traverse(a, k), undefined), or: { key: (altKey: string) => (a: unknown, key?: string) => traverse(a, key) ?? traverse(a, altKey), value: (altValue: unknown) => (a: unknown, key?: string) => traverse(a, key) ?? altValue, func: (altFunc: Func) => (a: unknown, key?: string) => traverse(a, key) ?? altFunc(a, key), }, to: <T = unknown>(ctorOrFunc: Constructor<T> | ((v: any) => T)) => { const apply = (v: any): T => (isConstructor(ctorOrFunc) ? new ctorOrFunc(v) : (ctorOrFunc as (v: any) => T)(v)); const base = (a: any, key?: string): T | undefined => { const v = traverse(a, key); return v != null ? apply(v) : undefined; }; return Object.assign(base, { or: { key: (altKey: string) => (a: unknown, key?: string) => base(a, key) ?? traverse(a, altKey), value: (altValue: T) => (a: unknown, key?: string) => base(a, key) ?? altValue, func: (altFunc: Func) => (a: unknown, key?: string) => base(a, key) ?? altFunc(a, key), }, }); }, } as const;