UNPKG

inventoresed

Version:

Z-Wave driver written entirely in JavaScript/TypeScript

448 lines (393 loc) 14.5 kB
import type { TypedClassDecorator } from "@zwave-js/shared"; import "reflect-metadata"; type Constructor<T> = new (...args: any[]) => T; export interface ReflectionDecorator< // eslint-disable-next-line @typescript-eslint/ban-types TBase extends Object, TArgs extends any[], TValue, TConstructor extends Constructor<TBase> = Constructor<TBase>, > { /** The decorator which is used to decorate classes */ decorator: <TTarget extends TBase>( ...args: TArgs ) => TypedClassDecorator<TTarget>; /** Looks up the value which was assigned to the target class by the decorator, using a class instance */ lookupValue: (target: TBase) => TValue | undefined; /** Looks up the value which was assigned to the target class by the decorator, using the class itself */ // eslint-disable-next-line @typescript-eslint/ban-types lookupValueStatic: (constr: Function) => TValue | undefined; /** Looks up the class constructor for a given value. This can only be used if the value does not need to be transformed using `constructorLookupKey`. */ lookupConstructorByValue: (value: TValue) => TConstructor | undefined; /** Looks up the class constructor for a given lookup key. This MUST be used if the value needs to be transformed using `constructorLookupKey`. */ lookupConstructorByKey: (key: string) => TConstructor | undefined; } export interface CreateReflectionDecoratorOptions< // eslint-disable-next-line @typescript-eslint/ban-types TBase extends Object, TArgs extends any[], TValue, TConstructor extends Constructor<TBase> = Constructor<TBase>, > { /** The name of this decorator */ name: string; /** Determines the value to be stored for the given arguments */ valueFromArgs: (...args: TArgs) => TValue; /** * Determines the key under which the constructor should be stored in the Map for reverse constructor lookup. * Defaults to the value. Return `false` to disable storing the constructor for lookup. */ constructorLookupKey?: | false | ((target: TConstructor, ...args: TArgs) => string); } /** Creates a reflection decorator and corresponding methods for reverse lookup of values and constructors */ export function createReflectionDecorator< // eslint-disable-next-line @typescript-eslint/ban-types TBase extends Object, TArgs extends any[], TValue, TConstructor extends Constructor<TBase> = Constructor<TBase>, >({ name, valueFromArgs, // getConstructorLookupTarget, constructorLookupKey, }: CreateReflectionDecoratorOptions< TBase, TArgs, TValue, TConstructor >): ReflectionDecorator<TBase, TArgs, TValue, TConstructor> { const key = Symbol.for(`METADATA_${name}`); const mapKey = Symbol.for(`METADATA_MAP_${name}`); const lookupTarget = Object.create(null); const grp: ReflectionDecorator<TBase, TArgs, TValue, TConstructor> = { decorator: (...args): TypedClassDecorator<TBase> => { const value = valueFromArgs(...args); let body = (target: TConstructor) => { Reflect.defineMetadata(key, value, target); if (constructorLookupKey === false) return; const reverseLookupKey = constructorLookupKey?.(target, ...args) ?? String(value); // Store the constructor on the reverse lookup target const map: Map<string, TConstructor> = Reflect.getMetadata(mapKey, lookupTarget) || new Map(); map.set(reverseLookupKey, target); Reflect.defineMetadata(mapKey, map, lookupTarget); }; // Rename the decorator body so it is easier to identify in stack traces body = Object.defineProperty(body, "name", { value: "decoratorBody_" + name, }); return body as any; }, lookupValue: (target) => { return Reflect.getMetadata(key, target.constructor); }, lookupValueStatic: (constr) => { return Reflect.getMetadata(key, constr); }, lookupConstructorByValue: (value) => { if (constructorLookupKey === false) { throw new Error( "Constructor lookup is disabled for this decorator!", ); } else if (constructorLookupKey) { throw new Error( "Cannot lookup constructor by value when constructorLookupKey is used", ); } else { return grp.lookupConstructorByKey(String(value)); } }, lookupConstructorByKey: (key: string) => { if (constructorLookupKey === false) { throw new Error( "Constructor lookup is disabled for this decorator!", ); } const map = Reflect.getMetadata(mapKey, lookupTarget) as | Map<string, TConstructor> | undefined; return map?.get(key); }, }; // Rename the decorator functions so they are easier to identify in stack traces for (const property of [ "decorator", "lookupValue", "lookupValueStatic", "lookupConstructorByValue", "lookupConstructorByKey", ] as const) { grp[property] = Object.defineProperty(grp[property], "name", { value: `${property}_${name}`, }) as any; } return grp; } export interface SimpleReflectionDecorator< // eslint-disable-next-line @typescript-eslint/ban-types TBase extends Object, TArgs extends [any], TConstructor extends Constructor<TBase> = Constructor<TBase>, > { /** The decorator which is used to decorate the super class */ decorator: <TTarget extends TBase>( ...args: TArgs ) => TypedClassDecorator<TTarget>; /** Looks up the value which was assigned to the target class by the decorator, using a class instance */ lookupValue: (target: TBase) => TArgs[0] | undefined; /** Looks up the value which was assigned to the target class by the decorator, using the class itself */ // eslint-disable-next-line @typescript-eslint/ban-types lookupValueStatic: (constr: Function) => TArgs[0] | undefined; /** Looks up the super class constructor for a given value. */ lookupConstructor: (...args: TArgs) => TConstructor | undefined; } export interface CreateSimpleReflectionDecoratorOptions { /** The name of the decorator */ name: string; } /** * Like {@link createReflectionDecorator}, but for single-value decorators. This has the advantage that the returned functions can be reused easier with named args. */ export function createSimpleReflectionDecorator< // eslint-disable-next-line @typescript-eslint/ban-types TBase extends Object, TArgs extends [any], TConstructor extends Constructor<TBase> = Constructor<TBase>, >({ name, }: CreateSimpleReflectionDecoratorOptions): SimpleReflectionDecorator< TBase, TArgs, TConstructor > { const decorator = createReflectionDecorator< TBase, TArgs, TArgs[0], TConstructor >({ name, valueFromArgs: (arg) => arg, }); const ret: SimpleReflectionDecorator<TBase, TArgs, TConstructor> = { decorator: decorator.decorator, lookupValue: decorator.lookupValue, lookupValueStatic: decorator.lookupValueStatic, lookupConstructor: decorator.lookupConstructorByValue, }; return ret; } export interface ValuelessReflectionDecorator< // eslint-disable-next-line @typescript-eslint/ban-types TBase extends Object, > { /** The decorator which is used to decorate the super class */ decorator: <TTarget extends TBase>() => TypedClassDecorator<TTarget>; /** Checks if the target class was decorated by this decorator, using a class instance */ isDecorated: (target: TBase) => boolean; /** Checks if the target class was decorated by this decorator, using the class itself */ // eslint-disable-next-line @typescript-eslint/ban-types isDecoratedStatic: (constr: Function) => boolean; } export interface CreateValuelessReflectionDecoratorOptions { /** The name of the decorator */ name: string; } /** * Like {@link createReflectionDecorator}, but for valueless decorators. */ export function createValuelessReflectionDecorator< // eslint-disable-next-line @typescript-eslint/ban-types TBase extends Object, >({ name, }: CreateValuelessReflectionDecoratorOptions): ValuelessReflectionDecorator<TBase> { const decorator = createReflectionDecorator<TBase, [], boolean>({ name, valueFromArgs: () => true, }); const ret: ValuelessReflectionDecorator<TBase> = { decorator: decorator.decorator, isDecorated: (target: TBase) => !!decorator.lookupValue(target), // eslint-disable-next-line @typescript-eslint/ban-types isDecoratedStatic: (constr: Function) => !!decorator.lookupValueStatic(constr), }; return ret; } export interface ReflectionDecoratorPair< // eslint-disable-next-line @typescript-eslint/ban-types TBase extends Object, TSuperArgs extends [any], TSubArgs extends [any], TConstructor extends Constructor<TBase> = Constructor<TBase>, > { /** The decorator which is used to decorate the super class */ superDecorator: <TTarget extends TBase>( ...args: TSuperArgs ) => TypedClassDecorator<TTarget>; /** The decorator which is used to decorate the sub classes */ subDecorator: <TTarget extends TBase>( ...args: TSubArgs ) => TypedClassDecorator<TTarget>; /** Looks up the value which was assigned to the target super class by the decorator, using a class instance */ lookupSuperValue: (target: TBase) => TSuperArgs[0] | undefined; /** Looks up the value which was assigned to the target sub class by the decorator, using a class instance */ lookupSubValue: (target: TBase) => TSubArgs[0] | undefined; /** Looks up the value which was assigned to the target super class by the decorator, using the class itself */ // eslint-disable-next-line @typescript-eslint/ban-types lookupSuperValueStatic: (constr: Function) => TSuperArgs[0] | undefined; /** Looks up the value which was assigned to the target sub class by the decorator, using the class itself */ // eslint-disable-next-line @typescript-eslint/ban-types lookupSubValueStatic: (constr: Function) => TSubArgs[0] | undefined; /** Looks up the super class constructor for a given value. */ lookupSuperConstructor: (...args: TSuperArgs) => TConstructor | undefined; /** Looks up the sub class constructor for a given value pair. */ lookupSubConstructor: ( ...args: [...TSuperArgs, ...TSubArgs] ) => TConstructor | undefined; } export interface CreateReflectionDecoratorPairOptions { /** The name of the super decorator */ superName: string; /** The name of the sub decorator */ subName: string; } /** * Creates a pair of reflection decorators and corresponding methods for reverse lookup of values and constructors. * This pair is meant to decorate a super class and several of its subclasses */ export function createReflectionDecoratorPair< // eslint-disable-next-line @typescript-eslint/ban-types TBase extends Object, TSuperArgs extends [any], TSubArgs extends [any], TConstructor extends Constructor<TBase> = Constructor<TBase>, >({ superName, subName, }: CreateReflectionDecoratorPairOptions): ReflectionDecoratorPair< TBase, TSuperArgs, TSubArgs, TConstructor > { const superDecorator = createReflectionDecorator< TBase, TSuperArgs, TSuperArgs[0], TConstructor >({ name: superName, valueFromArgs: (arg) => arg, }); const getLookupKey = (superArg: TSuperArgs[0], subArg: TSubArgs[0]) => { return JSON.stringify({ [superName]: superArg, [subName]: subArg }); }; const subDecorator = createReflectionDecorator< TBase, TSubArgs, TSubArgs[0], TConstructor >({ name: subName, valueFromArgs: (arg) => arg, constructorLookupKey: (target, subArg) => { const superArg = superDecorator.lookupValueStatic(target); return getLookupKey(superArg, subArg); }, }); const ret: ReflectionDecoratorPair< TBase, TSuperArgs, TSubArgs, TConstructor > = { superDecorator: superDecorator.decorator, subDecorator: subDecorator.decorator, lookupSuperValue: superDecorator.lookupValue, lookupSubValue: subDecorator.lookupValue, lookupSuperValueStatic: superDecorator.lookupValueStatic, lookupSubValueStatic: subDecorator.lookupValueStatic, lookupSuperConstructor: superDecorator.lookupConstructorByValue, lookupSubConstructor: (...args) => { return subDecorator.lookupConstructorByKey( getLookupKey(args[0], args[1]), ); }, }; return ret; } // export interface PropertyReflectionDecorator< // // eslint-disable-next-line @typescript-eslint/ban-types // TTarget extends Object, // TArgs extends any[], // TValue, // > { // /** The decorator which is used to decorate properties */ // decorator: (...args: TArgs) => TypedPropertyDecorator<TTarget>; // /** Looks up all decorated properties and the decorator arguments for a class instance */ // lookupValues: (target: TTarget) => ReadonlyMap<string | number, TValue>; // } // export interface CreatePropertyReflectionDecoratorOptions< // TArgs extends any[], // TValue, // > { // /** The name of this decorator */ // name: string; // /** Determines the value to be stored for the given arguments */ // valueFromArgs: (...args: TArgs) => TValue; // } // /** Creates a reflection decorator for a class property and the corresponding method for reverse lookup of defined values */ // export function createPropertyReflectionDecorator< // // eslint-disable-next-line @typescript-eslint/ban-types // TTarget extends Object, // TArgs extends any[], // TValue, // >({ // name, // valueFromArgs, // }: CreatePropertyReflectionDecoratorOptions< // TArgs, // TValue // >): PropertyReflectionDecorator<TTarget, TArgs, TValue> { // const key = Symbol.for(`METADATA_${name}`); // const prp: PropertyReflectionDecorator<TTarget, TArgs, TValue> = { // decorator: (...args) => { // const value = valueFromArgs(...args); // let body = ( // target: TTarget, // property: string | number | symbol, // ) => { // // get the class constructor // const constr = target.constructor; // // retrieve the current metadata // const metadata: Map<string | number, TValue> = // Reflect.getMetadata(key, constr) ?? new Map(); // // Add the variable // metadata.set(property as string | number, value); // // And store it back // Reflect.defineMetadata(key, metadata, constr); // }; // // Rename the decorator body so it is easier to identify in stack traces // body = Object.defineProperty(body, "name", { // value: "decoratorBody_" + name, // }); // return body; // }, // lookupValues: (target) => { // return Reflect.getMetadata(key, target.constructor); // }, // }; // // Rename the decorator functions so they are easier to identify in stack traces // for (const property of ["decorator", "lookupValues"] as const) { // prp[property] = Object.defineProperty(prp[property], "name", { // value: `${property}_${name}`, // }) as any; // } // return prp; // }