Updated Dive Deeper (markdown)

Shinwoo PARK 2023-12-10 23:54:16 +09:00
parent be56a30fc8
commit cb6ecd2299

@ -26,9 +26,9 @@ All `LoggedParam`, `InjectLogger`, `ScopeKey` is powered by `reflect-metadata` p
It provides **Metadata Reflection API**, which gives functionality to add additional metadatas to a class.
It is pretty useful for saving the metadata in a decorator, and then use it in another decorator.
## `InjectLogger`, `LoggedParam`, `ScopeKey` Implementation Explained
## Parameter Decorator Implementation Explained
If you look at `src/reflected.ts` file, there is implementations for `InjectLogger`, `LoggedParam`, `ScopeKey` decorators.
If you look at `src/reflected.ts` file, there is implementations for parameter decorators.
What these decorators have in common is they store metadatas using metadata reflection api.
### `InjectLogger`
@ -80,4 +80,65 @@ The only different thing is, `ScopeKey` sorts its metadata array by priority.
Although `ShouldScoped` is a method decorator, it only defined metadata like `InjectLogger`, and it's very simple.
It just set `forceScopeKey` to true in method metadata.
Getting the metadata value which is not defined is same as undefined, so the default value is falsy.
Getting the metadata value which is not defined is same as undefined, so the default value is falsy.
## Method Decorator Implementation Explained
Now, look at `src/logged.ts`.
This file contains `LoggedFunction`, `LoggedRoute`, `LoggedInjectable`, `LoggedController`.
Most basic implementation is `LoggedFunction`.
Let's look inside of that.
`LoggedFunction` is a method decorator itself, and has three parameters.
1. _the prototype of the class for an instance member_ (`_target: any`) (prototype of the class that owns the method)
2. _The name of the member_ (`key: string`)
3. _The Property Descriptor for the member_ (`descriptor: TypedPropertyDescriptor<(...args: F) => Promise<R>>`)
First of all, it calls `loggerInit` function with _target.
The `loggerInit` function is at the top of file, taking _target: any as the only parameter.
When `loggerInit` is called, if _target (prototype) has no logger, make new logger with their name, and define it as its property.
Then, after loggerInit finished, the LoggedFunction takes initialized _target.logger, and descriptor.value as constant fn.
descriptor.value is the function that LoggedFunction decorator will be applied.
It is ensured to be function. if it is not a function, logger will warn about this, and stop applying the decorator.
The next step is taking all the metadata sets out. It will be explained later, so just keep that in mind.
Next four constants are just getting data of `InjectLogger`, `LoggedParam`, `ScopeKey`, `ShouldScoped` decorators.
and then it creates overrideFunction constant with overrideBuild.
What overrideBuild makes is just a function that has same parameter and return type with original descriptor.value.
But the difference is - it takes all parameter data, original function, logger, etc.
It is really important to understand the inside of the overrideBuild function.
It makes new ScopedLogger, print parameter logs, and injects logger, log return response.
If it catches any error while running original function, it catches and logs with injected logger, and then throws it back.
It takes input, works, and return output same as original function, but internally logging things.
After building core function that will be override on top of original function, it overrides the function.
_target[key] overriding is for class decorators like `LoggedInjectable` or `LoggedController`,
and descriptor.value overriding is for direct decorators like `LoggedRoute` or `LoggedFunction`.
`LoggedRoute` and `LoggedFunction` has almost same implementation, but `LoggedRoute` takes `path` and `method` metadata that is defined by nestjs.
`@nestjs/common` internally uses reflect-metadata, and saves path and method as metadata.
So it gets the metadata from "path" and "method" key, and then use it inside of overrideBuild to build a log.
The logging decorator lastly does save all the metadata previously got.
This is required, because when it overrides function, the metadata which was originally included in function is wiped by new function.
This problem is really serious, because it wipes the path, method, guards, everything it has.
Without redefining step, the function will not behave like you expected.
NestJS will not detect the method as the request handler, or guard does not work, etc.
So, redefining every single original metadata is really important step.