lib-reflect
v1.2.2
Published
Reflection functionality.
Downloads
8
Readme
lib-reflect
Opinionated project for helping with Reflection and Decorators in typescript. It tries to simplify storing data on the reflected target without using reflect-metadata and simplifies creation of decorators.
Installation
Standard npm package install
npm i lib-reflect
Reflection
The reflection system is made, so it's easy to use without working with the reflect-metadata system directly. It works in conjuction with the decorator helpers bellow.
class MyTestClass{}
const myTestData: ClassData = ReflectHelper.getOrCreateClassData(MyTestClass);
console.log(myTestData);
The ReflectHelper class has the following methods to help with retrieving / creating class reflection information.
Before using the ClassData we need to call build on it so it can gather extra information from the generated decorators (like the return type for the method, or extra parameters not decorated by us);
const cd = ReflectHelper.getClass(<someClass>);
cd.build();
// Now we have all the information including extra information added by the generated decorators
class TestClass {}
// Will retrieve if it exists or create the class data reflection info object for that class
const cd: ClassData = ReflectHelper.getOrCreateClassData(TestClass);
// Will retrieve the reflection info object for the class if it exists, if not it will return null
const cd: ClassData = ReflectHelper.getClass(TestClass);
// Will retrieve reflection info objects for the class and all the parents if they have any reflection data
// The data order will be from derived to base (TestClassData, ParentData, ParentParentData ....)
const cd: ClassData[] = ReflectHelper.getClassWithParentsIncluded(TestClass);
The following classes are available and contain reflection information:
ClassData
Contains information about the reflected class it is created / retrieved using the ReflectHelper methods:
class ClassData {
// Name of the class
public name: string;
// The constructor function
public target: Function;
// Attributes applied to the class
public attributesData: AttributeData[];
// Methods
public methods: MethodData[];
// Properties
public properties: PropertyData[];
// Extra data attached to the class
public tags: { [key: string]: any };
}
In order to get / create the reflected data on a method we can use the following function:
const methodData: MethodData = classData.getOrCreateMethod(methodName);
In order to get / create the reflected data on a property we can use the following function:
const propertyData: PropertyData = classData.getOrCreateProperty(propertyName);
In order to get the constructor method info:
const constructorData: MethodData = classData.getConstructorData();
PropertyData
Contains information about the reflected property:
enum PropertyFlags {
INSTANCE,
STATIC
}
class PropertyData {
// Name of the property
public name: string;
// Attributes for the property
public attributesData: AttributeData[];
// The data type of the property
public type: Function;
// Tags attached to this property by various decorators if needed
public tags: { [key: string]: any };
// Flags for the property
public flags: PropertyFlags;
// The parent class
public parent: ClassData;
}
MethodData
Contains information about the reflected method:
enum MethodFlags {
INSTANCE,
STATIC,
CONSTRUCTOR
}
class MethodData {
// Name of the method to invoke on the controller
public name: string;
// The options applied on the method
public attributesData: AttributeData[];
// The parameters needed to the handler method
public parameters: ParameterData[];
// Flag indicating if this method has been processed or not
// We don't always decorate the parameters, the reflection system will try and get
// additional information from the typescript system, in order to do this we need to 'process'
// the method, this flag is indicating if the process has happened or not
public processed: boolean;
// Tags attached to this method by various decorators if needed
public tags: { [key: string]: any };
// The return type of the function as taken from typescript
public returnType: Function;
// Flags for the method
public flags: MethodFlags;
// The parent class
public parent: ClassData;
}
In order to get / create a new parameter reflection data we can use the following function:
const paramData: ParameterData = methodData.getOrCreateParameter(paramIdx);
ParameterData
Contains information about the reflected parameter:
class ParameterData {
// The index of the parameter
public idx: number;
// The type of the parameter
public type: Function;
// Custom attribute data that decorators can add
public attributesData: AttributeData[];
// Tags attached to this parameter by various modules if needed
public tags: { [key: string]: any };
// The name of the parameter
// In theory we might be able to get the parameter name for the method
// it's not an orthodox method, however it is beeing used by other libraries like 'angularjs'
// I would suggest not to rely on this field
// Also for constructor functions this is not available
public name: string;
// The parent method
public parent: MethodData;
}
Decorators
Expose utility functions that use the Reflection system to ease the creation and management of decorators and decorator data.
One of the problem with decorators is that a method / property decorator is called before the class decorator. Using the functions bellow it does not matter what is called when, the implementation will take care of creating what is needed and sending the data to the handler. The decorate call order will still be the same, however the class data will exist in all handlers regardless of the order.
The following helper methods are available:
ClassDecoratorFactory
It accepts a callback that takes the ClassData as argument, you can modify the reflection information inside that callback.
// Example of a Class Decorator created using the ClassDecoratorFactory
const classDecorator = (val:number): ClassDecorator => ClassDecoratorFactory(
(cd: ClassData) => {
cd.tags['MyCustomReflectionValue'] = val;
// Manipulate the class reflection data if needed
}
);
@classDecorator(10)
class Test {
}
PropertyDecoratorFactory
It accepts a callback that takes the ClassData and the PropertyData as arguments, you can modify the reflection information inside that callback.
// Example of a Property Decorator created using the PropertyDecoratorFactory
const propertyDecorator = (val:number): PropertyDecorator => PropertyDecoratorFactory(
(cd: ClassData, prop:PropertyData) => {
// Manipulate the class / property reflection data if needed
prop.tags['MyCustomPropertyValue'] = val;
}
);
class Test {
@propertyDecorator(10)
public prop:string;
}
MethodDecoratorFactory
It accepts a callback that takes the ClassData, MethodData and the property descriptor as arguments, you can modify the reflection information inside that callback.
// Example of a Method Decorator created using the MethodDecoratorFactory
const methodDecorator = (val:number): MethodDecorator => MethodDecoratorFactory(
(cd: ClassData, md:MethodData, descr: any) => {
// Manipulate the class / method reflection data if needed
md.tags['MethodCacheTimeout'] = val;
}
);
class Test {
@methodDecorator(10)
public method(arg1:string):void{}
}
ParameterDecoratorFactory
It accepts a callback that takes the ClassData, MethodData and the ParameterData as arguments, you can modify the reflection information inside that callback.
// Example of a Argument Decorator created using the ParameterDecoratorFactory
const parameterDecorator = (): ParameterDecorator => ParameterDecoratorFactory(
(cd: ClassData, md:MethodData, param: ParameterData) => {
// Manipulate the class / method / argument reflection data if needed
}
);
class Test {
public method(@parameterDecorator() arg1:string):void{}
}
AnyDecoratorFactory
It accepts a callback that takes a combination of all the above as arguments, you can modify the reflection information inside that callback. It can be applied on anything (class, method, properties, parameters)
enum DecoratorType {
Class,
Method,
Property,
Parameter
}
// Example of a Decorator that can be used to decorate anything created using the AnyDecoratorFactory
const anyDecorator = (): AnyDecorator => AnyDecoratorFactory(
(cd: ClassData, arg1: MethodData | PropertyData | undefined, arg2: ParameterData | any) => {
// Manipulate the class / property / method / argument reflection data if needed
const type = GetDecoratorType(cd, arg1, arg2);
switch(type)...
}
);
@anyDecorator()
class Test {
@anyDecorator()
public prop:string;
@anyDecorator()
public method(@anyDecorator() arg1:string):void{}
}
Reflect Hooks
Allow hooks to be added to the decorator / reflection system to intercept various points in the flow of creating reflection information. Adding hooks is done by calling addHook on the ReflectHelper class. This can be usefull for example to keep track of all classes that have decorators by intercepting class reflection info creation.
ReflectHelper.addHook(hook : Partial<IReflectionHook>);
interface IReflectionHook {
// Called after the class data has been created
onCreateClass(cd: ClassData): void;
// Called after a decorator is applied
onDecoratedClass(cd: ClassData): void;
// Called once the class has been augmented with information from typescript / javascript reflection
onProcessedClass(cd: ClassData): void;
// Called once a method reflection information is created
onCreateMethod(cd: ClassData, md: MethodData): void;
// Called after a decorator is applied
onDecoratedMethod(cd: ClassData, md: MethodData): void;
// Called once a method is processed as part of the class augmentation process, after this
// - The method should have return types
// - The parameters should have names
// - Any parameter not decorated should have the target associated
// - The method flag should be valid
onProcessedMethod(cd: ClassData, md: MethodData): void;
// Called once a property reflection information is created
onCreateProperty(cd: ClassData, pd: PropertyData): void;
// Called after a decorator is applied
onDecoratedProperty(cd: ClassData, pd: PropertyData): void;
// Called once a parameter reflection information is created
onCreateParameter(cd: ClassData, md: MethodData, pd: ParameterData): void;
// Called after a decorator is applied
onDecoratedParameter(cd: ClassData, md: MethodData, pd: ParameterData): void;
}
Dynamic
Functionality for defining a class / methods at runtime, simple interface, simple to use, simple to understand
Not too much that can be said. The example contains 99% of all there is to it. Some methods have more optional arguments to improve utilisation.
Method chaining can be used.
const MyClassDecorator = (): ClassDecorator => ClassDecoratorFactory(
(cd: ClassData) => { cd.tags['dec'] = true; }
);
const MyPropDecorator = () => PropertyDecoratorFactory(
(cd: ClassData, prop: PropertyData) => { prop.tags['dec'] = true; }
);
const MyMethodDecorator = () => MethodDecoratorFactory(
(cd: ClassData, md: MethodData, descr: any) => { md.tags['dec'] = true; }
);
const MyParamDecorator = () => ParameterDecoratorFactory(
(cd: ClassData, md: MethodData, p: ParameterData) => { p.tags['dec'] = true; }
);
const classData: ClassData = Dynamic.createClass('MyCustomDynamicClass', null, (dc: DynamicClass) => {
// Add the class decorator
dc.decorate(MyClassDecorator());
// dc.decorate(AnotherDecorator);
dc.addMethod('dynamicMethod', (dm: DynamicMethod) => {
// Add the method decorator
dm.decorate(MyMethodDecorator());
// First argument
dm.addParameter()
// Second argument with decoration
dm.addParameter((da: DynamicParameter) => {
da.decorate(MyParamDecorator());
});
// Set the method body
// WARNING: Be carefull of using lambda here, you must understand the context of 'this'
dm.addBody(function (arg1, arg2) { return arg1 + arg2; });
});
// Properties
dc.addProperty('lastName');
dc.addProperty('firstName', (dp: DynamicProperty) => {
dp.decorate(MyPropDecorator());
dp.setValue('YourFirstName');
});
});
const instance = Reflect.create(classData.target, []);
// This will log 3
console.log(instance.dynamicMethod(1, 2));
// This will log all reflection info we have on the class
console.log(classData);
Example
For a complex example including lib-intercept go to lib-intercept-example