@nxgt/graphql-codegen-zod 0.1.0 → 0.2.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.
package/README.md CHANGED
@@ -3,7 +3,8 @@
3
3
  A [graphql-codegen](https://the-guild.dev/graphql/codegen) plugin that writes
4
4
  Zod 4 schemas, and their TypeScript types, from your SDL: one per enum and per
5
5
  input type, one per field that takes arguments, one per named operation's
6
- variables. It carries every `@constraint` of
6
+ variables, one per object type, interface and union, and one per named
7
+ operation's result and per fragment. It carries every `@constraint` of
7
8
  [`@nxgt/graphql-validation`](https://www.npmjs.com/package/@nxgt/graphql-validation),
8
9
  through that package's own rules, so the server (`withValidation`) and the
9
10
  generated code accept and refuse the same values. For graphql-codegen 5 to 7
@@ -29,18 +30,37 @@ run `withValidation` on a server.
29
30
 
30
31
  ## Setup
31
32
 
32
- Declare `@constraint` in the schema codegen reads, with `constraintTypeDefs`
33
- or the `.graphqls` file that package ships. Without the directive nothing is
34
- constrained; plain types are still generated.
33
+ Write the SDL files codegen reads: `@constraint` from
34
+ `@nxgt/graphql-validation` and the custom scalars from `@nxgt/graphql-scalars`.
35
+ Install both, so their bins are yours, whatever the package manager links:
36
+ `bun add @nxgt/graphql-validation @nxgt/graphql-scalars` (the generated file
37
+ imports `@nxgt/graphql-scalars` at run time anyway). Without the directive
38
+ nothing is constrained; plain types are still generated.
35
39
 
36
- Then add the plugin, writing to **its own file**:
40
+ ```sh
41
+ bunx nxgt-graphql-validation typedefs --out # generated/graphql/constraint.graphqls
42
+ bunx nxgt-graphql-scalars typedefs --out # generated/graphql/scalars.graphqls
43
+ ```
44
+
45
+ Both bins are described in the
46
+ [validation](https://www.npmjs.com/package/@nxgt/graphql-validation#ide-support)
47
+ and [scalars](https://www.npmjs.com/package/@nxgt/graphql-scalars#the-sdl-as-a-file)
48
+ READMEs. `typedefs DateTime Long --out` keeps only the scalars you use. Or skip the CLI and list
49
+ `node_modules/@nxgt/graphql-validation/graphql/constraint.graphqls` and
50
+ `node_modules/@nxgt/graphql-scalars/graphql/scalars.graphqls` in `schema`.
51
+
52
+ Then add the plugin, writing to **its own file**, with those files in `schema`:
37
53
 
38
54
  ```ts
39
55
  // codegen.ts
40
56
  import type { CodegenConfig } from '@graphql-codegen/cli';
41
57
 
42
58
  const config: CodegenConfig = {
43
- schema: 'src/schema/**/*.graphql',
59
+ schema: [
60
+ 'generated/graphql/constraint.graphqls',
61
+ 'generated/graphql/scalars.graphqls',
62
+ 'src/schema/**/*.graphql',
63
+ ],
44
64
  documents: 'src/operations/**/*.graphql',
45
65
  generates: {
46
66
  'src/generated/zod.ts': {
@@ -55,11 +75,24 @@ export default config;
55
75
  The plugin writes types named as `@graphql-codegen/typescript` names them
56
76
  (`SignUpInput`, `MutationSignUpArgs`), which would collide with that plugin's
57
77
  in one file. Its `*Args` and `*Variables` types can replace the typescript
58
- plugin's.
78
+ plugin's; so can its object types, interfaces and unions (`User`), or
79
+ `objects: false` leaves them to that plugin. Its `*Query`, `*Mutation` and
80
+ `*Fragment` types can replace typescript-operations' (`SearchQuery`,
81
+ `UserFieldsFragment`), or `operations: false` leaves them to it.
59
82
 
60
83
  `scalarSchemas` supplies a Zod schema for each custom scalar of the schema;
61
84
  see [Scalars](docs/guide/output.md#scalars).
62
85
 
86
+ Run codegen, once or on every change:
87
+
88
+ ```sh
89
+ bunx graphql-codegen --config codegen.ts
90
+ bunx graphql-codegen --config codegen.ts --watch
91
+ ```
92
+
93
+ or add `"codegen": "graphql-codegen"` to the `scripts` of `package.json` and
94
+ run `bun run codegen`.
95
+
63
96
  ## Usage
64
97
 
65
98
  ### On the server: type the resolvers
@@ -76,6 +109,24 @@ export const signUp = (_: unknown, { input }: MutationSignUpArgs) => {
76
109
  };
77
110
  ```
78
111
 
112
+ ### On the server: type what a resolver returns
113
+
114
+ Object types, interfaces and unions are typed
115
+ `z.output`: custom scalars decoded, `__typename` optional, nullable fields
116
+ optional.
117
+
118
+ ```ts
119
+ import type { User } from './generated/zod';
120
+
121
+ export const me = (): User => ({ id: 'u_1', name: 'Al', joined: new Date() });
122
+ ```
123
+
124
+ `zUser.parse(value)` reads a wire value: a `DateTime` string becomes a
125
+ `Date`, and a field the schema does not declare is dropped. To check what a
126
+ resolver returns, which holds a `Date`, encode it back to the wire form:
127
+ `z.encode(zUser, value)`, or `z.safeEncode(zUser, value)` for a result. A
128
+ resolver's string for a `DateTime` is refused.
129
+
79
130
  ### On the client: check before sending
80
131
 
81
132
  Variables are typed `z.input`: what a client sends. A schema per named
@@ -92,6 +143,32 @@ if (!result.success) {
92
143
  }
93
144
  ```
94
145
 
146
+ ### On the client: parse the response
147
+
148
+ An operation's result is typed `z.output`: what the response holds, custom
149
+ scalars decoded. An abstract type's selection is a union on `__typename` when
150
+ its members select different fields, so checking it narrows.
151
+
152
+ ```graphql
153
+ query Search($text: String!) {
154
+ search(text: $text) {
155
+ __typename
156
+ ... on User { id joined }
157
+ ... on Post { id title }
158
+ }
159
+ }
160
+ ```
161
+
162
+ ```ts
163
+ import { zSearchQuery } from './generated/zod';
164
+
165
+ const search = zSearchQuery.parse(response.data).search;
166
+ for (const hit of search) {
167
+ if (hit.__typename === 'User') hit.joined.getFullYear(); // a Date
168
+ else hit.title; // Post
169
+ }
170
+ ```
171
+
95
172
  What is generated, how it is named, defaults, recursion, `@oneOf` and scalars
96
173
  are in [Output](docs/guide/output.md).
97
174
 
@@ -106,6 +183,8 @@ are in [Output](docs/guide/output.md).
106
183
  | `typesPrefix`, `typesSuffix` | `string` | none | around each type's name |
107
184
  | `addUnderscoreToArgsType` | `boolean` | `false` | `Query_FindUserArgs` |
108
185
  | `dedupeOperationSuffix`, `omitOperationSuffix` | `boolean` | `false` | as in `typescript-operations` |
186
+ | `objects` | `boolean` | `true` | write the object types, interfaces and unions; `false` writes no object types, interfaces or unions; results are still written unless `operations: false` |
187
+ | `operations` | `boolean` | `true` | write each named operation's result and each fragment; `false` writes none, the variables stay |
109
188
 
110
189
  Each is detailed in [Output](docs/guide/output.md).
111
190
 
@@ -128,10 +207,19 @@ Each is detailed in [Output](docs/guide/output.md).
128
207
  [validation troubleshooting](https://github.com/softistx/nxgt-graphql/blob/develop/packages/graphql-validation/docs/troubleshooting.md).
129
208
  - A custom scalar mapped nowhere fails generation, naming it, rather than
130
209
  becoming an unchecked `z.unknown()`.
210
+ - An abstract type whose possible types select different fields is a union on
211
+ `__typename`: select it, under one key for every member, or generation
212
+ fails. When they all select the same fields, no `__typename` is needed.
213
+ - A field under `@skip`, `@include` or `@defer` may be absent: it is
214
+ `.optional()`.
131
215
  - Generate into a file of its own: the type names collide with the typescript
132
- plugins'.
133
- - An input type in a cycle also declares `<Type>Input`: a GraphQL type of
134
- that name fails generation. Rename it, or set `typesSuffix`.
216
+ plugins', `*Query` and `*Fragment` with typescript-operations'. Take them from
217
+ this plugin, or set `objects: false` and `operations: false`.
218
+ - An input type in a cycle also declares `<Type>Input`, and an object type in a
219
+ cycle `<Type>Wire`: a GraphQL type of that name fails generation. Rename it,
220
+ or set `typesSuffix`.
221
+ - 0.1.0's output needs both `objects: false` and `operations: false`: a custom
222
+ scalar an operation selects needs a schema, whichever option is set alone.
135
223
 
136
224
  Every error and its fix is in [Troubleshooting](docs/troubleshooting.md).
137
225
 
@@ -0,0 +1,24 @@
1
+ import { type FieldNode, type FragmentDefinitionNode, type GraphQLObjectType, type GraphQLSchema, type SelectionSetNode } from 'graphql';
2
+ /** A selection set, and whether the response may leave it out. */
3
+ export interface Selected {
4
+ readonly set: SelectionSetNode;
5
+ readonly conditional: boolean;
6
+ }
7
+ /** A response key: its field, every node selecting it, if it may be absent. */
8
+ export interface Collected {
9
+ readonly name: string;
10
+ readonly nodes: {
11
+ readonly node: FieldNode;
12
+ readonly conditional: boolean;
13
+ }[];
14
+ optional: boolean;
15
+ }
16
+ /**
17
+ * The fields `sets` select on `type`, by response key, as graphql's
18
+ * CollectFields gathers them for every value of the variables: inline
19
+ * fragments and spreads whose type condition `type` satisfies, the same
20
+ * key's nodes merged, each node keeping whether it may be left out, so a
21
+ * sub-field only a conditional node selects stays optional.
22
+ */
23
+ export declare function collectFields(schema: GraphQLSchema, fragments: ReadonlyMap<string, FragmentDefinitionNode>, type: GraphQLObjectType, sets: readonly Selected[]): Map<string, Collected>;
24
+ //# sourceMappingURL=collect-fields.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collect-fields.d.ts","sourceRoot":"","sources":["../src/collect-fields.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,SAAS,EACd,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAIlB,KAAK,gBAAgB,EACrB,MAAM,SAAS,CAAC;AAEjB,kEAAkE;AAClE,MAAM,WAAW,QAAQ;IACxB,QAAQ,CAAC,GAAG,EAAE,gBAAgB,CAAC;IAC/B,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;CAC9B;AAED,+EAA+E;AAC/E,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;QAAC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAA;KAAE,EAAE,CAAC;IAC9E,QAAQ,EAAE,OAAO,CAAC;CAClB;AAKD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC5B,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,sBAAsB,CAAC,EACtD,IAAI,EAAE,iBAAiB,EACvB,IAAI,EAAE,SAAS,QAAQ,EAAE,GACvB,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CA8CxB"}
package/dist/config.d.ts CHANGED
@@ -34,6 +34,16 @@ export interface CodegenZodConfig {
34
34
  readonly addUnderscoreToArgsType?: boolean;
35
35
  readonly dedupeOperationSuffix?: boolean;
36
36
  readonly omitOperationSuffix?: boolean;
37
+ /**
38
+ * The SDL's object types, interfaces and unions, as what a resolver
39
+ * returns: `zUser`, `User`. Default `true`.
40
+ */
41
+ readonly objects?: boolean;
42
+ /**
43
+ * Each named operation's result and each fragment, as the response
44
+ * holds them: `zUserQuery`, `UserFieldsFragment`. Default `true`.
45
+ */
46
+ readonly operations?: boolean;
37
47
  }
38
48
  /** A file of `documents`, as graphql-codegen hands it to a plugin. */
39
49
  export interface DocumentFile {
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5C;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAChC;;;;OAIG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACvD,gEAAgE;IAChE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;;OAKG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EACvB,MAAM,GACN,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC,GAC1B;QACA,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC;QACzD,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;QAC9B,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;KACtC,CAAC;IACL,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAC3C,QAAQ,CAAC,qBAAqB,CAAC,EAAE,OAAO,CAAC;IACzC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;CACvC;AAED,sEAAsE;AACtE,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,QAAQ,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;IAC7C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACvC"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5C;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAChC;;;;OAIG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACvD,gEAAgE;IAChE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;;OAKG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EACvB,MAAM,GACN,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC,GAC1B;QACA,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC;QACzD,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;QAC9B,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;KACtC,CAAC;IACL,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAC3C,QAAQ,CAAC,qBAAqB,CAAC,EAAE,OAAO,CAAC;IACzC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IACvC;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,sEAAsE;AACtE,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,QAAQ,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;IAC7C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACvC"}
package/dist/cycles.d.ts CHANGED
@@ -6,4 +6,11 @@ import { type GraphQLSchema } from 'graphql';
6
6
  * types out.
7
7
  */
8
8
  export declare function inputCycles(schema: GraphQLSchema): ReadonlySet<string>;
9
+ /**
10
+ * The object types that reach themselves through their fields, an
11
+ * interface's or a union's members included: TypeScript infers such a
12
+ * schema only so deep (TS2589 past a loop of ten), so the plugin writes
13
+ * their types out.
14
+ */
15
+ export declare function outputCycles(schema: GraphQLSchema): ReadonlySet<string>;
9
16
  //# sourceMappingURL=cycles.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cycles.d.ts","sourceRoot":"","sources":["../src/cycles.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,KAAK,aAAa,EAGlB,MAAM,SAAS,CAAC;AAEjB;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,aAAa,GAAG,WAAW,CAAC,MAAM,CAAC,CAOtE"}
1
+ {"version":3,"file":"cycles.d.ts","sourceRoot":"","sources":["../src/cycles.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,KAAK,aAAa,EAMlB,MAAM,SAAS,CAAC;AAEjB;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,aAAa,GAAG,WAAW,CAAC,MAAM,CAAC,CAUtE;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,aAAa,GAAG,WAAW,CAAC,MAAM,CAAC,CAavE"}
package/dist/index.d.ts CHANGED
@@ -3,8 +3,8 @@ import type { CodegenZodConfig, DocumentFile } from './config';
3
3
  export type { CodegenZodConfig, DocumentFile } from './config';
4
4
  /**
5
5
  * The graphql-codegen plugin: Zod schemas, and their types, for the
6
- * schema's enums, input types and field arguments, and for the variables of
7
- * each operation in `documents`, with every `@constraint` of
6
+ * schema's enums, input types, field arguments, object types, interfaces and
7
+ * unions, and for the variables of each operation in `documents`, with every `@constraint` of
8
8
  * `@nxgt/graphql-validation`. It refuses a schema `withValidation` would
9
9
  * refuse, with the same message.
10
10
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACN,KAAK,aAAa,EAIlB,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAQ/D,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE/D;;;;;;GAMG;AACH,wBAAsB,MAAM,CAC3B,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,SAAS,YAAY,EAAE,EAClC,MAAM,GAAE,gBAAqB,EAC7B,IAAI,CAAC,EAAE;IAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GACjD,OAAO,CAAC,MAAM,CAAC,CAiCjB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACN,KAAK,aAAa,EAIlB,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAU/D,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE/D;;;;;;GAMG;AACH,wBAAsB,MAAM,CAC3B,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,SAAS,YAAY,EAAE,EAClC,MAAM,GAAE,gBAAqB,EAC7B,IAAI,CAAC,EAAE;IAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GACjD,OAAO,CAAC,MAAM,CAAC,CAqCjB"}