@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.
- package/LICENSE +22 -0
- package/README.md +39 -0
- package/dist/esm/cli.d.ts +3 -0
- package/dist/esm/cli.d.ts.map +1 -0
- package/dist/esm/cli.js +90 -0
- package/dist/esm/cli.js.map +1 -0
- package/dist/esm/code-generator.d.ts +4 -0
- package/dist/esm/code-generator.d.ts.map +1 -0
- package/dist/esm/code-generator.js +75 -0
- package/dist/esm/code-generator.js.map +1 -0
- package/dist/esm/config.d.ts +46 -0
- package/dist/esm/config.d.ts.map +1 -0
- package/dist/esm/config.js +57 -0
- package/dist/esm/config.js.map +1 -0
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +12 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/schema-scanner.d.ts +32 -0
- package/dist/esm/schema-scanner.d.ts.map +1 -0
- package/dist/esm/schema-scanner.js +221 -0
- package/dist/esm/schema-scanner.js.map +1 -0
- package/package.json +98 -0
- package/src/cli.ts +101 -0
- package/src/code-generator.ts +82 -0
- package/src/config.ts +96 -0
- package/src/index.ts +15 -0
- package/src/schema-scanner.ts +297 -0
|
@@ -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
|
+
}
|