@trpc/server 11.1.0 → 11.1.1

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.
@@ -42,8 +42,8 @@ function fastifyTRPCPlugin(fastify, opts, done) {
42
42
  });
43
43
  fastify.get(prefix ?? '/', {
44
44
  websocket: true
45
- }, async (socket, req)=>{
46
- await onConnection(socket, req.raw);
45
+ }, (socket, req)=>{
46
+ onConnection(socket, req.raw);
47
47
  if (trpcOptions?.keepAlive?.enabled) {
48
48
  const { pingMs, pongWaitMs } = trpcOptions.keepAlive;
49
49
  ws.handleKeepAlive(socket, pingMs, pongWaitMs);
@@ -40,8 +40,8 @@ function fastifyTRPCPlugin(fastify, opts, done) {
40
40
  });
41
41
  fastify.get(prefix ?? '/', {
42
42
  websocket: true
43
- }, async (socket, req)=>{
44
- await onConnection(socket, req.raw);
43
+ }, (socket, req)=>{
44
+ onConnection(socket, req.raw);
45
45
  if (trpcOptions?.keepAlive?.enabled) {
46
46
  const { pingMs, pongWaitMs } = trpcOptions.keepAlive;
47
47
  handleKeepAlive(socket, pingMs, pongWaitMs);
@@ -43,7 +43,7 @@ export type WSSHandlerOptions<TRouter extends AnyRouter> = WSConnectionHandlerOp
43
43
  */
44
44
  dangerouslyDisablePong?: boolean;
45
45
  };
46
- export declare function getWSConnectionHandler<TRouter extends AnyRouter>(opts: WSSHandlerOptions<TRouter>): (client: ws.WebSocket, req: IncomingMessage) => Promise<void>;
46
+ export declare function getWSConnectionHandler<TRouter extends AnyRouter>(opts: WSSHandlerOptions<TRouter>): (client: ws.WebSocket, req: IncomingMessage) => void;
47
47
  /**
48
48
  * Handle WebSocket keep-alive messages
49
49
  */
@@ -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;AASzB,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAa/D,OAAO,EAKL,KAAK,YAAY,EAClB,MAAM,gCAAgC,CAAC;AAIxC,OAAO,EAAa,KAAK,8BAA8B,EAAE,MAAM,aAAa,CAAC;AAQ7E;;GAEG;AACH,MAAM,MAAM,yBAAyB,GAAG,8BAA8B,CACpE,eAAe,EACf,EAAE,CAAC,SAAS,CACb,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;IACF;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC,CAAC;AAGJ,wBAAgB,sBAAsB,CAAC,OAAO,SAAS,SAAS,EAC9D,IAAI,EAAE,iBAAiB,CAAC,OAAO,CAAC,IAKlB,QAAQ,EAAE,CAAC,SAAS,EAAE,KAAK,eAAe,mBAmYzD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,EAAE,CAAC,SAAS,EACpB,MAAM,SAAS,EACf,UAAU,SAAQ,QAiCnB;AAED,wBAAgB,eAAe,CAAC,OAAO,SAAS,SAAS,EACvD,IAAI,EAAE,iBAAiB,CAAC,OAAO,CAAC;;EAwCjC"}
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;AASzB,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAa/D,OAAO,EAKL,KAAK,YAAY,EAClB,MAAM,gCAAgC,CAAC;AAMxC,OAAO,EAAa,KAAK,8BAA8B,EAAE,MAAM,aAAa,CAAC;AAQ7E;;GAEG;AACH,MAAM,MAAM,yBAAyB,GAAG,8BAA8B,CACpE,eAAe,EACf,EAAE,CAAC,SAAS,CACb,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;IACF;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC,CAAC;AAEJ,wBAAgB,sBAAsB,CAAC,OAAO,SAAS,SAAS,EAC9D,IAAI,EAAE,iBAAiB,CAAC,OAAO,CAAC,IAKxB,QAAQ,EAAE,CAAC,SAAS,EAAE,KAAK,eAAe,UAyanD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,EAAE,CAAC,SAAS,EACpB,MAAM,SAAS,EACf,UAAU,SAAQ,QAiCnB;AAED,wBAAgB,eAAe,CAAC,OAAO,SAAS,SAAS,EACvD,IAAI,EAAE,iBAAiB,CAAC,OAAO,CAAC;;EAyBjC"}
@@ -84,11 +84,10 @@ function _ts_dispose_resources(env) {
84
84
  * Importing ws causes a build error
85
85
  * @see https://github.com/trpc/trpc/pull/5279
86
86
  */ const WEBSOCKET_OPEN = 1; /* ws.WebSocket.OPEN */
87
- const unsetContextPromiseSymbol = Symbol('unsetContextPromise');
88
87
  function getWSConnectionHandler(opts) {
89
88
  const { createContext, router: router$1 } = opts;
90
89
  const { transformer: transformer$1 } = router$1._def._config;
91
- return async (client, req)=>{
90
+ return (client, req)=>{
92
91
  const clientSubscriptions = new Map();
93
92
  const abortController = new AbortController();
94
93
  if (opts.keepAlive?.enabled) {
@@ -98,23 +97,28 @@ function getWSConnectionHandler(opts) {
98
97
  function respond(untransformedJSON) {
99
98
  client.send(JSON.stringify(transformer.transformTRPCResponse(router$1._def._config, untransformedJSON)));
100
99
  }
101
- function createCtxPromise(getConnectionParams) {
102
- return utils.run(async ()=>{
103
- ctx = await createContext?.({
104
- req,
105
- res: client,
106
- info: {
107
- connectionParams: getConnectionParams(),
108
- calls: [],
109
- isBatchCall: false,
110
- accept: null,
111
- type: 'unknown',
112
- signal: abortController.signal,
113
- url: null
114
- }
100
+ async function createCtxPromise(getConnectionParams) {
101
+ try {
102
+ return await utils.run(async ()=>{
103
+ ctx = await createContext?.({
104
+ req,
105
+ res: client,
106
+ info: {
107
+ connectionParams: getConnectionParams(),
108
+ calls: [],
109
+ isBatchCall: false,
110
+ accept: null,
111
+ type: 'unknown',
112
+ signal: abortController.signal,
113
+ url: null
114
+ }
115
+ });
116
+ return {
117
+ ok: true,
118
+ value: ctx
119
+ };
115
120
  });
116
- return ctx;
117
- }).catch((cause)=>{
121
+ } catch (cause) {
118
122
  const error = TRPCError.getTRPCErrorFromUnknown(cause);
119
123
  opts.onError?.({
120
124
  error,
@@ -139,8 +143,11 @@ function getWSConnectionHandler(opts) {
139
143
  (globalThis.setImmediate ?? globalThis.setTimeout)(()=>{
140
144
  client.close();
141
145
  });
142
- throw error;
143
- });
146
+ return {
147
+ ok: false,
148
+ error
149
+ };
150
+ }
144
151
  }
145
152
  let ctx = undefined;
146
153
  /**
@@ -148,14 +155,35 @@ function getWSConnectionHandler(opts) {
148
155
  *
149
156
  * - the context promise will be created immediately on connection if no connectionParams are expected
150
157
  * - if connection params are expected, they will be created once received
151
- */ let ctxPromise = incomingMessageToRequest.createURL(req).searchParams.get('connectionParams') === '1' ? unsetContextPromiseSymbol : createCtxPromise(()=>null);
152
- async function handleRequest(msg) {
158
+ */ let ctxPromise = incomingMessageToRequest.createURL(req).searchParams.get('connectionParams') === '1' ? null : createCtxPromise(()=>null);
159
+ function handleRequest(msg) {
153
160
  const { id, jsonrpc } = msg;
154
- /* istanbul ignore next -- @preserve */ if (id === null) {
155
- throw new TRPCError.TRPCError({
156
- code: 'BAD_REQUEST',
161
+ if (id === null) {
162
+ const error = TRPCError.getTRPCErrorFromUnknown(new TRPCError.TRPCError({
163
+ code: 'PARSE_ERROR',
157
164
  message: '`id` is required'
165
+ }));
166
+ opts.onError?.({
167
+ error,
168
+ path: undefined,
169
+ type: 'unknown',
170
+ ctx,
171
+ req,
172
+ input: undefined
158
173
  });
174
+ respond({
175
+ id,
176
+ jsonrpc,
177
+ error: getErrorShape.getErrorShape({
178
+ config: router$1._def._config,
179
+ error,
180
+ type: 'unknown',
181
+ path: undefined,
182
+ input: undefined,
183
+ ctx
184
+ })
185
+ });
186
+ return;
159
187
  }
160
188
  if (msg.method === 'subscription.stop') {
161
189
  clientSubscriptions.get(id)?.abort();
@@ -164,20 +192,24 @@ function getWSConnectionHandler(opts) {
164
192
  const { path, lastEventId } = msg.params;
165
193
  let { input } = msg.params;
166
194
  const type = msg.method;
167
- try {
168
- if (lastEventId !== undefined) {
169
- if (utils.isObject(input)) {
170
- input = {
171
- ...input,
172
- lastEventId: lastEventId
173
- };
174
- } else {
175
- input ?? (input = {
176
- lastEventId: lastEventId
177
- });
178
- }
195
+ if (lastEventId !== undefined) {
196
+ if (utils.isObject(input)) {
197
+ input = {
198
+ ...input,
199
+ lastEventId: lastEventId
200
+ };
201
+ } else {
202
+ input ?? (input = {
203
+ lastEventId: lastEventId
204
+ });
205
+ }
206
+ }
207
+ utils.run(async ()=>{
208
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
209
+ const res = await ctxPromise; // asserts context has been set
210
+ if (!res.ok) {
211
+ throw res.error;
179
212
  }
180
- await ctxPromise; // asserts context has been set
181
213
  const abortController = new AbortController();
182
214
  const result = await router.callProcedure({
183
215
  router: router$1,
@@ -345,7 +377,7 @@ function getWSConnectionHandler(opts) {
345
377
  type: 'started'
346
378
  }
347
379
  });
348
- } catch (cause) /* istanbul ignore next -- @preserve */ {
380
+ }).catch((cause)=>{
349
381
  // procedure threw an error
350
382
  const error = TRPCError.getTRPCErrorFromUnknown(cause);
351
383
  opts.onError?.({
@@ -368,9 +400,9 @@ function getWSConnectionHandler(opts) {
368
400
  ctx
369
401
  })
370
402
  });
371
- }
403
+ });
372
404
  }
373
- client.on('message', async (rawData)=>{
405
+ client.on('message', (rawData)=>{
374
406
  // eslint-disable-next-line @typescript-eslint/no-base-to-string
375
407
  const msgStr = rawData.toString();
376
408
  if (msgStr === 'PONG') {
@@ -382,7 +414,7 @@ function getWSConnectionHandler(opts) {
382
414
  }
383
415
  return;
384
416
  }
385
- if (ctxPromise === unsetContextPromiseSymbol) {
417
+ if (!ctxPromise) {
386
418
  // If the ctxPromise wasn't created immediately, we're expecting the first message to be a TRPCConnectionParamsMessage
387
419
  ctxPromise = createCtxPromise(()=>{
388
420
  let msg;
@@ -403,30 +435,33 @@ function getWSConnectionHandler(opts) {
403
435
  });
404
436
  return;
405
437
  }
406
- try {
407
- const msgJSON = JSON.parse(msgStr);
408
- const msgs = Array.isArray(msgJSON) ? msgJSON : [
409
- msgJSON
410
- ];
411
- const promises = msgs.map((raw)=>parseTRPCMessage.parseTRPCMessage(raw, transformer$1)).map(handleRequest);
412
- await Promise.all(promises);
413
- } catch (cause) {
414
- const error = new TRPCError.TRPCError({
415
- code: 'PARSE_ERROR',
416
- cause
417
- });
418
- respond({
419
- id: null,
420
- error: getErrorShape.getErrorShape({
421
- config: router$1._def._config,
422
- error,
423
- type: 'unknown',
424
- path: undefined,
425
- input: undefined,
426
- ctx: undefined
427
- })
428
- });
429
- }
438
+ const parsedMsgs = utils.run(()=>{
439
+ try {
440
+ const msgJSON = JSON.parse(msgStr);
441
+ const msgs = Array.isArray(msgJSON) ? msgJSON : [
442
+ msgJSON
443
+ ];
444
+ return msgs.map((raw)=>parseTRPCMessage.parseTRPCMessage(raw, transformer$1));
445
+ } catch (cause) {
446
+ const error = new TRPCError.TRPCError({
447
+ code: 'PARSE_ERROR',
448
+ cause
449
+ });
450
+ respond({
451
+ id: null,
452
+ error: getErrorShape.getErrorShape({
453
+ config: router$1._def._config,
454
+ error,
455
+ type: 'unknown',
456
+ path: undefined,
457
+ input: undefined,
458
+ ctx
459
+ })
460
+ });
461
+ return [];
462
+ }
463
+ });
464
+ parsedMsgs.map(handleRequest);
430
465
  });
431
466
  // WebSocket errors should be handled, as otherwise unhandled exceptions will crash Node.js.
432
467
  // This line was introduced after the following error brought down production systems:
@@ -449,9 +484,6 @@ function getWSConnectionHandler(opts) {
449
484
  clientSubscriptions.clear();
450
485
  abortController.abort();
451
486
  });
452
- if (ctxPromise !== unsetContextPromiseSymbol) {
453
- await ctxPromise;
454
- }
455
487
  };
456
488
  }
457
489
  /**
@@ -488,21 +520,7 @@ function applyWSSHandler(opts) {
488
520
  if (opts.prefix && !req.url?.startsWith(opts.prefix)) {
489
521
  return;
490
522
  }
491
- onConnection(client, req).catch((cause)=>{
492
- opts.onError?.({
493
- error: new TRPCError.TRPCError({
494
- code: 'INTERNAL_SERVER_ERROR',
495
- cause,
496
- message: 'Failed to handle WebSocket connection'
497
- }),
498
- req: req,
499
- path: undefined,
500
- type: 'unknown',
501
- ctx: undefined,
502
- input: undefined
503
- });
504
- client.close();
505
- });
523
+ onConnection(client, req);
506
524
  });
507
525
  return {
508
526
  broadcastReconnectNotification: ()=>{
@@ -82,11 +82,10 @@ function _ts_dispose_resources(env) {
82
82
  * Importing ws causes a build error
83
83
  * @see https://github.com/trpc/trpc/pull/5279
84
84
  */ const WEBSOCKET_OPEN = 1; /* ws.WebSocket.OPEN */
85
- const unsetContextPromiseSymbol = Symbol('unsetContextPromise');
86
85
  function getWSConnectionHandler(opts) {
87
86
  const { createContext, router } = opts;
88
87
  const { transformer } = router._def._config;
89
- return async (client, req)=>{
88
+ return (client, req)=>{
90
89
  const clientSubscriptions = new Map();
91
90
  const abortController = new AbortController();
92
91
  if (opts.keepAlive?.enabled) {
@@ -96,23 +95,28 @@ function getWSConnectionHandler(opts) {
96
95
  function respond(untransformedJSON) {
97
96
  client.send(JSON.stringify(transformTRPCResponse(router._def._config, untransformedJSON)));
98
97
  }
99
- function createCtxPromise(getConnectionParams) {
100
- return run(async ()=>{
101
- ctx = await createContext?.({
102
- req,
103
- res: client,
104
- info: {
105
- connectionParams: getConnectionParams(),
106
- calls: [],
107
- isBatchCall: false,
108
- accept: null,
109
- type: 'unknown',
110
- signal: abortController.signal,
111
- url: null
112
- }
98
+ async function createCtxPromise(getConnectionParams) {
99
+ try {
100
+ return await run(async ()=>{
101
+ ctx = await createContext?.({
102
+ req,
103
+ res: client,
104
+ info: {
105
+ connectionParams: getConnectionParams(),
106
+ calls: [],
107
+ isBatchCall: false,
108
+ accept: null,
109
+ type: 'unknown',
110
+ signal: abortController.signal,
111
+ url: null
112
+ }
113
+ });
114
+ return {
115
+ ok: true,
116
+ value: ctx
117
+ };
113
118
  });
114
- return ctx;
115
- }).catch((cause)=>{
119
+ } catch (cause) {
116
120
  const error = getTRPCErrorFromUnknown(cause);
117
121
  opts.onError?.({
118
122
  error,
@@ -137,8 +141,11 @@ function getWSConnectionHandler(opts) {
137
141
  (globalThis.setImmediate ?? globalThis.setTimeout)(()=>{
138
142
  client.close();
139
143
  });
140
- throw error;
141
- });
144
+ return {
145
+ ok: false,
146
+ error
147
+ };
148
+ }
142
149
  }
143
150
  let ctx = undefined;
144
151
  /**
@@ -146,14 +153,35 @@ function getWSConnectionHandler(opts) {
146
153
  *
147
154
  * - the context promise will be created immediately on connection if no connectionParams are expected
148
155
  * - if connection params are expected, they will be created once received
149
- */ let ctxPromise = createURL(req).searchParams.get('connectionParams') === '1' ? unsetContextPromiseSymbol : createCtxPromise(()=>null);
150
- async function handleRequest(msg) {
156
+ */ let ctxPromise = createURL(req).searchParams.get('connectionParams') === '1' ? null : createCtxPromise(()=>null);
157
+ function handleRequest(msg) {
151
158
  const { id, jsonrpc } = msg;
152
- /* istanbul ignore next -- @preserve */ if (id === null) {
153
- throw new TRPCError({
154
- code: 'BAD_REQUEST',
159
+ if (id === null) {
160
+ const error = getTRPCErrorFromUnknown(new TRPCError({
161
+ code: 'PARSE_ERROR',
155
162
  message: '`id` is required'
163
+ }));
164
+ opts.onError?.({
165
+ error,
166
+ path: undefined,
167
+ type: 'unknown',
168
+ ctx,
169
+ req,
170
+ input: undefined
156
171
  });
172
+ respond({
173
+ id,
174
+ jsonrpc,
175
+ error: getErrorShape({
176
+ config: router._def._config,
177
+ error,
178
+ type: 'unknown',
179
+ path: undefined,
180
+ input: undefined,
181
+ ctx
182
+ })
183
+ });
184
+ return;
157
185
  }
158
186
  if (msg.method === 'subscription.stop') {
159
187
  clientSubscriptions.get(id)?.abort();
@@ -162,20 +190,24 @@ function getWSConnectionHandler(opts) {
162
190
  const { path, lastEventId } = msg.params;
163
191
  let { input } = msg.params;
164
192
  const type = msg.method;
165
- try {
166
- if (lastEventId !== undefined) {
167
- if (isObject(input)) {
168
- input = {
169
- ...input,
170
- lastEventId: lastEventId
171
- };
172
- } else {
173
- input ?? (input = {
174
- lastEventId: lastEventId
175
- });
176
- }
193
+ if (lastEventId !== undefined) {
194
+ if (isObject(input)) {
195
+ input = {
196
+ ...input,
197
+ lastEventId: lastEventId
198
+ };
199
+ } else {
200
+ input ?? (input = {
201
+ lastEventId: lastEventId
202
+ });
203
+ }
204
+ }
205
+ run(async ()=>{
206
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
207
+ const res = await ctxPromise; // asserts context has been set
208
+ if (!res.ok) {
209
+ throw res.error;
177
210
  }
178
- await ctxPromise; // asserts context has been set
179
211
  const abortController = new AbortController();
180
212
  const result = await callProcedure({
181
213
  router,
@@ -343,7 +375,7 @@ function getWSConnectionHandler(opts) {
343
375
  type: 'started'
344
376
  }
345
377
  });
346
- } catch (cause) /* istanbul ignore next -- @preserve */ {
378
+ }).catch((cause)=>{
347
379
  // procedure threw an error
348
380
  const error = getTRPCErrorFromUnknown(cause);
349
381
  opts.onError?.({
@@ -366,9 +398,9 @@ function getWSConnectionHandler(opts) {
366
398
  ctx
367
399
  })
368
400
  });
369
- }
401
+ });
370
402
  }
371
- client.on('message', async (rawData)=>{
403
+ client.on('message', (rawData)=>{
372
404
  // eslint-disable-next-line @typescript-eslint/no-base-to-string
373
405
  const msgStr = rawData.toString();
374
406
  if (msgStr === 'PONG') {
@@ -380,7 +412,7 @@ function getWSConnectionHandler(opts) {
380
412
  }
381
413
  return;
382
414
  }
383
- if (ctxPromise === unsetContextPromiseSymbol) {
415
+ if (!ctxPromise) {
384
416
  // If the ctxPromise wasn't created immediately, we're expecting the first message to be a TRPCConnectionParamsMessage
385
417
  ctxPromise = createCtxPromise(()=>{
386
418
  let msg;
@@ -401,30 +433,33 @@ function getWSConnectionHandler(opts) {
401
433
  });
402
434
  return;
403
435
  }
404
- try {
405
- const msgJSON = JSON.parse(msgStr);
406
- const msgs = Array.isArray(msgJSON) ? msgJSON : [
407
- msgJSON
408
- ];
409
- const promises = msgs.map((raw)=>parseTRPCMessage(raw, transformer)).map(handleRequest);
410
- await Promise.all(promises);
411
- } catch (cause) {
412
- const error = new TRPCError({
413
- code: 'PARSE_ERROR',
414
- cause
415
- });
416
- respond({
417
- id: null,
418
- error: getErrorShape({
419
- config: router._def._config,
420
- error,
421
- type: 'unknown',
422
- path: undefined,
423
- input: undefined,
424
- ctx: undefined
425
- })
426
- });
427
- }
436
+ const parsedMsgs = run(()=>{
437
+ try {
438
+ const msgJSON = JSON.parse(msgStr);
439
+ const msgs = Array.isArray(msgJSON) ? msgJSON : [
440
+ msgJSON
441
+ ];
442
+ return msgs.map((raw)=>parseTRPCMessage(raw, transformer));
443
+ } catch (cause) {
444
+ const error = new TRPCError({
445
+ code: 'PARSE_ERROR',
446
+ cause
447
+ });
448
+ respond({
449
+ id: null,
450
+ error: getErrorShape({
451
+ config: router._def._config,
452
+ error,
453
+ type: 'unknown',
454
+ path: undefined,
455
+ input: undefined,
456
+ ctx
457
+ })
458
+ });
459
+ return [];
460
+ }
461
+ });
462
+ parsedMsgs.map(handleRequest);
428
463
  });
429
464
  // WebSocket errors should be handled, as otherwise unhandled exceptions will crash Node.js.
430
465
  // This line was introduced after the following error brought down production systems:
@@ -447,9 +482,6 @@ function getWSConnectionHandler(opts) {
447
482
  clientSubscriptions.clear();
448
483
  abortController.abort();
449
484
  });
450
- if (ctxPromise !== unsetContextPromiseSymbol) {
451
- await ctxPromise;
452
- }
453
485
  };
454
486
  }
455
487
  /**
@@ -486,21 +518,7 @@ function applyWSSHandler(opts) {
486
518
  if (opts.prefix && !req.url?.startsWith(opts.prefix)) {
487
519
  return;
488
520
  }
489
- onConnection(client, req).catch((cause)=>{
490
- opts.onError?.({
491
- error: new TRPCError({
492
- code: 'INTERNAL_SERVER_ERROR',
493
- cause,
494
- message: 'Failed to handle WebSocket connection'
495
- }),
496
- req: req,
497
- path: undefined,
498
- type: 'unknown',
499
- ctx: undefined,
500
- input: undefined
501
- });
502
- client.close();
503
- });
521
+ onConnection(client, req);
504
522
  });
505
523
  return {
506
524
  broadcastReconnectNotification: ()=>{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trpc/server",
3
- "version": "11.1.0",
3
+ "version": "11.1.1",
4
4
  "description": "The tRPC server library",
5
5
  "author": "KATT",
6
6
  "license": "MIT",
@@ -152,5 +152,5 @@
152
152
  "peerDependencies": {
153
153
  "typescript": ">=5.7.2"
154
154
  },
155
- "gitHead": "0b93d77369ffbcaf70afa1052691bc643cf33a61"
155
+ "gitHead": "9c1c753a778ac7a2709b1e81be392231c4657319"
156
156
  }
@@ -68,8 +68,8 @@ export function fastifyTRPCPlugin<TRouter extends AnyRouter>(
68
68
  ...trpcOptions,
69
69
  });
70
70
 
71
- fastify.get(prefix ?? '/', { websocket: true }, async (socket, req) => {
72
- await onConnection(socket, req.raw);
71
+ fastify.get(prefix ?? '/', { websocket: true }, (socket, req) => {
72
+ onConnection(socket, req.raw);
73
73
  if (trpcOptions?.keepAlive?.enabled) {
74
74
  const { pingMs, pongWaitMs } = trpcOptions.keepAlive;
75
75
  handleKeepAlive(socket, pingMs, pongWaitMs);
@@ -34,6 +34,8 @@ import {
34
34
  type MaybePromise,
35
35
  } from '../unstable-core-do-not-import';
36
36
  // eslint-disable-next-line no-restricted-imports
37
+ import type { Result } from '../unstable-core-do-not-import';
38
+ // eslint-disable-next-line no-restricted-imports
37
39
  import { iteratorResource } from '../unstable-core-do-not-import/stream/utils/asyncIterable';
38
40
  import { Unpromise } from '../vendor/unpromise';
39
41
  import { createURL, type NodeHTTPCreateContextFnOptions } from './node-http';
@@ -98,14 +100,16 @@ export type WSSHandlerOptions<TRouter extends AnyRouter> =
98
100
  dangerouslyDisablePong?: boolean;
99
101
  };
100
102
 
101
- const unsetContextPromiseSymbol = Symbol('unsetContextPromise');
102
103
  export function getWSConnectionHandler<TRouter extends AnyRouter>(
103
104
  opts: WSSHandlerOptions<TRouter>,
104
105
  ) {
105
106
  const { createContext, router } = opts;
106
107
  const { transformer } = router._def._config;
107
108
 
108
- return async (client: ws.WebSocket, req: IncomingMessage) => {
109
+ return (client: ws.WebSocket, req: IncomingMessage) => {
110
+ type Context = inferRouterContext<TRouter>;
111
+ type ContextResult = Result<Context>;
112
+
109
113
  const clientSubscriptions = new Map<number | string, AbortController>();
110
114
  const abortController = new AbortController();
111
115
 
@@ -122,26 +126,31 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
122
126
  );
123
127
  }
124
128
 
125
- function createCtxPromise(
129
+ async function createCtxPromise(
126
130
  getConnectionParams: () => TRPCRequestInfo['connectionParams'],
127
- ): Promise<inferRouterContext<TRouter>> {
128
- return run(async () => {
129
- ctx = await createContext?.({
130
- req,
131
- res: client,
132
- info: {
133
- connectionParams: getConnectionParams(),
134
- calls: [],
135
- isBatchCall: false,
136
- accept: null,
137
- type: 'unknown',
138
- signal: abortController.signal,
139
- url: null,
140
- },
141
- });
131
+ ): Promise<ContextResult> {
132
+ try {
133
+ return await run(async (): Promise<ContextResult> => {
134
+ ctx = await createContext?.({
135
+ req,
136
+ res: client,
137
+ info: {
138
+ connectionParams: getConnectionParams(),
139
+ calls: [],
140
+ isBatchCall: false,
141
+ accept: null,
142
+ type: 'unknown',
143
+ signal: abortController.signal,
144
+ url: null,
145
+ },
146
+ });
142
147
 
143
- return ctx;
144
- }).catch((cause) => {
148
+ return {
149
+ ok: true,
150
+ value: ctx,
151
+ };
152
+ });
153
+ } catch (cause) {
145
154
  const error = getTRPCErrorFromUnknown(cause);
146
155
  opts.onError?.({
147
156
  error,
@@ -167,12 +176,14 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
167
176
  (globalThis.setImmediate ?? globalThis.setTimeout)(() => {
168
177
  client.close();
169
178
  });
170
-
171
- throw error;
172
- });
179
+ return {
180
+ ok: false,
181
+ error,
182
+ };
183
+ }
173
184
  }
174
185
 
175
- let ctx: inferRouterContext<TRouter> | undefined = undefined;
186
+ let ctx: Context | undefined = undefined;
176
187
 
177
188
  /**
178
189
  * promise for initializing the context
@@ -182,18 +193,40 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
182
193
  */
183
194
  let ctxPromise =
184
195
  createURL(req).searchParams.get('connectionParams') === '1'
185
- ? unsetContextPromiseSymbol
196
+ ? null
186
197
  : createCtxPromise(() => null);
187
198
 
188
- async function handleRequest(msg: TRPCClientOutgoingMessage) {
199
+ function handleRequest(msg: TRPCClientOutgoingMessage) {
189
200
  const { id, jsonrpc } = msg;
190
201
 
191
- /* istanbul ignore next -- @preserve */
192
202
  if (id === null) {
193
- throw new TRPCError({
194
- code: 'BAD_REQUEST',
195
- message: '`id` is required',
203
+ const error = getTRPCErrorFromUnknown(
204
+ new TRPCError({
205
+ code: 'PARSE_ERROR',
206
+ message: '`id` is required',
207
+ }),
208
+ );
209
+ opts.onError?.({
210
+ error,
211
+ path: undefined,
212
+ type: 'unknown',
213
+ ctx,
214
+ req,
215
+ input: undefined,
196
216
  });
217
+ respond({
218
+ id,
219
+ jsonrpc,
220
+ error: getErrorShape({
221
+ config: router._def._config,
222
+ error,
223
+ type: 'unknown',
224
+ path: undefined,
225
+ input: undefined,
226
+ ctx,
227
+ }),
228
+ });
229
+ return;
197
230
  }
198
231
  if (msg.method === 'subscription.stop') {
199
232
  clientSubscriptions.get(id)?.abort();
@@ -202,20 +235,25 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
202
235
  const { path, lastEventId } = msg.params;
203
236
  let { input } = msg.params;
204
237
  const type = msg.method;
205
- try {
206
- if (lastEventId !== undefined) {
207
- if (isObject(input)) {
208
- input = {
209
- ...input,
210
- lastEventId: lastEventId,
211
- };
212
- } else {
213
- input ??= {
214
- lastEventId: lastEventId,
215
- };
216
- }
238
+
239
+ if (lastEventId !== undefined) {
240
+ if (isObject(input)) {
241
+ input = {
242
+ ...input,
243
+ lastEventId: lastEventId,
244
+ };
245
+ } else {
246
+ input ??= {
247
+ lastEventId: lastEventId,
248
+ };
249
+ }
250
+ }
251
+ run(async () => {
252
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
253
+ const res = await ctxPromise!; // asserts context has been set
254
+ if (!res.ok) {
255
+ throw res.error;
217
256
  }
218
- await ctxPromise; // asserts context has been set
219
257
 
220
258
  const abortController = new AbortController();
221
259
  const result = await callTRPCProcedure({
@@ -384,7 +422,7 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
384
422
  type: 'started',
385
423
  },
386
424
  });
387
- } catch (cause) /* istanbul ignore next -- @preserve */ {
425
+ }).catch((cause) => {
388
426
  // procedure threw an error
389
427
  const error = getTRPCErrorFromUnknown(cause);
390
428
  opts.onError?.({ error, path, type, ctx, req, input });
@@ -400,9 +438,9 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
400
438
  ctx,
401
439
  }),
402
440
  });
403
- }
441
+ });
404
442
  }
405
- client.on('message', async (rawData) => {
443
+ client.on('message', (rawData) => {
406
444
  // eslint-disable-next-line @typescript-eslint/no-base-to-string
407
445
  const msgStr = rawData.toString();
408
446
  if (msgStr === 'PONG') {
@@ -414,7 +452,7 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
414
452
  }
415
453
  return;
416
454
  }
417
- if (ctxPromise === unsetContextPromiseSymbol) {
455
+ if (!ctxPromise) {
418
456
  // If the ctxPromise wasn't created immediately, we're expecting the first message to be a TRPCConnectionParamsMessage
419
457
  ctxPromise = createCtxPromise(() => {
420
458
  let msg;
@@ -438,31 +476,36 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
438
476
  });
439
477
  return;
440
478
  }
441
- try {
442
- const msgJSON: unknown = JSON.parse(msgStr);
443
- const msgs: unknown[] = Array.isArray(msgJSON) ? msgJSON : [msgJSON];
444
- const promises = msgs
445
- .map((raw) => parseTRPCMessage(raw, transformer))
446
- .map(handleRequest);
447
- await Promise.all(promises);
448
- } catch (cause) {
449
- const error = new TRPCError({
450
- code: 'PARSE_ERROR',
451
- cause,
452
- });
453
479
 
454
- respond({
455
- id: null,
456
- error: getErrorShape({
457
- config: router._def._config,
458
- error,
459
- type: 'unknown',
460
- path: undefined,
461
- input: undefined,
462
- ctx: undefined,
463
- }),
464
- });
465
- }
480
+ const parsedMsgs = run(() => {
481
+ try {
482
+ const msgJSON: unknown = JSON.parse(msgStr);
483
+ const msgs: unknown[] = Array.isArray(msgJSON) ? msgJSON : [msgJSON];
484
+
485
+ return msgs.map((raw) => parseTRPCMessage(raw, transformer));
486
+ } catch (cause) {
487
+ const error = new TRPCError({
488
+ code: 'PARSE_ERROR',
489
+ cause,
490
+ });
491
+
492
+ respond({
493
+ id: null,
494
+ error: getErrorShape({
495
+ config: router._def._config,
496
+ error,
497
+ type: 'unknown',
498
+ path: undefined,
499
+ input: undefined,
500
+ ctx,
501
+ }),
502
+ });
503
+
504
+ return [];
505
+ }
506
+ });
507
+
508
+ parsedMsgs.map(handleRequest);
466
509
  });
467
510
 
468
511
  // WebSocket errors should be handled, as otherwise unhandled exceptions will crash Node.js.
@@ -487,10 +530,6 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
487
530
  clientSubscriptions.clear();
488
531
  abortController.abort();
489
532
  });
490
-
491
- if (ctxPromise !== unsetContextPromiseSymbol) {
492
- await ctxPromise;
493
- }
494
533
  };
495
534
  }
496
535
 
@@ -544,22 +583,7 @@ export function applyWSSHandler<TRouter extends AnyRouter>(
544
583
  return;
545
584
  }
546
585
 
547
- onConnection(client, req).catch((cause) => {
548
- opts.onError?.({
549
- error: new TRPCError({
550
- code: 'INTERNAL_SERVER_ERROR',
551
- cause,
552
- message: 'Failed to handle WebSocket connection',
553
- }),
554
- req: req,
555
- path: undefined,
556
- type: 'unknown',
557
- ctx: undefined,
558
- input: undefined,
559
- });
560
-
561
- client.close();
562
- });
586
+ onConnection(client, req);
563
587
  });
564
588
 
565
589
  return {