@newmo/graphql-fake-server 0.1.1

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.
@@ -0,0 +1,297 @@
1
+ import { transformComment } from '@graphql-codegen/visitor-plugin-common';
2
+ import {
3
+ ASTNode,
4
+ ConstValueNode,
5
+ FieldDefinitionNode,
6
+ GraphQLSchema,
7
+ InputObjectTypeDefinitionNode,
8
+ InputValueDefinitionNode,
9
+ InterfaceTypeDefinitionNode,
10
+ Kind,
11
+ ListTypeNode,
12
+ NamedTypeNode,
13
+ NonNullTypeNode,
14
+ ObjectTypeDefinitionNode,
15
+ TypeNode,
16
+ UnionTypeDefinitionNode,
17
+ } from 'graphql';
18
+ import { Config } from './config.js';
19
+ // The fork of https://github.com/dotansimha/graphql-code-generator/blob/e1dc75f3c598bf7f83138ca533619716fc73f823/packages/plugins/typescript/resolvers/src/visitor.ts#L85-L91
20
+
21
+ // The fork of https://github.com/dotansimha/graphql-code-generator/blob/ba84a3a2758d94dac27fcfbb1bafdf3ed7c32929/packages/plugins/other/visitor-plugin-common/src/base-visitor.ts#L422
22
+ function convertName(node: ASTNode | string, config: Config): string {
23
+ let convertedName = '';
24
+ convertedName += config.typesPrefix;
25
+ convertedName += config.convert(node);
26
+ convertedName += config.typesSuffix;
27
+ return convertedName;
28
+ }
29
+
30
+ const parseTypeNodeStructure = (node: TypeNode): string => {
31
+ if (node.kind === Kind.NON_NULL_TYPE) {
32
+ return parseTypeNodeStructure(node.type);
33
+ } else if (node.kind === Kind.LIST_TYPE) {
34
+ return `array`
35
+ } else {
36
+ // string, number, boolean, null
37
+ if (node.name.value === "String") {
38
+ return "string"
39
+ }
40
+ if (node.name.value === "Int") {
41
+ return "number"
42
+ }
43
+ if (node.name.value === "Float") {
44
+ return "number"
45
+ }
46
+ if (node.name.value === "Boolean") {
47
+ return "boolean"
48
+ }
49
+ if (node.name.value === "ID") {
50
+ return "string"
51
+ }
52
+ return `object`
53
+ }
54
+ }
55
+ type ValuePrimitive = string | number | boolean | null;
56
+ type ValueArray = ValuePrimitive[];
57
+ type ValueObject = Record<string, ValuePrimitive | ValueArray>;
58
+ export type ExampleDirectiveValue = {
59
+ value: ValuePrimitive | ValueArray | ValueObject
60
+ };
61
+ export type ExampleDirectionExpresion = {
62
+ expression: string
63
+ }
64
+ export type ExampleDirective = ExampleDirectiveValue | ExampleDirectionExpresion;
65
+
66
+ function valueOf(value: ConstValueNode): ValuePrimitive | ValueArray | ValueObject {
67
+ // object
68
+ if (value.kind === Kind.OBJECT) {
69
+ return value.fields.reduce((acc, field) => {
70
+ // @ts-expect-error TODO: nesting type
71
+ acc[field.name.value] = valueOf(field.value)
72
+ return acc
73
+ }, {} as ValueObject)
74
+ }
75
+ // list
76
+ if (value.kind === Kind.LIST) {
77
+ return value.values.map(v => {
78
+ return valueOf(v)
79
+ }) as ValueArray
80
+ }
81
+ // null
82
+ if (value.kind === Kind.NULL) {
83
+ return null
84
+ }
85
+ // string
86
+ if (value.kind === Kind.STRING) {
87
+ return value.value
88
+ }
89
+ // enum
90
+ if (value.kind === Kind.ENUM) {
91
+ return value.value
92
+ }
93
+ // int
94
+ if (value.kind === Kind.INT) {
95
+ return Number.parseInt(value.value, 10)
96
+ }
97
+ // float
98
+ if (value.kind === Kind.FLOAT) {
99
+ return Number.parseFloat(value.value)
100
+ }
101
+ // boolean
102
+ if (value.kind === Kind.BOOLEAN) {
103
+ return value.value
104
+ }
105
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
106
+ throw new Error(`Unknown kind of value ${value satisfies never}`)
107
+ }
108
+
109
+ const typeToFunction = (type: string, config: Config): string => {
110
+ switch (type) {
111
+ case "String":
112
+ return `"${config.defaultValues.String}"`
113
+ case "Int":
114
+ return `${config.defaultValues.Int}`
115
+ case "Float":
116
+ return `${config.defaultValues.Float}`
117
+ case "Boolean":
118
+ return `${config.defaultValues.Boolean ? "true" : "false"}`
119
+ case "ID":
120
+ return `"${config.defaultValues.ID}"`
121
+ default:
122
+ // reference to the object
123
+ return `${type}`;
124
+ }
125
+ }
126
+ const typeToFunctionWithArray = (type: string, config: Config): string => {
127
+ return `Array.from({ length: ${config.defaultValues.listLength} }).map(() => ${typeToFunction(type, config)})`
128
+ }
129
+ // NamedType/ListType handling
130
+ const nodeToExpression = ({ currentNode, isArray = false, config }: {
131
+ currentNode: NonNullTypeNode | NamedTypeNode | ListTypeNode,
132
+ config: Config
133
+ isArray?: boolean,
134
+ }): ExampleDirectionExpresion => {
135
+ if (currentNode.kind === "NonNullType") {
136
+ return nodeToExpression({
137
+ currentNode:
138
+ currentNode.type, isArray, config
139
+ });
140
+ } else if (currentNode.kind === "NamedType") {
141
+ if (isArray) {
142
+ return {
143
+ expression: typeToFunctionWithArray(currentNode.name.value, config)
144
+ }
145
+ } else {
146
+ return {
147
+ expression: typeToFunction(currentNode.name.value, config)
148
+ }
149
+ }
150
+ } else if (currentNode.kind === "ListType") {
151
+ return nodeToExpression({ currentNode: currentNode.type, isArray: true, config })
152
+ }
153
+ throw new Error("Unknown node kind")
154
+ }
155
+
156
+ function parseFieldOrInputValueDefinition(
157
+ node: FieldDefinitionNode | InputValueDefinitionNode,
158
+ convertedTypeName: string,
159
+ config: Config,
160
+ ): { example?: ExampleDirective | undefined } {
161
+ const exampleDirective = node.directives?.find(d => d.name.value === "example");
162
+ // fake
163
+ // if @example directive is not found, return random value for the scalar type
164
+ if (!exampleDirective) {
165
+ return {
166
+ example: nodeToExpression({ currentNode: node.type, config })
167
+ };
168
+ }
169
+ if (!exampleDirective.arguments) {
170
+ throw new Error("@example directive must have arguments")
171
+ }
172
+ /**
173
+ * @example(value: "value")
174
+ * -> { value: "value" }
175
+ */
176
+ const value = exampleDirective.arguments.find(a => a.name.value === "value");
177
+ if (value) {
178
+ // if node type is not equal to the value type, throw an error
179
+ const rawValue = valueOf(value.value);
180
+ const nodeType = parseTypeNodeStructure(node.type);
181
+ const fieldName = node.name.value;
182
+ // array, object, string, number, boolean, null
183
+ const rawValueType = Object.prototype.toString.call(rawValue).slice(8, -1).toLowerCase();
184
+ if (nodeType !== rawValueType) {
185
+ throw new Error(`${convertedTypeName}.${fieldName}: @example directive value type must be ${nodeType}. @example(value: ${nodeType})`)
186
+ }
187
+ return { example: { value: rawValue } }
188
+ }
189
+ throw new Error(`@example directive must have value argument. @example(value: "value")`)
190
+ }
191
+
192
+ function parseObjectTypeOrInputObjectTypeDefinition(
193
+ node: ObjectTypeDefinitionNode | InputObjectTypeDefinitionNode,
194
+ config: Config,
195
+ ): ObjectTypeInfo {
196
+ const originalTypeName = node.name.value;
197
+ const convertedTypeName = convertName(originalTypeName, config);
198
+ return {
199
+ type: 'object',
200
+ name: originalTypeName,
201
+ fields: [
202
+ ...(node.fields ?? []).map((field) => ({
203
+ name: field.name.value,
204
+ ...parseFieldOrInputValueDefinition(field, convertedTypeName, config),
205
+ })),
206
+ ],
207
+ };
208
+ }
209
+
210
+ type FieldInfo = {
211
+ name: string;
212
+ example?: ExampleDirective | undefined;
213
+ }
214
+ export type ObjectTypeInfo = {
215
+ type: 'object';
216
+ name: string;
217
+ fields: FieldInfo[];
218
+ };
219
+ export type AbstractTypeInfo = {
220
+ type: 'abstract';
221
+ name: string;
222
+ possibleTypes: string[];
223
+ comment?: string | undefined;
224
+ example?: ExampleDirective | undefined
225
+ };
226
+ export type TypeInfo = ObjectTypeInfo | AbstractTypeInfo;
227
+
228
+ export function getTypeInfos(config: Config, schema: GraphQLSchema): TypeInfo[] {
229
+ const types = Object.values(schema.getTypeMap());
230
+
231
+ const userDefinedTypeDefinitions = types
232
+ .map((type) => type.astNode)
233
+ .filter(
234
+ (
235
+ node,
236
+ ): node is
237
+ | ObjectTypeDefinitionNode
238
+ | InputObjectTypeDefinitionNode
239
+ | InterfaceTypeDefinitionNode
240
+ | UnionTypeDefinitionNode => {
241
+ if (!node) return false;
242
+ return (
243
+ node.kind === Kind.OBJECT_TYPE_DEFINITION ||
244
+ node.kind === Kind.INPUT_OBJECT_TYPE_DEFINITION ||
245
+ node.kind === Kind.INTERFACE_TYPE_DEFINITION ||
246
+ node.kind === Kind.UNION_TYPE_DEFINITION
247
+ );
248
+ },
249
+ );
250
+ const objectTypeDefinitions = userDefinedTypeDefinitions.filter((node): node is ObjectTypeDefinitionNode => {
251
+ if (!node) return false;
252
+ return node.kind === Kind.OBJECT_TYPE_DEFINITION;
253
+ });
254
+
255
+ return types
256
+ .map((type) => type.astNode)
257
+ .filter(
258
+ (
259
+ node,
260
+ ): node is
261
+ | ObjectTypeDefinitionNode
262
+ | InputObjectTypeDefinitionNode
263
+ | InterfaceTypeDefinitionNode
264
+ | UnionTypeDefinitionNode => {
265
+ if (!node) return false;
266
+ return (
267
+ node.kind === Kind.OBJECT_TYPE_DEFINITION ||
268
+ node.kind === Kind.INPUT_OBJECT_TYPE_DEFINITION ||
269
+ node.kind === Kind.INTERFACE_TYPE_DEFINITION ||
270
+ node.kind === Kind.UNION_TYPE_DEFINITION
271
+ );
272
+ },
273
+ )
274
+ .map((node) => {
275
+ if (node?.kind === Kind.OBJECT_TYPE_DEFINITION || node?.kind === Kind.INPUT_OBJECT_TYPE_DEFINITION) {
276
+ return parseObjectTypeOrInputObjectTypeDefinition(node, config);
277
+ } else if (node?.kind === Kind.INTERFACE_TYPE_DEFINITION) {
278
+ return {
279
+ type: 'abstract',
280
+ name: convertName(node.name.value, config),
281
+ possibleTypes: objectTypeDefinitions
282
+ .filter((objectTypeDefinitionNode) =>
283
+ (objectTypeDefinitionNode.interfaces ?? []).some((i) => i.name.value === node.name.value),
284
+ )
285
+ .map((objectTypeDefinitionNode) => convertName(objectTypeDefinitionNode.name.value, config)),
286
+ comment: node.description ? transformComment(node.description) : undefined,
287
+ };
288
+ } else {
289
+ return {
290
+ type: 'abstract',
291
+ name: convertName(node.name.value, config),
292
+ possibleTypes: (node.types ?? []).map((type) => convertName(type.name.value, config)),
293
+ comment: node.description ? transformComment(node.description) : undefined,
294
+ };
295
+ }
296
+ });
297
+ }