import 'reflect-metadata'; import { List, toList } from './List'; import { isDefined } from './Is'; import { on, use } from './Constructor'; import { Optional } from './Types'; import { Entry, entries, values } from './Object'; type MetaParseOptions = { initial?: any; skipUndefined?: boolean }; class ClassMeta { constructor( readonly subject: any, private readonly data: any = (subject.prototype ?? subject).constructor ) {} get = (key: string): T => Reflect.getMetadata(key, this.data) as T; set = (key: string, value: T): T => { Reflect.defineMetadata(key, value, this.data); return value; }; entries = (): List> => entries(this.subject); parse = (f: (v: Value) => Out, options: MetaParseOptions = {}): Record => { const { initial = {}, skipUndefined = false } = options; return this.entries().reduce((a, [k, val]) => use(f(val), v => (isDefined(v) || !skipUndefined ? on(a, a => (a[k] = v)) : a)), initial); }; properties = (key?: string): List => toList([...Object.getOwnPropertyNames(this.subject), ...Object.getOwnPropertyNames(Object.getPrototypeOf(this.subject))]) .map(p => this.property(p)) .filter(p => (key ? p.get(key) : p)); keys = (key: string): List => this.properties() .map(p => p.get(key)) .reduce((list, u) => (u ? list.add(u) : list), toList()); values = (): List => values(this.subject); property = (property: string | symbol): PropertyMeta => new PropertyMeta(this.subject, property); } class PropertyMeta { constructor( readonly subject: any, readonly property: string | symbol, private readonly data = Reflect.getMetadata(property, subject) ) {} get value(): any { return this.subject[this.property]; } get = (key: string): Optional => (isDefined(this.data) && isDefined(this.data[key]) ? (this.data[key] as T) : undefined); set = (key: string, value: T): T => { Reflect.defineMetadata(this.property, { ...this.data, [key]: value }, this.subject); return value; }; } export const meta = (subject: unknown): ClassMeta => new ClassMeta(subject ?? {});