UNPKG

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
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)); }; }