@nxgt/graphql-validation 0.2.0 → 0.3.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 (79) hide show
  1. package/README.md +63 -6
  2. package/dist/bad-user-input.d.ts +1 -1
  3. package/dist/builder/args-schema.d.ts +1 -1
  4. package/dist/builder/check-constraints.d.ts +6 -1
  5. package/dist/builder/check-constraints.d.ts.map +1 -1
  6. package/dist/builder/constraints.d.ts +1 -1
  7. package/dist/builder/input-code.d.ts +15 -2
  8. package/dist/builder/input-code.d.ts.map +1 -1
  9. package/dist/builder/input-schema.d.ts +3 -1
  10. package/dist/builder/input-schema.d.ts.map +1 -1
  11. package/dist/builder/leaf.d.ts +3 -3
  12. package/dist/builder/leaf.d.ts.map +1 -1
  13. package/dist/chunks/{index-s1kkmg35.js → index-1mzzydy3.js} +219 -23
  14. package/dist/chunks/index-1mzzydy3.js.map +25 -0
  15. package/dist/chunks/{index-rbxe5m3y.js → index-xcaf9kes.js} +24 -97
  16. package/dist/chunks/index-xcaf9kes.js.map +27 -0
  17. package/dist/cli.js +1 -1
  18. package/dist/codegen.d.ts +4 -3
  19. package/dist/codegen.d.ts.map +1 -1
  20. package/dist/codegen.js +19 -9
  21. package/dist/codegen.js.map +3 -3
  22. package/dist/formats/all.d.ts +8 -8
  23. package/dist/formats/byte.d.ts +1 -1
  24. package/dist/formats/byte.d.ts.map +1 -1
  25. package/dist/formats/date-time.d.ts +1 -1
  26. package/dist/formats/date-time.d.ts.map +1 -1
  27. package/dist/formats/date.d.ts +1 -1
  28. package/dist/formats/date.d.ts.map +1 -1
  29. package/dist/formats/email.d.ts +1 -1
  30. package/dist/formats/email.d.ts.map +1 -1
  31. package/dist/formats/format.d.ts +9 -3
  32. package/dist/formats/format.d.ts.map +1 -1
  33. package/dist/formats/index.d.ts +7 -9
  34. package/dist/formats/index.d.ts.map +1 -1
  35. package/dist/formats/ipv4.d.ts +1 -1
  36. package/dist/formats/ipv4.d.ts.map +1 -1
  37. package/dist/formats/ipv6.d.ts +1 -1
  38. package/dist/formats/ipv6.d.ts.map +1 -1
  39. package/dist/formats/registry.d.ts +50 -0
  40. package/dist/formats/registry.d.ts.map +1 -0
  41. package/dist/formats/uri.d.ts +1 -1
  42. package/dist/formats/uri.d.ts.map +1 -1
  43. package/dist/formats/uuid.d.ts +1 -1
  44. package/dist/formats/uuid.d.ts.map +1 -1
  45. package/dist/index.d.ts +8 -5
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +18 -5
  48. package/dist/index.js.map +3 -3
  49. package/dist/rules/all.d.ts +15 -15
  50. package/dist/rules/contains.d.ts +1 -1
  51. package/dist/rules/ends-with.d.ts +1 -1
  52. package/dist/rules/exclusive-max.d.ts +1 -1
  53. package/dist/rules/exclusive-min.d.ts +1 -1
  54. package/dist/rules/format.d.ts +1 -1
  55. package/dist/rules/format.d.ts.map +1 -1
  56. package/dist/rules/index.d.ts +9 -4
  57. package/dist/rules/index.d.ts.map +1 -1
  58. package/dist/rules/max-items.d.ts +1 -1
  59. package/dist/rules/max-length.d.ts +1 -1
  60. package/dist/rules/max.d.ts +1 -1
  61. package/dist/rules/min-items.d.ts +1 -1
  62. package/dist/rules/min-length.d.ts +1 -1
  63. package/dist/rules/min.d.ts +1 -1
  64. package/dist/rules/multiple-of.d.ts +1 -1
  65. package/dist/rules/not-contains.d.ts +1 -1
  66. package/dist/rules/pattern.d.ts +1 -1
  67. package/dist/rules/rule.d.ts +20 -3
  68. package/dist/rules/rule.d.ts.map +1 -1
  69. package/dist/rules/starts-with.d.ts +1 -1
  70. package/dist/with-validation.d.ts +17 -1
  71. package/dist/with-validation.d.ts.map +1 -1
  72. package/docs/README.md +1 -1
  73. package/docs/guide/constraints.md +133 -4
  74. package/docs/guide/errors.md +4 -1
  75. package/docs/roadmap.md +21 -15
  76. package/docs/troubleshooting.md +165 -6
  77. package/package.json +2 -2
  78. package/dist/chunks/index-rbxe5m3y.js.map +0 -37
  79. package/dist/chunks/index-s1kkmg35.js.map +0 -14
package/README.md CHANGED
@@ -19,14 +19,14 @@ Peers, all **required**:
19
19
  | --- | --- |
20
20
  | `graphql` | `^16.11.0 \|\| ^17.0.0` |
21
21
  | `zod` | `>=4.6.5 <5` |
22
- | `typescript` | `^6.0.3` |
22
+ | `typescript` | `^6.0.3 \|\| ^7.0.0` |
23
23
 
24
24
  Your `tsconfig.json` needs:
25
25
 
26
26
  ```jsonc
27
27
  {
28
28
  "compilerOptions": {
29
- "moduleResolution": "bundler" // `nodenext` is not supported
29
+ "moduleResolution": "bundler" // or "nodenext" / "node16", from 0.2.1; "node" / "node10" ignore `exports`
30
30
  }
31
31
  }
32
32
  ```
@@ -83,6 +83,61 @@ request. The resolver receives the parsed arguments.
83
83
  Every argument, its rule and its Zod equivalent, and the formats, are in
84
84
  [Constraints](docs/guide/constraints.md).
85
85
 
86
+ ### Your own formats
87
+
88
+ Name a format of your own in `@constraint(format: "...")`: write each as a Zod
89
+ string schema in one module, and hand the record to `withValidation`.
90
+
91
+ ```ts
92
+ // formats.ts
93
+ import { z } from 'zod';
94
+
95
+ export const formatSchemas = {
96
+ siret: z.string().regex(/^\d{14}$/, 'Invalid SIRET: write its 14 digits'),
97
+ 'work-email': z.email().endsWith('@example.com', 'Use your work address'),
98
+ };
99
+ ```
100
+
101
+ ```ts
102
+ import { makeExecutableSchema } from '@graphql-tools/schema';
103
+ import { constraintTypeDefs, withValidation } from '@nxgt/graphql-validation';
104
+ import { formatSchemas } from './formats';
105
+
106
+ const typeDefs = [
107
+ constraintTypeDefs,
108
+ /* GraphQL */ `
109
+ input Company {
110
+ siret: String! @constraint(format: "siret")
111
+ contact: String @constraint(format: "work-email", maxLength: 64)
112
+ }
113
+ type Mutation { register(input: Company!): Boolean }
114
+ `,
115
+ ];
116
+ const resolvers = { Mutation: { register: () => true } };
117
+
118
+ export const schema = withValidation(
119
+ makeExecutableSchema({ typeDefs, resolvers }),
120
+ { formats: formatSchemas },
121
+ );
122
+ ```
123
+
124
+ A format is `z.string()` or a string format (`z.email()`, …), narrowed as you
125
+ like (`.regex()`, `.refine()`) but never transformed nor rewritten (no
126
+ `.transform()`, `.trim()`, `.toLowerCase()`, `z.url()`, which trims,
127
+ `z.coerce.string()`, …): the resolver receives what the client sent. The one
128
+ exception is the built-in `uri`, `z.url()`, which trims surrounding white
129
+ space (see [Formats](docs/guide/constraints.md#formats)). The other rules
130
+ narrow your format as they narrow a built-in one. Its messages are yours: a
131
+ refusal reads as your schema writes it, and its issue carries
132
+ `constraint: "format"`. A name is lowercase letters, digits and hyphens; a
133
+ built-in name (`email`, …) is a type error and refused at startup, and so is
134
+ a schema that rewrites the value (a custom `.check()` that does fails the
135
+ request, naming the format). An async check (`.refine(async …)`) works in a
136
+ request, and runs once per value. Call `withValidation` once, with every
137
+ format: a second call with the same record wraps nothing; with other
138
+ formats, or none, it throws. Without `formats`, nothing changes. Details in
139
+ [Constraints](docs/guide/constraints.md#your-own-formats).
140
+
86
141
  ### Read the error
87
142
 
88
143
  ```json
@@ -171,12 +226,13 @@ the directive declared is this package's. Both at once throw
171
226
  | Export | Is |
172
227
  | --- | --- |
173
228
  | `constraintTypeDefs` | the SDL of `@constraint`, to add to `typeDefs` |
174
- | `withValidation(schema)` | checks every `@constraint` of a schema before its resolvers run |
229
+ | `withValidation(schema, options?)` | checks every `@constraint` of a schema before its resolvers run; `options.formats` adds your own formats |
175
230
  | `validated(schema \| shape, resolver)` | a resolver whose arguments a Zod schema checks |
176
231
  | `badUserInput(where, zodError)` | builds the error above, for code that validates by hand |
177
232
  | `ValidationIssue`, `BadUserInputExtensions` | the types of `extensions.issues` and `extensions` |
178
233
  | `ConstraintArgument` | the type of `issues[].constraint`: `'minLength' \| 'format' \| ...` |
179
234
  | `ArgsSchema`, `SchemaOf` | the types `validated` accepts |
235
+ | `WithValidationOptions`, `FormatSchemas`, `OwnFormats`, `StringSchema`, `FormatName` | the types of `withValidation`'s options: a record of string schemas, with no built-in format name (`FormatName`) |
180
236
 
181
237
  `@nxgt/graphql-validation/codegen` is for a code generator, such as
182
238
  [`@nxgt/graphql-codegen-zod`](https://www.npmjs.com/package/@nxgt/graphql-codegen-zod): it writes the schemas `withValidation` builds as source, and
@@ -184,9 +240,10 @@ refuses the schemas `withValidation` refuses. A server does not need it.
184
240
 
185
241
  | Export | Is |
186
242
  | --- | --- |
187
- | `checkConstraints(schema, onArgs?)` | runs every startup check of `withValidation` without wrapping anything; returns the `@constraint` directive, or `undefined` when the schema declares none |
243
+ | `checkConstraints(schema, onArgs?, formats?)` | runs every startup check of `withValidation` without wrapping anything; returns the `@constraint` directive, or `undefined` when the schema declares none. `formats` is the record `withValidation` takes, checked the same way |
188
244
  | `constraintsOn(directive, node)` | the `Constraint`s written on an argument or input field (`arg.astNode`), `format` first |
189
- | `inputCode(type, constraints, where, named, options?)` | the schema of that argument or field as source, `z` a free identifier: built-in scalars and lists written in full, nullable types `.nullish()`; every other named type (enum, input object, custom scalar) is what `named(type)` returns. A constraint that cannot apply throws the startup message, naming `where`. `options` (`InputCodeOptions`): `list({ type, code, single })` returns each list's source, given `code` (the array with every rule) and `single` (the list's named type, non-null, without rules), e.g. to take a single value as graphql does: `single`, wrapped, piped into `code` |
245
+ | `inputCode(type, constraints, where, named, options?, formats?)` | the schema of that argument or field as source, `z` a free identifier: built-in scalars and lists written in full, nullable types `.nullish()`; every other named type (enum, input object, custom scalar) is what `named(type)` returns. A constraint that cannot apply throws the startup message, naming `where`. `options` (`InputCodeOptions`): `list({ type, code, single })` returns each list's source, given `code` (the array with every rule) and `single` (the list's named type, non-null, without rules), e.g. to take a single value as graphql does: `single`, wrapped, piped into `code`. `format(name)` returns the source of one of your formats, since only the generator knows where your record is imported from: a reference to your schema, which the other rules narrow (`formatSchemas.siret.max(14)`); built-in formats never reach it. `formats`: the record `withValidation` takes, checked the same way, so its names are known; a field using one of them without the `format` option throws `@constraint(format: "<name>") is one of the application's formats: its source is the code generator's to write, through inputCode's format option.` |
246
+ | `FormatSchemas` | the type of that record |
190
247
  | `Constraint` | `{ rule, value }`, one `@constraint` argument. Of `rule`, read `argument` (`'minItems'`, `'format'`, …), `target` (`'list'` for `minItems`/`maxItems`, else what the rule narrows) and `base` (`true` for `format`, which picks the schema the others narrow); the rest is internal |
191
248
 
192
249
  ```ts
@@ -233,4 +290,4 @@ Every startup error and its fix is in [Troubleshooting](docs/troubleshooting.md)
233
290
  - [Documentation index](docs/README.md)
234
291
  - Guides: [Constraints](docs/guide/constraints.md), [Errors](docs/guide/errors.md)
235
292
  - [Troubleshooting](docs/troubleshooting.md)
236
- - [Roadmap](docs/roadmap.md): a Zod codegen plugin, your own formats, schemas that read the context
293
+ - [Roadmap](docs/roadmap.md): your own formats in the Zod codegen plugin, schemas that read the context
@@ -1,6 +1,6 @@
1
1
  import { GraphQLError } from 'graphql';
2
2
  import type { z } from 'zod';
3
- import type { ConstraintArgument } from './rules';
3
+ import type { ConstraintArgument } from './rules/index.js';
4
4
  /** One reason an input was refused, at its path inside the arguments. */
5
5
  export interface ValidationIssue {
6
6
  /** `['input', 'address', 'zip']`: the argument, then the input fields. */
@@ -1,6 +1,6 @@
1
1
  import type { GraphQLField, GraphQLObjectType } from 'graphql';
2
2
  import { z } from 'zod';
3
- import type { InputSchemas } from './input-schema';
3
+ import type { InputSchemas } from './input-schema.js';
4
4
  /**
5
5
  * The schema of a field's arguments, or `undefined` when none of them can
6
6
  * break a constraint, so a field with nothing to check is left untouched.
@@ -1,5 +1,6 @@
1
1
  import { type GraphQLDirective, type GraphQLField, type GraphQLObjectType, type GraphQLSchema } from 'graphql';
2
2
  import type { z } from 'zod';
3
+ import { type FormatSchemas } from '../formats/registry.js';
3
4
  /**
4
5
  * Runs every check `withValidation` makes at startup, so a code generator
5
6
  * refuses the same schemas: the declaration is `constraintTypeDefs`', every
@@ -8,8 +9,12 @@ import type { z } from 'zod';
8
9
  * default value breaks its own. `onArgs` receives each object field's
9
10
  * arguments schema, for a field with something to check.
10
11
  *
12
+ * `formats` are the application's own, which `@constraint(format: "...")`
13
+ * may name beside the built-in ones; they are checked first (name, no
14
+ * built-in replaced, a Zod string schema each).
15
+ *
11
16
  * Returns the `@constraint` directive, or `undefined` when the schema
12
17
  * declares none (and so carries no constraint).
13
18
  */
14
- export declare function checkConstraints(schema: GraphQLSchema, onArgs?: (type: GraphQLObjectType, field: GraphQLField<unknown, unknown>, args: z.ZodType) => void): GraphQLDirective | undefined;
19
+ export declare function checkConstraints(schema: GraphQLSchema, onArgs?: (type: GraphQLObjectType, field: GraphQLField<unknown, unknown>, args: z.ZodType) => void, formats?: FormatSchemas): GraphQLDirective | undefined;
15
20
  //# sourceMappingURL=check-constraints.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"check-constraints.d.ts","sourceRoot":"","sources":["../../src/builder/check-constraints.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAGlB,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAM7B;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC/B,MAAM,EAAE,aAAa,EACrB,MAAM,CAAC,EAAE,CACR,IAAI,EAAE,iBAAiB,EACvB,KAAK,EAAE,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,EACrC,IAAI,EAAE,CAAC,CAAC,OAAO,KACX,IAAI,GACP,gBAAgB,GAAG,SAAS,CAyB9B"}
1
+ {"version":3,"file":"check-constraints.d.ts","sourceRoot":"","sources":["../../src/builder/check-constraints.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAGlB,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,EAAE,KAAK,aAAa,EAAc,MAAM,qBAAqB,CAAC;AAKrE;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAC/B,MAAM,EAAE,aAAa,EACrB,MAAM,CAAC,EAAE,CACR,IAAI,EAAE,iBAAiB,EACvB,KAAK,EAAE,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,EACrC,IAAI,EAAE,CAAC,CAAC,OAAO,KACX,IAAI,EACT,OAAO,CAAC,EAAE,aAAa,GACrB,gBAAgB,GAAG,SAAS,CAyB9B"}
@@ -1,5 +1,5 @@
1
1
  import { type DirectiveNode, type GraphQLDirective } from 'graphql';
2
- import { type RegisteredRule } from '../rules';
2
+ import { type RegisteredRule } from '../rules/index.js';
3
3
  /** One `@constraint` argument written on an argument or an input field. */
4
4
  export interface Constraint {
5
5
  readonly rule: RegisteredRule;
@@ -1,5 +1,6 @@
1
1
  import { type GraphQLInputType, type GraphQLList, type GraphQLNamedInputType } from 'graphql';
2
- import type { Constraint } from './constraints';
2
+ import { type FormatSchemas } from '../formats/registry.js';
3
+ import type { Constraint } from './constraints.js';
3
4
  /**
4
5
  * The source of the schema of one argument or input field: what
5
6
  * `InputSchemas` builds at startup, written for a code generator. The
@@ -10,8 +11,12 @@ import type { Constraint } from './constraints';
10
11
  * object's or an enum's schema by name, a custom scalar's from a mapping.
11
12
  * The built-in scalars are written here, with their constraints. A
12
13
  * constraint that cannot apply fails as it does at startup.
14
+ *
15
+ * `formats` are the application's own, as `withValidation` takes them: a
16
+ * name among them is a known format, which a built-in one is not. Their
17
+ * source is `options.format`'s to write.
13
18
  */
14
- export declare function inputCode(type: GraphQLInputType, constraints: readonly Constraint[], where: string, named: (type: GraphQLNamedInputType) => string, options?: InputCodeOptions): string;
19
+ export declare function inputCode(type: GraphQLInputType, constraints: readonly Constraint[], where: string, named: (type: GraphQLNamedInputType) => string, options?: InputCodeOptions, formats?: FormatSchemas): string;
15
20
  /** How `inputCode` writes what a generator may want written otherwise. */
16
21
  export interface InputCodeOptions {
17
22
  /**
@@ -25,5 +30,13 @@ export interface InputCodeOptions {
25
30
  readonly code: string;
26
31
  readonly single: string;
27
32
  }) => string;
33
+ /**
34
+ * The source of one of the application's formats, given its name (a key
35
+ * of the `formats` record): a reference to the application's own schema,
36
+ * which the rules after `format` narrow (`formatSchemas.slug.max(40)`).
37
+ * A built-in format is written inline and never reaches it. Without it,
38
+ * a field using one of the application's formats throws.
39
+ */
40
+ readonly format?: (name: string) => string;
28
41
  }
29
42
  //# sourceMappingURL=input-code.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"input-code.d.ts","sourceRoot":"","sources":["../../src/builder/input-code.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,qBAAqB,EAK1B,MAAM,SAAS,CAAC;AAEjB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAGhD;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CACxB,IAAI,EAAE,gBAAgB,EACtB,WAAW,EAAE,SAAS,UAAU,EAAE,EAClC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,CAAC,IAAI,EAAE,qBAAqB,KAAK,MAAM,EAC9C,OAAO,GAAE,gBAAqB,GAC5B,MAAM,CAGR;AAED,0EAA0E;AAC1E,MAAM,WAAW,gBAAgB;IAChC;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE;QACtB,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC,gBAAgB,CAAC,CAAC;QAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;KACxB,KAAK,MAAM,CAAC;CACb"}
1
+ {"version":3,"file":"input-code.d.ts","sourceRoot":"","sources":["../../src/builder/input-code.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,qBAAqB,EAK1B,MAAM,SAAS,CAAC;AACjB,OAAO,EAEN,KAAK,aAAa,EAElB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAGhD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,SAAS,CACxB,IAAI,EAAE,gBAAgB,EACtB,WAAW,EAAE,SAAS,UAAU,EAAE,EAClC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,CAAC,IAAI,EAAE,qBAAqB,KAAK,MAAM,EAC9C,OAAO,GAAE,gBAAqB,EAC9B,OAAO,CAAC,EAAE,aAAa,GACrB,MAAM,CAQR;AAED,0EAA0E;AAC1E,MAAM,WAAW,gBAAgB;IAChC;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE;QACtB,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC,gBAAgB,CAAC,CAAC;QAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;KACxB,KAAK,MAAM,CAAC;IACb;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CAC3C"}
@@ -1,5 +1,6 @@
1
1
  import { type GraphQLArgument, type GraphQLDirective, type GraphQLInputField, type GraphQLInputType, type GraphQLSchema } from 'graphql';
2
2
  import { z } from 'zod';
3
+ import { type FormatRegistry } from '../formats/registry.js';
3
4
  /**
4
5
  * Builds the schemas of the arguments and input fields of one GraphQL
5
6
  * schema, once each: an input type is walked a single time however many
@@ -9,7 +10,8 @@ import { z } from 'zod';
9
10
  export declare class InputSchemas {
10
11
  #private;
11
12
  readonly directive: GraphQLDirective | undefined;
12
- constructor(schema: GraphQLSchema);
13
+ /** `formats`: the built-in ones by default, the application's added. */
14
+ constructor(schema: GraphQLSchema, formats?: FormatRegistry);
13
15
  /** Whether a value of this type can break a constraint anywhere inside. */
14
16
  isConstrained(type: GraphQLInputType): boolean;
15
17
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"input-schema.d.ts","sourceRoot":"","sources":["../../src/builder/input-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EAEtB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAMlB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB;;;;;GAKG;AACH,qBAAa,YAAY;;IACxB,QAAQ,CAAC,SAAS,EAAE,gBAAgB,GAAG,SAAS,CAAC;gBAWrC,MAAM,EAAE,aAAa;IAwBjC,2EAA2E;IAC3E,aAAa,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO;IAK9C;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI;IAMrC,gEAAgE;IAChE,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,iBAAiB,GAAG,OAAO;IAO/D,8EAA8E;IAC9E,EAAE,CAAC,KAAK,EAAE,eAAe,GAAG,iBAAiB,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC,CAAC,OAAO;IAYxE;;;;OAIG;IACH,aAAa,IAAI,IAAI;CAoErB"}
1
+ {"version":3,"file":"input-schema.d.ts","sourceRoot":"","sources":["../../src/builder/input-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EAEtB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAMlB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,KAAK,cAAc,EAAc,MAAM,qBAAqB,CAAC;AAMtE;;;;;GAKG;AACH,qBAAa,YAAY;;IACxB,QAAQ,CAAC,SAAS,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAYjD,wEAAwE;gBAC5D,MAAM,EAAE,aAAa,EAAE,OAAO,GAAE,cAA6B;IAyBzE,2EAA2E;IAC3E,aAAa,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO;IAK9C;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI;IAMrC,gEAAgE;IAChE,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,iBAAiB,GAAG,OAAO;IAO/D,8EAA8E;IAC9E,EAAE,CAAC,KAAK,EAAE,eAAe,GAAG,iBAAiB,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC,CAAC,OAAO;IAYxE;;;;OAIG;IACH,aAAa,IAAI,IAAI;CA+ErB"}
@@ -1,7 +1,7 @@
1
1
  import { type GraphQLLeafType } from 'graphql';
2
2
  import { z } from 'zod';
3
- import type { Target } from '../rules/rule';
4
- import type { Constraint } from './constraints';
3
+ import type { RuleContext, Target } from '../rules/rule.js';
4
+ import type { Constraint } from './constraints.js';
5
5
  /**
6
6
  * Fails unless every constraint applies to a value of `type`: `minLength` on
7
7
  * an `Int`, anything on a custom scalar or an enum, fails, naming `where`.
@@ -16,7 +16,7 @@ export declare function assertObjectTargets(typeName: string, constraints: reado
16
16
  * without any is left as graphql parsed it. A constraint that cannot apply
17
17
  * fails here, at startup.
18
18
  */
19
- export declare function leafSchema(type: GraphQLLeafType, constraints: readonly Constraint[], where: string): z.ZodType;
19
+ export declare function leafSchema(type: GraphQLLeafType, constraints: readonly Constraint[], where: string, context: RuleContext): z.ZodType;
20
20
  /** The source of a built-in scalar's schema, before its constraints. */
21
21
  export declare function baseCode(type: GraphQLLeafType): string | undefined;
22
22
  /** What a rule's target reads as in an error. */
@@ -1 +1 @@
1
- {"version":3,"file":"leaf.d.ts","sourceRoot":"","sources":["../../src/builder/leaf.ts"],"names":[],"mappings":"AAAA,OAAO,EAKN,KAAK,eAAe,EAEpB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAehD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAChC,IAAI,EAAE,eAAe,EACrB,WAAW,EAAE,SAAS,UAAU,EAAE,EAClC,KAAK,EAAE,MAAM,GACX,IAAI,CAMN;AAED,2EAA2E;AAC3E,wBAAgB,mBAAmB,CAClC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,SAAS,UAAU,EAAE,EAClC,KAAK,EAAE,MAAM,GACX,IAAI,CAIN;AAYD;;;;GAIG;AACH,wBAAgB,UAAU,CACzB,IAAI,EAAE,eAAe,EACrB,WAAW,EAAE,SAAS,UAAU,EAAE,EAClC,KAAK,EAAE,MAAM,GACX,CAAC,CAAC,OAAO,CAQX;AAED,wEAAwE;AACxE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,GAAG,SAAS,CAMlE;AAED,iDAAiD;AACjD,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAIrD"}
1
+ {"version":3,"file":"leaf.d.ts","sourceRoot":"","sources":["../../src/builder/leaf.ts"],"names":[],"mappings":"AAAA,OAAO,EAKN,KAAK,eAAe,EAEpB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAehD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAChC,IAAI,EAAE,eAAe,EACrB,WAAW,EAAE,SAAS,UAAU,EAAE,EAClC,KAAK,EAAE,MAAM,GACX,IAAI,CAMN;AAED,2EAA2E;AAC3E,wBAAgB,mBAAmB,CAClC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,SAAS,UAAU,EAAE,EAClC,KAAK,EAAE,MAAM,GACX,IAAI,CAIN;AAYD;;;;GAIG;AACH,wBAAgB,UAAU,CACzB,IAAI,EAAE,eAAe,EACrB,WAAW,EAAE,SAAS,UAAU,EAAE,EAClC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,WAAW,GAClB,CAAC,CAAC,OAAO,CAQX;AAED,wEAAwE;AACxE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,GAAG,SAAS,CAMlE;AAED,iDAAiD;AACjD,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAIrD"}
@@ -1,8 +1,10 @@
1
1
  import {
2
+ __export,
3
+ RULE_PARAM,
2
4
  rules,
3
5
  applyRule,
4
6
  assertOwnConstraint
5
- } from "./index-rbxe5m3y.js";
7
+ } from "./index-xcaf9kes.js";
6
8
 
7
9
  // src/builder/constraints.ts
8
10
  import {
@@ -24,12 +26,196 @@ import {
24
26
  isObjectType
25
27
  } from "graphql";
26
28
 
27
- // src/builder/args-schema.ts
29
+ // src/formats/registry.ts
30
+ import { z as z9 } from "zod";
31
+
32
+ // src/formats/all.ts
33
+ var exports_all = {};
34
+ __export(exports_all, {
35
+ byteFormat: () => byteFormat,
36
+ dateFormat: () => dateFormat,
37
+ dateTimeFormat: () => dateTimeFormat,
38
+ emailFormat: () => emailFormat,
39
+ ipv4Format: () => ipv4Format,
40
+ ipv6Format: () => ipv6Format,
41
+ uriFormat: () => uriFormat,
42
+ uuidFormat: () => uuidFormat
43
+ });
44
+
45
+ // src/formats/byte.ts
28
46
  import { z } from "zod";
47
+
48
+ // src/formats/format.ts
49
+ function defineFormat(format) {
50
+ return format;
51
+ }
52
+
53
+ // src/formats/byte.ts
54
+ var byteFormat = defineFormat({
55
+ name: "byte",
56
+ toZod: () => z.base64(),
57
+ toCode: () => "z.base64()"
58
+ });
59
+ // src/formats/date.ts
60
+ import { z as z2 } from "zod";
61
+ var dateFormat = defineFormat({
62
+ name: "date",
63
+ toZod: () => z2.iso.date(),
64
+ toCode: () => "z.iso.date()"
65
+ });
66
+ // src/formats/date-time.ts
67
+ import { z as z3 } from "zod";
68
+ var dateTimeFormat = defineFormat({
69
+ name: "date-time",
70
+ toZod: () => z3.iso.datetime({ offset: true }),
71
+ toCode: () => "z.iso.datetime({ offset: true })"
72
+ });
73
+ // src/formats/email.ts
74
+ import { z as z4 } from "zod";
75
+ var emailFormat = defineFormat({
76
+ name: "email",
77
+ toZod: () => z4.email(),
78
+ toCode: () => "z.email()"
79
+ });
80
+ // src/formats/ipv4.ts
81
+ import { z as z5 } from "zod";
82
+ var ipv4Format = defineFormat({
83
+ name: "ipv4",
84
+ toZod: () => z5.ipv4(),
85
+ toCode: () => "z.ipv4()"
86
+ });
87
+ // src/formats/ipv6.ts
88
+ import { z as z6 } from "zod";
89
+ var ipv6Format = defineFormat({
90
+ name: "ipv6",
91
+ toZod: () => z6.ipv6(),
92
+ toCode: () => "z.ipv6()"
93
+ });
94
+ // src/formats/uri.ts
95
+ import { z as z7 } from "zod";
96
+ var uriFormat = defineFormat({
97
+ name: "uri",
98
+ toZod: () => z7.url({ protocol: /^(https?|ftp)$/ }),
99
+ toCode: () => "z.url({ protocol: /^(https?|ftp)$/ })"
100
+ });
101
+ // src/formats/uuid.ts
102
+ import { z as z8 } from "zod";
103
+ var uuidFormat = defineFormat({
104
+ name: "uuid",
105
+ toZod: () => z8.uuid(),
106
+ toCode: () => "z.uuid()"
107
+ });
108
+ // src/formats/index.ts
109
+ var formats = Object.fromEntries(Object.values(exports_all).map((format) => [format.name, format]));
110
+
111
+ // src/formats/registry.ts
112
+ var NAME = /^[a-z][a-z0-9-]*$/;
113
+
114
+ class FormatRegistry {
115
+ #formats = new Map;
116
+ #async;
117
+ constructor(own = {}) {
118
+ for (const format of Object.values(formats))
119
+ this.#formats.set(format.name, format);
120
+ for (const [name, schema] of Object.entries(own)) {
121
+ if (!NAME.test(name)) {
122
+ throw new Error(`Invalid format name ${JSON.stringify(name)}: write it in lowercase letters, digits and hyphens, starting with a letter.`);
123
+ }
124
+ if (this.#formats.has(name)) {
125
+ throw new Error(`The format "${name}" is built in: give yours another name.`);
126
+ }
127
+ if (!isStringSchema(schema)) {
128
+ throw new Error(`The format "${name}" is not a Zod string schema: write z.string() or a string format such as z.email(), narrowed with .regex() or .refine(), never transformed.`);
129
+ }
130
+ if (!runnable(schema)) {
131
+ throw new Error(`The format "${name}" cannot be run: its definition reads type: 'string' but it has no _zod.run, so it was not built by zod 4. Pass the schema z.string() or a string format returns, not an object shaped like one.`);
132
+ }
133
+ if (rewrites(schema)) {
134
+ throw new Error(`The format "${name}" rewrites the value (.trim(), .toLowerCase(), .toUpperCase(), .normalize(), .slugify(), .overwrite(), z.url(), z.httpUrl() or z.coerce.string()): refuse what is not canonical with .regex() or .refine() instead, so the server and the client check the value as it was sent.`);
135
+ }
136
+ const own = marked(name, schema, () => {
137
+ this.#async = name;
138
+ });
139
+ this.#formats.set(name, { name, toZod: () => own });
140
+ }
141
+ }
142
+ named(name) {
143
+ const format = this.#formats.get(name);
144
+ if (format)
145
+ return format;
146
+ throw new Error(`Unknown @constraint format "${name}". Known formats: ${[...this.#formats.keys()].join(", ")}.`);
147
+ }
148
+ takeAsync() {
149
+ const name = this.#async;
150
+ this.#async = undefined;
151
+ return name;
152
+ }
153
+ }
154
+ function isStringSchema(value) {
155
+ return definition(value)?.type === "string";
156
+ }
157
+ function runnable(schema) {
158
+ return typeof schema?._zod?.run === "function";
159
+ }
160
+ function rewrites(schema) {
161
+ const def = definition(schema);
162
+ if (def?.coerce || def?.format === "url")
163
+ return true;
164
+ return (def?.checks ?? []).some((check) => {
165
+ const own = definition(check);
166
+ return own?.check === "overwrite" || own?.format === "url";
167
+ });
168
+ }
169
+ function definition(value) {
170
+ return value?._zod?.def;
171
+ }
172
+ function marked(name, schema, onAsync) {
173
+ return z9.string().superRefine((value, ctx) => {
174
+ const raise = (result) => {
175
+ if (result.issues.length === 0 && result.value !== value) {
176
+ throw new Error(`The format "${name}" rewrote the value it checked: a format checks the value and never changes it, so the server and the client check the value as it was sent. Refuse what is not canonical with .regex() or .refine() instead of setting payload.value in a .check().`);
177
+ }
178
+ for (const raw of result.issues) {
179
+ const issue = z9.core.util.finalizeIssue(raw, RUN, z9.core.config());
180
+ const params = "params" in issue ? issue.params : undefined;
181
+ ctx.addIssue({
182
+ ...issue,
183
+ params: { ...params, [RULE_PARAM]: "format" },
184
+ ...raw.continue === false && { continue: false }
185
+ });
186
+ }
187
+ };
188
+ const result = schema._zod.run({ value, issues: [] }, RUN);
189
+ if (!(result instanceof Promise)) {
190
+ raise(result);
191
+ return;
192
+ }
193
+ const pending = result.then(raise);
194
+ pending.catch(() => {});
195
+ onAsync();
196
+ return pending;
197
+ });
198
+ }
199
+ var RUN = { async: true };
200
+ var builtInRegistry = new FormatRegistry;
201
+ var registries = new WeakMap;
202
+ function registryOf(own) {
203
+ if (!own)
204
+ return builtInRegistry;
205
+ let registry = registries.get(own);
206
+ if (!registry) {
207
+ registry = new FormatRegistry(own);
208
+ registries.set(own, registry);
209
+ }
210
+ return registry;
211
+ }
212
+
213
+ // src/builder/args-schema.ts
214
+ import { z as z10 } from "zod";
29
215
  function argsSchemaOf(inputs, parent, field) {
30
216
  if (!field.args.some((arg) => inputs.needsCheck(arg)))
31
217
  return;
32
- return z.object(Object.fromEntries(field.args.map((arg) => [
218
+ return z10.object(Object.fromEntries(field.args.map((arg) => [
33
219
  arg.name,
34
220
  inputs.of(arg, `${parent}.${field.name}(${arg.name}:)`)
35
221
  ])));
@@ -65,7 +251,7 @@ import {
65
251
  isNonNullType,
66
252
  valueFromAST
67
253
  } from "graphql";
68
- import { z as z3 } from "zod";
254
+ import { z as z12 } from "zod";
69
255
 
70
256
  // src/builder/leaf.ts
71
257
  import {
@@ -75,7 +261,7 @@ import {
75
261
  GraphQLInt,
76
262
  GraphQLString
77
263
  } from "graphql";
78
- import { z as z2 } from "zod";
264
+ import { z as z11 } from "zod";
79
265
  var kinds = {
80
266
  [GraphQLString.name]: "string",
81
267
  [GraphQLID.name]: "string",
@@ -84,8 +270,8 @@ var kinds = {
84
270
  };
85
271
  function baseOf(type) {
86
272
  if (type.name === GraphQLInt.name)
87
- return z2.number().int();
88
- return kinds[type.name] === "number" ? z2.number() : z2.string();
273
+ return z11.number().int();
274
+ return kinds[type.name] === "number" ? z11.number() : z11.string();
89
275
  }
90
276
  function assertLeafTargets(type, constraints, where) {
91
277
  const misplaced = constraints.find(({ rule }) => rule.target === "list");
@@ -104,13 +290,13 @@ function assertObjectTargets(typeName, constraints, where) {
104
290
  function misplacedOn({ rule }, where, typeName) {
105
291
  return new Error(`@constraint(${rule.argument}) on ${where} needs ${describeTarget(rule.target)}, not ${typeName}.`);
106
292
  }
107
- function leafSchema(type, constraints, where) {
293
+ function leafSchema(type, constraints, where, context) {
108
294
  if (constraints.length === 0)
109
- return z2.unknown();
295
+ return z11.unknown();
110
296
  assertLeafTargets(type, constraints, where);
111
297
  let schema = baseOf(type);
112
298
  for (const { rule, value } of constraints) {
113
- schema = applyRule(rule, schema, value);
299
+ schema = applyRule(rule, schema, value, context);
114
300
  }
115
301
  return schema;
116
302
  }
@@ -134,10 +320,12 @@ function describeTarget(target) {
134
320
  // src/builder/input-schema.ts
135
321
  class InputSchemas {
136
322
  directive;
323
+ #context;
137
324
  #objects = new Map;
138
325
  #defaults = [];
139
326
  #constrained = new Set;
140
- constructor(schema) {
327
+ constructor(schema, formats = registryOf()) {
328
+ this.#context = { formats };
141
329
  this.directive = schema.getDirective("constraint") ?? undefined;
142
330
  const objects = Object.values(schema.getTypeMap()).filter(isInputObjectType);
143
331
  for (let changed = true;changed; ) {
@@ -178,7 +366,15 @@ class InputSchemas {
178
366
  const value = valueFromAST(literal, type);
179
367
  if (value === undefined)
180
368
  continue;
181
- const result = schema.safeParse(value);
369
+ let result;
370
+ try {
371
+ result = schema.safeParse(value);
372
+ } catch (error) {
373
+ if (!(error instanceof z12.core.$ZodAsyncError))
374
+ throw error;
375
+ const format = this.#context.formats.takeAsync();
376
+ throw new Error(`The default value of ${where} cannot be checked at startup: ${format ? `the format "${format}"` : "its format"} checks asynchronously, and a default value is checked synchronously. Drop the default, or move the async check out of the format into validated().`);
377
+ }
182
378
  if (!result.success) {
183
379
  throw new Error(`The default value of ${where} breaks its @constraint: ${result.error.issues[0]?.message}`);
184
380
  }
@@ -195,26 +391,26 @@ class InputSchemas {
195
391
  if (isListType(type)) {
196
392
  const own = constraints.filter(({ rule }) => rule.target === "list");
197
393
  const items = constraints.filter(({ rule }) => rule.target !== "list");
198
- let list = z3.array(this.#typed(type.ofType, items, where));
394
+ let list = z12.array(this.#typed(type.ofType, items, where));
199
395
  for (const { rule, value } of own)
200
- list = applyRule(rule, list, value);
396
+ list = applyRule(rule, list, value, this.#context);
201
397
  return list;
202
398
  }
203
399
  if (isInputObjectType(type)) {
204
400
  assertObjectTargets(type.name, constraints, where);
205
401
  return this.#object(type);
206
402
  }
207
- return leafSchema(type, constraints, where);
403
+ return leafSchema(type, constraints, where, this.#context);
208
404
  }
209
405
  #object(type) {
210
406
  if (!this.isConstrained(type))
211
- return z3.unknown();
407
+ return z12.unknown();
212
408
  const built = this.#objects.get(type.name);
213
409
  if (built)
214
410
  return built;
215
411
  let schema;
216
- this.#objects.set(type.name, z3.lazy(() => schema));
217
- schema = z3.object(Object.fromEntries(Object.values(type.getFields()).map((field) => [
412
+ this.#objects.set(type.name, z12.lazy(() => schema));
413
+ schema = z12.object(Object.fromEntries(Object.values(type.getFields()).map((field) => [
218
414
  field.name,
219
415
  this.of(field, `${type.name}.${field.name}`)
220
416
  ])));
@@ -224,8 +420,8 @@ class InputSchemas {
224
420
  }
225
421
 
226
422
  // src/builder/check-constraints.ts
227
- function checkConstraints2(schema, onArgs) {
228
- const inputs = new InputSchemas(schema);
423
+ function checkConstraints2(schema, onArgs, formats) {
424
+ const inputs = new InputSchemas(schema, registryOf(formats));
229
425
  if (!inputs.directive)
230
426
  return;
231
427
  assertOwnConstraint(inputs.directive);
@@ -260,7 +456,7 @@ function assertNoDirectiveArgumentConstrained(inputs, schema) {
260
456
  }
261
457
  }
262
458
 
263
- export { constraintsOn2, assertLeafTargets, assertObjectTargets, baseCode, checkConstraints2 };
459
+ export { registryOf, constraintsOn2, assertLeafTargets, assertObjectTargets, baseCode, checkConstraints2 };
264
460
 
265
- //# debugId=7C317F3D691975BE64756E2164756E21
266
- //# sourceMappingURL=index-s1kkmg35.js.map
461
+ //# debugId=3026B70F7346E03264756E2164756E21
462
+ //# sourceMappingURL=index-1mzzydy3.js.map