diff --git a/CHANGELOG.md b/CHANGELOG.md index 69d4acb95..78d0179ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - **Breaking Change**: update `graphql-js` peer dependency to `^14.6.0` - **Breaking Change**: update `graphql-query-complexity` dependency to `^0.4.1` and drop support for `fieldConfigEstimator` (use `fieldExtensionsEstimator` instead) - **Breaking Change**: introduce `sortedSchema` option in `PrintSchemaOptions` and emit sorted schema file by default +- **Breaking Change**: make `class-validator` an optional, peer dependency (#366) - update `TypeResolver` interface to match with `GraphQLTypeResolver` from `graphql-js` - add basic support for directives with `@Directive()` decorator (#369) - add possibility to tune up the performance and disable auth & middlewares stack for simple field resolvers (#479) diff --git a/docs/installation.md b/docs/installation.md index 25b1847f8..e2ccb1ba2 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -10,10 +10,10 @@ Before getting started with TypeGraphQL we need to install some additional depen ## Packages installation -First, we have to install the main package, as well as [`graphql-js`](https://github.com/graphql/graphql-js) which is a peer dependency of TypeGraphQL: +First, we have to install the main package, as well as [`graphql-js`](https://github.com/graphql/graphql-js) and [`class-validator`](https://github.com/typestack/class-validator) which are peer dependencies of TypeGraphQL: ```sh -npm i graphql type-graphql +npm i graphql class-validator type-graphql ``` Also, the `reflect-metadata` shim is required to make the type reflection work: diff --git a/docs/validation.md b/docs/validation.md index dff6f9cb9..63704e26c 100644 --- a/docs/validation.md +++ b/docs/validation.md @@ -46,9 +46,9 @@ And that's it! 😉 TypeGraphQL will automatically validate our inputs and arguments based on the definitions: ```typescript -@Resolver(of => Recipe) +@Resolver((of) => Recipe) export class RecipeResolver { - @Mutation(returns => Recipe) + @Mutation((returns) => Recipe) async addRecipe(@Arg("input") recipeInput: RecipeInput): Promise { // you can be 100% sure that the input is correct console.assert(recipeInput.title.length <= 30); @@ -73,7 +73,7 @@ And we can still enable it per resolver's argument if we need to: ```typescript class RecipeResolver { - @Mutation(returns => Recipe) + @Mutation((returns) => Recipe) async addRecipe(@Arg("input", { validate: true }) recipeInput: RecipeInput) { // ... } @@ -84,7 +84,7 @@ The `ValidatorOptions` object used for setting features like [validation groups] ```typescript class RecipeResolver { - @Mutation(returns => Recipe) + @Mutation((returns) => Recipe) async addRecipe( @Arg("input", { validate: { groups: ["admin"] } }) recipeInput: RecipeInput, @@ -167,6 +167,12 @@ By default, the `apollo-server` package from the [bootstrap guide](bootstrap.md) Of course we can also create our own custom implementation of the `formatError` function provided in the `ApolloServer` config options which will transform the `GraphQLError` with a `ValidationError` array in the desired output format (e.g. `extensions.code = "ARGUMENT_VALIDATION_ERROR"`). +## Caveats + +Even if we don't use the validation feature (and we have provided `{ validate: false }` option to `buildSchema`), we still need to have `class-validator` installed as a dev dependency in order to compile our app without errors using `tsc`. + +An alternative solution that allows to completely get rid off big `class-validator` from our project's `node_modules` folder is to suppress the `error TS2307: Cannot find module 'class-validator'` TS error by providing `"skipLibCheck": true` setting in `tsconfig.json`. + ## Example To see how this works, check out the [simple real life example](https://github.com/MichalLytek/type-graphql/tree/master/examples/automatic-validation). diff --git a/package-lock.json b/package-lock.json index 166306c3f..df167b0c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1661,6 +1661,12 @@ "integrity": "sha512-Z4TYuEKn9+RbNVk1Ll2SS4x1JeLHecolIbM/a8gveaHsW0Hr+RQMraZACwTO2VD7JvepgA6UO1A1VrbktQrIbQ==", "dev": true }, + "@types/validator": { + "version": "10.11.3", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-10.11.3.tgz", + "integrity": "sha512-GKF2VnEkMmEeEGvoo03ocrP9ySMuX1ypKazIYMlsjfslfBMhOAtC5dmEWKdJioW4lJN7MZRS88kalTsVClyQ9w==", + "dev": true + }, "@types/vinyl": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.2.tgz", @@ -3075,12 +3081,14 @@ } }, "class-validator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.9.1.tgz", - "integrity": "sha512-3wApflrd3ywVZyx4jaasGoFt8pmo4aGLPPAEKCKCsTRWVGPilahD88q3jQjRQwja50rl9a7rsP5LAxJYwGK8/Q==", + "version": "0.12.0-rc.0", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.12.0-rc.0.tgz", + "integrity": "sha512-juMzKxJbk3rkUzMFYqwrZwYFWAVWxGz6JQjLY3Mixs/H4jQxaxsMzEqzYQboo9KWaX2+qMSA1aG/cRlmUFYX/Q==", + "dev": true, "requires": { + "@types/validator": "10.11.3", "google-libphonenumber": "^3.1.6", - "validator": "10.4.0" + "validator": "12.0.0" } }, "clean-stack": { @@ -5292,9 +5300,10 @@ } }, "google-libphonenumber": { - "version": "3.1.11", - "resolved": "https://registry.npmjs.org/google-libphonenumber/-/google-libphonenumber-3.1.11.tgz", - "integrity": "sha512-i5gPUiHT1L8PMhJA3u1Bno2gXU6Ec9mP05J0wxNFk5tfuJNyE14HqlVCCP/i1zwRKl0iIGKrAc5s9YlyzTSF8w==" + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/google-libphonenumber/-/google-libphonenumber-3.2.8.tgz", + "integrity": "sha512-iWs1KcxOozmKQbCeGjvU0M7urrkNjBYOSBtb819RjkUNJHJLfn7DADKkKwdJTOMPLcLOE11/4h/FyFwJsTiwLg==", + "dev": true }, "graceful-fs": { "version": "4.1.11", @@ -13346,9 +13355,10 @@ } }, "validator": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-10.4.0.tgz", - "integrity": "sha512-Q/wBy3LB1uOyssgNlXSRmaf22NxjvDNZM2MtIQ4jaEOAB61xsh1TQxsq1CgzUMBV1lDrVMogIh8GjG1DYW0zLg==" + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-12.0.0.tgz", + "integrity": "sha512-r5zA1cQBEOgYlesRmSEwc9LkbfNLTtji+vWyaHzRZUxCTHdsX3bd+sdHfs5tGZ2W6ILGGsxWxCNwT/h3IY/3ng==", + "dev": true }, "value-or-function": { "version": "3.0.0", diff --git a/package.json b/package.json index 66eb8708c..b366502fd 100644 --- a/package.json +++ b/package.json @@ -18,13 +18,13 @@ "postinstall": "node ./dist/postinstall || exit 0" }, "peerDependencies": { - "graphql": "^14.6.0" + "graphql": "^14.6.0", + "class-validator": ">=0.9.1" }, "dependencies": { "@types/glob": "^7.1.1", "@types/node": "*", "@types/semver": "^7.1.0", - "class-validator": ">=0.9.1", "glob": "^7.1.6", "graphql-query-complexity": "^0.4.1", "graphql-subscriptions": "^1.1.0", @@ -48,6 +48,7 @@ "apollo-server": "^2.11.0", "apollo-server-express": "^2.11.0", "class-transformer": "^0.2.3", + "class-validator": "^0.12.0-rc.0", "del": "^5.1.0", "express": "^4.17.1", "graphql": "^14.6.0", diff --git a/src/decorators/types.ts b/src/decorators/types.ts index 2d023aa93..2fdc171ec 100644 --- a/src/decorators/types.ts +++ b/src/decorators/types.ts @@ -1,5 +1,5 @@ import { GraphQLScalarType } from "graphql"; -import { ValidatorOptions } from "class-validator"; +import type { ValidatorOptions } from "class-validator"; import { ResolverFilterData, diff --git a/src/errors/ArgumentValidationError.ts b/src/errors/ArgumentValidationError.ts index c77db3c3f..5e4dda17f 100644 --- a/src/errors/ArgumentValidationError.ts +++ b/src/errors/ArgumentValidationError.ts @@ -1,4 +1,4 @@ -import { ValidationError } from "class-validator"; +import type { ValidationError } from "class-validator"; export class ArgumentValidationError extends Error { constructor(public validationErrors: ValidationError[]) { diff --git a/src/metadata/definitions/param-metadata.ts b/src/metadata/definitions/param-metadata.ts index 85bfcdfb9..65491dfca 100644 --- a/src/metadata/definitions/param-metadata.ts +++ b/src/metadata/definitions/param-metadata.ts @@ -1,4 +1,4 @@ -import { ValidatorOptions } from "class-validator"; +import type { ValidatorOptions } from "class-validator"; import { TypeValueThunk, TypeOptions } from "../../decorators/types"; import { ResolverData } from "../../interfaces"; diff --git a/src/resolvers/helpers.ts b/src/resolvers/helpers.ts index b3893e0ba..55ea351ac 100644 --- a/src/resolvers/helpers.ts +++ b/src/resolvers/helpers.ts @@ -1,5 +1,5 @@ import { PubSubEngine } from "graphql-subscriptions"; -import { ValidatorOptions } from "class-validator"; +import type { ValidatorOptions } from "class-validator"; import { ParamMetadata } from "../metadata/definitions"; import { convertToType } from "../helpers/types"; @@ -19,7 +19,7 @@ export function getParams( ): Promise | any[] { const paramValues = params .sort((a, b) => a.index - b.index) - .map(paramInfo => { + .map((paramInfo) => { switch (paramInfo.kind) { case "args": return validateArg( diff --git a/src/resolvers/validate-arg.ts b/src/resolvers/validate-arg.ts index f813d1bde..d9c47a68c 100644 --- a/src/resolvers/validate-arg.ts +++ b/src/resolvers/validate-arg.ts @@ -1,4 +1,4 @@ -import { ValidatorOptions } from "class-validator"; +import type { ValidatorOptions } from "class-validator"; import { ArgumentValidationError } from "../errors/ArgumentValidationError"; @@ -24,7 +24,7 @@ export async function validateArg( const { validateOrReject } = await import("class-validator"); try { if (Array.isArray(arg)) { - await Promise.all(arg.map(argItem => validateOrReject(argItem, validatorOptions))); + await Promise.all(arg.map((argItem) => validateOrReject(argItem, validatorOptions))); } else { await validateOrReject(arg, validatorOptions); } diff --git a/src/schema/build-context.ts b/src/schema/build-context.ts index a439efcbe..15a7ccee3 100644 --- a/src/schema/build-context.ts +++ b/src/schema/build-context.ts @@ -1,5 +1,5 @@ import { GraphQLScalarType } from "graphql"; -import { ValidatorOptions } from "class-validator"; +import type { ValidatorOptions } from "class-validator"; import { PubSubEngine, PubSub, PubSubOptions } from "graphql-subscriptions"; import { AuthChecker, AuthMode } from "../interfaces";