@apiquest/plugin-graphql 1.0.4 → 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.
Files changed (2) hide show
  1. package/package.json +2 -3
  2. package/src/index.ts +0 -291
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apiquest/plugin-graphql",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "GraphQL protocol plugin for ApiQuest",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -53,8 +53,7 @@
53
53
  "basic",
54
54
  "oauth2",
55
55
  "apikey"
56
- ],
57
- "strictAuthList": false
56
+ ]
58
57
  }
59
58
  }
60
59
  }
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;