typescript - IntersectionType fungerer ikke med class-transformer-dekoratører _når_ det pakkes via esbuild, selv når esbuild-plugins brukes
Så dette spørsmålet er relatert til at IntersectionType ikke fungerer med class-transformer. Løsningen der var å oppgradere fra nestjs 7 til 8 (som effektivt oppgraderer @nestjs/swagger fra 4.5.12-next.1 til 5.1.2). Men dessverre, det er ikke slutten på historien. Det jeg ser er at dekoratørene fra class-transformer ikke arves av IntersectionType når det pakkes med esbuild (selv med anbefalte plugins). Men class-validate-dekoratørene arves som forventet.
For å reprodusere, klon https://github.com/adrian-grajdeanu/nestjs-issue0 Neste inkluderer jeg og diskuterer kode fra ovennevnte repo.
Her er et demokode:
import "reflect-metadata"
import { ValidationError, BadRequestException, ValidationPipe } from '@nestjs/common';
import { Transform, Type } from "class-transformer";
import { IsBoolean, IsInt, IsString, Max, MaxLength, Min } from "class-validator";
import { IntersectionType } from '@nestjs/mapped-types';
const optionalBooleanMapper = new Map([
['1', true],
['0', false],
]);
function parseOptionalBoolean({ value }: any) {
console.log(`In parseOptionalBoolean: parsing ${value} (${typeof value})`);
const bValue = optionalBooleanMapper.get(value.toLowerCase());
console.log(`In parseOptionalBoolean: parsed ${bValue} (${typeof bValue})`);
return bValue === undefined ? value : bValue;
}
export class Props {
@IsInt()
@Type(() => {
console.log('In @Type');
return Number;
})
@Min(1)
@Max(1024)
count?: number;
@IsBoolean()
@Transform(parseOptionalBoolean)
flag?: boolean;
}
export class Category {
@IsString()
@MaxLength(1024)
category!: String;
}
class Frankenstein extends IntersectionType(Category, Props) {}
const pipe = new ValidationPipe({
transform: true,
whitelist: true,
forbidNonWhitelisted: true,
exceptionFactory: (errors: ValidationError[]) => new BadRequestException({
message: 'Validation failed',
payload: errors.map(error => Object.values(error.constraints || {})).reduce((prev, curr) => [...prev, ...curr], []),
}),
});
async function validate(arg: any) {
try {
const obj = await pipe.transform(arg, {
type: 'query',
metatype: Frankenstein,
});
console.log(obj);
} catch (e) {
console.log(e);
}
}
validate({
count: '10',
category: 'foo',
flag: '0',
});
Når man kjører det ovenstående via ts-node, går alt fint:
pnpx ts-node cc.ts
In @Type
In parseOptionalBoolean: parsing 0 (string)
In parseOptionalBoolean: parsed false (boolean)
Frankenstein { count: 10, category: 'foo', flag: false }
$
Merk at kode inne i @Type og @Transform kjøres, og validering/transformasjon lykkes.
Men pakk denne cc.ts med esbuild. Her er pakkeskriptet:
import { build } from 'esbuild';
import { esbuildDecorators } from '@anatine/esbuild-decorators';
build({
"plugins": [
esbuildDecorators({
tsconfig: './tsconfig.json',
cwd: process.cwd(),
})
],
"entryPoints": [
"cc.ts"
],
"external": [
"@nestjs/microservices",
"@nestjs/websockets",
"class-transformer/storage"
],
"minify": true,
"bundle": true,
"target": "node14",
"platform": "node",
"mainFields": [
"module",
"main"
],
"outfile": "c.js"
})
.catch((e) => {
console.error('Failed to bundle');
console.error(e);
process.exit(1);
});
Kjør dette
pnpx ts-node bundle.ts
$
og det produserer c.js-pakkefilen. Hvis jeg kjører denne
pnpx node c.js
uA: Validation failed
at d2.exceptionFactory (.../c.js:17:199649)
at d2.transform (.../c.js:17:177741)
at async M1e (.../c.js:17:199822) {
response: {
message: 'Validation failed',
payload: [
'count must not be greater than 1024',
'count must not be less than 1',
'count must be an integer number',
'flag must be a boolean value'
]
},
status: 400
}
$
Merk at koden i @Type og @Transform-dekoratører ikke kjøres, og valideringen mislykkes. Klassen transformeres ikke i det hele tatt (jeg plasserte en shim foran plainToClass-funksjonen fra class-transform og inspiserte før og etter). Men valideringen mislykkes, fordi class-validate-dekoratørene arves av IntersectionType.
Det er et spørsmål om IntersectionType! Jeg har prøvd uten det, og alt fungerer som forventet. Jeg vet at det er problemer med esbuild og dekoratører, og jeg har spesielt brukt esbuildDecorators-pluginen fra @anatine/esbuild-decorators.
Her er min tsconfig.json
{
"compilerOptions": {
"target": "ES2018",
"module": "commonjs",
"lib": [
"es2018"
],
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedParameters": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"typeRoots": [
"./node_modules/@types"
],
"moduleResolution": "node",
"esModuleInterop": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"exclude": [
"node_modules"
],
"include": ["bin/**/*.ts", "lib/**/*.ts"]
}
og package.json
{
"name": "main",
"version": "0.1.0",
"bin": {
"main": "bin/main.js"
},
"devDependencies": {
"@anatine/esbuild-decorators": "^0.2.18",
"@types/node": "10.17.27",
"esbuild": "^0.14.21",
"ts-node": "^9.1.1",
"typescript": "^4.5.5"
},
"dependencies": {
"@nestjs/common": "^8.2.6",
"@nestjs/core": "^8.2.6",
"@nestjs/mapped-types": "^1.0.1",
"@nestjs/platform-express": "^8.2.6",
"@nestjs/swagger": "^5.2.0",
"ajv": "^8.8.2",
"cache-manager": "^3.4.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.13.2",
"express": "^4.17.2",
"reflect-metadata": "^0.1.13",
"regenerator-runtime": "^0.13.9",
"rxjs": "^7.5.2",
"source-map-support": "^0.5.21",
"swagger-ui-express": "^4.3.0"
}
}
Går jeg glipp av noe, eller er dette endelig en blindvei for denne tilnærmingen jeg tok i prosjektet?