@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
- 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
- },
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
- const sequenceId = crypto.randomUUID();
51
- // register fake response for GetBooks query
52
- const resRegister = await fakeClient.registerGetBooksQueryResponse(sequenceId, {
53
- books: [
54
- {
55
- id: "new id",
56
- title: "new title",
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
- // Got fake response
68
- const response = await client.request(GetBooksDocument);
69
- expect(response).toMatchInlineSnapshot(`
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
- // Get actual request and response for testing
80
- const calledResults = await fakeClient.calledGetBooksDocumentQuery(sequenceId);
81
- console.log(calledResults[0].request);
82
- console.log(calledResults[0].response);
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
- return `async register${name}QueryResponse(sequenceId:string, queryResponse: ${name}Query): Promise<${registerOperationResponseType}> {
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
- return `async register${name}MutationResponse(sequenceId:string, mutationResponse: ${name}Mutation): Promise<${registerOperationResponseType}> {
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.19.1",
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": "0bd15a2a4307c7be9c9ae673bdc305bef561d30c"
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
- return `async register${name}QueryResponse(sequenceId:string, queryResponse: ${name}Query): Promise<${registerOperationResponseType}> {
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
- return `async register${name}MutationResponse(sequenceId:string, mutationResponse: ${name}Mutation): Promise<${registerOperationResponseType}> {
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(documentName, config)}Query } from '${
257
- config.typesFile
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 =