UNPKG

grafserv

Version:

A highly optimized server for GraphQL, powered by Grafast

274 lines 11.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.grafserv = exports.HonoGrafserv = void 0; const graphql_ws_1 = require("graphql-ws"); const index_js_1 = require("../../../index.js"); const utils_js_1 = require("../../../utils.js"); function getDigest(ctx) { const req = ctx.req; return { httpVersionMajor: 1, // Hono uses Fetch API, which doesn't expose HTTP version httpVersionMinor: 1, isSecure: req.url.startsWith("https:"), method: req.method, path: req.path, headers: (0, index_js_1.processHeaders)(req.header()), getQueryParams() { return req.query(); }, async getBody() { const json = await req.json(); if (!json) { throw new Error("Failed to retrieve body from hono"); } return { type: "json", json, }; }, requestContext: { honov4: { ctx: ctx, }, }, }; } const utf8TextDecoder = new TextDecoder("utf-8"); class HonoGrafserv extends index_js_1.GrafservBase { constructor(config, upgradeWebSocket) { super(config); this.upgradeWebSocket = upgradeWebSocket; } makeWsHandler(upgradeWebSocket) { const graphqlWsServer = (0, graphql_ws_1.makeServer)((0, index_js_1.makeGraphQLWSConfig)(this)); return upgradeWebSocket((c) => { let onMessage; let onClose; let isOpened = false; const initGraphqlServer = (ws) => { onClose = graphqlWsServer.opened({ protocol: ws.protocol ?? graphql_ws_1.GRAPHQL_TRANSPORT_WS_PROTOCOL, send(data) { ws.send(data); }, close(code, reason) { console.log("close", code, reason); ws.close(code, reason); isOpened = false; }, onMessage(cb) { onMessage = cb; }, }, { socket: ws, request: c.req }); isOpened = true; }; const badData = (ws, e) => { console.error(`Didn't understand the data`, e); try { ws.close(1003, "Unsupported data"); } catch (e) { console.error(`Failed to close websocket, maybe it's already closed`, e); } }; return { onOpen(evt, ws) { initGraphqlServer(ws); }, onMessage(evt, ws) { // cloudflare workers don't support the open event // so we initialize the server on the first message if (!isOpened) { initGraphqlServer(ws); } const { data } = evt; if (typeof data === "string") { onMessage?.(data); } else if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) { let text; try { text = utf8TextDecoder.decode(data instanceof ArrayBuffer ? data : data.buffer); } catch (e) { badData(ws, e); return; } onMessage?.(text); } else if (data instanceof Blob) { (async () => { let text; try { const arrayBuffer = await data.arrayBuffer(); text = utf8TextDecoder.decode(arrayBuffer); } catch (e) { badData(ws, e); return; } onMessage?.(text); })().catch(utils_js_1.noop); } else { console.warn("Unexpected WebSocket data type", typeof data); } }, onClose(evt) { onClose?.(evt.code, evt.reason); }, onError(evt) { console.error("An error occured in the websocket:", evt); }, }; }); } async handleGraphQLEvent(ctx) { const digest = getDigest(ctx); const handlerResult = await this.graphqlHandler((0, index_js_1.normalizeRequest)(digest), this.graphiqlHandler); const result = await (0, index_js_1.convertHandlerResultToResult)(handlerResult); return this.send(ctx, result); } async handleGraphiqlEvent(ctx) { const digest = getDigest(ctx); const handlerResult = await this.graphiqlHandler((0, index_js_1.normalizeRequest)(digest)); const result = await (0, index_js_1.convertHandlerResultToResult)(handlerResult); return this.send(ctx, result); } async handleGraphiqlStaticEvent(ctx) { const digest = getDigest(ctx); const handlerResult = await this.graphiqlStaticHandler((0, index_js_1.normalizeRequest)(digest)); const result = await (0, index_js_1.convertHandlerResultToResult)(handlerResult); return this.send(ctx, result); } async handleEventStreamEvent(ctx) { const digest = getDigest(ctx); const handlerResult = { type: "event-stream", request: (0, index_js_1.normalizeRequest)(digest), dynamicOptions: this.dynamicOptions, payload: this.makeStream(), statusCode: 200, }; const result = await (0, index_js_1.convertHandlerResultToResult)(handlerResult); return this.send(ctx, result); } async send(ctx, result) { if (result === null) { // 404 ctx.status(404); return ctx.text("¯\\_(ツ)_/¯"); } switch (result.type) { case "error": { const { statusCode, headers } = result; this.setResponseHeaders(ctx, headers); ctx.status(statusCode); const errorWithStatus = Object.assign(result.error, { status: statusCode, }); throw errorWithStatus; } case "buffer": { const { statusCode, headers, buffer } = result; this.setResponseHeaders(ctx, headers); ctx.status(statusCode); return ctx.body(buffer); } case "json": { const { statusCode, headers, json } = result; this.setResponseHeaders(ctx, headers); ctx.status(statusCode); // @ts-expect-error Some issue with types causing "excessively deep and possibly infinite" return ctx.json(json); } case "noContent": { const { statusCode, headers } = result; this.setResponseHeaders(ctx, headers); ctx.status(statusCode); return ctx.body(null); } case "bufferStream": { // TODO : handle bufferStream? console.log("bufferStream is not handled yet"); // Force the iterator to close const { bufferIterator } = result; if (bufferIterator.return) { bufferIterator.return(); } else if (bufferIterator.throw) { bufferIterator.throw(new Error("Unimplemented")); } this.setResponseHeaders(ctx, { "Content-Type": "text/plain" }); ctx.status(501); return ctx.text("Server hasn't implemented this yet"); } default: { const never = result; console.log("Unhandled:"); console.dir(never); this.setResponseHeaders(ctx, { "Content-Type": "text/plain" }); ctx.status(501); return ctx.text("Server hasn't implemented this yet"); } } } async addTo(app) { const dynamicOptions = this.dynamicOptions; if (this.resolvedPreset.grafserv?.websockets && !this.upgradeWebSocket) { throw new Error("grafserv.websockets is enabled but no upgradeWebSocket was provided"); } if (!this.resolvedPreset.grafserv?.websockets && this.upgradeWebSocket) { console.warn("UpgradeWebSocket was provided but grafserv.websockets is disabled - websockets will not be activated"); } app.post(this.dynamicOptions.graphqlPath, (c) => this.handleGraphQLEvent(c)); const websocketHandler = this.resolvedPreset.grafserv?.websockets && this.upgradeWebSocket ? this.makeWsHandler(this.upgradeWebSocket) : undefined; const shouldServeGetHandler = this.dynamicOptions.graphqlOverGET || this.dynamicOptions.graphiqlOnGraphQLGET || websocketHandler; if (shouldServeGetHandler) { app.get(this.dynamicOptions.graphqlPath, (c, next) => { if (c.req.header("Upgrade") === "websocket" && websocketHandler) { return websocketHandler(c, next); } return this.handleGraphQLEvent(c); }); } if (dynamicOptions.graphiql) { app.get(this.dynamicOptions.graphiqlPath, (c) => this.handleGraphiqlEvent(c)); } if (dynamicOptions.watch) { app.get(this.dynamicOptions.eventStreamPath, (c) => this.handleEventStreamEvent(c)); } if (dynamicOptions.graphiql) { // Must come last, because wildcard app.get(this.dynamicOptions.graphiqlStaticPath + "*", (c) => this.handleGraphiqlStaticEvent(c)); } } setResponseHeaders(ctx, headers) { for (const key in headers) { if (key.toLowerCase() === "content-length") { // H4 takes care of setting Content-Length for us continue; } ctx.header(key, headers[key]); } } } exports.HonoGrafserv = HonoGrafserv; /** * Creates a new instance of HonoGrafserv. * * @param config - The configuration object for Grafserv. * @param upgradeWebSocket - Optional parameter required when using websockets. * Hono uses the upgradeWebsocket helper depending on the environment. * Refer to https://hono.dev/docs/helpers/websocket for more details. * @returns An instance of HonoGrafserv. */ const grafserv = (config, upgradeWebSocket) => { return new HonoGrafserv(config, upgradeWebSocket); }; exports.grafserv = grafserv; //# sourceMappingURL=index.js.map