@nxgt/graphql-codegen-zod 0.1.0 → 0.2.1
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.
- package/README.md +98 -10
- package/dist/collect-fields.d.ts +24 -0
- package/dist/collect-fields.d.ts.map +1 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/cycles.d.ts +7 -0
- package/dist/cycles.d.ts.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +530 -133
- package/dist/index.js.map +12 -7
- package/dist/naming.d.ts +5 -1
- package/dist/naming.d.ts.map +1 -1
- package/dist/objects-output.d.ts +16 -0
- package/dist/objects-output.d.ts.map +1 -0
- package/dist/output-types.d.ts +15 -0
- package/dist/output-types.d.ts.map +1 -0
- package/dist/results-output.d.ts +16 -0
- package/dist/results-output.d.ts.map +1 -0
- package/dist/results-selections.d.ts +21 -0
- package/dist/results-selections.d.ts.map +1 -0
- package/dist/scalars.d.ts +2 -2
- package/dist/schema-output.d.ts +2 -2
- package/dist/source.d.ts +2 -2
- package/dist/type-code.d.ts +2 -2
- package/dist/variables-output.d.ts +3 -3
- package/dist/writer.d.ts +2 -2
- package/docs/README.md +1 -1
- package/docs/guide/output.md +279 -7
- package/docs/roadmap.md +8 -5
- package/docs/troubleshooting.md +65 -6
- package/package.json +4 -3
package/docs/guide/output.md
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
# Output
|
|
2
2
|
|
|
3
3
|
What the plugin writes from your schema and operations: the schemas, the
|
|
4
|
-
types, their names, and how defaults, recursion, `@oneOf
|
|
5
|
-
come out.
|
|
4
|
+
types, their names, and how defaults, recursion, `@oneOf`, custom scalars,
|
|
5
|
+
output types and operation results come out.
|
|
6
6
|
|
|
7
7
|
## The smallest example
|
|
8
8
|
|
|
9
|
+
The SDL files for `@constraint` and the scalars, and how to run codegen, are in
|
|
10
|
+
the README's [Setup](../../README.md#setup).
|
|
11
|
+
|
|
9
12
|
```graphql
|
|
10
13
|
# schema
|
|
11
14
|
input SignUpInput {
|
|
@@ -41,8 +44,11 @@ export default config;
|
|
|
41
44
|
```
|
|
42
45
|
|
|
43
46
|
The plugin writes, in this order: every enum, every input type, the
|
|
44
|
-
arguments of every object or interface field that takes any,
|
|
45
|
-
|
|
47
|
+
arguments of every object or interface field that takes any, every object
|
|
48
|
+
type, then every interface and union (see [Output types](#output-types)),
|
|
49
|
+
then the variables of every named operation. The results come last: every
|
|
50
|
+
fragment's, then every named operation's (see
|
|
51
|
+
[Operation results](#operation-results)). Each is a schema and its type.
|
|
46
52
|
|
|
47
53
|
```ts
|
|
48
54
|
import { z } from "zod";
|
|
@@ -65,6 +71,12 @@ export const zMutationSignUpArgs = z.object({
|
|
|
65
71
|
});
|
|
66
72
|
export type MutationSignUpArgs = z.output<typeof zMutationSignUpArgs>;
|
|
67
73
|
|
|
74
|
+
export const zMutation = z.object({
|
|
75
|
+
__typename: z.literal("Mutation").optional(),
|
|
76
|
+
signUp: z.boolean().nullish(),
|
|
77
|
+
});
|
|
78
|
+
export type Mutation = z.output<typeof zMutation>;
|
|
79
|
+
|
|
68
80
|
export const zSignUpMutationVariables = z.object({
|
|
69
81
|
get input() {
|
|
70
82
|
return zSignUpInput;
|
|
@@ -99,6 +111,9 @@ export const zQueryUserArgs = z.object({
|
|
|
99
111
|
| `z<Input>` | an `input` | `z.output` | the value a resolver receives |
|
|
100
112
|
| `z<Parent><Field>Args` | an object or interface field with arguments | `z.output` | typing the resolver's arguments |
|
|
101
113
|
| `z<Operation><Kind>Variables` | a named operation in `documents` | `z.input` | checking a form before sending it |
|
|
114
|
+
| `z<Type>` | an object type, interface or union | `z.output` | typing what a resolver returns; `z.encode` checks it |
|
|
115
|
+
| `z<Operation><Kind>` | a named operation in `documents` | `z.output` | parsing the response on the client |
|
|
116
|
+
| `z<Fragment>Fragment` | a fragment in `documents` | `z.output` | parsing the part of a response a fragment selects |
|
|
102
117
|
|
|
103
118
|
`z.output` is what a resolver receives after `withValidation` parsed the
|
|
104
119
|
arguments; `z.input` is what a client sends. They differ where a default
|
|
@@ -139,6 +154,9 @@ and `typescript-operations` give them.
|
|
|
139
154
|
| enum | `zRole` | `Role` |
|
|
140
155
|
| field arguments | `zMutationSignUpArgs` | `MutationSignUpArgs` |
|
|
141
156
|
| operation variables | `zSignUpMutationVariables` | `SignUpMutationVariables` |
|
|
157
|
+
| object type, interface, union | `zUser` | `User` |
|
|
158
|
+
| operation result | `zSearchQuery` | `SearchQuery` |
|
|
159
|
+
| fragment | `zUserFieldsFragment` | `UserFieldsFragment` |
|
|
142
160
|
|
|
143
161
|
| Option | Type | Default | Effect |
|
|
144
162
|
| --- | --- | --- | --- |
|
|
@@ -147,8 +165,8 @@ and `typescript-operations` give them.
|
|
|
147
165
|
| `typesPrefix` | `string` | `''` | before each type's name: `ISignUpInput`; the schema's name follows: `zISignUpInput` |
|
|
148
166
|
| `typesSuffix` | `string` | `''` | after each type's name |
|
|
149
167
|
| `addUnderscoreToArgsType` | `boolean` | `false` | `Query_FindUserArgs` instead of `QueryFindUserArgs` |
|
|
150
|
-
| `dedupeOperationSuffix` | `boolean` | `false` | `findUserQuery` gives `FindUserQueryVariables`, not `FindUserQueryQueryVariables` |
|
|
151
|
-
| `omitOperationSuffix` | `boolean` | `false` | `FindUserVariables`: no `Query`, `Mutation` or `
|
|
168
|
+
| `dedupeOperationSuffix` | `boolean` | `false` | `findUserQuery` gives `FindUserQueryVariables` and `FindUserQuery`, not `FindUserQueryQueryVariables` and `FindUserQueryQuery`; a fragment `UFFragment` gives `UfFragment` |
|
|
169
|
+
| `omitOperationSuffix` | `boolean` | `false` | `FindUserVariables`, `FindUser`: no `Query`, `Mutation`, `Subscription` or `Fragment` |
|
|
152
170
|
|
|
153
171
|
```ts
|
|
154
172
|
config: {
|
|
@@ -181,7 +199,9 @@ Any other module string (`'my-module#myCase'`) throws, see
|
|
|
181
199
|
|
|
182
200
|
Generate into a file the typescript plugins do not write to: the names would
|
|
183
201
|
collide. This plugin's types can replace the typescript plugin's `*Args` and
|
|
184
|
-
`*Variables`,
|
|
202
|
+
`*Variables`, and typescript-operations' `*Query`, `*Mutation` and `*Fragment`,
|
|
203
|
+
so the other plugins can be limited to the output types. Or set
|
|
204
|
+
`operations: false`, and keep those from typescript-operations.
|
|
185
205
|
|
|
186
206
|
## Defaults
|
|
187
207
|
|
|
@@ -430,6 +450,258 @@ price: moneySchema,
|
|
|
430
450
|
- A rule on a custom scalar (`@constraint` on a `DateTime` field) is refused,
|
|
431
451
|
as `withValidation` refuses it; check the value in the scalar's own schema.
|
|
432
452
|
|
|
453
|
+
## Output types
|
|
454
|
+
|
|
455
|
+
Every object type gets a schema and a type, typed `z.output`: what a
|
|
456
|
+
resolver returns. The root types are written too: a field may return
|
|
457
|
+
`Query` (a mutation payload's `query: Query!`). Then every interface and union, as a `z.union` of its
|
|
458
|
+
object types. `objects: false` writes none of them.
|
|
459
|
+
|
|
460
|
+
```graphql
|
|
461
|
+
"""Someone with an account."""
|
|
462
|
+
type User implements Node {
|
|
463
|
+
id: ID!
|
|
464
|
+
name: String!
|
|
465
|
+
role: Role
|
|
466
|
+
"Who they follow."
|
|
467
|
+
friends: [User!]
|
|
468
|
+
pinned: SearchResult
|
|
469
|
+
joined: DateTime!
|
|
470
|
+
}
|
|
471
|
+
union SearchResult = User | Post
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
A type outside any cycle is a `z.object`, its type inferred:
|
|
475
|
+
|
|
476
|
+
```ts
|
|
477
|
+
export const zMutation = z.object({
|
|
478
|
+
__typename: z.literal("Mutation").optional(),
|
|
479
|
+
signUp: z.boolean().nullish(),
|
|
480
|
+
});
|
|
481
|
+
export type Mutation = z.output<typeof zMutation>;
|
|
482
|
+
|
|
483
|
+
export const zSearchResult = z.union([zUser, zPost]);
|
|
484
|
+
export type SearchResult = z.output<typeof zSearchResult>;
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
An interface no object type implements is `z.never()`; a union of one
|
|
488
|
+
member is that member's schema.
|
|
489
|
+
|
|
490
|
+
A type in a cycle (`User.friends: [User!]`, or `User` → `Org` → `User`) has
|
|
491
|
+
its types written out, as an input type in a cycle has: TypeScript infers a
|
|
492
|
+
looping schema only so deep, and fails with TS2589 on a loop of ten types.
|
|
493
|
+
`User` is what a resolver returns, `UserWire` the wire value (not
|
|
494
|
+
`UserInput`, which an SDL `input UserInput` often already takes), and `zUser`
|
|
495
|
+
is typed by both:
|
|
496
|
+
|
|
497
|
+
```ts
|
|
498
|
+
/** Someone with an account. */
|
|
499
|
+
export type User = {
|
|
500
|
+
__typename?: "User" | undefined;
|
|
501
|
+
id: string;
|
|
502
|
+
name: string;
|
|
503
|
+
role?: z.output<typeof zRole> | null | undefined;
|
|
504
|
+
/** Who they follow. */
|
|
505
|
+
friends?: Array<User> | null | undefined;
|
|
506
|
+
pinned?: z.output<typeof zSearchResult> | null | undefined;
|
|
507
|
+
joined: z.output<typeof scalarSchemas.DateTime>;
|
|
508
|
+
};
|
|
509
|
+
/** Someone with an account. */
|
|
510
|
+
export type UserWire = {
|
|
511
|
+
/* the same, with z.input for scalars, UserWire, z.input<typeof zSearchResult> */
|
|
512
|
+
};
|
|
513
|
+
/** Someone with an account. */
|
|
514
|
+
export const zUser: z.ZodType<User, UserWire> = z.object({
|
|
515
|
+
__typename: z.literal("User").optional(),
|
|
516
|
+
id: z.string(),
|
|
517
|
+
name: z.string(),
|
|
518
|
+
role: zRole.nullish(),
|
|
519
|
+
/** Who they follow. */
|
|
520
|
+
get friends() {
|
|
521
|
+
return z.array(zUser).nullish();
|
|
522
|
+
},
|
|
523
|
+
get pinned() {
|
|
524
|
+
return zSearchResult.nullish();
|
|
525
|
+
},
|
|
526
|
+
joined: scalarSchemas.DateTime,
|
|
527
|
+
});
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
`zUser` is then a `z.ZodType`, not a `z.ZodObject`: `.shape`, `.pick` and
|
|
531
|
+
`.extend` are not offered on a type in a cycle.
|
|
532
|
+
|
|
533
|
+
- **`__typename` is optional:** a resolver rarely sets it. When it is set, it
|
|
534
|
+
must be the type's name.
|
|
535
|
+
- **A field the type does not declare is dropped** (`z.object`): a newer
|
|
536
|
+
server's extra field does not fail a parse.
|
|
537
|
+
- **Custom scalars are decoded:** `User['joined']` is a `Date` with
|
|
538
|
+
`@nxgt/graphql-scalars`; `z.input<typeof zUser>` has the wire `string`.
|
|
539
|
+
- **Every field is written**, arguments ignored: `User` is the whole type, as
|
|
540
|
+
`@graphql-codegen/typescript` writes it.
|
|
541
|
+
- **Nesting goes through getters**, so types refer to each other in any
|
|
542
|
+
order. Interfaces and unions are declared after every object type.
|
|
543
|
+
- `Int` is `z.int32()` and `ID` a `z.string()`, as for inputs. A nullable
|
|
544
|
+
field is `.nullish()`.
|
|
545
|
+
- **Every custom scalar needs a schema**, an output-only one included
|
|
546
|
+
(`createdAt: DateTime` on an object type): generation fails naming it,
|
|
547
|
+
as for inputs. 0.1.0 did not read output fields, and nor operations'
|
|
548
|
+
results; 0.1.0's output needs both `objects: false` and `operations: false`.
|
|
549
|
+
- **A name the plugin already writes clashes:** an object type named
|
|
550
|
+
`QueryFindArgs`, or `FilterInput` beside a cyclic input `Filter`, fails
|
|
551
|
+
generation with "would declare … twice". Rename it, set `typesSuffix`, or
|
|
552
|
+
set `objects: false` (or `operations: false`, for a clash with a result
|
|
553
|
+
such as `SearchQuery`).
|
|
554
|
+
|
|
555
|
+
## Operation results
|
|
556
|
+
|
|
557
|
+
Each fragment and each named operation gets a schema and a type, typed
|
|
558
|
+
`z.output`: the response as it arrives, under the aliases the document gave.
|
|
559
|
+
`operations: false` writes none of them; the variables stay. An anonymous
|
|
560
|
+
operation gets none, as for variables.
|
|
561
|
+
|
|
562
|
+
```graphql
|
|
563
|
+
query Search($text: String!, $id: ID!, $withTags: Boolean!) {
|
|
564
|
+
search(text: $text) {
|
|
565
|
+
__typename
|
|
566
|
+
... on User { id who: name joined role }
|
|
567
|
+
... on Post { id title tags @include(if: $withTags) author { ...UserFields } }
|
|
568
|
+
}
|
|
569
|
+
node(id: $id) { __typename id kind: __typename }
|
|
570
|
+
}
|
|
571
|
+
fragment UserFields on User { id name }
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
```ts
|
|
575
|
+
export const zSearchQuery = z.object({
|
|
576
|
+
search: z.array(z.discriminatedUnion("__typename", [
|
|
577
|
+
z.object({
|
|
578
|
+
__typename: z.literal("User"),
|
|
579
|
+
id: z.string(),
|
|
580
|
+
who: z.string(),
|
|
581
|
+
joined: scalarSchemas.DateTime,
|
|
582
|
+
role: zRole.nullable(),
|
|
583
|
+
}),
|
|
584
|
+
z.object({
|
|
585
|
+
__typename: z.literal("Post"),
|
|
586
|
+
id: z.string(),
|
|
587
|
+
title: z.string(),
|
|
588
|
+
tags: z.array(z.array(z.string().nullable()).nullable()).nullable().optional(),
|
|
589
|
+
author: z.object({
|
|
590
|
+
id: z.string(),
|
|
591
|
+
name: z.string(),
|
|
592
|
+
}),
|
|
593
|
+
}),
|
|
594
|
+
])),
|
|
595
|
+
node: z.object({
|
|
596
|
+
__typename: z.enum(["User", "Post"]),
|
|
597
|
+
id: z.string(),
|
|
598
|
+
kind: z.enum(["User", "Post"]),
|
|
599
|
+
}).nullable(),
|
|
600
|
+
});
|
|
601
|
+
export type SearchQuery = z.output<typeof zSearchQuery>;
|
|
602
|
+
|
|
603
|
+
export const zUserFieldsFragment = z.object({
|
|
604
|
+
id: z.string(),
|
|
605
|
+
name: z.string(),
|
|
606
|
+
});
|
|
607
|
+
export type UserFieldsFragment = z.output<typeof zUserFieldsFragment>;
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
- **Custom scalars are decoded:** `joined` is a `Date` with
|
|
611
|
+
`@nxgt/graphql-scalars`.
|
|
612
|
+
- **`z.object`:** a field the selection does not name is dropped, so a newer
|
|
613
|
+
server's extra field does not fail a parse.
|
|
614
|
+
- **A fragment spread is inlined** into the operation: `author` holds
|
|
615
|
+
`UserFields`' `id` and `name`. A fragment has its own schema too, and the
|
|
616
|
+
inlined one is not a reference to it.
|
|
617
|
+
- **An abstract type's selection is a `z.discriminatedUnion` on
|
|
618
|
+
`__typename`** when its possible types select different fields, and then
|
|
619
|
+
`__typename` must be selected for every member. Without it generation
|
|
620
|
+
fails, see [Troubleshooting](../troubleshooting.md). A `__typename` under
|
|
621
|
+
`@skip` or `@include` does not count.
|
|
622
|
+
- **Possible types that select the same fields share one member**, with
|
|
623
|
+
`__typename: z.enum([...])`: `node` selects only `id` and `kind`, the same
|
|
624
|
+
for `User` and `Post`. A single group is a plain object, not a union.
|
|
625
|
+
- **When every possible type selects the same fields, `__typename` is not
|
|
626
|
+
needed:** the result is one plain object. `plain: node(id: $id) { id }`
|
|
627
|
+
gives `plain: z.object({ id: z.string() }).nullable()`. Selected there,
|
|
628
|
+
`__typename` is `z.enum([...])`.
|
|
629
|
+
- **An aliased `__typename` is the discriminator under its alias:** the
|
|
630
|
+
union is discriminated on `kind` if the document selects only
|
|
631
|
+
`kind: __typename`. The members must select it under one key; when several
|
|
632
|
+
keys select it, `__typename` is preferred. Otherwise generation fails.
|
|
633
|
+
- **A field under `@skip`, `@include` or `@defer` is `.optional()`:** `tags`
|
|
634
|
+
may be absent. A field selected both with and without the directive is
|
|
635
|
+
required, but a sub-field that only the conditional node selects stays
|
|
636
|
+
optional: with `friends { id } friends @include(if: $v) { name }`, `name`
|
|
637
|
+
is optional. A field selected only under the directive is optional, and
|
|
638
|
+
its own fields are not: `maybe: user(id: $id) @include(if: $v) { id }`
|
|
639
|
+
gives `{ id: string } | null | undefined`.
|
|
640
|
+
- **A nullable field is `.nullable()`**, not `.nullish()`: a response holds
|
|
641
|
+
null, not undefined.
|
|
642
|
+
- `Int` is `z.int32()` and `ID` a `z.string()`, as elsewhere. An enum is its
|
|
643
|
+
schema (`zRole`).
|
|
644
|
+
- **An introspection field** (`__schema`, `__type`) is `z.unknown()`.
|
|
645
|
+
|
|
646
|
+
**Deep selections.** Past five nested levels, a selection is declared apart,
|
|
647
|
+
so TypeScript infers an operation of any depth (it failed with TS2589 at
|
|
648
|
+
fourteen levels). The helpers are not exported; identical deep selections
|
|
649
|
+
share one:
|
|
650
|
+
|
|
651
|
+
```ts
|
|
652
|
+
const zDeepQuery$1$ = z.object({ /* the selection */ });
|
|
653
|
+
const zDeepQuery$1: z.ZodType<z.output<typeof zDeepQuery$1$>, z.input<typeof zDeepQuery$1$>> = zDeepQuery$1$;
|
|
654
|
+
// zDeepQuery$2 and zDeepQuery$3 the same way, each using the one before.
|
|
655
|
+
export const zDeepQuery = z.object({
|
|
656
|
+
user: z.object({
|
|
657
|
+
friends: z.array(z.object({
|
|
658
|
+
friends: z.array(z.object({
|
|
659
|
+
friends: z.array(z.object({
|
|
660
|
+
friends: z.array(zDeepQuery$3).nullable(),
|
|
661
|
+
})).nullable(),
|
|
662
|
+
})).nullable(),
|
|
663
|
+
})).nullable(),
|
|
664
|
+
}).nullable(),
|
|
665
|
+
});
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
A fragment's name follows `omitOperationSuffix` and `dedupeOperationSuffix`,
|
|
669
|
+
as typescript-operations names it: `fragment UserFields` is
|
|
670
|
+
`UserFieldsFragment`, and `UserFields` under `omitOperationSuffix`;
|
|
671
|
+
`fragment UFFragment` is `UfFragmentFragment`, and `UfFragment` under either
|
|
672
|
+
option.
|
|
673
|
+
|
|
674
|
+
A fragment on an abstract type, or one that spreads into several places,
|
|
675
|
+
follows the same rules. Here `Pinned` selects `pinned`, a union, whose `User`
|
|
676
|
+
member holds only `__typename`:
|
|
677
|
+
|
|
678
|
+
```ts
|
|
679
|
+
export const zPinnedFragment = z.object({
|
|
680
|
+
pinned: z.discriminatedUnion("__typename", [
|
|
681
|
+
z.object({
|
|
682
|
+
__typename: z.literal("User"),
|
|
683
|
+
}),
|
|
684
|
+
z.object({
|
|
685
|
+
__typename: z.literal("Post"),
|
|
686
|
+
title: z.string(),
|
|
687
|
+
}),
|
|
688
|
+
]).nullable(),
|
|
689
|
+
});
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
On the client, parse the response, then narrow on the discriminator:
|
|
693
|
+
|
|
694
|
+
```ts
|
|
695
|
+
import { zSearchQuery, type SearchQuery } from './generated/zod';
|
|
696
|
+
|
|
697
|
+
export function names(data: unknown): string[] {
|
|
698
|
+
const { search }: SearchQuery = zSearchQuery.parse(data);
|
|
699
|
+
return search.map((hit) =>
|
|
700
|
+
hit.__typename === 'User' ? hit.who : hit.title,
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
```
|
|
704
|
+
|
|
433
705
|
## Errors
|
|
434
706
|
|
|
435
707
|
A schema that declares `@constraint` must be read from its SDL files: an
|
package/docs/roadmap.md
CHANGED
|
@@ -4,10 +4,10 @@ What `@nxgt/graphql-codegen-zod` is heading for, phrased as what you get.
|
|
|
4
4
|
|
|
5
5
|
## Now
|
|
6
6
|
|
|
7
|
-
- **
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
- **Output types.** Zod schemas and types for the SDL's object types,
|
|
8
|
+
interfaces and unions, and for each named operation's result and each
|
|
9
|
+
fragment, with `__typename` discriminating abstract types. Both parts are
|
|
10
|
+
written; they ship with the next release.
|
|
11
11
|
|
|
12
12
|
## Next
|
|
13
13
|
|
|
@@ -32,4 +32,7 @@ Nothing yet.
|
|
|
32
32
|
|
|
33
33
|
## Shipped
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
- **The first release, 0.1.0.** Zod 4 schemas and types for enums, input
|
|
36
|
+
types, field arguments and operation variables, carrying every
|
|
37
|
+
`@constraint` of `@nxgt/graphql-validation`, so the client refuses what the
|
|
38
|
+
server refuses.
|
package/docs/troubleshooting.md
CHANGED
|
@@ -29,10 +29,16 @@ config: { namingConvention: (name) => name.toUpperCase() }
|
|
|
29
29
|
|
|
30
30
|
### `@nxgt/graphql-codegen-zod: the scalar Money is not in zodScalars, and no scalarSchemas is set. Map it: zodScalars: { Money: './module#export' }, or set scalarSchemas: '@nxgt/graphql-scalars'.`
|
|
31
31
|
|
|
32
|
-
**When:** running codegen on a schema with a custom scalar (here `Money`)
|
|
32
|
+
**When:** running codegen on a schema with a custom scalar (here `Money`),
|
|
33
|
+
on an input or, since 0.2.0, on an object type's field or in an operation's
|
|
34
|
+
result only (`createdAt: Money`). 0.1.0 did not read output fields, so such a
|
|
35
|
+
scalar used to need no schema.
|
|
33
36
|
**Why:** a scalar written as `z.unknown()` would check nothing, in silence, so
|
|
34
37
|
the plugin refuses instead.
|
|
35
|
-
**Fix:** map the scalar, or point `scalarSchemas` at a module that has it.
|
|
38
|
+
**Fix:** map the scalar, or point `scalarSchemas` at a module that has it. If
|
|
39
|
+
the scalar is only on output fields and in operations' results, and you do not
|
|
40
|
+
need those types, `objects: false` and `operations: false` together skip them:
|
|
41
|
+
either alone still reads it, wherever an operation selects the scalar.
|
|
36
42
|
|
|
37
43
|
```ts
|
|
38
44
|
config: { zodScalars: { Money: './money#moneySchema' } }
|
|
@@ -95,6 +101,38 @@ document.
|
|
|
95
101
|
mutation SignUp($input: SignUpInput!) { signUp(input: $input) }
|
|
96
102
|
```
|
|
97
103
|
|
|
104
|
+
### `@nxgt/graphql-codegen-zod: Bare.search selects the abstract type SearchResult without __typename for User. Select __typename there, so the result tells its members apart.`
|
|
105
|
+
|
|
106
|
+
**When:** running codegen on a named operation or a fragment. The operation
|
|
107
|
+
(or `fragment <Name>`), the path, the type and the member in the message are
|
|
108
|
+
yours.
|
|
109
|
+
**Why:** when the possible types of an interface or a union select different
|
|
110
|
+
fields, the selection becomes a union discriminated on `__typename`, and
|
|
111
|
+
without it the result cannot tell the members apart. When they all select the
|
|
112
|
+
same fields, no `__typename` is needed. A `__typename` under `@skip` or
|
|
113
|
+
`@include` may be absent, so it counts as missing.
|
|
114
|
+
**Fix:** select `__typename` in that selection, or set `operations: false`.
|
|
115
|
+
|
|
116
|
+
```graphql
|
|
117
|
+
# fails
|
|
118
|
+
query Bare { search(text: "a") { ... on User { id } } }
|
|
119
|
+
# generates
|
|
120
|
+
query Bare { search(text: "a") { __typename ... on User { id } } }
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### `@nxgt/graphql-codegen-zod: Keys.search selects __typename under a different key for each member of SearchResult (t, k). Select it under one key for every member, so the result tells its members apart.`
|
|
124
|
+
|
|
125
|
+
**When:** running codegen on an operation or a fragment. The path, the type
|
|
126
|
+
and the keys in the message are yours.
|
|
127
|
+
**Why:** the union is discriminated on one key, and the members select
|
|
128
|
+
`__typename` under different aliases. When several keys select it,
|
|
129
|
+
`__typename` itself is preferred.
|
|
130
|
+
**Fix:** select `__typename` under one key for every member.
|
|
131
|
+
|
|
132
|
+
```graphql
|
|
133
|
+
query Keys { search(text: "a") { ... on User { t: __typename id } ... on Post { k: __typename title } } }
|
|
134
|
+
```
|
|
135
|
+
|
|
98
136
|
### `@nxgt/graphql-codegen-zod: Both($v:) is passed to Query.e(v:) (format: "email") and to Query.u(v:) (format: "uuid"), whose schemas cannot both apply. Use one variable for each.`
|
|
99
137
|
|
|
100
138
|
**When:** running codegen on an operation that passes one variable to two
|
|
@@ -109,6 +147,18 @@ query Both($e: String, $u: String) { e(v: $e) u(v: $u) }
|
|
|
109
147
|
|
|
110
148
|
## The generated file
|
|
111
149
|
|
|
150
|
+
### `TS2305: Module '"@nxgt/graphql-scalars"' has no exported member 'scalarSchemas'`
|
|
151
|
+
|
|
152
|
+
**When:** type-checking the generated file under `moduleResolution`
|
|
153
|
+
`nodenext` or `node16`, with `scalarSchemas: '@nxgt/graphql-scalars'`.
|
|
154
|
+
**Why:** `@nxgt/graphql-scalars` before 0.4.1 ships declarations that
|
|
155
|
+
`nodenext` does not resolve.
|
|
156
|
+
**Fix:** upgrade it to 0.4.1 or later.
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
bun add @nxgt/graphql-scalars@latest
|
|
160
|
+
```
|
|
161
|
+
|
|
112
162
|
### `Property 'Money' does not exist on type '{ DateTime: ... }'` (or `TS2339`)
|
|
113
163
|
|
|
114
164
|
**When:** type-checking the generated file, with `scalarSchemas` set.
|
|
@@ -159,15 +209,24 @@ field. The variables object itself ignores an extra variable, as `graphql` does.
|
|
|
159
209
|
|
|
160
210
|
## The schema
|
|
161
211
|
|
|
162
|
-
### `@nxgt/graphql-codegen-zod: the file would declare FilterInput twice: two GraphQL names, or
|
|
212
|
+
### `@nxgt/graphql-codegen-zod: the file would declare FilterInput twice: two GraphQL names, or a type in a cycle and its Input or Wire type, give the same name. Rename one of the GraphQL types or operations, set typesSuffix, or set objects: false or operations: false.`
|
|
163
213
|
|
|
164
214
|
**When:** running codegen on a schema where two declarations of the same kind
|
|
165
|
-
get one name:
|
|
166
|
-
|
|
215
|
+
get one name:
|
|
216
|
+
- an input type in a cycle (`Filter`) declares `FilterInput`, or an object
|
|
217
|
+
type in a cycle (`User`) declares `UserWire`, and the schema also has a
|
|
218
|
+
type of that name;
|
|
219
|
+
- an object type is named like a type the plugin already writes, such as
|
|
220
|
+
`QueryFindArgs` beside a field `Query.find(...)`. With 0.1.0 this generated,
|
|
221
|
+
because object types were not written.
|
|
222
|
+
|
|
167
223
|
**Why:** the file cannot declare one name twice.
|
|
168
224
|
**Fix:** rename one of the GraphQL types, or set `typesSuffix`: `Filter`
|
|
169
225
|
then declares `FilterTInput`, apart from `FilterInputT`. A `typesPrefix`
|
|
170
|
-
does not part them.
|
|
226
|
+
does not part them. When the clash comes from an object type and you do not
|
|
227
|
+
need the output types, `objects: false` writes none of them. When it comes
|
|
228
|
+
from an operation's or a fragment's result (`SearchQuery`), `operations:
|
|
229
|
+
false` writes none of them.
|
|
171
230
|
|
|
172
231
|
```graphql
|
|
173
232
|
input Filter { and: [Filter] }
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxgt/graphql-codegen-zod",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "A graphql-codegen plugin that writes Zod schemas from your SDL — inputs, enums, field arguments
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "A graphql-codegen plugin that writes Zod schemas from your SDL — inputs, enums, field arguments, operation variables, operation results and output types — with the @constraint rules of @nxgt/graphql-validation",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.js",
|
|
@@ -46,10 +46,11 @@
|
|
|
46
46
|
"generated:write": "bun run test/write-generated.ts"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@nxgt/graphql-validation": "^0.2.
|
|
49
|
+
"@nxgt/graphql-validation": "^0.2.1",
|
|
50
50
|
"change-case": "^5.4.4"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
+
"@nxgt/graphql-scalars": "^0.4.1",
|
|
53
54
|
"@types/bun": "^1.4.0",
|
|
54
55
|
"graphql": "16.11.0",
|
|
55
56
|
"zod": "4.6.5"
|