UNPKG

@itrocks/reflect

Version:

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

114 lines 4.35 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 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; } inheritPropertyDefaults(propertyDefaults) { const parent = this.parent; if (parent) { Object.assign(propertyDefaults, parent.propertyDefaults); } } inheritPropertyTypes(propertyTypes) { const parent = this.parent; if (parent) { Object.assign(propertyTypes, parent.propertyTypes); } } get parent() { const parentType = Object.getPrototypeOf(this.type); const parent = (parentType === Function.prototype) ? null : new ReflectClass(parentType); Object.defineProperty(this, 'parent', { configurable: true, enumerable: false, value: parent, writable: true }); return parent; } get properties() { const properties = new Array; for (const name of this.propertyNames) { properties.push(new ReflectProperty(this, name)); } Object.defineProperty(this, 'properties', { configurable: true, enumerable: false, value: properties, writable: true }); return properties; } get property() { const properties = {}; for (const property of this.properties) { properties[property.name] = property; } Object.defineProperty(this, 'property', { 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.inheritPropertyDefaults(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; // TODO: replace runtime instantiation with .d.ts AST analysis try { object = new this.type; } catch { return []; } 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.inheritPropertyTypes(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