From aa8785b46aafd044551e72768afac1d3daf61f2a Mon Sep 17 00:00:00 2001 From: Thiago Date: Tue, 24 Sep 2024 16:24:46 -0300 Subject: [PATCH] fix: improving readme Adding instructions for the use of decorator helpers into the readme --- README.md | 45 ++++++++++++++++++++++++++++++++++++---- src/decorator-helpers.ts | 26 ++++++++++++++++++----- src/meta-info.ts | 6 ++++-- src/meta-type.ts | 3 ++- src/plugin/decorators.ts | 9 ++++++++ src/plugin/emitter.ts | 4 +++- 6 files changed, 80 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index b0760df..3a1e29a 100644 --- a/README.md +++ b/README.md @@ -43,11 +43,7 @@ Metadata of all classes is accessible through the method **getClassMetadata**, w You can also iterate over all metadata registered through **iterateMetadata**. Finally, metadata may be a sensitive data of your application, so, you can erase all its information using **clearAllMetadata**. We recommend you to do so, if you use this library, don't keep any hard logic depending on what this package will register, just construct whenever you need and clear it all at the end. -## What we're not doing yet. - -* We're not generating metadata of get and set accessors; -This is a point of evolution of this library and we'll address them as soon as possible. If you have any suggestions or contributions to do, feel free to contact us! ## How to use it with Jest? @@ -67,3 +63,44 @@ You can set the transformer of this library to run with jest following the examp ``` This will be enough to make it apply it during transpilation. + +## Helpers to apply decorators + +One of the advantages of having all this metadata emitted is that you can apply decorators for existing classes in separated scopes! To do that, there're two helper functions this library offers: + +the first one is **applyPropertyAndMethodsDecorators**: +```ts +applyPropertyAndMethodsDecorators(MyClass, { + prop1: [ + @ApiProperty({ + example: '123' + }) + ], + prop2: [ + @ApiProperty() + @IsEnum(MyEnum) + ], + [DEFAULT]: [@ApiProperty()] +}) +``` + +Notice the symbol **DEFAULT**, it's a symbol imported from our library and serves to purpose of define a decorator that'll be applied to every property or method that doesn't have a specific set os decorators informed. This second parameter is a strongly typed object and it'll only allow names of properties and methods of MyClass (static or not), or the **DEFAULT** symbol. +Executing this code will have the same effect as applying the decorators directly in the class. + +The second helper method is simpler, **applyClassDecorators**: + +```ts +applyClassDecorators(MyClass, [ + @Model() + @PrimaryKey({ field1: 1, field2: 2 }) + ] +}) +``` + +This one will apply the decorators to the class itself, not its properties. + +## What we're not doing yet. + +* We're not generating metadata of get and set accessors; + +This is a point of evolution of this library and we'll address them as soon as possible. If you have any suggestions or contributions to do, feel free to contact us! diff --git a/src/decorator-helpers.ts b/src/decorator-helpers.ts index 364bfe8..da3aee7 100644 --- a/src/decorator-helpers.ts +++ b/src/decorator-helpers.ts @@ -1,18 +1,34 @@ import { getProperMetadataTarget } from './internal/get-proper-metadata-target'; import { getClassMetadata } from './meta-info'; -import { ClassType, Key } from './meta-type'; +import { ClassMetadata, ClassType, Key } from './meta-type'; +import { isMetadata } from './plugin'; export const DEFAULT = Symbol('default'); export function applyPropertyAndMethodsDecorators( - cls: ClassType, + clsOrMeta: ClassMetadata, propertyAndMethodsDecorators: Partial< Record | typeof DEFAULT, (MethodDecorator | PropertyDecorator)[]> >, -) { - const def = propertyAndMethodsDecorators[DEFAULT]; - const meta = getClassMetadata(cls); +): void; +export function applyPropertyAndMethodsDecorators( + clsOrMeta: ClassType, + propertyAndMethodsDecorators: Partial< + Record | typeof DEFAULT, (MethodDecorator | PropertyDecorator)[]> + >, +): void; +export function applyPropertyAndMethodsDecorators( + clsOrMeta: ClassType | ClassMetadata, + propertyAndMethodsDecorators: Partial< + Record | typeof DEFAULT, (MethodDecorator | PropertyDecorator)[]> + >, +): void { + const meta = isMetadata(clsOrMeta) + ? clsOrMeta + : getClassMetadata(clsOrMeta); if (!meta) throw new Error('No class metadata found'); + const cls = meta.ctor.cls; + const def = propertyAndMethodsDecorators[DEFAULT]; for (const prop of meta?.properties.values()) { const decorators = propertyAndMethodsDecorators[prop.name] ?? def; if (decorators) { diff --git a/src/meta-info.ts b/src/meta-info.ts index 9ec0446..f5308a4 100644 --- a/src/meta-info.ts +++ b/src/meta-info.ts @@ -8,8 +8,10 @@ const metadata = getMetadataStorage(); * Return metadata of the class informed, or undefined if there is none * @param cls The Class to get metadata from */ -export function getClassMetadata(cls: ClassType): ClassMetadata | undefined { - return metadata.get(cls.prototype); +export function getClassMetadata( + cls: ClassType, +): ClassMetadata | undefined { + return metadata.get(cls.prototype) as ClassMetadata | undefined; } /** diff --git a/src/meta-type.ts b/src/meta-type.ts index 5fd841b..553cd55 100644 --- a/src/meta-type.ts +++ b/src/meta-type.ts @@ -2,7 +2,8 @@ export type ClassType = abstract new ( // eslint-disable-next-line @typescript-eslint/no-explicit-any ...args: any[] ) => T; -export type Key = keyof T | keyof ClassType; +export type Key = (keyof T | keyof ClassType) & + (string | symbol); export interface ModifiersMetadata { public: boolean; diff --git a/src/plugin/decorators.ts b/src/plugin/decorators.ts index 5c2ae74..4c94df7 100644 --- a/src/plugin/decorators.ts +++ b/src/plugin/decorators.ts @@ -10,6 +10,8 @@ import { } from '../meta-type'; const metadata = getMetadataStorage(); +const metadataSymbol = Symbol('metadataSymbol'); +const metadataMarker = { [metadataSymbol]: true }; function getMeta(prototype: T) { let ref = metadata.get(prototype) as ClassMetadata | undefined; @@ -19,11 +21,18 @@ function getMeta(prototype: T) { properties: new Map, PropertyMetadata>(), methods: new Map, MethodMetadata>(), }; + Object.assign(ref, metadataMarker); metadata.set(prototype, ref as unknown as ClassMetadata); } return ref; } +export function isMetadata( + ref: unknown, +): ref is ClassMetadata { + return typeof ref === 'object' && !!ref && metadataSymbol in ref; +} + export function registerClassMetadata(modifiers: ModifiersMetadata) { return (cls: ClassType) => { const { prototype } = cls; diff --git a/src/plugin/emitter.ts b/src/plugin/emitter.ts index 5a40d0e..e3db68e 100644 --- a/src/plugin/emitter.ts +++ b/src/plugin/emitter.ts @@ -27,7 +27,9 @@ function* emitPropertyAssignments( if (k in obj) { yield tsBinary.factory.createPropertyAssignment( k, - obj[k] ? tsBinary.factory.createTrue() : tsBinary.factory.createFalse(), + obj[k as Key] + ? tsBinary.factory.createTrue() + : tsBinary.factory.createFalse(), ); } }