Skip to content

Commit

Permalink
feat(common): Add @context decorator
Browse files Browse the repository at this point in the history
- Add documentation for @Locals, @context, @Session
- Update LogIncomingRequestMiddleware.
- RequestContext now create RequestLogger with the right options.
  • Loading branch information
Romakita committed Nov 16, 2019
1 parent 90dad00 commit 469d0b9
Show file tree
Hide file tree
Showing 25 changed files with 468 additions and 185 deletions.
4 changes: 2 additions & 2 deletions integration/getting-started/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"travis:coveralls": "nyc report --reporter=text-lcov | coveralls",
"tsc": "tsc --project tsconfig.json",
"tsc:w": "tsc --project tsconfig.json -w",
"start": "nodemon --watch 'src/**/*.ts' --ignore 'node_modules/**/*' --exec ts-node src/index.ts",
"start": "nodemon --watch src/**/*.ts --ignore node_modules/**/* --exec ts-node src/index.ts",
"start:prod": "cross-env NODE_ENV=production node dist/index.js",
"docker:build": "yarn build && docker-compose build",
"deploy": "exit 0"
Expand Down Expand Up @@ -62,4 +62,4 @@
"tslint": "^5.12.0",
"typescript": "^3.2.2"
}
}
}
5 changes: 3 additions & 2 deletions packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@
"json-schema": "^0.2.3",
"rxjs": "^6.5.2",
"ts-httpexceptions": "^4.1.0",
"ts-log-debug": "^5.1.0",
"tslib": "^1.10.0"
"ts-log-debug": "^5.1.1",
"tslib": "1.10.0",
"uuid": "3.3.3"
},
"peerDependencies": {
"@tsed/core": "0.0.0-PLACEHOLDER",
Expand Down
1 change: 1 addition & 0 deletions packages/common/src/mvc/decorators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export * from "./params/bodyParams";
export * from "./params/cookies";
export * from "./params/headerParams";
export * from "./params/locals";
export * from "./params/context";
export * from "./params/pathParams";
export * from "./params/queryParams";
export * from "./params/session";
Expand Down
64 changes: 64 additions & 0 deletions packages/common/src/mvc/decorators/params/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {ParamTypes} from "../../models/ParamTypes";
import {UseFilter} from "./useFilter";
import {mapParamsOptions} from "./utils/mapParamsOptions";

/**
* Context decorator return the @@RequestContext@@ created by Ts.ED when request is handled by the server.
*
* It contains some information as following:
*
* - The request id,
* - The request container used by the Ts.ED DI. It contain all services annotated with `@Scope(ProviderScope.REQUEST)`,
* - The current @@EndpointMetadata@@ resolved by Ts.ED during the request,
* - The data return by the previous endpoint if you use multiple handler on the same route. By default data is empty.
*
* ::: tip
* The @@RequestContext@@ inherit from Map class. So you can store any information with.
* :::
*
* #### Example
*
* ```typescript
* @Middleware()
* class AuthTokenMiddleware {
* use(@Req() request: Express.Request, @Context() context: RequestContext) {
* if (!context.has('auth')){
* context.set('auth', new AuthToken(req))
* }
*
* try {
* context.get('auth').claims() // check token
* } catch(er){
* throw Forbidden("Access forbidden - Bad token")
* }
* }
* }
*
* @Controller('/')
* @UseBefore(AuthTokenMiddleware) // protect all routes for this controller
* class MyCtrl {
* @Get('/')
* get(@Context('auth') auth: AuthToken) {
* console.log('auth', auth);
* console.log('auth.accessToken', auth.accessToken);
* console.log('auth.idToken', auth.idToken);
* }
* }
* ```
*
* @param expression The path of the property to get.
* @decorator
* @returns {Function}
*/
export function Context(expression: string): ParameterDecorator;
export function Context(): ParameterDecorator;
export function Context(...args: any[]): ParameterDecorator {
const {expression, useType, useConverter = false, useValidation = false} = mapParamsOptions(args);

return UseFilter(ParamTypes.CONTEXT, {
expression,
useType,
useConverter,
useValidation
});
}
2 changes: 1 addition & 1 deletion packages/common/src/mvc/decorators/params/cookies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export function CookiesParams(...args: any[]): ParameterDecorator {
}

/**
* Cookies o CookiesParams return the value from [request.cookies](http://expressjs.com/en/4x/api.html#req.cookies) object.
* Cookies or CookiesParams return the value from [request.cookies](http://expressjs.com/en/4x/api.html#req.cookies) object.
*
* #### Example
*
Expand Down
31 changes: 19 additions & 12 deletions packages/common/src/mvc/decorators/params/locals.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,46 @@
import {Type} from "@tsed/core";
import {IParamOptions} from "../../interfaces/IParamOptions";
import {ParamTypes} from "../../models/ParamTypes";
import {UseFilter} from "./useFilter";
import {mapParamsOptions} from "./utils/mapParamsOptions";

/**
* Locals return the value from [response.locals](http://expressjs.com/en/4x/api.html#res.locals) object.
*
* ::: tip
* Locals are generally used by express and third-party like templating engine to render a page/template.
* See [Templating](http://tsed.io/tutorials/templating.html) section for more details.
* :::
*
* #### Example
*
* ```typescript
* * @Middleware()
* class LocalsMiddleware {
* use(@Locals() locals: any) {
* // set some on locals
* locals.user = "user"
* }
* }
*
* @Controller('/')
* @UseBefore(LocalsMiddleware)
* class MyCtrl {
* @Get('/')
* get(@Locals() locals: any) {
* console.log('Entire locals', locals);
* }
*
* @Get('/')
* @Render('home.ejs') // will use locals and returned data to render the page
* get(@Locals('user') user: any) {
* console.log('user', user);
*
* return {
* description: 'Hello world'
* }
* }
* }
* ```
* > For more information on deserialization see [converters](/docs/converters.md) page.
*
* @param expression The path of the property to get.
* @param useType The type of the class that to be used to deserialize the data.
* @decorator
* @returns {Function}
*/
export function Locals(expression: string, useType: Type<any>): ParameterDecorator;
export function Locals(expression: string): ParameterDecorator;
export function Locals(useType: Type<any>): ParameterDecorator;
export function Locals(options: IParamOptions<any>): ParameterDecorator;
export function Locals(): ParameterDecorator;
export function Locals(...args: any[]): ParameterDecorator {
const {expression, useType, useConverter = false, useValidation = false} = mapParamsOptions(args);
Expand Down
9 changes: 6 additions & 3 deletions packages/common/src/mvc/decorators/params/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,21 @@ import {mapParamsOptions} from "./utils/mapParamsOptions";
* create(@Session('id') id: string) {
* console.log('ID', id);
* }
*
* @Post('/') // Example to deserialize use from session
* create(@Session({expression: 'user', useConverter: true}) user: User) {
* console.log('user', user);
* console.log('instanceOf user', user instanceof User);
* }
* }
* ```
* > For more information on deserialization see [converters](/docs/converters.md) page.
*
* @param expression The path of the property to get.
* @param useType The type of the class that to be used to deserialize the data.
* @decorator
* @returns {Function}
*/
export function Session(expression: string, useType: Type<any>): ParameterDecorator;
export function Session(expression: string): ParameterDecorator;
export function Session(useType: Type<any>): ParameterDecorator;
export function Session(options: IParamOptions<any>): ParameterDecorator;
export function Session(): ParameterDecorator;
export function Session(...args: any[]): ParameterDecorator {
Expand Down
6 changes: 2 additions & 4 deletions packages/common/src/mvc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,14 @@ export * from "./models/EndpointMetadata";
export * from "./models/HandlerMetadata";
export * from "./models/ParamMetadata";
export * from "./models/ParamTypes";
export * from "./models/RequestContext";
export * from "./models/RequestLogger";

// builders
export * from "./builders/ControllerBuilder";
export * from "./builders/HandlerBuilder";
export * from "./builders/ParamBuilder";

// provide
export * from "./models/Context";
export * from "./models/RequestLogger";

// registries
export * from "./registries/ControllerRegistry";
export * from "./registries/EndpointRegistry";
Expand Down
7 changes: 4 additions & 3 deletions packages/common/src/mvc/interfaces/Express.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Context} from "../models/Context";
import {EndpointMetadata} from "../models/EndpointMetadata";
import {RequestContext} from "../models/RequestContext";
import {RequestLogger} from "../models/RequestLogger";

declare global {
Expand All @@ -12,11 +12,12 @@ declare global {
headersSent: boolean;
}

export interface Application {}
export interface Application {
}

export interface Request {
id: string;
ctx: Context;
ctx: RequestContext;
log: RequestLogger;

/**
Expand Down
21 changes: 0 additions & 21 deletions packages/common/src/mvc/models/Context.ts

This file was deleted.

73 changes: 73 additions & 0 deletions packages/common/src/mvc/models/RequestContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import {LocalsContainer} from "@tsed/di";
import {EndpointMetadata} from "./EndpointMetadata";
import {RequestLogger} from "./RequestLogger";

export interface IRequestContextOptions {
id: string;
logger: any;
url: string;
ignoreUrlPatterns?: any[];
}

export class RequestContext extends Map<any, any> {
/**
* Request id generated by @@createMiddleware@@.
*
* ::: tip
* By default Ts.ED generate uuid like that `uuidv4().replace(/-/gi, ""))`.
* Dash are removed to simplify tracking logs in Kibana
* :::
*
* ::: tip
* Request id can by customized by changing the server configuration.
*
* ```typescript
* @ServerSettings({
* logger: {
* reqIdBuilder: createUniqId // give your own id generator function
* }
* })
* class Server {
*
* }
* ```
* :::
*
*/
readonly id: string;
/**
* Date when request have been handled by the server. @@RequestLogger@@ use this date to log request duration.
*/
readonly dateStart: Date = new Date();
/**
* The request container used by the Ts.ED DI. It contain all services annotated with `@Scope(ProviderScope.REQUEST)`
*/
readonly container = new LocalsContainer<any>();
/**
* The current @@EndpointMetadata@@ resolved by Ts.ED during the request.
*/
public endpoint: EndpointMetadata;
/**
* The data return by the previous endpoint if you use multiple handler on the same route. By default data is empty.
*/
public data: any;
/**
* Logger attached to the context request.
*/
readonly logger: RequestLogger;

constructor({id, logger, url, ignoreUrlPatterns = []}: IRequestContextOptions) {
super();
this.id = id;
this.logger = new RequestLogger(logger, {
id,
startDate: this.dateStart,
url,
ignoreUrlPatterns
});
}

async destroy() {
await this.container.destroy();
}
}
10 changes: 5 additions & 5 deletions packages/common/src/mvc/models/RequestLogger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@ export class RequestLogger {
readonly id: string;
readonly url: string;
readonly startDate: Date;
public minimalRequestPicker: Function;
public completeRequestPicker: Function;
private readonly ignoreUrlPatterns: any[];
private readonly minimalRequestPicker: Function;
private readonly completeRequestPicker: Function;
private stack: any = [];

constructor(private logger: any, {id, startDate, url, ignoreUrlPatterns, minimalRequestPicker, completeRequestPicker}: any) {
constructor(private logger: any, {id, startDate, url, ignoreUrlPatterns = [], minimalRequestPicker, completeRequestPicker}: any) {
this.id = id;
this.url = url;
this.startDate = startDate;
this.ignoreUrlPatterns = ignoreUrlPatterns.map((pattern: string | RegExp) =>
typeof pattern === "string" ? new RegExp(pattern, "gi") : pattern
);

this.minimalRequestPicker = minimalRequestPicker;
this.completeRequestPicker = completeRequestPicker;
this.minimalRequestPicker = minimalRequestPicker || ((l: any) => l);
this.completeRequestPicker = completeRequestPicker || ((l: any) => l);
}

info(obj: any) {
Expand Down
Loading

0 comments on commit 469d0b9

Please sign in to comment.