@trpc/server 11.0.0-rc.369 → 11.0.0-rc.373

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.
@@ -8,27 +8,29 @@ require('../../unstable-core-do-not-import/rootConfig.js');
8
8
  */ function incomingMessageToBodyStream(req, opts) {
9
9
  let size = 0;
10
10
  const maxBodySize = opts.maxBodySize;
11
- let controller = null;
11
+ let hasClosed = false;
12
12
  const stream = new ReadableStream({
13
- start (c) {
14
- controller = c;
15
- },
16
- async pull (c) {
17
- const chunk = req.read();
18
- if (chunk) {
13
+ start (controller) {
14
+ req.on('data', (chunk)=>{
19
15
  size += chunk.length;
20
- }
21
- if (maxBodySize !== null && size > maxBodySize) {
22
- controller.error(new TRPCError.TRPCError({
23
- code: 'PAYLOAD_TOO_LARGE'
24
- }));
25
- return;
26
- }
27
- if (chunk === null) {
28
- c.close();
29
- return;
30
- }
31
- controller.enqueue(chunk);
16
+ if (maxBodySize != null && size > maxBodySize) {
17
+ controller.error(new TRPCError.TRPCError({
18
+ code: 'PAYLOAD_TOO_LARGE'
19
+ }));
20
+ // an error is thrown if we try to close the controller after
21
+ // erroring, so track the closure
22
+ hasClosed = true;
23
+ return;
24
+ }
25
+ controller.enqueue(chunk);
26
+ });
27
+ req.once('end', ()=>{
28
+ if (hasClosed) {
29
+ return;
30
+ }
31
+ hasClosed = true;
32
+ controller.close();
33
+ });
32
34
  },
33
35
  cancel () {
34
36
  req.destroy();
@@ -6,27 +6,29 @@ import '../../unstable-core-do-not-import/rootConfig.mjs';
6
6
  */ function incomingMessageToBodyStream(req, opts) {
7
7
  let size = 0;
8
8
  const maxBodySize = opts.maxBodySize;
9
- let controller = null;
9
+ let hasClosed = false;
10
10
  const stream = new ReadableStream({
11
- start (c) {
12
- controller = c;
13
- },
14
- async pull (c) {
15
- const chunk = req.read();
16
- if (chunk) {
11
+ start (controller) {
12
+ req.on('data', (chunk)=>{
17
13
  size += chunk.length;
18
- }
19
- if (maxBodySize !== null && size > maxBodySize) {
20
- controller.error(new TRPCError({
21
- code: 'PAYLOAD_TOO_LARGE'
22
- }));
23
- return;
24
- }
25
- if (chunk === null) {
26
- c.close();
27
- return;
28
- }
29
- controller.enqueue(chunk);
14
+ if (maxBodySize != null && size > maxBodySize) {
15
+ controller.error(new TRPCError({
16
+ code: 'PAYLOAD_TOO_LARGE'
17
+ }));
18
+ // an error is thrown if we try to close the controller after
19
+ // erroring, so track the closure
20
+ hasClosed = true;
21
+ return;
22
+ }
23
+ controller.enqueue(chunk);
24
+ });
25
+ req.once('end', ()=>{
26
+ if (hasClosed) {
27
+ return;
28
+ }
29
+ hasClosed = true;
30
+ controller.close();
31
+ });
30
32
  },
31
33
  cancel () {
32
34
  req.destroy();
@@ -20,6 +20,23 @@ export type WSConnectionHandlerOptions<TRouter extends AnyRouter> = BaseHandlerO
20
20
  export type WSSHandlerOptions<TRouter extends AnyRouter> = WSConnectionHandlerOptions<TRouter> & {
21
21
  wss: ws.WebSocketServer;
22
22
  prefix?: string;
23
+ keepAlive?: {
24
+ /**
25
+ * Enable heartbeat messages
26
+ * @default false
27
+ */
28
+ enabled: boolean;
29
+ /**
30
+ * Heartbeat interval in milliseconds
31
+ * @default 30000
32
+ */
33
+ pingMs?: number;
34
+ /**
35
+ * Terminate the WebSocket if no pong is received after this many milliseconds
36
+ * @default 5000
37
+ */
38
+ pongWaitMs?: number;
39
+ };
23
40
  };
24
41
  export declare function getWSConnectionHandler<TRouter extends AnyRouter>(opts: WSConnectionHandlerOptions<TRouter>): (client: ws.WebSocket, req: IncomingMessage) => Promise<void>;
25
42
  export declare function applyWSSHandler<TRouter extends AnyRouter>(opts: WSSHandlerOptions<TRouter>): {
@@ -1 +1 @@
1
- {"version":3,"file":"ws.d.ts","sourceRoot":"","sources":["../../src/adapters/ws.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AAC5C,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,EACV,SAAS,EACT,qBAAqB,EACrB,kBAAkB,EACnB,MAAM,iBAAiB,CAAC;AAQzB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAY/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,aAAa,CAAC;AAQlE;;GAEG;AACH,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAC1C,8BAA8B,CAAC,eAAe,EAAE,EAAE,CAAC,SAAS,CAAC,EAC7D,MAAM,CACP,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,kBAAkB,CAAC,OAAO,SAAS,SAAS,IAAI,CAC1D,IAAI,EAAE,yBAAyB,KAC5B,YAAY,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAAC;AAE/C,MAAM,MAAM,0BAA0B,CAAC,OAAO,SAAS,SAAS,IAC9D,kBAAkB,CAAC,OAAO,EAAE,eAAe,CAAC,GAC1C,qBAAqB,CACnB,kBAAkB,CAAC,OAAO,CAAC,EAC3B,kBAAkB,CAAC,OAAO,CAAC,CAC5B,CAAC;AAEN;;GAEG;AACH,MAAM,MAAM,iBAAiB,CAAC,OAAO,SAAS,SAAS,IACrD,0BAA0B,CAAC,OAAO,CAAC,GAAG;IACpC,GAAG,EAAE,EAAE,CAAC,eAAe,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEJ,wBAAgB,sBAAsB,CAAC,OAAO,SAAS,SAAS,EAC9D,IAAI,EAAE,0BAA0B,CAAC,OAAO,CAAC,YAKnB,YAAY,OAAO,eAAe,mBAoPzD;AAED,wBAAgB,eAAe,CAAC,OAAO,SAAS,SAAS,EACvD,IAAI,EAAE,iBAAiB,CAAC,OAAO,CAAC;;EA2BjC"}
1
+ {"version":3,"file":"ws.d.ts","sourceRoot":"","sources":["../../src/adapters/ws.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AAC5C,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,EACV,SAAS,EACT,qBAAqB,EACrB,kBAAkB,EACnB,MAAM,iBAAiB,CAAC;AAQzB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAY/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,aAAa,CAAC;AAQlE;;GAEG;AACH,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAC1C,8BAA8B,CAAC,eAAe,EAAE,EAAE,CAAC,SAAS,CAAC,EAC7D,MAAM,CACP,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,kBAAkB,CAAC,OAAO,SAAS,SAAS,IAAI,CAC1D,IAAI,EAAE,yBAAyB,KAC5B,YAAY,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAAC;AAE/C,MAAM,MAAM,0BAA0B,CAAC,OAAO,SAAS,SAAS,IAC9D,kBAAkB,CAAC,OAAO,EAAE,eAAe,CAAC,GAC1C,qBAAqB,CACnB,kBAAkB,CAAC,OAAO,CAAC,EAC3B,kBAAkB,CAAC,OAAO,CAAC,CAC5B,CAAC;AAEN;;GAEG;AACH,MAAM,MAAM,iBAAiB,CAAC,OAAO,SAAS,SAAS,IACrD,0BAA0B,CAAC,OAAO,CAAC,GAAG;IACpC,GAAG,EAAE,EAAE,CAAC,eAAe,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE;QACV;;;WAGG;QACH,OAAO,EAAE,OAAO,CAAC;QACjB;;;WAGG;QACH,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB;;;WAGG;QACH,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;CACH,CAAC;AAEJ,wBAAgB,sBAAsB,CAAC,OAAO,SAAS,SAAS,EAC9D,IAAI,EAAE,0BAA0B,CAAC,OAAO,CAAC,YAKnB,YAAY,OAAO,eAAe,mBAoPzD;AAiCD,wBAAgB,eAAe,CAAC,OAAO,SAAS,SAAS,EACvD,IAAI,EAAE,iBAAiB,CAAC,OAAO,CAAC;;EA+BjC"}
@@ -259,14 +259,43 @@ function getWSConnectionHandler(opts) {
259
259
  await createContextAsync();
260
260
  };
261
261
  }
262
+ /**
263
+ * Handle WebSocket keep-alive messages
264
+ */ function handleKeepAlive(client, pingMs = 30000, pongWaitMs = 5000) {
265
+ let heartbeatTimeout;
266
+ const heartbeatInterval = setInterval(()=>{
267
+ if (client.readyState !== WEBSOCKET_OPEN) {
268
+ return;
269
+ }
270
+ // First we send a ping message and wait for a pong
271
+ client.ping();
272
+ // We set a timeout to close the connection if the pong is not received
273
+ heartbeatTimeout = setTimeout(()=>{
274
+ client.terminate();
275
+ clearInterval(heartbeatInterval);
276
+ }, pongWaitMs);
277
+ }, pingMs).unref();
278
+ // When we receive a pong message, we clear the timeout
279
+ client.on('pong', ()=>{
280
+ heartbeatTimeout && clearTimeout(heartbeatTimeout);
281
+ });
282
+ // If the connection is closed, we clear the interval
283
+ client.on('close', ()=>{
284
+ clearInterval(heartbeatInterval);
285
+ });
286
+ }
262
287
  function applyWSSHandler(opts) {
263
- const { wss , prefix } = opts;
288
+ const { wss , prefix , keepAlive } = opts;
264
289
  const onConnection = getWSConnectionHandler(opts);
265
290
  wss.on('connection', async (client, req)=>{
266
291
  if (prefix && !req.url?.startsWith(prefix)) {
267
292
  return;
268
293
  }
269
294
  await onConnection(client, req);
295
+ if (keepAlive?.enabled) {
296
+ const { pingMs , pongWaitMs } = keepAlive;
297
+ handleKeepAlive(client, pingMs, pongWaitMs);
298
+ }
270
299
  });
271
300
  return {
272
301
  broadcastReconnectNotification: ()=>{
@@ -257,14 +257,43 @@ function getWSConnectionHandler(opts) {
257
257
  await createContextAsync();
258
258
  };
259
259
  }
260
+ /**
261
+ * Handle WebSocket keep-alive messages
262
+ */ function handleKeepAlive(client, pingMs = 30000, pongWaitMs = 5000) {
263
+ let heartbeatTimeout;
264
+ const heartbeatInterval = setInterval(()=>{
265
+ if (client.readyState !== WEBSOCKET_OPEN) {
266
+ return;
267
+ }
268
+ // First we send a ping message and wait for a pong
269
+ client.ping();
270
+ // We set a timeout to close the connection if the pong is not received
271
+ heartbeatTimeout = setTimeout(()=>{
272
+ client.terminate();
273
+ clearInterval(heartbeatInterval);
274
+ }, pongWaitMs);
275
+ }, pingMs).unref();
276
+ // When we receive a pong message, we clear the timeout
277
+ client.on('pong', ()=>{
278
+ heartbeatTimeout && clearTimeout(heartbeatTimeout);
279
+ });
280
+ // If the connection is closed, we clear the interval
281
+ client.on('close', ()=>{
282
+ clearInterval(heartbeatInterval);
283
+ });
284
+ }
260
285
  function applyWSSHandler(opts) {
261
- const { wss , prefix } = opts;
286
+ const { wss , prefix , keepAlive } = opts;
262
287
  const onConnection = getWSConnectionHandler(opts);
263
288
  wss.on('connection', async (client, req)=>{
264
289
  if (prefix && !req.url?.startsWith(prefix)) {
265
290
  return;
266
291
  }
267
292
  await onConnection(client, req);
293
+ if (keepAlive?.enabled) {
294
+ const { pingMs , pongWaitMs } = keepAlive;
295
+ handleKeepAlive(client, pingMs, pongWaitMs);
296
+ }
268
297
  });
269
298
  return {
270
299
  broadcastReconnectNotification: ()=>{
@@ -1,12 +1,12 @@
1
1
  {
2
- "bundleSize": 85905,
3
- "bundleOrigSize": 132659,
4
- "bundleReduction": 35.24,
2
+ "bundleSize": 87199,
3
+ "bundleOrigSize": 134166,
4
+ "bundleReduction": 35.01,
5
5
  "modules": [
6
6
  {
7
7
  "id": "/src/adapters/ws.ts",
8
- "size": 10170,
9
- "origSize": 9440,
8
+ "size": 11241,
9
+ "origSize": 10870,
10
10
  "renderedExports": [
11
11
  "getWSConnectionHandler",
12
12
  "applyWSSHandler"
@@ -15,7 +15,7 @@
15
15
  "dependents": [
16
16
  "/src/adapters/fastify/fastifyTRPCPlugin.ts"
17
17
  ],
18
- "percent": 11.84,
18
+ "percent": 12.89,
19
19
  "reduction": 0
20
20
  },
21
21
  {
@@ -29,7 +29,7 @@
29
29
  "dependents": [
30
30
  "/src/unstable-core-do-not-import.ts"
31
31
  ],
32
- "percent": 11.05,
32
+ "percent": 10.89,
33
33
  "reduction": 4.97
34
34
  },
35
35
  {
@@ -44,7 +44,7 @@
44
44
  "/src/unstable-core-do-not-import.ts",
45
45
  "/src/unstable-core-do-not-import/initTRPC.ts"
46
46
  ],
47
- "percent": 7.36,
47
+ "percent": 7.25,
48
48
  "reduction": 59.52
49
49
  },
50
50
  {
@@ -63,7 +63,7 @@
63
63
  "/src/unstable-core-do-not-import/http/resolveResponse.ts",
64
64
  "/src/unstable-core-do-not-import/initTRPC.ts"
65
65
  ],
66
- "percent": 6.97,
66
+ "percent": 6.87,
67
67
  "reduction": 41.05
68
68
  },
69
69
  {
@@ -78,7 +78,7 @@
78
78
  "/src/unstable-core-do-not-import.ts",
79
79
  "/src/unstable-core-do-not-import/http/resolveResponse.ts"
80
80
  ],
81
- "percent": 6.46,
81
+ "percent": 6.36,
82
82
  "reduction": 0
83
83
  },
84
84
  {
@@ -92,7 +92,7 @@
92
92
  "dependents": [
93
93
  "/src/adapters/aws-lambda/index.ts"
94
94
  ],
95
- "percent": 5.65,
95
+ "percent": 5.56,
96
96
  "reduction": 13.62
97
97
  },
98
98
  {
@@ -109,7 +109,7 @@
109
109
  "/src/observable/index.ts",
110
110
  "/src/observable/operators.ts"
111
111
  ],
112
- "percent": 3.64,
112
+ "percent": 3.59,
113
113
  "reduction": 0.67
114
114
  },
115
115
  {
@@ -123,7 +123,7 @@
123
123
  "dependents": [
124
124
  "/src/adapters/next-app-dir.ts"
125
125
  ],
126
- "percent": 3.55,
126
+ "percent": 3.5,
127
127
  "reduction": 20.12
128
128
  },
129
129
  {
@@ -139,7 +139,7 @@
139
139
  "dependents": [
140
140
  "/src/observable/index.ts"
141
141
  ],
142
- "percent": 3.21,
142
+ "percent": 3.16,
143
143
  "reduction": 0
144
144
  },
145
145
  {
@@ -159,7 +159,7 @@
159
159
  "/src/unstable-core-do-not-import/router.ts",
160
160
  "/src/unstable-core-do-not-import/initTRPC.ts"
161
161
  ],
162
- "percent": 3.19,
162
+ "percent": 3.15,
163
163
  "reduction": 45.94
164
164
  },
165
165
  {
@@ -173,7 +173,7 @@
173
173
  "dependents": [
174
174
  "/src/unstable-core-do-not-import.ts"
175
175
  ],
176
- "percent": 3.09,
176
+ "percent": 3.04,
177
177
  "reduction": 41.46
178
178
  },
179
179
  {
@@ -193,7 +193,7 @@
193
193
  "/src/unstable-core-do-not-import/initTRPC.ts",
194
194
  "/src/unstable-core-do-not-import/procedureBuilder.ts"
195
195
  ],
196
- "percent": 3.06,
196
+ "percent": 3.01,
197
197
  "reduction": 55.5
198
198
  },
199
199
  {
@@ -207,9 +207,24 @@
207
207
  "dependents": [
208
208
  "/src/adapters/fetch/index.ts"
209
209
  ],
210
- "percent": 2.62,
210
+ "percent": 2.59,
211
211
  "reduction": 2.17
212
212
  },
213
+ {
214
+ "id": "/src/adapters/node-http/incomingMessageToRequest.ts",
215
+ "size": 2177,
216
+ "origSize": 2535,
217
+ "renderedExports": [
218
+ "incomingMessageToRequest"
219
+ ],
220
+ "removedExports": [],
221
+ "dependents": [
222
+ "/src/adapters/node-http/index.ts",
223
+ "/src/adapters/node-http/nodeHTTPRequestHandler.ts"
224
+ ],
225
+ "percent": 2.5,
226
+ "reduction": 14.12
227
+ },
213
228
  {
214
229
  "id": "/src/unstable-core-do-not-import/createProxy.ts",
215
230
  "size": 2000,
@@ -223,24 +238,9 @@
223
238
  "/src/unstable-core-do-not-import.ts",
224
239
  "/src/unstable-core-do-not-import/router.ts"
225
240
  ],
226
- "percent": 2.33,
241
+ "percent": 2.29,
227
242
  "reduction": 0
228
243
  },
229
- {
230
- "id": "/src/adapters/node-http/incomingMessageToRequest.ts",
231
- "size": 1954,
232
- "origSize": 2458,
233
- "renderedExports": [
234
- "incomingMessageToRequest"
235
- ],
236
- "removedExports": [],
237
- "dependents": [
238
- "/src/adapters/node-http/index.ts",
239
- "/src/adapters/node-http/nodeHTTPRequestHandler.ts"
240
- ],
241
- "percent": 2.27,
242
- "reduction": 20.5
243
- },
244
244
  {
245
245
  "id": "/src/unstable-core-do-not-import/error/TRPCError.ts",
246
246
  "size": 1733,
@@ -259,7 +259,7 @@
259
259
  "/src/unstable-core-do-not-import/http/contentType.ts",
260
260
  "/src/unstable-core-do-not-import/procedureBuilder.ts"
261
261
  ],
262
- "percent": 2.02,
262
+ "percent": 1.99,
263
263
  "reduction": 19.47
264
264
  },
265
265
  {
@@ -273,7 +273,7 @@
273
273
  "dependents": [
274
274
  "/src/adapters/node-http/index.ts"
275
275
  ],
276
- "percent": 2,
276
+ "percent": 1.97,
277
277
  "reduction": 20.03
278
278
  },
279
279
  {
@@ -285,7 +285,7 @@
285
285
  ],
286
286
  "removedExports": [],
287
287
  "dependents": [],
288
- "percent": 1.84,
288
+ "percent": 1.81,
289
289
  "reduction": 21.04
290
290
  },
291
291
  {
@@ -297,7 +297,7 @@
297
297
  ],
298
298
  "removedExports": [],
299
299
  "dependents": [],
300
- "percent": 1.8,
300
+ "percent": 1.77,
301
301
  "reduction": 26.94
302
302
  },
303
303
  {
@@ -310,7 +310,7 @@
310
310
  ],
311
311
  "removedExports": [],
312
312
  "dependents": [],
313
- "percent": 1.77,
313
+ "percent": 1.75,
314
314
  "reduction": 27.6
315
315
  },
316
316
  {
@@ -324,7 +324,7 @@
324
324
  "dependents": [
325
325
  "/src/adapters/fastify/index.ts"
326
326
  ],
327
- "percent": 1.75,
327
+ "percent": 1.72,
328
328
  "reduction": 34.7
329
329
  },
330
330
  {
@@ -341,7 +341,7 @@
341
341
  "/src/unstable-core-do-not-import/http/resolveResponse.ts",
342
342
  "/src/unstable-core-do-not-import/error/getErrorShape.ts"
343
343
  ],
344
- "percent": 1.47,
344
+ "percent": 1.45,
345
345
  "reduction": 23.5
346
346
  },
347
347
  {
@@ -356,7 +356,7 @@
356
356
  "/src/adapters/fastify/index.ts",
357
357
  "/src/adapters/fastify/fastifyTRPCPlugin.ts"
358
358
  ],
359
- "percent": 1.32,
359
+ "percent": 1.3,
360
360
  "reduction": 47.59
361
361
  },
362
362
  {
@@ -371,7 +371,7 @@
371
371
  "/src/unstable-core-do-not-import.ts",
372
372
  "/src/unstable-core-do-not-import/procedureBuilder.ts"
373
373
  ],
374
- "percent": 1.15,
374
+ "percent": 1.14,
375
375
  "reduction": 56.22
376
376
  },
377
377
  {
@@ -388,7 +388,7 @@
388
388
  "/src/adapters/next-app-dir/nextAppDirCaller.ts",
389
389
  "/src/adapters/next-app-dir/rethrowNextErrors.ts"
390
390
  ],
391
- "percent": 1.15,
391
+ "percent": 1.13,
392
392
  "reduction": 13.65
393
393
  },
394
394
  {
@@ -405,15 +405,15 @@
405
405
  "removedExports": [],
406
406
  "dependents": [
407
407
  "/src/unstable-core-do-not-import.ts",
408
+ "/src/unstable-core-do-not-import/rpc/parseTRPCMessage.ts",
408
409
  "/src/unstable-core-do-not-import/error/TRPCError.ts",
409
410
  "/src/unstable-core-do-not-import/router.ts",
410
411
  "/src/unstable-core-do-not-import/transformer.ts",
411
412
  "/src/unstable-core-do-not-import/middleware.ts",
412
- "/src/unstable-core-do-not-import/rpc/parseTRPCMessage.ts",
413
413
  "/src/unstable-core-do-not-import/http/contentType.ts",
414
414
  "/src/unstable-core-do-not-import/procedureBuilder.ts"
415
415
  ],
416
- "percent": 1.09,
416
+ "percent": 1.08,
417
417
  "reduction": 25.69
418
418
  },
419
419
  {
@@ -427,7 +427,7 @@
427
427
  "dependents": [
428
428
  "/src/adapters/next-app-dir/nextAppDirCaller.ts"
429
429
  ],
430
- "percent": 0.95,
430
+ "percent": 0.94,
431
431
  "reduction": 0.97
432
432
  },
433
433
  {
@@ -439,7 +439,7 @@
439
439
  ],
440
440
  "removedExports": [],
441
441
  "dependents": [],
442
- "percent": 0.88,
442
+ "percent": 0.87,
443
443
  "reduction": 66.9
444
444
  },
445
445
  {
@@ -454,7 +454,7 @@
454
454
  "/src/unstable-core-do-not-import.ts",
455
455
  "/src/unstable-core-do-not-import/http/resolveResponse.ts"
456
456
  ],
457
- "percent": 0.78,
457
+ "percent": 0.76,
458
458
  "reduction": 0.6
459
459
  },
460
460
  {
@@ -469,7 +469,7 @@
469
469
  "/src/unstable-core-do-not-import.ts",
470
470
  "/src/unstable-core-do-not-import/http/resolveResponse.ts"
471
471
  ],
472
- "percent": 0.73,
472
+ "percent": 0.72,
473
473
  "reduction": 43.49
474
474
  },
475
475
  {
@@ -482,7 +482,7 @@
482
482
  ],
483
483
  "removedExports": [],
484
484
  "dependents": [],
485
- "percent": 0.61,
485
+ "percent": 0.6,
486
486
  "reduction": 67.09
487
487
  },
488
488
  {
@@ -496,7 +496,7 @@
496
496
  "dependents": [
497
497
  "/src/adapters/next-app-dir/nextAppDirCaller.ts"
498
498
  ],
499
- "percent": 0.49,
499
+ "percent": 0.48,
500
500
  "reduction": 40.06
501
501
  },
502
502
  {
@@ -511,7 +511,7 @@
511
511
  "/src/unstable-core-do-not-import.ts",
512
512
  "/src/unstable-core-do-not-import/initTRPC.ts"
513
513
  ],
514
- "percent": 0.4,
514
+ "percent": 0.39,
515
515
  "reduction": 87.07
516
516
  },
517
517
  {
@@ -523,7 +523,7 @@
523
523
  ],
524
524
  "removedExports": [],
525
525
  "dependents": [],
526
- "percent": 0.33,
526
+ "percent": 0.32,
527
527
  "reduction": 72.92
528
528
  },
529
529
  {
@@ -551,7 +551,7 @@
551
551
  "dependents": [
552
552
  "/src/unstable-core-do-not-import.ts"
553
553
  ],
554
- "percent": 0.26,
554
+ "percent": 0.25,
555
555
  "reduction": 94.55
556
556
  },
557
557
  {
@@ -624,9 +624,9 @@
624
624
  "reduction": 100
625
625
  },
626
626
  {
627
- "id": "/src/index.ts",
627
+ "id": "/src/rpc.ts",
628
628
  "size": 0,
629
- "origSize": 32,
629
+ "origSize": 36,
630
630
  "renderedExports": [],
631
631
  "removedExports": [],
632
632
  "dependents": [],
@@ -634,9 +634,9 @@
634
634
  "reduction": 100
635
635
  },
636
636
  {
637
- "id": "/src/shared.ts",
637
+ "id": "/src/index.ts",
638
638
  "size": 0,
639
- "origSize": 653,
639
+ "origSize": 32,
640
640
  "renderedExports": [],
641
641
  "removedExports": [],
642
642
  "dependents": [],
@@ -644,9 +644,9 @@
644
644
  "reduction": 100
645
645
  },
646
646
  {
647
- "id": "/src/rpc.ts",
647
+ "id": "/src/unstable-core-do-not-import.ts",
648
648
  "size": 0,
649
- "origSize": 36,
649
+ "origSize": 1908,
650
650
  "renderedExports": [],
651
651
  "removedExports": [],
652
652
  "dependents": [],
@@ -654,9 +654,9 @@
654
654
  "reduction": 100
655
655
  },
656
656
  {
657
- "id": "/src/unstable-core-do-not-import.ts",
657
+ "id": "/src/shared.ts",
658
658
  "size": 0,
659
- "origSize": 1908,
659
+ "origSize": 653,
660
660
  "renderedExports": [],
661
661
  "removedExports": [],
662
662
  "dependents": [],
@@ -713,8 +713,8 @@
713
713
  "removedExports": [],
714
714
  "dependents": [
715
715
  "/src/adapters/express.ts",
716
- "/src/adapters/standalone.ts",
717
716
  "/src/adapters/next.ts",
717
+ "/src/adapters/standalone.ts",
718
718
  "/src/adapters/fastify/fastifyRequestHandler.ts"
719
719
  ],
720
720
  "percent": 0,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trpc/server",
3
- "version": "11.0.0-rc.369+0fb9a1d0a",
3
+ "version": "11.0.0-rc.373+db2ec5cae",
4
4
  "description": "The tRPC server library",
5
5
  "author": "KATT",
6
6
  "license": "MIT",
@@ -149,5 +149,5 @@
149
149
  "funding": [
150
150
  "https://trpc.io/sponsor"
151
151
  ],
152
- "gitHead": "0fb9a1d0a5ed26aa155b6d01d5deebe22cbf19cf"
152
+ "gitHead": "db2ec5cae339cabd8dfa19bdc1d596214568d205"
153
153
  }
@@ -17,32 +17,32 @@ function incomingMessageToBodyStream(
17
17
  type Value = Buffer | Uint8Array | string | null;
18
18
  let size = 0;
19
19
  const maxBodySize = opts.maxBodySize;
20
+ let hasClosed = false;
20
21
 
21
- let controller: ReadableStreamDefaultController<Value> =
22
- null as unknown as ReadableStreamDefaultController<Value>;
23
22
  const stream = new ReadableStream<Value>({
24
- start(c) {
25
- controller = c;
26
- },
27
- async pull(c) {
28
- const chunk: Value = req.read();
29
-
30
- if (chunk) {
23
+ start(controller) {
24
+ req.on('data', (chunk) => {
31
25
  size += chunk.length;
32
- }
33
- if (maxBodySize !== null && size > maxBodySize) {
34
- controller.error(
35
- new TRPCError({
36
- code: 'PAYLOAD_TOO_LARGE',
37
- }),
38
- );
39
- return;
40
- }
41
- if (chunk === null) {
42
- c.close();
43
- return;
44
- }
45
- controller.enqueue(chunk);
26
+ if (maxBodySize != null && size > maxBodySize) {
27
+ controller.error(
28
+ new TRPCError({
29
+ code: 'PAYLOAD_TOO_LARGE',
30
+ }),
31
+ );
32
+ // an error is thrown if we try to close the controller after
33
+ // erroring, so track the closure
34
+ hasClosed = true;
35
+ return;
36
+ }
37
+ controller.enqueue(chunk);
38
+ });
39
+ req.once('end', () => {
40
+ if (hasClosed) {
41
+ return;
42
+ }
43
+ hasClosed = true;
44
+ controller.close();
45
+ });
46
46
  },
47
47
  cancel() {
48
48
  req.destroy();
@@ -62,6 +62,23 @@ export type WSSHandlerOptions<TRouter extends AnyRouter> =
62
62
  WSConnectionHandlerOptions<TRouter> & {
63
63
  wss: ws.WebSocketServer;
64
64
  prefix?: string;
65
+ keepAlive?: {
66
+ /**
67
+ * Enable heartbeat messages
68
+ * @default false
69
+ */
70
+ enabled: boolean;
71
+ /**
72
+ * Heartbeat interval in milliseconds
73
+ * @default 30000
74
+ */
75
+ pingMs?: number;
76
+ /**
77
+ * Terminate the WebSocket if no pong is received after this many milliseconds
78
+ * @default 5000
79
+ */
80
+ pongWaitMs?: number;
81
+ };
65
82
  };
66
83
 
67
84
  export function getWSConnectionHandler<TRouter extends AnyRouter>(
@@ -316,10 +333,41 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
316
333
  };
317
334
  }
318
335
 
336
+ /**
337
+ * Handle WebSocket keep-alive messages
338
+ */
339
+ function handleKeepAlive(
340
+ client: ws.WebSocket,
341
+ pingMs = 30000,
342
+ pongWaitMs = 5000,
343
+ ) {
344
+ let heartbeatTimeout: NodeJS.Timeout | undefined;
345
+ const heartbeatInterval = setInterval(() => {
346
+ if (client.readyState !== WEBSOCKET_OPEN) {
347
+ return;
348
+ }
349
+ // First we send a ping message and wait for a pong
350
+ client.ping();
351
+ // We set a timeout to close the connection if the pong is not received
352
+ heartbeatTimeout = setTimeout(() => {
353
+ client.terminate();
354
+ clearInterval(heartbeatInterval);
355
+ }, pongWaitMs);
356
+ }, pingMs).unref();
357
+ // When we receive a pong message, we clear the timeout
358
+ client.on('pong', () => {
359
+ heartbeatTimeout && clearTimeout(heartbeatTimeout);
360
+ });
361
+ // If the connection is closed, we clear the interval
362
+ client.on('close', () => {
363
+ clearInterval(heartbeatInterval);
364
+ });
365
+ }
366
+
319
367
  export function applyWSSHandler<TRouter extends AnyRouter>(
320
368
  opts: WSSHandlerOptions<TRouter>,
321
369
  ) {
322
- const { wss, prefix } = opts;
370
+ const { wss, prefix, keepAlive } = opts;
323
371
 
324
372
  const onConnection = getWSConnectionHandler(opts);
325
373
  wss.on('connection', async (client, req) => {
@@ -328,6 +376,10 @@ export function applyWSSHandler<TRouter extends AnyRouter>(
328
376
  }
329
377
 
330
378
  await onConnection(client, req);
379
+ if (keepAlive?.enabled) {
380
+ const { pingMs, pongWaitMs } = keepAlive;
381
+ handleKeepAlive(client, pingMs, pongWaitMs);
382
+ }
331
383
  });
332
384
 
333
385
  return {