@newmo/graphql-codegen-fake-server-client 0.19.0 → 0.20.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",
34
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
+ },
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,90 @@ 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 call count or 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
+ // Return different response on first call
109
+ await fakeClient.registerGetBooksQueryResponse(
110
+ sequenceId,
111
+ {
112
+ books: [{ id: "1", title: "First Call" }],
113
+ },
114
+ {
115
+ requestCondition: { type: "count", value: 1 },
116
+ }
117
+ );
118
+
119
+ // Return different response on second call
120
+ await fakeClient.registerGetBooksQueryResponse(
121
+ sequenceId,
122
+ {
123
+ books: [{ id: "2", title: "Second Call" }],
124
+ },
125
+ {
126
+ requestCondition: { type: "count", value: 2 },
127
+ }
128
+ );
129
+
130
+ // Type-safe variables condition!
131
+ // TypeScript will enforce the correct variables shape for this operation
132
+ await fakeClient.registerListDestinationCandidatesQueryResponse(
133
+ sequenceId,
134
+ {
135
+ destinationCandidates: [{ id: "3", name: "Tokyo Station" }],
136
+ },
137
+ {
138
+ requestCondition: {
139
+ type: "variables",
140
+ value: { text: "tokyo" }, // ✅ Type-safe! Must match ListDestinationCandidatesQueryVariables
141
+ },
142
+ }
143
+ );
144
+
145
+ // Another type-safe variables condition example
146
+ await fakeClient.registerCreateUrlRideHistoryMutationResponse(
147
+ sequenceId,
148
+ {
149
+ createURLRideHistory: { id: "4", name: "Shibuya" },
150
+ },
151
+ {
152
+ requestCondition: {
153
+ type: "variables",
154
+ value: { desinationName: "Shibuya" }, // ✅ Type-safe for this mutation!
155
+ },
156
+ }
157
+ );
83
158
  });
84
159
  ```
85
160
 
161
+ #### Type Safety Benefits
162
+
163
+ - **Variables validation**: TypeScript will ensure variables in conditions match the exact shape expected by your GraphQL operation
164
+ - **Autocomplete**: Your IDE will provide autocomplete for available variable fields
165
+ - **Compile-time errors**: Typos or wrong variable types will be caught at compile time
166
+ - **Refactoring safety**: If you change your GraphQL schema, TypeScript will help you update the affected conditions
167
+
86
168
  ## Options
87
169
 
88
170
  - `typesFile` (required): Path to the generated client's graphql file.
@@ -3,10 +3,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const config_1 = require("./config");
4
4
  const convertName_1 = require("./convertName");
5
5
  const plugin = {
6
- plugin(schema, documents, rawConfig, _info) {
6
+ plugin(_schema, documents, rawConfig, _info) {
7
7
  const config = (0, config_1.normalizeConfig)(rawConfig);
8
- const fakeEndpoint = config.fakeServerEndpoint;
9
- const registerOperationResponseType = "{ ok: true } | { ok: false; errors: string[] }";
8
+ const _fakeEndpoint = config.fakeServerEndpoint;
9
+ const registerOperationResponseType = "{ ok: true } | { ok: false; errors: string[] }"; // Conditional fake types with generic Variables
10
+ const conditionRuleTypes = `
11
+ export type FakeClientCountConditionRule = { type: "count"; value: number };
12
+ export type FakeClientVariablesConditionRule<TVariables = Record<string, any>> = { type: "variables"; value: TVariables };
13
+ export type FakeClientConditionRule<TVariables = Record<string, any>> = FakeClientCountConditionRule | 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,8 @@ ${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}> {
55
61
  return await fetch(${fakeEndpointVariableName}, {
56
62
  method: 'POST',
57
63
  headers: {
@@ -61,7 +67,8 @@ ${exportsFunctions
61
67
  body: JSON.stringify({
62
68
  type: "operation",
63
69
  operationName: "${name}",
64
- data: queryResponse
70
+ data: queryResponse,
71
+ ...(sequenceOptions?.requestCondition && { requestCondition: sequenceOptions.requestCondition })
65
72
  }),
66
73
  }).then((res) => res.json()) as ${registerOperationResponseType};
67
74
  }`;
@@ -84,7 +91,8 @@ ${exportsFunctions
84
91
  }`;
85
92
  };
86
93
  const generateRegisterMutationMethod = (name, fakeEndpointVariableName) => {
87
- return `async register${name}MutationResponse(sequenceId:string, mutationResponse: ${name}Mutation): Promise<${registerOperationResponseType}> {
94
+ const variablesType = `${(0, convertName_1.convertName)(name, config)}MutationVariables`;
95
+ return `async register${name}MutationResponse(sequenceId:string, mutationResponse: ${name}Mutation, sequenceOptions?: FakeClientRegisterSequenceOptions<${variablesType}>): Promise<${registerOperationResponseType}> {
88
96
  return await fetch(${fakeEndpointVariableName}, {
89
97
  method: 'POST',
90
98
  headers: {
@@ -94,7 +102,8 @@ ${exportsFunctions
94
102
  body: JSON.stringify({
95
103
  type: "operation",
96
104
  operationName: "${name}",
97
- data: mutationResponse
105
+ data: mutationResponse,
106
+ ...(sequenceOptions?.requestCondition && { requestCondition: sequenceOptions.requestCondition })
98
107
  }),
99
108
  }).then((res) => res.json()) as ${registerOperationResponseType};
100
109
  }`;
@@ -215,7 +224,7 @@ ${exportsFunctions
215
224
  }`;
216
225
  };
217
226
  const importQueryIdentifierName = (documentName) => {
218
- return `import type { ${(0, convertName_1.convertName)(documentName, config)}Query } from '${config.typesFile}';`;
227
+ return `import type { ${(0, convertName_1.convertName)(documentName, config)}Query, ${(0, convertName_1.convertName)(documentName, config)}QueryVariables } from '${config.typesFile}';`;
219
228
  };
220
229
  const importMutationIdentifierName = (documentName) => {
221
230
  return `import type { ${(0, convertName_1.convertName)(documentName, config)}Mutation, ${(0, convertName_1.convertName)(documentName, config)}MutationVariables } from '${config.typesFile}';`;
@@ -240,6 +249,7 @@ ${documents
240
249
  });
241
250
  })
242
251
  .join("\n")}
252
+ ${conditionRuleTypes}
243
253
  ${generateFakeClient(documents.flatMap((document) => {
244
254
  const flatMap = document.document?.definitions?.flatMap((definition) => {
245
255
  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.0",
3
+ "version": "0.20.0",
4
4
  "private": false,
5
5
  "description": "GraphQL Codegen plugin for generating a fake server client",
6
6
  "keywords": [
@@ -37,19 +37,19 @@
37
37
  "codegen": "graphql-codegen --config graphql-codegen.ts"
38
38
  },
39
39
  "dependencies": {
40
- "@graphql-codegen/plugin-helpers": "^5.1.0",
41
- "@graphql-codegen/visitor-plugin-common": "^5.6.1"
40
+ "@graphql-codegen/plugin-helpers": "^5.1.1",
41
+ "@graphql-codegen/visitor-plugin-common": "^5.8.0"
42
42
  },
43
43
  "devDependencies": {
44
- "@graphql-codegen/cli": "^5.0.4",
45
- "@graphql-codegen/typescript": "^4.1.3",
44
+ "@graphql-codegen/cli": "^5.0.7",
45
+ "@graphql-codegen/typescript": "^4.1.6",
46
46
  "@graphql-typed-document-node/core": "^3.2.0",
47
47
  "@tsconfig/node18": "^18.2.0",
48
48
  "@tsconfig/strictest": "^2.0.1",
49
49
  "@types/eslint": "^9.6.1",
50
- "@types/node": "^22.13.1",
51
- "graphql": "^16.10.0",
52
- "typescript": "^5.7.3"
50
+ "@types/node": "^24.0.3",
51
+ "graphql": "^16.11.0",
52
+ "typescript": "^5.8.3"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "graphql": "^16.8.1"
@@ -61,5 +61,5 @@
61
61
  "access": "public",
62
62
  "registry": "https://registry.npmjs.org/"
63
63
  },
64
- "gitHead": "45163d171085e62acfac5f5116d99cb4e7b1e200"
64
+ "gitHead": "e7fb0401bb3d6d989adfc98cc15ec4c936bc0cde"
65
65
  }
@@ -1,12 +1,17 @@
1
1
  import type { CodegenPlugin } from "@graphql-codegen/plugin-helpers";
2
- import { type RawPluginConfig, normalizeConfig } from "./config";
2
+ import { normalizeConfig, type RawPluginConfig } from "./config";
3
3
  import { convertName } from "./convertName";
4
4
 
5
5
  const plugin: CodegenPlugin<RawPluginConfig> = {
6
- plugin(schema, documents, rawConfig, _info) {
6
+ plugin(_schema, documents, rawConfig, _info) {
7
7
  const config = normalizeConfig(rawConfig);
8
- const fakeEndpoint = config.fakeServerEndpoint;
9
- const registerOperationResponseType = "{ ok: true } | { ok: false; errors: string[] }";
8
+ const _fakeEndpoint = config.fakeServerEndpoint;
9
+ const registerOperationResponseType = "{ ok: true } | { ok: false; errors: string[] }"; // Conditional fake types with generic Variables
10
+ const conditionRuleTypes = `
11
+ export type FakeClientCountConditionRule = { type: "count"; value: number };
12
+ export type FakeClientVariablesConditionRule<TVariables = Record<string, any>> = { type: "variables"; value: TVariables };
13
+ export type FakeClientConditionRule<TVariables = Record<string, any>> = FakeClientCountConditionRule | 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,8 @@ ${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}> {
85
91
  return await fetch(${fakeEndpointVariableName}, {
86
92
  method: 'POST',
87
93
  headers: {
@@ -91,7 +97,8 @@ ${exportsFunctions
91
97
  body: JSON.stringify({
92
98
  type: "operation",
93
99
  operationName: "${name}",
94
- data: queryResponse
100
+ data: queryResponse,
101
+ ...(sequenceOptions?.requestCondition && { requestCondition: sequenceOptions.requestCondition })
95
102
  }),
96
103
  }).then((res) => res.json()) as ${registerOperationResponseType};
97
104
  }`;
@@ -117,7 +124,8 @@ ${exportsFunctions
117
124
  }`;
118
125
  };
119
126
  const generateRegisterMutationMethod = (name: string, fakeEndpointVariableName: string) => {
120
- return `async register${name}MutationResponse(sequenceId:string, mutationResponse: ${name}Mutation): Promise<${registerOperationResponseType}> {
127
+ const variablesType = `${convertName(name, config)}MutationVariables`;
128
+ return `async register${name}MutationResponse(sequenceId:string, mutationResponse: ${name}Mutation, sequenceOptions?: FakeClientRegisterSequenceOptions<${variablesType}>): Promise<${registerOperationResponseType}> {
121
129
  return await fetch(${fakeEndpointVariableName}, {
122
130
  method: 'POST',
123
131
  headers: {
@@ -127,7 +135,8 @@ ${exportsFunctions
127
135
  body: JSON.stringify({
128
136
  type: "operation",
129
137
  operationName: "${name}",
130
- data: mutationResponse
138
+ data: mutationResponse,
139
+ ...(sequenceOptions?.requestCondition && { requestCondition: sequenceOptions.requestCondition })
131
140
  }),
132
141
  }).then((res) => res.json()) as ${registerOperationResponseType};
133
142
  }`;
@@ -253,9 +262,10 @@ ${exportsFunctions
253
262
  };
254
263
 
255
264
  const importQueryIdentifierName = (documentName: string) => {
256
- return `import type { ${convertName(documentName, config)}Query } from '${
257
- config.typesFile
258
- }';`;
265
+ return `import type { ${convertName(
266
+ documentName,
267
+ config,
268
+ )}Query, ${convertName(documentName, config)}QueryVariables } from '${config.typesFile}';`;
259
269
  };
260
270
  const importMutationIdentifierName = (documentName: string) => {
261
271
  return `import type { ${convertName(documentName, config)}Mutation, ${convertName(
@@ -287,6 +297,7 @@ ${documents
287
297
  });
288
298
  })
289
299
  .join("\n")}
300
+ ${conditionRuleTypes}
290
301
  ${generateFakeClient(
291
302
  documents.flatMap((document) => {
292
303
  const flatMap =