@nxgt/graphql-codegen-zod 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Steve Tsala
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,145 @@
1
- # Temporary Holding Version
1
+ # @nxgt/graphql-codegen-zod
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A [graphql-codegen](https://the-guild.dev/graphql/codegen) plugin that writes
4
+ Zod 4 schemas, and their TypeScript types, from your SDL: one per enum and per
5
+ input type, one per field that takes arguments, one per named operation's
6
+ variables. It carries every `@constraint` of
7
+ [`@nxgt/graphql-validation`](https://www.npmjs.com/package/@nxgt/graphql-validation),
8
+ through that package's own rules, so the server (`withValidation`) and the
9
+ generated code accept and refuse the same values. For graphql-codegen 5 to 7
10
+ (ESM) and `graphql` 16 and 17.
11
+
12
+ ## Install
13
+
14
+ ```sh
15
+ bun add -d @nxgt/graphql-codegen-zod @graphql-codegen/cli
16
+ bun add graphql zod typescript
17
+ ```
18
+
19
+ Peers, all **required**:
20
+
21
+ | Peer | Range |
22
+ | --- | --- |
23
+ | `graphql` | `^16.11.0 \|\| ^17.0.0` |
24
+ | `zod` | `>=4.6.5 <5` |
25
+ | `typescript` | `^6.0.3` |
26
+
27
+ `@nxgt/graphql-validation` is a dependency; you install it yourself only to
28
+ run `withValidation` on a server.
29
+
30
+ ## Setup
31
+
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.
35
+
36
+ Then add the plugin, writing to **its own file**:
37
+
38
+ ```ts
39
+ // codegen.ts
40
+ import type { CodegenConfig } from '@graphql-codegen/cli';
41
+
42
+ const config: CodegenConfig = {
43
+ schema: 'src/schema/**/*.graphql',
44
+ documents: 'src/operations/**/*.graphql',
45
+ generates: {
46
+ 'src/generated/zod.ts': {
47
+ plugins: ['@nxgt/graphql-codegen-zod'],
48
+ config: { scalarSchemas: '@nxgt/graphql-scalars' },
49
+ },
50
+ },
51
+ };
52
+ export default config;
53
+ ```
54
+
55
+ The plugin writes types named as `@graphql-codegen/typescript` names them
56
+ (`SignUpInput`, `MutationSignUpArgs`), which would collide with that plugin's
57
+ in one file. Its `*Args` and `*Variables` types can replace the typescript
58
+ plugin's.
59
+
60
+ `scalarSchemas` supplies a Zod schema for each custom scalar of the schema;
61
+ see [Scalars](docs/guide/output.md#scalars).
62
+
63
+ ## Usage
64
+
65
+ ### On the server: type the resolvers
66
+
67
+ Arguments are typed `z.output`: what a resolver receives once
68
+ `withValidation` has parsed them.
69
+
70
+ ```ts
71
+ import type { MutationSignUpArgs } from './generated/zod';
72
+
73
+ export const signUp = (_: unknown, { input }: MutationSignUpArgs) => {
74
+ // input.role is always set: its default was filled in
75
+ return { id: 'u_1', email: input.email };
76
+ };
77
+ ```
78
+
79
+ ### On the client: check before sending
80
+
81
+ Variables are typed `z.input`: what a client sends. A schema per named
82
+ operation accepts and refuses what the server would.
83
+
84
+ ```ts
85
+ import { zSignUpMutationVariables } from './generated/zod';
86
+
87
+ const result = zSignUpMutationVariables.safeParse({
88
+ input: { email: form.email, name: form.name },
89
+ });
90
+ if (!result.success) {
91
+ for (const issue of result.error.issues) showError(issue.path, issue.message);
92
+ }
93
+ ```
94
+
95
+ What is generated, how it is named, defaults, recursion, `@oneOf` and scalars
96
+ are in [Output](docs/guide/output.md).
97
+
98
+ ## Options
99
+
100
+ | Option | Type | Default | Effect |
101
+ | --- | --- | --- | --- |
102
+ | `scalarSchemas` | `string` | none | module exporting a `scalarSchemas` record keyed by scalar name |
103
+ | `zodScalars` | `Record<string, string>` | none | `'<module>#<export>'` per scalar; wins over `scalarSchemas` |
104
+ | `schemaPrefix` | `string` | `'z'` | before each schema's name |
105
+ | `namingConvention` | `'keep' \| 'change-case-all#<case>' \| (name) => string \| { typeNames, transformUnderscore, enumValues }` | PascalCase per underscore part | how type names are cased, as the typescript plugins read it |
106
+ | `typesPrefix`, `typesSuffix` | `string` | none | around each type's name |
107
+ | `addUnderscoreToArgsType` | `boolean` | `false` | `Query_FindUserArgs` |
108
+ | `dedupeOperationSuffix`, `omitOperationSuffix` | `boolean` | `false` | as in `typescript-operations` |
109
+
110
+ Each is detailed in [Output](docs/guide/output.md).
111
+
112
+ ## Traps
113
+
114
+ - An `ID` is a string on the client: `graphql` also takes an `Int` for an
115
+ `ID`, the generated schema does not. Send `{ id: "5" }`.
116
+ - A single value for a list is taken, as `graphql` takes it, for every item
117
+ type and at any depth: `tags: "a"` parses to `["a"]`, `contacts: { phone }`
118
+ to `[{ phone }]`.
119
+ - An input type in a cycle (`Filter.and: [Filter]`) has its types written
120
+ out, `Filter` and `FilterInput`, and its schema is typed
121
+ `z.ZodType<Filter, FilterInput>`: it has no `.shape` or `.extend`.
122
+ - Input types are `z.strictObject`: a field the schema does not declare is
123
+ refused.
124
+ - A schema that declares `@constraint` must come from SDL files; an
125
+ introspected one fails generation.
126
+ - The plugin refuses a schema `withValidation` would refuse, with the same
127
+ message; those are in the
128
+ [validation troubleshooting](https://github.com/softistx/nxgt-graphql/blob/develop/packages/graphql-validation/docs/troubleshooting.md).
129
+ - A custom scalar mapped nowhere fails generation, naming it, rather than
130
+ becoming an unchecked `z.unknown()`.
131
+ - 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`.
135
+
136
+ Every error and its fix is in [Troubleshooting](docs/troubleshooting.md).
137
+
138
+ ## Documentation
139
+
140
+ The package exports `plugin` and the `CodegenZodConfig` and `DocumentFile` types from `.`; graphql-codegen finds `plugin` by the package name. See [Output](docs/guide/output.md#the-plugin-function).
141
+
142
+ - [Documentation index](docs/README.md)
143
+ - Guides: [Output](docs/guide/output.md)
144
+ - [Troubleshooting](docs/troubleshooting.md)
145
+ - [Roadmap](docs/roadmap.md)
@@ -0,0 +1,43 @@
1
+ import type { DocumentNode } from 'graphql';
2
+ /**
3
+ * The plugin's options, under `config` in `codegen.ts`. The naming options
4
+ * mean what they mean to `@graphql-codegen/typescript`, so the types this
5
+ * plugin writes carry the names the typescript plugins give them.
6
+ */
7
+ export interface CodegenZodConfig {
8
+ /**
9
+ * A module exporting `scalarSchemas`, a record of Zod schemas keyed by
10
+ * GraphQL scalar name: `'@nxgt/graphql-scalars'`, or a path relative to
11
+ * the generated file.
12
+ */
13
+ readonly scalarSchemas?: string;
14
+ /**
15
+ * One scalar's schema, `'<module>#<export>'`, winning over
16
+ * `scalarSchemas`: `{ Money: './money#moneySchema' }`.
17
+ */
18
+ readonly zodScalars?: Readonly<Record<string, string>>;
19
+ /** Before each schema's name: `zSignUpInput`. Default `'z'`. */
20
+ readonly schemaPrefix?: string;
21
+ /**
22
+ * As the typescript plugins read it, for type names: `'keep'`,
23
+ * `'change-case-all#<case>'`, a function, or `{ typeNames,
24
+ * transformUnderscore }`. Default: PascalCase each part between
25
+ * underscores.
26
+ */
27
+ readonly namingConvention?: string | ((name: string) => string) | {
28
+ readonly typeNames?: string | ((name: string) => string);
29
+ readonly enumValues?: unknown;
30
+ readonly transformUnderscore?: boolean;
31
+ };
32
+ readonly typesPrefix?: string;
33
+ readonly typesSuffix?: string;
34
+ readonly addUnderscoreToArgsType?: boolean;
35
+ readonly dedupeOperationSuffix?: boolean;
36
+ readonly omitOperationSuffix?: boolean;
37
+ }
38
+ /** A file of `documents`, as graphql-codegen hands it to a plugin. */
39
+ export interface DocumentFile {
40
+ readonly document?: DocumentNode | undefined;
41
+ readonly location?: string | undefined;
42
+ }
43
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,9 @@
1
+ import { type GraphQLSchema } from 'graphql';
2
+ /**
3
+ * The input types that reach themselves through their fields, directly
4
+ * (`Filter.and: [Filter]`) or through others: TypeScript cannot infer the
5
+ * schema of such a type once a field wraps it, so the plugin writes their
6
+ * types out.
7
+ */
8
+ export declare function inputCycles(schema: GraphQLSchema): ReadonlySet<string>;
9
+ //# sourceMappingURL=cycles.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,21 @@
1
+ import { type ConstValueNode, type GraphQLArgument, type GraphQLInputField, type GraphQLInputType } from 'graphql';
2
+ /**
3
+ * A default's value as the client parses it: its literal coerced as
4
+ * graphql coerces it. An integer a JavaScript number cannot keep exact,
5
+ * given to an ID or a custom scalar, fails, naming `where`: the server
6
+ * would hand the scalar every digit. An Int or a Float rounds on both sides.
7
+ */
8
+ export declare function parsedDefault(literal: ConstValueNode, type: GraphQLInputType, where: string): unknown;
9
+ /**
10
+ * The default of an argument or input field as a literal: its SDL's, or,
11
+ * for a schema built without SDL (introspected), its value written back.
12
+ * graphql 16 holds that value in `defaultValue`, graphql 17 in `default`.
13
+ */
14
+ export declare function defaultLiteral(input: GraphQLArgument | GraphQLInputField): ConstValueNode | undefined;
15
+ /**
16
+ * A default as graphql coerces it, through lists and input objects: a
17
+ * single value given for a list is a list of it (`[String!] = "a"` is
18
+ * `["a"]`, `[[Int]] = 1` is `[[1]]`), at any depth.
19
+ */
20
+ export declare function coerced(value: unknown, type: GraphQLInputType): unknown;
21
+ //# sourceMappingURL=defaults.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../src/defaults.ts"],"names":[],"mappings":"AACA,OAAO,EAEN,KAAK,cAAc,EACnB,KAAK,eAAe,EAEpB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EAUrB,MAAM,SAAS,CAAC;AAEjB;;;;;GAKG;AACH,wBAAgB,aAAa,CAC5B,OAAO,EAAE,cAAc,EACvB,IAAI,EAAE,gBAAgB,EACtB,KAAK,EAAE,MAAM,GACX,OAAO,CAGT;AAmCD;;;;GAIG;AACH,wBAAgB,cAAc,CAC7B,KAAK,EAAE,eAAe,GAAG,iBAAiB,GACxC,cAAc,GAAG,SAAS,CAyB5B;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAoBvE"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The named imports of the generated file, one line per module, an alias
3
+ * when two modules export the same name.
4
+ */
5
+ export declare class Imports {
6
+ #private;
7
+ /** `reserved`: the names the file declares, which no import may take. */
8
+ constructor(reserved?: Iterable<string>);
9
+ /** The local name `exported` from `module` is reached by. */
10
+ add(module: string, exported: string): string;
11
+ /** The import lines, `zod` first, double-quoted as every string the file holds. */
12
+ lines(): string[];
13
+ }
14
+ //# sourceMappingURL=imports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"imports.d.ts","sourceRoot":"","sources":["../src/imports.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,OAAO;;IAMnB,yEAAyE;gBAC7D,QAAQ,GAAE,QAAQ,CAAC,MAAM,CAAM;IAI3C,6DAA6D;IAC7D,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;IAY7C,mFAAmF;IACnF,KAAK,IAAI,MAAM,EAAE;CAUjB"}
@@ -0,0 +1,14 @@
1
+ import { type GraphQLSchema } from 'graphql';
2
+ import type { CodegenZodConfig, DocumentFile } from './config';
3
+ export type { CodegenZodConfig, DocumentFile } from './config';
4
+ /**
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
8
+ * `@nxgt/graphql-validation`. It refuses a schema `withValidation` would
9
+ * refuse, with the same message.
10
+ */
11
+ export declare function plugin(schema: GraphQLSchema, documents: readonly DocumentFile[], config?: CodegenZodConfig, info?: {
12
+ readonly outputFile?: string | undefined;
13
+ }): Promise<string>;
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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"}