@newmo/graphql-codegen-fake-server-client 0.19.1 → 0.21.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/README.md
CHANGED
|
@@ -17,21 +17,21 @@ GraphQL Code Generator configuration:
|
|
|
17
17
|
import type { CodegenConfig } from "@graphql-codegen/cli";
|
|
18
18
|
|
|
19
19
|
const config: CodegenConfig = {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
},
|
|
20
|
+
overwrite: true,
|
|
21
|
+
schema: "./api/graphql/api.graphqls",
|
|
22
|
+
documents: "./api/graphql/query.graphql",
|
|
23
|
+
generates: {
|
|
24
|
+
"./generated/": {
|
|
25
|
+
preset: "client",
|
|
26
|
+
},
|
|
27
|
+
"./generated/fake-client.ts": {
|
|
28
|
+
plugins: ["@newmo/graphql-codegen-fake-server-client"],
|
|
29
|
+
config: {
|
|
30
|
+
// Required: path to the generated client's graphql file
|
|
31
|
+
typesFile: "./graphql",
|
|
32
|
+
},
|
|
34
33
|
},
|
|
34
|
+
},
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
export default config;
|
|
@@ -39,34 +39,39 @@ export default config;
|
|
|
39
39
|
|
|
40
40
|
You can use `./generated/fake-client.ts` to register the fake to the fake server.
|
|
41
41
|
|
|
42
|
+
### Basic Usage
|
|
43
|
+
|
|
42
44
|
```ts
|
|
43
45
|
import { it, expect } from "vitest";
|
|
44
46
|
import { createFakeClient } from "./generated/fake-client";
|
|
45
47
|
|
|
46
48
|
const fakeClient = createFakeClient({
|
|
47
|
-
fakeServerEndpoint: "http://localhost:4000"
|
|
48
|
-
})
|
|
49
|
+
fakeServerEndpoint: "http://localhost:4000",
|
|
50
|
+
});
|
|
49
51
|
it("register fake response for query", async () => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
});
|
|
60
|
-
expect(resRegister).toMatchInlineSnapshot(`"{"ok":true}"`);
|
|
61
|
-
// request to server
|
|
62
|
-
const client = new GraphQLClient(`${fakeServerUrl}/graphql`, {
|
|
63
|
-
headers: {
|
|
64
|
-
"sequence-id": sequenceId,
|
|
52
|
+
const sequenceId = crypto.randomUUID();
|
|
53
|
+
// register fake response for GetBooks query
|
|
54
|
+
const resRegister = await fakeClient.registerGetBooksQueryResponse(
|
|
55
|
+
sequenceId,
|
|
56
|
+
{
|
|
57
|
+
books: [
|
|
58
|
+
{
|
|
59
|
+
id: "new id",
|
|
60
|
+
title: "new title",
|
|
65
61
|
},
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
62
|
+
],
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
expect(resRegister).toMatchInlineSnapshot(`"{"ok":true}"`);
|
|
66
|
+
// request to server
|
|
67
|
+
const client = new GraphQLClient(`${fakeServerUrl}/graphql`, {
|
|
68
|
+
headers: {
|
|
69
|
+
"sequence-id": sequenceId,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
// Got fake response
|
|
73
|
+
const response = await client.request(GetBooksDocument);
|
|
74
|
+
expect(response).toMatchInlineSnapshot(`
|
|
70
75
|
{
|
|
71
76
|
"books": [
|
|
72
77
|
{
|
|
@@ -76,13 +81,68 @@ it("register fake response for query", async () => {
|
|
|
76
81
|
],
|
|
77
82
|
}
|
|
78
83
|
`);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
84
|
+
// Get actual request and response for testing
|
|
85
|
+
const calledResults = await fakeClient.calledGetBooksDocumentQuery(
|
|
86
|
+
sequenceId
|
|
87
|
+
);
|
|
88
|
+
console.log(calledResults[0].request);
|
|
89
|
+
console.log(calledResults[0].response);
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Conditional Fake Responses
|
|
94
|
+
|
|
95
|
+
You can register conditional fake responses that return different results based on variables. **The variables are now fully type-safe** based on your GraphQL operations:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import { it, expect } from "vitest";
|
|
99
|
+
import { createFakeClient } from "./generated/fake-client";
|
|
100
|
+
|
|
101
|
+
const fakeClient = createFakeClient({
|
|
102
|
+
fakeServerEndpoint: "http://localhost:4000",
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("register conditional fake responses", async () => {
|
|
106
|
+
const sequenceId = crypto.randomUUID();
|
|
107
|
+
|
|
108
|
+
// Type-safe variables condition!
|
|
109
|
+
// TypeScript will enforce the correct variables shape for this operation
|
|
110
|
+
await fakeClient.registerListDestinationCandidatesQueryResponse(
|
|
111
|
+
sequenceId,
|
|
112
|
+
{
|
|
113
|
+
destinationCandidates: [{ id: "3", name: "Tokyo Station" }],
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
requestCondition: {
|
|
117
|
+
type: "variables",
|
|
118
|
+
value: { text: "tokyo" }, // ✅ Type-safe! Must match ListDestinationCandidatesQueryVariables
|
|
119
|
+
},
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
// Another type-safe variables condition example
|
|
124
|
+
await fakeClient.registerCreateUrlRideHistoryMutationResponse(
|
|
125
|
+
sequenceId,
|
|
126
|
+
{
|
|
127
|
+
createURLRideHistory: { id: "4", name: "Shibuya" },
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
requestCondition: {
|
|
131
|
+
type: "variables",
|
|
132
|
+
value: { desinationName: "Shibuya" }, // ✅ Type-safe for this mutation!
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
);
|
|
83
136
|
});
|
|
84
137
|
```
|
|
85
138
|
|
|
139
|
+
#### Type Safety Benefits
|
|
140
|
+
|
|
141
|
+
- **Variables validation**: TypeScript will ensure variables in conditions match the exact shape expected by your GraphQL operation
|
|
142
|
+
- **Autocomplete**: Your IDE will provide autocomplete for available variable fields
|
|
143
|
+
- **Compile-time errors**: Typos or wrong variable types will be caught at compile time
|
|
144
|
+
- **Refactoring safety**: If you change your GraphQL schema, TypeScript will help you update the affected conditions
|
|
145
|
+
|
|
86
146
|
## Options
|
|
87
147
|
|
|
88
148
|
- `typesFile` (required): Path to the generated client's graphql file.
|
|
@@ -6,7 +6,12 @@ const plugin = {
|
|
|
6
6
|
plugin(_schema, documents, rawConfig, _info) {
|
|
7
7
|
const config = (0, config_1.normalizeConfig)(rawConfig);
|
|
8
8
|
const _fakeEndpoint = config.fakeServerEndpoint;
|
|
9
|
-
const registerOperationResponseType = "{ ok: true } | { ok: false; errors: string[] }";
|
|
9
|
+
const registerOperationResponseType = "{ ok: true } | { ok: false; errors: string[] }"; // Conditional fake types with generic Variables
|
|
10
|
+
const conditionRuleTypes = `
|
|
11
|
+
export type FakeClientAlwaysConditionRule = { type: "always" };
|
|
12
|
+
export type FakeClientVariablesConditionRule<TVariables = Record<string, any>> = { type: "variables"; value: TVariables };
|
|
13
|
+
export type FakeClientConditionRule<TVariables = Record<string, any>> = FakeClientAlwaysConditionRule | FakeClientVariablesConditionRule<TVariables>;
|
|
14
|
+
export type FakeClientRegisterSequenceOptions<TVariables = Record<string, any>> = { requestCondition?: FakeClientConditionRule<TVariables> };`;
|
|
10
15
|
const indentEachLine = (indent, text) => {
|
|
11
16
|
return text
|
|
12
17
|
.split("\n")
|
|
@@ -51,7 +56,9 @@ ${exportsFunctions
|
|
|
51
56
|
}`;
|
|
52
57
|
};
|
|
53
58
|
const generateRegisterOperationMethod = (name, fakeEndpointVariableName) => {
|
|
54
|
-
|
|
59
|
+
const variablesType = `${(0, convertName_1.convertName)(name, config)}QueryVariables`;
|
|
60
|
+
return `async register${name}QueryResponse(sequenceId:string, queryResponse: ${name}Query, sequenceOptions?: FakeClientRegisterSequenceOptions<${variablesType}>): Promise<${registerOperationResponseType}> {
|
|
61
|
+
const requestCondition = sequenceOptions?.requestCondition ?? { type: "always" };
|
|
55
62
|
return await fetch(${fakeEndpointVariableName}, {
|
|
56
63
|
method: 'POST',
|
|
57
64
|
headers: {
|
|
@@ -61,7 +68,8 @@ ${exportsFunctions
|
|
|
61
68
|
body: JSON.stringify({
|
|
62
69
|
type: "operation",
|
|
63
70
|
operationName: "${name}",
|
|
64
|
-
data: queryResponse
|
|
71
|
+
data: queryResponse,
|
|
72
|
+
requestCondition: requestCondition
|
|
65
73
|
}),
|
|
66
74
|
}).then((res) => res.json()) as ${registerOperationResponseType};
|
|
67
75
|
}`;
|
|
@@ -84,7 +92,9 @@ ${exportsFunctions
|
|
|
84
92
|
}`;
|
|
85
93
|
};
|
|
86
94
|
const generateRegisterMutationMethod = (name, fakeEndpointVariableName) => {
|
|
87
|
-
|
|
95
|
+
const variablesType = `${(0, convertName_1.convertName)(name, config)}MutationVariables`;
|
|
96
|
+
return `async register${name}MutationResponse(sequenceId:string, mutationResponse: ${name}Mutation, sequenceOptions?: FakeClientRegisterSequenceOptions<${variablesType}>): Promise<${registerOperationResponseType}> {
|
|
97
|
+
const requestCondition = sequenceOptions?.requestCondition ?? { type: "always" };
|
|
88
98
|
return await fetch(${fakeEndpointVariableName}, {
|
|
89
99
|
method: 'POST',
|
|
90
100
|
headers: {
|
|
@@ -94,7 +104,8 @@ ${exportsFunctions
|
|
|
94
104
|
body: JSON.stringify({
|
|
95
105
|
type: "operation",
|
|
96
106
|
operationName: "${name}",
|
|
97
|
-
data: mutationResponse
|
|
107
|
+
data: mutationResponse,
|
|
108
|
+
requestCondition: requestCondition
|
|
98
109
|
}),
|
|
99
110
|
}).then((res) => res.json()) as ${registerOperationResponseType};
|
|
100
111
|
}`;
|
|
@@ -215,7 +226,7 @@ ${exportsFunctions
|
|
|
215
226
|
}`;
|
|
216
227
|
};
|
|
217
228
|
const importQueryIdentifierName = (documentName) => {
|
|
218
|
-
return `import type { ${(0, convertName_1.convertName)(documentName, config)}Query } from '${config.typesFile}';`;
|
|
229
|
+
return `import type { ${(0, convertName_1.convertName)(documentName, config)}Query, ${(0, convertName_1.convertName)(documentName, config)}QueryVariables } from '${config.typesFile}';`;
|
|
219
230
|
};
|
|
220
231
|
const importMutationIdentifierName = (documentName) => {
|
|
221
232
|
return `import type { ${(0, convertName_1.convertName)(documentName, config)}Mutation, ${(0, convertName_1.convertName)(documentName, config)}MutationVariables } from '${config.typesFile}';`;
|
|
@@ -240,6 +251,7 @@ ${documents
|
|
|
240
251
|
});
|
|
241
252
|
})
|
|
242
253
|
.join("\n")}
|
|
254
|
+
${conditionRuleTypes}
|
|
243
255
|
${generateFakeClient(documents.flatMap((document) => {
|
|
244
256
|
const flatMap = document.document?.definitions?.flatMap((definition) => {
|
|
245
257
|
if (definition.kind === "OperationDefinition" &&
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@newmo/graphql-codegen-fake-server-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "GraphQL Codegen plugin for generating a fake server client",
|
|
6
6
|
"keywords": [
|
|
@@ -61,5 +61,5 @@
|
|
|
61
61
|
"access": "public",
|
|
62
62
|
"registry": "https://registry.npmjs.org/"
|
|
63
63
|
},
|
|
64
|
-
"gitHead": "
|
|
64
|
+
"gitHead": "251e1b4311a0f070af89abc7e49f6aa1e9b075c0"
|
|
65
65
|
}
|
|
@@ -6,7 +6,12 @@ const plugin: CodegenPlugin<RawPluginConfig> = {
|
|
|
6
6
|
plugin(_schema, documents, rawConfig, _info) {
|
|
7
7
|
const config = normalizeConfig(rawConfig);
|
|
8
8
|
const _fakeEndpoint = config.fakeServerEndpoint;
|
|
9
|
-
const registerOperationResponseType = "{ ok: true } | { ok: false; errors: string[] }";
|
|
9
|
+
const registerOperationResponseType = "{ ok: true } | { ok: false; errors: string[] }"; // Conditional fake types with generic Variables
|
|
10
|
+
const conditionRuleTypes = `
|
|
11
|
+
export type FakeClientAlwaysConditionRule = { type: "always" };
|
|
12
|
+
export type FakeClientVariablesConditionRule<TVariables = Record<string, any>> = { type: "variables"; value: TVariables };
|
|
13
|
+
export type FakeClientConditionRule<TVariables = Record<string, any>> = FakeClientAlwaysConditionRule | FakeClientVariablesConditionRule<TVariables>;
|
|
14
|
+
export type FakeClientRegisterSequenceOptions<TVariables = Record<string, any>> = { requestCondition?: FakeClientConditionRule<TVariables> };`;
|
|
10
15
|
type GenerateFakeFunction =
|
|
11
16
|
| {
|
|
12
17
|
type: "query";
|
|
@@ -81,7 +86,9 @@ ${exportsFunctions
|
|
|
81
86
|
name: string,
|
|
82
87
|
fakeEndpointVariableName: string,
|
|
83
88
|
) => {
|
|
84
|
-
|
|
89
|
+
const variablesType = `${convertName(name, config)}QueryVariables`;
|
|
90
|
+
return `async register${name}QueryResponse(sequenceId:string, queryResponse: ${name}Query, sequenceOptions?: FakeClientRegisterSequenceOptions<${variablesType}>): Promise<${registerOperationResponseType}> {
|
|
91
|
+
const requestCondition = sequenceOptions?.requestCondition ?? { type: "always" };
|
|
85
92
|
return await fetch(${fakeEndpointVariableName}, {
|
|
86
93
|
method: 'POST',
|
|
87
94
|
headers: {
|
|
@@ -91,7 +98,8 @@ ${exportsFunctions
|
|
|
91
98
|
body: JSON.stringify({
|
|
92
99
|
type: "operation",
|
|
93
100
|
operationName: "${name}",
|
|
94
|
-
data: queryResponse
|
|
101
|
+
data: queryResponse,
|
|
102
|
+
requestCondition: requestCondition
|
|
95
103
|
}),
|
|
96
104
|
}).then((res) => res.json()) as ${registerOperationResponseType};
|
|
97
105
|
}`;
|
|
@@ -117,7 +125,9 @@ ${exportsFunctions
|
|
|
117
125
|
}`;
|
|
118
126
|
};
|
|
119
127
|
const generateRegisterMutationMethod = (name: string, fakeEndpointVariableName: string) => {
|
|
120
|
-
|
|
128
|
+
const variablesType = `${convertName(name, config)}MutationVariables`;
|
|
129
|
+
return `async register${name}MutationResponse(sequenceId:string, mutationResponse: ${name}Mutation, sequenceOptions?: FakeClientRegisterSequenceOptions<${variablesType}>): Promise<${registerOperationResponseType}> {
|
|
130
|
+
const requestCondition = sequenceOptions?.requestCondition ?? { type: "always" };
|
|
121
131
|
return await fetch(${fakeEndpointVariableName}, {
|
|
122
132
|
method: 'POST',
|
|
123
133
|
headers: {
|
|
@@ -127,7 +137,8 @@ ${exportsFunctions
|
|
|
127
137
|
body: JSON.stringify({
|
|
128
138
|
type: "operation",
|
|
129
139
|
operationName: "${name}",
|
|
130
|
-
data: mutationResponse
|
|
140
|
+
data: mutationResponse,
|
|
141
|
+
requestCondition: requestCondition
|
|
131
142
|
}),
|
|
132
143
|
}).then((res) => res.json()) as ${registerOperationResponseType};
|
|
133
144
|
}`;
|
|
@@ -253,9 +264,10 @@ ${exportsFunctions
|
|
|
253
264
|
};
|
|
254
265
|
|
|
255
266
|
const importQueryIdentifierName = (documentName: string) => {
|
|
256
|
-
return `import type { ${convertName(
|
|
257
|
-
|
|
258
|
-
|
|
267
|
+
return `import type { ${convertName(
|
|
268
|
+
documentName,
|
|
269
|
+
config,
|
|
270
|
+
)}Query, ${convertName(documentName, config)}QueryVariables } from '${config.typesFile}';`;
|
|
259
271
|
};
|
|
260
272
|
const importMutationIdentifierName = (documentName: string) => {
|
|
261
273
|
return `import type { ${convertName(documentName, config)}Mutation, ${convertName(
|
|
@@ -287,6 +299,7 @@ ${documents
|
|
|
287
299
|
});
|
|
288
300
|
})
|
|
289
301
|
.join("\n")}
|
|
302
|
+
${conditionRuleTypes}
|
|
290
303
|
${generateFakeClient(
|
|
291
304
|
documents.flatMap((document) => {
|
|
292
305
|
const flatMap =
|