|
1 | 1 | import { readFile, writeFile } from "node:fs/promises";
|
2 | 2 | import { parseArgs } from "node:util";
|
3 | 3 |
|
4 |
| -import { |
5 |
| - buildSchema, |
6 |
| - GraphQLFieldConfig, |
7 |
| - GraphQLFieldConfigMap, |
8 |
| - GraphQLInterfaceType, |
9 |
| - GraphQLList, |
10 |
| - GraphQLNamedType, |
11 |
| - GraphQLNonNull, |
12 |
| - GraphQLObjectType, |
13 |
| - GraphQLOutputType, |
14 |
| - GraphQLSchema, |
15 |
| - GraphQLSemanticNonNull, |
16 |
| - GraphQLType, |
17 |
| - GraphQLUnionType, |
18 |
| - Kind, |
19 |
| - printSchema, |
20 |
| - validateSchema, |
21 |
| -} from "graphql"; |
22 |
| -import type { Maybe } from "graphql/jsutils/Maybe"; |
| 4 | +import { buildSchema, printSchema,validateSchema } from "graphql"; |
| 5 | + |
| 6 | +import { semanticToNullable,semanticToStrict } from "./index.js"; |
23 | 7 |
|
24 | 8 | export async function main(toStrict = false) {
|
25 | 9 | const {
|
@@ -51,203 +35,10 @@ export async function main(toStrict = false) {
|
51 | 35 | throw new Error("Invalid schema");
|
52 | 36 | }
|
53 | 37 |
|
54 |
| - const derivedSchema = convertSchema(schema, toStrict); |
| 38 | + const derivedSchema = toStrict |
| 39 | + ? semanticToStrict(schema) |
| 40 | + : semanticToNullable(schema); |
55 | 41 |
|
56 | 42 | const newSdl = printSchema(derivedSchema);
|
57 | 43 | await writeFile(output, newSdl + "\n");
|
58 | 44 | }
|
59 |
| - |
60 |
| -function convertSchema(schema: GraphQLSchema, toStrict: boolean) { |
61 |
| - const config = schema.toConfig(); |
62 |
| - const convertType = makeConvertType(toStrict); |
63 |
| - const derivedSchema = new GraphQLSchema({ |
64 |
| - ...config, |
65 |
| - query: convertType(config.query), |
66 |
| - mutation: convertType(config.mutation), |
67 |
| - subscription: convertType(config.subscription), |
68 |
| - types: config.types |
69 |
| - .filter((t) => !t.name.startsWith("__")) |
70 |
| - .map((t) => convertType(t)), |
71 |
| - directives: config.directives.filter((d) => d.name !== "semanticNonNull"), |
72 |
| - }); |
73 |
| - return derivedSchema; |
74 |
| -} |
75 |
| - |
76 |
| -export function semanticToNullable(schema: GraphQLSchema) { |
77 |
| - return convertSchema(schema, false); |
78 |
| -} |
79 |
| - |
80 |
| -export function semanticToStrict(schema: GraphQLSchema) { |
81 |
| - return convertSchema(schema, true); |
82 |
| -} |
83 |
| - |
84 |
| -function makeConvertType(toStrict: boolean) { |
85 |
| - const cache = new Map<string, GraphQLNamedType>(); |
86 |
| - |
87 |
| - function convertFields(fields: GraphQLFieldConfigMap<unknown, unknown>) { |
88 |
| - return () => { |
89 |
| - return Object.fromEntries( |
90 |
| - Object.entries(fields).map(([fieldName, inSpec]) => { |
91 |
| - const spec = applySemanticNonNullDirectiveToFieldConfig(inSpec); |
92 |
| - return [ |
93 |
| - fieldName, |
94 |
| - { |
95 |
| - ...spec, |
96 |
| - type: convertType(spec.type), |
97 |
| - }, |
98 |
| - ]; |
99 |
| - }), |
100 |
| - ) as GraphQLFieldConfigMap<unknown, unknown>; |
101 |
| - }; |
102 |
| - } |
103 |
| - |
104 |
| - function convertTypes( |
105 |
| - types: readonly GraphQLInterfaceType[] | null | undefined, |
106 |
| - ): undefined | (() => readonly GraphQLInterfaceType[]); |
107 |
| - function convertTypes( |
108 |
| - types: readonly GraphQLObjectType[], |
109 |
| - ): () => readonly GraphQLObjectType[]; |
110 |
| - function convertTypes( |
111 |
| - types: readonly GraphQLNamedType[], |
112 |
| - ): undefined | (() => readonly GraphQLNamedType[]); |
113 |
| - function convertTypes( |
114 |
| - types: readonly GraphQLNamedType[] | undefined, |
115 |
| - ): undefined | (() => readonly GraphQLNamedType[]); |
116 |
| - function convertTypes( |
117 |
| - types: readonly GraphQLNamedType[] | null | undefined, |
118 |
| - ): undefined | (() => readonly GraphQLNamedType[]) { |
119 |
| - if (!types) { |
120 |
| - return undefined; |
121 |
| - } |
122 |
| - return () => types.map((t) => convertType(t)); |
123 |
| - } |
124 |
| - |
125 |
| - function convertType(type: null | undefined): null | undefined; |
126 |
| - function convertType(type: GraphQLObjectType): GraphQLObjectType; |
127 |
| - function convertType( |
128 |
| - type: Maybe<GraphQLObjectType>, |
129 |
| - ): Maybe<GraphQLObjectType>; |
130 |
| - function convertType(type: GraphQLNamedType): GraphQLNamedType; |
131 |
| - function convertType(type: GraphQLType): GraphQLType; |
132 |
| - function convertType(type: GraphQLType | null | undefined) { |
133 |
| - if (!type) { |
134 |
| - return type; |
135 |
| - } |
136 |
| - if (type instanceof GraphQLSemanticNonNull) { |
137 |
| - const unwrapped = convertType(type.ofType); |
138 |
| - // Here's where we do our thing! |
139 |
| - if (toStrict) { |
140 |
| - return new GraphQLNonNull(unwrapped); |
141 |
| - } else { |
142 |
| - return unwrapped; |
143 |
| - } |
144 |
| - } else if (type instanceof GraphQLNonNull) { |
145 |
| - return new GraphQLNonNull(convertType(type.ofType)); |
146 |
| - } else if (type instanceof GraphQLList) { |
147 |
| - return new GraphQLList(convertType(type.ofType)); |
148 |
| - } |
149 |
| - if (type.name.startsWith("__")) { |
150 |
| - return null; |
151 |
| - } |
152 |
| - if (cache.has(type.name)) { |
153 |
| - return cache.get(type.name); |
154 |
| - } |
155 |
| - const newType = (() => { |
156 |
| - if (type instanceof GraphQLObjectType) { |
157 |
| - const config = type.toConfig(); |
158 |
| - return new GraphQLObjectType({ |
159 |
| - ...config, |
160 |
| - fields: convertFields(config.fields), |
161 |
| - interfaces: convertTypes(config.interfaces), |
162 |
| - }); |
163 |
| - } else if (type instanceof GraphQLInterfaceType) { |
164 |
| - const config = type.toConfig(); |
165 |
| - return new GraphQLInterfaceType({ |
166 |
| - ...config, |
167 |
| - fields: convertFields(config.fields), |
168 |
| - interfaces: convertTypes(config.interfaces), |
169 |
| - }); |
170 |
| - } else if (type instanceof GraphQLUnionType) { |
171 |
| - const config = type.toConfig(); |
172 |
| - return new GraphQLUnionType({ |
173 |
| - ...config, |
174 |
| - types: convertTypes(config.types), |
175 |
| - }); |
176 |
| - } else { |
177 |
| - return type; |
178 |
| - } |
179 |
| - })(); |
180 |
| - cache.set(type.name, newType); |
181 |
| - return newType; |
182 |
| - } |
183 |
| - |
184 |
| - return convertType; |
185 |
| -} |
186 |
| - |
187 |
| -/** |
188 |
| - * Takes a GraphQL field config and checks to see if the `@semanticNonNull` |
189 |
| - * directive was applied; if so, converts to a field config using explicit |
190 |
| - * GraphQLSemanticNonNull wrapper types instead. |
191 |
| - * |
192 |
| - * @see {@url https://www.apollographql.com/docs/kotlin/advanced/nullability/#semanticnonnull} |
193 |
| - */ |
194 |
| -export function applySemanticNonNullDirectiveToFieldConfig( |
195 |
| - spec: GraphQLFieldConfig<unknown, unknown, unknown>, |
196 |
| -): GraphQLFieldConfig<unknown, unknown, unknown> { |
197 |
| - const directive = spec.astNode?.directives?.find( |
198 |
| - (d) => d.name.value === "semanticNonNull", |
199 |
| - ); |
200 |
| - if (!directive) { |
201 |
| - return spec; |
202 |
| - } |
203 |
| - const levelsArg = directive.arguments?.find((a) => a.name.value === "levels"); |
204 |
| - const levels = |
205 |
| - levelsArg?.value?.kind === Kind.LIST |
206 |
| - ? levelsArg.value.values |
207 |
| - .filter((v) => v.kind === Kind.INT) |
208 |
| - .map((v) => Number(v.value)) |
209 |
| - : [0]; |
210 |
| - function recurse(type: GraphQLOutputType, level: number): GraphQLOutputType { |
211 |
| - if (type instanceof GraphQLSemanticNonNull) { |
212 |
| - // Strip semantic-non-null types; this should never happen but if someone |
213 |
| - // uses both semantic-non-null and the `@semanticNonNull` directive, we |
214 |
| - // want the directive to win (I guess?) |
215 |
| - return recurse(type.ofType, level); |
216 |
| - } else if (type instanceof GraphQLNonNull) { |
217 |
| - const inner = recurse(type.ofType, level); |
218 |
| - if (levels.includes(level)) { |
219 |
| - // Semantic non-null from `inner` replaces our GrpahQLNonNull wrapper |
220 |
| - return inner; |
221 |
| - } else { |
222 |
| - // Keep non-null wrapper; no semantic-non-null was added to `inner` |
223 |
| - return new GraphQLNonNull(inner); |
224 |
| - } |
225 |
| - } else if (type instanceof GraphQLList) { |
226 |
| - const inner = new GraphQLList(recurse(type.ofType, level + 1)); |
227 |
| - if (levels.includes(level)) { |
228 |
| - return new GraphQLSemanticNonNull(inner); |
229 |
| - } else { |
230 |
| - return inner; |
231 |
| - } |
232 |
| - } else { |
233 |
| - if (levels.includes(level)) { |
234 |
| - return new GraphQLSemanticNonNull(type); |
235 |
| - } else { |
236 |
| - return type; |
237 |
| - } |
238 |
| - } |
239 |
| - } |
240 |
| - |
241 |
| - return { |
242 |
| - ...spec, |
243 |
| - type: recurse(spec.type, 0), |
244 |
| - astNode: spec.astNode |
245 |
| - ? { |
246 |
| - ...spec.astNode, |
247 |
| - directives: spec.astNode.directives?.filter( |
248 |
| - (d) => d.name.value !== "semanticNonNull", |
249 |
| - ), |
250 |
| - } |
251 |
| - : undefined, |
252 |
| - }; |
253 |
| -} |
0 commit comments