graphql-http 1.11.0 → 1.13.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.
@@ -11,27 +11,46 @@ export * from '../utils';
11
11
  *
12
12
  * @private
13
13
  */
14
- export declare function audit(name: AuditName, fn: () => Promise<void>): Audit;
14
+ export declare function audit(id: string, name: AuditName, fn: () => Promise<void>): Audit;
15
15
  /**
16
- * Will throw a string if the assertion fails.
17
- *
18
- * All fatal problems will throw an instance of Error.
16
+ * Error thrown when an assertion test fails.
19
17
  *
20
18
  * @private
21
19
  */
22
- export declare function assert<T = unknown>(name: string, actual: T): {
23
- toBe: (expected: T) => void;
24
- toBeBetween: (min: T extends number ? T : never, max: T extends number ? T : never) => void;
25
- toContain: (expected: T extends (infer U)[] ? U : T extends string ? T : never) => void;
26
- notToContain: (expected: T extends (infer U_1)[] ? U_1 : T extends string ? T : never) => void;
27
- toHaveProperty: (prop: T extends Record<any, any> ? PropertyKey : never) => void;
28
- notToHaveProperty: (prop: T extends Record<any, any> ? PropertyKey : never) => void;
29
- };
20
+ export declare class AuditError {
21
+ /**
22
+ * Response from the server.
23
+ */
24
+ response: Response;
25
+ /**
26
+ * Reason for the failing audit.
27
+ */
28
+ reason: string;
29
+ constructor(response: Response, reason: string);
30
+ }
30
31
  /**
31
- * Parses the string as JSON and safely reports parsing issues for audits.
32
+ * Will throw an AuditError if the assertion on Response fails.
33
+ *
34
+ * All fatal problems will throw an instance of an Error.
32
35
  *
33
- * Assumes the parsed JSON will be an `ExecutionResult`.
36
+ * The name "ressert" is a wordplay combining "response" and "assert".
34
37
  *
35
38
  * @private
36
- * */
37
- export declare function assertBodyAsExecutionResult(res: Response): Promise<ExecutionResult<import("graphql/jsutils/ObjMap").ObjMap<unknown>, import("graphql/jsutils/ObjMap").ObjMap<unknown>>>;
39
+ */
40
+ export declare function ressert(res: Response): {
41
+ status: {
42
+ toBe(code: number): void;
43
+ toBeBetween: (min: number, max: number) => void;
44
+ };
45
+ header(key: 'content-type'): {
46
+ toContain(part: string): void;
47
+ notToContain(part: string): void;
48
+ };
49
+ bodyAsExecutionResult: {
50
+ data: {
51
+ toBe(val: ExecutionResult['data']): Promise<void>;
52
+ };
53
+ toHaveProperty(key: keyof ExecutionResult): Promise<void>;
54
+ notToHaveProperty(key: keyof ExecutionResult): Promise<void>;
55
+ };
56
+ };
@@ -11,27 +11,46 @@ export * from '../utils';
11
11
  *
12
12
  * @private
13
13
  */
14
- export declare function audit(name: AuditName, fn: () => Promise<void>): Audit;
14
+ export declare function audit(id: string, name: AuditName, fn: () => Promise<void>): Audit;
15
15
  /**
16
- * Will throw a string if the assertion fails.
17
- *
18
- * All fatal problems will throw an instance of Error.
16
+ * Error thrown when an assertion test fails.
19
17
  *
20
18
  * @private
21
19
  */
22
- export declare function assert<T = unknown>(name: string, actual: T): {
23
- toBe: (expected: T) => void;
24
- toBeBetween: (min: T extends number ? T : never, max: T extends number ? T : never) => void;
25
- toContain: (expected: T extends (infer U)[] ? U : T extends string ? T : never) => void;
26
- notToContain: (expected: T extends (infer U_1)[] ? U_1 : T extends string ? T : never) => void;
27
- toHaveProperty: (prop: T extends Record<any, any> ? PropertyKey : never) => void;
28
- notToHaveProperty: (prop: T extends Record<any, any> ? PropertyKey : never) => void;
29
- };
20
+ export declare class AuditError {
21
+ /**
22
+ * Response from the server.
23
+ */
24
+ response: Response;
25
+ /**
26
+ * Reason for the failing audit.
27
+ */
28
+ reason: string;
29
+ constructor(response: Response, reason: string);
30
+ }
30
31
  /**
31
- * Parses the string as JSON and safely reports parsing issues for audits.
32
+ * Will throw an AuditError if the assertion on Response fails.
33
+ *
34
+ * All fatal problems will throw an instance of an Error.
32
35
  *
33
- * Assumes the parsed JSON will be an `ExecutionResult`.
36
+ * The name "ressert" is a wordplay combining "response" and "assert".
34
37
  *
35
38
  * @private
36
- * */
37
- export declare function assertBodyAsExecutionResult(res: Response): Promise<ExecutionResult<import("graphql/jsutils/ObjMap").ObjMap<unknown>, import("graphql/jsutils/ObjMap").ObjMap<unknown>>>;
39
+ */
40
+ export declare function ressert(res: Response): {
41
+ status: {
42
+ toBe(code: number): void;
43
+ toBeBetween: (min: number, max: number) => void;
44
+ };
45
+ header(key: 'content-type'): {
46
+ toContain(part: string): void;
47
+ notToContain(part: string): void;
48
+ };
49
+ bodyAsExecutionResult: {
50
+ data: {
51
+ toBe(val: ExecutionResult['data']): Promise<void>;
52
+ };
53
+ toHaveProperty(key: keyof ExecutionResult): Promise<void>;
54
+ notToHaveProperty(key: keyof ExecutionResult): Promise<void>;
55
+ };
56
+ };
@@ -19,37 +19,41 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
19
19
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
20
20
  };
21
21
  Object.defineProperty(exports, "__esModule", { value: true });
22
- exports.assertBodyAsExecutionResult = exports.assert = exports.audit = void 0;
22
+ exports.ressert = exports.AuditError = exports.audit = void 0;
23
23
  __exportStar(require("../utils"), exports);
24
24
  /**
25
25
  * Wrap and prepare an audit for testing.
26
26
  *
27
27
  * @private
28
28
  */
29
- function audit(name, fn) {
29
+ function audit(id, name, fn) {
30
30
  return {
31
+ id,
31
32
  name,
32
33
  fn: async () => {
33
34
  try {
34
35
  await fn();
35
36
  return {
37
+ id,
36
38
  name,
37
39
  status: 'ok',
38
40
  };
39
41
  }
40
- catch (errOrReason) {
41
- if (typeof errOrReason !== 'string') {
42
- // anything thrown that is not an assertion string is considered fatal
43
- throw errOrReason;
42
+ catch (err) {
43
+ if (!(err instanceof AuditError)) {
44
+ // anything thrown that is not an assertion error is considered fatal
45
+ throw err;
44
46
  }
45
47
  return {
48
+ id,
46
49
  name,
47
50
  status: name.startsWith('MUST')
48
51
  ? // only failing MUSTs are considered errors
49
52
  'error'
50
53
  : // everything else is optional and considered a warning
51
54
  'warn',
52
- reason: errOrReason,
55
+ reason: err.reason,
56
+ response: err.response,
53
57
  };
54
58
  }
55
59
  },
@@ -57,71 +61,98 @@ function audit(name, fn) {
57
61
  }
58
62
  exports.audit = audit;
59
63
  /**
60
- * Will throw a string if the assertion fails.
64
+ * Error thrown when an assertion test fails.
61
65
  *
62
- * All fatal problems will throw an instance of Error.
66
+ * @private
67
+ */
68
+ class AuditError {
69
+ constructor(response, reason) {
70
+ this.response = response;
71
+ this.reason = reason;
72
+ }
73
+ }
74
+ exports.AuditError = AuditError;
75
+ /**
76
+ * Will throw an AuditError if the assertion on Response fails.
77
+ *
78
+ * All fatal problems will throw an instance of an Error.
79
+ *
80
+ * The name "ressert" is a wordplay combining "response" and "assert".
63
81
  *
64
82
  * @private
65
83
  */
66
- function assert(name, actual) {
84
+ function ressert(res) {
67
85
  return {
68
- toBe: (expected) => {
69
- if (actual !== expected) {
70
- throw `${name} ${actual} is not ${expected}`;
71
- }
72
- },
73
- toBeBetween: (min, max) => {
74
- if (!(min <= actual && actual <= max)) {
75
- throw `${name} ${actual} is not between ${min} and ${max}`;
76
- }
77
- },
78
- toContain: (expected) => {
79
- // @ts-expect-error types will match, otherwise never
80
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
81
- if (!actual.includes(expected)) {
82
- throw `${name} ${JSON.stringify(actual)} does not contain ${JSON.stringify(expected)}`;
83
- }
84
- },
85
- notToContain: (expected) => {
86
- // @ts-expect-error types will match, otherwise never
87
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
88
- if (actual.includes(expected)) {
89
- throw `${name} ${JSON.stringify(actual)} contains ${JSON.stringify(expected)}`;
90
- }
86
+ status: {
87
+ toBe(code) {
88
+ if (res.status !== code) {
89
+ throw new AuditError(res, `Response status code is not ${code}`);
90
+ }
91
+ },
92
+ toBeBetween: (min, max) => {
93
+ if (!(min <= res.status && res.status <= max)) {
94
+ throw new AuditError(res, `Response status is not between ${min} and ${max}`);
95
+ }
96
+ },
91
97
  },
92
- toHaveProperty: (
93
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
94
- prop) => {
95
- // @ts-expect-error types will match, otherwise never
96
- if (!(prop in actual)) {
97
- throw `${name} ${JSON.stringify(actual)} does not have a property '${String(prop)}'`;
98
- }
98
+ header(key) {
99
+ return {
100
+ toContain(part) {
101
+ var _a;
102
+ if (!((_a = res.headers.get(key)) === null || _a === void 0 ? void 0 : _a.includes(part))) {
103
+ throw new AuditError(res, `Response header ${key} does not contain ${part}`);
104
+ }
105
+ },
106
+ notToContain(part) {
107
+ var _a;
108
+ if ((_a = res.headers.get(key)) === null || _a === void 0 ? void 0 : _a.includes(part)) {
109
+ throw new AuditError(res, `Response header ${key} contains ${part}`);
110
+ }
111
+ },
112
+ };
99
113
  },
100
- notToHaveProperty: (
101
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
102
- prop) => {
103
- // @ts-expect-error types will match, otherwise never
104
- if (prop in actual) {
105
- throw `${name} ${JSON.stringify(actual)} does have a property '${String(prop)}'`;
106
- }
114
+ bodyAsExecutionResult: {
115
+ data: {
116
+ async toBe(val) {
117
+ const body = await assertBodyAsExecutionResult(res);
118
+ if (body.data !== val) {
119
+ throw new AuditError(res, `Response body execution result data is not "${val}"`);
120
+ }
121
+ },
122
+ },
123
+ async toHaveProperty(key) {
124
+ const body = await assertBodyAsExecutionResult(res);
125
+ if (!(key in body)) {
126
+ throw new AuditError(res, `Response body execution result does not have a property "${key}"`);
127
+ }
128
+ },
129
+ async notToHaveProperty(key) {
130
+ const body = await assertBodyAsExecutionResult(res);
131
+ if (key in body) {
132
+ throw new AuditError(res, `Response body execution result has a property "${key}"`);
133
+ }
134
+ },
107
135
  },
108
136
  };
109
137
  }
110
- exports.assert = assert;
111
- /**
112
- * Parses the string as JSON and safely reports parsing issues for audits.
113
- *
114
- * Assumes the parsed JSON will be an `ExecutionResult`.
115
- *
116
- * @private
117
- * */
138
+ exports.ressert = ressert;
139
+ /** @private */
118
140
  async function assertBodyAsExecutionResult(res) {
119
- const str = await res.text();
141
+ let decoded;
142
+ try {
143
+ const decoder = new TextDecoder('utf-8');
144
+ const buff = await res.arrayBuffer();
145
+ decoded = decoder.decode(buff);
146
+ }
147
+ catch (err) {
148
+ throw new AuditError(res, 'Response body is not UTF-8 encoded');
149
+ }
150
+ let body;
120
151
  try {
121
- return JSON.parse(str);
152
+ body = JSON.parse(decoded);
122
153
  }
123
154
  catch (err) {
124
- throw `Response body is not valid JSON. Got ${JSON.stringify(str)}`;
155
+ throw new AuditError(res, 'Response body is not valid JSON');
125
156
  }
157
+ return body;
126
158
  }
127
- exports.assertBodyAsExecutionResult = assertBodyAsExecutionResult;
@@ -9,99 +9,130 @@ export * from '../utils.mjs';
9
9
  *
10
10
  * @private
11
11
  */
12
- export function audit(name, fn) {
12
+ export function audit(id, name, fn) {
13
13
  return {
14
+ id,
14
15
  name,
15
16
  fn: async () => {
16
17
  try {
17
18
  await fn();
18
19
  return {
20
+ id,
19
21
  name,
20
22
  status: 'ok',
21
23
  };
22
24
  }
23
- catch (errOrReason) {
24
- if (typeof errOrReason !== 'string') {
25
- // anything thrown that is not an assertion string is considered fatal
26
- throw errOrReason;
25
+ catch (err) {
26
+ if (!(err instanceof AuditError)) {
27
+ // anything thrown that is not an assertion error is considered fatal
28
+ throw err;
27
29
  }
28
30
  return {
31
+ id,
29
32
  name,
30
33
  status: name.startsWith('MUST')
31
34
  ? // only failing MUSTs are considered errors
32
35
  'error'
33
36
  : // everything else is optional and considered a warning
34
37
  'warn',
35
- reason: errOrReason,
38
+ reason: err.reason,
39
+ response: err.response,
36
40
  };
37
41
  }
38
42
  },
39
43
  };
40
44
  }
41
45
  /**
42
- * Will throw a string if the assertion fails.
46
+ * Error thrown when an assertion test fails.
43
47
  *
44
- * All fatal problems will throw an instance of Error.
48
+ * @private
49
+ */
50
+ export class AuditError {
51
+ constructor(response, reason) {
52
+ this.response = response;
53
+ this.reason = reason;
54
+ }
55
+ }
56
+ /**
57
+ * Will throw an AuditError if the assertion on Response fails.
58
+ *
59
+ * All fatal problems will throw an instance of an Error.
60
+ *
61
+ * The name "ressert" is a wordplay combining "response" and "assert".
45
62
  *
46
63
  * @private
47
64
  */
48
- export function assert(name, actual) {
65
+ export function ressert(res) {
49
66
  return {
50
- toBe: (expected) => {
51
- if (actual !== expected) {
52
- throw `${name} ${actual} is not ${expected}`;
53
- }
54
- },
55
- toBeBetween: (min, max) => {
56
- if (!(min <= actual && actual <= max)) {
57
- throw `${name} ${actual} is not between ${min} and ${max}`;
58
- }
59
- },
60
- toContain: (expected) => {
61
- // @ts-expect-error types will match, otherwise never
62
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
63
- if (!actual.includes(expected)) {
64
- throw `${name} ${JSON.stringify(actual)} does not contain ${JSON.stringify(expected)}`;
65
- }
66
- },
67
- notToContain: (expected) => {
68
- // @ts-expect-error types will match, otherwise never
69
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
70
- if (actual.includes(expected)) {
71
- throw `${name} ${JSON.stringify(actual)} contains ${JSON.stringify(expected)}`;
72
- }
67
+ status: {
68
+ toBe(code) {
69
+ if (res.status !== code) {
70
+ throw new AuditError(res, `Response status code is not ${code}`);
71
+ }
72
+ },
73
+ toBeBetween: (min, max) => {
74
+ if (!(min <= res.status && res.status <= max)) {
75
+ throw new AuditError(res, `Response status is not between ${min} and ${max}`);
76
+ }
77
+ },
73
78
  },
74
- toHaveProperty: (
75
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
76
- prop) => {
77
- // @ts-expect-error types will match, otherwise never
78
- if (!(prop in actual)) {
79
- throw `${name} ${JSON.stringify(actual)} does not have a property '${String(prop)}'`;
80
- }
79
+ header(key) {
80
+ return {
81
+ toContain(part) {
82
+ var _a;
83
+ if (!((_a = res.headers.get(key)) === null || _a === void 0 ? void 0 : _a.includes(part))) {
84
+ throw new AuditError(res, `Response header ${key} does not contain ${part}`);
85
+ }
86
+ },
87
+ notToContain(part) {
88
+ var _a;
89
+ if ((_a = res.headers.get(key)) === null || _a === void 0 ? void 0 : _a.includes(part)) {
90
+ throw new AuditError(res, `Response header ${key} contains ${part}`);
91
+ }
92
+ },
93
+ };
81
94
  },
82
- notToHaveProperty: (
83
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
84
- prop) => {
85
- // @ts-expect-error types will match, otherwise never
86
- if (prop in actual) {
87
- throw `${name} ${JSON.stringify(actual)} does have a property '${String(prop)}'`;
88
- }
95
+ bodyAsExecutionResult: {
96
+ data: {
97
+ async toBe(val) {
98
+ const body = await assertBodyAsExecutionResult(res);
99
+ if (body.data !== val) {
100
+ throw new AuditError(res, `Response body execution result data is not "${val}"`);
101
+ }
102
+ },
103
+ },
104
+ async toHaveProperty(key) {
105
+ const body = await assertBodyAsExecutionResult(res);
106
+ if (!(key in body)) {
107
+ throw new AuditError(res, `Response body execution result does not have a property "${key}"`);
108
+ }
109
+ },
110
+ async notToHaveProperty(key) {
111
+ const body = await assertBodyAsExecutionResult(res);
112
+ if (key in body) {
113
+ throw new AuditError(res, `Response body execution result has a property "${key}"`);
114
+ }
115
+ },
89
116
  },
90
117
  };
91
118
  }
92
- /**
93
- * Parses the string as JSON and safely reports parsing issues for audits.
94
- *
95
- * Assumes the parsed JSON will be an `ExecutionResult`.
96
- *
97
- * @private
98
- * */
99
- export async function assertBodyAsExecutionResult(res) {
100
- const str = await res.text();
119
+ /** @private */
120
+ async function assertBodyAsExecutionResult(res) {
121
+ let decoded;
122
+ try {
123
+ const decoder = new TextDecoder('utf-8');
124
+ const buff = await res.arrayBuffer();
125
+ decoded = decoder.decode(buff);
126
+ }
127
+ catch (err) {
128
+ throw new AuditError(res, 'Response body is not UTF-8 encoded');
129
+ }
130
+ let body;
101
131
  try {
102
- return JSON.parse(str);
132
+ body = JSON.parse(decoded);
103
133
  }
104
134
  catch (err) {
105
- throw `Response body is not valid JSON. Got ${JSON.stringify(str)}`;
135
+ throw new AuditError(res, 'Response body is not valid JSON');
106
136
  }
137
+ return body;
107
138
  }
package/lib/handler.js CHANGED
@@ -326,41 +326,40 @@ exports.getAcceptableMediaType = getAcceptableMediaType;
326
326
  * @category Server
327
327
  */
328
328
  function makeResponse(resultOrErrors, acceptedMediaType) {
329
- if ((0, utils_1.isExecutionResult)(resultOrErrors)) {
329
+ const errors = (0, utils_1.isGraphQLError)(resultOrErrors)
330
+ ? [resultOrErrors]
331
+ : (0, utils_1.areGraphQLErrors)(resultOrErrors)
332
+ ? resultOrErrors
333
+ : null;
334
+ if (errors) {
330
335
  return [
331
- JSON.stringify(resultOrErrors),
332
- {
333
- status: 200,
334
- statusText: 'OK',
335
- headers: {
336
+ JSON.stringify({ errors }),
337
+ Object.assign(Object.assign({}, (acceptedMediaType === 'application/json'
338
+ ? {
339
+ status: 200,
340
+ statusText: 'OK',
341
+ }
342
+ : {
343
+ status: 400,
344
+ statusText: 'Bad Request',
345
+ })), { headers: {
336
346
  'content-type': acceptedMediaType === 'application/json'
337
347
  ? 'application/json; charset=utf-8'
338
348
  : 'application/graphql-response+json; charset=utf-8',
339
- },
340
- },
349
+ } }),
341
350
  ];
342
351
  }
343
352
  return [
344
- JSON.stringify({
345
- errors: Array.isArray(resultOrErrors)
346
- ? (0, utils_1.isObject)(resultOrErrors)
347
- ? resultOrErrors
348
- : new graphql_1.GraphQLError(String(resultOrErrors))
349
- : [resultOrErrors],
350
- }),
351
- Object.assign(Object.assign({}, (acceptedMediaType === 'application/json'
352
- ? {
353
- status: 200,
354
- statusText: 'OK',
355
- }
356
- : {
357
- status: 400,
358
- statusText: 'Bad Request',
359
- })), { headers: {
353
+ JSON.stringify(resultOrErrors),
354
+ {
355
+ status: 200,
356
+ statusText: 'OK',
357
+ headers: {
360
358
  'content-type': acceptedMediaType === 'application/json'
361
359
  ? 'application/json; charset=utf-8'
362
360
  : 'application/graphql-response+json; charset=utf-8',
363
- } }),
361
+ },
362
+ },
364
363
  ];
365
364
  }
366
365
  exports.makeResponse = makeResponse;
package/lib/handler.mjs CHANGED
@@ -4,7 +4,7 @@
4
4
  *
5
5
  */
6
6
  import { validate as graphqlValidate, execute as graphqlExecute, parse as graphqlParse, getOperationAST as graphqlGetOperationAST, GraphQLError, } from 'graphql';
7
- import { areGraphQLErrors, isAsyncIterable, isExecutionResult, isObject, } from './utils.mjs';
7
+ import { areGraphQLErrors, isAsyncIterable, isExecutionResult, isGraphQLError, isObject, } from './utils.mjs';
8
8
  /**
9
9
  * Checks whether the passed value is the `graphql-http` server agnostic response.
10
10
  *
@@ -320,41 +320,40 @@ export function getAcceptableMediaType(acceptHeader) {
320
320
  * @category Server
321
321
  */
322
322
  export function makeResponse(resultOrErrors, acceptedMediaType) {
323
- if (isExecutionResult(resultOrErrors)) {
323
+ const errors = isGraphQLError(resultOrErrors)
324
+ ? [resultOrErrors]
325
+ : areGraphQLErrors(resultOrErrors)
326
+ ? resultOrErrors
327
+ : null;
328
+ if (errors) {
324
329
  return [
325
- JSON.stringify(resultOrErrors),
326
- {
327
- status: 200,
328
- statusText: 'OK',
329
- headers: {
330
+ JSON.stringify({ errors }),
331
+ Object.assign(Object.assign({}, (acceptedMediaType === 'application/json'
332
+ ? {
333
+ status: 200,
334
+ statusText: 'OK',
335
+ }
336
+ : {
337
+ status: 400,
338
+ statusText: 'Bad Request',
339
+ })), { headers: {
330
340
  'content-type': acceptedMediaType === 'application/json'
331
341
  ? 'application/json; charset=utf-8'
332
342
  : 'application/graphql-response+json; charset=utf-8',
333
- },
334
- },
343
+ } }),
335
344
  ];
336
345
  }
337
346
  return [
338
- JSON.stringify({
339
- errors: Array.isArray(resultOrErrors)
340
- ? isObject(resultOrErrors)
341
- ? resultOrErrors
342
- : new GraphQLError(String(resultOrErrors))
343
- : [resultOrErrors],
344
- }),
345
- Object.assign(Object.assign({}, (acceptedMediaType === 'application/json'
346
- ? {
347
- status: 200,
348
- statusText: 'OK',
349
- }
350
- : {
351
- status: 400,
352
- statusText: 'Bad Request',
353
- })), { headers: {
347
+ JSON.stringify(resultOrErrors),
348
+ {
349
+ status: 200,
350
+ statusText: 'OK',
351
+ headers: {
354
352
  'content-type': acceptedMediaType === 'application/json'
355
353
  ? 'application/json; charset=utf-8'
356
354
  : 'application/graphql-response+json; charset=utf-8',
357
- } }),
355
+ },
356
+ },
358
357
  ];
359
358
  }
360
359
  function getHeader(req, key) {