@apiquest/plugin-graphql 1.0.2 → 1.0.5

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
@@ -1,125 +1,129 @@
1
- # @apiquest/plugin-graphql
2
-
3
- GraphQL protocol plugin for ApiQuest. Provides support for GraphQL queries, mutations, and subscriptions with variable support.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- npm install -g @apiquest/plugin-graphql
9
- ```
10
-
11
- ## Features
12
-
13
- - GraphQL queries and mutations
14
- - Variable support
15
- - Operation name specification (for multi-operation documents)
16
- - Custom HTTP headers
17
- - Fragment support
18
- - Authentication integration (via `@apiquest/plugin-auth`)
19
-
20
- ## Usage
21
-
22
- Set the collection protocol to `graphql`:
23
-
24
- ```json
25
- {
26
- "$schema": "https://apiquest.net/schemas/collection-v1.0.json",
27
- "protocol": "graphql",
28
- "items": [
29
- {
30
- "type": "request",
31
- "id": "get-user",
32
- "name": "Get User by ID",
33
- "data": {
34
- "url": "https://api.example.com/graphql",
35
- "query": "query GetUser($id: ID!) {\n user(id: $id) {\n id\n name\n email\n }\n}",
36
- "variables": {
37
- "id": "{{userId}}"
38
- }
39
- }
40
- }
41
- ]
42
- }
43
- ```
44
-
45
- ### Mutation Example
46
-
47
- ```json
48
- {
49
- "type": "request",
50
- "id": "create-user",
51
- "name": "Create User",
52
- "data": {
53
- "url": "https://api.example.com/graphql",
54
- "mutation": "mutation CreateUser($input: UserInput!) {\n createUser(input: $input) {\n id\n name\n email\n }\n}",
55
- "variables": {
56
- "input": {
57
- "name": "John Doe",
58
- "email": "john@example.com"
59
- }
60
- }
61
- }
62
- }
63
- ```
64
-
65
- ### With Custom Headers
66
-
67
- ```json
68
- {
69
- "data": {
70
- "url": "https://api.example.com/graphql",
71
- "query": "{ users { id name } }",
72
- "headers": {
73
- "x-api-version": "2024-01-01",
74
- "x-request-id": "{{$guid}}"
75
- }
76
- }
77
- }
78
- ```
79
-
80
- ### Multi-Operation Documents
81
-
82
- ```json
83
- {
84
- "data": {
85
- "url": "https://api.example.com/graphql",
86
- "query": "query GetUser { user { id } }\nquery GetPosts { posts { id } }",
87
- "operationName": "GetUser"
88
- }
89
- }
90
- ```
91
-
92
- ## Response Handling
93
-
94
- Access GraphQL response data in post-request scripts:
95
-
96
- ```javascript
97
- quest.test('Query successful', () => {
98
- expect(quest.response.status).to.equal(200);
99
- });
100
-
101
- quest.test('No GraphQL errors', () => {
102
- const body = quest.response.json();
103
- expect(body.errors).to.be.undefined;
104
- });
105
-
106
- quest.test('User data returned', () => {
107
- const body = quest.response.json();
108
- expect(body.data.user).to.be.an('object');
109
- expect(body.data.user.id).to.be.a('string');
110
- });
111
- ```
112
-
113
- ## Compatibility
114
-
115
- - **Authentication:** Works with `@apiquest/plugin-auth` for Bearer, Basic, OAuth2, API Key
116
- - **Node.js:** Requires Node.js 20+
117
-
118
- ## Documentation
119
-
120
- - [Fracture Documentation](https://apiquest.net/docs/fracture)
121
- - [Schema Reference](https://apiquest.net/schemas/collection-v1.0.json)
122
-
123
- ## License
124
-
125
- Dual-licensed under AGPL-3.0-or-later and commercial license. See LICENSE.txt for details.
1
+ # @apiquest/plugin-graphql
2
+
3
+ GraphQL protocol plugin for ApiQuest. Provides support for GraphQL queries, mutations, and subscriptions with variable support.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ # Using npm
9
+ npm install -g @apiquest/plugin-graphql
10
+
11
+ # Or using fracture CLI
12
+ fracture plugin install graphql
13
+ ```
14
+
15
+ ## Features
16
+
17
+ - GraphQL queries and mutations
18
+ - Variable support
19
+ - Operation name specification (for multi-operation documents)
20
+ - Custom HTTP headers
21
+ - Fragment support
22
+ - Authentication integration (via `@apiquest/plugin-auth`)
23
+
24
+ ## Usage
25
+
26
+ Set the collection protocol to `graphql`:
27
+
28
+ ```json
29
+ {
30
+ "$schema": "https://apiquest.net/schemas/collection-v1.0.json",
31
+ "protocol": "graphql",
32
+ "items": [
33
+ {
34
+ "type": "request",
35
+ "id": "get-user",
36
+ "name": "Get User by ID",
37
+ "data": {
38
+ "url": "https://api.example.com/graphql",
39
+ "query": "query GetUser($id: ID!) {\n user(id: $id) {\n id\n name\n email\n }\n}",
40
+ "variables": {
41
+ "id": "{{userId}}"
42
+ }
43
+ }
44
+ }
45
+ ]
46
+ }
47
+ ```
48
+
49
+ ### Mutation Example
50
+
51
+ ```json
52
+ {
53
+ "type": "request",
54
+ "id": "create-user",
55
+ "name": "Create User",
56
+ "data": {
57
+ "url": "https://api.example.com/graphql",
58
+ "mutation": "mutation CreateUser($input: UserInput!) {\n createUser(input: $input) {\n id\n name\n email\n }\n}",
59
+ "variables": {
60
+ "input": {
61
+ "name": "John Doe",
62
+ "email": "john@example.com"
63
+ }
64
+ }
65
+ }
66
+ }
67
+ ```
68
+
69
+ ### With Custom Headers
70
+
71
+ ```json
72
+ {
73
+ "data": {
74
+ "url": "https://api.example.com/graphql",
75
+ "query": "{ users { id name } }",
76
+ "headers": {
77
+ "x-api-version": "2024-01-01",
78
+ "x-request-id": "{{$guid}}"
79
+ }
80
+ }
81
+ }
82
+ ```
83
+
84
+ ### Multi-Operation Documents
85
+
86
+ ```json
87
+ {
88
+ "data": {
89
+ "url": "https://api.example.com/graphql",
90
+ "query": "query GetUser { user { id } }\nquery GetPosts { posts { id } }",
91
+ "operationName": "GetUser"
92
+ }
93
+ }
94
+ ```
95
+
96
+ ## Response Handling
97
+
98
+ Access GraphQL response data in post-request scripts:
99
+
100
+ ```javascript
101
+ quest.test('Query successful', () => {
102
+ expect(quest.response.status).to.equal(200);
103
+ });
104
+
105
+ quest.test('No GraphQL errors', () => {
106
+ const body = quest.response.json();
107
+ expect(body.errors).to.be.undefined;
108
+ });
109
+
110
+ quest.test('User data returned', () => {
111
+ const body = quest.response.json();
112
+ expect(body.data.user).to.be.an('object');
113
+ expect(body.data.user.id).to.be.a('string');
114
+ });
115
+ ```
116
+
117
+ ## Compatibility
118
+
119
+ - **Authentication:** Works with `@apiquest/plugin-auth` for Bearer, Basic, OAuth2, API Key
120
+ - **Node.js:** Requires Node.js 20+
121
+
122
+ ## Documentation
123
+
124
+ - [Fracture Documentation](https://apiquest.net/docs/fracture)
125
+ - [Schema Reference](https://apiquest.net/schemas/collection-v1.0.json)
126
+
127
+ ## License
128
+
129
+ Dual-licensed under AGPL-3.0-or-later and commercial license. See LICENSE.txt for details.
package/package.json CHANGED
@@ -1,56 +1,60 @@
1
- {
2
- "name": "@apiquest/plugin-graphql",
3
- "version": "1.0.2",
4
- "description": "GraphQL protocol plugin for ApiQuest",
5
- "type": "module",
6
- "main": "./dist/index.js",
7
- "types": "./dist/index.d.ts",
8
- "scripts": {
9
- "build": "rollup -c && tsc --emitDeclarationOnly",
10
- "dev": "rollup -c --watch",
11
- "test": "vitest"
12
- },
13
- "keywords": [
14
- "apiquest",
15
- "graphql",
16
- "plugin"
17
- ],
18
- "author": "",
19
- "license": "AGPL-3.0-or-later",
20
- "dependencies": {
21
- "@apiquest/types": "workspace:*",
22
- "got": "^14.6.6"
23
- },
24
- "devDependencies": {
25
- "@apiquest/types": "workspace:*",
26
- "@rollup/plugin-commonjs": "^25.0.0",
27
- "@rollup/plugin-node-resolve": "^15.0.0",
28
- "@rollup/plugin-typescript": "^11.0.0",
29
- "@types/node": "^20.10.6",
30
- "rollup": "^4.0.0",
31
- "typescript": "^5.3.3",
32
- "vitest": "^4.0.18"
33
- },
34
- "apiquest": {
35
- "type": "protocol",
36
- "runtime": [
37
- "fracture"
38
- ],
39
- "capabilities": {
40
- "provides": {
41
- "protocols": [
42
- "graphql"
43
- ]
44
- },
45
- "supports": {
46
- "authTypes": [
47
- "bearer",
48
- "basic",
49
- "oauth2",
50
- "apikey"
51
- ],
52
- "strictAuthList": false
53
- }
54
- }
55
- }
56
- }
1
+ {
2
+ "name": "@apiquest/plugin-graphql",
3
+ "version": "1.0.5",
4
+ "description": "GraphQL protocol plugin for ApiQuest",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/hh-apiquest/fracture.git",
11
+ "directory": "packages/plugin-graphql"
12
+ },
13
+ "scripts": {
14
+ "build": "rollup -c && tsc --emitDeclarationOnly",
15
+ "dev": "rollup -c --watch",
16
+ "test": "vitest"
17
+ },
18
+ "keywords": [
19
+ "apiquest",
20
+ "graphql",
21
+ "plugin"
22
+ ],
23
+ "author": "",
24
+ "license": "AGPL-3.0-or-later",
25
+ "dependencies": {
26
+ "@apiquest/types": "workspace:*",
27
+ "got": "^14.6.6"
28
+ },
29
+ "devDependencies": {
30
+ "@apiquest/types": "workspace:*",
31
+ "@rollup/plugin-commonjs": "^29.0.0",
32
+ "@rollup/plugin-node-resolve": "^16.0.3",
33
+ "@rollup/plugin-typescript": "^12.3.0",
34
+ "@types/node": "^25.2.3",
35
+ "rollup": "^4.57.1",
36
+ "typescript": "^5.3.3",
37
+ "vitest": "^4.0.18"
38
+ },
39
+ "apiquest": {
40
+ "type": "protocol",
41
+ "runtime": [
42
+ "fracture"
43
+ ],
44
+ "capabilities": {
45
+ "provides": {
46
+ "protocols": [
47
+ "graphql"
48
+ ]
49
+ },
50
+ "supports": {
51
+ "authTypes": [
52
+ "bearer",
53
+ "basic",
54
+ "oauth2",
55
+ "apikey"
56
+ ]
57
+ }
58
+ }
59
+ }
60
+ }
package/rollup.config.js CHANGED
@@ -1,31 +1,31 @@
1
- import typescript from '@rollup/plugin-typescript';
2
- import resolve from '@rollup/plugin-node-resolve';
3
- import commonjs from '@rollup/plugin-commonjs';
4
-
5
- export default {
6
- input: 'src/index.ts',
7
- output: {
8
- file: 'dist/index.js',
9
- format: 'esm',
10
- sourcemap: true,
11
- },
12
- external: [
13
- // Externalize peer dependencies
14
- '@apiquest/fracture',
15
- ],
16
- plugins: [
17
- // Resolve node modules
18
- resolve({
19
- preferBuiltins: true, // Prefer Node.js built-in modules
20
- exportConditions: ['node', 'import', 'default'],
21
- }),
22
- // Convert CommonJS to ESM (for any CJS dependencies)
23
- commonjs(),
24
- // Compile TypeScript
25
- typescript({
26
- tsconfig: './tsconfig.json',
27
- sourceMap: true,
28
- declaration: false, // We'll use tsc for declarations
29
- }),
30
- ],
31
- };
1
+ import typescript from '@rollup/plugin-typescript';
2
+ import resolve from '@rollup/plugin-node-resolve';
3
+ import commonjs from '@rollup/plugin-commonjs';
4
+
5
+ export default {
6
+ input: 'src/index.ts',
7
+ output: {
8
+ file: 'dist/index.js',
9
+ format: 'esm',
10
+ sourcemap: true,
11
+ },
12
+ external: [
13
+ // Externalize peer dependencies
14
+ '@apiquest/fracture',
15
+ ],
16
+ plugins: [
17
+ // Resolve node modules
18
+ resolve({
19
+ preferBuiltins: true, // Prefer Node.js built-in modules
20
+ exportConditions: ['node', 'import', 'default'],
21
+ }),
22
+ // Convert CommonJS to ESM (for any CJS dependencies)
23
+ commonjs(),
24
+ // Compile TypeScript
25
+ typescript({
26
+ tsconfig: './tsconfig.json',
27
+ sourceMap: true,
28
+ declaration: false, // We'll use tsc for declarations
29
+ }),
30
+ ],
31
+ };
package/tsconfig.json CHANGED
@@ -1,20 +1,20 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "ES2022",
5
- "lib": ["ES2022"],
6
- "moduleResolution": "bundler",
7
- "outDir": "./dist",
8
- "declaration": true,
9
- "declarationMap": true,
10
- "sourceMap": true,
11
- "strict": true,
12
- "esModuleInterop": true,
13
- "skipLibCheck": true,
14
- "forceConsistentCasingInFileNames": true,
15
- "resolveJsonModule": true,
16
- "allowSyntheticDefaultImports": true
17
- },
18
- "include": ["src/**/*"],
19
- "exclude": ["node_modules", "dist", "tests"]
20
- }
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ES2022",
5
+ "lib": ["ES2022"],
6
+ "moduleResolution": "bundler",
7
+ "outDir": "./dist",
8
+ "declaration": true,
9
+ "declarationMap": true,
10
+ "sourceMap": true,
11
+ "strict": true,
12
+ "esModuleInterop": true,
13
+ "skipLibCheck": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "resolveJsonModule": true,
16
+ "allowSyntheticDefaultImports": true
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "dist", "tests"]
20
+ }
@@ -1,5 +1,5 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "include": ["tests/**/*"],
4
- "exclude": []
5
- }
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "include": ["tests/**/*"],
4
+ "exclude": []
5
+ }
package/vitest.config.ts CHANGED
@@ -1,12 +1,12 @@
1
- import { defineConfig } from 'vitest/config';
2
-
3
- export default defineConfig({
4
- test: {
5
- globals: true,
6
- environment: 'node',
7
- coverage: {
8
- provider: 'v8',
9
- reporter: ['text', 'json', 'html'],
10
- },
11
- },
12
- });
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: 'node',
7
+ coverage: {
8
+ provider: 'v8',
9
+ reporter: ['text', 'json', 'html'],
10
+ },
11
+ },
12
+ });
package/src/index.ts DELETED
@@ -1,291 +0,0 @@
1
- import got, { OptionsOfTextResponseBody, Response, RequestError } from 'got';
2
- import type { IProtocolPlugin, Request, ExecutionContext, ProtocolResponse, ValidationResult, ValidationError, RuntimeOptions, ILogger } from '@apiquest/types';
3
-
4
- // Helper functions for string validation
5
- function isNullOrEmpty(value: string | null | undefined): boolean {
6
- return value === null || value === undefined || value === '';
7
- }
8
-
9
- function isNullOrWhitespace(value: string | null | undefined): boolean {
10
- return value === null || value === undefined || value.trim() === '';
11
- }
12
-
13
- export const graphqlPlugin: IProtocolPlugin = {
14
- name: 'GraphQL',
15
- version: '1.0.0',
16
- description: 'GraphQL query and mutation support',
17
- protocols: ['graphql'],
18
- supportedAuthTypes: ['bearer', 'basic', 'apikey', 'oauth2'],
19
- strictAuthList: false,
20
- dataSchema: {
21
- type: 'object',
22
- required: ['url'],
23
- properties: {
24
- url: {
25
- type: 'string',
26
- description: 'GraphQL endpoint URL'
27
- },
28
- query: {
29
- type: 'string',
30
- description: 'GraphQL query'
31
- },
32
- mutation: {
33
- type: 'string',
34
- description: 'GraphQL mutation'
35
- },
36
- variables: {
37
- type: 'object',
38
- description: 'GraphQL variables'
39
- },
40
- operationName: {
41
- type: 'string',
42
- description: 'Operation name (for multi-operation documents)'
43
- },
44
- headers: {
45
- type: 'object',
46
- description: 'Custom HTTP headers',
47
- additionalProperties: { type: 'string' }
48
- }
49
- }
50
- },
51
-
52
- // Options schema for runtime configuration
53
- optionsSchema: {
54
- timeout: {
55
- type: 'number',
56
- default: 30000,
57
- description: 'Request timeout in milliseconds'
58
- },
59
- validateCertificates: {
60
- type: 'boolean',
61
- default: true,
62
- description: 'Validate SSL/TLS certificates'
63
- }
64
- },
65
-
66
- validate(request: Request, options: RuntimeOptions): ValidationResult {
67
- const errors: ValidationError[] = [];
68
-
69
- // Check URL
70
- if (typeof request.data.url !== 'string' || isNullOrWhitespace(request.data.url)) {
71
- errors.push({
72
- message: 'GraphQL endpoint URL is required',
73
- location: '',
74
- source: 'protocol'
75
- });
76
- }
77
-
78
- // Check that either query or mutation is present
79
- const hasQuery = typeof request.data.query === 'string' && !isNullOrEmpty(request.data.query);
80
- const hasMutation = typeof request.data.mutation === 'string' && !isNullOrEmpty(request.data.mutation);
81
-
82
- if (!hasQuery && !hasMutation) {
83
- errors.push({
84
- message: 'Either query or mutation is required',
85
- location: '',
86
- source: 'protocol'
87
- });
88
- }
89
-
90
- if (hasQuery && hasMutation) {
91
- errors.push({
92
- message: 'Cannot have both query and mutation - use one or the other',
93
- location: '',
94
- source: 'protocol'
95
- });
96
- }
97
-
98
- if (errors.length > 0) {
99
- return {
100
- valid: false,
101
- errors
102
- };
103
- }
104
-
105
- return { valid: true };
106
- },
107
-
108
- async execute(
109
- request: Request,
110
- context: ExecutionContext,
111
- options: RuntimeOptions,
112
- emitEvent?: (eventName: string, eventData: unknown) => Promise<void>,
113
- logger?: ILogger
114
- ): Promise<ProtocolResponse> {
115
- const startTime = Date.now();
116
- const url = String(request.data.url ?? '');
117
-
118
- try {
119
- if (isNullOrWhitespace(url)) {
120
- logger?.error('GraphQL request missing URL');
121
- throw new Error('GraphQL endpoint URL is required');
122
- }
123
-
124
- // Request configuration
125
- const operation = request.data.query ?? request.data.mutation;
126
-
127
- const graphqlBody: {
128
- query: string;
129
- variables?: Record<string, unknown>;
130
- operationName?: string;
131
- } = {
132
- query: String(operation),
133
- };
134
-
135
- if (request.data.variables !== undefined && request.data.variables !== null) {
136
- graphqlBody.variables = request.data.variables as Record<string, unknown>;
137
- }
138
-
139
- if (request.data.operationName !== undefined && request.data.operationName !== null) {
140
- graphqlBody.operationName = String(request.data.operationName);
141
- }
142
-
143
- // Headers
144
- const headers: Record<string, string> = {
145
- 'Content-Type': 'application/json',
146
- };
147
-
148
- if (typeof request.data.headers === 'object' && request.data.headers !== null) {
149
- Object.entries(request.data.headers as Record<string, unknown>).forEach(([key, value]) => {
150
- headers[key] = String(value);
151
- });
152
- }
153
-
154
- const graphqlOptions: Record<string, unknown> = (options.plugins?.graphql as Record<string, unknown> | null | undefined) ?? {};
155
- const graphqlTimeout = typeof graphqlOptions.timeout === 'number' ? graphqlOptions.timeout : null;
156
- const timeout = options.timeout?.request ?? graphqlTimeout ?? 60000;
157
- const graphqlValidateCerts = typeof graphqlOptions.validateCertificates === 'boolean' ? graphqlOptions.validateCertificates : null;
158
- const validateCerts = options.ssl?.validateCertificates ?? graphqlValidateCerts ?? true;
159
-
160
- logger?.debug('GraphQL request options resolved', { timeout, validateCerts });
161
-
162
- // Cookie handling
163
- const cookieHeader = context.cookieJar.getCookieHeader(url);
164
- if (cookieHeader !== null) {
165
- headers['Cookie'] = cookieHeader;
166
- logger?.trace('Cookie header applied', { url });
167
- }
168
-
169
- const gotOptions: OptionsOfTextResponseBody = {
170
- method: 'POST',
171
- headers: { ...headers },
172
- json: graphqlBody,
173
- throwHttpErrors: false,
174
- timeout: { request: timeout },
175
- followRedirect: true,
176
- https: {
177
- rejectUnauthorized: validateCerts
178
- }
179
- };
180
-
181
- // Dispatch
182
- logger?.debug('GraphQL request dispatch', { url });
183
- const response: Response = await got(url, gotOptions);
184
- const duration = Date.now() - startTime;
185
-
186
- // Response normalization
187
- const normalizedHeaders: Record<string, string | string[]> = {};
188
- if (typeof response.headers === 'object' && response.headers !== null) {
189
- Object.entries(response.headers).forEach(([key, value]) => {
190
- if (Array.isArray(value)) {
191
- normalizedHeaders[key.toLowerCase()] = value.map(item => String(item));
192
- } else if (value !== undefined && value !== null) {
193
- normalizedHeaders[key.toLowerCase()] = String(value);
194
- }
195
- });
196
- }
197
-
198
- if (normalizedHeaders['set-cookie'] !== undefined) {
199
- context.cookieJar.store(normalizedHeaders['set-cookie'], url);
200
- logger?.trace('Cookies stored from response', { url });
201
- }
202
-
203
- let errorMsg: string | undefined = undefined;
204
- try {
205
- const responseData = JSON.parse(String(response.body)) as { errors?: Array<{ message: string }> };
206
- if (responseData?.errors !== undefined && responseData.errors !== null && responseData.errors.length > 0) {
207
- errorMsg = `GraphQL errors: ${responseData.errors.map((e: { message: string }) => e.message).join(', ')}`;
208
- }
209
- } catch (parseError) {
210
- logger?.trace('GraphQL response body not JSON');
211
- }
212
-
213
- logger?.debug('GraphQL response received', { status: response.statusCode, duration });
214
-
215
- return {
216
- status: response.statusCode,
217
- statusText: (response.statusMessage !== null && response.statusMessage !== undefined && response.statusMessage.length > 0) ? response.statusMessage : '',
218
- headers: normalizedHeaders,
219
- body: String(response.body),
220
- duration,
221
- error: errorMsg
222
- };
223
- } catch (err) {
224
- const duration = Date.now() - startTime;
225
- const error = err as RequestError;
226
-
227
- if (error instanceof RequestError) {
228
- if (error.response !== undefined) {
229
- const normalizedHeaders: Record<string, string | string[]> = {};
230
- if (typeof error.response.headers === 'object' && error.response.headers !== null) {
231
- Object.entries(error.response.headers).forEach(([key, value]) => {
232
- if (Array.isArray(value)) {
233
- normalizedHeaders[key.toLowerCase()] = value.map(item => String(item));
234
- } else if (value !== undefined && value !== null) {
235
- normalizedHeaders[key.toLowerCase()] = String(value);
236
- }
237
- });
238
- }
239
-
240
- if (normalizedHeaders['set-cookie'] !== undefined) {
241
- context.cookieJar.store(normalizedHeaders['set-cookie'], url);
242
- logger?.trace('Cookies stored from error response', { url });
243
- }
244
-
245
- let errorResponseMsg: string | undefined = undefined;
246
- try {
247
- const responseData = JSON.parse(String(error.response.body)) as { errors?: Array<{ message: string }> };
248
- if (responseData?.errors !== undefined && responseData.errors !== null && responseData.errors.length > 0) {
249
- errorResponseMsg = `GraphQL errors: ${responseData.errors.map((e: { message: string }) => e.message).join(', ')}`;
250
- }
251
- } catch (parseError) {
252
- logger?.trace('GraphQL error response body not JSON');
253
- }
254
-
255
- logger?.debug('GraphQL error response received', { status: error.response.statusCode, duration });
256
-
257
- return {
258
- status: error.response.statusCode,
259
- statusText: (error.response.statusMessage !== null && error.response.statusMessage !== undefined && error.response.statusMessage.length > 0) ? error.response.statusMessage : '',
260
- headers: normalizedHeaders,
261
- body: String(error.response.body),
262
- duration,
263
- error: errorResponseMsg
264
- };
265
- } else {
266
- logger?.warn('GraphQL network error', { message: error.message, duration });
267
- return {
268
- status: 0,
269
- statusText: 'Network Error',
270
- headers: {},
271
- body: '',
272
- duration,
273
- error: !isNullOrEmpty(error.message) ? error.message : 'Network request failed'
274
- };
275
- }
276
- }
277
-
278
- logger?.error('GraphQL unexpected error', { error: err instanceof Error ? err.message : String(err), duration });
279
- return {
280
- status: 0,
281
- statusText: 'Error',
282
- headers: {},
283
- body: '',
284
- duration,
285
- error: err instanceof Error ? err.message : String(err)
286
- };
287
- }
288
- },
289
- };
290
-
291
- export default graphqlPlugin;