@nxgt/graphql-validation 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +209 -2
  3. package/dist/bad-user-input.d.ts +35 -0
  4. package/dist/bad-user-input.d.ts.map +1 -0
  5. package/dist/builder/args-schema.d.ts +17 -0
  6. package/dist/builder/args-schema.d.ts.map +1 -0
  7. package/dist/builder/constraints.d.ts +16 -0
  8. package/dist/builder/constraints.d.ts.map +1 -0
  9. package/dist/builder/input-schema.d.ts +31 -0
  10. package/dist/builder/input-schema.d.ts.map +1 -0
  11. package/dist/builder/leaf.d.ts +14 -0
  12. package/dist/builder/leaf.d.ts.map +1 -0
  13. package/dist/chunks/index-n3hypgpf.js +315 -0
  14. package/dist/chunks/index-n3hypgpf.js.map +37 -0
  15. package/dist/cli.d.ts +3 -0
  16. package/dist/cli.d.ts.map +1 -0
  17. package/dist/cli.js +69 -0
  18. package/dist/cli.js.map +11 -0
  19. package/dist/constraint-directive.d.ts +21 -0
  20. package/dist/constraint-directive.d.ts.map +1 -0
  21. package/dist/formats/all.d.ts +9 -0
  22. package/dist/formats/all.d.ts.map +1 -0
  23. package/dist/formats/byte.d.ts +2 -0
  24. package/dist/formats/byte.d.ts.map +1 -0
  25. package/dist/formats/date-time.d.ts +2 -0
  26. package/dist/formats/date-time.d.ts.map +1 -0
  27. package/dist/formats/date.d.ts +2 -0
  28. package/dist/formats/date.d.ts.map +1 -0
  29. package/dist/formats/email.d.ts +2 -0
  30. package/dist/formats/email.d.ts.map +1 -0
  31. package/dist/formats/format.d.ts +15 -0
  32. package/dist/formats/format.d.ts.map +1 -0
  33. package/dist/formats/index.d.ts +13 -0
  34. package/dist/formats/index.d.ts.map +1 -0
  35. package/dist/formats/ipv4.d.ts +2 -0
  36. package/dist/formats/ipv4.d.ts.map +1 -0
  37. package/dist/formats/ipv6.d.ts +2 -0
  38. package/dist/formats/ipv6.d.ts.map +1 -0
  39. package/dist/formats/uri.d.ts +2 -0
  40. package/dist/formats/uri.d.ts.map +1 -0
  41. package/dist/formats/uuid.d.ts +2 -0
  42. package/dist/formats/uuid.d.ts.map +1 -0
  43. package/dist/index.d.ts +6 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +315 -0
  46. package/dist/index.js.map +16 -0
  47. package/dist/rules/all.d.ts +16 -0
  48. package/dist/rules/all.d.ts.map +1 -0
  49. package/dist/rules/contains.d.ts +2 -0
  50. package/dist/rules/contains.d.ts.map +1 -0
  51. package/dist/rules/ends-with.d.ts +2 -0
  52. package/dist/rules/ends-with.d.ts.map +1 -0
  53. package/dist/rules/exclusive-max.d.ts +2 -0
  54. package/dist/rules/exclusive-max.d.ts.map +1 -0
  55. package/dist/rules/exclusive-min.d.ts +2 -0
  56. package/dist/rules/exclusive-min.d.ts.map +1 -0
  57. package/dist/rules/format.d.ts +2 -0
  58. package/dist/rules/format.d.ts.map +1 -0
  59. package/dist/rules/index.d.ts +26 -0
  60. package/dist/rules/index.d.ts.map +1 -0
  61. package/dist/rules/max-items.d.ts +2 -0
  62. package/dist/rules/max-items.d.ts.map +1 -0
  63. package/dist/rules/max-length.d.ts +2 -0
  64. package/dist/rules/max-length.d.ts.map +1 -0
  65. package/dist/rules/max.d.ts +2 -0
  66. package/dist/rules/max.d.ts.map +1 -0
  67. package/dist/rules/min-items.d.ts +2 -0
  68. package/dist/rules/min-items.d.ts.map +1 -0
  69. package/dist/rules/min-length.d.ts +2 -0
  70. package/dist/rules/min-length.d.ts.map +1 -0
  71. package/dist/rules/min.d.ts +2 -0
  72. package/dist/rules/min.d.ts.map +1 -0
  73. package/dist/rules/multiple-of.d.ts +2 -0
  74. package/dist/rules/multiple-of.d.ts.map +1 -0
  75. package/dist/rules/not-contains.d.ts +2 -0
  76. package/dist/rules/not-contains.d.ts.map +1 -0
  77. package/dist/rules/pattern.d.ts +2 -0
  78. package/dist/rules/pattern.d.ts.map +1 -0
  79. package/dist/rules/rule.d.ts +55 -0
  80. package/dist/rules/rule.d.ts.map +1 -0
  81. package/dist/rules/starts-with.d.ts +2 -0
  82. package/dist/rules/starts-with.d.ts.map +1 -0
  83. package/dist/typedefs-command.d.ts +10 -0
  84. package/dist/typedefs-command.d.ts.map +1 -0
  85. package/dist/validated.d.ts +22 -0
  86. package/dist/validated.d.ts.map +1 -0
  87. package/dist/with-validation.d.ts +26 -0
  88. package/dist/with-validation.d.ts.map +1 -0
  89. package/docs/README.md +15 -0
  90. package/docs/guide/constraints.md +291 -0
  91. package/docs/guide/errors.md +194 -0
  92. package/docs/roadmap.md +30 -0
  93. package/docs/troubleshooting.md +260 -0
  94. package/graphql/constraint.graphqls +20 -0
  95. package/package.json +67 -5
@@ -0,0 +1,260 @@
1
+ # Troubleshooting
2
+
3
+ One entry per error you can hit, headed by the message you will search for.
4
+ `withValidation` and `buildSchema` errors happen at startup; the last section
5
+ is the one a client sees.
6
+
7
+ - [Install](#install)
8
+ - [Startup](#startup)
9
+ - [Silent traps](#silent-traps)
10
+ - [Runtime](#runtime)
11
+ - [The bin](#the-bin)
12
+
13
+ ## Install
14
+
15
+ ### `TS2307: Cannot find module '@nxgt/graphql-validation'` (or its types)
16
+
17
+ **When:** type-checking an import of the package.
18
+ **Why:** the package is ESM with an `exports` map, which `moduleResolution`
19
+ `node`/`node10` ignores, and `nodenext` is not supported.
20
+ **Fix:**
21
+
22
+ ```jsonc
23
+ { "compilerOptions": { "moduleResolution": "bundler" } }
24
+ ```
25
+
26
+ ## Startup
27
+
28
+ ### `@constraint(minLength) on Query.check(age:) needs a String or an ID, not Int.`
29
+
30
+ **When:** calling `withValidation`.
31
+ **Why:** a rule narrows one kind of type. `format`, `minLength`, `maxLength`,
32
+ `startsWith`, `endsWith`, `contains`, `notContains` and `pattern` need a
33
+ `String` or an `ID`; `min`, `max`, `exclusiveMin`, `exclusiveMax` and
34
+ `multipleOf` need an `Int` or a `Float`. The same message, with `an Int or a
35
+ Float`, appears for a number rule on a `String`.
36
+ **Fix:** use a rule that fits the type.
37
+
38
+ ```graphql
39
+ check(age: Int @constraint(min: 18)): Boolean
40
+ ```
41
+
42
+ ### `@constraint(minItems) on Query.check(name:) needs a list, not String.`
43
+
44
+ **When:** calling `withValidation`.
45
+ **Why:** `minItems` and `maxItems` apply to a list.
46
+ **Fix:** put them on a list argument, or use `minLength`/`maxLength` for a string.
47
+
48
+ ```graphql
49
+ check(tags: [String!] @constraint(minItems: 1)): Boolean
50
+ ```
51
+
52
+ ### `@constraint(maxLength) on In.at needs a String or an ID, not DateTime.`
53
+
54
+ **When:** calling `withValidation`.
55
+ **Why:** a rule on a custom scalar, an enum or an input object (`..., not In.`)
56
+ has nothing to narrow: only `String`, `ID`, `Int`, `Float` and lists take
57
+ rules. The message names the place (`In.at`, `Query.check(input:)`) and the
58
+ type. This is checked in every input type, even one no argument reaches.
59
+ **Fix:** remove the directive; check a custom scalar in its own definition (for
60
+ example with `@nxgt/graphql-scalars`), or on a field with [`validated`](guide/errors.md#validated).
61
+
62
+ ### `Unknown @constraint format "siret". Known formats: byte, date, date-time, email, ipv4, ipv6, uri, uuid.`
63
+
64
+ **When:** calling `withValidation`.
65
+ **Why:** `format` takes one of the listed names. Custom formats are on the
66
+ [roadmap](roadmap.md).
67
+ **Fix:** use a known format, or `pattern: "..."`.
68
+
69
+ ### `Invalid @constraint pattern "[a-":`
70
+
71
+ **When:** calling `withValidation`.
72
+ **Why:** `pattern` is compiled with `new RegExp`; the text after the colon is
73
+ the runtime's own RegExp message, which differs between engines. V8 (Node)
74
+ says `Invalid regular expression: /[a-/: Unterminated character class`.
75
+ **Fix:** write a valid pattern. In SDL, escape backslashes: `"\\d+"`.
76
+
77
+ ### `Unknown directive "@constraint".`
78
+
79
+ **When:** in the IDE (JetBrains GraphQL, VS Code GraphQL), on every `@constraint`
80
+ in a `.graphql` file. The server runs fine.
81
+ **Why:** the plugin reads your schema files and `constraintTypeDefs` is not one.
82
+ **Fix:** add the shipped file to `graphql.config.yml`, or write a copy into the
83
+ project and list that instead:
84
+
85
+ ```yaml
86
+ schema:
87
+ - src/**/*.graphql
88
+ - node_modules/@nxgt/graphql-validation/graphql/constraint.graphqls
89
+ ```
90
+
91
+ ```sh
92
+ bunx nxgt-graphql-validation typedefs --out
93
+ ```
94
+
95
+ If the server scans the folder you write it to for its type definitions, the
96
+ copy declares the directive for the server too: drop `constraintTypeDefs`, or
97
+ keep the copy out of the scan. See the next entry.
98
+
99
+ ### `There can be only one directive named "@constraint".`
100
+
101
+ **When:** building the schema.
102
+ **Why:** `@constraint` is declared twice, usually `constraintTypeDefs` plus a
103
+ `constraint.graphqls` copy that the server's type definitions scan picked up.
104
+ **Fix:** keep one. Either drop `constraintTypeDefs` and let the scanned copy
105
+ declare the directive, or keep `constraintTypeDefs` and exclude the copy from
106
+ the scan (it then serves the IDE only).
107
+
108
+ ### `withValidation: this schema declares no @constraint directive. Add constraintTypeDefs to its type definitions.`
109
+
110
+ **When:** calling `withValidation`.
111
+ **Why:** the schema has no `@constraint` directive, so there is nothing to
112
+ read. Usually `constraintTypeDefs` was not added, or the schema is code-first and
113
+ declares no directive.
114
+ **Fix:**
115
+
116
+ ```ts
117
+ const typeDefs = [constraintTypeDefs, yourTypeDefs];
118
+ ```
119
+
120
+ ### `Directive "@constraint" may not be used on FIELD_DEFINITION.`
121
+
122
+ **When:** building the schema.
123
+ **Why:** `@constraint` is allowed on arguments and input fields only.
124
+ graphql-constraint-directive also allows output fields; this package refuses
125
+ them, since a resolver's result is the job of output scalars.
126
+ **Fix:** move it to the argument or input field, or use an output scalar.
127
+
128
+ ### `Unknown argument "uniqueTypeName" on directive "@constraint".`
129
+
130
+ **When:** building the schema, migrating from graphql-constraint-directive.
131
+ **Why:** `uniqueTypeName` is a detail of that package's scalar wrapping and
132
+ does not exist here.
133
+ **Fix:** remove it.
134
+
135
+ ### `@constraint on Named.name(style:) is not repeated on Person.name(style:), which resolves it (@constraint(maxLength: 5) there, no @constraint here). Write the same @constraint on both.`
136
+
137
+ **When:** calling `withValidation`, with a `@constraint` on an interface
138
+ field's argument.
139
+ **Why:** the objects resolve the field, not the interface, so a constraint
140
+ the object drops would go unchecked. `Named`, `Person` and `style` are your
141
+ interface, object and argument. When the object writes a different constraint
142
+ the message reads `... differs on Person.name(style:), which resolves it
143
+ (@constraint(maxLength: 5) there, @constraint(maxLength: 5, minLength: 1)
144
+ here). Write the same @constraint on both.`
145
+ **Fix:**
146
+
147
+ ```graphql
148
+ interface Named { name(style: String @constraint(maxLength: 5)): String }
149
+ type Person implements Named {
150
+ name(style: String @constraint(maxLength: 5)): String
151
+ }
152
+ ```
153
+
154
+ ### `This schema's @constraint is not constraintTypeDefs': it is allowed on FIELD_DEFINITION; declares uniqueTypeName, which no rule reads. Declare it with constraintTypeDefs.`
155
+
156
+ **When:** calling `withValidation`, typically after migrating from
157
+ graphql-constraint-directive and keeping its SDL.
158
+ **Why:** the schema declares its own `@constraint`: extra locations would be
159
+ accepted and checked by nothing, extra arguments mean nothing here, and an
160
+ argument of another type would reach a rule as the wrong kind of value. The
161
+ message lists every difference: `repeatable, and only its first use is read`,
162
+ `allowed on <LOCATION>`, `declares <name>, which no rule reads`, or
163
+ `declares minLength: String, not Int`.
164
+ **Fix:** drop the old directive declaration and use the package's.
165
+
166
+ ```ts
167
+ const typeDefs = [constraintTypeDefs, yourTypeDefs]; // no other `directive @constraint`
168
+ ```
169
+
170
+ ### `The default value of Query.a(name:) breaks its @constraint: Too small: expected string to have >=2 characters`
171
+
172
+ **When:** calling `withValidation`; for an argument (`Query.a(name:)`) or an
173
+ input field (`Page.size`).
174
+ **Why:** a request that leaves the value out would be refused for something the
175
+ client never sent. The text after the colon is the first Zod message.
176
+ The default is checked as graphql coerces it: a single value for a list
177
+ (`[String!] = "ab"` is `["ab"]`), an input object filled with its fields' own
178
+ defaults.
179
+ **Fix:** make the default satisfy the constraint, or relax the constraint.
180
+
181
+ ```graphql
182
+ type Query { a(name: String = "xy" @constraint(minLength: 2)): Int }
183
+ ```
184
+
185
+ ### `@constraint on @cached(ttl:) checks nothing: a directive's argument reaches no resolver. Remove it.`
186
+
187
+ **When:** calling `withValidation`, with `@constraint` written on an argument
188
+ of a directive definition (`directive @cached(ttl: Int @constraint(min: 1))`).
189
+ **Why:** graphql allows it there (it is an ARGUMENT_DEFINITION), but no
190
+ resolver receives a directive's argument, so the constraint would check
191
+ nothing.
192
+ **Fix:** remove it; check the directive's arguments where you read them.
193
+
194
+ ## Silent traps
195
+
196
+ These raise no error.
197
+
198
+ ### A constrained argument is never refused
199
+
200
+ **When:** an invalid value reaches the resolver.
201
+ **Why:** one of three causes. The schema declares `@constraint` but the constrained
202
+ types and fields have no SDL (code-first, or merged pieces): `@constraint` is
203
+ read from the SDL, and there is none.
204
+ Or `withValidation` was called before a resolver was attached: a resolver set on
205
+ a field afterwards replaces the check. Or the schema you serve is not the one
206
+ you passed (`withValidation` returns the same schema, so use either).
207
+ **Fix:** build from type definitions and call `withValidation` last.
208
+
209
+ ```ts
210
+ const schema = makeExecutableSchema({ typeDefs, resolvers });
211
+ export default withValidation(schema); // last
212
+ ```
213
+
214
+ ### `Invalid ISO datetime` for a date-time that looks right
215
+
216
+ **When:** `format: "date-time"` refuses `2024-03-10t12:00:00z`, `2024-03-10 12:00:00Z` or `2024-03-10T12:00Z`.
217
+ **Why:** only the canonical form is accepted: uppercase `T` and `Z`, seconds
218
+ present, then `Z` or a numeric offset.
219
+ **Fix:** send `2024-03-10T12:00:00Z` or `2024-03-10T12:00:00+02:00`.
220
+
221
+ ## Runtime
222
+
223
+ ### `Invalid arguments for Mutation.signUp. input.email: Invalid email address`
224
+
225
+ **When:** a request carries an argument that breaks a `@constraint` or a
226
+ `validated` schema.
227
+ **Why:** the message is `Invalid arguments for <Type>.<field>. <path>: <first
228
+ issue>`; `extensions.code` is `BAD_USER_INPUT` and `extensions.issues` lists
229
+ every issue. The resolver did not run.
230
+ **Fix:** send a valid value, or show each issue beside its field. See
231
+ [Errors](guide/errors.md). Common issues:
232
+
233
+ | Message | Cause |
234
+ | --- | --- |
235
+ | `Invalid email address` | `format: "email"` |
236
+ | `Invalid URL` | `format: "uri"`: not absolute, or a scheme other than `http`, `https`, `ftp` |
237
+ | `Invalid ISO datetime` / `Invalid ISO date` | `format: "date-time"` / `"date"` |
238
+ | `Too small: expected string to have >=2 characters` | `minLength: 2` |
239
+ | `Too big: expected array to have <=2 items` | `maxItems: 2` |
240
+ | `Too small: expected number to be >=18` | `min: 18` |
241
+ | `Invalid string: must match pattern /.../` | `pattern` |
242
+
243
+ ## The bin
244
+
245
+ ### `typedefs failed: <message>`
246
+
247
+ **When:** `nxgt-graphql-validation typedefs --out <file>` exits 1, for example
248
+ with `typedefs failed: EACCES: permission denied, mkdir 'schema'`, or
249
+ `EISDIR` when `<file>` is a folder.
250
+ **Why:** the file or its folder could not be written; the rest of the line is
251
+ the system's own message.
252
+ **Fix:** point `--out` at a file path you can write, or print to stdout and
253
+ redirect: `nxgt-graphql-validation typedefs > generated/graphql/constraint.graphqls`.
254
+
255
+ ### `Unknown command "<x>".` or `Unexpected arguments: …`
256
+
257
+ **When:** the bin exits 2 and prints its usage.
258
+ **Why:** the only command is `typedefs`, and its only flag is `--out [<file>]` (and `--help`).
259
+ **Fix:** `nxgt-graphql-validation typedefs [--out [<file>]]`; `--help` prints
260
+ the usage and exits 0.
@@ -0,0 +1,20 @@
1
+ # The @constraint directive of @nxgt/graphql-validation, for your IDE.
2
+ # Generated by `nxgt-graphql-validation typedefs`: do not edit.
3
+
4
+ directive @constraint(
5
+ contains: String
6
+ endsWith: String
7
+ exclusiveMax: Float
8
+ exclusiveMin: Float
9
+ format: String
10
+ maxItems: Int
11
+ maxLength: Int
12
+ max: Float
13
+ minItems: Int
14
+ minLength: Int
15
+ min: Float
16
+ multipleOf: Float
17
+ notContains: String
18
+ pattern: String
19
+ startsWith: String
20
+ ) on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION
package/package.json CHANGED
@@ -1,6 +1,68 @@
1
1
  {
2
- "name": "@nxgt/graphql-validation",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
2
+ "name": "@nxgt/graphql-validation",
3
+ "version": "0.1.0",
4
+ "description": "Validate GraphQL arguments and input fields with @constraint directives, checked by Zod schemas, for graphql 16 and 17",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "bin": {
10
+ "nxgt-graphql-validation": "./dist/cli.js"
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "docs",
15
+ "graphql",
16
+ "README.md",
17
+ "package.json",
18
+ "LICENSE"
19
+ ],
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js",
24
+ "default": "./dist/index.js"
25
+ },
26
+ "./package.json": "./package.json"
27
+ },
28
+ "keywords": [
29
+ "graphql",
30
+ "validation",
31
+ "constraint",
32
+ "directive",
33
+ "zod",
34
+ "bun",
35
+ "typescript"
36
+ ],
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/softistx/nxgt-graphql.git",
40
+ "directory": "packages/graphql-validation"
41
+ },
42
+ "publishConfig": {
43
+ "registry": "https://registry.npmjs.org",
44
+ "access": "public"
45
+ },
46
+ "scripts": {
47
+ "build": "bun run ../../build.ts",
48
+ "test": "bun test src",
49
+ "typecheck": "tsc --noEmit",
50
+ "typedefs:write": "bun run src/cli.ts typedefs --out graphql/constraint.graphqls"
51
+ },
52
+ "nxgt": {
53
+ "entrypoints": [
54
+ "src/index.ts",
55
+ "src/cli.ts"
56
+ ]
57
+ },
58
+ "devDependencies": {
59
+ "@types/bun": "^1.4.0",
60
+ "graphql": "16.11.0",
61
+ "zod": "4.6.5"
62
+ },
63
+ "peerDependencies": {
64
+ "graphql": "^16.11.0 || ^17.0.0",
65
+ "typescript": "^6.0.3",
66
+ "zod": ">=4.6.5 <5"
67
+ }
68
+ }