UNPKG

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
'use strict'; 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