@davars/graphql-codegen-zod 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 +22 -0
- package/README.md +83 -0
- package/dist/config.d.ts +363 -0
- package/dist/config.js +1 -0
- package/dist/directive.d.ts +24 -0
- package/dist/directive.js +211 -0
- package/dist/graphql.d.ts +21 -0
- package/dist/graphql.js +167 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +37 -0
- package/dist/regexp.d.ts +1 -0
- package/dist/regexp.js +2 -0
- package/dist/schema_visitor.d.ts +17 -0
- package/dist/schema_visitor.js +33 -0
- package/dist/transform_config.d.ts +20 -0
- package/dist/transform_config.js +1 -0
- package/dist/transforms.d.ts +35 -0
- package/dist/transforms.js +137 -0
- package/dist/types.d.ts +10 -0
- package/dist/types.js +1 -0
- package/dist/visitor.d.ts +19 -0
- package/dist/visitor.js +68 -0
- package/dist/zod.d.ts +32 -0
- package/dist/zod.js +368 -0
- package/package.json +66 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { Kind, valueFromASTUntyped } from 'graphql';
|
|
2
|
+
import { isConvertableRegexp } from './regexp.js';
|
|
3
|
+
function isFormattedDirectiveObjectArguments(arg) {
|
|
4
|
+
return arg !== undefined && !Array.isArray(arg);
|
|
5
|
+
}
|
|
6
|
+
// ```yml
|
|
7
|
+
// directives:
|
|
8
|
+
// required:
|
|
9
|
+
// msg: required
|
|
10
|
+
// constraint:
|
|
11
|
+
// minLength: min
|
|
12
|
+
// format:
|
|
13
|
+
// uri: url
|
|
14
|
+
// email: email
|
|
15
|
+
// ```
|
|
16
|
+
//
|
|
17
|
+
// This function convterts to like below
|
|
18
|
+
// {
|
|
19
|
+
// 'required': {
|
|
20
|
+
// 'msg': ['required', '$1'],
|
|
21
|
+
// },
|
|
22
|
+
// 'constraint': {
|
|
23
|
+
// 'minLength': ['min', '$1'],
|
|
24
|
+
// 'format': {
|
|
25
|
+
// 'uri': ['url', '$2'],
|
|
26
|
+
// 'email': ['email', '$2'],
|
|
27
|
+
// }
|
|
28
|
+
// }
|
|
29
|
+
// }
|
|
30
|
+
export function formatDirectiveConfig(config) {
|
|
31
|
+
return Object.fromEntries(Object.entries(config).map(([directive, arg]) => {
|
|
32
|
+
const formatted = Object.fromEntries(Object.entries(arg).map(([arg, val]) => {
|
|
33
|
+
if (Array.isArray(val))
|
|
34
|
+
return [arg, val];
|
|
35
|
+
if (typeof val === 'string')
|
|
36
|
+
return [arg, [val, '$1']];
|
|
37
|
+
return [arg, formatDirectiveObjectArguments(val)];
|
|
38
|
+
}));
|
|
39
|
+
return [directive, formatted];
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
// ```yml
|
|
43
|
+
// format:
|
|
44
|
+
// # For example, `@constraint(format: "uri")`. this case $1 will be "uri".
|
|
45
|
+
// # Therefore the generator generates yup schema `.url()` followed by `uri: 'url'`
|
|
46
|
+
// # If $1 does not match anywhere, the generator will ignore.
|
|
47
|
+
// uri: url
|
|
48
|
+
// email: ["email", "$2"]
|
|
49
|
+
// ```
|
|
50
|
+
//
|
|
51
|
+
// This function convterts to like below
|
|
52
|
+
// {
|
|
53
|
+
// 'uri': ['url', '$2'],
|
|
54
|
+
// 'email': ['email'],
|
|
55
|
+
// }
|
|
56
|
+
export function formatDirectiveObjectArguments(args) {
|
|
57
|
+
const formatted = Object.entries(args).map(([arg, val]) => {
|
|
58
|
+
if (Array.isArray(val))
|
|
59
|
+
return [arg, val];
|
|
60
|
+
return [arg, [val, '$2']];
|
|
61
|
+
});
|
|
62
|
+
return Object.fromEntries(formatted);
|
|
63
|
+
}
|
|
64
|
+
// This function generates `.required("message").min(100).email()`
|
|
65
|
+
//
|
|
66
|
+
// config
|
|
67
|
+
// {
|
|
68
|
+
// 'required': {
|
|
69
|
+
// 'msg': ['required', '$1'],
|
|
70
|
+
// },
|
|
71
|
+
// 'constraint': {
|
|
72
|
+
// 'minLength': ['min', '$1'],
|
|
73
|
+
// 'format': {
|
|
74
|
+
// 'uri': ['url', '$2'],
|
|
75
|
+
// 'email': ['email', '$2'],
|
|
76
|
+
// }
|
|
77
|
+
// }
|
|
78
|
+
// }
|
|
79
|
+
//
|
|
80
|
+
// GraphQL schema
|
|
81
|
+
// ```graphql
|
|
82
|
+
// input ExampleInput {
|
|
83
|
+
// email: String! @required(msg: "message") @constraint(minLength: 100, format: "email")
|
|
84
|
+
// }
|
|
85
|
+
// ```
|
|
86
|
+
export function buildApi(config, directives) {
|
|
87
|
+
return directives
|
|
88
|
+
.filter(directive => config[directive.name.value] !== undefined)
|
|
89
|
+
.map((directive) => {
|
|
90
|
+
const directiveName = directive.name.value;
|
|
91
|
+
const argsConfig = config[directiveName];
|
|
92
|
+
return buildApiFromDirectiveArguments(argsConfig, directive.arguments ?? []);
|
|
93
|
+
})
|
|
94
|
+
.join('');
|
|
95
|
+
}
|
|
96
|
+
// This function generates `[v.minLength(100), v.email()]`
|
|
97
|
+
// NOTE: valibot's API is not a method chain, so it is prepared separately from buildApi.
|
|
98
|
+
//
|
|
99
|
+
// config
|
|
100
|
+
// {
|
|
101
|
+
// 'constraint': {
|
|
102
|
+
// 'minLength': ['minLength', '$1'],
|
|
103
|
+
// 'format': {
|
|
104
|
+
// 'uri': ['url', '$2'],
|
|
105
|
+
// 'email': ['email', '$2'],
|
|
106
|
+
// }
|
|
107
|
+
// }
|
|
108
|
+
// }
|
|
109
|
+
//
|
|
110
|
+
// GraphQL schema
|
|
111
|
+
// ```graphql
|
|
112
|
+
// input ExampleInput {
|
|
113
|
+
// email: String! @required(msg: "message") @constraint(minLength: 100, format: "email")
|
|
114
|
+
// }
|
|
115
|
+
// ```
|
|
116
|
+
//
|
|
117
|
+
// FIXME: v.required() is not supported yet. v.required() is classified as `Methods` and must wrap the schema. ex) `v.required(v.object({...}))`
|
|
118
|
+
export function buildApiForValibot(config, directives) {
|
|
119
|
+
return directives
|
|
120
|
+
.filter(directive => config[directive.name.value] !== undefined)
|
|
121
|
+
.map((directive) => {
|
|
122
|
+
const directiveName = directive.name.value;
|
|
123
|
+
const argsConfig = config[directiveName];
|
|
124
|
+
const apis = _buildApiFromDirectiveArguments(argsConfig, directive.arguments ?? []);
|
|
125
|
+
return apis.map(api => `v${api}`);
|
|
126
|
+
})
|
|
127
|
+
.flat();
|
|
128
|
+
}
|
|
129
|
+
function buildApiSchema(validationSchema, argValue) {
|
|
130
|
+
if (!validationSchema)
|
|
131
|
+
return '';
|
|
132
|
+
const schemaApi = validationSchema[0];
|
|
133
|
+
const schemaApiArgs = validationSchema.slice(1).map((templateArg) => {
|
|
134
|
+
const gqlSchemaArgs = apiArgsFromConstValueNode(argValue);
|
|
135
|
+
return applyArgToApiSchemaTemplate(templateArg, gqlSchemaArgs);
|
|
136
|
+
});
|
|
137
|
+
return `.${schemaApi}(${schemaApiArgs.join(', ')})`;
|
|
138
|
+
}
|
|
139
|
+
function buildApiFromDirectiveArguments(config, args) {
|
|
140
|
+
return _buildApiFromDirectiveArguments(config, args).join('');
|
|
141
|
+
}
|
|
142
|
+
function _buildApiFromDirectiveArguments(config, args) {
|
|
143
|
+
return args
|
|
144
|
+
.map((arg) => {
|
|
145
|
+
const argName = arg.name.value;
|
|
146
|
+
const validationSchema = config[argName];
|
|
147
|
+
if (isFormattedDirectiveObjectArguments(validationSchema))
|
|
148
|
+
return buildApiFromDirectiveObjectArguments(validationSchema, arg.value);
|
|
149
|
+
return buildApiSchema(validationSchema, arg.value);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
function buildApiFromDirectiveObjectArguments(config, argValue) {
|
|
153
|
+
if (argValue.kind !== Kind.STRING && argValue.kind !== Kind.ENUM)
|
|
154
|
+
return '';
|
|
155
|
+
const validationSchema = config[argValue.value];
|
|
156
|
+
return buildApiSchema(validationSchema, argValue);
|
|
157
|
+
}
|
|
158
|
+
function applyArgToApiSchemaTemplate(template, apiArgs) {
|
|
159
|
+
const matches = template.matchAll(/\$(\d+)/g);
|
|
160
|
+
for (const match of matches) {
|
|
161
|
+
const placeholder = match[0]; // `$1`
|
|
162
|
+
const idx = Number.parseInt(match[1], 10) - 1; // start with `1 - 1`
|
|
163
|
+
const apiArg = apiArgs[idx];
|
|
164
|
+
if (apiArg === undefined) {
|
|
165
|
+
template = template.replace(placeholder, '');
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (template === placeholder)
|
|
169
|
+
return stringify(apiArg);
|
|
170
|
+
template = template.replace(placeholder, apiArg);
|
|
171
|
+
}
|
|
172
|
+
if (template !== '')
|
|
173
|
+
return stringify(template, true);
|
|
174
|
+
return template;
|
|
175
|
+
}
|
|
176
|
+
function stringify(arg, quoteString) {
|
|
177
|
+
if (Array.isArray(arg))
|
|
178
|
+
return arg.map(v => stringify(v, true)).join(',');
|
|
179
|
+
if (typeof arg === 'string') {
|
|
180
|
+
if (isConvertableRegexp(arg))
|
|
181
|
+
return arg;
|
|
182
|
+
const v = tryEval(arg);
|
|
183
|
+
if (v !== undefined)
|
|
184
|
+
arg = v;
|
|
185
|
+
if (quoteString)
|
|
186
|
+
return JSON.stringify(arg);
|
|
187
|
+
}
|
|
188
|
+
if (typeof arg === 'boolean' || typeof arg === 'number' || typeof arg === 'bigint' || arg === 'undefined' || arg === null)
|
|
189
|
+
return `${arg}`;
|
|
190
|
+
return JSON.stringify(arg);
|
|
191
|
+
}
|
|
192
|
+
function apiArgsFromConstValueNode(value) {
|
|
193
|
+
const val = valueFromASTUntyped(value);
|
|
194
|
+
if (Array.isArray(val))
|
|
195
|
+
return val;
|
|
196
|
+
return [val];
|
|
197
|
+
}
|
|
198
|
+
function tryEval(maybeValidJavaScript) {
|
|
199
|
+
try {
|
|
200
|
+
// eslint-disable-next-line no-eval
|
|
201
|
+
return eval(maybeValidJavaScript);
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
export const exportedForTesting = {
|
|
208
|
+
applyArgToApiSchemaTemplate,
|
|
209
|
+
buildApiFromDirectiveObjectArguments,
|
|
210
|
+
buildApiFromDirectiveArguments,
|
|
211
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { DocumentNode, GraphQLSchema, InterfaceTypeDefinitionNode, ListTypeNode, NamedTypeNode, NonNullTypeNode, ObjectTypeDefinitionNode, TypeNode } from 'graphql';
|
|
2
|
+
import { Graph } from 'graphlib';
|
|
3
|
+
export declare const isListType: (typ?: TypeNode) => typ is ListTypeNode;
|
|
4
|
+
export declare const isNonNullType: (typ?: TypeNode) => typ is NonNullTypeNode;
|
|
5
|
+
export declare const isNamedType: (typ?: TypeNode) => typ is NamedTypeNode;
|
|
6
|
+
export declare const isInput: (kind: string) => boolean;
|
|
7
|
+
type ObjectTypeDefinitionFn = (node: ObjectTypeDefinitionNode) => any;
|
|
8
|
+
type InterfaceTypeDefinitionFn = (node: InterfaceTypeDefinitionNode) => any;
|
|
9
|
+
export declare function ObjectTypeDefinitionBuilder(useObjectTypes: boolean | undefined, callback: ObjectTypeDefinitionFn): ObjectTypeDefinitionFn | undefined;
|
|
10
|
+
export declare function InterfaceTypeDefinitionBuilder(useInterfaceTypes: boolean | undefined, callback: InterfaceTypeDefinitionFn): InterfaceTypeDefinitionFn | undefined;
|
|
11
|
+
export declare function topologicalSortAST(schema: GraphQLSchema, ast: DocumentNode): DocumentNode;
|
|
12
|
+
/**
|
|
13
|
+
* [Re-implemented topsort function][topsort-ref] with cycle handling. This version iterates over
|
|
14
|
+
* all nodes in the graph to ensure every node is visited, even if the graph contains cycles.
|
|
15
|
+
*
|
|
16
|
+
* [topsort-ref]: https://github.com/dagrejs/graphlib/blob/8d27cb89029081c72eb89dde652602805bdd0a34/lib/alg/topsort.js
|
|
17
|
+
*/
|
|
18
|
+
export declare function topsort(g: Graph): string[];
|
|
19
|
+
export declare function isGeneratedByIntrospection(schema: GraphQLSchema): boolean;
|
|
20
|
+
export declare function escapeGraphQLCharacters(input: string): string;
|
|
21
|
+
export {};
|
package/dist/graphql.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { Graph } from 'graphlib';
|
|
2
|
+
import { isSpecifiedScalarType, Kind, visit, } from 'graphql';
|
|
3
|
+
/**
|
|
4
|
+
* Recursively unwraps a GraphQL type until it reaches the NamedType.
|
|
5
|
+
*
|
|
6
|
+
* Since a GraphQL type is defined as either a NamedTypeNode, ListTypeNode, or NonNullTypeNode,
|
|
7
|
+
* this implementation safely recurses until the underlying NamedTypeNode is reached.
|
|
8
|
+
*/
|
|
9
|
+
function getNamedType(typeNode) {
|
|
10
|
+
return typeNode.kind === Kind.NAMED_TYPE ? typeNode : getNamedType(typeNode.type);
|
|
11
|
+
}
|
|
12
|
+
export const isListType = (typ) => typ?.kind === Kind.LIST_TYPE;
|
|
13
|
+
export const isNonNullType = (typ) => typ?.kind === Kind.NON_NULL_TYPE;
|
|
14
|
+
export const isNamedType = (typ) => typ?.kind === Kind.NAMED_TYPE;
|
|
15
|
+
export const isInput = (kind) => kind.includes('Input');
|
|
16
|
+
export function ObjectTypeDefinitionBuilder(useObjectTypes, callback) {
|
|
17
|
+
if (!useObjectTypes)
|
|
18
|
+
return undefined;
|
|
19
|
+
return (node) => {
|
|
20
|
+
if (/^(Query|Mutation|Subscription)$/.test(node.name.value))
|
|
21
|
+
return;
|
|
22
|
+
return callback(node);
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export function InterfaceTypeDefinitionBuilder(useInterfaceTypes, callback) {
|
|
26
|
+
if (!useInterfaceTypes)
|
|
27
|
+
return undefined;
|
|
28
|
+
return (node) => {
|
|
29
|
+
return callback(node);
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function topologicalSortAST(schema, ast) {
|
|
33
|
+
const dependencyGraph = new Graph();
|
|
34
|
+
const targetKinds = [
|
|
35
|
+
Kind.OBJECT_TYPE_DEFINITION,
|
|
36
|
+
Kind.INPUT_OBJECT_TYPE_DEFINITION,
|
|
37
|
+
Kind.INTERFACE_TYPE_DEFINITION,
|
|
38
|
+
Kind.SCALAR_TYPE_DEFINITION,
|
|
39
|
+
Kind.ENUM_TYPE_DEFINITION,
|
|
40
|
+
Kind.UNION_TYPE_DEFINITION,
|
|
41
|
+
];
|
|
42
|
+
visit(ast, {
|
|
43
|
+
enter: (node) => {
|
|
44
|
+
switch (node.kind) {
|
|
45
|
+
case Kind.OBJECT_TYPE_DEFINITION:
|
|
46
|
+
case Kind.INPUT_OBJECT_TYPE_DEFINITION:
|
|
47
|
+
case Kind.INTERFACE_TYPE_DEFINITION: {
|
|
48
|
+
const typeName = node.name.value;
|
|
49
|
+
dependencyGraph.setNode(typeName);
|
|
50
|
+
if (node.fields) {
|
|
51
|
+
node.fields.forEach((field) => {
|
|
52
|
+
// Unwrap the type
|
|
53
|
+
const namedTypeNode = getNamedType(field.type);
|
|
54
|
+
const dependency = namedTypeNode.name.value;
|
|
55
|
+
const namedType = schema.getType(dependency);
|
|
56
|
+
if (namedType?.astNode?.kind === undefined
|
|
57
|
+
|| !targetKinds.includes(namedType.astNode.kind)) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (!dependencyGraph.hasNode(dependency)) {
|
|
61
|
+
dependencyGraph.setNode(dependency);
|
|
62
|
+
}
|
|
63
|
+
dependencyGraph.setEdge(typeName, dependency);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
case Kind.SCALAR_TYPE_DEFINITION:
|
|
69
|
+
case Kind.ENUM_TYPE_DEFINITION: {
|
|
70
|
+
dependencyGraph.setNode(node.name.value);
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
case Kind.UNION_TYPE_DEFINITION: {
|
|
74
|
+
const dependency = node.name.value;
|
|
75
|
+
if (!dependencyGraph.hasNode(dependency))
|
|
76
|
+
dependencyGraph.setNode(dependency);
|
|
77
|
+
node.types?.forEach((type) => {
|
|
78
|
+
const dependency = type.name.value;
|
|
79
|
+
const typ = schema.getType(dependency);
|
|
80
|
+
if (typ?.astNode?.kind === undefined || !targetKinds.includes(typ.astNode.kind))
|
|
81
|
+
return;
|
|
82
|
+
dependencyGraph.setEdge(node.name.value, dependency);
|
|
83
|
+
});
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
default:
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
const sorted = topsort(dependencyGraph);
|
|
92
|
+
// Create a map of definitions for quick access, using the definition's name as the key.
|
|
93
|
+
const definitionsMap = new Map();
|
|
94
|
+
// SCHEMA_DEFINITION does not have a name.
|
|
95
|
+
// https://spec.graphql.org/October2021/#sec-Schema
|
|
96
|
+
const astDefinitions = ast.definitions.filter(def => def.kind !== Kind.SCHEMA_DEFINITION);
|
|
97
|
+
astDefinitions.forEach((definition) => {
|
|
98
|
+
if (hasNameField(definition) && definition.name)
|
|
99
|
+
definitionsMap.set(definition.name.value, definition);
|
|
100
|
+
});
|
|
101
|
+
// Two arrays to store sorted and unsorted definitions.
|
|
102
|
+
const sortedDefinitions = [];
|
|
103
|
+
const notSortedDefinitions = [];
|
|
104
|
+
// Iterate over sorted type names and retrieve their corresponding definitions.
|
|
105
|
+
sorted.forEach((sortedType) => {
|
|
106
|
+
const definition = definitionsMap.get(sortedType);
|
|
107
|
+
if (definition) {
|
|
108
|
+
sortedDefinitions.push(definition);
|
|
109
|
+
definitionsMap.delete(sortedType);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
// Definitions that are left in the map were not included in sorted list
|
|
113
|
+
// Add them to notSortedDefinitions.
|
|
114
|
+
definitionsMap.forEach(definition => notSortedDefinitions.push(definition));
|
|
115
|
+
const newDefinitions = [...sortedDefinitions, ...notSortedDefinitions];
|
|
116
|
+
if (newDefinitions.length !== astDefinitions.length) {
|
|
117
|
+
throw new Error(`Unexpected definition length after sorting: want ${astDefinitions.length} but got ${newDefinitions.length}`);
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
...ast,
|
|
121
|
+
definitions: newDefinitions,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function hasNameField(node) {
|
|
125
|
+
return 'name' in node;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* [Re-implemented topsort function][topsort-ref] with cycle handling. This version iterates over
|
|
129
|
+
* all nodes in the graph to ensure every node is visited, even if the graph contains cycles.
|
|
130
|
+
*
|
|
131
|
+
* [topsort-ref]: https://github.com/dagrejs/graphlib/blob/8d27cb89029081c72eb89dde652602805bdd0a34/lib/alg/topsort.js
|
|
132
|
+
*/
|
|
133
|
+
export function topsort(g) {
|
|
134
|
+
const visited = new Set();
|
|
135
|
+
const results = [];
|
|
136
|
+
function visit(node) {
|
|
137
|
+
if (visited.has(node))
|
|
138
|
+
return;
|
|
139
|
+
visited.add(node);
|
|
140
|
+
// Recursively visit all predecessors of the node.
|
|
141
|
+
g.predecessors(node)?.forEach(visit);
|
|
142
|
+
results.push(node);
|
|
143
|
+
}
|
|
144
|
+
// Visit every node in the graph (instead of only sinks)
|
|
145
|
+
g.nodes().forEach(visit);
|
|
146
|
+
return results.reverse();
|
|
147
|
+
}
|
|
148
|
+
export function isGeneratedByIntrospection(schema) {
|
|
149
|
+
return Object.entries(schema.getTypeMap())
|
|
150
|
+
.filter(([name, type]) => !name.startsWith('__') && !isSpecifiedScalarType(type))
|
|
151
|
+
.every(([, type]) => type.astNode === undefined);
|
|
152
|
+
}
|
|
153
|
+
// https://spec.graphql.org/October2021/#EscapedCharacter
|
|
154
|
+
const escapeMap = {
|
|
155
|
+
'\"': '\\\"',
|
|
156
|
+
'\\': '\\\\',
|
|
157
|
+
'\/': '\\/',
|
|
158
|
+
'\b': '\\b',
|
|
159
|
+
'\f': '\\f',
|
|
160
|
+
'\n': '\\n',
|
|
161
|
+
'\r': '\\r',
|
|
162
|
+
'\t': '\\t',
|
|
163
|
+
};
|
|
164
|
+
export function escapeGraphQLCharacters(input) {
|
|
165
|
+
// eslint-disable-next-line regexp/no-escape-backspace
|
|
166
|
+
return input.replace(/["\\/\f\n\r\t\b]/g, match => escapeMap[match]);
|
|
167
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { PluginFunction, Types } from '@graphql-codegen/plugin-helpers';
|
|
2
|
+
import type { ValidationSchemaPluginConfig } from './config.js';
|
|
3
|
+
export declare const plugin: PluginFunction<ValidationSchemaPluginConfig, Types.ComplexPluginOutput>;
|
|
4
|
+
export type { ValidationSchemaPluginConfig };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { transformSchemaAST } from '@graphql-codegen/schema-ast';
|
|
2
|
+
import { buildSchema, printSchema, visit } from 'graphql';
|
|
3
|
+
import { isGeneratedByIntrospection, topologicalSortAST } from './graphql.js';
|
|
4
|
+
import { loadTransforms } from './transforms.js';
|
|
5
|
+
import { ZodSchemaVisitor } from './zod.js';
|
|
6
|
+
export const plugin = (schema, _documents, config, info) => {
|
|
7
|
+
const { schema: _schema, ast } = _transformSchemaAST(schema, config);
|
|
8
|
+
let resolvedTransforms = {};
|
|
9
|
+
if (config.transforms) {
|
|
10
|
+
const outputFile = info?.outputFile ?? 'output.ts';
|
|
11
|
+
resolvedTransforms = loadTransforms(config.transforms, outputFile);
|
|
12
|
+
}
|
|
13
|
+
const visitor = new ZodSchemaVisitor(_schema, config, resolvedTransforms);
|
|
14
|
+
const result = visit(ast, visitor);
|
|
15
|
+
const generated = result.definitions.filter(def => typeof def === 'string');
|
|
16
|
+
return {
|
|
17
|
+
prepend: visitor.buildImports(),
|
|
18
|
+
content: [visitor.initialEmit(), ...generated].join('\n'),
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
function _transformSchemaAST(schema, config) {
|
|
22
|
+
const { schema: _schema, ast } = transformSchemaAST(schema, config);
|
|
23
|
+
// See: https://github.com/Code-Hex/graphql-codegen-typescript-validation-schema/issues/394
|
|
24
|
+
const __schema = isGeneratedByIntrospection(_schema) ? buildSchema(printSchema(_schema)) : _schema;
|
|
25
|
+
// This affects the performance of code generation, so it is
|
|
26
|
+
// enabled only when this option is selected.
|
|
27
|
+
if (config.validationSchemaExportType === 'const') {
|
|
28
|
+
return {
|
|
29
|
+
schema: __schema,
|
|
30
|
+
ast: topologicalSortAST(__schema, ast),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
schema: __schema,
|
|
35
|
+
ast,
|
|
36
|
+
};
|
|
37
|
+
}
|
package/dist/regexp.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const isConvertableRegexp: (maybeRegexp: string) => boolean;
|
package/dist/regexp.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { FieldDefinitionNode, GraphQLSchema, InputValueDefinitionNode, InterfaceTypeDefinitionNode, ObjectTypeDefinitionNode } from 'graphql';
|
|
2
|
+
import type { ValidationSchemaPluginConfig } from './config.js';
|
|
3
|
+
import type { SchemaVisitor } from './types.js';
|
|
4
|
+
import { Visitor } from './visitor.js';
|
|
5
|
+
export declare abstract class BaseSchemaVisitor implements SchemaVisitor {
|
|
6
|
+
protected schema: GraphQLSchema;
|
|
7
|
+
protected config: ValidationSchemaPluginConfig;
|
|
8
|
+
protected importTypes: string[];
|
|
9
|
+
protected enumDeclarations: string[];
|
|
10
|
+
constructor(schema: GraphQLSchema, config: ValidationSchemaPluginConfig);
|
|
11
|
+
abstract importValidationSchema(): string;
|
|
12
|
+
buildImports(): string[];
|
|
13
|
+
abstract initialEmit(): string;
|
|
14
|
+
createVisitor(scalarDirection: 'input' | 'output' | 'both'): Visitor;
|
|
15
|
+
protected abstract buildInputFields(fields: readonly (FieldDefinitionNode | InputValueDefinitionNode)[], visitor: Visitor, name: string): string;
|
|
16
|
+
protected buildTypeDefinitionArguments(node: ObjectTypeDefinitionNode | InterfaceTypeDefinitionNode, visitor: Visitor): string | undefined;
|
|
17
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Visitor } from './visitor.js';
|
|
2
|
+
export class BaseSchemaVisitor {
|
|
3
|
+
schema;
|
|
4
|
+
config;
|
|
5
|
+
importTypes = [];
|
|
6
|
+
enumDeclarations = [];
|
|
7
|
+
constructor(schema, config) {
|
|
8
|
+
this.schema = schema;
|
|
9
|
+
this.config = config;
|
|
10
|
+
}
|
|
11
|
+
buildImports() {
|
|
12
|
+
if (this.config.importFrom && this.importTypes.length > 0) {
|
|
13
|
+
const namedImportPrefix = this.config.useTypeImports ? 'type ' : '';
|
|
14
|
+
const importCore = this.config.schemaNamespacedImportName
|
|
15
|
+
? `* as ${this.config.schemaNamespacedImportName}`
|
|
16
|
+
: `${namedImportPrefix}{ ${this.importTypes.join(', ')} }`;
|
|
17
|
+
return [
|
|
18
|
+
this.importValidationSchema(),
|
|
19
|
+
`import ${importCore} from '${this.config.importFrom}'`,
|
|
20
|
+
];
|
|
21
|
+
}
|
|
22
|
+
return [this.importValidationSchema()];
|
|
23
|
+
}
|
|
24
|
+
createVisitor(scalarDirection) {
|
|
25
|
+
return new Visitor(scalarDirection, this.schema, this.config);
|
|
26
|
+
}
|
|
27
|
+
buildTypeDefinitionArguments(node, visitor) {
|
|
28
|
+
return visitor.buildArgumentsSchemaBlock(node, (typeName, field) => {
|
|
29
|
+
this.importTypes.push(typeName);
|
|
30
|
+
return this.buildInputFields(field.arguments ?? [], visitor, typeName);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A transform function that converts a plain parsed object into a custom type.
|
|
3
|
+
* Used with Zod's `.transform()` to produce class instances from generated schemas.
|
|
4
|
+
*/
|
|
5
|
+
export type TransformFn = (raw: any) => unknown;
|
|
6
|
+
/**
|
|
7
|
+
* Maps GraphQL type names to their transform functions.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import type { TransformConfig } from '@heath/codegen-plugin/transform_config'
|
|
12
|
+
* import { transformDateRange } from './models/DateRange'
|
|
13
|
+
*
|
|
14
|
+
* const config: TransformConfig = {
|
|
15
|
+
* DateRange: transformDateRange,
|
|
16
|
+
* }
|
|
17
|
+
* export default config
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export type TransformConfig = Record<string, TransformFn>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
export interface ResolvedTransform {
|
|
3
|
+
/** The function name to import in generated code */
|
|
4
|
+
symbolName: string;
|
|
5
|
+
/** The module path to import from (relative to output file) */
|
|
6
|
+
importPath: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Loads and resolves transform configuration using pure static analysis.
|
|
10
|
+
*
|
|
11
|
+
* Parses the TypeScript config file's AST to extract:
|
|
12
|
+
* 1. Import declarations → symbol name to module path mapping
|
|
13
|
+
* 2. Default export object → GraphQL type name to symbol name mapping
|
|
14
|
+
* 3. Rebased import paths → adjusted for the output file location
|
|
15
|
+
*/
|
|
16
|
+
export declare function loadTransforms(configPath: string, outputPath: string): Record<string, ResolvedTransform>;
|
|
17
|
+
/**
|
|
18
|
+
* Extracts a map of imported symbol names to their module paths.
|
|
19
|
+
* Skips `import type` declarations since we need value imports.
|
|
20
|
+
*/
|
|
21
|
+
export declare function extractImportMap(sourceFile: ts.SourceFile): Map<string, string>;
|
|
22
|
+
/**
|
|
23
|
+
* Extracts the default export object literal, mapping property names to
|
|
24
|
+
* their identifier values (the imported symbol names).
|
|
25
|
+
*
|
|
26
|
+
* Supports these patterns:
|
|
27
|
+
* export default { TypeName: transformFn }
|
|
28
|
+
* const config = { TypeName: transformFn }; export default config;
|
|
29
|
+
*/
|
|
30
|
+
export declare function extractDefaultExportMap(sourceFile: ts.SourceFile): Map<string, string>;
|
|
31
|
+
/**
|
|
32
|
+
* Adjusts a relative import path so it's correct relative to the output file
|
|
33
|
+
* rather than the config file. Non-relative paths (aliases, packages) pass through unchanged.
|
|
34
|
+
*/
|
|
35
|
+
export declare function rebasePath(importPath: string, configPath: string, outputPath: string): string;
|