graphql-sse
Version:
Zero-dependency, HTTP/1 safe, simple, GraphQL over Server-Sent Events Protocol server and client
97 lines (96 loc) • 3.25 kB
JavaScript
import { createHandler as createRawHandler, } from '../handler.mjs';
/**
* The ready-to-use handler for [Koa](https://expressjs.com).
*
* Errors thrown from the provided options or callbacks (or even due to
* library misuse or potential bugs) will reject the handler or bubble to the
* returned iterator. 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.
*
* ```js
* import Koa from 'koa'; // yarn add koa
* import mount from 'koa-mount'; // yarn add koa-mount
* import { createHandler } from 'graphql-sse/lib/use/koa';
* import { schema } from './my-graphql/index.mjs';
*
* const app = new Koa();
* app.use(
* mount('/graphql/stream', async (ctx, next) => {
* try {
* await handler(ctx, next);
* } catch (err) {
* console.error(err);
* ctx.response.status = 500;
* ctx.response.message = 'Internal Server Error';
* }
* }),
* );
*
* app.listen({ port: 4000 });
* console.log('Listening to port 4000');
* ```
*
* @category Server/koa
*/
export function createHandler(options) {
const handler = createRawHandler(options);
return async function requestListener(ctx) {
const [body, init] = await handler({
url: ctx.url,
method: ctx.method,
headers: {
get(key) {
const header = ctx.headers[key];
return Array.isArray(header) ? header.join('\n') : header;
},
},
body: () => {
// in case koa has a body parser
const body = ctx.request.body ||
ctx.req.body;
if (body) {
return body;
}
return new Promise((resolve) => {
let body = '';
ctx.req.on('data', (chunk) => (body += chunk));
ctx.req.on('end', () => resolve(body));
});
},
raw: ctx.req,
context: ctx,
});
ctx.response.status = init.status;
ctx.response.message = init.statusText;
if (init.headers) {
for (const [name, value] of Object.entries(init.headers)) {
ctx.response.set(name, value);
}
}
if (!body || typeof body === 'string') {
ctx.body = body;
return;
}
ctx.res.once('close', body.return);
for await (const value of body) {
const closed = await new Promise((resolve, reject) => {
if (!ctx.res.writable) {
// response's close event might be late
resolve(true);
}
else {
ctx.res.write(value, (err) => (err ? reject(err) : resolve(false)));
}
});
if (closed) {
break;
}
}
ctx.res.off('close', body.return);
return new Promise((resolve) => ctx.res.end(resolve));
};
}