nestjs-typebox
Version:
This library provides helper utilities for writing and validating NestJS APIs using [TypeBox](https://github.com/sinclairzx81/typebox) as an alternative to class-validator/class-transformer. It also includes a patch for @nestjs/swagger allowing OpenAPI ge
223 lines (211 loc) • 9.08 kB
JavaScript
;
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: 'Module' } });
const typebox = require('@sinclair/typebox');
const compiler = require('@sinclair/typebox/compiler');
const common = require('@nestjs/common');
const routeParamtypes_enum = require('@nestjs/common/enums/route-paramtypes.enum');
const constants = require('@nestjs/common/constants');
const constants$1 = require('@nestjs/swagger/dist/constants');
const format = require('@sinclair/typebox/format');
const core = require('@nestjs/core');
const operators = require('rxjs/operators');
const schemaObjectFactory = require('@nestjs/swagger/dist/services/schema-object-factory');
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
function isTypeboxDto(metatype) {
return typeof metatype === 'function' && metatype?.isTypeboxDto;
}
const tryCoerceToNumber = (val)=>{
switch(typeof val){
case 'number':
return val;
case 'boolean':
return val === true ? 1 : 0;
case 'string':
{
const v = Number(val);
if (Number.isFinite(v)) {
return v;
}
break;
}
case 'object':
{
if (val === null) return 0;
break;
}
}
return val;
};
class TypeboxValidationException extends common.BadRequestException {
constructor(errors){
super({
statusCode: common.HttpStatus.BAD_REQUEST,
message: 'Validation failed',
errors: [
...errors
]
});
}
}
class TypeboxModel {
}
const createTypeboxDto = (schema, options)=>{
let AugmentedTypeboxDto = class AugmentedTypeboxDto extends TypeboxModel {
static isTypeboxDto = true;
static schema = schema;
static options = options;
static toJsonSchema() {
return typebox.Type.Strict(this.schema);
}
static beforeValidate(data) {
const result = this.options?.stripUnknownProps ? {} : data;
if (this.options?.coerceTypes || this.options?.stripUnknownProps) {
const schema = this.toJsonSchema();
for (const [prop, def] of Object.entries(schema.properties)){
if (data[prop] === undefined) continue;
switch(def.type){
case 'number':
result[prop] = tryCoerceToNumber(data[prop]);
break;
default:
result[prop] = data[prop];
}
}
}
return result;
}
static validate(data) {
if (!this.validator) {
this.validator = compiler.TypeCompiler.Compile(this.schema);
}
if (!this.validator.Check(data)) {
throw new TypeboxValidationException(this.validator.Errors(data));
}
}
static transform(data) {
return this.options?.transform?.(data) ?? data;
}
constructor(data){
super();
this.data = data;
}
};
return AugmentedTypeboxDto;
};
const Params = ()=>{
return (target, key, index)=>{
const args = Reflect.getMetadata(constants.ROUTE_ARGS_METADATA, target.constructor, key) || {};
const [type] = Reflect.getMetadata('design:paramtypes', target, key);
if (isTypeboxDto(type)) {
const objSchema = type.toJsonSchema();
if (objSchema.type === 'object') {
const parameters = Object.entries(objSchema.properties).map(([name, { description , examples , ...schema }])=>({
in: 'path',
name,
description,
examples,
schema,
required: objSchema.required?.includes(name)
}));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Reflect.defineMetadata(constants$1.DECORATORS.API_PARAMETERS, parameters, target[key]);
}
}
Reflect.defineMetadata(constants.ROUTE_ARGS_METADATA, common.assignMetadata(args, routeParamtypes_enum.RouteParamtypes.PARAM, index), target.constructor, key);
};
};
const emailRegex = /.+\@.+\..+/;
const emailFormat = (value)=>value.match(emailRegex) !== null;
const applyFormats = ()=>{
format.Format.Set('email', emailFormat);
};
var __decorate$1 = globalThis && globalThis.__decorate || function(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = globalThis && globalThis.__metadata || function(k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
exports.TypeboxTransformInterceptor = class TypeboxTransformInterceptor {
constructor(reflector){
this.reflector = reflector;
}
intercept(context, next) {
return next.handle().pipe(operators.map((data)=>{
const responseMeta = this.reflector.get(constants$1.DECORATORS.API_RESPONSE, context.getHandler());
const responseType = (responseMeta['200'] || responseMeta['201'] || {})['type'];
if (!responseType) return data;
const dataArray = Array.isArray(data) ? data : [
data
];
return dataArray.map((dataOrModel)=>{
const data = dataOrModel instanceof TypeboxModel ? dataOrModel.data : dataOrModel;
if (responseType.validate) {
responseType.validate(data);
}
return responseType.transform ? responseType.transform(data) : data;
});
}));
}
};
exports.TypeboxTransformInterceptor = __decorate$1([
common.Injectable(),
__metadata("design:type", Function),
__metadata("design:paramtypes", [
typeof core.Reflector === "undefined" ? Object : core.Reflector
])
], exports.TypeboxTransformInterceptor);
var __decorate = globalThis && globalThis.__decorate || function(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
exports.TypeboxValidationPipe = class TypeboxValidationPipe {
transform(value, metadata) {
const { metatype } = metadata;
if (!isTypeboxDto(metatype)) {
return value;
}
metatype.validate(metatype.beforeValidate(value));
return value;
}
};
exports.TypeboxValidationPipe = __decorate([
common.Injectable()
], exports.TypeboxValidationPipe);
function patchNestJsSwagger() {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if (schemaObjectFactory.SchemaObjectFactory.prototype.__primatePatched) return;
const defaultExplore = schemaObjectFactory.SchemaObjectFactory.prototype.exploreModelSchema;
const extendedExplore = function exploreModelSchema(type, schemas, schemaRefsStack) {
if (this['isLazyTypeFunc'](type)) {
const factory = type;
type = factory();
}
if (!isTypeboxDto(type)) {
return defaultExplore.apply(this, [
type,
schemas,
schemaRefsStack
]);
}
schemas[type.name] = type.toJsonSchema();
return type.name;
};
schemaObjectFactory.SchemaObjectFactory.prototype.exploreModelSchema = extendedExplore;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
schemaObjectFactory.SchemaObjectFactory.prototype.__primatePatched = true;
}
exports.Params = Params;
exports.TypeboxModel = TypeboxModel;
exports.TypeboxValidationException = TypeboxValidationException;
exports.applyFormats = applyFormats;
exports.createTypeboxDto = createTypeboxDto;
exports.emailFormat = emailFormat;
exports.isTypeboxDto = isTypeboxDto;
exports.patchNestJsSwagger = patchNestJsSwagger;
exports.tryCoerceToNumber = tryCoerceToNumber;
//# sourceMappingURL=nestjs-typebox.cjs.js.map