Pluggable Validation
zod, valibot, or arktype from one neutral schema IR.
nestjs-codegen translates your @Body()/@Query() DTOs (class-validator decorators)
and defineContract schemas into a neutral SchemaNode IR, then renders that IR
with a validation adapter. zod, valibot, and arktype each ship as their own adapter
package (none is bundled in core). Adding a new library is one render function away (the interface is shaped
around Standard Schema).
Choosing an adapter
import { defineConfig } from '@dudousxd/nestjs-codegen';
import { zodAdapter } from '@dudousxd/nestjs-codegen-zod';
export default defineConfig({
validation: zodAdapter,
});import { defineConfig } from '@dudousxd/nestjs-codegen';
import { valibotAdapter } from '@dudousxd/nestjs-codegen-valibot';
export default defineConfig({
validation: valibotAdapter,
});import { defineConfig } from '@dudousxd/nestjs-codegen';
import { arktypeAdapter } from '@dudousxd/nestjs-codegen-arktype';
export default defineConfig({
validation: arktypeAdapter,
});What gets generated
Given this DTO:
class CreateUserDto {
@IsEmail() email!: string;
@MinLength(8) password!: string;
}each adapter renders forms.ts in its own idiom:
import { z } from 'zod';
export const CreateBodySchema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
export type CreateBody = z.infer<typeof CreateBodySchema>;import * as v from 'valibot';
export const CreateBodySchema = v.object({
email: v.pipe(v.string(), v.email()),
password: v.pipe(v.string(), v.minLength(8)),
});
export type CreateBody = v.InferOutput<typeof CreateBodySchema>;import { type } from 'arktype';
export const CreateBodySchema = type({
email: 'string.email',
password: 'string >= 8',
});
export type CreateBody = (typeof CreateBodySchema).infer;Decorator coverage
The class-validator → IR translator understands the common decorators: @IsString,
@IsNumber/@IsInt, @IsBoolean, @IsDate, @IsEmail/@IsUrl/@IsUUID,
@Matches, @MinLength/@MaxLength/@Length, @Min/@Max, @IsPositive/
@IsNegative, @IsEnum/@IsIn, @IsOptional/@IsDefined, @IsNotEmpty,
@ValidateNested + @Type(() => X) (nested + arrays), @IsObject, @Allow.
Unmappable decorators are kept as a comment with a warning — the schema still compiles.
defineContract schemas are hand-written zod. They render verbatim under the zod
adapter; under valibot/arktype they're skipped with a warning (use class-validator
DTOs for cross-adapter forms, or the zod adapter for contracts).
Writing your own adapter
A ValidationAdapter renders the IR to source text:
import type { ValidationAdapter } from '@dudousxd/nestjs-codegen';
export const myAdapter: ValidationAdapter = {
name: 'my-lib',
importStatements: ({ used }) => (used ? ["import * as m from 'my-lib';"] : []),
render(node, ctx) { /* SchemaNode → string */ return '…'; },
renderModule(mod) { /* root + hoisted named schemas */ return { schemaText: '…', namedNestedSchemas: new Map(), warnings: [] }; },
inferType: (constName) => `m.Infer<typeof ${constName}>`,
};Pass the instance to validation and you're done.