From 20da06c572df8f0aa331911fb031a7d2d0762422 Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Sat, 5 Nov 2022 14:14:56 +0100 Subject: [PATCH 01/19] use node and popular frameworks --- README.md | 113 ++++++--------------------- package.json | 18 +++++ src/use/express.ts | 60 ++++++++++++++ src/use/fastify.ts | 55 +++++++++++++ src/use/node.ts | 61 +++++++++++++++ yarn.lock | 190 ++++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 403 insertions(+), 94 deletions(-) create mode 100644 src/use/express.ts create mode 100644 src/use/fastify.ts create mode 100644 src/use/node.ts diff --git a/README.md b/README.md index 4656a4d0..1812c2ec 100644 --- a/README.md +++ b/README.md @@ -51,34 +51,18 @@ const schema = new GraphQLSchema({ ```js import http from 'http'; -import { createHandler } from 'graphql-http'; +import { createHandler } from 'graphql-http/lib/use/node'; import { schema } from './previous-step'; -// Create the GraphQL over HTTP handler +// Create the GraphQL over HTTP Node request handler const handler = createHandler({ schema }); -// Create a HTTP server using the handler on `/graphql` -const server = http.createServer(async (req, res) => { +// Create a HTTP server using the listner on `/graphql` +const server = http.createServer((req, res) => { if (!req.url.startsWith('/graphql')) { - return res.writeHead(404).end(); - } - - try { - const [body, init] = await handler({ - url: req.url, - method: req.method, - headers: req.headers, - body: () => - new Promise((resolve) => { - let body = ''; - req.on('data', (chunk) => (body += chunk)); - req.on('end', () => resolve(body)); - }), - raw: req, - }); - res.writeHead(init.status, init.statusText, init.headers).end(body); - } catch (err) { - res.writeHead(500).end(err.message); + handler(req, res); + } else { + res.writeHead(404).end(); } }); @@ -98,10 +82,10 @@ $ openssl req -x509 -newkey rsa:2048 -nodes -sha256 -subj '/CN=localhost' \ ```js import fs from 'fs'; import http2 from 'http2'; -import { createHandler } from 'graphql-http'; +import { createHandler } from 'graphql-http/lib/use/node'; import { schema } from './previous-step'; -// Create the GraphQL over HTTP handler +// Create the GraphQL over HTTP Node request handler const handler = createHandler({ schema }); // Create a HTTP/2 server using the handler on `/graphql` @@ -110,27 +94,11 @@ const server = http2.createSecureServer( key: fs.readFileSync('localhost-privkey.pem'), cert: fs.readFileSync('localhost-cert.pem'), }, - async (req, res) => { - if (!req.url.startsWith('/graphql')) { - return res.writeHead(404).end(); - } - - try { - const [body, init] = await handler({ - url: req.url, - method: req.method, - headers: req.headers, - body: () => - new Promise((resolve) => { - let body = ''; - req.on('data', (chunk) => (body += chunk)); - req.on('end', () => resolve(body)); - }), - raw: req, - }); - res.writeHead(init.status, init.statusText, init.headers).end(body); - } catch (err) { - res.writeHead(500).end(err.message); + (req, res) => { + if (req.url.startsWith('/graphql')) { + handler(req, res); + } else { + res.writeHead(404).end(); } }, ); @@ -143,35 +111,15 @@ console.log('Listening to port 4000'); ```js import express from 'express'; // yarn add express -import { createHandler } from 'graphql-http'; +import { createHandler } from 'graphql-http/lib/use/express'; import { schema } from './previous-step'; -// Create the GraphQL over HTTP handler -const handler = createHandler({ schema }); - -// Create an express app serving all methods on `/graphql` +// Create a express instance serving all methods on `/graphql` +// where the GraphQL over HTTP express request handler is. const app = express(); -app.use('/graphql', async (req, res) => { - try { - const [body, init] = await handler({ - url: req.url, - method: req.method, - headers: req.headers, - body: () => - new Promise((resolve) => { - let body = ''; - req.on('data', (chunk) => (body += chunk)); - req.on('end', () => resolve(body)); - }), - raw: req, - }); - res.writeHead(init.status, init.statusText, init.headers).end(body); - } catch (err) { - res.writeHead(500).end(err.message); - } -}); +app.all('/graphql', createHandler({ schema })); -app.listen(4000); +app.listen({ port: 4000 }); console.log('Listening to port 4000'); ``` @@ -179,30 +127,15 @@ console.log('Listening to port 4000'); ```js import Fastify from 'fastify'; // yarn add fastify -import { createHandler } from 'graphql-http'; +import { createHandler } from 'graphql-http/lib/use/fastify'; import { schema } from './previous-step'; -// Create the GraphQL over HTTP handler -const handler = createHandler({ schema }); - // Create a fastify instance serving all methods on `/graphql` +// where the GraphQL over HTTP fastify request handler is. const fastify = Fastify(); -fastify.all('/graphql', async (req, res) => { - try { - const [body, init] = await handler({ - url: req.url, - method: req.method, - headers: req.headers, - body: req.body, // fastify reads the body for you - raw: req, - }); - res.writeHead(init.status, init.statusText, init.headers).end(body); - } catch (err) { - res.writeHead(500).end(err.message); - } -}); +fastify.all('/graphql', createHandler({ schema })); -fastify.listen(4000); +fastify.listen({ port: 4000 }); console.log('Listening to port 4000'); ``` diff --git a/package.json b/package.json index 375d06b6..0d122007 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,21 @@ "types": "./lib/index.d.ts", "browser": "./umd/graphql-http.js" }, + "./lib/use/node": { + "types": "./lib/use/node.d.ts", + "require": "./lib/use/node.js", + "import": "./lib/use/node.mjs" + }, + "./lib/use/express": { + "types": "./lib/use/express.d.ts", + "require": "./lib/use/express.js", + "import": "./lib/use/express.mjs" + }, + "./lib/use/fastify": { + "types": "./lib/use/fastify.d.ts", + "require": "./lib/use/fastify.js", + "import": "./lib/use/fastify.mjs" + }, "./package.json": "./package.json" }, "types": "lib/index.d.ts", @@ -78,6 +93,7 @@ "@rollup/plugin-typescript": "^8.4.0", "@semantic-release/changelog": "^6.0.1", "@semantic-release/git": "^10.0.1", + "@types/express": "^4.17.14", "@types/jest": "^29.0.0", "@typescript-eslint/eslint-plugin": "^5.36.1", "@typescript-eslint/parser": "^5.36.1", @@ -85,6 +101,8 @@ "eslint": "^8.23.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-prettier": "^4.2.1", + "express": "^4.18.2", + "fastify": "^4.9.2", "graphql": "^16.6.0", "jest": "^29.0.1", "jest-jasmine2": "^29.0.1", diff --git a/src/use/express.ts b/src/use/express.ts new file mode 100644 index 00000000..3a608391 --- /dev/null +++ b/src/use/express.ts @@ -0,0 +1,60 @@ +import type { Request, Handler } from 'express'; +import { + createHandler as createRawHandler, + HandlerOptions, + OperationContext, +} from '../handler'; + +export function createHandler( + options: HandlerOptions, +): Handler { + const isProd = process.env.NODE_ENV === 'production'; + const handle = createRawHandler(options); + return async function requestListener(req, res) { + try { + const [body, init] = await handle({ + url: req.url, + method: req.method, + headers: req.headers, + body: () => { + if (req.body) { + // in case express has a body parser + return req.body; + } + new Promise((resolve) => { + let body = ''; + req.on('data', (chunk) => (body += chunk)); + req.on('end', () => resolve(body)); + }); + }, + raw: req, + context: undefined, + }); + res.writeHead(init.status, init.statusText, init.headers).end(body); + } catch (err) { + // The handler shouldnt throw errors. + // If you wish to handle them differently, consider implementing your own request handler. + console.error( + 'Internal error occurred during request handling. ' + + 'Please check your implementation.', + err, + ); + if (isProd) { + res.writeHead(500).end(); + } else { + res + .writeHead(500, { 'content-type': 'application/json; charset=utf-8' }) + .end({ + errors: [ + err instanceof Error + ? { + message: err.message, + stack: err.stack, + } + : JSON.stringify(err), + ], + }); + } + } + }; +} diff --git a/src/use/fastify.ts b/src/use/fastify.ts new file mode 100644 index 00000000..4fe0a495 --- /dev/null +++ b/src/use/fastify.ts @@ -0,0 +1,55 @@ +import type { FastifyRequest, RouteHandler } from 'fastify'; +import { + createHandler as createRawHandler, + HandlerOptions, + OperationContext, +} from '../handler'; + +export function createHandler( + options: HandlerOptions, +): RouteHandler { + const isProd = process.env.NODE_ENV === 'production'; + const handle = createRawHandler(options); + return async function requestListener(req, reply) { + try { + const [body, init] = await handle({ + url: req.url, + method: req.method, + headers: req.headers, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + body: req.body as any, + raw: req, + context: undefined, + }); + reply + .status(init.status) + .headers(init.headers || {}) + .send(body); + } catch (err) { + // The handler shouldnt throw errors. + // If you wish to handle them differently, consider implementing your own request handler. + console.error( + 'Internal error occurred during request handling. ' + + 'Please check your implementation.', + err, + ); + if (isProd) { + reply.status(500).send(); + } else { + reply + .status(500) + .header('content-type', 'application/json; charset=utf-8') + .send({ + errors: [ + err instanceof Error + ? { + message: err.message, + stack: err.stack, + } + : err, + ], + }); + } + } + }; +} diff --git a/src/use/node.ts b/src/use/node.ts new file mode 100644 index 00000000..ab855a2c --- /dev/null +++ b/src/use/node.ts @@ -0,0 +1,61 @@ +import type { IncomingMessage, RequestListener } from 'http'; +import { + createHandler as createRawHandler, + HandlerOptions, + OperationContext, +} from '../handler'; + +export function createHandler( + options: HandlerOptions, +): RequestListener { + const isProd = process.env.NODE_ENV === 'production'; + const handle = createRawHandler(options); + return async function requestListener(req, res) { + try { + if (!req.url) { + throw new Error('Missing request URL'); + } + if (!req.method) { + throw new Error('Missing request method'); + } + const [body, init] = await handle({ + url: req.url, + method: req.method, + headers: req.headers, + body: () => + new Promise((resolve) => { + let body = ''; + req.on('data', (chunk) => (body += chunk)); + req.on('end', () => resolve(body)); + }), + raw: req, + context: undefined, + }); + res.writeHead(init.status, init.statusText, init.headers).end(body); + } catch (err) { + // The handler shouldnt throw errors. + // If you wish to handle them differently, consider implementing your own request handler. + console.error( + 'Internal error occurred during request handling. ' + + 'Please check your implementation.', + err, + ); + if (isProd) { + res.writeHead(500).end(); + } else { + res + .writeHead(500, { 'content-type': 'application/json; charset=utf-8' }) + .end({ + errors: [ + err instanceof Error + ? { + message: err.message, + stack: err.stack, + } + : JSON.stringify(err), + ], + }); + } + } + }; +} diff --git a/yarn.lock b/yarn.lock index 8a536eee..1edf6389 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1885,6 +1885,17 @@ __metadata: languageName: node linkType: hard +"@fastify/ajv-compiler@npm:^3.3.1": + version: 3.4.0 + resolution: "@fastify/ajv-compiler@npm:3.4.0" + dependencies: + ajv: ^8.11.0 + ajv-formats: ^2.1.1 + fast-uri: ^2.0.0 + checksum: 3e03f9673f0f13ce343bfb4a84f4e908d12bd775a2b82ff4bdf09ac062d09c6b89b62df7f96fab970dd61f77a9e43be2908eb28cd59e27654b25931444bde825 + languageName: node + linkType: hard + "@fastify/deepmerge@npm:^1.0.0": version: 1.1.0 resolution: "@fastify/deepmerge@npm:1.1.0" @@ -1899,7 +1910,7 @@ __metadata: languageName: node linkType: hard -"@fastify/fast-json-stringify-compiler@npm:^4.0.0": +"@fastify/fast-json-stringify-compiler@npm:^4.0.0, @fastify/fast-json-stringify-compiler@npm:^4.1.0": version: 4.1.0 resolution: "@fastify/fast-json-stringify-compiler@npm:4.1.0" dependencies: @@ -4173,6 +4184,18 @@ __metadata: languageName: node linkType: hard +"@types/express@npm:^4.17.14": + version: 4.17.14 + resolution: "@types/express@npm:4.17.14" + dependencies: + "@types/body-parser": "*" + "@types/express-serve-static-core": ^4.17.18 + "@types/qs": "*" + "@types/serve-static": "*" + checksum: 15c1af46d02de834e4a225eccaa9d85c0370fdbb3ed4e1bc2d323d24872309961542b993ae236335aeb3e278630224a6ea002078d39e651d78a3b0356b1eaa79 + languageName: node + linkType: hard + "@types/graceful-fs@npm:^4.1.3": version: 4.1.5 resolution: "@types/graceful-fs@npm:4.1.5" @@ -4632,7 +4655,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:8.11.0, ajv@npm:^8.0.0, ajv@npm:^8.10.0": +"ajv@npm:8.11.0, ajv@npm:^8.0.0, ajv@npm:^8.10.0, ajv@npm:^8.11.0": version: 8.11.0 resolution: "ajv@npm:8.11.0" dependencies: @@ -4992,7 +5015,7 @@ __metadata: languageName: node linkType: hard -"avvio@npm:^8.1.3": +"avvio@npm:^8.1.3, avvio@npm:^8.2.0": version: 8.2.0 resolution: "avvio@npm:8.2.0" dependencies: @@ -5230,6 +5253,26 @@ __metadata: languageName: node linkType: hard +"body-parser@npm:1.20.1": + version: 1.20.1 + resolution: "body-parser@npm:1.20.1" + dependencies: + bytes: 3.1.2 + content-type: ~1.0.4 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.11.0 + raw-body: 2.5.1 + type-is: ~1.6.18 + unpipe: 1.0.0 + checksum: f1050dbac3bede6a78f0b87947a8d548ce43f91ccc718a50dd774f3c81f2d8b04693e52acf62659fad23101827dd318da1fb1363444ff9a8482b886a3e4a5266 + languageName: node + linkType: hard + "bottleneck@npm:^2.18.1": version: 2.19.5 resolution: "bottleneck@npm:2.19.5" @@ -6639,6 +6682,45 @@ __metadata: languageName: node linkType: hard +"express@npm:^4.18.2": + version: 4.18.2 + resolution: "express@npm:4.18.2" + dependencies: + accepts: ~1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.1 + content-disposition: 0.5.4 + content-type: ~1.0.4 + cookie: 0.5.0 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: ~1.0.2 + escape-html: ~1.0.3 + etag: ~1.8.1 + finalhandler: 1.2.0 + fresh: 0.5.2 + http-errors: 2.0.0 + merge-descriptors: 1.0.1 + methods: ~1.1.2 + on-finished: 2.4.1 + parseurl: ~1.3.3 + path-to-regexp: 0.1.7 + proxy-addr: ~2.0.7 + qs: 6.11.0 + range-parser: ~1.2.1 + safe-buffer: 5.2.1 + send: 0.18.0 + serve-static: 1.15.0 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: ~1.6.18 + utils-merge: 1.0.1 + vary: ~1.1.2 + checksum: 3c4b9b076879442f6b968fe53d85d9f1eeacbb4f4c41e5f16cc36d77ce39a2b0d81b3f250514982110d815b2f7173f5561367f9110fcc541f9371948e8c8b037 + languageName: node + linkType: hard + "extract-files@npm:^11.0.0": version: 11.0.0 resolution: "extract-files@npm:11.0.0" @@ -6646,6 +6728,13 @@ __metadata: languageName: node linkType: hard +"fast-decode-uri-component@npm:^1.0.1": + version: 1.0.1 + resolution: "fast-decode-uri-component@npm:1.0.1" + checksum: 427a48fe0907e76f0e9a2c228e253b4d8a8ab21d130ee9e4bb8339c5ba4086235cf9576831f7b20955a752eae4b525a177ff9d5825dd8d416e7726939194fbee + languageName: node + linkType: hard + "fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": version: 3.1.3 resolution: "fast-deep-equal@npm:3.1.3" @@ -6712,6 +6801,15 @@ __metadata: languageName: node linkType: hard +"fast-querystring@npm:^1.0.0": + version: 1.0.0 + resolution: "fast-querystring@npm:1.0.0" + dependencies: + fast-decode-uri-component: ^1.0.1 + checksum: 5f70df27d02fcf86ea2baa16ea59e0da8bbd891e3a97aa1e95b1c0c64d5445aeab3bde5ce3e603b21d48c87db70a458febf05150a9dbe7c099aced5f123b3ffd + languageName: node + linkType: hard + "fast-redact@npm:^3.1.1": version: 3.1.2 resolution: "fast-redact@npm:3.1.2" @@ -6762,6 +6860,28 @@ __metadata: languageName: node linkType: hard +"fastify@npm:^4.9.2": + version: 4.9.2 + resolution: "fastify@npm:4.9.2" + dependencies: + "@fastify/ajv-compiler": ^3.3.1 + "@fastify/error": ^3.0.0 + "@fastify/fast-json-stringify-compiler": ^4.1.0 + abstract-logging: ^2.0.1 + avvio: ^8.2.0 + find-my-way: ^7.3.0 + light-my-request: ^5.6.1 + pino: ^8.5.0 + process-warning: ^2.0.0 + proxy-addr: ^2.0.7 + rfdc: ^1.3.0 + secure-json-parse: ^2.5.0 + semver: ^7.3.7 + tiny-lru: ^9.0.2 + checksum: 78d931a569b8ef30ceade1c3f6c820ad36eebb4caf1551e4c3f181fd4750309c4912e3db85fbeeb1cabf087454d7cc1568f2614e17aef44f69461268968c87a3 + languageName: node + linkType: hard + "fastparallel@npm:^2.3.0": version: 2.4.1 resolution: "fastparallel@npm:2.4.1" @@ -6893,6 +7013,17 @@ __metadata: languageName: node linkType: hard +"find-my-way@npm:^7.3.0": + version: 7.3.1 + resolution: "find-my-way@npm:7.3.1" + dependencies: + fast-deep-equal: ^3.1.3 + fast-querystring: ^1.0.0 + safe-regex2: ^2.0.0 + checksum: eec65665c34fbfeb323a52989de51b106485ec0d6182996fc70d42570a73f88b9637572bb8ae89332532da9ca856615e195768116aeede75d73b929b9534bf7a + languageName: node + linkType: hard + "find-up@npm:^2.0.0": version: 2.1.0 resolution: "find-up@npm:2.1.0" @@ -7316,6 +7447,7 @@ __metadata: "@rollup/plugin-typescript": ^8.4.0 "@semantic-release/changelog": ^6.0.1 "@semantic-release/git": ^10.0.1 + "@types/express": ^4.17.14 "@types/jest": ^29.0.0 "@typescript-eslint/eslint-plugin": ^5.36.1 "@typescript-eslint/parser": ^5.36.1 @@ -7323,6 +7455,8 @@ __metadata: eslint: ^8.23.0 eslint-config-prettier: ^8.5.0 eslint-plugin-prettier: ^4.2.1 + express: ^4.18.2 + fastify: ^4.9.2 graphql: ^16.6.0 jest: ^29.0.1 jest-jasmine2: ^29.0.1 @@ -8982,6 +9116,17 @@ __metadata: languageName: node linkType: hard +"light-my-request@npm:^5.6.1": + version: 5.6.1 + resolution: "light-my-request@npm:5.6.1" + dependencies: + cookie: ^0.5.0 + process-warning: ^2.0.0 + set-cookie-parser: ^2.4.1 + checksum: c527702045c23150a2805b2f3a421e398bfa660733aaefb5e110e568bb4d323ff6a46212623cba43c000421c36ac6da5f5a37029603a60d6db68fbd25551bc8b + languageName: node + linkType: hard + "lines-and-columns@npm:^1.1.6": version: 1.2.4 resolution: "lines-and-columns@npm:1.2.4" @@ -10571,6 +10716,27 @@ __metadata: languageName: node linkType: hard +"pino@npm:^8.5.0": + version: 8.7.0 + resolution: "pino@npm:8.7.0" + dependencies: + atomic-sleep: ^1.0.0 + fast-redact: ^3.1.1 + on-exit-leak-free: ^2.1.0 + pino-abstract-transport: v1.0.0 + pino-std-serializers: ^6.0.0 + process-warning: ^2.0.0 + quick-format-unescaped: ^4.0.3 + real-require: ^0.2.0 + safe-stable-stringify: ^2.3.1 + sonic-boom: ^3.1.0 + thread-stream: ^2.0.0 + bin: + pino: bin.js + checksum: 4aa2e320aa88f4a90fd25884ee4e3b9ef7963b3c59c514f3693b5a5c987b112cf3ab4e39a8c51efe32c861f5c058d7cfa7fcda59d964ed878f842fdbc6ab2876 + languageName: node + linkType: hard + "pirates@npm:^4.0.4": version: 4.0.5 resolution: "pirates@npm:4.0.5" @@ -10790,6 +10956,15 @@ __metadata: languageName: node linkType: hard +"qs@npm:6.11.0": + version: 6.11.0 + resolution: "qs@npm:6.11.0" + dependencies: + side-channel: ^1.0.4 + checksum: 6e1f29dd5385f7488ec74ac7b6c92f4d09a90408882d0c208414a34dd33badc1a621019d4c799a3df15ab9b1d0292f97c1dd71dc7c045e69f81a8064e5af7297 + languageName: node + linkType: hard + "queue-microtask@npm:^1.2.2": version: 1.2.3 resolution: "queue-microtask@npm:1.2.3" @@ -11312,7 +11487,7 @@ __metadata: languageName: node linkType: hard -"secure-json-parse@npm:^2.4.0": +"secure-json-parse@npm:^2.4.0, secure-json-parse@npm:^2.5.0": version: 2.5.0 resolution: "secure-json-parse@npm:2.5.0" checksum: 84147a32615ce0d93d2fbba60cde85ae362f45cc948ea134e4d6d1e678bb4b7f3a5ce9b9692ed052baefeb2e1c8ba183b34920390e6a089925b97b0d8f7ab064 @@ -12094,6 +12269,13 @@ __metadata: languageName: node linkType: hard +"tiny-lru@npm:^9.0.2": + version: 9.0.3 + resolution: "tiny-lru@npm:9.0.3" + checksum: 8ded11a875d622a2d4c3ee2d33331ddfaa80f86b5b280bad17810ecbfd94d774c1726948f91031527bb03e2fd38c4385bd4db27fd5614b3c59fe3fa7d3359451 + languageName: node + linkType: hard + "tiny-relative-date@npm:^1.3.0": version: 1.3.0 resolution: "tiny-relative-date@npm:1.3.0" From dbae18d9a926d38f9eb88d6aebdb1cb98bcf6217 Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Sat, 5 Nov 2022 14:42:20 +0100 Subject: [PATCH 02/19] use fetch and bun example --- README.md | 44 ++++++++++++++++++++++++++++---------------- package.json | 5 +++++ src/use/fetch.ts | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 16 deletions(-) create mode 100644 src/use/fetch.ts diff --git a/README.md b/README.md index 1812c2ec..eba39b68 100644 --- a/README.md +++ b/README.md @@ -143,35 +143,47 @@ console.log('Listening to port 4000'); ```ts import { serve } from 'https://deno.land/std@0.151.0/http/server.ts'; -import { createHandler } from 'https://esm.sh/graphql-http'; +import { createHandler } from 'https://esm.sh/graphql-http/lib/use/fetch'; import { schema } from './previous-step'; -// Create the GraphQL over HTTP handler -const handler = createHandler({ schema }); +// Create the GraphQL over HTTP native fetch handler +const handler = createHandler({ schema }); // Start serving on `/graphql` using the handler await serve( - async (req: Request) => { - const [path, _search] = req.url.split('?'); + (req: Request) => { if (!path.endsWith('/graphql')) { + return handler(req); + } else { return new Response(null, { status: 404, statusText: 'Not Found' }); } - - const [body, init] = await handler({ - url: req.url, - method: req.method, - headers: req.headers, - body: () => req.text(), - raw: req, - }); - return new Response(body, init); }, { - port: 4000, + port: 4000, // Listening to port 4000 }, ); +``` + +##### With [`Bun`](https://bun.sh/) -// Listening to port 4000 +```ts +import { createHandler } from 'https://esm.sh/graphql-http/lib/use/fetch'; +import { schema } from './previous-step'; + +// Create the GraphQL over HTTP native fetch handler +const handler = createHandler({ schema }); + +// Start serving on `/graphql` using the handler +export default { + port: 4000, // Listening to port 4000 + fetch(req) { + if (!path.endsWith('/graphql')) { + return handler(req); + } else { + return new Response(null, { status: 404, statusText: 'Not Found' }); + } + }, +}; ``` #### Use the client diff --git a/package.json b/package.json index 0d122007..1a209b53 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,11 @@ "types": "./lib/index.d.ts", "browser": "./umd/graphql-http.js" }, + "./lib/use/fetch": { + "types": "./lib/use/fetch.d.ts", + "require": "./lib/use/fetch.js", + "import": "./lib/use/fetch.mjs" + }, "./lib/use/node": { "types": "./lib/use/node.d.ts", "require": "./lib/use/node.js", diff --git a/src/use/fetch.ts b/src/use/fetch.ts new file mode 100644 index 00000000..7b544487 --- /dev/null +++ b/src/use/fetch.ts @@ -0,0 +1,35 @@ +import { + createHandler as createRawHandler, + HandlerOptions, + OperationContext, +} from '../handler'; + +export interface FetchAPI { + Response: typeof Response; + ReadableStream: typeof ReadableStream; + TextEncoder: typeof TextEncoder; +} + +export function createHandler( + options: HandlerOptions, + fetchApi: Partial = {}, +): (req: Request) => Promise { + const api: FetchAPI = { + Response: fetchApi.Response || Response, + TextEncoder: fetchApi.TextEncoder || TextEncoder, + ReadableStream: fetchApi.ReadableStream || ReadableStream, + }; + + const handler = createRawHandler(options); + return async function handleRequest(req) { + const [body, init] = await handler({ + method: req.method, + url: req.url, + headers: req.headers, + body: () => req.text(), + raw: req, + context: api, + }); + return new api.Response(body, init); + }; +} From 1d499b94f886694e34f1ca8909cbffe202caa93b Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Sat, 5 Nov 2022 14:52:49 +0100 Subject: [PATCH 03/19] handle handler errors in fetch --- src/use/fetch.ts | 52 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/src/use/fetch.ts b/src/use/fetch.ts index 7b544487..0d9d21b4 100644 --- a/src/use/fetch.ts +++ b/src/use/fetch.ts @@ -14,22 +14,54 @@ export function createHandler( options: HandlerOptions, fetchApi: Partial = {}, ): (req: Request) => Promise { + const isProd = process.env.NODE_ENV === 'production'; const api: FetchAPI = { Response: fetchApi.Response || Response, TextEncoder: fetchApi.TextEncoder || TextEncoder, ReadableStream: fetchApi.ReadableStream || ReadableStream, }; - const handler = createRawHandler(options); return async function handleRequest(req) { - const [body, init] = await handler({ - method: req.method, - url: req.url, - headers: req.headers, - body: () => req.text(), - raw: req, - context: api, - }); - return new api.Response(body, init); + try { + const [body, init] = await handler({ + method: req.method, + url: req.url, + headers: req.headers, + body: () => req.text(), + raw: req, + context: api, + }); + return new api.Response(body, init); + } catch (err) { + // The handler shouldnt throw errors. + // If you wish to handle them differently, consider implementing your own request handler. + console.error( + 'Internal error occurred during request handling. ' + + 'Please check your implementation.', + err, + ); + if (isProd) { + return new api.Response(null, { status: 500 }); + } else { + return new api.Response( + JSON.stringify({ + errors: [ + err instanceof Error + ? { + message: err.message, + stack: err.stack, + } + : err, + ], + }), + { + status: 500, + headers: { + 'content-type': 'application/json; charset=utf-8', + }, + }, + ); + } + } }; } From 18da9bb6561ab767103751b7e31ab8fcd0ea8c20 Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Sat, 5 Nov 2022 14:52:57 +0100 Subject: [PATCH 04/19] stringify whole body --- src/use/express.ts | 22 ++++++++++++---------- src/use/node.ts | 22 ++++++++++++---------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/use/express.ts b/src/use/express.ts index 3a608391..98fd2837 100644 --- a/src/use/express.ts +++ b/src/use/express.ts @@ -44,16 +44,18 @@ export function createHandler( } else { res .writeHead(500, { 'content-type': 'application/json; charset=utf-8' }) - .end({ - errors: [ - err instanceof Error - ? { - message: err.message, - stack: err.stack, - } - : JSON.stringify(err), - ], - }); + .end( + JSON.stringify({ + errors: [ + err instanceof Error + ? { + message: err.message, + stack: err.stack, + } + : err, + ], + }), + ); } } }; diff --git a/src/use/node.ts b/src/use/node.ts index ab855a2c..6dbaf79c 100644 --- a/src/use/node.ts +++ b/src/use/node.ts @@ -45,16 +45,18 @@ export function createHandler( } else { res .writeHead(500, { 'content-type': 'application/json; charset=utf-8' }) - .end({ - errors: [ - err instanceof Error - ? { - message: err.message, - stack: err.stack, - } - : JSON.stringify(err), - ], - }); + .end( + JSON.stringify({ + errors: [ + err instanceof Error + ? { + message: err.message, + stack: err.stack, + } + : err, + ], + }), + ); } } }; From 39852268acbce53df42684495818e688feb378db Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Sat, 5 Nov 2022 14:55:18 +0100 Subject: [PATCH 05/19] bun example is js --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eba39b68..307268cc 100644 --- a/README.md +++ b/README.md @@ -166,7 +166,7 @@ await serve( ##### With [`Bun`](https://bun.sh/) -```ts +```js import { createHandler } from 'https://esm.sh/graphql-http/lib/use/fetch'; import { schema } from './previous-step'; From e536ea7d46bd8ee50ce3cbba2e635683d6942aee Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Sat, 5 Nov 2022 14:56:17 +0100 Subject: [PATCH 06/19] unnecessary status text --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 307268cc..0792376e 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,7 @@ await serve( if (!path.endsWith('/graphql')) { return handler(req); } else { - return new Response(null, { status: 404, statusText: 'Not Found' }); + return new Response(null, { status: 404 }); } }, { @@ -180,7 +180,7 @@ export default { if (!path.endsWith('/graphql')) { return handler(req); } else { - return new Response(null, { status: 404, statusText: 'Not Found' }); + return new Response(null, { status: 404 }); } }, }; From 99a1336b58bf77ebff1197d60e5fedb922bcf3f4 Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Sat, 5 Nov 2022 14:56:48 +0100 Subject: [PATCH 07/19] serve on /graphql route --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0792376e..17a0076f 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ const handler = createHandler({ schema }); // Create a HTTP server using the listner on `/graphql` const server = http.createServer((req, res) => { - if (!req.url.startsWith('/graphql')) { + if (req.url.startsWith('/graphql')) { handler(req, res); } else { res.writeHead(404).end(); @@ -152,7 +152,7 @@ const handler = createHandler({ schema }); // Start serving on `/graphql` using the handler await serve( (req: Request) => { - if (!path.endsWith('/graphql')) { + if (path.endsWith('/graphql')) { return handler(req); } else { return new Response(null, { status: 404 }); @@ -177,7 +177,7 @@ const handler = createHandler({ schema }); export default { port: 4000, // Listening to port 4000 fetch(req) { - if (!path.endsWith('/graphql')) { + if (path.endsWith('/graphql')) { return handler(req); } else { return new Response(null, { status: 404 }); From 26973e47f981f4e608c2bf259e10ef09d7f660ef Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Sat, 5 Nov 2022 14:57:24 +0100 Subject: [PATCH 08/19] no dot --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 17a0076f..8a6ca6e2 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ import { createHandler } from 'graphql-http/lib/use/express'; import { schema } from './previous-step'; // Create a express instance serving all methods on `/graphql` -// where the GraphQL over HTTP express request handler is. +// where the GraphQL over HTTP express request handler is const app = express(); app.all('/graphql', createHandler({ schema })); @@ -131,7 +131,7 @@ import { createHandler } from 'graphql-http/lib/use/fastify'; import { schema } from './previous-step'; // Create a fastify instance serving all methods on `/graphql` -// where the GraphQL over HTTP fastify request handler is. +// where the GraphQL over HTTP fastify request handler is const fastify = Fastify(); fastify.all('/graphql', createHandler({ schema })); From 6b609ca4c886ab9acd1b33cd09eedd4a33616242 Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Sat, 5 Nov 2022 14:59:17 +0100 Subject: [PATCH 09/19] bun client recipe --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 8a6ca6e2..8c1be19b 100644 --- a/README.md +++ b/README.md @@ -509,6 +509,21 @@ const client = createClient({ +
+🔗 Client usage in Bun + +```js +import { createClient } from 'graphql-http'; + +const client = createClient({ + url: 'http://bun.bread:4000/graphql', +}); + +// consider other recipes for usage inspiration +``` + +
+
🔗 Server handler usage with authentication From 641f3e3cd24d3a659fe647f2bcdfb2392fb4d72e Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Sat, 5 Nov 2022 15:01:32 +0100 Subject: [PATCH 10/19] bun and deno pkg resolution --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8c1be19b..197dfb15 100644 --- a/README.md +++ b/README.md @@ -167,7 +167,7 @@ await serve( ##### With [`Bun`](https://bun.sh/) ```js -import { createHandler } from 'https://esm.sh/graphql-http/lib/use/fetch'; +import { createHandler } from 'graphql-http/lib/use/fetch'; // bun install graphql-http import { schema } from './previous-step'; // Create the GraphQL over HTTP native fetch handler @@ -498,7 +498,7 @@ const client = createClient({ 🔗 Client usage in Deno ```js -import { createClient } from 'graphql-http'; +import { createClient } from 'https://esm.sh/graphql-http'; const client = createClient({ url: 'http://deno.earth:4000/graphql', @@ -513,7 +513,7 @@ const client = createClient({ 🔗 Client usage in Bun ```js -import { createClient } from 'graphql-http'; +import { createClient } from 'graphql-http'; // bun install graphql-http const client = createClient({ url: 'http://bun.bread:4000/graphql', From c04c1f878540de6dfe0485fb704be8aa6f3bf333 Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Sun, 6 Nov 2022 13:58:02 +0100 Subject: [PATCH 11/19] docs --- docs/README.md | 372 +----------------- ...NetworkError.md => client.NetworkError.md} | 12 +- docs/interfaces/Audit.md | 34 -- docs/interfaces/ResponseInit.md | 32 -- docs/interfaces/audits_common.Audit.md | 36 ++ ...uditFail.md => audits_common.AuditFail.md} | 10 +- .../{AuditOk.md => audits_common.AuditOk.md} | 8 +- ...md => audits_server.ServerAuditOptions.md} | 8 +- .../{Client.md => client.Client.md} | 12 +- ...ientOptions.md => client.ClientOptions.md} | 26 +- ...ResponseLike.md => client.ResponseLike.md} | 10 +- ...questParams.md => common.RequestParams.md} | 12 +- docs/interfaces/{Sink.md => common.Sink.md} | 10 +- ...erOptions.md => handler.HandlerOptions.md} | 46 +-- .../{Request.md => handler.Request.md} | 18 +- docs/interfaces/handler.ResponseInit.md | 34 ++ docs/interfaces/use_fetch.FetchAPI.md | 33 ++ docs/modules/audits_common.md | 41 ++ docs/modules/audits_server.md | 52 +++ docs/modules/client.md | 55 +++ docs/modules/common.md | 10 + docs/modules/handler.md | 267 +++++++++++++ docs/modules/use_express.md | 46 +++ docs/modules/use_fastify.md | 46 +++ docs/modules/use_fetch.md | 71 ++++ docs/modules/use_node.md | 45 +++ src/use/express.ts | 18 + src/use/fastify.ts | 18 + src/use/fetch.ts | 31 ++ src/use/node.ts | 17 + typedoc.js | 1 + 31 files changed, 929 insertions(+), 502 deletions(-) rename docs/classes/{NetworkError.md => client.NetworkError.md} (66%) delete mode 100644 docs/interfaces/Audit.md delete mode 100644 docs/interfaces/ResponseInit.md create mode 100644 docs/interfaces/audits_common.Audit.md rename docs/interfaces/{AuditFail.md => audits_common.AuditFail.md} (64%) rename docs/interfaces/{AuditOk.md => audits_common.AuditOk.md} (52%) rename docs/interfaces/{ServerAuditOptions.md => audits_server.ServerAuditOptions.md} (64%) rename docs/interfaces/{Client.md => client.Client.md} (75%) rename docs/interfaces/{ClientOptions.md => client.ClientOptions.md} (85%) rename docs/interfaces/{ResponseLike.md => client.ResponseLike.md} (55%) rename docs/interfaces/{RequestParams.md => common.RequestParams.md} (60%) rename docs/interfaces/{Sink.md => common.Sink.md} (77%) rename docs/interfaces/{HandlerOptions.md => handler.HandlerOptions.md} (63%) rename docs/interfaces/{Request.md => handler.Request.md} (72%) create mode 100644 docs/interfaces/handler.ResponseInit.md create mode 100644 docs/interfaces/use_fetch.FetchAPI.md create mode 100644 docs/modules/audits_common.md create mode 100644 docs/modules/audits_server.md create mode 100644 docs/modules/client.md create mode 100644 docs/modules/common.md create mode 100644 docs/modules/handler.md create mode 100644 docs/modules/use_express.md create mode 100644 docs/modules/use_fastify.md create mode 100644 docs/modules/use_fetch.md create mode 100644 docs/modules/use_node.md diff --git a/docs/README.md b/docs/README.md index 4b0aab8a..7b4af6a7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,364 +4,14 @@ graphql-http ## Table of contents -### Classes - -- [NetworkError](classes/NetworkError.md) - -### Interfaces - -- [Audit](interfaces/Audit.md) -- [AuditFail](interfaces/AuditFail.md) -- [AuditOk](interfaces/AuditOk.md) -- [Client](interfaces/Client.md) -- [ClientOptions](interfaces/ClientOptions.md) -- [HandlerOptions](interfaces/HandlerOptions.md) -- [Request](interfaces/Request.md) -- [RequestParams](interfaces/RequestParams.md) -- [ResponseInit](interfaces/ResponseInit.md) -- [ResponseLike](interfaces/ResponseLike.md) -- [ServerAuditOptions](interfaces/ServerAuditOptions.md) -- [Sink](interfaces/Sink.md) - -### Type Aliases - -- [AcceptableMediaType](README.md#acceptablemediatype) -- [AuditName](README.md#auditname) -- [AuditRequirement](README.md#auditrequirement) -- [AuditResult](README.md#auditresult) -- [Handler](README.md#handler) -- [OperationArgs](README.md#operationargs) -- [OperationContext](README.md#operationcontext) -- [RequestHeaders](README.md#requestheaders) -- [Response](README.md#response) -- [ResponseBody](README.md#responsebody) -- [ResponseHeaders](README.md#responseheaders) - -### Functions - -- [auditServer](README.md#auditserver) -- [createClient](README.md#createclient) -- [createHandler](README.md#createhandler) -- [getAcceptableMediaType](README.md#getacceptablemediatype) -- [isResponse](README.md#isresponse) -- [makeResponse](README.md#makeresponse) -- [serverAudits](README.md#serveraudits) - -## Audits - -### AuditName - -Ƭ **AuditName**: \`${AuditRequirement} ${string}\` - -Audit name starting with the audit requirement level. - -___ - -### AuditRequirement - -Ƭ **AuditRequirement**: ``"MUST"`` \| ``"SHOULD"`` \| ``"MAY"`` - -Audit requirement levels as per [RFC2119](https://www.rfc-editor.org/rfc/rfc2119). - -___ - -### AuditResult - -Ƭ **AuditResult**: [`AuditOk`](interfaces/AuditOk.md) \| [`AuditFail`](interfaces/AuditFail.md) - -Result of the performed audit. See `AuditOk` and `AuditFail` for more information. - -___ - -### auditServer - -▸ **auditServer**(`opts`): `Promise`<[`AuditResult`](README.md#auditresult)[]\> - -Performs the full list of server audits required for GraphQL over HTTP spec conformance. - -Please consult the `AuditResult` for more information. - -#### Parameters - -| Name | Type | -| :------ | :------ | -| `opts` | [`ServerAuditOptions`](interfaces/ServerAuditOptions.md) | - -#### Returns - -`Promise`<[`AuditResult`](README.md#auditresult)[]\> - -___ - -### serverAudits - -▸ **serverAudits**(`opts`): [`Audit`](interfaces/Audit.md)[] - -List of server audits required to check GraphQL over HTTP spec conformance. - -#### Parameters - -| Name | Type | -| :------ | :------ | -| `opts` | [`ServerAuditOptions`](interfaces/ServerAuditOptions.md) | - -#### Returns - -[`Audit`](interfaces/Audit.md)[] - -## Client - -### createClient - -▸ **createClient**(`options`): [`Client`](interfaces/Client.md) - -Creates a disposable GraphQL over HTTP client to transmit -GraphQL operation results. - -#### Parameters - -| Name | Type | -| :------ | :------ | -| `options` | [`ClientOptions`](interfaces/ClientOptions.md) | - -#### Returns - -[`Client`](interfaces/Client.md) - -## Server - -### AcceptableMediaType - -Ƭ **AcceptableMediaType**: ``"application/graphql-response+json"`` \| ``"application/json"`` - -Request's Media-Type that the server accepts. - -___ - -### Handler - -Ƭ **Handler**<`RequestRaw`, `RequestContext`\>: (`req`: [`Request`](interfaces/Request.md)<`RequestRaw`, `RequestContext`\>) => `Promise`<[`Response`](README.md#response)\> - -#### Type parameters - -| Name | Type | -| :------ | :------ | -| `RequestRaw` | `unknown` | -| `RequestContext` | `unknown` | - -#### Type declaration - -▸ (`req`): `Promise`<[`Response`](README.md#response)\> - -The ready-to-use handler. Simply plug it in your favourite HTTP framework -and enjoy. - -Errors thrown from **any** of the provided options or callbacks (or even due to -library misuse or potential bugs) will reject the handler's promise. They are -considered internal errors and you should take care of them accordingly. - -##### Parameters - -| Name | Type | -| :------ | :------ | -| `req` | [`Request`](interfaces/Request.md)<`RequestRaw`, `RequestContext`\> | - -##### Returns - -`Promise`<[`Response`](README.md#response)\> - -___ - -### OperationArgs - -Ƭ **OperationArgs**<`Context`\>: `ExecutionArgs` & { `contextValue?`: `Context` } - -#### Type parameters - -| Name | Type | -| :------ | :------ | -| `Context` | extends [`OperationContext`](README.md#operationcontext) = `undefined` | - -___ - -### OperationContext - -Ƭ **OperationContext**: `Record`<`PropertyKey`, `unknown`\> \| `symbol` \| `number` \| `string` \| `boolean` \| `undefined` \| ``null`` - -A concrete GraphQL execution context value type. - -Mainly used because TypeScript collapes unions -with `any` or `unknown` to `any` or `unknown`. So, -we use a custom type to allow definitions such as -the `context` server option. - -___ - -### RequestHeaders - -Ƭ **RequestHeaders**: { `[key: string]`: `string` \| `string`[] \| `undefined`; `set-cookie?`: `string` \| `string`[] } \| { `get`: (`key`: `string`) => `string` \| ``null`` } - -The incoming request headers the implementing server should provide. - -___ - -### Response - -Ƭ **Response**: readonly [body: ResponseBody \| null, init: ResponseInit] - -Server agnostic response returned from `graphql-http` containing the -body and init options needing to be coerced to the server implementation in use. - -___ - -### ResponseBody - -Ƭ **ResponseBody**: `string` - -Server agnostic response body returned from `graphql-http` needing -to be coerced to the server implementation in use. - -___ - -### ResponseHeaders - -Ƭ **ResponseHeaders**: { `accept?`: `string` ; `allow?`: `string` ; `content-type?`: `string` } & `Record`<`string`, `string`\> - -The response headers that get returned from graphql-http. - -___ - -### createHandler - -▸ **createHandler**<`RequestRaw`, `RequestContext`, `Context`\>(`options`): [`Handler`](README.md#handler)<`RequestRaw`, `RequestContext`\> - -Makes a GraphQL over HTTP Protocol compliant server handler. The handler can -be used with your favourite server library. - -Beware that the handler resolves only after the whole operation completes. - -Errors thrown from **any** of the provided options or callbacks (or even due to -library misuse or potential bugs) will reject the handler's promise. They are -considered internal errors and you should take care of them accordingly. - -For production environments, its recommended not to transmit the exact internal -error details to the client, but instead report to an error logging tool or simply -the console. - -Simple example usage with Node: - -```js -import http from 'http'; -import { createHandler } from 'graphql-http'; -import { schema } from './my-graphql-schema'; - -// Create the GraphQL over HTTP handler -const handler = createHandler({ schema }); - -// Create a HTTP server using the handler on `/graphql` -const server = http.createServer(async (req, res) => { - if (!req.url.startsWith('/graphql')) { - return res.writeHead(404).end(); - } - - try { - const [body, init] = await handler({ - url: req.url, - method: req.method, - headers: req.headers, - body: () => new Promise((resolve) => { - let body = ''; - req.on('data', (chunk) => (body += chunk)); - req.on('end', () => resolve(body)); - }), - raw: req, - }); - res.writeHead(init.status, init.statusText, init.headers).end(body); - } catch (err) { - // BEWARE not to transmit the exact internal error message in production environments - res.writeHead(500).end(err.message); - } -}); - -server.listen(4000); -console.log('Listening to port 4000'); -``` - -#### Type parameters - -| Name | Type | -| :------ | :------ | -| `RequestRaw` | `unknown` | -| `RequestContext` | `unknown` | -| `Context` | extends [`OperationContext`](README.md#operationcontext) = `undefined` | - -#### Parameters - -| Name | Type | -| :------ | :------ | -| `options` | [`HandlerOptions`](interfaces/HandlerOptions.md)<`RequestRaw`, `RequestContext`, `Context`\> | - -#### Returns - -[`Handler`](README.md#handler)<`RequestRaw`, `RequestContext`\> - -___ - -### getAcceptableMediaType - -▸ **getAcceptableMediaType**(`acceptHeader`): [`AcceptableMediaType`](README.md#acceptablemediatype) \| ``null`` - -Inspects the request and detects the appropriate/acceptable Media-Type -looking at the `Accept` header while complying with the GraphQL over HTTP Protocol. - -#### Parameters - -| Name | Type | -| :------ | :------ | -| `acceptHeader` | `undefined` \| ``null`` \| `string` | - -#### Returns - -[`AcceptableMediaType`](README.md#acceptablemediatype) \| ``null`` - -___ - -### isResponse - -▸ **isResponse**(`val`): val is Response - -Checks whether the passed value is the `graphql-http` server agnostic response. - -#### Parameters - -| Name | Type | -| :------ | :------ | -| `val` | `unknown` | - -#### Returns - -val is Response - -___ - -### makeResponse - -▸ **makeResponse**(`resultOrErrors`, `acceptedMediaType`): [`Response`](README.md#response) - -Creates an appropriate GraphQL over HTTP response following the provided arguments. - -If the first argument is an `ExecutionResult`, the operation will be treated as "successful". - -If the first argument is _any_ object without the `data` field, it will be treated as an error (as per the spec) -and the response will be constructed with the help of `acceptedMediaType` complying with the GraphQL over HTTP Protocol. - -#### Parameters - -| Name | Type | -| :------ | :------ | -| `resultOrErrors` | readonly `GraphQLError`[] \| `Readonly`<`ExecutionResult`<`ObjMap`<`unknown`\>, `ObjMap`<`unknown`\>\>\> \| `Readonly`<`GraphQLError`\> | -| `acceptedMediaType` | [`AcceptableMediaType`](README.md#acceptablemediatype) | - -#### Returns - -[`Response`](README.md#response) +### Modules + +- [audits/common](modules/audits_common.md) +- [audits/server](modules/audits_server.md) +- [client](modules/client.md) +- [common](modules/common.md) +- [handler](modules/handler.md) +- [use/express](modules/use_express.md) +- [use/fastify](modules/use_fastify.md) +- [use/fetch](modules/use_fetch.md) +- [use/node](modules/use_node.md) diff --git a/docs/classes/NetworkError.md b/docs/classes/client.NetworkError.md similarity index 66% rename from docs/classes/NetworkError.md rename to docs/classes/client.NetworkError.md index 9a746b12..9cd10511 100644 --- a/docs/classes/NetworkError.md +++ b/docs/classes/client.NetworkError.md @@ -1,7 +1,9 @@ -[graphql-http](../README.md) / NetworkError +[graphql-http](../README.md) / [client](../modules/client.md) / NetworkError # Class: NetworkError +[client](../modules/client.md).NetworkError + A network error caused by the client or an unexpected response from the server. To avoid bundling DOM typings (because the client can run in Node env too), @@ -11,7 +13,7 @@ you should supply the `Response` generic depending on your Fetch implementation. | Name | Type | | :------ | :------ | -| `Response` | extends [`ResponseLike`](../interfaces/ResponseLike.md) = [`ResponseLike`](../interfaces/ResponseLike.md) | +| `Response` | extends [`ResponseLike`](../interfaces/client.ResponseLike.md) = [`ResponseLike`](../interfaces/client.ResponseLike.md) | ## Hierarchy @@ -23,11 +25,11 @@ you should supply the `Response` generic depending on your Fetch implementation. ### Constructors -- [constructor](NetworkError.md#constructor) +- [constructor](client.NetworkError.md#constructor) ### Properties -- [response](NetworkError.md#response) +- [response](client.NetworkError.md#response) ## Constructors @@ -39,7 +41,7 @@ you should supply the `Response` generic depending on your Fetch implementation. | Name | Type | | :------ | :------ | -| `Response` | extends [`ResponseLike`](../interfaces/ResponseLike.md) = [`ResponseLike`](../interfaces/ResponseLike.md) | +| `Response` | extends [`ResponseLike`](../interfaces/client.ResponseLike.md) = [`ResponseLike`](../interfaces/client.ResponseLike.md) | #### Parameters diff --git a/docs/interfaces/Audit.md b/docs/interfaces/Audit.md deleted file mode 100644 index a73c9d7f..00000000 --- a/docs/interfaces/Audit.md +++ /dev/null @@ -1,34 +0,0 @@ -[graphql-http](../README.md) / Audit - -# Interface: Audit - -Actual audit test returning an result. - -The test function will throw only if the error is fatal. - -## Table of contents - -### Properties - -- [fn](Audit.md#fn) -- [name](Audit.md#name) - -## Properties - -### fn - -• **fn**: () => `Promise`<[`AuditResult`](../README.md#auditresult)\> - -#### Type declaration - -▸ (): `Promise`<[`AuditResult`](../README.md#auditresult)\> - -##### Returns - -`Promise`<[`AuditResult`](../README.md#auditresult)\> - -___ - -### name - -• **name**: \`MUST ${string}\` \| \`SHOULD ${string}\` \| \`MAY ${string}\` diff --git a/docs/interfaces/ResponseInit.md b/docs/interfaces/ResponseInit.md deleted file mode 100644 index f8aa6845..00000000 --- a/docs/interfaces/ResponseInit.md +++ /dev/null @@ -1,32 +0,0 @@ -[graphql-http](../README.md) / ResponseInit - -# Interface: ResponseInit - -Server agnostic response options (ex. status and headers) returned from -`graphql-http` needing to be coerced to the server implementation in use. - -## Table of contents - -### Properties - -- [headers](ResponseInit.md#headers) -- [status](ResponseInit.md#status) -- [statusText](ResponseInit.md#statustext) - -## Properties - -### headers - -• `Optional` `Readonly` **headers**: [`ResponseHeaders`](../README.md#responseheaders) - -___ - -### status - -• `Readonly` **status**: `number` - -___ - -### statusText - -• `Readonly` **statusText**: `string` diff --git a/docs/interfaces/audits_common.Audit.md b/docs/interfaces/audits_common.Audit.md new file mode 100644 index 00000000..abbcbe9d --- /dev/null +++ b/docs/interfaces/audits_common.Audit.md @@ -0,0 +1,36 @@ +[graphql-http](../README.md) / [audits/common](../modules/audits_common.md) / Audit + +# Interface: Audit + +[audits/common](../modules/audits_common.md).Audit + +Actual audit test returning an result. + +The test function will throw only if the error is fatal. + +## Table of contents + +### Properties + +- [fn](audits_common.Audit.md#fn) +- [name](audits_common.Audit.md#name) + +## Properties + +### fn + +• **fn**: () => `Promise`<[`AuditResult`](../modules/audits_common.md#auditresult)\> + +#### Type declaration + +▸ (): `Promise`<[`AuditResult`](../modules/audits_common.md#auditresult)\> + +##### Returns + +`Promise`<[`AuditResult`](../modules/audits_common.md#auditresult)\> + +___ + +### name + +• **name**: \`MUST ${string}\` \| \`SHOULD ${string}\` \| \`MAY ${string}\` diff --git a/docs/interfaces/AuditFail.md b/docs/interfaces/audits_common.AuditFail.md similarity index 64% rename from docs/interfaces/AuditFail.md rename to docs/interfaces/audits_common.AuditFail.md index 6c527171..1b810463 100644 --- a/docs/interfaces/AuditFail.md +++ b/docs/interfaces/audits_common.AuditFail.md @@ -1,7 +1,9 @@ -[graphql-http](../README.md) / AuditFail +[graphql-http](../README.md) / [audits/common](../modules/audits_common.md) / AuditFail # Interface: AuditFail +[audits/common](../modules/audits_common.md).AuditFail + Indicates that the audit failed. If the status is `warn`, the audit is not a requirement but rather a recommendation. @@ -13,9 +15,9 @@ is therefore not compliant. ### Properties -- [name](AuditFail.md#name) -- [reason](AuditFail.md#reason) -- [status](AuditFail.md#status) +- [name](audits_common.AuditFail.md#name) +- [reason](audits_common.AuditFail.md#reason) +- [status](audits_common.AuditFail.md#status) ## Properties diff --git a/docs/interfaces/AuditOk.md b/docs/interfaces/audits_common.AuditOk.md similarity index 52% rename from docs/interfaces/AuditOk.md rename to docs/interfaces/audits_common.AuditOk.md index f468ceb8..25162bce 100644 --- a/docs/interfaces/AuditOk.md +++ b/docs/interfaces/audits_common.AuditOk.md @@ -1,15 +1,17 @@ -[graphql-http](../README.md) / AuditOk +[graphql-http](../README.md) / [audits/common](../modules/audits_common.md) / AuditOk # Interface: AuditOk +[audits/common](../modules/audits_common.md).AuditOk + Indicates that the audit was successful. ## Table of contents ### Properties -- [name](AuditOk.md#name) -- [status](AuditOk.md#status) +- [name](audits_common.AuditOk.md#name) +- [status](audits_common.AuditOk.md#status) ## Properties diff --git a/docs/interfaces/ServerAuditOptions.md b/docs/interfaces/audits_server.ServerAuditOptions.md similarity index 64% rename from docs/interfaces/ServerAuditOptions.md rename to docs/interfaces/audits_server.ServerAuditOptions.md index f0d002b6..07e528bd 100644 --- a/docs/interfaces/ServerAuditOptions.md +++ b/docs/interfaces/audits_server.ServerAuditOptions.md @@ -1,15 +1,17 @@ -[graphql-http](../README.md) / ServerAuditOptions +[graphql-http](../README.md) / [audits/server](../modules/audits_server.md) / ServerAuditOptions # Interface: ServerAuditOptions +[audits/server](../modules/audits_server.md).ServerAuditOptions + Options for server audits required to check GraphQL over HTTP spec conformance. ## Table of contents ### Properties -- [fetchFn](ServerAuditOptions.md#fetchfn) -- [url](ServerAuditOptions.md#url) +- [fetchFn](audits_server.ServerAuditOptions.md#fetchfn) +- [url](audits_server.ServerAuditOptions.md#url) ## Properties diff --git a/docs/interfaces/Client.md b/docs/interfaces/client.Client.md similarity index 75% rename from docs/interfaces/Client.md rename to docs/interfaces/client.Client.md index 9a2f3669..c6cd2d18 100644 --- a/docs/interfaces/Client.md +++ b/docs/interfaces/client.Client.md @@ -1,16 +1,18 @@ -[graphql-http](../README.md) / Client +[graphql-http](../README.md) / [client](../modules/client.md) / Client # Interface: Client +[client](../modules/client.md).Client + ## Table of contents ### Properties -- [dispose](Client.md#dispose) +- [dispose](client.Client.md#dispose) ### Methods -- [subscribe](Client.md#subscribe) +- [subscribe](client.Client.md#subscribe) ## Properties @@ -50,8 +52,8 @@ function used for canceling active requests and cleaning up. | Name | Type | | :------ | :------ | -| `request` | [`RequestParams`](RequestParams.md) | -| `sink` | [`Sink`](Sink.md)<`ExecutionResult`<`Data`, `Extensions`\>\> | +| `request` | [`RequestParams`](common.RequestParams.md) | +| `sink` | [`Sink`](common.Sink.md)<`ExecutionResult`<`Data`, `Extensions`\>\> | #### Returns diff --git a/docs/interfaces/ClientOptions.md b/docs/interfaces/client.ClientOptions.md similarity index 85% rename from docs/interfaces/ClientOptions.md rename to docs/interfaces/client.ClientOptions.md index e71d3a58..7c1e1f4f 100644 --- a/docs/interfaces/ClientOptions.md +++ b/docs/interfaces/client.ClientOptions.md @@ -1,19 +1,21 @@ -[graphql-http](../README.md) / ClientOptions +[graphql-http](../README.md) / [client](../modules/client.md) / ClientOptions # Interface: ClientOptions +[client](../modules/client.md).ClientOptions + ## Table of contents ### Properties -- [abortControllerImpl](ClientOptions.md#abortcontrollerimpl) -- [credentials](ClientOptions.md#credentials) -- [fetchFn](ClientOptions.md#fetchfn) -- [headers](ClientOptions.md#headers) -- [referrer](ClientOptions.md#referrer) -- [referrerPolicy](ClientOptions.md#referrerpolicy) -- [shouldRetry](ClientOptions.md#shouldretry) -- [url](ClientOptions.md#url) +- [abortControllerImpl](client.ClientOptions.md#abortcontrollerimpl) +- [credentials](client.ClientOptions.md#credentials) +- [fetchFn](client.ClientOptions.md#fetchfn) +- [headers](client.ClientOptions.md#headers) +- [referrer](client.ClientOptions.md#referrer) +- [referrerPolicy](client.ClientOptions.md#referrerpolicy) +- [shouldRetry](client.ClientOptions.md#shouldretry) +- [url](client.ClientOptions.md#url) ## Properties @@ -114,7 +116,7 @@ ___ ### shouldRetry -• `Optional` **shouldRetry**: (`err`: [`NetworkError`](../classes/NetworkError.md)<[`ResponseLike`](ResponseLike.md)\>, `retries`: `number`) => `Promise`<`boolean`\> +• `Optional` **shouldRetry**: (`err`: [`NetworkError`](../classes/client.NetworkError.md)<[`ResponseLike`](client.ResponseLike.md)\>, `retries`: `number`) => `Promise`<`boolean`\> #### Type declaration @@ -143,7 +145,7 @@ the `err` argument, will report it instead. | Name | Type | | :------ | :------ | -| `err` | [`NetworkError`](../classes/NetworkError.md)<[`ResponseLike`](ResponseLike.md)\> | +| `err` | [`NetworkError`](../classes/client.NetworkError.md)<[`ResponseLike`](client.ResponseLike.md)\> | | `retries` | `number` | ##### Returns @@ -154,7 +156,7 @@ ___ ### url -• **url**: `string` \| (`request`: [`RequestParams`](RequestParams.md)) => `string` \| `Promise`<`string`\> +• **url**: `string` \| (`request`: [`RequestParams`](common.RequestParams.md)) => `string` \| `Promise`<`string`\> URL of the GraphQL over HTTP server to connect. diff --git a/docs/interfaces/ResponseLike.md b/docs/interfaces/client.ResponseLike.md similarity index 55% rename from docs/interfaces/ResponseLike.md rename to docs/interfaces/client.ResponseLike.md index 113d06c7..71a0f919 100644 --- a/docs/interfaces/ResponseLike.md +++ b/docs/interfaces/client.ResponseLike.md @@ -1,16 +1,18 @@ -[graphql-http](../README.md) / ResponseLike +[graphql-http](../README.md) / [client](../modules/client.md) / ResponseLike # Interface: ResponseLike +[client](../modules/client.md).ResponseLike + Concrete interface a response needs to implement for the client. ## Table of contents ### Properties -- [ok](ResponseLike.md#ok) -- [status](ResponseLike.md#status) -- [statusText](ResponseLike.md#statustext) +- [ok](client.ResponseLike.md#ok) +- [status](client.ResponseLike.md#status) +- [statusText](client.ResponseLike.md#statustext) ## Properties diff --git a/docs/interfaces/RequestParams.md b/docs/interfaces/common.RequestParams.md similarity index 60% rename from docs/interfaces/RequestParams.md rename to docs/interfaces/common.RequestParams.md index edb59597..0c7b90eb 100644 --- a/docs/interfaces/RequestParams.md +++ b/docs/interfaces/common.RequestParams.md @@ -1,7 +1,9 @@ -[graphql-http](../README.md) / RequestParams +[graphql-http](../README.md) / [common](../modules/common.md) / RequestParams # Interface: RequestParams +[common](../modules/common.md).RequestParams + Parameters for GraphQL's request for execution. Reference: https://graphql.github.io/graphql-over-http/draft/#sec-Request-Parameters @@ -10,10 +12,10 @@ Reference: https://graphql.github.io/graphql-over-http/draft/#sec-Request-Parame ### Properties -- [extensions](RequestParams.md#extensions) -- [operationName](RequestParams.md#operationname) -- [query](RequestParams.md#query) -- [variables](RequestParams.md#variables) +- [extensions](common.RequestParams.md#extensions) +- [operationName](common.RequestParams.md#operationname) +- [query](common.RequestParams.md#query) +- [variables](common.RequestParams.md#variables) ## Properties diff --git a/docs/interfaces/Sink.md b/docs/interfaces/common.Sink.md similarity index 77% rename from docs/interfaces/Sink.md rename to docs/interfaces/common.Sink.md index d2688978..b58ff391 100644 --- a/docs/interfaces/Sink.md +++ b/docs/interfaces/common.Sink.md @@ -1,7 +1,9 @@ -[graphql-http](../README.md) / Sink +[graphql-http](../README.md) / [common](../modules/common.md) / Sink # Interface: Sink +[common](../modules/common.md).Sink + A representation of any set of values over any amount of time. ## Type parameters @@ -14,9 +16,9 @@ A representation of any set of values over any amount of time. ### Methods -- [complete](Sink.md#complete) -- [error](Sink.md#error) -- [next](Sink.md#next) +- [complete](common.Sink.md#complete) +- [error](common.Sink.md#error) +- [next](common.Sink.md#next) ## Methods diff --git a/docs/interfaces/HandlerOptions.md b/docs/interfaces/handler.HandlerOptions.md similarity index 63% rename from docs/interfaces/HandlerOptions.md rename to docs/interfaces/handler.HandlerOptions.md index 281b006a..bbba6cb7 100644 --- a/docs/interfaces/HandlerOptions.md +++ b/docs/interfaces/handler.HandlerOptions.md @@ -1,33 +1,35 @@ -[graphql-http](../README.md) / HandlerOptions +[graphql-http](../README.md) / [handler](../modules/handler.md) / HandlerOptions # Interface: HandlerOptions +[handler](../modules/handler.md).HandlerOptions + ## Type parameters | Name | Type | | :------ | :------ | | `RequestRaw` | `unknown` | | `RequestContext` | `unknown` | -| `Context` | extends [`OperationContext`](../README.md#operationcontext) = `undefined` | +| `Context` | extends [`OperationContext`](../modules/handler.md#operationcontext) = `undefined` | ## Table of contents ### Properties -- [context](HandlerOptions.md#context) -- [execute](HandlerOptions.md#execute) -- [getOperationAST](HandlerOptions.md#getoperationast) -- [onOperation](HandlerOptions.md#onoperation) -- [onSubscribe](HandlerOptions.md#onsubscribe) -- [parse](HandlerOptions.md#parse) -- [schema](HandlerOptions.md#schema) -- [validate](HandlerOptions.md#validate) +- [context](handler.HandlerOptions.md#context) +- [execute](handler.HandlerOptions.md#execute) +- [getOperationAST](handler.HandlerOptions.md#getoperationast) +- [onOperation](handler.HandlerOptions.md#onoperation) +- [onSubscribe](handler.HandlerOptions.md#onsubscribe) +- [parse](handler.HandlerOptions.md#parse) +- [schema](handler.HandlerOptions.md#schema) +- [validate](handler.HandlerOptions.md#validate) ## Properties ### context -• `Optional` **context**: `Context` \| (`req`: [`Request`](Request.md)<`RequestRaw`, `RequestContext`\>, `params`: [`RequestParams`](RequestParams.md)) => [`Response`](../README.md#response) \| `Context` \| `Promise`<[`Response`](../README.md#response) \| `Context`\> +• `Optional` **context**: `Context` \| (`req`: [`Request`](handler.Request.md)<`RequestRaw`, `RequestContext`\>, `params`: [`RequestParams`](common.RequestParams.md)) => [`Response`](../modules/handler.md#response) \| `Context` \| `Promise`<[`Response`](../modules/handler.md#response) \| `Context`\> A value which is provided to every resolver and holds important contextual information like the currently @@ -91,11 +93,11 @@ ___ ### onOperation -• `Optional` **onOperation**: (`req`: [`Request`](Request.md)<`RequestRaw`, `RequestContext`\>, `args`: [`OperationArgs`](../README.md#operationargs)<`Context`\>, `result`: `ExecutionResult`<`ObjMap`<`unknown`\>, `ObjMap`<`unknown`\>\>) => `void` \| `ExecutionResult`<`ObjMap`<`unknown`\>, `ObjMap`<`unknown`\>\> \| [`Response`](../README.md#response) \| `Promise`<`void` \| `ExecutionResult`<`ObjMap`<`unknown`\>, `ObjMap`<`unknown`\>\> \| [`Response`](../README.md#response)\> +• `Optional` **onOperation**: (`req`: [`Request`](handler.Request.md)<`RequestRaw`, `RequestContext`\>, `args`: [`OperationArgs`](../modules/handler.md#operationargs)<`Context`\>, `result`: `ExecutionResult`<`ObjMap`<`unknown`\>, `ObjMap`<`unknown`\>\>) => `void` \| `ExecutionResult`<`ObjMap`<`unknown`\>, `ObjMap`<`unknown`\>\> \| [`Response`](../modules/handler.md#response) \| `Promise`<`void` \| `ExecutionResult`<`ObjMap`<`unknown`\>, `ObjMap`<`unknown`\>\> \| [`Response`](../modules/handler.md#response)\> #### Type declaration -▸ (`req`, `args`, `result`): `void` \| `ExecutionResult`<`ObjMap`<`unknown`\>, `ObjMap`<`unknown`\>\> \| [`Response`](../README.md#response) \| `Promise`<`void` \| `ExecutionResult`<`ObjMap`<`unknown`\>, `ObjMap`<`unknown`\>\> \| [`Response`](../README.md#response)\> +▸ (`req`, `args`, `result`): `void` \| `ExecutionResult`<`ObjMap`<`unknown`\>, `ObjMap`<`unknown`\>\> \| [`Response`](../modules/handler.md#response) \| `Promise`<`void` \| `ExecutionResult`<`ObjMap`<`unknown`\>, `ObjMap`<`unknown`\>\> \| [`Response`](../modules/handler.md#response)\> Executed after the operation call resolves. @@ -113,23 +115,23 @@ further execution. | Name | Type | | :------ | :------ | -| `req` | [`Request`](Request.md)<`RequestRaw`, `RequestContext`\> | -| `args` | [`OperationArgs`](../README.md#operationargs)<`Context`\> | +| `req` | [`Request`](handler.Request.md)<`RequestRaw`, `RequestContext`\> | +| `args` | [`OperationArgs`](../modules/handler.md#operationargs)<`Context`\> | | `result` | `ExecutionResult`<`ObjMap`<`unknown`\>, `ObjMap`<`unknown`\>\> | ##### Returns -`void` \| `ExecutionResult`<`ObjMap`<`unknown`\>, `ObjMap`<`unknown`\>\> \| [`Response`](../README.md#response) \| `Promise`<`void` \| `ExecutionResult`<`ObjMap`<`unknown`\>, `ObjMap`<`unknown`\>\> \| [`Response`](../README.md#response)\> +`void` \| `ExecutionResult`<`ObjMap`<`unknown`\>, `ObjMap`<`unknown`\>\> \| [`Response`](../modules/handler.md#response) \| `Promise`<`void` \| `ExecutionResult`<`ObjMap`<`unknown`\>, `ObjMap`<`unknown`\>\> \| [`Response`](../modules/handler.md#response)\> ___ ### onSubscribe -• `Optional` **onSubscribe**: (`req`: [`Request`](Request.md)<`RequestRaw`, `RequestContext`\>, `params`: [`RequestParams`](RequestParams.md)) => `void` \| readonly `GraphQLError`[] \| `ExecutionResult`<`ObjMap`<`unknown`\>, `ObjMap`<`unknown`\>\> \| [`Response`](../README.md#response) \| [`OperationArgs`](../README.md#operationargs)<`Context`\> \| `Promise`<`void` \| readonly `GraphQLError`[] \| `ExecutionResult`<`ObjMap`<`unknown`\>, `ObjMap`<`unknown`\>\> \| [`Response`](../README.md#response) \| [`OperationArgs`](../README.md#operationargs)<`Context`\>\> +• `Optional` **onSubscribe**: (`req`: [`Request`](handler.Request.md)<`RequestRaw`, `RequestContext`\>, `params`: [`RequestParams`](common.RequestParams.md)) => `void` \| readonly `GraphQLError`[] \| `ExecutionResult`<`ObjMap`<`unknown`\>, `ObjMap`<`unknown`\>\> \| [`Response`](../modules/handler.md#response) \| [`OperationArgs`](../modules/handler.md#operationargs)<`Context`\> \| `Promise`<`void` \| readonly `GraphQLError`[] \| `ExecutionResult`<`ObjMap`<`unknown`\>, `ObjMap`<`unknown`\>\> \| [`Response`](../modules/handler.md#response) \| [`OperationArgs`](../modules/handler.md#operationargs)<`Context`\>\> #### Type declaration -▸ (`req`, `params`): `void` \| readonly `GraphQLError`[] \| `ExecutionResult`<`ObjMap`<`unknown`\>, `ObjMap`<`unknown`\>\> \| [`Response`](../README.md#response) \| [`OperationArgs`](../README.md#operationargs)<`Context`\> \| `Promise`<`void` \| readonly `GraphQLError`[] \| `ExecutionResult`<`ObjMap`<`unknown`\>, `ObjMap`<`unknown`\>\> \| [`Response`](../README.md#response) \| [`OperationArgs`](../README.md#operationargs)<`Context`\>\> +▸ (`req`, `params`): `void` \| readonly `GraphQLError`[] \| `ExecutionResult`<`ObjMap`<`unknown`\>, `ObjMap`<`unknown`\>\> \| [`Response`](../modules/handler.md#response) \| [`OperationArgs`](../modules/handler.md#operationargs)<`Context`\> \| `Promise`<`void` \| readonly `GraphQLError`[] \| `ExecutionResult`<`ObjMap`<`unknown`\>, `ObjMap`<`unknown`\>\> \| [`Response`](../modules/handler.md#response) \| [`OperationArgs`](../modules/handler.md#operationargs)<`Context`\>\> The subscribe callback executed right after processing the request before proceeding with the GraphQL operation execution. @@ -162,12 +164,12 @@ further execution. | Name | Type | | :------ | :------ | -| `req` | [`Request`](Request.md)<`RequestRaw`, `RequestContext`\> | -| `params` | [`RequestParams`](RequestParams.md) | +| `req` | [`Request`](handler.Request.md)<`RequestRaw`, `RequestContext`\> | +| `params` | [`RequestParams`](common.RequestParams.md) | ##### Returns -`void` \| readonly `GraphQLError`[] \| `ExecutionResult`<`ObjMap`<`unknown`\>, `ObjMap`<`unknown`\>\> \| [`Response`](../README.md#response) \| [`OperationArgs`](../README.md#operationargs)<`Context`\> \| `Promise`<`void` \| readonly `GraphQLError`[] \| `ExecutionResult`<`ObjMap`<`unknown`\>, `ObjMap`<`unknown`\>\> \| [`Response`](../README.md#response) \| [`OperationArgs`](../README.md#operationargs)<`Context`\>\> +`void` \| readonly `GraphQLError`[] \| `ExecutionResult`<`ObjMap`<`unknown`\>, `ObjMap`<`unknown`\>\> \| [`Response`](../modules/handler.md#response) \| [`OperationArgs`](../modules/handler.md#operationargs)<`Context`\> \| `Promise`<`void` \| readonly `GraphQLError`[] \| `ExecutionResult`<`ObjMap`<`unknown`\>, `ObjMap`<`unknown`\>\> \| [`Response`](../modules/handler.md#response) \| [`OperationArgs`](../modules/handler.md#operationargs)<`Context`\>\> ___ @@ -197,7 +199,7 @@ ___ ### schema -• `Optional` **schema**: `GraphQLSchema` \| (`req`: [`Request`](Request.md)<`RequestRaw`, `RequestContext`\>, `args`: `Omit`<[`OperationArgs`](../README.md#operationargs)<`Context`\>, ``"schema"``\>) => [`Response`](../README.md#response) \| `GraphQLSchema` \| `Promise`<[`Response`](../README.md#response) \| `GraphQLSchema`\> +• `Optional` **schema**: `GraphQLSchema` \| (`req`: [`Request`](handler.Request.md)<`RequestRaw`, `RequestContext`\>, `args`: `Omit`<[`OperationArgs`](../modules/handler.md#operationargs)<`Context`\>, ``"schema"``\>) => [`Response`](../modules/handler.md#response) \| `GraphQLSchema` \| `Promise`<[`Response`](../modules/handler.md#response) \| `GraphQLSchema`\> The GraphQL schema on which the operations will be executed and validated against. diff --git a/docs/interfaces/Request.md b/docs/interfaces/handler.Request.md similarity index 72% rename from docs/interfaces/Request.md rename to docs/interfaces/handler.Request.md index 64e82cba..2d8a95c2 100644 --- a/docs/interfaces/Request.md +++ b/docs/interfaces/handler.Request.md @@ -1,7 +1,9 @@ -[graphql-http](../README.md) / Request +[graphql-http](../README.md) / [handler](../modules/handler.md) / Request # Interface: Request +[handler](../modules/handler.md).Request + Server agnostic request interface containing the raw request which is server dependant. @@ -16,12 +18,12 @@ which is server dependant. ### Properties -- [body](Request.md#body) -- [context](Request.md#context) -- [headers](Request.md#headers) -- [method](Request.md#method) -- [raw](Request.md#raw) -- [url](Request.md#url) +- [body](handler.Request.md#body) +- [context](handler.Request.md#context) +- [headers](handler.Request.md#headers) +- [method](handler.Request.md#method) +- [raw](handler.Request.md#raw) +- [url](handler.Request.md#url) ## Properties @@ -46,7 +48,7 @@ ___ ### headers -• `Readonly` **headers**: [`RequestHeaders`](../README.md#requestheaders) +• `Readonly` **headers**: [`RequestHeaders`](../modules/handler.md#requestheaders) ___ diff --git a/docs/interfaces/handler.ResponseInit.md b/docs/interfaces/handler.ResponseInit.md new file mode 100644 index 00000000..f4e96395 --- /dev/null +++ b/docs/interfaces/handler.ResponseInit.md @@ -0,0 +1,34 @@ +[graphql-http](../README.md) / [handler](../modules/handler.md) / ResponseInit + +# Interface: ResponseInit + +[handler](../modules/handler.md).ResponseInit + +Server agnostic response options (ex. status and headers) returned from +`graphql-http` needing to be coerced to the server implementation in use. + +## Table of contents + +### Properties + +- [headers](handler.ResponseInit.md#headers) +- [status](handler.ResponseInit.md#status) +- [statusText](handler.ResponseInit.md#statustext) + +## Properties + +### headers + +• `Optional` `Readonly` **headers**: [`ResponseHeaders`](../modules/handler.md#responseheaders) + +___ + +### status + +• `Readonly` **status**: `number` + +___ + +### statusText + +• `Readonly` **statusText**: `string` diff --git a/docs/interfaces/use_fetch.FetchAPI.md b/docs/interfaces/use_fetch.FetchAPI.md new file mode 100644 index 00000000..7a1d8cf3 --- /dev/null +++ b/docs/interfaces/use_fetch.FetchAPI.md @@ -0,0 +1,33 @@ +[graphql-http](../README.md) / [use/fetch](../modules/use_fetch.md) / FetchAPI + +# Interface: FetchAPI + +[use/fetch](../modules/use_fetch.md).FetchAPI + +The necessary API from the fetch environment for the handler. + +## Table of contents + +### Properties + +- [ReadableStream](use_fetch.FetchAPI.md#readablestream) +- [Response](use_fetch.FetchAPI.md#response) +- [TextEncoder](use_fetch.FetchAPI.md#textencoder) + +## Properties + +### ReadableStream + +• **ReadableStream**: `Object` + +___ + +### Response + +• **Response**: `Object` + +___ + +### TextEncoder + +• **TextEncoder**: `Object` diff --git a/docs/modules/audits_common.md b/docs/modules/audits_common.md new file mode 100644 index 00000000..6b6ce2b1 --- /dev/null +++ b/docs/modules/audits_common.md @@ -0,0 +1,41 @@ +[graphql-http](../README.md) / audits/common + +# Module: audits/common + +## Table of contents + +### Interfaces + +- [Audit](../interfaces/audits_common.Audit.md) +- [AuditFail](../interfaces/audits_common.AuditFail.md) +- [AuditOk](../interfaces/audits_common.AuditOk.md) + +### Type Aliases + +- [AuditName](audits_common.md#auditname) +- [AuditRequirement](audits_common.md#auditrequirement) +- [AuditResult](audits_common.md#auditresult) + +## Audits + +### AuditName + +Ƭ **AuditName**: \`${AuditRequirement} ${string}\` + +Audit name starting with the audit requirement level. + +___ + +### AuditRequirement + +Ƭ **AuditRequirement**: ``"MUST"`` \| ``"SHOULD"`` \| ``"MAY"`` + +Audit requirement levels as per [RFC2119](https://www.rfc-editor.org/rfc/rfc2119). + +___ + +### AuditResult + +Ƭ **AuditResult**: [`AuditOk`](../interfaces/audits_common.AuditOk.md) \| [`AuditFail`](../interfaces/audits_common.AuditFail.md) + +Result of the performed audit. See `AuditOk` and `AuditFail` for more information. diff --git a/docs/modules/audits_server.md b/docs/modules/audits_server.md new file mode 100644 index 00000000..fbd09669 --- /dev/null +++ b/docs/modules/audits_server.md @@ -0,0 +1,52 @@ +[graphql-http](../README.md) / audits/server + +# Module: audits/server + +## Table of contents + +### Interfaces + +- [ServerAuditOptions](../interfaces/audits_server.ServerAuditOptions.md) + +### Functions + +- [auditServer](audits_server.md#auditserver) +- [serverAudits](audits_server.md#serveraudits) + +## Audits + +### auditServer + +▸ **auditServer**(`opts`): `Promise`<[`AuditResult`](audits_common.md#auditresult)[]\> + +Performs the full list of server audits required for GraphQL over HTTP spec conformance. + +Please consult the `AuditResult` for more information. + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `opts` | [`ServerAuditOptions`](../interfaces/audits_server.ServerAuditOptions.md) | + +#### Returns + +`Promise`<[`AuditResult`](audits_common.md#auditresult)[]\> + +___ + +### serverAudits + +▸ **serverAudits**(`opts`): [`Audit`](../interfaces/audits_common.Audit.md)[] + +List of server audits required to check GraphQL over HTTP spec conformance. + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `opts` | [`ServerAuditOptions`](../interfaces/audits_server.ServerAuditOptions.md) | + +#### Returns + +[`Audit`](../interfaces/audits_common.Audit.md)[] diff --git a/docs/modules/client.md b/docs/modules/client.md new file mode 100644 index 00000000..6ad543c7 --- /dev/null +++ b/docs/modules/client.md @@ -0,0 +1,55 @@ +[graphql-http](../README.md) / client + +# Module: client + +## Table of contents + +### References + +- [RequestParams](client.md#requestparams) +- [Sink](client.md#sink) + +### Classes + +- [NetworkError](../classes/client.NetworkError.md) + +### Interfaces + +- [Client](../interfaces/client.Client.md) +- [ClientOptions](../interfaces/client.ClientOptions.md) +- [ResponseLike](../interfaces/client.ResponseLike.md) + +### Functions + +- [createClient](client.md#createclient) + +## Client + +### createClient + +▸ **createClient**(`options`): [`Client`](../interfaces/client.Client.md) + +Creates a disposable GraphQL over HTTP client to transmit +GraphQL operation results. + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `options` | [`ClientOptions`](../interfaces/client.ClientOptions.md) | + +#### Returns + +[`Client`](../interfaces/client.Client.md) + +## Other + +### RequestParams + +Re-exports [RequestParams](../interfaces/common.RequestParams.md) + +___ + +### Sink + +Re-exports [Sink](../interfaces/common.Sink.md) diff --git a/docs/modules/common.md b/docs/modules/common.md new file mode 100644 index 00000000..16b97e2e --- /dev/null +++ b/docs/modules/common.md @@ -0,0 +1,10 @@ +[graphql-http](../README.md) / common + +# Module: common + +## Table of contents + +### Interfaces + +- [RequestParams](../interfaces/common.RequestParams.md) +- [Sink](../interfaces/common.Sink.md) diff --git a/docs/modules/handler.md b/docs/modules/handler.md new file mode 100644 index 00000000..0ccbdbff --- /dev/null +++ b/docs/modules/handler.md @@ -0,0 +1,267 @@ +[graphql-http](../README.md) / handler + +# Module: handler + +## Table of contents + +### Interfaces + +- [HandlerOptions](../interfaces/handler.HandlerOptions.md) +- [Request](../interfaces/handler.Request.md) +- [ResponseInit](../interfaces/handler.ResponseInit.md) + +### Type Aliases + +- [AcceptableMediaType](handler.md#acceptablemediatype) +- [Handler](handler.md#handler) +- [OperationArgs](handler.md#operationargs) +- [OperationContext](handler.md#operationcontext) +- [RequestHeaders](handler.md#requestheaders) +- [Response](handler.md#response) +- [ResponseBody](handler.md#responsebody) +- [ResponseHeaders](handler.md#responseheaders) + +### Functions + +- [createHandler](handler.md#createhandler) +- [getAcceptableMediaType](handler.md#getacceptablemediatype) +- [isResponse](handler.md#isresponse) +- [makeResponse](handler.md#makeresponse) + +## Server + +### AcceptableMediaType + +Ƭ **AcceptableMediaType**: ``"application/graphql-response+json"`` \| ``"application/json"`` + +Request's Media-Type that the server accepts. + +___ + +### Handler + +Ƭ **Handler**<`RequestRaw`, `RequestContext`\>: (`req`: [`Request`](../interfaces/handler.Request.md)<`RequestRaw`, `RequestContext`\>) => `Promise`<[`Response`](handler.md#response)\> + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `RequestRaw` | `unknown` | +| `RequestContext` | `unknown` | + +#### Type declaration + +▸ (`req`): `Promise`<[`Response`](handler.md#response)\> + +The ready-to-use handler. Simply plug it in your favourite HTTP framework +and enjoy. + +Errors thrown from **any** of the provided options or callbacks (or even due to +library misuse or potential bugs) will reject the handler's promise. They are +considered internal errors and you should take care of them accordingly. + +##### Parameters + +| Name | Type | +| :------ | :------ | +| `req` | [`Request`](../interfaces/handler.Request.md)<`RequestRaw`, `RequestContext`\> | + +##### Returns + +`Promise`<[`Response`](handler.md#response)\> + +___ + +### OperationArgs + +Ƭ **OperationArgs**<`Context`\>: `ExecutionArgs` & { `contextValue?`: `Context` } + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Context` | extends [`OperationContext`](handler.md#operationcontext) = `undefined` | + +___ + +### OperationContext + +Ƭ **OperationContext**: `Record`<`PropertyKey`, `unknown`\> \| `symbol` \| `number` \| `string` \| `boolean` \| `undefined` \| ``null`` + +A concrete GraphQL execution context value type. + +Mainly used because TypeScript collapes unions +with `any` or `unknown` to `any` or `unknown`. So, +we use a custom type to allow definitions such as +the `context` server option. + +___ + +### RequestHeaders + +Ƭ **RequestHeaders**: { `[key: string]`: `string` \| `string`[] \| `undefined`; `set-cookie?`: `string` \| `string`[] } \| { `get`: (`key`: `string`) => `string` \| ``null`` } + +The incoming request headers the implementing server should provide. + +___ + +### Response + +Ƭ **Response**: readonly [body: ResponseBody \| null, init: ResponseInit] + +Server agnostic response returned from `graphql-http` containing the +body and init options needing to be coerced to the server implementation in use. + +___ + +### ResponseBody + +Ƭ **ResponseBody**: `string` + +Server agnostic response body returned from `graphql-http` needing +to be coerced to the server implementation in use. + +___ + +### ResponseHeaders + +Ƭ **ResponseHeaders**: { `accept?`: `string` ; `allow?`: `string` ; `content-type?`: `string` } & `Record`<`string`, `string`\> + +The response headers that get returned from graphql-http. + +___ + +### createHandler + +▸ **createHandler**<`RequestRaw`, `RequestContext`, `Context`\>(`options`): [`Handler`](handler.md#handler)<`RequestRaw`, `RequestContext`\> + +Makes a GraphQL over HTTP Protocol compliant server handler. The handler can +be used with your favourite server library. + +Beware that the handler resolves only after the whole operation completes. + +Errors thrown from **any** of the provided options or callbacks (or even due to +library misuse or potential bugs) will reject the handler's promise. They are +considered internal errors and you should take care of them accordingly. + +For production environments, its recommended not to transmit the exact internal +error details to the client, but instead report to an error logging tool or simply +the console. + +Simple example usage with Node: + +```js +import http from 'http'; +import { createHandler } from 'graphql-http'; +import { schema } from './my-graphql-schema'; + +// Create the GraphQL over HTTP handler +const handler = createHandler({ schema }); + +// Create a HTTP server using the handler on `/graphql` +const server = http.createServer(async (req, res) => { + if (!req.url.startsWith('/graphql')) { + return res.writeHead(404).end(); + } + + try { + const [body, init] = await handler({ + url: req.url, + method: req.method, + headers: req.headers, + body: () => new Promise((resolve) => { + let body = ''; + req.on('data', (chunk) => (body += chunk)); + req.on('end', () => resolve(body)); + }), + raw: req, + }); + res.writeHead(init.status, init.statusText, init.headers).end(body); + } catch (err) { + // BEWARE not to transmit the exact internal error message in production environments + res.writeHead(500).end(err.message); + } +}); + +server.listen(4000); +console.log('Listening to port 4000'); +``` + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `RequestRaw` | `unknown` | +| `RequestContext` | `unknown` | +| `Context` | extends [`OperationContext`](handler.md#operationcontext) = `undefined` | + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `options` | [`HandlerOptions`](../interfaces/handler.HandlerOptions.md)<`RequestRaw`, `RequestContext`, `Context`\> | + +#### Returns + +[`Handler`](handler.md#handler)<`RequestRaw`, `RequestContext`\> + +___ + +### getAcceptableMediaType + +▸ **getAcceptableMediaType**(`acceptHeader`): [`AcceptableMediaType`](handler.md#acceptablemediatype) \| ``null`` + +Inspects the request and detects the appropriate/acceptable Media-Type +looking at the `Accept` header while complying with the GraphQL over HTTP Protocol. + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `acceptHeader` | `undefined` \| ``null`` \| `string` | + +#### Returns + +[`AcceptableMediaType`](handler.md#acceptablemediatype) \| ``null`` + +___ + +### isResponse + +▸ **isResponse**(`val`): val is Response + +Checks whether the passed value is the `graphql-http` server agnostic response. + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `val` | `unknown` | + +#### Returns + +val is Response + +___ + +### makeResponse + +▸ **makeResponse**(`resultOrErrors`, `acceptedMediaType`): [`Response`](handler.md#response) + +Creates an appropriate GraphQL over HTTP response following the provided arguments. + +If the first argument is an `ExecutionResult`, the operation will be treated as "successful". + +If the first argument is _any_ object without the `data` field, it will be treated as an error (as per the spec) +and the response will be constructed with the help of `acceptedMediaType` complying with the GraphQL over HTTP Protocol. + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `resultOrErrors` | readonly `GraphQLError`[] \| `Readonly`<`ExecutionResult`<`ObjMap`<`unknown`\>, `ObjMap`<`unknown`\>\>\> \| `Readonly`<`GraphQLError`\> | +| `acceptedMediaType` | [`AcceptableMediaType`](handler.md#acceptablemediatype) | + +#### Returns + +[`Response`](handler.md#response) diff --git a/docs/modules/use_express.md b/docs/modules/use_express.md new file mode 100644 index 00000000..259d3626 --- /dev/null +++ b/docs/modules/use_express.md @@ -0,0 +1,46 @@ +[graphql-http](../README.md) / use/express + +# Module: use/express + +## Table of contents + +### Functions + +- [createHandler](use_express.md#createhandler) + +## Server/express + +### createHandler + +▸ **createHandler**<`Context`\>(`options`): `Handler` + +Create a GraphQL over HTTP Protocol compliant request handler for +the express framework. + +```js +import express from 'express'; // yarn add express +import { createHandler } from 'graphql-http/lib/use/express'; +import { schema } from './my-graphql-schema'; + +const app = express(); +app.all('/graphql', createHandler({ schema })); + +app.listen({ port: 4000 }); +console.log('Listening to port 4000'); +``` + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Context` | extends [`OperationContext`](handler.md#operationcontext) = `undefined` | + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `options` | [`HandlerOptions`](../interfaces/handler.HandlerOptions.md)<`Request`<`ParamsDictionary`, `any`, `any`, `ParsedQs`, `Record`<`string`, `any`\>\>, `undefined`, `Context`\> | + +#### Returns + +`Handler` diff --git a/docs/modules/use_fastify.md b/docs/modules/use_fastify.md new file mode 100644 index 00000000..746a270e --- /dev/null +++ b/docs/modules/use_fastify.md @@ -0,0 +1,46 @@ +[graphql-http](../README.md) / use/fastify + +# Module: use/fastify + +## Table of contents + +### Functions + +- [createHandler](use_fastify.md#createhandler) + +## Server/fastify + +### createHandler + +▸ **createHandler**<`Context`\>(`options`): `RouteHandler` + +Create a GraphQL over HTTP Protocol compliant request handler for +the fastify framework. + +```js +import Fastify from 'fastify'; // yarn add fastify +import { createHandler } from 'graphql-http/lib/use/express'; +import { schema } from './my-graphql-schema'; + +const fastify = Fastify(); +fastify.all('/graphql', createHandler({ schema })); + +fastify.listen({ port: 4000 }); +console.log('Listening to port 4000'); +``` + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Context` | extends [`OperationContext`](handler.md#operationcontext) = `undefined` | + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `options` | [`HandlerOptions`](../interfaces/handler.HandlerOptions.md)<`FastifyRequest`<`RouteGenericInterface`, `Server`, `IncomingMessage`, `FastifySchema`, `FastifyTypeProviderDefault`, `unknown`, `FastifyBaseLogger`, `ResolveFastifyRequestType`<`FastifyTypeProviderDefault`, `FastifySchema`, `RouteGenericInterface`\>\>, `undefined`, `Context`\> | + +#### Returns + +`RouteHandler` diff --git a/docs/modules/use_fetch.md b/docs/modules/use_fetch.md new file mode 100644 index 00000000..27e7b02e --- /dev/null +++ b/docs/modules/use_fetch.md @@ -0,0 +1,71 @@ +[graphql-http](../README.md) / use/fetch + +# Module: use/fetch + +## Table of contents + +### Interfaces + +- [FetchAPI](../interfaces/use_fetch.FetchAPI.md) + +### Functions + +- [createHandler](use_fetch.md#createhandler) + +## Server/fetch + +### createHandler + +▸ **createHandler**<`Context`\>(`options`, `fetchApi?`): (`req`: `Request`) => `Promise`<`Response`\> + +Create a GraphQL over HTTP Protocol compliant request handler for +a fetch environment like Deno, Bun, CloudFlare Workers, Lambdas, etc. + +You can use [@whatwg-node/server](https://github.com/ardatan/whatwg-node/tree/master/packages/server) to create a server adapter and +isomorphically use it in _any_ environment. See an example: + +```js +import http from 'http'; +import { createServerAdapter } from '@whatwg-node/server'; // yarn add @whatwg-node/server +import { createHandler } from 'graphql-http/lib/use/fetch'; +import { schema } from './my-graphql-step'; + +// Use this adapter in _any_ environment. +const adapter = createServerAdapter({ + handleRequest: createHandler({ schema }), +}); + +const server = http.createServer(adapter); + +server.listen(4000); +console.log('Listening to port 4000'); +``` + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Context` | extends [`OperationContext`](handler.md#operationcontext) = `undefined` | + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `options` | [`HandlerOptions`](../interfaces/handler.HandlerOptions.md)<`Request`, [`FetchAPI`](../interfaces/use_fetch.FetchAPI.md), `Context`\> | +| `fetchApi` | `Partial`<[`FetchAPI`](../interfaces/use_fetch.FetchAPI.md)\> | + +#### Returns + +`fn` + +▸ (`req`): `Promise`<`Response`\> + +##### Parameters + +| Name | Type | +| :------ | :------ | +| `req` | `Request` | + +##### Returns + +`Promise`<`Response`\> diff --git a/docs/modules/use_node.md b/docs/modules/use_node.md new file mode 100644 index 00000000..4ac3e2c1 --- /dev/null +++ b/docs/modules/use_node.md @@ -0,0 +1,45 @@ +[graphql-http](../README.md) / use/node + +# Module: use/node + +## Table of contents + +### Functions + +- [createHandler](use_node.md#createhandler) + +## Server/node + +### createHandler + +▸ **createHandler**<`Context`\>(`options`): `RequestListener` + +Create a GraphQL over HTTP Protocol compliant request handler for +the Node environment. + +```js +import http from 'http'; +import { createHandler } from 'graphql-http/lib/use/node'; +import { schema } from './my-graphql-step'; + +const server = http.createServer(createHandler({ schema })); + +server.listen(4000); +console.log('Listening to port 4000'); +``` + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Context` | extends [`OperationContext`](handler.md#operationcontext) = `undefined` | + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `options` | [`HandlerOptions`](../interfaces/handler.HandlerOptions.md)<`IncomingMessage`, `undefined`, `Context`\> | + +#### Returns + +`RequestListener` diff --git a/src/use/express.ts b/src/use/express.ts index 98fd2837..b37ee638 100644 --- a/src/use/express.ts +++ b/src/use/express.ts @@ -5,6 +5,24 @@ import { OperationContext, } from '../handler'; +/** + * Create a GraphQL over HTTP Protocol compliant request handler for + * the express framework. + * + * ```js + * import express from 'express'; // yarn add express + * import { createHandler } from 'graphql-http/lib/use/express'; + * import { schema } from './my-graphql-schema'; + * + * const app = express(); + * app.all('/graphql', createHandler({ schema })); + * + * app.listen({ port: 4000 }); + * console.log('Listening to port 4000'); + * ``` + * + * @category Server/express + */ export function createHandler( options: HandlerOptions, ): Handler { diff --git a/src/use/fastify.ts b/src/use/fastify.ts index 4fe0a495..4111a9a0 100644 --- a/src/use/fastify.ts +++ b/src/use/fastify.ts @@ -5,6 +5,24 @@ import { OperationContext, } from '../handler'; +/** + * Create a GraphQL over HTTP Protocol compliant request handler for + * the fastify framework. + * + * ```js + * import Fastify from 'fastify'; // yarn add fastify + * import { createHandler } from 'graphql-http/lib/use/express'; + * import { schema } from './my-graphql-schema'; + * + * const fastify = Fastify(); + * fastify.all('/graphql', createHandler({ schema })); + * + * fastify.listen({ port: 4000 }); + * console.log('Listening to port 4000'); + * ``` + * + * @category Server/fastify + */ export function createHandler( options: HandlerOptions, ): RouteHandler { diff --git a/src/use/fetch.ts b/src/use/fetch.ts index 0d9d21b4..81c8b8b9 100644 --- a/src/use/fetch.ts +++ b/src/use/fetch.ts @@ -4,12 +4,43 @@ import { OperationContext, } from '../handler'; +/** + * The necessary API from the fetch environment for the handler. + * + * @category Server/fetch + */ export interface FetchAPI { Response: typeof Response; ReadableStream: typeof ReadableStream; TextEncoder: typeof TextEncoder; } +/** + * Create a GraphQL over HTTP Protocol compliant request handler for + * a fetch environment like Deno, Bun, CloudFlare Workers, Lambdas, etc. + * + * You can use [@whatwg-node/server](https://github.com/ardatan/whatwg-node/tree/master/packages/server) to create a server adapter and + * isomorphically use it in _any_ environment. See an example: + * + * ```js + * import http from 'http'; + * import { createServerAdapter } from '@whatwg-node/server'; // yarn add @whatwg-node/server + * import { createHandler } from 'graphql-http/lib/use/fetch'; + * import { schema } from './my-graphql-step'; + * + * // Use this adapter in _any_ environment. + * const adapter = createServerAdapter({ + * handleRequest: createHandler({ schema }), + * }); + * + * const server = http.createServer(adapter); + * + * server.listen(4000); + * console.log('Listening to port 4000'); + * ``` + * + * @category Server/fetch + */ export function createHandler( options: HandlerOptions, fetchApi: Partial = {}, diff --git a/src/use/node.ts b/src/use/node.ts index 6dbaf79c..3036ce94 100644 --- a/src/use/node.ts +++ b/src/use/node.ts @@ -5,6 +5,23 @@ import { OperationContext, } from '../handler'; +/** + * Create a GraphQL over HTTP Protocol compliant request handler for + * the Node environment. + * + * ```js + * import http from 'http'; + * import { createHandler } from 'graphql-http/lib/use/node'; + * import { schema } from './my-graphql-step'; + * + * const server = http.createServer(createHandler({ schema })); + * + * server.listen(4000); + * console.log('Listening to port 4000'); + * ``` + * + * @category Server/node + */ export function createHandler( options: HandlerOptions, ): RequestListener { diff --git a/typedoc.js b/typedoc.js index c3880bd7..f5105555 100644 --- a/typedoc.js +++ b/typedoc.js @@ -1,4 +1,5 @@ module.exports = { + entryPointStrategy: 'expand', out: './docs', readme: 'none', plugin: 'typedoc-plugin-markdown', From 6579e8db330f2ee4f72c8dca9d246d377dbc864b Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Sun, 6 Nov 2022 14:00:06 +0100 Subject: [PATCH 12/19] explain fetchApi --- docs/modules/use_fetch.md | 8 ++++---- src/use/fetch.ts | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/modules/use_fetch.md b/docs/modules/use_fetch.md index 27e7b02e..f3319d47 100644 --- a/docs/modules/use_fetch.md +++ b/docs/modules/use_fetch.md @@ -49,10 +49,10 @@ console.log('Listening to port 4000'); #### Parameters -| Name | Type | -| :------ | :------ | -| `options` | [`HandlerOptions`](../interfaces/handler.HandlerOptions.md)<`Request`, [`FetchAPI`](../interfaces/use_fetch.FetchAPI.md), `Context`\> | -| `fetchApi` | `Partial`<[`FetchAPI`](../interfaces/use_fetch.FetchAPI.md)\> | +| Name | Type | Description | +| :------ | :------ | :------ | +| `options` | [`HandlerOptions`](../interfaces/handler.HandlerOptions.md)<`Request`, [`FetchAPI`](../interfaces/use_fetch.FetchAPI.md), `Context`\> | - | +| `fetchApi` | `Partial`<[`FetchAPI`](../interfaces/use_fetch.FetchAPI.md)\> | Custom fetch API engine, will use from global scope if left undefined. | #### Returns diff --git a/src/use/fetch.ts b/src/use/fetch.ts index 81c8b8b9..4ca29b89 100644 --- a/src/use/fetch.ts +++ b/src/use/fetch.ts @@ -39,6 +39,8 @@ export interface FetchAPI { * console.log('Listening to port 4000'); * ``` * + * @param fetchApi - Custom fetch API engine, will use from global scope if left undefined. + * * @category Server/fetch */ export function createHandler( From 0fb2d2cf0c875bcf4b5b53f5552dc04e887a9b29 Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Sun, 6 Nov 2022 19:51:10 +0100 Subject: [PATCH 13/19] tests --- src/__tests__/use.ts | 86 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 src/__tests__/use.ts diff --git a/src/__tests__/use.ts b/src/__tests__/use.ts new file mode 100644 index 00000000..830dfa78 --- /dev/null +++ b/src/__tests__/use.ts @@ -0,0 +1,86 @@ +import { fetch } from '@whatwg-node/fetch'; +import http from 'http'; +import express from 'express'; +import fastify from 'fastify'; +import { createServerAdapter } from '@whatwg-node/server'; +import { startDisposableServer } from './utils/tserver'; +import { serverAudits } from '../audits'; +import { schema } from './fixtures/simple'; + +import { createHandler as createNodeHandler } from '../use/node'; +import { createHandler as createExpressHandler } from '../use/express'; +import { createHandler as createFastifyHandler } from '../use/fastify'; +import { createHandler as createFetchHandler } from '../use/fetch'; + +describe('node', () => { + const [url, dispose] = startDisposableServer( + http.createServer(createNodeHandler({ schema })), + ); + afterAll(dispose); + + for (const audit of serverAudits({ url, fetchFn: fetch })) { + it(audit.name, async () => { + const result = await audit.fn(); + if (result.status !== 'ok') { + throw result.reason; + } + }); + } +}); + +describe('express', () => { + const app = express(); + app.all('/', createExpressHandler({ schema })); + + const [url, dispose] = startDisposableServer(app.listen(0)); + afterAll(dispose); + + for (const audit of serverAudits({ url, fetchFn: fetch })) { + it(audit.name, async () => { + const result = await audit.fn(); + if (result.status !== 'ok') { + throw result.reason; + } + }); + } +}); + +describe('fastify', () => { + const app = fastify(); + app.all('/', createFastifyHandler({ schema })); + + // call ready since we're not calling listen + app.ready(); + + const [url, dispose] = startDisposableServer(app.server); + afterAll(dispose); + + for (const audit of serverAudits({ url, fetchFn: fetch })) { + it(audit.name, async () => { + const result = await audit.fn(); + if (result.status !== 'ok') { + throw result.reason; + } + }); + } +}); + +describe('fetch', () => { + const [url, dispose] = startDisposableServer( + http.createServer( + createServerAdapter({ + handleRequest: createFetchHandler({ schema }), + }), + ), + ); + afterAll(dispose); + + for (const audit of serverAudits({ url, fetchFn: fetch })) { + it(audit.name, async () => { + const result = await audit.fn(); + if (result.status !== 'ok') { + throw result.reason; + } + }); + } +}); From d2af3bf0b2727450b2ed02d5ee6c8ee9abcb665f Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Sun, 6 Nov 2022 19:51:45 +0100 Subject: [PATCH 14/19] tserver dont listen if already listening --- src/__tests__/utils/tserver.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/__tests__/utils/tserver.ts b/src/__tests__/utils/tserver.ts index 99d05bb0..c0e4f4b8 100644 --- a/src/__tests__/utils/tserver.ts +++ b/src/__tests__/utils/tserver.ts @@ -93,7 +93,9 @@ export function startDisposableServer( }; leftovers.push(dispose); - server.listen(0); + if (!server.listening) { + server.listen(0); + } const { port } = server.address() as net.AddressInfo; const url = `http://localhost:${port}`; From 0039907dafa08c193bbd4415ecc06b5da1a2518e Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Sun, 6 Nov 2022 20:19:16 +0100 Subject: [PATCH 15/19] fix fastify body --- src/use/fastify.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/use/fastify.ts b/src/use/fastify.ts index 4111a9a0..b7c32638 100644 --- a/src/use/fastify.ts +++ b/src/use/fastify.ts @@ -42,7 +42,8 @@ export function createHandler( reply .status(init.status) .headers(init.headers || {}) - .send(body); + // "or undefined" because `null` will be JSON stringified + .send(body || undefined); } catch (err) { // The handler shouldnt throw errors. // If you wish to handle them differently, consider implementing your own request handler. From b64e8ab17e77d635d8ff7b1f2da3502fd58ce77a Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Sun, 6 Nov 2022 20:23:21 +0100 Subject: [PATCH 16/19] json as string always for fastify --- src/__tests__/use.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/__tests__/use.ts b/src/__tests__/use.ts index 830dfa78..9d0721f7 100644 --- a/src/__tests__/use.ts +++ b/src/__tests__/use.ts @@ -47,6 +47,16 @@ describe('express', () => { describe('fastify', () => { const app = fastify(); + + // otherwise will throw error code 400 when json data is malformed + app.addContentTypeParser( + 'application/json', + { parseAs: 'string' }, + function (_, data, done) { + done(null, String(data)); + }, + ); + app.all('/', createFastifyHandler({ schema })); // call ready since we're not calling listen From ecf6cdb76a21aeb862e0e1cb2ed471ac35c3e260 Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Sun, 6 Nov 2022 20:27:37 +0100 Subject: [PATCH 17/19] actually return body parsing in express --- src/use/express.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/use/express.ts b/src/use/express.ts index b37ee638..9e09d3fa 100644 --- a/src/use/express.ts +++ b/src/use/express.ts @@ -39,7 +39,7 @@ export function createHandler( // in case express has a body parser return req.body; } - new Promise((resolve) => { + return new Promise((resolve) => { let body = ''; req.on('data', (chunk) => (body += chunk)); req.on('end', () => resolve(body)); From 0d1ffa08e05a51d63d45a7e833ec2960e2761473 Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Sun, 6 Nov 2022 20:28:01 +0100 Subject: [PATCH 18/19] add @whatwg-node/fetch --- package.json | 1 + yarn.lock | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/package.json b/package.json index 1a209b53..caaccfa4 100644 --- a/package.json +++ b/package.json @@ -102,6 +102,7 @@ "@types/jest": "^29.0.0", "@typescript-eslint/eslint-plugin": "^5.36.1", "@typescript-eslint/parser": "^5.36.1", + "@whatwg-node/fetch": "^0.5.1", "babel-jest": "^29.0.1", "eslint": "^8.23.0", "eslint-config-prettier": "^8.5.0", diff --git a/yarn.lock b/yarn.lock index 1edf6389..43d122d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4517,6 +4517,22 @@ __metadata: languageName: node linkType: hard +"@whatwg-node/fetch@npm:^0.5.1": + version: 0.5.1 + resolution: "@whatwg-node/fetch@npm:0.5.1" + dependencies: + "@peculiar/webcrypto": ^1.4.0 + abort-controller: ^3.0.0 + busboy: ^1.6.0 + form-data-encoder: ^1.7.1 + formdata-node: ^4.3.1 + node-fetch: ^2.6.7 + undici: ^5.12.0 + web-streams-polyfill: ^3.2.0 + checksum: f8d43b1bbf7ab20fc36bb9cc1d83094e1bb3d4b8256abd41045a02c3c9569d01f64309646273a4a50838929875408fc66396ada336747d81b2d3f965830bd0b6 + languageName: node + linkType: hard + "@whatwg-node/server@npm:0.3.0": version: 0.3.0 resolution: "@whatwg-node/server@npm:0.3.0" @@ -7451,6 +7467,7 @@ __metadata: "@types/jest": ^29.0.0 "@typescript-eslint/eslint-plugin": ^5.36.1 "@typescript-eslint/parser": ^5.36.1 + "@whatwg-node/fetch": ^0.5.1 babel-jest: ^29.0.1 eslint: ^8.23.0 eslint-config-prettier: ^8.5.0 @@ -12603,6 +12620,15 @@ __metadata: languageName: node linkType: hard +"undici@npm:^5.12.0": + version: 5.12.0 + resolution: "undici@npm:5.12.0" + dependencies: + busboy: ^1.6.0 + checksum: fbc227704943c05aa3dc1630695e10309c17d0a535678594d136db107c50593248e9ace70e1ab77496a6c837bf14aa2ab3c501a7a6c45fb6277dbf0846e15ffe + languageName: node + linkType: hard + "unicode-canonical-property-names-ecmascript@npm:^2.0.0": version: 2.0.0 resolution: "unicode-canonical-property-names-ecmascript@npm:2.0.0" From c1a90d99b37f56ab1f7037b17b6e58e296285b6f Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Sun, 6 Nov 2022 20:44:15 +0100 Subject: [PATCH 19/19] fetch needs path --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 197dfb15..316fc3d0 100644 --- a/README.md +++ b/README.md @@ -152,6 +152,7 @@ const handler = createHandler({ schema }); // Start serving on `/graphql` using the handler await serve( (req: Request) => { + const [path, _search] = req.url.split('?'); if (path.endsWith('/graphql')) { return handler(req); } else { @@ -177,6 +178,7 @@ const handler = createHandler({ schema }); export default { port: 4000, // Listening to port 4000 fetch(req) { + const [path, _search] = req.url.split('?'); if (path.endsWith('/graphql')) { return handler(req); } else {