@thisisagile/easy
Version:
Straightforward library for building domain-driven microservice architectures
143 lines (127 loc) • 5.77 kB
text/typescript
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;