graphql-http 1.10.0 → 1.12.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 +18 -0
- package/lib/audits/common.d.mts +1 -0
- package/lib/audits/common.d.ts +1 -0
- package/lib/audits/server.js +75 -87
- package/lib/audits/server.mjs +76 -88
- package/lib/audits/utils.d.mts +34 -15
- package/lib/audits/utils.d.ts +34 -15
- package/lib/audits/utils.js +86 -58
- package/lib/audits/utils.mjs +84 -56
- package/lib/handler.js +24 -25
- package/lib/handler.mjs +25 -26
- package/lib/use/koa.d.mts +24 -0
- package/lib/use/koa.d.ts +24 -0
- package/lib/use/koa.js +78 -0
- package/lib/use/koa.mjs +74 -0
- package/lib/utils.d.mts +4 -1
- package/lib/utils.d.ts +4 -1
- package/lib/utils.js +9 -4
- package/lib/utils.mjs +7 -3
- package/package.json +25 -16
- package/umd/graphql-http.js +2 -2
- package/umd/graphql-http.min.js +1 -1
- package/umd/graphql-http.min.js.gz +0 -0
package/lib/audits/utils.js
CHANGED
|
@@ -19,7 +19,7 @@ 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.
|
|
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.
|
|
@@ -37,10 +37,10 @@ function audit(name, fn) {
|
|
|
37
37
|
status: 'ok',
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
|
-
catch (
|
|
41
|
-
if (
|
|
42
|
-
// anything thrown that is not an assertion
|
|
43
|
-
throw
|
|
40
|
+
catch (err) {
|
|
41
|
+
if (!(err instanceof AuditError)) {
|
|
42
|
+
// anything thrown that is not an assertion error is considered fatal
|
|
43
|
+
throw err;
|
|
44
44
|
}
|
|
45
45
|
return {
|
|
46
46
|
name,
|
|
@@ -49,7 +49,8 @@ function audit(name, fn) {
|
|
|
49
49
|
'error'
|
|
50
50
|
: // everything else is optional and considered a warning
|
|
51
51
|
'warn',
|
|
52
|
-
reason:
|
|
52
|
+
reason: err.reason,
|
|
53
|
+
response: err.response,
|
|
53
54
|
};
|
|
54
55
|
}
|
|
55
56
|
},
|
|
@@ -57,71 +58,98 @@ function audit(name, fn) {
|
|
|
57
58
|
}
|
|
58
59
|
exports.audit = audit;
|
|
59
60
|
/**
|
|
60
|
-
*
|
|
61
|
+
* Error thrown when an assertion test fails.
|
|
61
62
|
*
|
|
62
|
-
*
|
|
63
|
+
* @private
|
|
64
|
+
*/
|
|
65
|
+
class AuditError {
|
|
66
|
+
constructor(response, reason) {
|
|
67
|
+
this.response = response;
|
|
68
|
+
this.reason = reason;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.AuditError = AuditError;
|
|
72
|
+
/**
|
|
73
|
+
* Will throw an AuditError if the assertion on Response fails.
|
|
74
|
+
*
|
|
75
|
+
* All fatal problems will throw an instance of an Error.
|
|
76
|
+
*
|
|
77
|
+
* The name "ressert" is a wordplay combining "response" and "assert".
|
|
63
78
|
*
|
|
64
79
|
* @private
|
|
65
80
|
*/
|
|
66
|
-
function
|
|
81
|
+
function ressert(res) {
|
|
67
82
|
return {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
}
|
|
83
|
+
status: {
|
|
84
|
+
toBe(code) {
|
|
85
|
+
if (res.status !== code) {
|
|
86
|
+
throw new AuditError(res, `Response status code is not ${code}`);
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
toBeBetween: (min, max) => {
|
|
90
|
+
if (!(min <= res.status && res.status <= max)) {
|
|
91
|
+
throw new AuditError(res, `Response status is not between ${min} and ${max}`);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
91
94
|
},
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
header(key) {
|
|
96
|
+
return {
|
|
97
|
+
toContain(part) {
|
|
98
|
+
var _a;
|
|
99
|
+
if (!((_a = res.headers.get(key)) === null || _a === void 0 ? void 0 : _a.includes(part))) {
|
|
100
|
+
throw new AuditError(res, `Response header ${key} does not contain ${part}`);
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
notToContain(part) {
|
|
104
|
+
var _a;
|
|
105
|
+
if ((_a = res.headers.get(key)) === null || _a === void 0 ? void 0 : _a.includes(part)) {
|
|
106
|
+
throw new AuditError(res, `Response header ${key} contains ${part}`);
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
};
|
|
99
110
|
},
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
111
|
+
bodyAsExecutionResult: {
|
|
112
|
+
data: {
|
|
113
|
+
async toBe(val) {
|
|
114
|
+
const body = await assertBodyAsExecutionResult(res);
|
|
115
|
+
if (body.data !== val) {
|
|
116
|
+
throw new AuditError(res, `Response body execution result data is not "${val}"`);
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
async toHaveProperty(key) {
|
|
121
|
+
const body = await assertBodyAsExecutionResult(res);
|
|
122
|
+
if (!(key in body)) {
|
|
123
|
+
throw new AuditError(res, `Response body execution result does not have a property "${key}"`);
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
async notToHaveProperty(key) {
|
|
127
|
+
const body = await assertBodyAsExecutionResult(res);
|
|
128
|
+
if (key in body) {
|
|
129
|
+
throw new AuditError(res, `Response body execution result has a property "${key}"`);
|
|
130
|
+
}
|
|
131
|
+
},
|
|
107
132
|
},
|
|
108
133
|
};
|
|
109
134
|
}
|
|
110
|
-
exports.
|
|
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
|
-
* */
|
|
135
|
+
exports.ressert = ressert;
|
|
136
|
+
/** @private */
|
|
118
137
|
async function assertBodyAsExecutionResult(res) {
|
|
119
|
-
|
|
138
|
+
let decoded;
|
|
139
|
+
try {
|
|
140
|
+
const decoder = new TextDecoder('utf-8');
|
|
141
|
+
const buff = await res.arrayBuffer();
|
|
142
|
+
decoded = decoder.decode(buff);
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
throw new AuditError(res, 'Response body is not UTF-8 encoded');
|
|
146
|
+
}
|
|
147
|
+
let body;
|
|
120
148
|
try {
|
|
121
|
-
|
|
149
|
+
body = JSON.parse(decoded);
|
|
122
150
|
}
|
|
123
151
|
catch (err) {
|
|
124
|
-
throw
|
|
152
|
+
throw new AuditError(res, 'Response body is not valid JSON');
|
|
125
153
|
}
|
|
154
|
+
return body;
|
|
126
155
|
}
|
|
127
|
-
exports.assertBodyAsExecutionResult = assertBodyAsExecutionResult;
|
package/lib/audits/utils.mjs
CHANGED
|
@@ -20,10 +20,10 @@ export function audit(name, fn) {
|
|
|
20
20
|
status: 'ok',
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
|
-
catch (
|
|
24
|
-
if (
|
|
25
|
-
// anything thrown that is not an assertion
|
|
26
|
-
throw
|
|
23
|
+
catch (err) {
|
|
24
|
+
if (!(err instanceof AuditError)) {
|
|
25
|
+
// anything thrown that is not an assertion error is considered fatal
|
|
26
|
+
throw err;
|
|
27
27
|
}
|
|
28
28
|
return {
|
|
29
29
|
name,
|
|
@@ -32,76 +32,104 @@ export function audit(name, fn) {
|
|
|
32
32
|
'error'
|
|
33
33
|
: // everything else is optional and considered a warning
|
|
34
34
|
'warn',
|
|
35
|
-
reason:
|
|
35
|
+
reason: err.reason,
|
|
36
|
+
response: err.response,
|
|
36
37
|
};
|
|
37
38
|
}
|
|
38
39
|
},
|
|
39
40
|
};
|
|
40
41
|
}
|
|
41
42
|
/**
|
|
42
|
-
*
|
|
43
|
+
* Error thrown when an assertion test fails.
|
|
43
44
|
*
|
|
44
|
-
*
|
|
45
|
+
* @private
|
|
46
|
+
*/
|
|
47
|
+
export class AuditError {
|
|
48
|
+
constructor(response, reason) {
|
|
49
|
+
this.response = response;
|
|
50
|
+
this.reason = reason;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Will throw an AuditError if the assertion on Response fails.
|
|
55
|
+
*
|
|
56
|
+
* All fatal problems will throw an instance of an Error.
|
|
57
|
+
*
|
|
58
|
+
* The name "ressert" is a wordplay combining "response" and "assert".
|
|
45
59
|
*
|
|
46
60
|
* @private
|
|
47
61
|
*/
|
|
48
|
-
export function
|
|
62
|
+
export function ressert(res) {
|
|
49
63
|
return {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
}
|
|
64
|
+
status: {
|
|
65
|
+
toBe(code) {
|
|
66
|
+
if (res.status !== code) {
|
|
67
|
+
throw new AuditError(res, `Response status code is not ${code}`);
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
toBeBetween: (min, max) => {
|
|
71
|
+
if (!(min <= res.status && res.status <= max)) {
|
|
72
|
+
throw new AuditError(res, `Response status is not between ${min} and ${max}`);
|
|
73
|
+
}
|
|
74
|
+
},
|
|
73
75
|
},
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
header(key) {
|
|
77
|
+
return {
|
|
78
|
+
toContain(part) {
|
|
79
|
+
var _a;
|
|
80
|
+
if (!((_a = res.headers.get(key)) === null || _a === void 0 ? void 0 : _a.includes(part))) {
|
|
81
|
+
throw new AuditError(res, `Response header ${key} does not contain ${part}`);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
notToContain(part) {
|
|
85
|
+
var _a;
|
|
86
|
+
if ((_a = res.headers.get(key)) === null || _a === void 0 ? void 0 : _a.includes(part)) {
|
|
87
|
+
throw new AuditError(res, `Response header ${key} contains ${part}`);
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
};
|
|
81
91
|
},
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
92
|
+
bodyAsExecutionResult: {
|
|
93
|
+
data: {
|
|
94
|
+
async toBe(val) {
|
|
95
|
+
const body = await assertBodyAsExecutionResult(res);
|
|
96
|
+
if (body.data !== val) {
|
|
97
|
+
throw new AuditError(res, `Response body execution result data is not "${val}"`);
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
async toHaveProperty(key) {
|
|
102
|
+
const body = await assertBodyAsExecutionResult(res);
|
|
103
|
+
if (!(key in body)) {
|
|
104
|
+
throw new AuditError(res, `Response body execution result does not have a property "${key}"`);
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
async notToHaveProperty(key) {
|
|
108
|
+
const body = await assertBodyAsExecutionResult(res);
|
|
109
|
+
if (key in body) {
|
|
110
|
+
throw new AuditError(res, `Response body execution result has a property "${key}"`);
|
|
111
|
+
}
|
|
112
|
+
},
|
|
89
113
|
},
|
|
90
114
|
};
|
|
91
115
|
}
|
|
92
|
-
/**
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
116
|
+
/** @private */
|
|
117
|
+
async function assertBodyAsExecutionResult(res) {
|
|
118
|
+
let decoded;
|
|
119
|
+
try {
|
|
120
|
+
const decoder = new TextDecoder('utf-8');
|
|
121
|
+
const buff = await res.arrayBuffer();
|
|
122
|
+
decoded = decoder.decode(buff);
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
throw new AuditError(res, 'Response body is not UTF-8 encoded');
|
|
126
|
+
}
|
|
127
|
+
let body;
|
|
101
128
|
try {
|
|
102
|
-
|
|
129
|
+
body = JSON.parse(decoded);
|
|
103
130
|
}
|
|
104
131
|
catch (err) {
|
|
105
|
-
throw
|
|
132
|
+
throw new AuditError(res, 'Response body is not valid JSON');
|
|
106
133
|
}
|
|
134
|
+
return body;
|
|
107
135
|
}
|
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
|
-
|
|
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(
|
|
332
|
-
{
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
-
|
|
323
|
+
const errors = isGraphQLError(resultOrErrors)
|
|
324
|
+
? [resultOrErrors]
|
|
325
|
+
: areGraphQLErrors(resultOrErrors)
|
|
326
|
+
? resultOrErrors
|
|
327
|
+
: null;
|
|
328
|
+
if (errors) {
|
|
324
329
|
return [
|
|
325
|
-
JSON.stringify(
|
|
326
|
-
{
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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) {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import type { Middleware } from 'koa';
|
|
3
|
+
import type { IncomingMessage } from 'http';
|
|
4
|
+
import { HandlerOptions, OperationContext } from '../handler';
|
|
5
|
+
/**
|
|
6
|
+
* Create a GraphQL over HTTP Protocol compliant request handler for
|
|
7
|
+
* the Koa framework.
|
|
8
|
+
*
|
|
9
|
+
* ```js
|
|
10
|
+
* import Koa from 'koa'; // yarn add koa
|
|
11
|
+
* import mount from 'koa-mount'; // yarn add koa-mount
|
|
12
|
+
* import { createHandler } from 'graphql-http/lib/use/koa';
|
|
13
|
+
* import { schema } from './my-graphql-schema';
|
|
14
|
+
*
|
|
15
|
+
* const app = new Koa();
|
|
16
|
+
* app.use(mount('/', createHandler({ schema })));
|
|
17
|
+
*
|
|
18
|
+
* app.listen({ port: 4000 });
|
|
19
|
+
* console.log('Listening to port 4000');
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @category Server/koa
|
|
23
|
+
*/
|
|
24
|
+
export declare function createHandler<Context extends OperationContext = undefined>(options: HandlerOptions<IncomingMessage, undefined, Context>): Middleware;
|
package/lib/use/koa.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import type { Middleware } from 'koa';
|
|
3
|
+
import type { IncomingMessage } from 'http';
|
|
4
|
+
import { HandlerOptions, OperationContext } from '../handler';
|
|
5
|
+
/**
|
|
6
|
+
* Create a GraphQL over HTTP Protocol compliant request handler for
|
|
7
|
+
* the Koa framework.
|
|
8
|
+
*
|
|
9
|
+
* ```js
|
|
10
|
+
* import Koa from 'koa'; // yarn add koa
|
|
11
|
+
* import mount from 'koa-mount'; // yarn add koa-mount
|
|
12
|
+
* import { createHandler } from 'graphql-http/lib/use/koa';
|
|
13
|
+
* import { schema } from './my-graphql-schema';
|
|
14
|
+
*
|
|
15
|
+
* const app = new Koa();
|
|
16
|
+
* app.use(mount('/', createHandler({ schema })));
|
|
17
|
+
*
|
|
18
|
+
* app.listen({ port: 4000 });
|
|
19
|
+
* console.log('Listening to port 4000');
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @category Server/koa
|
|
23
|
+
*/
|
|
24
|
+
export declare function createHandler<Context extends OperationContext = undefined>(options: HandlerOptions<IncomingMessage, undefined, Context>): Middleware;
|
package/lib/use/koa.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createHandler = void 0;
|
|
4
|
+
const handler_1 = require("../handler");
|
|
5
|
+
/**
|
|
6
|
+
* Create a GraphQL over HTTP Protocol compliant request handler for
|
|
7
|
+
* the Koa framework.
|
|
8
|
+
*
|
|
9
|
+
* ```js
|
|
10
|
+
* import Koa from 'koa'; // yarn add koa
|
|
11
|
+
* import mount from 'koa-mount'; // yarn add koa-mount
|
|
12
|
+
* import { createHandler } from 'graphql-http/lib/use/koa';
|
|
13
|
+
* import { schema } from './my-graphql-schema';
|
|
14
|
+
*
|
|
15
|
+
* const app = new Koa();
|
|
16
|
+
* app.use(mount('/', createHandler({ schema })));
|
|
17
|
+
*
|
|
18
|
+
* app.listen({ port: 4000 });
|
|
19
|
+
* console.log('Listening to port 4000');
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @category Server/koa
|
|
23
|
+
*/
|
|
24
|
+
function createHandler(options) {
|
|
25
|
+
const isProd = process.env.NODE_ENV === 'production';
|
|
26
|
+
const handle = (0, handler_1.createHandler)(options);
|
|
27
|
+
return async function requestListener(ctx) {
|
|
28
|
+
try {
|
|
29
|
+
const [body, init] = await handle({
|
|
30
|
+
url: ctx.url,
|
|
31
|
+
method: ctx.method,
|
|
32
|
+
headers: ctx.headers,
|
|
33
|
+
body: () => {
|
|
34
|
+
if (ctx.body) {
|
|
35
|
+
// in case koa has a body parser
|
|
36
|
+
return ctx.body;
|
|
37
|
+
}
|
|
38
|
+
return new Promise((resolve) => {
|
|
39
|
+
let body = '';
|
|
40
|
+
ctx.req.on('data', (chunk) => (body += chunk));
|
|
41
|
+
ctx.req.on('end', () => resolve(body));
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
raw: ctx.req,
|
|
45
|
+
context: undefined,
|
|
46
|
+
});
|
|
47
|
+
ctx.body = body;
|
|
48
|
+
ctx.response.status = init.status;
|
|
49
|
+
ctx.response.message = init.statusText;
|
|
50
|
+
if (init.headers) {
|
|
51
|
+
for (const [name, value] of Object.entries(init.headers)) {
|
|
52
|
+
ctx.response.set(name, value);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
// The handler shouldnt throw errors.
|
|
58
|
+
// If you wish to handle them differently, consider implementing your own request handler.
|
|
59
|
+
console.error('Internal error occurred during request handling. ' +
|
|
60
|
+
'Please check your implementation.', err);
|
|
61
|
+
ctx.response.status = 500;
|
|
62
|
+
if (!isProd) {
|
|
63
|
+
ctx.response.set('content-type', 'application/json; charset=utf-8');
|
|
64
|
+
ctx.body = {
|
|
65
|
+
errors: [
|
|
66
|
+
err instanceof Error
|
|
67
|
+
? {
|
|
68
|
+
message: err.message,
|
|
69
|
+
stack: err.stack,
|
|
70
|
+
}
|
|
71
|
+
: err,
|
|
72
|
+
],
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
exports.createHandler = createHandler;
|