1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
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;
};
}
|