aboutsummaryrefslogtreecommitdiff
path: root/source/ws.ts
diff options
context:
space:
mode:
authornetop://ウィビ <paul@webb.page>2026-05-03 12:40:04 -0700
committernetop://ウィビ <paul@webb.page>2026-05-03 12:40:04 -0700
commit52ee736bd2ac407952863c8e8397770bf1495a45 (patch)
treef982218600682f7714a5a5157e940acc0e0e8a13 /source/ws.ts
parent83f2352fef7dc93396e3cf8f224acba271f1ef9d (diff)
downloadgq-52ee736bd2ac407952863c8e8397770bf1495a45.tar.gz
gq-52ee736bd2ac407952863c8e8397770bf1495a45.zip
adds support for subscriptions
Diffstat (limited to 'source/ws.ts')
-rw-r--r--source/ws.ts99
1 files changed, 99 insertions, 0 deletions
diff --git a/source/ws.ts b/source/ws.ts
new file mode 100644
index 0000000..fdd9fb1
--- /dev/null
+++ b/source/ws.ts
@@ -0,0 +1,99 @@
+
+
+
+/*** IMPORT ------------------------------------------- ***/
+
+import { makeServer, type ServerOptions } from "graphql-ws";
+
+/*** UTILITY ------------------------------------------ ***/
+
+import type { GraphQLSchema } from "graphql";
+
+/*** EXPORT ------------------------------------------- ***/
+
+/**
+ * Configuration accepted by {@link GraphQLWS}.
+ *
+ * Wraps `graphql-ws`'s {@link ServerOptions} and requires a `schema`. Anything
+ * else accepted by `graphql-ws` (custom `execute`, `subscribe`, `onConnect`,
+ * `onSubscribe`, etc.) can be passed through.
+ */
+export type WSOptions<Ctx = unknown> = {
+ /** Builds the context value passed to resolvers. Runs per WS connection. */
+ context?: (ctx: { connectionParams?: Record<string, unknown> }) => Ctx | Promise<Ctx>;
+ /** The executable schema to subscribe against. */
+ schema: GraphQLSchema;
+} & Partial<Omit<ServerOptions, "schema" | "context">>;
+
+/**
+ * Builds a WebSocket subscription handler that speaks the
+ * `graphql-transport-ws` protocol via `graphql-ws`.
+ *
+ * The returned function expects a Fetch `Request` with an `Upgrade: websocket`
+ * header. It performs the upgrade with `Deno.upgradeWebSocket` and hands the
+ * socket to `graphql-ws`. Pass the result as `subscriptions` to {@link GraphQLHTTP}
+ * so HTTP and WS share one endpoint.
+ *
+ * @example
+ * ```ts
+ * import { executeSchema, GraphQLHTTP, GraphQLWS, PubSub, gql } from "@eol/gq";
+ *
+ * const pubsub = new PubSub();
+ *
+ * const schema = executeSchema({
+ * resolvers: {
+ * Subscription: {
+ * pinged: { subscribe: () => pubsub.asyncIterator(["PING"]) }
+ * }
+ * },
+ * typeDefs: gql`type Subscription { pinged: String }`
+ * });
+ *
+ * const subscriptions = GraphQLWS({ schema });
+ * Deno.serve(GraphQLHTTP({ schema, subscriptions }));
+ * ```
+ */
+export function GraphQLWS<Ctx = unknown>(options: WSOptions<Ctx>) {
+ const server = makeServer({
+ ...options,
+ context: options.context as ServerOptions["context"],
+ schema: options.schema
+ });
+
+ return (request: Request): Response => {
+ const { response, socket } = Deno.upgradeWebSocket(request, {
+ protocol: "graphql-transport-ws"
+ });
+
+ socket.onopen = () => {
+ const closed = server.opened(
+ {
+ close: (code, reason) => socket.close(code, reason),
+ onMessage: (cb) => {
+ socket.onmessage = async (event) => {
+ try {
+ await cb(
+ typeof event.data === "string" ?
+ event.data :
+ await (event.data as Blob).text()
+ );
+ } catch (err) {
+ socket.close(1011, (err as Error).message);
+ }
+ };
+ },
+ protocol: socket.protocol,
+ send: (data) => Promise.resolve(socket.send(data))
+ },
+ {
+ request,
+ socket
+ }
+ );
+
+ socket.onclose = (event) => closed(event.code, event.reason);
+ };
+
+ return response;
+ };
+}