UNPKG

@itrocks/reflect

Version:

Runtime introspection of TypeScript classes and their properties, including property type

106 lines 4.01 kB
import 'reflect-metadata'; import { fileOf } from '@itrocks/class-file'; import { baseType } from '@itrocks/class-type'; import { isObject } from '@itrocks/class-type'; import { typeOf } from '@itrocks/class-type'; import { propertyDefaultsFromFile } from '@itrocks/property-default'; import { propertyTypesFromFile } from '@itrocks/property-type'; import { SortedArray } from '@itrocks/sorted-array'; import { ReflectProperty } from './property.js'; const DEFAULTS = Symbol('defaults'); const TYPES = Symbol('types'); class Properties { *[Symbol.iterator]() { for (const value of Object.values(this)) { yield value; } } } class SortedPropertyNames extends SortedArray { static get [Symbol.species]() { return Array; } constructor(object) { super(...Object.getOwnPropertyNames(object).sort()); } } export class ReflectClass { name; object; type; constructor(object) { this.object = isObject(object) ? object : undefined; this.type = typeOf(object); this.name = this.type.name; } inheritedPropertyDefaults(propertyDefaults) { const parent = this.parent; if (parent) { Object.assign(propertyDefaults, parent.propertyDefaults); } } inheritedPropertyTypes(propertyTypes) { const parent = this.parent; if (parent) { Object.assign(propertyTypes, parent.propertyTypes); } } get parent() { const parentType = Object.getPrototypeOf(this.type); const value = (parentType === Function.prototype) ? null : new ReflectClass(parentType); Object.defineProperty(this, 'parent', { configurable: true, enumerable: false, value, writable: true }); return value; } get properties() { const properties = new Properties; for (const name of this.propertyNames) { properties[name] = new ReflectProperty(this, name); } Object.defineProperty(this, 'properties', { configurable: true, enumerable: false, value: properties, writable: true }); return properties; } get propertyDefaults() { let value = Reflect.getOwnMetadata(DEFAULTS, this.type); if (!value) { value = {}; Reflect.defineMetadata(DEFAULTS, value, this.type); this.inheritedPropertyDefaults(value); if (this.type === baseType(this.type)) { Object.assign(value, propertyDefaultsFromFile(fileOf(this.type))); } return value; } Object.defineProperty(this, 'propertyDefaults', { configurable: true, enumerable: false, value, writable: true }); return value; } get propertyNames() { let object = new this.type; const propertyNames = new SortedPropertyNames(object); propertyNames.distinct = true; while (object) { Object.entries(Object.getOwnPropertyDescriptors(object)).forEach(([name, descriptor]) => { if (!descriptor.get || (name[0] === '_')) return; propertyNames.push(name); }); object = Object.getPrototypeOf(object); } Object.defineProperty(this, 'propertyNames', { configurable: true, enumerable: false, value: propertyNames, writable: true }); return propertyNames; } get propertyTypes() { let value = Reflect.getOwnMetadata(TYPES, this.type); if (!value) { value = {}; Reflect.defineMetadata(TYPES, value, this.type); this.inheritedPropertyTypes(value); if (this.type === baseType(this.type)) { Object.assign(value, propertyTypesFromFile(fileOf(this.type))); } return value; } Object.defineProperty(this, 'propertyTypes', { configurable: true, enumerable: false, value, writable: true }); return value; } } //# sourceMappingURL=class.js.map