Giter VIP home page Giter VIP logo

nestjs-zod's People

Contributors

alfredringstad avatar austinwoon avatar daleseo avatar drago96 avatar effervescentia avatar eyalch avatar kevinedry avatar nicolabovolato avatar nmokkenstorm avatar therosbif avatar vargasd avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

nestjs-zod's Issues

Support extraction of inferred types โ€“ z.infer<typeof Schema>

Please support the extraction of inferred types from schemas.

Original Zod supports the extraction of TypeScript types, which is very handy:

import { z } from "zod";

const User = z.object({
  username: z.string(),
});

User.parse({ username: "Ludwig" });

// extract the inferred type
type User = z.infer<typeof User>;
// { username: string }

See: https://github.com/colinhacks/zod#basic-usage

In my current project I had to import original Zod for that use case only. I don't like that, because I'm afraid, that nestjs-zod's createZodDto on schemas created with original Zod may fail in the future due to compatibility issues. And it's also confusing for developers to have two variants of Zod in the same project.

`createZodDto` doesnt generate openapi spec for param

I've tried to create dto for path param using createZodDto but it gives no openapi description for those class.
In example below I've showed one example without zod(it works fine) and extended by createZodDto class.

Here is reproduction example:

import { Controller, Get, Param } from '@nestjs/common'
import { ApiProperty } from '@nestjs/swagger'
import { createZodDto } from 'nestjs-zod'
import { z } from 'nestjs-zod/z'

class SingleIdDto {
  @ApiProperty({ type: () => Number })
  id: number
}

export class SingleIdDto2 extends createZodDto(
  z.object({
    id: z.number().int().positive()
  })
) {}

@Controller('samples')
export class SampleController {
  @Get('/:id')
  async getHello(@Param() params: SingleIdDto2): Promise<any> {
    console.log({ params })

    return {}
  }
}

image

Error with Exported Variable and Type Inference

Descripttion

I encountered an issue that involves TypeScript type inference and named exports. The problem arises when attempting to export a variable created via zodToOpenAPI, which leads to an error related to the ExtendedSchemaObject

Steps to Reproduce:

import { createZodDto, zodToOpenAPI } from 'nestjs-zod';
import { z } from 'nestjs-zod/z';

export const EnvConfigSchema = z.object({
  PORT: z.coerce.number().int().positive().describe('Port number'),
});

export const EnvConfigApi = zodToOpenAPI(EnvConfigSchema);

Expected Behavior:

Typescript intellisense should not scream.

Actual Behavior:

Exported variable EnvConfigApi has or is using name ExtendedSchemaObject from external module "node_modules/.pnpm/nestjs-zod@2.3.3_@nestjs+common@10.0.0_@nestjs+core@10.0.0_@nestjs[email protected][email protected]/node_modules/nestjs-zod/dist/index" but cannot be named.

Proposed Solution:

The interface ExtendedSchemaObject should be included in the module's exports.
This resolves the issue.

Could `zod` be a peer dependency?

I ask because our use of zod is such that we have schemas defined in a shared package used among several applications. When combining them, we get type errors whenever the zod types change between versions. As of this writing, the version of zod used in this library is 3.14.3 while the latest is 3.20.2, and there are a few type incompatibilities we have already encountered.

I am also wondering whether or not this project is actively maintained and are there plans to continue maintaining it?

Thanks!

[BUG] ZodValidationPipe not validating ZodPassword

import { createZodDto } from 'nestjs-zod'
import { z } from 'nestjs-zod/z'

const LoginSchema = z.object({
  email: z.string().email().nonempty(),
  password: z.password()
    .atLeastOne('digit')
    .atLeastOne('lowercase')
    .atLeastOne('special')
    .atLeastOne('uppercase')
    .min(6),
})

export class LoginDto extends createZodDto(LoginSchema) {}

The only thing that gets validated is if I don't pass a password, or if the length is less than 6.
All the other validations like atLeastOne('special') doesn't run.

Environment

@nestjs/core: 10.1.2
@nestjs-zod: 1.2.3
typescript: 5.1.6

merge-deep vulnerability

HI, I tried to use the library and I get a vulnerability error in the package that it has as a dependency merge-deep 3.0.3

TypeError: Cannot read properties of undefined (reading 'isLazyTypeFunc')

Getting TypeError: Cannot read properties of undefined (reading 'isLazyTypeFunc') when I use patchNestJsSwagger and I try to apply a ApiBadRequestResponse decorator from @nestjs/swagger. Is there any way to extend the OpenAPI object through @nestjs/swagger decorators when using patchNestJsSwagger?

Support for async refinements

When using a .refine that returns a Promise I get the following error from Zod:

Async refinement encountered during synchronous parse operation. Use .parseAsync instead.

I assume nestjs-zod uses .parse under the hood. Is there a way to tell nestjs-zod to use .parseAsync instead?

Cannot read properties of undefined - isLazyTypeFunc

After looking through the issues I saw that this was already reported two times, which should be fixed with the MR #18, but seems still an Issue here.

My only dto:

import { PartialType } from '@nestjs/swagger';
import { createZodDto } from 'nestjs-zod';
import { z } from 'nestjs-zod/z';

const CreateLinkSchema = z.object({
  url: z.string().url(),
  slug: z.string().min(1).max(15),
});

export class CreateLinkDto extends createZodDto(CreateLinkSchema) {}
export class UpdateLinkDto extends PartialType(CreateLinkDto) {}

main.ts

import { Logger } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { patchNestJsSwagger } from 'nestjs-zod';
import { AppModule } from './app.module';
import env from './env';

async function bootstrap() {
  const logger = new Logger('Main');
  const app = await NestFactory.create(AppModule);

  if (env.devMode) {
    patchNestJsSwagger();
    const documentBuilder = new DocumentBuilder()
      .setTitle('URL Shortener backend')
      .setVersion('1.0')
      .build();
    const document = SwaggerModule.createDocument(app, documentBuilder);
    SwaggerModule.setup('docs', app, document);
  }

  logger.log(`App listening on port ${env.port}`);
  await app.listen(env.port);
}
bootstrap();

Throws this exception:

path/backend/node_modules/.pnpm/@[email protected]_bgmtsjzu2jyijhogwtog4e3a6e/node_modules/@nestjs/swagger/dist/services/schema-object-factory.js:89
        if (this.isLazyTypeFunc(type)) {
                 ^
TypeError: Cannot read properties of undefined (reading 'isLazyTypeFunc')
    at exploreModelSchema (path/backend/node_modules/.pnpm/@[email protected]_bgmtsjzu2jyijhogwtog4e3a6e/node_modules/@nestjs/swagger/dist/services/schema-object-factory.js:89:18)
    at SchemaObjectFactory.e.exploreModelSchema (path/backend/node_modules/.pnpm/[email protected]_kwb2a4f7bk4iyyo5b2u32jtvxm/node_modules/nestjs-zod/dist/index.js:1:13958)
    at path/backend/node_modules/.pnpm/@[email protected]_bgmtsjzu2jyijhogwtog4e3a6e/node_modules/@nestjs/swagger/dist/services/schema-object-factory.js:33:36
    at Array.map (<anonymous>)
    at SchemaObjectFactory.createFromModel (path/backend/node_modules/.pnpm/@[email protected]_bgmtsjzu2jyijhogwtog4e3a6e/node_modules/@nestjs/swagger/dist/services/schema-object-factory.js:20:45)
    at exploreApiParametersMetadata (path/backend/node_modules/.pnpm/@[email protected]_bgmtsjzu2jyijhogwtog4e3a6e/node_modules/@nestjs/swagger/dist/explorers/api-parameters.explorer.js:35:55)
    at path/backend/node_modules/.pnpm/@[email protected]_bgmtsjzu2jyijhogwtog4e3a6e/node_modules/@nestjs/swagger/dist/swagger-explorer.js:72:45
    at Array.reduce (<anonymous>)
    at path/backend/node_modules/.pnpm/@[email protected]_bgmtsjzu2jyijhogwtog4e3a6e/node_modules/@nestjs/swagger/dist/swagger-explorer.js:71:104
    at path/backend/node_modules/.pnpm/[email protected]/node_modules/lodash/lodash.js:13469:38

package.json - reduced to minimum

  "dependencies": {
    "@nestjs/common": "^9.0.0",
    "@nestjs/core": "^9.0.0",
    "@nestjs/platform-express": "^9.0.0",
    "nestjs-zod": "^1.2.3",
    "zod": "^3.21.4"
  },
  "devDependencies": {
    "@nestjs/cli": "^9.0.0",
    "@nestjs/schematics": "^9.0.0",
    "@nestjs/swagger": "^6.2.1",
    "@nestjs/testing": "^9.0.0",
    "tsconfig-paths": "4.1.0",
    "typescript": "^4.7.4"
  }

TypeError: Cannot read properties of undefined (reading 'isLazyTypeFunc')

Hey I'm pretty stoked about the direction that this library is headed so I thought I'd pop it into our application tonight and see if would possibly give me a path to move away from Class-Transformer and Class-Validator on the backend.

We already have a fair number of endpoints and functionality in the app that heavily uses all the @nestjs/swagger functionality so that we can generate a type safe client to talk to the API from our frontend.

My versions are:
@nestjs/swagger 5.2.1
@nestjs/{common/core} 8.4.7

I'm invoking patchNestJsSwagger(); near the top of my main.ts file which seems to be what the doc suggests.

When I boot up the app I get the following stack trace:

/home/jesse/code/work/circadian-risk/project-ember-web/node_modules/@nestjs/swagger/dist/services/schema-object-factory.js:89
        if (this.isLazyTypeFunc(type)) {
                 ^
TypeError: Cannot read properties of undefined (reading 'isLazyTypeFunc')
    at exploreModelSchema (/home/jesse/code/work/circadian-risk/project-ember-web/node_modules/@nestjs/swagger/dist/services/schema-object-factory.js:89:18)
    at SchemaObjectFactory.e.exploreModelSchema (/home/jesse/code/work/circadian-risk/project-ember-web/node_modules/nestjs-zod/dist/index.js:1:13822)
    at /home/jesse/code/work/circadian-risk/project-ember-web/node_modules/@nestjs/swagger/dist/services/schema-object-factory.js:33:36
    at Array.map (<anonymous>)
    at SchemaObjectFactory.createFromModel (/home/jesse/code/work/circadian-risk/project-ember-web/node_modules/@nestjs/swagger/dist/services/schema-object-factory.js:20:45)
    at exploreApiParametersMetadata (/home/jesse/code/work/circadian-risk/project-ember-web/node_modules/@nestjs/swagger/dist/explorers/api-parameters.explorer.js:33:55)
    at /home/jesse/code/work/circadian-risk/project-ember-web/node_modules/@nestjs/swagger/dist/swagger-explorer.js:72:45
    at Array.reduce (<anonymous>)
    at /home/jesse/code/work/circadian-risk/project-ember-web/node_modules/@nestjs/swagger/dist/swagger-explorer.js:71:99
    at /home/jesse/code/work/circadian-risk/project-ember-web/node_modules/lodash/lodash.js:13469:38
    at /home/jesse/code/work/circadian-risk/project-ember-web/node_modules/lodash/lodash.js:4967:15
    at baseForOwn (/home/jesse/code/work/circadian-risk/project-ember-web/node_modules/lodash/lodash.js:3032:24)
    at Function.mapValues (/home/jesse/code/work/circadian-risk/project-ember-web/node_modules/lodash/lodash.js:13468:7)
    at MapIterator.iteratee (/home/jesse/code/work/circadian-risk/project-ember-web/node_modules/@nestjs/swagger/dist/swagger-explorer.js:71:45)
    at MapIterator.next (/home/jesse/code/work/circadian-risk/project-ember-web/node_modules/iterare/src/map.ts:9:39)
    at FilterIterator.next (/home/jesse/code/work/circadian-risk/project-ember-web/node_modules/iterare/src/filter.ts:11:34)

Curious if you have any top of mind thoughts for whats going on here if not I can try and make a minimal repro and see if the same issue can be shown outside of my work project

ZodSerializerDto isn't used for OpenAPI

It looks like the @ZodSerializerDto attribute isn't being used when generating the OpenAPI spec.

I have a method on a controller that looks like

  @Get('foo')
  @ZodSerializerDto(TestSchemaDto)
  test() {
    return { id: '3' }
  }

However, when I generate the OpenAPI document, I get

"/api_keys/foo": {
  "get": {
    "operationId": "ApiKeyController_test",
    "parameters": [],
    "responses": {
      "200": {
        "description": ""
      }
    }
  }
},

Explicitly adding the return type to the method does write out the info to the swagger file

 "/api_keys/foo": {
      "get": {
        "operationId": "ApiKeyController_test",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TestSchemaDto"
                }
              }
            }
          }
        }
      }
    },

And just for completeness, here is my zod dto

export const TestSchema = z.object({
  id: z.string(),
})
export class TestSchemaDto extends createZodDto(TestSchema) {}

Date is not shown in Swagger doc.

The date fields createdAt and updatedAt were not shown in the Swagger doc.

export const WorkSchema = z.object({
  id: z.string().uuid(),
  title: z.string(),
  createdAt: z.coerce.date(),
  updatedAt: z.coerce.date(),
})

The reason was the version of swagger-ui-express.

Updating the module fixed the issue.

-    "swagger-ui-express": "^4.1.4",
+   "swagger-ui-express": "^4.6.3",

NestJS Interceptor receives a Promise instead of resolved data

I am using ts-rest in a NestJS application and I've encountered an issue with interceptors. I have an interceptor that is supposed to modify the response before it is sent. However, when I try to access the response data in the interceptor, I get a Promise instead of the resolved data.

Here is a simplified version of my interceptor:

@Injectable()
export class MyInterceptor implements NestInterceptor {
	intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
		return next.handle().pipe(
			tap((data) => {
				console.log("data", data);
				// Modify the response...
			}),
		);
	}
}

I use the interceptor in a controller method like this (using the single handler approach):


@TsRestHandler(c.login)
@UseInterceptors(AuthDataInterceptor)
@UseGuards(EmailPasswordAuthGuard)
async login(@AuthDataFromGuard() authData: AuthData) {
	return tsRestHandler(c.login, async () => {
                // ...
		return {
		  status: 201, 
		  body: authData,
		}
	});
}

When I log the tapped data in the interceptor, I get a Promise, not the resolved data. This makes it impossible for me to modify the response as intended. I would expect the interceptor to receive the resolved data, not a Promise.

Is this a known issue, or am I doing something wrong? Any help would be appreciated.

TypeScript support broken when you use `moduleResolution "node16"`

If a user has moduleResolution set to node16 (or nodenext, probably) they will get this error when importing nestjs-zod/z:

src/example.ts:1:19 - error TS7016: Could not find a declaration file for module 'nestjs-zod/z'. '/Users/jonah/example/node_modules/nestjs-zod/dist/z.js' implicitly has an 'any' type.
  If the 'nestjs-zod' package actually exposes this module, try adding a new declaration (.d.ts) file containing `declare module 'nestjs-zod/z';`

1 import { z } from 'nestjs-zod/z';

I believe this is because the package.json does not specify types fields for any of the conditional exports. Because we don't explicitly say where to look for declarations, tsc checks the adjacent file (ex. dist/z.js -> dist/z.d.ts), but this file doesn't exist, causing the error.

ZodResponseDto not defined

Hi everybody,

I was trying to use @ZodResponseDto as a validation for the response of my endpoints but it seems that this is not present in nestjs-zod.
image

How can I implement a validation of the response from an endpoint?

Thanks in advance!

Creating a DTO of a schema with union doesn't work

Hey @risenforces!

My schema includes the usage of z.union. Creating the DTO using createZodDto works fine, but creating a class that extends the result of the call to createZodDto doesn't work.

import { createZodDto } from "nestjs-zod"
import { z } from "nestjs-zod/z"

const schema = z.union([z.number(), z.string()])

const dto = createZodDto(schema)

class Dto extends dto {}

The code above produces the following error:

src/test.dto.ts:8:19 - error TS2509: Base constructor return type 'string | number' is not an object type or intersection of object types with statically known members.

27 class Dto extends dto {}
                     ~~~

After playing a bit with the library's code, I found that changing the signature of new in ZodDto to return this suddenly works. The following code contains my own implementations of ZodDto and createZodDto.

import { ZodSchema, ZodTypeDef, z } from "nestjs-zod/z"

export interface ZodDto<
  TOutput = any,
  TDef extends ZodTypeDef = ZodTypeDef,
  TInput = TOutput
> {
  new (): this

  isZodDto: true
  schema: ZodSchema<TOutput, TDef, TInput>

  create(input: unknown): TOutput
}

export function createZodDto<
  TOutput = any,
  TDef extends ZodTypeDef = ZodTypeDef,
  TInput = TOutput
>(schema: ZodSchema<TOutput, TDef, TInput>) {
  class AugmentedZodDto {
    public static isZodDto = true
    public static schema = schema

    public static create(input: unknown) {
      return this.schema.parse(input)
    }
  }

  return AugmentedZodDto as unknown as ZodDto<TOutput, TDef, TInput>
}

const schema = z.union([z.number(), z.string()])

const dto = createZodDto(schema)

class Dto extends dto {}

The change is basically from this:

-  new (): TOutput;
+  new (): this

Unless I'm missing something, it doesn't make sense to me not to return this from the constructor.

This also makes me consider whether we even need the ZodDto interface. Couldn't we replace it with the (currently dynamic) AugmentedZodDto class?

Error accessing z properties

Hello,

I'm trying to reproduce the first steps but i'm getting this error. Could someone help me overpass it?

Property 'string' does not exist on type 'typeof import("service/node_modules/nestjs-zod/dist/z-only-override")'.
You're trying to access object on an object that doesn't contain it.

Screenshot 2023-04-17 at 11 36 13

  << package.json >>
  "dependencies": {
    "@nestjs/common": "^9.3.12",
    "@nestjs/core": "^9.3.12",
    "@nestjs/swagger": "^6.2.1",
    "nestjs-zod": "^2.2.0",
  },
  "devDependencies": {
    "typescript": "^4.3.5"
  },

node v18.15.0

Export the `validate` function

We have a guard with the following code:

const result = z
  .strictObject({ id: z.number() })
  .safeParse(request.params)

if (!result.success) {
  throw new ZodValidationException(result.error)
}

const id = result.data

This logic is basically what the validate function does. We would gladly use this function instead, but it seems to not be exported.

Is it possible to export the validate function?

`DOCS`: Recommended migration pathway from class-transformer

Greetings! I recently discovered Zod as a way to validate JSON schemas internally, and I absolutely love the project. I have a fairly extensive API, and would love to migrate the DTOs to Zod as well so that I can have a single source of truth for all of the data types, OpenAPI schema, and validation. With class validator, the reliance on decorators for validation and schema generation means that there is no guarantee that the type annotations match the decorators.

Is there a recommended migration pathway that doesn't require removing the useGlobalPipes for class transformer? If so, it would certainly be worth pointing out in the library documentation IMO.

Unable to create Dto from `z.discriminatedUnion`

I'm trying to create a dto from a zod discriminated union like this:

const BaseSchema = z.object({
  names: z.string(),
  last_name: z.string(),
  document_id: z.string(),
  document_type: z.nativeEnum(DocumentType),
  nationality: z.string(),
  email: z.string().email(),
});

const EmployeeSchema = BaseSchema.extend({
  user_type: z.literal(UserType.EMPLOYEE), // UserType.EMPLOYEE === "EMPLOYEE"
});

const SellerSchema = BaseSchema.extend({
  user_type: z.literal(UserType.SELLER), // UserType.SELLER === "SELLER"
  code: z.number(),
});

const CreateUserBodySchema = z.discriminatedUnion('user_type', [
  EmployeeSchema,
  SellerSchema,
]);

export class CreateUserBodyDto extends createZodDto(CreateUserBodySchema) {}

createZodDto(CreateUserBodySchema) yields the following error: TS2509

Base constructor return type '{ names: string; last_name: string; document_id: string; document_type: "PASSPORT" | "RUT"; nationality: string; email: string; user_type: "HHRR"; } | { names: string; last_name: string; document_id: string; ... 4 more ...; user_type: "SELLER"; }' is not an object type or intersection of object types with statically known members.

nestjs-zod version: 2.2.0
typescript version: 4.9.5

Dto is not working as expected

First, it shows all the errors correctly like below.

{
  "statusCode": 400,
  "message": "Validation failed",
  "errors": [
    {
      "code": "invalid_type",
      "expected": "string",
      "received": "undefined",
      "path": [
        "title"
      ],
      "message": "Required"
    },
   ...
  ]
}

however, when all the requirements are full-filled it shows the following error. I am using DTO and don't get any clue.

{
  "statusCode": 400,
  "message": [
    "property title should not exist",
    "property currency should not exist",
    "property logo should not exist",
    "property road should not exist",
    "property propertyNumber should not exist",
    "property zip should not exist",
    "property city should not exist",
    "property country should not exist"
  ],
  "error": "Bad Request"
}

Dto:

import { createZodDto } from 'nestjs-zod';

export class CreateCompanyDto extends createZodDto(CreateCompanyInfoFormSchemaBE) {}

this is the controller.

image

Versions:

Nest JS version: 9.2.1
nestjs-zod: 2.2.3

All the type info gone !!

Dto by following docs

import { createZodDto } from 'nestjs-zod';
import { z } from 'nestjs-zod/z';

const CreateFeedSchema = z.object({
  title: z.string(),
  body: z.string(),
  // attachment: z.
});

export class CreateFeedDto extends createZodDto(CreateFeedSchema) {}

in the controller

  @Post()
  create(@Body() createFeedDto: CreateFeedDto) {
    const d = createFeedDto. // << Intellisense gone! no suggestion
    return this.feedsService.create(createFeedDto);
  }

Dtos not instance of the dto class but plain jsons might cause runtime errors uncaught by TS

Hi,

When working with class-validator in nestjs, a very common problem I saw in several projects was that the DTOs weren't actually turned into instances of the specified dto class. For example

@Post()
async create(@Body() dto: CreateMarketDto) {}

Without setting {transform: true} in the ValidationPipeOptions (documented here), dto was just a plain javascript object at runtime. At compile time, however, this looks like dto instanceof CreateMarketDto should return true, meaning also that any methods on the DTO will not give me compile errors.

nestjs-zod seems to suffer from the same problem. What am I missing or what's the best way to avoid this issue (besides the obvious 'rule' in the team to not put methods on Dto classes)? Is there a way to make dto into actual instances of CreateMarketDto?

Here's a minimal example that shows that TS does not show compiler errors but runtime errors.

export class CreateMarketDto extends createZodDto(
  z.object({ name: z.string() }),
) {
  hello() {
    console.log('hello');
  }
}
@Controller('markets')
export class MarketsController {
  @Post()
  async create(@Body() dto: CreateMarketDto) {
    dto.hello();
    return market;
  }
}
// in app.module
  providers: [
    {
      provide: APP_PIPE,
      useClass: ZodValidationPipe,
    },
  ],

Calling the controller causes TypeError: dto.hello is not a function

Add support to `.brand` schemas

Branded types in zod are very helpful in ensuring that a function argument or a value has been validated through a zod schema.

Today when using .brand in a schema, the zodToOpenAPI function doesn't generate the correct type at OpenAPI schema

Way to create DTO for output type of schema

Is there a way to create a DTO for the transformed output type of schema?

Let's say, this is the schema for the DB model:

const modelSchemaDb = z.object({
  id: z.string(),
  subItem: {
    id: z.string(),
    content: z.string(),
  },
})

In my API I want want the nested DB record to be flattened in the response, so I add a transform to the schema:

const modelSchemaTransform = modelSchemaDb.transform((item) => ({
  id: item.id,
  content: item.subitem.content,
}))

Now I want to create a DTO class for the transformed output of the schema, but

class modelDtoTransform extends createZodDto(modelSchemaTransform) {}

creates the same thing as

class modelDtoDb extends createZodDto(modelSchemaDb) {}

A class matching the nested type:

{
  id: string;
  subItem: {
    id: string;
    content: string;
  }
}

which leads to a wrong open api definition, in my case.

I need a DTO class matching the flattened type:

{
  id: string;
  content: string;
}

Is there already a way to achive this?

Support OpenAPI example field

Hello,

Is there any way to add the example property to the generated OpenAPI?

I'm interested to use this for DTOs. This is usually possible when declaring DTOs manually with the ApiProperty decorator, but I'm not sure how to do it when using createZodDto.

Thanks!

Ghost Dependency: RxJS

Started to use YarnPnp on a monorepo project.

However when I attempt to run my NestJs app which uses nestjs-zod, I get the following error in my terminal:

Error: nestjs-zod tried to access rxjs, but it isn't declared in its dependencies; this makes the require call ambiguous and unsound.

Required package: rxjs
Required by: nestjs-zod@virtual:ace0b506873355d11cd586117594d7eb22f90ff3a2d2c145296f0ff6acc08f08194cf3dbc82554ba9fa4f646cc58853bb4c8c565dd25360ab7f67bae8a39b6db#npm:3.0.0 (via <oproject-root>/.yarn/__virtual__/nestjs-zod-virtual-f8417711e6/3/.yarn/berry/cache/nestjs-zod-npm-3.0.0-162e2c9a86-10c0.zip/node_modules/nestjs-zod/dist/)

Require stack:
- <oproject-root>/.yarn/__virtual__/nestjs-zod-virtual-f8417711e6/3/.yarn/berry/cache/nestjs-zod-npm-3.0.0-162e2c9a86-10c0.zip/node_modules/nestjs-zod/dist/index.js
- <oproject-root>/test-api/dist/dto/CreatePlotDTO.js
- <oproject-root>/test-api/dist/app.controller.js
- <oproject-root>/test-api/dist/app.module.js
- <oproject-root>/test-api/dist/main.js
    at Function.require$$0.Module._resolveFilename (<oproject-root>/.pnp.cjs:15160:13)
    at Function.Module._load (node:internal/modules/cjs/loader:920:27)
    at Function.require$$0.Module._load (<oproject-root>/.pnp.cjs:15051:31)
    at Module.require (node:internal/modules/cjs/loader:1141:19)
    at require (node:internal/modules/cjs/helpers:110:18)
    at Object.<anonymous> (<oproject-root>/.yarn/__virtual__/nestjs-zod-virtual-f8417711e6/3/.yarn/berry/cache/nestjs-zod-npm-3.0.0-162e2c9a86-10c0.zip/node_modules/nestjs-zod/dist/index.js:8:12)
    at Module._compile (node:internal/modules/cjs/loader:1254:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1308:10)
    at Object.require$$0.Module._extensions..js (<oproject-root>/.pnp.cjs:15203:33)
    at Mo

https://github.com/risen228/nestjs-zod/blob/main/package.json#L61

I can see the "rxjs" dep is listed as a "devDependency"; is this something we can change to an actual dependency?

Option to add custom zod validator

Hi, I've an usecase where I need to pass custom options to Zod's .parse() or .safeParse() but I couldnt find anywhere in the docs on how to do that.

Basically, I need to pass an errorMap for localization, that depends on a query from the request.

Is there anyway to actually create a validate function without using the bultin? Where we've access to the request and have to call Zod ourselves?

Created classes have properties marked as optional.

So I have a Zod schema createAddressSchema that works out to be the type below.

ZodObject<{name: ZodOptional<ZodNullable<ZodString>>, firstLine: ZodString}>

When I turn this into a class

import { createZodDto } from 'nestjs-zod';

export class NewAddress extends createZodDto(createAddressSchema) {}

However, looking at the properties on the generated class, every property is optional, even though firstLine should not be. Is there a reason for this?

{
    name?: string;
    firstLine?: string;
}

There is a chance it has to do with how I'm generating the Zod schema but since the type works out to not be wrapped in ZodOptional it seems like it should be fine.

Thanks!

ZodSerializerInterceptor should return 500 instead of 400

I've registered ZodSerializerInterceptor as an APP_INTERCEPTOR in my root module.
It looks like that when a response object does not match what is defined in @ZodSerializerDto() a 400 error is returned, along with its error details.
I think that errors in response validation should return 500 instead, if the server doesn't return what it's supposed to that's a server error, isn't it?

`dateString` does not generate `type` in OpenAPI schema

I have a zod schema that has an object with a date field, and I'm using the "@nestjs/swagger" to generate an OpenAPI json file. The schema type for the date field isn't generating as expected.

Other strings generate as expected.

Example code

This is what my zod schema and dto look like, as well as my controller using them:

import { Body, Controller, Post } from "@nestjs/common"
import { ApiBody } from "@nestjs/swagger"
import { createZodDto, zodToOpenAPI } from "nestjs-zod"
import { z } from "nestjs-zod/z"

const DateSchema = z.object({
  date: z.dateString().format("date"),
})

class DateDto extends createZodDto(DateSchema) {}

const schema = zodToOpenAPI(DateSchema)

console.log({ schema })

@Controller("/test")
export class TestController {
  @Post("/date")
  @ApiBody({
    schema,
  })
  async setDate(@Body() { date }: DateDto) {
    console.log(date)
  }
}

Actual result

The generated OpenAPI json file does not have a type or format specified:

{
  "schema": {
    "type": "object",
    "properties": {
      "date": {}
    },
    "required": [
      "date"
    ]
  }
}

Expected result

I expect that my generated OpenAPI json file should specify that this field is a string with a date format:

{
  "schema": {
    "type": "object",
    "properties": {
      "date": {
        "type": "string",
        "format": "date"
      },
    }
    "required": [
      "date"
    ]
  }
}

Versions

node: v16.16.0
nestjs: ^9.0.0 (9.0.11)
nestjs-zod: 1.2.1

DTO is not a class, all fields typed as optional

Hi.

First off, I'm happy there is a project using Zod instead of having to deal with class-validator. However, the createZodDto function does not create a class instance, but rather an object.

const createEnvironmentSchema = z.object({
  label: z.string().max(191).nonempty(),
  fromName: z.string().max(191).nonempty(),
  fromEmail: z.string().max(191).email(),
  smtpHost: z.string().max(191).nonempty(),
  smtpPort: z.number().int().max(65535),
  smtpUser: z.string().max(191).nonempty(),
  smtpPass: z.string().max(191).nonempty(),
})

export class CreateEnvironmentDto extends createZodDto(createEnvironmentSchema) {

  mailbox(): string {
    return `"${this.fromName}" <${this.fromEmail}>`
  }
}

I expected the function to return an instance of the class, so I can add additional methods and acessors, and be able to use them as well as instanceof calls downstream, as I've been accustomed to do using class-transfromer. However, the value returned to the controller is a POJO.

@Controller('environments')
export class EnvironmentsController {

  @Post()
  async create(@Body() dto: CreateEnvironmentDto) {
    console.log(dto, dto instanceof CreateEnvironmentDto)
  }
}

The above console statement produces { ... }, false instead of CreateEnvironmentDto { ... }, true

Another issue is that the object properties are all optional whereas the Zod schema has no optional properties. The LSP reports the shape of the object as such:

label?: string;
fromName?: string;
fromEmail?: string;
smtpHost?: string;
smtpPort?: number;
smtpUser?: string;
smtpPass?: string;

The actual type shown is this:

(alias) createZodDto<{
    label?: string;
    fromName?: string;
    fromEmail?: string;
    smtpHost?: string;
    smtpPort?: number;
    smtpUser?: string;
    smtpPass?: string;
}, z.ZodObjectDef<{
    label: z.ZodString;
    fromName: z.ZodString;
    fromEmail: z.ZodString;
    smtpHost: z.ZodString;
    smtpPort: z.ZodNumber;
    smtpUser: z.ZodString;
    smtpPass: z.ZodString;
}, "strip", z.ZodTypeAny>, {
    ...;
}>(schema: z.ZodType<...>): ZodDto<...>
import createZodDto

This keeps it from being useful for downstream API's where it should be assumed that all required properties are present in the DTO, which of course they are, but the LSP doesn't know that.

Using the most recent versions of this library and NestJS:

"@nestjs/common": "^10.0.0",
"@nestjs/core": "^10.0.0",
"nestjs-zod": "^2.3.3",
"zod": "^3.22.2",

Hopefully there is a fix? I REALLY do not want to go back to using class-validator and vlass-transformer which are just tedious to deal with.

Union type issue

When the schema contains a union I get an error:

error TS2509: Base constructor return type [...] is not an object type or intersection of object types with statically known members

example:

import { z } from 'nestjs-zod/z';

const unionTypeSchema = z.union([z.string(), z.number()]);
class Dto extends createZodDto(unionTypeSchema) {}

version: 3.0.0

Fix: dateString regex issue

Hi!

I tried to create a pr for this problem but I wasn't allowed so here it comes. You should change the date-string.ts regex to:

const formatToRegex: Record<DateStringFormat, RegExp> = { 'date': /^\d{4}-\d{2}-\d{2}$/, 'date-time': /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?([+-]\d{2}:\d{2}|Z))$/, }

and add a new test to line 60 for example

is('2017-08-20T20:00:00.000000Z', true)

feat: Interceptor for serialisation of responses

Context

With class-validator, we have ClassSerializerInterceptor to strip out unwanted properties from responses. This can be useful if you would like to exclude additional properties from being returned to the client that should not be exposed.

Proposed Solution

Have a ZodSerializerInterceptor similar to ClassSerializerInterceptor to transform response before client receives it

Example

User Shape

type User {
    name: string
    password: string
}

Response Schema

export const responseSchema = z.object({'name': z.string() })

Controller Class

@ZodSerializerInterceptor(responseSchema)
getUser(@Param() id: number) {
    return this.userService.findOne(id) // we return the user object with password property here
}

In the above example, even though userService.findOne(id) returns the password property, with our ZodSerializerInterceptor, we ensure that the password property gets stripped.

If this gets approved, i'll be happy to do up a PR to implement this feature.

Is it possible to have transformed values in the Controllers after the DTOs pass validation?

Say I have this schema:

export const BaseCreateAppointmentDtoSchema = z.object({
    title: z.string(),
    description: z.string().nullish(),
    startAt: z.coerce.date(),
    endAt: z.coerce.date(),
    place: z.string().nullish(),
    internalGuestsEmails: z.array(z.string().email()).optional(),
    externalGuestsEmails: z.array(z.string().email()).optional(),
    isOnlineMeeting: z.boolean().optional(),
})

export const CreateAppointmentDtoSchema = BaseCreateAppointmentDtoSchema.transform((dto) => {
    console.log('transform rodou!')
    dto.internalGuestsEmails = uniq(dto.internalGuestsEmails)
    dto.externalGuestsEmails = uniq(dto.externalGuestsEmails)
    return dto
})
export type CreateAppointmentDto = z.infer<typeof CreateAppointmentDtoSchema>

The object that gets to my controller does not have the email lists uniquefied. Would it be possible to make the transforms apply?

Thanks for the awesome library.

when createZodDto is imported, server side library can't be tree-shaked.

I'm using yarn berry and using nestjs-zod for validating dtos.
I want to import createZodDto in client-side without installing no server-side library.
but i can't because createZodDto is imported from index.ts which imports other server-side dependent files.

I suggest it should be in other folder, where server-side library is not related with.

Enum type issue

Hello, I am using Prisma Zod and nestjs-zod and nestjs-zod-prisma
Which should be compitable with each others

I am generating the Zod schemas with npx prisma generate
And everything goes well except for Role Enum
schema.prisma

enum Role {
  USER
  STORE_OWNER
}

so when i run the npx prisma generate A Schema is generated based on my prisma table definitions
zod/storeowner.ts

import { Role } from './enums';

export const StoreOwnerModel = z.object({
  role: z.nativeEnum(Role).default(Role.STORE_OWNER),
  // etc...
});
export class StoreOwnerEntity extends createZodDto(StoreOwnerModel) {}

zod/enums.ts

export enum Role {
  USER = "USER",
  STORE_OWNER = "STORE_OWNER"
}

When using the StoreOwnerEntity in my service it is giving ts error

store-owner.service.ts

  async findFirst(): Promise<StoreOwnerEntity | null> {
    return this.prismaService.storeOwner.findFirst();
  }

Error

Types of property 'role' are incompatible.
      Type 'import("c:/Users/saybers/Desktop/qbite/backend/node_modules/.prisma/client/index").$Enums.Role' is not assignable to type 'import("c:/Users/saybers/Desktop/qbite/backend/src/modules/zod/enums").Role'.
        Type '"USER"' is not assignable to type 'Role'.ts(2322)

Error using z.schema

Hello!

I have tried to use the lib using the example below

https://github.com/risenforces/nestjs-zod#creating-dto-from-zod-schema

But I got a this error:

error TS2551: Property 'schema' does not exist on type 'typeof import("/node_modules/.pnpm/[email protected]_cw6o3dkuyuli4a5gq7b223evly/node_modules/nestjs-zod/dist/z-only-override")'. Did you mean 'Schema'?

Example in Stackblitz: https://stackblitz.com/edit/node-jxr258?file=main.ts

I have looked for it in to the file and can't found the method schema or Schema.

I change the method z.schema to z.object and works fine. Is the example wrong?

NestJS version: 9.0.8
NestJS Zod version: 1.1.2
Typescript version: 4.7.4

Thanks!

z.string().datetime() is not generated correctly the format is missed in the openapi spec

When I use createZodDto with a schema like:

const CreateFanDto = z
  .object({
    gender: z.enum(['male', 'female', 'other']).nullish(),
    status: z.enum(['active', 'inactive']).optional(),
    firstname: z.string().nullish(),
    lastname: z.string().nullish(),
    phone: z.string().nullish(),
    address: z.string().nullish(),
    birthDate: z.string().datetime().nullish(),
    email: z.string().email(),
  })

It generates birthDate as string without format, but like indicated in the README for v3.0.0 it should accept native datetime and should generate the field as string with $datetime format.

Asking for Binary zod type

I am wondering if its possible to add binary type to a field for example

LogoFIle: z.binary()
OR
LogoFIle: z.string().binary()

Just so when i use formData i am able to see that file on swagger !

A few ideas

Hey @risen228, great job on creating this package! NestJS creator here.

I recently started working on the @nestjs/zod package to simplify the integration between the two (Nest and Zod), and then I came across your package and noticed that it already provides several features I planned to add to the official library (great job!)

So I'd like to suggest a few improvements/questions, just in case you have some time:

  • instead of patching the @nestjs/swagger, you could add a static _OPENAPI_METADATA_FACTORY method to your Zod DTO classes (see screenshot below - from my local project)
    image
  • guards shouldn't be used for validating input payload (incoming dtos)
  • any reason Zod isn't declared as a peer dependency?
  • ValidationPipe could expose a few additional options (screenshot form my local project)

image

Fantastic job again!

throw error if validation pipe removes items from request

Hi there,
First up, loving the library and amazed it doesn't have more stars.
I have applied ZodValidationPipe globally and it's working a treat removing any unspecified properties from requests according to the request DTO. What I'd ideally like though is for an error to be thrown if a non whitelisted property is present in the request, rather than it just silently being stripped.
Is this possible?
Thanks :-)

Zod + nestjs + prisma (all optional fields)

why do all types become optional inside me? I can't use it normally because the prism requires its own types and I have a conflict

I have some types just turn into optional and that's it

image
image
image
image
image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.