@carvajalconsultants/headstart 1.0.6 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/graphQLRouteHandler.ts +56 -34
- package/package.json +1 -2
package/graphQLRouteHandler.ts
CHANGED
|
@@ -3,13 +3,11 @@ import { makeGraphQLWSConfig } from "postgraphile/grafserv";
|
|
|
3
3
|
import { grafserv } from "postgraphile/grafserv/h3/v1";
|
|
4
4
|
import { defineEventHandler, getHeader, toWebRequest } from "vinxi/http";
|
|
5
5
|
|
|
6
|
-
import type { IncomingMessage } from "node:http";
|
|
7
6
|
import type { StartAPIHandlerCallback } from "@tanstack/start/api";
|
|
8
|
-
import { type Hooks, type
|
|
7
|
+
import { type Hooks, type Message, type Peer } from "crossws";
|
|
9
8
|
import type { GrafservBase } from "grafserv";
|
|
10
9
|
import type { H3Grafserv } from "grafserv/h3/v1";
|
|
11
10
|
import type { PostGraphileInstance } from "postgraphile";
|
|
12
|
-
import type { WebSocket } from "ws";
|
|
13
11
|
|
|
14
12
|
/**
|
|
15
13
|
* This is an H3 handler that does all of the GraphQL request processing in TSR (including Subscriptions).
|
|
@@ -17,53 +15,77 @@ import type { WebSocket } from "ws";
|
|
|
17
15
|
* Code is basically from: https://discord.com/channels/489127045289476126/498852330754801666/1260251871877271704
|
|
18
16
|
*/
|
|
19
17
|
|
|
18
|
+
type PeerState = {
|
|
19
|
+
onMessage: (data: string) => Promise<void>;
|
|
20
|
+
closed: (code: number, reason: string) => void;
|
|
21
|
+
};
|
|
22
|
+
|
|
20
23
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
24
|
+
* Builds the crossws WebSocket hooks for graphql-ws.
|
|
25
|
+
*
|
|
26
|
+
* Uses the platform-agnostic crossws peer API (peer.send, peer.close, peer.id)
|
|
27
|
+
* instead of the Node.js ws-library EventEmitter API (socket.on, socket.send(cb)).
|
|
28
|
+
* This makes subscriptions work on both Node.js and Bun runtimes.
|
|
25
29
|
*/
|
|
26
30
|
function makeWsHandler(instance: H3Grafserv): Partial<Hooks> {
|
|
27
31
|
const graphqlWsServer = makeServer(makeGraphQLWSConfig(instance));
|
|
32
|
+
const peerStates = new Map<string, PeerState>();
|
|
28
33
|
|
|
29
34
|
return {
|
|
30
|
-
open(peer) {
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
|
|
35
|
+
open(peer: Peer) {
|
|
36
|
+
// graphql-ws registers the message callback synchronously inside opened(),
|
|
37
|
+
// so messageCallback is guaranteed to be set before opened() returns.
|
|
38
|
+
let messageCallback: ((data: string) => Promise<void>) | null = null;
|
|
34
39
|
|
|
35
|
-
// a new socket opened, let graphql-ws take over
|
|
36
40
|
const closed = graphqlWsServer.opened(
|
|
37
41
|
{
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
// crossws polyfills peer.websocket.protocol from the Sec-WebSocket-Protocol header
|
|
43
|
+
protocol: peer.websocket.protocol ?? "",
|
|
44
|
+
|
|
45
|
+
// peer.send() works on both Node.js and Bun — no callback, no hanging Promise
|
|
46
|
+
send: async (data) => {
|
|
47
|
+
peer.send(data);
|
|
48
|
+
},
|
|
49
|
+
|
|
45
50
|
close: (code, reason) => {
|
|
46
|
-
|
|
51
|
+
peer.close(code, reason);
|
|
47
52
|
},
|
|
53
|
+
|
|
48
54
|
onMessage: (cb) => {
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
await cb(event.toString());
|
|
52
|
-
} catch (err) {
|
|
53
|
-
try {
|
|
54
|
-
socket.close(CloseCode.InternalServerError, err.message);
|
|
55
|
-
} catch {
|
|
56
|
-
// noop
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
});
|
|
55
|
+
messageCallback = cb;
|
|
60
56
|
},
|
|
61
57
|
},
|
|
62
|
-
// pass values to the `extra` field in the context
|
|
63
|
-
//{ peer, socket, request },
|
|
64
58
|
{},
|
|
65
59
|
);
|
|
66
|
-
|
|
60
|
+
|
|
61
|
+
if (messageCallback) {
|
|
62
|
+
peerStates.set(peer.id, { onMessage: messageCallback, closed });
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
// crossws calls this hook when a message arrives — works on both Node.js and Bun
|
|
67
|
+
async message(peer: Peer, message: Message) {
|
|
68
|
+
const state = peerStates.get(peer.id);
|
|
69
|
+
if (!state) return;
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
await state.onMessage(message.text());
|
|
73
|
+
} catch (err) {
|
|
74
|
+
try {
|
|
75
|
+
peer.close(CloseCode.InternalServerError, (err as Error).message);
|
|
76
|
+
} catch {
|
|
77
|
+
// noop
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
// Cleans up per-peer state when the connection closes
|
|
83
|
+
close(peer: Peer, details: { code?: number; reason?: string }) {
|
|
84
|
+
const state = peerStates.get(peer.id);
|
|
85
|
+
if (!state) return;
|
|
86
|
+
|
|
87
|
+
state.closed(details.code ?? 1000, details.reason ?? "");
|
|
88
|
+
peerStates.delete(peer.id);
|
|
67
89
|
},
|
|
68
90
|
};
|
|
69
91
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@carvajalconsultants/headstart",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "Library to assist in integrating PostGraphile with Tanstack Start and URQL.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Miguel Carvajal <omar@carvajalonline.com>",
|
|
@@ -27,7 +27,6 @@
|
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@biomejs/biome": "^1.9.4",
|
|
30
|
-
"@types/ws": "^8.5.14",
|
|
31
30
|
"typescript": "^5.7.3"
|
|
32
31
|
}
|
|
33
32
|
}
|