@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.
@@ -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 Peer, defineHooks } from "crossws";
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
- * TODO: make it generic when crossws implements the WS API (Peer.close, Peer.protocol)
22
- * instead of accessing the socket directly through context (server agnostic)
23
- * https://github.com/unjs/crossws/issues/23
24
- * https://github.com/unjs/crossws/issues/16
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
- // TODO Getting the socket like this causes problems deploying to bun. We really need a better websocket implementation with crossws
32
- // Websocket from the H3 instance, so this is the browser client
33
- const socket = peer.websocket as Required<WebSocket>;
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
- protocol: socket.protocol, // will be validated
39
- send: (data) =>
40
- new Promise((resolve, reject) => {
41
- socket.send(data, (err: Error) =>
42
- err ? reject(err) : resolve(),
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
- socket.close(code, reason);
51
+ peer.close(code, reason);
47
52
  },
53
+
48
54
  onMessage: (cb) => {
49
- socket.on("message", async (event) => {
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
- // socket.once("close", (_socket, code: number, reason: Buffer) => closed(code, reason.toString()));
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.6",
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
  }