@nxgt/graphql-codegen-zod 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +232 -2
- package/dist/collect-fields.d.ts +24 -0
- package/dist/collect-fields.d.ts.map +1 -0
- package/dist/config.d.ts +53 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/cycles.d.ts +16 -0
- package/dist/cycles.d.ts.map +1 -0
- package/dist/defaults.d.ts +21 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/imports.d.ts +14 -0
- package/dist/imports.d.ts.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1114 -0
- package/dist/index.js.map +25 -0
- package/dist/naming.d.ts +24 -0
- package/dist/naming.d.ts.map +1 -0
- 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 +21 -0
- package/dist/scalars.d.ts.map +1 -0
- package/dist/schema-output.d.ts +10 -0
- package/dist/schema-output.d.ts.map +1 -0
- package/dist/source.d.ts +9 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/type-code.d.ts +12 -0
- package/dist/type-code.d.ts.map +1 -0
- package/dist/variables-output.d.ts +13 -0
- package/dist/variables-output.d.ts.map +1 -0
- package/dist/writer.d.ts +36 -0
- package/dist/writer.d.ts.map +1 -0
- package/docs/README.md +14 -0
- package/docs/guide/output.md +732 -0
- package/docs/roadmap.md +38 -0
- package/docs/troubleshooting.md +267 -0
- package/package.json +62 -5
package/dist/index.js
ADDED
|
@@ -0,0 +1,1114 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { checkConstraints } from "@nxgt/graphql-validation/codegen";
|
|
3
|
+
import {
|
|
4
|
+
isInputObjectType as isInputObjectType7,
|
|
5
|
+
isInterfaceType as isInterfaceType4,
|
|
6
|
+
isObjectType as isObjectType6
|
|
7
|
+
} from "graphql";
|
|
8
|
+
|
|
9
|
+
// src/imports.ts
|
|
10
|
+
class Imports {
|
|
11
|
+
#byModule = new Map([
|
|
12
|
+
["zod", new Map([["z", "z"]])]
|
|
13
|
+
]);
|
|
14
|
+
#taken;
|
|
15
|
+
constructor(reserved = []) {
|
|
16
|
+
this.#taken = new Set(["z", ...reserved]);
|
|
17
|
+
}
|
|
18
|
+
add(module, exported) {
|
|
19
|
+
const names = this.#byModule.get(module) ?? new Map;
|
|
20
|
+
this.#byModule.set(module, names);
|
|
21
|
+
const known = names.get(exported);
|
|
22
|
+
if (known)
|
|
23
|
+
return known;
|
|
24
|
+
let local = exported;
|
|
25
|
+
for (let n = 2;this.#taken.has(local); n++)
|
|
26
|
+
local = `${exported}${n}`;
|
|
27
|
+
this.#taken.add(local);
|
|
28
|
+
names.set(exported, local);
|
|
29
|
+
return local;
|
|
30
|
+
}
|
|
31
|
+
lines() {
|
|
32
|
+
return [...this.#byModule].map(([module, names]) => `import { ${[...names].map(([exported, local]) => exported === local ? local : `${exported} as ${local}`).join(", ")} } from ${JSON.stringify(module)};`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/naming.ts
|
|
37
|
+
import * as changeCase from "change-case";
|
|
38
|
+
|
|
39
|
+
class Naming {
|
|
40
|
+
#config;
|
|
41
|
+
#convert;
|
|
42
|
+
constructor(config) {
|
|
43
|
+
this.#config = config;
|
|
44
|
+
this.#convert = converter(config.namingConvention);
|
|
45
|
+
}
|
|
46
|
+
type(name) {
|
|
47
|
+
return this.#wrap(this.#convert(name));
|
|
48
|
+
}
|
|
49
|
+
args(parent, field) {
|
|
50
|
+
const underscore = this.#config.addUnderscoreToArgsType ? "_" : "";
|
|
51
|
+
return this.#wrap(this.#convert(`${parent}${underscore}${this.#convert(field)}Args`));
|
|
52
|
+
}
|
|
53
|
+
variables(operation, kind) {
|
|
54
|
+
return this.#wrap(this.#convert(`${operation}${this.#suffix(operation, kind)}Variables`));
|
|
55
|
+
}
|
|
56
|
+
result(operation, kind) {
|
|
57
|
+
return this.#wrap(this.#convert(`${operation}${this.#suffix(operation, kind)}`));
|
|
58
|
+
}
|
|
59
|
+
fragment(name) {
|
|
60
|
+
return this.#wrap(this.#convert(`${name}${this.#suffix(name, "fragment")}`));
|
|
61
|
+
}
|
|
62
|
+
#suffix(operation, kind) {
|
|
63
|
+
const type = changeCase.pascalCase(kind);
|
|
64
|
+
const { omitOperationSuffix, dedupeOperationSuffix } = this.#config;
|
|
65
|
+
return omitOperationSuffix || dedupeOperationSuffix && operation.toLowerCase().endsWith(type.toLowerCase()) ? "" : type;
|
|
66
|
+
}
|
|
67
|
+
schema(type) {
|
|
68
|
+
return `${this.#config.schemaPrefix ?? "z"}${type}`;
|
|
69
|
+
}
|
|
70
|
+
#wrap(name) {
|
|
71
|
+
return `${this.#config.typesPrefix ?? ""}${name}${this.#config.typesSuffix ?? ""}`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
var lowerCaseFirst = (name) => name.charAt(0).toLowerCase() + name.slice(1);
|
|
75
|
+
var upperCaseFirst = (name) => name.charAt(0).toUpperCase() + name.slice(1);
|
|
76
|
+
var cases = {
|
|
77
|
+
"change-case-all": {
|
|
78
|
+
camelCase: changeCase.camelCase,
|
|
79
|
+
capitalCase: changeCase.capitalCase,
|
|
80
|
+
constantCase: changeCase.constantCase,
|
|
81
|
+
dotCase: changeCase.dotCase,
|
|
82
|
+
headerCase: changeCase.trainCase,
|
|
83
|
+
noCase: changeCase.noCase,
|
|
84
|
+
paramCase: changeCase.kebabCase,
|
|
85
|
+
pascalCase: changeCase.pascalCase,
|
|
86
|
+
pathCase: changeCase.pathCase,
|
|
87
|
+
sentenceCase: changeCase.sentenceCase,
|
|
88
|
+
snakeCase: changeCase.snakeCase,
|
|
89
|
+
lowerCase: (name) => name.toLowerCase(),
|
|
90
|
+
upperCase: (name) => name.toUpperCase(),
|
|
91
|
+
lowerCaseFirst,
|
|
92
|
+
upperCaseFirst
|
|
93
|
+
},
|
|
94
|
+
"change-case": {
|
|
95
|
+
camelCase: changeCase.camelCase,
|
|
96
|
+
capitalCase: changeCase.capitalCase,
|
|
97
|
+
constantCase: changeCase.constantCase,
|
|
98
|
+
dotCase: changeCase.dotCase,
|
|
99
|
+
kebabCase: changeCase.kebabCase,
|
|
100
|
+
noCase: changeCase.noCase,
|
|
101
|
+
pascalCase: changeCase.pascalCase,
|
|
102
|
+
pascalSnakeCase: changeCase.pascalSnakeCase,
|
|
103
|
+
pathCase: changeCase.pathCase,
|
|
104
|
+
sentenceCase: changeCase.sentenceCase,
|
|
105
|
+
snakeCase: changeCase.snakeCase,
|
|
106
|
+
trainCase: changeCase.trainCase
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
var byPart = (fn, transformUnderscore) => (name) => transformUnderscore ? fn(name) : name.split("_").map(fn).join("_");
|
|
110
|
+
function converter(convention) {
|
|
111
|
+
if (convention === undefined)
|
|
112
|
+
return byPart(changeCase.pascalCase, false);
|
|
113
|
+
if (convention === "keep")
|
|
114
|
+
return (name) => name;
|
|
115
|
+
if (typeof convention === "function")
|
|
116
|
+
return byPart(convention, false);
|
|
117
|
+
if (typeof convention === "string")
|
|
118
|
+
return byPart(caseNamed(convention), false);
|
|
119
|
+
const { typeNames, transformUnderscore } = convention;
|
|
120
|
+
if (typeNames === "keep")
|
|
121
|
+
return (name) => name;
|
|
122
|
+
if (typeNames === undefined)
|
|
123
|
+
return byPart(changeCase.pascalCase, transformUnderscore ?? false);
|
|
124
|
+
const fn = typeof typeNames === "function" ? typeNames : caseNamed(typeNames);
|
|
125
|
+
return byPart(fn, true);
|
|
126
|
+
}
|
|
127
|
+
function caseNamed(spec) {
|
|
128
|
+
const [module = "", name = ""] = spec.split("#");
|
|
129
|
+
const fn = cases[module]?.[name];
|
|
130
|
+
if (fn)
|
|
131
|
+
return fn;
|
|
132
|
+
throw new Error(`@nxgt/graphql-codegen-zod: namingConvention "${spec}" is not one this plugin reads. Use 'keep', a function, or 'change-case-all#<case>' with one of ${Object.keys(cases["change-case-all"] ?? {}).join(", ")}.`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/objects-output.ts
|
|
136
|
+
import {
|
|
137
|
+
GraphQLBoolean as GraphQLBoolean2,
|
|
138
|
+
GraphQLFloat as GraphQLFloat2,
|
|
139
|
+
GraphQLID as GraphQLID2,
|
|
140
|
+
GraphQLInt as GraphQLInt2,
|
|
141
|
+
GraphQLString as GraphQLString2,
|
|
142
|
+
getNamedType as getNamedType2,
|
|
143
|
+
isAbstractType as isAbstractType3,
|
|
144
|
+
isEnumType as isEnumType2,
|
|
145
|
+
isInterfaceType as isInterfaceType2,
|
|
146
|
+
isListType as isListType2,
|
|
147
|
+
isNonNullType as isNonNullType2,
|
|
148
|
+
isObjectType as isObjectType3,
|
|
149
|
+
isScalarType,
|
|
150
|
+
isUnionType
|
|
151
|
+
} from "graphql";
|
|
152
|
+
|
|
153
|
+
// src/cycles.ts
|
|
154
|
+
import {
|
|
155
|
+
getNamedType,
|
|
156
|
+
isAbstractType,
|
|
157
|
+
isInputObjectType,
|
|
158
|
+
isInterfaceType,
|
|
159
|
+
isObjectType
|
|
160
|
+
} from "graphql";
|
|
161
|
+
function inputCycles(schema) {
|
|
162
|
+
return cycles(Object.values(schema.getTypeMap()).filter(isInputObjectType), (type) => isInputObjectType(type) ? Object.values(type.getFields()).map((field) => getNamedType(field.type)).filter(isInputObjectType) : []);
|
|
163
|
+
}
|
|
164
|
+
function outputCycles(schema) {
|
|
165
|
+
return cycles(Object.values(schema.getTypeMap()).filter((type) => isObjectType(type) && !type.name.startsWith("__")), (type) => {
|
|
166
|
+
if (isAbstractType(type))
|
|
167
|
+
return schema.getPossibleTypes(type);
|
|
168
|
+
if (!isObjectType(type) && !isInterfaceType(type))
|
|
169
|
+
return [];
|
|
170
|
+
return Object.values(type.getFields()).map((field) => getNamedType(field.type)).filter((named) => isObjectType(named) || isAbstractType(named));
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
function cycles(starts, next) {
|
|
174
|
+
const reaches = (from, target, seen) => next(from).some((named) => {
|
|
175
|
+
if (named.name === target)
|
|
176
|
+
return true;
|
|
177
|
+
if (seen.has(named.name))
|
|
178
|
+
return false;
|
|
179
|
+
seen.add(named.name);
|
|
180
|
+
return reaches(named, target, seen);
|
|
181
|
+
});
|
|
182
|
+
return new Set(starts.filter((start) => reaches(start, start.name, new Set)).map((start) => start.name));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/output-types.ts
|
|
186
|
+
import {
|
|
187
|
+
GraphQLBoolean,
|
|
188
|
+
GraphQLFloat,
|
|
189
|
+
GraphQLID,
|
|
190
|
+
GraphQLInt,
|
|
191
|
+
GraphQLString,
|
|
192
|
+
isAbstractType as isAbstractType2,
|
|
193
|
+
isEnumType,
|
|
194
|
+
isListType,
|
|
195
|
+
isNonNullType,
|
|
196
|
+
isObjectType as isObjectType2
|
|
197
|
+
} from "graphql";
|
|
198
|
+
|
|
199
|
+
// src/source.ts
|
|
200
|
+
function docComment(description, indent = "") {
|
|
201
|
+
if (!description)
|
|
202
|
+
return "";
|
|
203
|
+
const lines = description.replaceAll(`\r
|
|
204
|
+
`, `
|
|
205
|
+
`).replaceAll("*/", "*\\/").split(`
|
|
206
|
+
`);
|
|
207
|
+
if (lines.length === 1)
|
|
208
|
+
return `${indent}/** ${lines[0]} */
|
|
209
|
+
`;
|
|
210
|
+
return `${indent}/**
|
|
211
|
+
${lines.map((line) => `${indent} * ${line}`.trimEnd()).join(`
|
|
212
|
+
`)}
|
|
213
|
+
${indent} */
|
|
214
|
+
`;
|
|
215
|
+
}
|
|
216
|
+
function objectMembers(members) {
|
|
217
|
+
return members.map(([name, { code, lazy, description }]) => {
|
|
218
|
+
const doc = docComment(description, "\t");
|
|
219
|
+
return lazy ? `${doc} get ${name}() {
|
|
220
|
+
return ${code};
|
|
221
|
+
},` : `${doc} ${name}: ${code},`;
|
|
222
|
+
}).join(`
|
|
223
|
+
`);
|
|
224
|
+
}
|
|
225
|
+
function declare(typeName, naming, code, side, description) {
|
|
226
|
+
const schemaName = naming.schema(typeName);
|
|
227
|
+
const doc = docComment(description);
|
|
228
|
+
return `${doc}export const ${schemaName} = ${code};
|
|
229
|
+
${doc}export type ${typeName} = z.${side}<typeof ${schemaName}>;`;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// src/output-types.ts
|
|
233
|
+
var builtIns = {
|
|
234
|
+
[GraphQLString.name]: "string",
|
|
235
|
+
[GraphQLID.name]: "string",
|
|
236
|
+
[GraphQLInt.name]: "number",
|
|
237
|
+
[GraphQLFloat.name]: "number",
|
|
238
|
+
[GraphQLBoolean.name]: "boolean"
|
|
239
|
+
};
|
|
240
|
+
function declareCyclicObject(type, code, context) {
|
|
241
|
+
const types = new OutputTypeCode(context);
|
|
242
|
+
const output = context.naming.type(type.name);
|
|
243
|
+
const input = `${output}Wire`;
|
|
244
|
+
const doc = docComment(type.description);
|
|
245
|
+
return [
|
|
246
|
+
`${doc}export type ${output} = ${types.object(type, "output")};`,
|
|
247
|
+
`${doc}export type ${input} = ${types.object(type, "input")};`,
|
|
248
|
+
`${doc}export const ${context.naming.schema(output)}: z.ZodType<${output}, ${input}> = ${code};`
|
|
249
|
+
].join(`
|
|
250
|
+
`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
class OutputTypeCode {
|
|
254
|
+
#context;
|
|
255
|
+
constructor(context) {
|
|
256
|
+
this.#context = context;
|
|
257
|
+
}
|
|
258
|
+
object(type, side) {
|
|
259
|
+
const members = [
|
|
260
|
+
` __typename?: ${JSON.stringify(type.name)} | undefined;`,
|
|
261
|
+
...Object.values(type.getFields()).map((field) => `${docComment(field.description, "\t")} ${this.#member(field, side)};`)
|
|
262
|
+
];
|
|
263
|
+
return `{
|
|
264
|
+
${members.join(`
|
|
265
|
+
`)}
|
|
266
|
+
}`;
|
|
267
|
+
}
|
|
268
|
+
#member(field, side) {
|
|
269
|
+
const type = this.#required(field.type, side);
|
|
270
|
+
return isNonNullType(field.type) ? `${field.name}: ${type}` : `${field.name}?: ${type} | null | undefined`;
|
|
271
|
+
}
|
|
272
|
+
#required(type, side) {
|
|
273
|
+
if (isNonNullType(type))
|
|
274
|
+
return this.#required(type.ofType, side);
|
|
275
|
+
if (!isListType(type))
|
|
276
|
+
return this.#named(type, side);
|
|
277
|
+
const item = this.#required(type.ofType, side);
|
|
278
|
+
return isNonNullType(type.ofType) ? `Array<${item}>` : `Array<${item} | null | undefined>`;
|
|
279
|
+
}
|
|
280
|
+
#named(type, side) {
|
|
281
|
+
const { writer, naming, cyclic } = this.#context;
|
|
282
|
+
const builtIn = builtIns[type.name];
|
|
283
|
+
if (builtIn)
|
|
284
|
+
return builtIn;
|
|
285
|
+
if (isEnumType(type))
|
|
286
|
+
return `z.output<typeof ${writer.schemaName(type)}>`;
|
|
287
|
+
if (isAbstractType2(type))
|
|
288
|
+
return `z.${side}<typeof ${naming.schema(naming.type(type.name))}>`;
|
|
289
|
+
if (isObjectType2(type)) {
|
|
290
|
+
const name = naming.type(type.name);
|
|
291
|
+
if (cyclic.has(type.name))
|
|
292
|
+
return side === "output" ? name : `${name}Wire`;
|
|
293
|
+
return `z.${side}<typeof ${naming.schema(name)}>`;
|
|
294
|
+
}
|
|
295
|
+
return `z.${side}<typeof ${writer.scalarCode(type)}>`;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// src/objects-output.ts
|
|
300
|
+
var builtIns2 = {
|
|
301
|
+
[GraphQLString2.name]: "z.string()",
|
|
302
|
+
[GraphQLID2.name]: "z.string()",
|
|
303
|
+
[GraphQLInt2.name]: "z.int32()",
|
|
304
|
+
[GraphQLFloat2.name]: "z.number()",
|
|
305
|
+
[GraphQLBoolean2.name]: "z.boolean()"
|
|
306
|
+
};
|
|
307
|
+
function objectBlocks(schema, writer, naming) {
|
|
308
|
+
const types = Object.values(schema.getTypeMap()).filter((type) => !type.name.startsWith("__"));
|
|
309
|
+
const context = { writer, naming, cyclic: outputCycles(schema) };
|
|
310
|
+
return [
|
|
311
|
+
...types.filter(isObjectType3).map((type) => objectBlock(type, context)),
|
|
312
|
+
...types.filter(isAbstractType3).map((type) => abstractBlock(schema, type, naming))
|
|
313
|
+
];
|
|
314
|
+
}
|
|
315
|
+
function objectBlock(type, context) {
|
|
316
|
+
const { writer, naming } = context;
|
|
317
|
+
const typename = {
|
|
318
|
+
code: `z.literal(${JSON.stringify(type.name)}).optional()`,
|
|
319
|
+
lazy: false
|
|
320
|
+
};
|
|
321
|
+
const members = Object.values(type.getFields()).map((field) => [
|
|
322
|
+
field.name,
|
|
323
|
+
{
|
|
324
|
+
...outputCode(field.type, writer, naming),
|
|
325
|
+
description: field.description
|
|
326
|
+
}
|
|
327
|
+
]);
|
|
328
|
+
const code = `z.object({
|
|
329
|
+
${objectMembers([["__typename", typename], ...members])}
|
|
330
|
+
})`;
|
|
331
|
+
return context.cyclic.has(type.name) ? declareCyclicObject(type, code, context) : declare(naming.type(type.name), naming, code, "output", type.description);
|
|
332
|
+
}
|
|
333
|
+
function abstractBlock(schema, type, naming) {
|
|
334
|
+
const members = (isUnionType(type) ? type.getTypes() : schema.getPossibleTypes(type)).map((member) => naming.schema(naming.type(member.name)));
|
|
335
|
+
const code = members.length === 0 ? "z.never()" : members.length === 1 ? members[0] ?? "z.never()" : `z.union([${members.join(", ")}])`;
|
|
336
|
+
return declare(naming.type(type.name), naming, code, "output", type.description);
|
|
337
|
+
}
|
|
338
|
+
function outputCode(type, writer, naming) {
|
|
339
|
+
const named = getNamedType2(type);
|
|
340
|
+
const lazy = !isScalarType(named) && !isEnumType2(named);
|
|
341
|
+
return { code: nullable(type, writer, naming), lazy };
|
|
342
|
+
}
|
|
343
|
+
function nullable(type, writer, naming) {
|
|
344
|
+
if (isNonNullType2(type))
|
|
345
|
+
return required(type.ofType, writer, naming);
|
|
346
|
+
return `${required(type, writer, naming)}.nullish()`;
|
|
347
|
+
}
|
|
348
|
+
function required(type, writer, naming) {
|
|
349
|
+
if (isNonNullType2(type))
|
|
350
|
+
return required(type.ofType, writer, naming);
|
|
351
|
+
if (isListType2(type))
|
|
352
|
+
return `z.array(${nullable(type.ofType, writer, naming)})`;
|
|
353
|
+
if (isScalarType(type))
|
|
354
|
+
return builtIns2[type.name] ?? writer.scalarCode(type);
|
|
355
|
+
if (isEnumType2(type))
|
|
356
|
+
return writer.schemaName(type);
|
|
357
|
+
if (isObjectType3(type) || isInterfaceType2(type) || isUnionType(type))
|
|
358
|
+
return naming.schema(naming.type(type.name));
|
|
359
|
+
return "z.never()";
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// src/results-output.ts
|
|
363
|
+
import {
|
|
364
|
+
isCompositeType,
|
|
365
|
+
Kind as Kind2
|
|
366
|
+
} from "graphql";
|
|
367
|
+
|
|
368
|
+
// src/results-selections.ts
|
|
369
|
+
import {
|
|
370
|
+
isEnumType as isEnumType3,
|
|
371
|
+
isListType as isListType3,
|
|
372
|
+
isNonNullType as isNonNullType3,
|
|
373
|
+
isObjectType as isObjectType4,
|
|
374
|
+
isScalarType as isScalarType2
|
|
375
|
+
} from "graphql";
|
|
376
|
+
|
|
377
|
+
// src/collect-fields.ts
|
|
378
|
+
import {
|
|
379
|
+
isAbstractType as isAbstractType4,
|
|
380
|
+
Kind
|
|
381
|
+
} from "graphql";
|
|
382
|
+
var conditions = new Set(["skip", "include", "defer"]);
|
|
383
|
+
function collectFields(schema, fragments, type, sets) {
|
|
384
|
+
const fields = new Map;
|
|
385
|
+
const applies = (condition) => {
|
|
386
|
+
const named = schema.getType(condition);
|
|
387
|
+
if (!named)
|
|
388
|
+
return false;
|
|
389
|
+
if (named.name === type.name)
|
|
390
|
+
return true;
|
|
391
|
+
return isAbstractType4(named) && schema.isSubType(named, type);
|
|
392
|
+
};
|
|
393
|
+
const visit = (set, optional, seen) => {
|
|
394
|
+
for (const selection of set.selections) {
|
|
395
|
+
const conditional = optional || isConditional(selection);
|
|
396
|
+
if (selection.kind === Kind.FIELD) {
|
|
397
|
+
const key = selection.alias?.value ?? selection.name.value;
|
|
398
|
+
const entry = { node: selection, conditional };
|
|
399
|
+
const field = fields.get(key);
|
|
400
|
+
if (field) {
|
|
401
|
+
field.nodes.push(entry);
|
|
402
|
+
field.optional &&= conditional;
|
|
403
|
+
} else
|
|
404
|
+
fields.set(key, {
|
|
405
|
+
name: selection.name.value,
|
|
406
|
+
nodes: [entry],
|
|
407
|
+
optional: conditional
|
|
408
|
+
});
|
|
409
|
+
} else if (selection.kind === Kind.INLINE_FRAGMENT) {
|
|
410
|
+
const condition = selection.typeCondition?.name.value;
|
|
411
|
+
if (!condition || applies(condition))
|
|
412
|
+
visit(selection.selectionSet, conditional, seen);
|
|
413
|
+
} else {
|
|
414
|
+
const name = selection.name.value;
|
|
415
|
+
const fragment = fragments.get(name);
|
|
416
|
+
if (fragment && !seen.has(name) && applies(fragment.typeCondition.name.value))
|
|
417
|
+
visit(fragment.selectionSet, conditional, new Set([...seen, name]));
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
for (const { set, conditional } of sets)
|
|
422
|
+
visit(set, conditional, new Set);
|
|
423
|
+
return fields;
|
|
424
|
+
}
|
|
425
|
+
function isConditional(selection) {
|
|
426
|
+
return (selection.directives ?? []).some((directive) => conditions.has(directive.name.value));
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// src/results-selections.ts
|
|
430
|
+
var failure = "@nxgt/graphql-codegen-zod:";
|
|
431
|
+
var typename = "\x00typename\x00";
|
|
432
|
+
var hoistDepth = 5;
|
|
433
|
+
|
|
434
|
+
class Selections {
|
|
435
|
+
#schema;
|
|
436
|
+
#fragments;
|
|
437
|
+
#writer;
|
|
438
|
+
#hoisted = new Map;
|
|
439
|
+
#prefix = "";
|
|
440
|
+
constructor(schema, fragments, writer) {
|
|
441
|
+
this.#schema = schema;
|
|
442
|
+
this.#fragments = fragments;
|
|
443
|
+
this.#writer = writer;
|
|
444
|
+
}
|
|
445
|
+
declare(name, type, sets, path) {
|
|
446
|
+
this.#hoisted = new Map;
|
|
447
|
+
this.#prefix = name;
|
|
448
|
+
const code = this.#code(type, sets, [path], "", 0);
|
|
449
|
+
return {
|
|
450
|
+
hoisted: [...this.#hoisted].map(([body, hoisted]) => `const ${hoisted}$ = ${body};
|
|
451
|
+
const ${hoisted}: z.ZodType<z.output<typeof ${hoisted}$>, z.input<typeof ${hoisted}$>> = ${hoisted}$;`),
|
|
452
|
+
code
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
#code(type, sets, path, indent, depth) {
|
|
456
|
+
if (depth >= hoistDepth) {
|
|
457
|
+
const body = this.#code(type, sets, path, "", 0);
|
|
458
|
+
const known = this.#hoisted.get(body);
|
|
459
|
+
if (known)
|
|
460
|
+
return known;
|
|
461
|
+
const name = `${this.#prefix}$${this.#hoisted.size + 1}`;
|
|
462
|
+
this.#hoisted.set(body, name);
|
|
463
|
+
return name;
|
|
464
|
+
}
|
|
465
|
+
if (isObjectType4(type))
|
|
466
|
+
return this.#object(type, sets, path, indent, depth).replaceAll(typename, `z.literal(${JSON.stringify(type.name)})`);
|
|
467
|
+
return this.#abstract(type, sets, path, indent, depth);
|
|
468
|
+
}
|
|
469
|
+
#abstract(type, sets, path, indent, depth) {
|
|
470
|
+
const members = this.#schema.getPossibleTypes(type);
|
|
471
|
+
if (members.length === 0)
|
|
472
|
+
return "z.never()";
|
|
473
|
+
const inner = `${indent} `;
|
|
474
|
+
const groups = new Map;
|
|
475
|
+
const keys = new Map;
|
|
476
|
+
for (const member of members) {
|
|
477
|
+
const code = this.#object(member, sets, path, inner, depth);
|
|
478
|
+
groups.set(code, [...groups.get(code) ?? [], member]);
|
|
479
|
+
keys.set(member.name, [...collectFields(this.#schema, this.#fragments, member, sets)].filter(([, field]) => field.name === "__typename" && !field.optional).map(([key]) => key));
|
|
480
|
+
}
|
|
481
|
+
const fill = (code, group) => {
|
|
482
|
+
const names = group.map((member) => JSON.stringify(member.name));
|
|
483
|
+
return code.replaceAll(typename, names.length === 1 ? `z.literal(${names[0]})` : `z.enum([${names.join(", ")}])`);
|
|
484
|
+
};
|
|
485
|
+
const all = [...groups];
|
|
486
|
+
if (all.length === 1) {
|
|
487
|
+
const [code = "", group = []] = all[0] ?? [];
|
|
488
|
+
return fill(code.replaceAll(`
|
|
489
|
+
${inner}`, `
|
|
490
|
+
${indent}`), group);
|
|
491
|
+
}
|
|
492
|
+
const discriminator = this.#discriminator(type.name, keys, path);
|
|
493
|
+
return `z.discriminatedUnion(${JSON.stringify(discriminator)}, [
|
|
494
|
+
${all.map(([code, group]) => `${inner}${fill(code, group)},`).join(`
|
|
495
|
+
`)}
|
|
496
|
+
${indent}])`;
|
|
497
|
+
}
|
|
498
|
+
#discriminator(type, keys, path) {
|
|
499
|
+
for (const [member, selected] of keys)
|
|
500
|
+
if (selected.length === 0)
|
|
501
|
+
throw new Error(`${failure} ${path.join(".")} selects the abstract type ${type} without __typename for ${member}. Select __typename there, so the result tells its members apart.`);
|
|
502
|
+
const lists = [...keys.values()];
|
|
503
|
+
const shared = (lists[0] ?? []).filter((key) => lists.every((selected) => selected.includes(key)));
|
|
504
|
+
const key = shared.includes("__typename") ? "__typename" : shared[0];
|
|
505
|
+
if (key === undefined)
|
|
506
|
+
throw new Error(`${failure} ${path.join(".")} selects __typename under a different key for each member of ${type} (${[...new Set(lists.flat())].join(", ")}). Select it under one key for every member, so the result tells its members apart.`);
|
|
507
|
+
return key;
|
|
508
|
+
}
|
|
509
|
+
#object(type, sets, path, indent, depth) {
|
|
510
|
+
const inner = `${indent} `;
|
|
511
|
+
const members = [
|
|
512
|
+
...collectFields(this.#schema, this.#fragments, type, sets)
|
|
513
|
+
].map(([key, field]) => {
|
|
514
|
+
const code = this.#field(type, field, [...path, key], inner, depth);
|
|
515
|
+
return `${inner}${key}: ${field.optional ? `${code}.optional()` : code},`;
|
|
516
|
+
});
|
|
517
|
+
return members.length === 0 ? "z.object({})" : `z.object({
|
|
518
|
+
${members.join(`
|
|
519
|
+
`)}
|
|
520
|
+
${indent}})`;
|
|
521
|
+
}
|
|
522
|
+
#field(parent, field, path, indent, depth) {
|
|
523
|
+
if (field.name === "__typename")
|
|
524
|
+
return typename;
|
|
525
|
+
const definition = parent.getFields()[field.name];
|
|
526
|
+
if (!definition)
|
|
527
|
+
return "z.unknown()";
|
|
528
|
+
const implied = field.optional && field.nodes.length === 1;
|
|
529
|
+
const sets = field.nodes.flatMap(({ node, conditional }) => node.selectionSet ? [{ set: node.selectionSet, conditional: conditional && !implied }] : []);
|
|
530
|
+
return this.#nullable(definition.type, sets, path, indent, depth + 1);
|
|
531
|
+
}
|
|
532
|
+
#nullable(type, sets, path, indent, depth) {
|
|
533
|
+
if (isNonNullType3(type))
|
|
534
|
+
return this.#required(type.ofType, sets, path, indent, depth);
|
|
535
|
+
return `${this.#required(type, sets, path, indent, depth)}.nullable()`;
|
|
536
|
+
}
|
|
537
|
+
#required(type, sets, path, indent, depth) {
|
|
538
|
+
if (isNonNullType3(type))
|
|
539
|
+
return this.#required(type.ofType, sets, path, indent, depth);
|
|
540
|
+
if (isListType3(type))
|
|
541
|
+
return `z.array(${this.#nullable(type.ofType, sets, path, indent, depth)})`;
|
|
542
|
+
if (isScalarType2(type))
|
|
543
|
+
return builtIns2[type.name] ?? this.#writer.scalarCode(type);
|
|
544
|
+
if (isEnumType3(type))
|
|
545
|
+
return this.#writer.schemaName(type);
|
|
546
|
+
return this.#code(type, sets, path, indent, depth);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// src/results-output.ts
|
|
551
|
+
function resultsBlocks(schema, documents, writer, naming) {
|
|
552
|
+
const definitions = documents.flatMap((file) => file.document?.definitions ?? []);
|
|
553
|
+
const fragments = new Map(definitions.filter((node) => node.kind === Kind2.FRAGMENT_DEFINITION).map((node) => [node.name.value, node]));
|
|
554
|
+
const selections = new Selections(schema, fragments, writer);
|
|
555
|
+
const block = (typeName, written) => [
|
|
556
|
+
...written.hoisted,
|
|
557
|
+
declare(typeName, naming, written.code, "output")
|
|
558
|
+
].join(`
|
|
559
|
+
`);
|
|
560
|
+
return [
|
|
561
|
+
...[...fragments.values()].flatMap((fragment) => {
|
|
562
|
+
const type = schema.getType(fragment.typeCondition.name.value);
|
|
563
|
+
if (!isCompositeType(type))
|
|
564
|
+
return [];
|
|
565
|
+
const typeName = naming.fragment(fragment.name.value);
|
|
566
|
+
const set = { set: fragment.selectionSet, conditional: false };
|
|
567
|
+
return [
|
|
568
|
+
block(typeName, selections.declare(naming.schema(typeName), type, [set], `fragment ${fragment.name.value}`))
|
|
569
|
+
];
|
|
570
|
+
}),
|
|
571
|
+
...definitions.filter((node) => node.kind === Kind2.OPERATION_DEFINITION).flatMap((operation) => {
|
|
572
|
+
const name = operation.name?.value;
|
|
573
|
+
const root = schema.getRootType(operation.operation);
|
|
574
|
+
if (!name || !root)
|
|
575
|
+
return [];
|
|
576
|
+
const typeName = naming.result(name, operation.operation);
|
|
577
|
+
const set = { set: operation.selectionSet, conditional: false };
|
|
578
|
+
return [
|
|
579
|
+
block(typeName, selections.declare(naming.schema(typeName), root, [set], name))
|
|
580
|
+
];
|
|
581
|
+
})
|
|
582
|
+
];
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// src/scalars.ts
|
|
586
|
+
import { createRequire } from "node:module";
|
|
587
|
+
import { dirname, resolve } from "node:path";
|
|
588
|
+
import { pathToFileURL } from "node:url";
|
|
589
|
+
|
|
590
|
+
class ScalarSources {
|
|
591
|
+
#config;
|
|
592
|
+
#imports;
|
|
593
|
+
#recordKeys;
|
|
594
|
+
constructor(config, imports, recordKeys) {
|
|
595
|
+
this.#config = config;
|
|
596
|
+
this.#imports = imports;
|
|
597
|
+
this.#recordKeys = recordKeys;
|
|
598
|
+
}
|
|
599
|
+
static async load(config, imports, outputFile) {
|
|
600
|
+
if (!config.scalarSchemas)
|
|
601
|
+
return new ScalarSources(config, imports, undefined);
|
|
602
|
+
const keys = await recordKeys(config.scalarSchemas, outputFile);
|
|
603
|
+
return new ScalarSources(config, imports, keys);
|
|
604
|
+
}
|
|
605
|
+
code(type) {
|
|
606
|
+
const own = this.#config.zodScalars?.[type.name];
|
|
607
|
+
if (own !== undefined) {
|
|
608
|
+
const at = own.lastIndexOf("#");
|
|
609
|
+
if (at <= 0 || at === own.length - 1) {
|
|
610
|
+
throw new Error(`@nxgt/graphql-codegen-zod: zodScalars.${type.name} is "${own}"; write it '<module>#<export>', e.g. './money#moneySchema'.`);
|
|
611
|
+
}
|
|
612
|
+
return this.#imports.add(own.slice(0, at), own.slice(at + 1));
|
|
613
|
+
}
|
|
614
|
+
const record = this.#config.scalarSchemas;
|
|
615
|
+
if (record && (!this.#recordKeys || this.#recordKeys.has(type.name))) {
|
|
616
|
+
return `${this.#imports.add(record, "scalarSchemas")}.${type.name}`;
|
|
617
|
+
}
|
|
618
|
+
const where = record ? `neither in zodScalars nor in ${record}'s scalarSchemas` : "not in zodScalars, and no scalarSchemas is set";
|
|
619
|
+
throw new Error(`@nxgt/graphql-codegen-zod: the scalar ${type.name} is ${where}. Map it: zodScalars: { ${type.name}: './module#export' }, or set scalarSchemas: '@nxgt/graphql-scalars'.`);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
async function recordKeys(module, outputFile) {
|
|
623
|
+
try {
|
|
624
|
+
const base = module.startsWith(".") ? resolve(dirname(resolve(outputFile ?? "index.ts")), module) : createRequire(resolve("package.json")).resolve(module);
|
|
625
|
+
const loaded = await import(pathToFileURL(base).href);
|
|
626
|
+
const record = loaded.scalarSchemas;
|
|
627
|
+
if (typeof record !== "object" || record === null) {
|
|
628
|
+
throw new Error(`@nxgt/graphql-codegen-zod: ${module} exports no scalarSchemas record.`);
|
|
629
|
+
}
|
|
630
|
+
return new Set(Object.keys(record));
|
|
631
|
+
} catch (error) {
|
|
632
|
+
if (error instanceof Error && error.message.startsWith("@nxgt/graphql-codegen-zod:"))
|
|
633
|
+
throw error;
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// src/schema-output.ts
|
|
639
|
+
import { constraintsOn } from "@nxgt/graphql-validation/codegen";
|
|
640
|
+
import {
|
|
641
|
+
isEnumType as isEnumType5,
|
|
642
|
+
isInputObjectType as isInputObjectType4,
|
|
643
|
+
isInterfaceType as isInterfaceType3,
|
|
644
|
+
isObjectType as isObjectType5
|
|
645
|
+
} from "graphql";
|
|
646
|
+
|
|
647
|
+
// src/defaults.ts
|
|
648
|
+
import * as graphql from "graphql";
|
|
649
|
+
import {
|
|
650
|
+
astFromValue,
|
|
651
|
+
GraphQLID as GraphQLID3,
|
|
652
|
+
getNamedType as getNamedType3,
|
|
653
|
+
getNullableType,
|
|
654
|
+
isInputObjectType as isInputObjectType2,
|
|
655
|
+
isListType as isListType4,
|
|
656
|
+
isNonNullType as isNonNullType4,
|
|
657
|
+
isScalarType as isScalarType3,
|
|
658
|
+
isSpecifiedScalarType,
|
|
659
|
+
Kind as Kind3,
|
|
660
|
+
valueFromASTUntyped
|
|
661
|
+
} from "graphql";
|
|
662
|
+
function parsedDefault(literal, type, where) {
|
|
663
|
+
assertExact(literal, type, where);
|
|
664
|
+
return coerced(valueFromASTUntyped(literal), type);
|
|
665
|
+
}
|
|
666
|
+
function assertExact(literal, type, where) {
|
|
667
|
+
const nullable = getNullableType(type);
|
|
668
|
+
if (isListType4(nullable)) {
|
|
669
|
+
const items = literal.kind === Kind3.LIST ? literal.values : [literal];
|
|
670
|
+
for (const item of items)
|
|
671
|
+
assertExact(item, nullable.ofType, where);
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
if (isInputObjectType2(nullable)) {
|
|
675
|
+
if (literal.kind !== Kind3.OBJECT)
|
|
676
|
+
return;
|
|
677
|
+
const fields = nullable.getFields();
|
|
678
|
+
for (const field of literal.fields) {
|
|
679
|
+
const definition = fields[field.name.value];
|
|
680
|
+
if (definition)
|
|
681
|
+
assertExact(field.value, definition.type, where);
|
|
682
|
+
}
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
const keeps = nullable === GraphQLID3 || isScalarType3(nullable) && !isSpecifiedScalarType(nullable);
|
|
686
|
+
if (keeps && literal.kind === Kind3.INT && !Number.isSafeInteger(Number(literal.value)))
|
|
687
|
+
throw new Error(`@nxgt/graphql-codegen-zod: the default of ${where} holds ${literal.value}, which a JavaScript number cannot keep exact. Write it as a string, if its scalar takes one.`);
|
|
688
|
+
}
|
|
689
|
+
function defaultLiteral(input) {
|
|
690
|
+
if (input.astNode)
|
|
691
|
+
return input.astNode.defaultValue;
|
|
692
|
+
const loose = input;
|
|
693
|
+
if (loose.default?.literal)
|
|
694
|
+
return loose.default.literal;
|
|
695
|
+
if (loose.default) {
|
|
696
|
+
const toLiteral = graphql.valueToLiteral;
|
|
697
|
+
if (loose.default.value === undefined || !toLiteral)
|
|
698
|
+
return;
|
|
699
|
+
return toLiteral(loose.default.value, input.type);
|
|
700
|
+
}
|
|
701
|
+
if (loose.defaultValue === undefined)
|
|
702
|
+
return;
|
|
703
|
+
return astFromValue(loose.defaultValue, input.type) ?? undefined;
|
|
704
|
+
}
|
|
705
|
+
function coerced(value, type) {
|
|
706
|
+
const nullable = isNonNullType4(type) ? type.ofType : type;
|
|
707
|
+
if (value === null || value === undefined)
|
|
708
|
+
return value;
|
|
709
|
+
if (isListType4(nullable)) {
|
|
710
|
+
const items = Array.isArray(value) ? value : [value];
|
|
711
|
+
return items.map((item) => coerced(item, nullable.ofType));
|
|
712
|
+
}
|
|
713
|
+
if (isInputObjectType2(nullable) && typeof value === "object") {
|
|
714
|
+
const fields = nullable.getFields();
|
|
715
|
+
return Object.fromEntries(Object.entries(value).map(([key, field]) => {
|
|
716
|
+
const definition = fields[key];
|
|
717
|
+
return [key, definition ? coerced(field, definition.type) : field];
|
|
718
|
+
}));
|
|
719
|
+
}
|
|
720
|
+
if (getNamedType3(nullable) === GraphQLID3 && typeof value === "number")
|
|
721
|
+
return String(value);
|
|
722
|
+
return value;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// src/type-code.ts
|
|
726
|
+
import {
|
|
727
|
+
GraphQLBoolean as GraphQLBoolean3,
|
|
728
|
+
GraphQLFloat as GraphQLFloat3,
|
|
729
|
+
GraphQLID as GraphQLID4,
|
|
730
|
+
GraphQLInt as GraphQLInt3,
|
|
731
|
+
GraphQLString as GraphQLString3,
|
|
732
|
+
getNamedType as getNamedType4,
|
|
733
|
+
isEnumType as isEnumType4,
|
|
734
|
+
isInputObjectType as isInputObjectType3,
|
|
735
|
+
isListType as isListType5,
|
|
736
|
+
isNonNullType as isNonNullType5
|
|
737
|
+
} from "graphql";
|
|
738
|
+
var builtIns3 = {
|
|
739
|
+
[GraphQLString3.name]: "string",
|
|
740
|
+
[GraphQLID4.name]: "string",
|
|
741
|
+
[GraphQLInt3.name]: "number",
|
|
742
|
+
[GraphQLFloat3.name]: "number",
|
|
743
|
+
[GraphQLBoolean3.name]: "boolean"
|
|
744
|
+
};
|
|
745
|
+
function declareCyclic(type, code, writer, naming, cyclic) {
|
|
746
|
+
const types = new TypeCode(writer, naming, cyclic);
|
|
747
|
+
const output = naming.type(type.name);
|
|
748
|
+
const input = `${output}Input`;
|
|
749
|
+
const doc = docComment(type.description);
|
|
750
|
+
return [
|
|
751
|
+
`${doc}export type ${output} = ${types.object(type, "output")};`,
|
|
752
|
+
`${doc}export type ${input} = ${types.object(type, "input")};`,
|
|
753
|
+
`${doc}export const ${naming.schema(output)}: z.ZodType<${output}, ${input}> = ${code};`
|
|
754
|
+
].join(`
|
|
755
|
+
`);
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
class TypeCode {
|
|
759
|
+
#writer;
|
|
760
|
+
#naming;
|
|
761
|
+
#cyclic;
|
|
762
|
+
constructor(writer, naming, cyclic) {
|
|
763
|
+
this.#writer = writer;
|
|
764
|
+
this.#naming = naming;
|
|
765
|
+
this.#cyclic = cyclic;
|
|
766
|
+
}
|
|
767
|
+
object(type, side) {
|
|
768
|
+
const fields = Object.values(type.getFields());
|
|
769
|
+
if (type.isOneOf)
|
|
770
|
+
return fields.map((field) => `{ ${field.name}: ${this.#required(field.type, side)} }`).join(" | ");
|
|
771
|
+
const members = fields.map((field) => `${docComment(field.description, "\t")} ${this.#member(field, side)};`);
|
|
772
|
+
return `{
|
|
773
|
+
${members.join(`
|
|
774
|
+
`)}
|
|
775
|
+
}`;
|
|
776
|
+
}
|
|
777
|
+
#member(field, side) {
|
|
778
|
+
const nullable = !isNonNullType5(field.type);
|
|
779
|
+
const filled = defaultLiteral(field) !== undefined;
|
|
780
|
+
const type = this.#required(field.type, side);
|
|
781
|
+
if (!nullable)
|
|
782
|
+
return side === "input" && filled ? `${field.name}?: ${type} | undefined` : `${field.name}: ${type}`;
|
|
783
|
+
if (side === "output" && filled)
|
|
784
|
+
return `${field.name}: ${type} | null`;
|
|
785
|
+
return `${field.name}?: ${type} | null | undefined`;
|
|
786
|
+
}
|
|
787
|
+
#required(type, side) {
|
|
788
|
+
if (isNonNullType5(type))
|
|
789
|
+
return this.#required(type.ofType, side);
|
|
790
|
+
if (!isListType5(type))
|
|
791
|
+
return this.#named(type, side);
|
|
792
|
+
const item = isNonNullType5(type.ofType) ? this.#required(type.ofType, side) : `${this.#required(type.ofType, side)} | null | undefined`;
|
|
793
|
+
const list = `Array<${item}>`;
|
|
794
|
+
return side === "input" ? `${this.#named(getNamedType4(type), side)} | ${list}` : list;
|
|
795
|
+
}
|
|
796
|
+
#named(type, side) {
|
|
797
|
+
const builtIn = builtIns3[type.name];
|
|
798
|
+
if (builtIn)
|
|
799
|
+
return builtIn;
|
|
800
|
+
if (isEnumType4(type))
|
|
801
|
+
return `z.output<typeof ${this.#writer.schemaName(type)}>`;
|
|
802
|
+
if (isInputObjectType3(type)) {
|
|
803
|
+
const name = this.#naming.type(type.name);
|
|
804
|
+
if (this.#cyclic.has(type.name))
|
|
805
|
+
return side === "output" ? name : `${name}Input`;
|
|
806
|
+
return `z.${side}<typeof ${this.#writer.schemaName(type)}>`;
|
|
807
|
+
}
|
|
808
|
+
return `z.${side}<typeof ${this.#writer.scalarCode(type)}>`;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// src/schema-output.ts
|
|
813
|
+
function schemaBlocks(schema, writer, naming) {
|
|
814
|
+
const types = Object.values(schema.getTypeMap()).filter((type) => !type.name.startsWith("__"));
|
|
815
|
+
const cyclic = inputCycles(schema);
|
|
816
|
+
return [
|
|
817
|
+
...types.filter(isEnumType5).map((type) => enumBlock(type, naming)),
|
|
818
|
+
...types.filter(isInputObjectType4).map((type) => inputBlock(type, writer, naming, cyclic)),
|
|
819
|
+
...types.flatMap((type) => {
|
|
820
|
+
if (!isObjectType5(type) && !isInterfaceType3(type))
|
|
821
|
+
return [];
|
|
822
|
+
return Object.values(type.getFields()).filter((field) => field.args.length > 0).map((field) => {
|
|
823
|
+
const name = naming.args(type.name, field.name);
|
|
824
|
+
const members = field.args.map((arg) => [
|
|
825
|
+
arg.name,
|
|
826
|
+
{
|
|
827
|
+
...writer.value(arg.type, constraintsOn(writer.directive, arg.astNode), `${type.name}.${field.name}(${arg.name}:)`, defaultLiteral(arg)),
|
|
828
|
+
description: arg.description
|
|
829
|
+
}
|
|
830
|
+
]);
|
|
831
|
+
return declare(name, naming, `z.object({
|
|
832
|
+
${objectMembers(members)}
|
|
833
|
+
})`, "output");
|
|
834
|
+
});
|
|
835
|
+
})
|
|
836
|
+
];
|
|
837
|
+
}
|
|
838
|
+
function enumBlock(type, naming) {
|
|
839
|
+
const values = type.getValues().map((value) => JSON.stringify(value.name));
|
|
840
|
+
return declare(naming.type(type.name), naming, `z.enum([${values.join(", ")}])`, "output", type.description);
|
|
841
|
+
}
|
|
842
|
+
function inputBlock(type, writer, naming, cyclic) {
|
|
843
|
+
const code = inputCode(type, writer);
|
|
844
|
+
return cyclic.has(type.name) ? declareCyclic(type, code, writer, naming, cyclic) : declare(naming.type(type.name), naming, code, "output", type.description);
|
|
845
|
+
}
|
|
846
|
+
function inputCode(type, writer) {
|
|
847
|
+
const fields = Object.values(type.getFields());
|
|
848
|
+
const constraints = (field) => constraintsOn(writer.directive, field.astNode);
|
|
849
|
+
const where = (field) => `${type.name}.${field.name}`;
|
|
850
|
+
if (type.isOneOf) {
|
|
851
|
+
const members = fields.map((field) => {
|
|
852
|
+
const member = {
|
|
853
|
+
...writer.required(field.type, constraints(field), where(field)),
|
|
854
|
+
description: field.description
|
|
855
|
+
};
|
|
856
|
+
return ` z.strictObject({
|
|
857
|
+
${indent(objectMembers([[field.name, member]]))}
|
|
858
|
+
})`;
|
|
859
|
+
});
|
|
860
|
+
return `z.union([
|
|
861
|
+
${members.join(`,
|
|
862
|
+
`)},
|
|
863
|
+
])`;
|
|
864
|
+
}
|
|
865
|
+
const members = fields.map((field) => [
|
|
866
|
+
field.name,
|
|
867
|
+
{
|
|
868
|
+
...writer.value(field.type, constraints(field), where(field), defaultLiteral(field)),
|
|
869
|
+
description: field.description
|
|
870
|
+
}
|
|
871
|
+
]);
|
|
872
|
+
return `z.strictObject({
|
|
873
|
+
${objectMembers(members)}
|
|
874
|
+
})`;
|
|
875
|
+
}
|
|
876
|
+
function indent(code) {
|
|
877
|
+
return code.split(`
|
|
878
|
+
`).map((line) => ` ${line}`).join(`
|
|
879
|
+
`);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// src/variables-output.ts
|
|
883
|
+
import {
|
|
884
|
+
constraintsOn as constraintsOn2
|
|
885
|
+
} from "@nxgt/graphql-validation/codegen";
|
|
886
|
+
import {
|
|
887
|
+
getNamedType as getNamedType5,
|
|
888
|
+
isInputObjectType as isInputObjectType5,
|
|
889
|
+
isListType as isListType6,
|
|
890
|
+
isNonNullType as isNonNullType6,
|
|
891
|
+
Kind as Kind4,
|
|
892
|
+
TypeInfo,
|
|
893
|
+
typeFromAST,
|
|
894
|
+
visit,
|
|
895
|
+
visitWithTypeInfo
|
|
896
|
+
} from "graphql";
|
|
897
|
+
function variablesBlocks(schema, documents, writer, naming) {
|
|
898
|
+
const definitions = documents.flatMap((file) => file.document?.definitions ?? []);
|
|
899
|
+
const fragments = new Map(definitions.filter((node) => node.kind === Kind4.FRAGMENT_DEFINITION).map((node) => [node.name.value, node]));
|
|
900
|
+
return definitions.filter((node) => node.kind === Kind4.OPERATION_DEFINITION).flatMap((operation) => {
|
|
901
|
+
const operationName = operation.name?.value;
|
|
902
|
+
if (!operationName)
|
|
903
|
+
return [];
|
|
904
|
+
const used = usages(schema, writer.directive, operation, fragments);
|
|
905
|
+
const members = (operation.variableDefinitions ?? []).map((definition) => {
|
|
906
|
+
const variable = definition.variable.name.value;
|
|
907
|
+
const type = typeFromAST(schema, definition.type);
|
|
908
|
+
if (!type) {
|
|
909
|
+
throw new Error(`@nxgt/graphql-codegen-zod: ${operationName}'s $${variable} has a type the schema does not define.`);
|
|
910
|
+
}
|
|
911
|
+
const where = `${operationName}($${variable}:)`;
|
|
912
|
+
const constraints = merged(used.get(variable) ?? [], where);
|
|
913
|
+
return [
|
|
914
|
+
variable,
|
|
915
|
+
writer.value(type, constraints, where, definition.defaultValue)
|
|
916
|
+
];
|
|
917
|
+
});
|
|
918
|
+
return [
|
|
919
|
+
declare(naming.variables(operationName, operation.operation), naming, `z.object({
|
|
920
|
+
${objectMembers(members)}
|
|
921
|
+
})`, "input")
|
|
922
|
+
];
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
function usages(schema, directive, operation, fragments) {
|
|
926
|
+
const walk = {
|
|
927
|
+
found: new Map,
|
|
928
|
+
variables: new Set((operation.variableDefinitions ?? []).map((d) => d.variable.name.value)),
|
|
929
|
+
directive
|
|
930
|
+
};
|
|
931
|
+
const seen = new Set;
|
|
932
|
+
const visitNode = (node) => {
|
|
933
|
+
const typeInfo = new TypeInfo(schema);
|
|
934
|
+
visit(node, visitWithTypeInfo(typeInfo, {
|
|
935
|
+
Argument(argument) {
|
|
936
|
+
const definition = typeInfo.getArgument();
|
|
937
|
+
const field = typeInfo.getFieldDef();
|
|
938
|
+
if (!definition || !field || typeInfo.getDirective() !== null)
|
|
939
|
+
return;
|
|
940
|
+
const where = `${typeInfo.getParentType()?.name}.${field.name}(${definition.name}:)`;
|
|
941
|
+
walkValue(walk, argument.value, definition.type, constraintsOn2(directive, definition.astNode), where);
|
|
942
|
+
},
|
|
943
|
+
FragmentSpread(spread) {
|
|
944
|
+
const fragment = fragments.get(spread.name.value);
|
|
945
|
+
if (fragment && !seen.has(fragment.name.value)) {
|
|
946
|
+
seen.add(fragment.name.value);
|
|
947
|
+
visitNode(fragment);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
}));
|
|
951
|
+
};
|
|
952
|
+
visitNode(operation);
|
|
953
|
+
return walk.found;
|
|
954
|
+
}
|
|
955
|
+
function walkValue(walk, value, type, constraints, where) {
|
|
956
|
+
if (value.kind === Kind4.VARIABLE) {
|
|
957
|
+
if (!walk.variables.has(value.name.value))
|
|
958
|
+
return;
|
|
959
|
+
const list = walk.found.get(value.name.value) ?? [];
|
|
960
|
+
list.push({ where, constraints });
|
|
961
|
+
walk.found.set(value.name.value, list);
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
const nullable = isNonNullType6(type) ? type.ofType : type;
|
|
965
|
+
if (value.kind === Kind4.LIST && isListType6(nullable)) {
|
|
966
|
+
const items = constraints.filter(({ rule }) => rule.target !== "list");
|
|
967
|
+
for (const item of value.values)
|
|
968
|
+
walkValue(walk, item, nullable.ofType, items, where);
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
const named = getNamedType5(type);
|
|
972
|
+
if (value.kind === Kind4.OBJECT && isInputObjectType5(named)) {
|
|
973
|
+
for (const field of value.fields) {
|
|
974
|
+
const definition = named.getFields()[field.name.value];
|
|
975
|
+
if (definition) {
|
|
976
|
+
walkValue(walk, field.value, definition.type, constraintsOn2(walk.directive, definition.astNode), `${named.name}.${definition.name}`);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
function merged(usages, where) {
|
|
982
|
+
const all = [];
|
|
983
|
+
for (const usage of usages) {
|
|
984
|
+
for (const constraint of usage.constraints) {
|
|
985
|
+
const same = all.some((known) => known.rule === constraint.rule && known.value === constraint.value);
|
|
986
|
+
if (!same)
|
|
987
|
+
all.push({ ...constraint, at: usage.where });
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
const bases = all.filter(({ rule }) => rule.base);
|
|
991
|
+
const [first, second] = bases;
|
|
992
|
+
if (first && second) {
|
|
993
|
+
throw new Error(`@nxgt/graphql-codegen-zod: ${where} is passed to ${first.at} (${first.rule.argument}: ${JSON.stringify(first.value)}) and to ${second.at} (${second.rule.argument}: ${JSON.stringify(second.value)}), whose schemas cannot both apply. Use one variable for each.`);
|
|
994
|
+
}
|
|
995
|
+
return [...bases, ...all.filter(({ rule }) => !rule.base)].map(({ rule, value }) => ({ rule, value }));
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// src/writer.ts
|
|
999
|
+
import {
|
|
1000
|
+
inputCode as inputCode2
|
|
1001
|
+
} from "@nxgt/graphql-validation/codegen";
|
|
1002
|
+
import {
|
|
1003
|
+
GraphQLNonNull,
|
|
1004
|
+
getNamedType as getNamedType6,
|
|
1005
|
+
isEnumType as isEnumType6,
|
|
1006
|
+
isInputObjectType as isInputObjectType6,
|
|
1007
|
+
isNonNullType as isNonNullType7,
|
|
1008
|
+
isScalarType as isScalarType4,
|
|
1009
|
+
isSpecifiedScalarType as isSpecifiedScalarType2
|
|
1010
|
+
} from "graphql";
|
|
1011
|
+
class Writer {
|
|
1012
|
+
directive;
|
|
1013
|
+
#naming;
|
|
1014
|
+
#scalars;
|
|
1015
|
+
constructor(directive, naming, scalars) {
|
|
1016
|
+
this.directive = directive;
|
|
1017
|
+
this.#naming = naming;
|
|
1018
|
+
this.#scalars = scalars;
|
|
1019
|
+
}
|
|
1020
|
+
scalarCode(type) {
|
|
1021
|
+
return this.#scalars.code(type);
|
|
1022
|
+
}
|
|
1023
|
+
schemaName(type) {
|
|
1024
|
+
return this.#naming.schema(this.#naming.type(type.name));
|
|
1025
|
+
}
|
|
1026
|
+
value(type, constraints, where, defaultValue) {
|
|
1027
|
+
let lazy = false;
|
|
1028
|
+
const named = (named) => {
|
|
1029
|
+
if (isInputObjectType6(named)) {
|
|
1030
|
+
lazy = true;
|
|
1031
|
+
return this.schemaName(named);
|
|
1032
|
+
}
|
|
1033
|
+
if (isEnumType6(named))
|
|
1034
|
+
return this.schemaName(named);
|
|
1035
|
+
return this.#scalars.code(named);
|
|
1036
|
+
};
|
|
1037
|
+
if (!defaultValue)
|
|
1038
|
+
return {
|
|
1039
|
+
code: inputCode2(type, constraints, where, named, {
|
|
1040
|
+
list: singleOrList
|
|
1041
|
+
}),
|
|
1042
|
+
lazy
|
|
1043
|
+
};
|
|
1044
|
+
const nullable = !isNonNullType7(type);
|
|
1045
|
+
const required = inputCode2(nullable ? new GraphQLNonNull(type) : type, constraints, where, named, { list: singleOrList });
|
|
1046
|
+
const value = JSON.stringify(parsedDefault(defaultValue, type, where));
|
|
1047
|
+
return {
|
|
1048
|
+
code: `${required}.prefault(${value})${nullable ? ".nullable()" : ""}`,
|
|
1049
|
+
lazy
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
required(type, constraints, where) {
|
|
1053
|
+
return this.value(isNonNullType7(type) ? type : new GraphQLNonNull(type), constraints, where);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
var singleOrList = ({
|
|
1057
|
+
type,
|
|
1058
|
+
code,
|
|
1059
|
+
single
|
|
1060
|
+
}) => {
|
|
1061
|
+
const named = getNamedType6(type);
|
|
1062
|
+
const transforms = isInputObjectType6(named) || isScalarType4(named) && !isSpecifiedScalarType2(named);
|
|
1063
|
+
const first = transforms ? `z.custom<z.input<typeof ${single}>>((value) => value != null && !Array.isArray(value))` : single;
|
|
1064
|
+
return `z.union([${code}, ${first}.transform((value): unknown[] => [value]).pipe(${code})])`;
|
|
1065
|
+
};
|
|
1066
|
+
|
|
1067
|
+
// src/index.ts
|
|
1068
|
+
async function plugin(schema, documents, config = {}, info) {
|
|
1069
|
+
const directive = checkConstraints(schema);
|
|
1070
|
+
if (directive)
|
|
1071
|
+
assertSdl(schema);
|
|
1072
|
+
const naming = new Naming(config);
|
|
1073
|
+
const write = async (imports) => {
|
|
1074
|
+
const scalars = await ScalarSources.load(config, imports, info?.outputFile);
|
|
1075
|
+
const writer = new Writer(directive, naming, scalars);
|
|
1076
|
+
return [
|
|
1077
|
+
...schemaBlocks(schema, writer, naming),
|
|
1078
|
+
...config.objects === false ? [] : objectBlocks(schema, writer, naming),
|
|
1079
|
+
...variablesBlocks(schema, documents, writer, naming),
|
|
1080
|
+
...config.operations === false ? [] : resultsBlocks(schema, documents, writer, naming)
|
|
1081
|
+
];
|
|
1082
|
+
};
|
|
1083
|
+
const declarations = (await write(new Imports)).flatMap((block) => [...block.matchAll(/^(?:export )?(const|type) ([\w$]+)/gm)].map((m) => `${m[1]} ${m[2]}`));
|
|
1084
|
+
const twice = declarations.find((declaration, index) => declarations.indexOf(declaration) !== index);
|
|
1085
|
+
if (twice)
|
|
1086
|
+
throw new Error(`@nxgt/graphql-codegen-zod: the file would declare ${twice.split(" ")[1]} 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.`);
|
|
1087
|
+
const declared = declarations.map((declaration) => declaration.split(" ")[1] ?? "");
|
|
1088
|
+
const imports = new Imports(declared);
|
|
1089
|
+
const blocks = await write(imports);
|
|
1090
|
+
return `${imports.lines().join(`
|
|
1091
|
+
`)}
|
|
1092
|
+
|
|
1093
|
+
${blocks.join(`
|
|
1094
|
+
|
|
1095
|
+
`)}
|
|
1096
|
+
`;
|
|
1097
|
+
}
|
|
1098
|
+
function assertSdl(schema) {
|
|
1099
|
+
for (const type of Object.values(schema.getTypeMap())) {
|
|
1100
|
+
if (type.name.startsWith("__"))
|
|
1101
|
+
continue;
|
|
1102
|
+
const inputs = isInputObjectType7(type) ? Object.values(type.getFields()) : isObjectType6(type) || isInterfaceType4(type) ? Object.values(type.getFields()).flatMap((field) => field.args) : [];
|
|
1103
|
+
const bare = inputs.find((input) => !input.astNode);
|
|
1104
|
+
if (bare) {
|
|
1105
|
+
throw new Error(`@nxgt/graphql-codegen-zod: the schema declares @constraint, but ${type.name} has no SDL to read it from (an introspected schema?). Point codegen's schema at the SDL files.`);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
export {
|
|
1110
|
+
plugin
|
|
1111
|
+
};
|
|
1112
|
+
|
|
1113
|
+
//# debugId=7FB3A417D01910B464756E2164756E21
|
|
1114
|
+
//# sourceMappingURL=index.js.map
|