@nestjs/common
Version:
Decorators, pipes, exceptions and utilities for NestJS applications: @Controller(), @Injectable(), @Body(), ValidationPipe, HttpException and more.
93 lines (92 loc) • 3.87 kB
JavaScript
import { __decorate, __metadata, __param } from "tslib";
import { map } from 'rxjs/operators';
import { Inject, Injectable, Optional } from '../decorators/core/index.js';
import { StreamableFile } from '../file-stream/index.js';
import { loadPackage } from '../utils/load-package.util.js';
import { isStreamingBody } from '../utils/is-streaming-body.util.js';
import { mapStreamingBody } from '../utils/map-streaming-body.util.js';
import { isObject } from '../utils/shared.utils.js';
import { CLASS_SERIALIZER_OPTIONS } from './class-serializer.constants.js';
let classTransformer = {};
// The package of the interceptor created last, which may still be loading.
// "intercept()" resolves it into "classTransformer", so that an interceptor
// created while responses are serialized (e.g. a request-scoped one) doesn't
// put a promise in place of the package they use.
let pendingClassTransformer;
// NOTE (external)
// We need to deduplicate them here due to the circular dependency
// between core and common packages
const REFLECTOR = 'Reflector';
/**
* @publicApi
*/
let ClassSerializerInterceptor = class ClassSerializerInterceptor {
reflector;
defaultOptions;
constructor(reflector, defaultOptions = {}) {
this.reflector = reflector;
this.defaultOptions = defaultOptions;
pendingClassTransformer =
defaultOptions?.transformerPackage ??
loadPackage('class-transformer', 'ClassSerializerInterceptor', () => import('class-transformer'));
if (!(pendingClassTransformer instanceof Promise)) {
classTransformer = pendingClassTransformer;
}
}
async intercept(context, next) {
classTransformer = (await pendingClassTransformer);
const contextOptions = this.getContextOptions(context);
const options = {
...this.defaultOptions,
...contextOptions,
};
// The router sends each object a stream yields as a line of JSON, so these
// are serialized as the stream yields them. Other transports leave what a
// stream yields to whatever consumes it (e.g. a GraphQL subscription).
const isHttp = context.getType?.() === 'http';
return next
.handle()
.pipe(map((res) => isHttp && isStreamingBody(res)
? mapStreamingBody(res, chunk => this.serialize(chunk, options))
: this.serialize(res, options)));
}
/**
* Serializes responses that are non-null objects nor streams.
*/
serialize(response, options) {
if (!isObject(response) ||
response instanceof StreamableFile ||
isStreamingBody(response)) {
return response;
}
return Array.isArray(response)
? response.map(item => this.transformToPlain(item, options))
: this.transformToPlain(response, options);
}
transformToPlain(plainOrClass, options) {
if (!plainOrClass) {
return plainOrClass;
}
if (!options.type) {
return classTransformer.classToPlain(plainOrClass, options);
}
if (plainOrClass instanceof options.type) {
return classTransformer.classToPlain(plainOrClass, options);
}
const instance = classTransformer.plainToInstance(options.type, plainOrClass, options);
return classTransformer.classToPlain(instance, options);
}
getContextOptions(context) {
return this.reflector.getAllAndOverride(CLASS_SERIALIZER_OPTIONS, [
context.getHandler(),
context.getClass(),
]);
}
};
ClassSerializerInterceptor = __decorate([
Injectable(),
__param(0, Inject(REFLECTOR)),
__param(1, Optional()),
__metadata("design:paramtypes", [Object, Object])
], ClassSerializerInterceptor);
export { ClassSerializerInterceptor };