Skip to content

Commit

Permalink
Merge pull request #11 from codibre/improving-readme
Browse files Browse the repository at this point in the history
fix: improving readme
  • Loading branch information
Farenheith authored Sep 24, 2024
2 parents a31a6cb + aa8785b commit 8f0fb74
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 13 deletions.
45 changes: 41 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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?

Expand All @@ -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!
26 changes: 21 additions & 5 deletions src/decorator-helpers.ts
Original file line number Diff line number Diff line change
@@ -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<T extends object = object>(
cls: ClassType<T>,
clsOrMeta: ClassMetadata<T>,
propertyAndMethodsDecorators: Partial<
Record<Key<T> | typeof DEFAULT, (MethodDecorator | PropertyDecorator)[]>
>,
) {
const def = propertyAndMethodsDecorators[DEFAULT];
const meta = getClassMetadata(cls);
): void;
export function applyPropertyAndMethodsDecorators<T extends object = object>(
clsOrMeta: ClassType<T>,
propertyAndMethodsDecorators: Partial<
Record<Key<T> | typeof DEFAULT, (MethodDecorator | PropertyDecorator)[]>
>,
): void;
export function applyPropertyAndMethodsDecorators<T extends object = object>(
clsOrMeta: ClassType<T> | ClassMetadata<T>,
propertyAndMethodsDecorators: Partial<
Record<Key<T> | typeof DEFAULT, (MethodDecorator | PropertyDecorator)[]>
>,
): void {
const meta = isMetadata<T>(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) {
Expand Down
6 changes: 4 additions & 2 deletions src/meta-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T extends object = object>(
cls: ClassType<T>,
): ClassMetadata<T> | undefined {
return metadata.get(cls.prototype) as ClassMetadata<T> | undefined;
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/meta-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ export type ClassType<T extends object = object> = abstract new (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...args: any[]
) => T;
export type Key<T extends object = object> = keyof T | keyof ClassType<T>;
export type Key<T extends object = object> = (keyof T | keyof ClassType<T>) &
(string | symbol);

export interface ModifiersMetadata {
public: boolean;
Expand Down
9 changes: 9 additions & 0 deletions src/plugin/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
} from '../meta-type';

const metadata = getMetadataStorage();
const metadataSymbol = Symbol('metadataSymbol');
const metadataMarker = { [metadataSymbol]: true };

function getMeta<T extends object = object>(prototype: T) {
let ref = metadata.get(prototype) as ClassMetadata<T> | undefined;
Expand All @@ -19,11 +21,18 @@ function getMeta<T extends object = object>(prototype: T) {
properties: new Map<Key<T>, PropertyMetadata>(),
methods: new Map<Key<T>, MethodMetadata>(),
};
Object.assign(ref, metadataMarker);
metadata.set(prototype, ref as unknown as ClassMetadata<object>);
}
return ref;
}

export function isMetadata<T extends object = object>(
ref: unknown,
): ref is ClassMetadata<T> {
return typeof ref === 'object' && !!ref && metadataSymbol in ref;
}

export function registerClassMetadata(modifiers: ModifiersMetadata) {
return (cls: ClassType) => {
const { prototype } = cls;
Expand Down
4 changes: 3 additions & 1 deletion src/plugin/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ function* emitPropertyAssignments<T extends object = object>(
if (k in obj) {
yield tsBinary.factory.createPropertyAssignment(
k,
obj[k] ? tsBinary.factory.createTrue() : tsBinary.factory.createFalse(),
obj[k as Key<T>]
? tsBinary.factory.createTrue()
: tsBinary.factory.createFalse(),
);
}
}
Expand Down

0 comments on commit 8f0fb74

Please sign in to comment.