feat(): add response passthrough configuration property

This commit is contained in:
Kamil Myśliwiec
2020-11-18 14:04:31 +01:00
parent 8009337e23
commit 55d7b0e890
7 changed files with 76 additions and 15 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "@nestjs/core",
"version": "7.4.0",
"version": "7.5.3",
"description": "Modern, fast, powerful node.js web framework",
"homepage": "https://nestjs.com",
"repository": {

View File

@@ -26,4 +26,5 @@ export const HTTP_CODE_METADATA = '__httpCode__';
export const MODULE_PATH = '__module_path__';
export const HEADERS_METADATA = '__headers__';
export const REDIRECT_METADATA = '__redirect__';
export const RESPONSE_PASSTHROUGH_METADATA = '__responsePassthrough__';
export const SSE_METADATA = '__sse__';

View File

@@ -1,9 +1,26 @@
import { ROUTE_ARGS_METADATA } from '../../constants';
import {
RESPONSE_PASSTHROUGH_METADATA,
ROUTE_ARGS_METADATA,
} from '../../constants';
import { RouteParamtypes } from '../../enums/route-paramtypes.enum';
import { PipeTransform } from '../../index';
import { Type } from '../../interfaces';
import { isNil, isString } from '../../utils/shared.utils';
/**
* The `@Response()`/`@Res` parameter decorator options.
*/
export interface ResponseDecoratorOptions {
/**
* Determines whether the response will be sent manually within the route handler,
* with the use of native response handling methods exposed by the platform-specific response object,
* or if it should passthrough Nest response processing pipeline.
*
* @default false
*/
passthrough: boolean;
}
export type ParamData = object | string | number;
export interface RouteParamMetadata {
index: number;
@@ -85,21 +102,35 @@ export const Request: () => ParameterDecorator = createRouteParamDecorator(
*
* Example: `logout(@Response() res)`
*
* @see [Request object](https://docs.nestjs.com/controllers#request-object)
*
* @publicApi
*/
export const Response: () => ParameterDecorator = createRouteParamDecorator(
RouteParamtypes.RESPONSE,
);
export const Response: (
options?: ResponseDecoratorOptions,
) => ParameterDecorator = (options?: ResponseDecoratorOptions) => (
target,
key,
index,
) => {
if (options?.passthrough) {
Reflect.defineMetadata(
RESPONSE_PASSTHROUGH_METADATA,
options?.passthrough,
target.constructor,
key,
);
}
return createRouteParamDecorator(RouteParamtypes.RESPONSE)()(
target,
key,
index,
);
};
/**
* Route handler parameter decorator. Extracts reference to the `Next` function
* from the underlying platform and populates the decorated
* parameter with the value of `Next`.
*
* @see [Request object](https://docs.nestjs.com/controllers#request-object)
*
* @publicApi
*/
export const Next: () => ParameterDecorator = createRouteParamDecorator(

View File

@@ -21,7 +21,7 @@ export interface DynamicModule extends ModuleMetadata {
* in all modules. Thereafter, modules that wish to inject a service exported
* from a global module do not need to import the provider module.
*
* Default: false
* @default false
*/
global?: boolean;
}

View File

@@ -1,5 +1,8 @@
import { ParamData } from '@nestjs/common';
import { PARAMTYPES_METADATA } from '@nestjs/common/constants';
import {
PARAMTYPES_METADATA,
RESPONSE_PASSTHROUGH_METADATA,
} from '@nestjs/common/constants';
import {
ContextType,
Controller,
@@ -38,6 +41,14 @@ export class ContextUtils {
return Reflect.getMetadata(metadataKey, instance.constructor, methodName);
}
public reflectPassthrough(instance: Controller, methodName: string): boolean {
return Reflect.getMetadata(
RESPONSE_PASSTHROUGH_METADATA,
instance.constructor,
methodName,
);
}
public getArgumentsLength<T>(keys: string[], metadata: T): number {
return Math.max(...keys.map(key => metadata[key].index)) + 1;
}

View File

@@ -215,9 +215,10 @@ export class RouterExecutionContext {
);
const paramsMetadata = getParamsMetadata(moduleKey);
const isResponseHandled = paramsMetadata.some(
({ type }) =>
type === RouteParamtypes.RESPONSE || type === RouteParamtypes.NEXT,
const isResponseHandled = this.isResponseHandled(
instance,
methodName,
paramsMetadata,
);
const httpRedirectResponse = this.reflectRedirect(callback);
@@ -444,4 +445,20 @@ export class RouterExecutionContext {
(await this.responseController.apply(result, res, httpStatusCode));
};
}
private isResponseHandled(
instance: Controller,
methodName: string,
paramsMetadata: ParamProperties[],
): boolean {
const hasResponseOrNextDecorator = paramsMetadata.some(
({ type }) =>
type === RouteParamtypes.RESPONSE || type === RouteParamtypes.NEXT,
);
const isPassthroughEnabled = this.contextUtils.reflectPassthrough(
instance,
methodName,
);
return hasResponseOrNextDecorator && !isPassthroughEnabled;
}
}

View File

@@ -8,10 +8,11 @@ import { map } from 'rxjs/operators';
@Controller()
export class AppController {
@Get()
index(@Res() response: Response) {
index(@Res({ passthrough: true }) response: Response) {
response
.type('text/html')
.send(readFileSync(join(__dirname, 'index.html')).toString());
return 'xddd';
}
@Sse('sse')