@trpc/server 11.0.0-rc.589 → 11.0.0-rc.590

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.
Files changed (26) hide show
  1. package/dist/bundle-analysis.json +114 -111
  2. package/dist/unstable-core-do-not-import/http/resolveResponse.d.ts.map +1 -1
  3. package/dist/unstable-core-do-not-import/http/resolveResponse.js +30 -16
  4. package/dist/unstable-core-do-not-import/http/resolveResponse.mjs +31 -17
  5. package/dist/unstable-core-do-not-import/stream/sse.d.ts +1 -0
  6. package/dist/unstable-core-do-not-import/stream/sse.d.ts.map +1 -1
  7. package/dist/unstable-core-do-not-import/stream/sse.js +3 -2
  8. package/dist/unstable-core-do-not-import/stream/sse.mjs +3 -2
  9. package/dist/unstable-core-do-not-import/stream/utils/asyncIterable.d.ts +2 -1
  10. package/dist/unstable-core-do-not-import/stream/utils/asyncIterable.d.ts.map +1 -1
  11. package/dist/unstable-core-do-not-import/stream/utils/asyncIterable.js +2 -2
  12. package/dist/unstable-core-do-not-import/stream/utils/asyncIterable.mjs +2 -2
  13. package/dist/unstable-core-do-not-import/stream/utils/promiseTimer.js +1 -1
  14. package/dist/unstable-core-do-not-import/stream/utils/promiseTimer.mjs +1 -1
  15. package/dist/unstable-core-do-not-import/utils.d.ts +13 -0
  16. package/dist/unstable-core-do-not-import/utils.d.ts.map +1 -1
  17. package/dist/unstable-core-do-not-import/utils.js +41 -0
  18. package/dist/unstable-core-do-not-import/utils.mjs +39 -1
  19. package/dist/unstable-core-do-not-import.js +3 -0
  20. package/dist/unstable-core-do-not-import.mjs +1 -1
  21. package/package.json +2 -2
  22. package/src/unstable-core-do-not-import/http/resolveResponse.ts +39 -21
  23. package/src/unstable-core-do-not-import/stream/sse.ts +14 -4
  24. package/src/unstable-core-do-not-import/stream/utils/asyncIterable.ts +3 -2
  25. package/src/unstable-core-do-not-import/stream/utils/promiseTimer.ts +1 -1
  26. package/src/unstable-core-do-not-import/utils.ts +48 -0
@@ -4,7 +4,7 @@ import { TRPCError, getTRPCErrorFromUnknown } from '../error/TRPCError.mjs';
4
4
  import { jsonlStreamProducer, isPromise } from '../stream/jsonl.mjs';
5
5
  import { sseStreamProducer, sseHeaders } from '../stream/sse.mjs';
6
6
  import { transformTRPCResponse } from '../transformer.mjs';
7
- import { isAsyncIterable, isObject } from '../utils.mjs';
7
+ import { abortSignalsAnyPonyfill, assert, isAsyncIterable, isObject } from '../utils.mjs';
8
8
  import { getRequestInfo } from './contentType.mjs';
9
9
  import { getHTTPStatusCode } from './getHTTPStatusCode.mjs';
10
10
 
@@ -185,21 +185,31 @@ async function resolveResponse(opts) {
185
185
  message: `Unsupported ${req.method}-request to ${proc._def.type} procedure at path "${call.path}"`
186
186
  });
187
187
  }
188
- /* istanbul ignore if -- @preserve */ if (proc._def.type === 'subscription' && info.isBatchCall) {
189
- throw new TRPCError({
190
- code: 'BAD_REQUEST',
191
- message: `Cannot batch subscription calls`
192
- });
188
+ let abortCtrl;
189
+ if (proc._def.type === 'subscription') {
190
+ /* istanbul ignore if -- @preserve */ if (info.isBatchCall) {
191
+ throw new TRPCError({
192
+ code: 'BAD_REQUEST',
193
+ message: `Cannot batch subscription calls`
194
+ });
195
+ }
196
+ abortCtrl = new AbortController();
193
197
  }
194
198
  const data = await proc({
195
199
  path: call.path,
196
200
  getRawInput: call.getRawInput,
197
201
  ctx,
198
202
  type: proc._def.type,
199
- signal: opts.req.signal
203
+ signal: abortCtrl ? abortSignalsAnyPonyfill([
204
+ opts.req.signal,
205
+ abortCtrl.signal
206
+ ]) : opts.req.signal
200
207
  });
201
208
  return [
202
- data
209
+ {
210
+ data,
211
+ abortCtrl
212
+ }
203
213
  ];
204
214
  } catch (cause) {
205
215
  const error = getTRPCErrorFromUnknown(cause);
@@ -221,7 +231,7 @@ async function resolveResponse(opts) {
221
231
  // ----------- response handlers -----------
222
232
  if (!info.isBatchCall) {
223
233
  const [call] = info.calls;
224
- const [data, error] = await rpcCalls[0];
234
+ const [result, error] = await rpcCalls[0];
225
235
  switch(info.type){
226
236
  case 'unknown':
227
237
  case 'mutation':
@@ -229,7 +239,7 @@ async function resolveResponse(opts) {
229
239
  {
230
240
  // httpLink
231
241
  headers.set('content-type', 'application/json');
232
- if (isDataStream(data)) {
242
+ if (isDataStream(result?.data)) {
233
243
  throw new TRPCError({
234
244
  code: 'UNSUPPORTED_MEDIA_TYPE',
235
245
  message: 'Cannot use stream-like response in non-streaming request - use httpBatchStreamLink'
@@ -246,7 +256,7 @@ async function resolveResponse(opts) {
246
256
  })
247
257
  } : {
248
258
  result: {
249
- data
259
+ data: result.data
250
260
  }
251
261
  };
252
262
  const headResponse = initResponse({
@@ -278,6 +288,8 @@ async function resolveResponse(opts) {
278
288
  if (error) {
279
289
  throw error;
280
290
  }
291
+ const { data , abortCtrl } = result;
292
+ assert(abortCtrl !== undefined, 'subscription type must have an AbortController');
281
293
  if (!isObservable(data) && !isAsyncIterable(data)) {
282
294
  throw new TRPCError({
283
295
  message: `Subscription ${call.path} did not return an observable or a AsyncGenerator`,
@@ -288,6 +300,7 @@ async function resolveResponse(opts) {
288
300
  const stream = sseStreamProducer({
289
301
  ...config.experimental?.sseSubscriptions,
290
302
  data: dataAsIterable,
303
+ abortCtrl,
291
304
  serialize: (v)=>config.transformer.output.serialize(v),
292
305
  formatError (errorOpts) {
293
306
  const error = getTRPCErrorFromUnknown(errorOpts.error);
@@ -373,13 +386,14 @@ async function resolveResponse(opts) {
373
386
  })
374
387
  };
375
388
  }
389
+ const { data } = result;
376
390
  /**
377
391
  * Not very pretty, but we need to wrap nested data in promises
378
392
  * Our stream producer will only resolve top-level async values or async values that are directly nested in another async value
379
- */ const data = isObservable(result) ? observableToAsyncIterable(result) : Promise.resolve(result);
393
+ */ const dataAsPromiseOrIterable = isObservable(data) ? observableToAsyncIterable(data) : Promise.resolve(data);
380
394
  return {
381
395
  result: Promise.resolve({
382
- data
396
+ data: dataAsPromiseOrIterable
383
397
  })
384
398
  };
385
399
  }),
@@ -420,11 +434,11 @@ async function resolveResponse(opts) {
420
434
  * - return a complete HTTPResponse
421
435
  */ headers.set('content-type', 'application/json');
422
436
  const results = (await Promise.all(rpcCalls)).map((res)=>{
423
- const [data, error] = res;
437
+ const [result, error] = res;
424
438
  if (error) {
425
439
  return res;
426
440
  }
427
- if (isDataStream(data)) {
441
+ if (isDataStream(result.data)) {
428
442
  return [
429
443
  null,
430
444
  new TRPCError({
@@ -435,7 +449,7 @@ async function resolveResponse(opts) {
435
449
  }
436
450
  return res;
437
451
  });
438
- const resultAsRPCResponse = results.map(([data, error], index)=>{
452
+ const resultAsRPCResponse = results.map(([result, error], index)=>{
439
453
  const call = info.calls[index];
440
454
  if (error) {
441
455
  return {
@@ -451,7 +465,7 @@ async function resolveResponse(opts) {
451
465
  }
452
466
  return {
453
467
  result: {
454
- data
468
+ data: result.data
455
469
  }
456
470
  };
457
471
  });
@@ -20,6 +20,7 @@ export interface PingOptions {
20
20
  export interface SSEStreamProducerOptions<TValue = unknown> {
21
21
  serialize?: Serialize;
22
22
  data: AsyncIterable<TValue>;
23
+ abortCtrl: AbortController;
23
24
  maxDepth?: number;
24
25
  ping?: PingOptions;
25
26
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../../../src/unstable-core-do-not-import/stream/sse.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AASpD,KAAK,SAAS,GAAG,CAAC,KAAK,EAAE,GAAG,KAAK,GAAG,CAAC;AACrC,KAAK,WAAW,GAAG,CAAC,KAAK,EAAE,GAAG,KAAK,GAAG,CAAC;AAEvC;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,wBAAwB,CAAC,MAAM,GAAG,OAAO;IACxD,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC;CACrD;AAUD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,GAAG,OAAO,EAAE,IAAI,EAAE,wBAAwB,CAAC,MAAM,CAAC,0BA0FzF;AAED,UAAU,wBAAwB;IAChC,WAAW,EAAE,WAAW,CAAC;CAC1B;AAED,UAAU,wBAAwB,CAAC,KAAK,CAAE,SAAQ,wBAAwB;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,kBAAkB,CAAC,KAAK,CAAC,CAAC;CACjC;AAED,UAAU,yBAA0B,SAAQ,wBAAwB;IAClE,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,UAAU,0BAA2B,SAAQ,wBAAwB;IACnE,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,UAAU,8BAA+B,SAAQ,wBAAwB;IACvE,IAAI,EAAE,YAAY,CAAC;CACpB;AAED,KAAK,oBAAoB,CAAC,KAAK,IAC3B,wBAAwB,CAAC,KAAK,CAAC,GAC/B,yBAAyB,GACzB,0BAA0B,GAC1B,8BAA8B,CAAC;AAEnC,MAAM,WAAW,wBAAwB;IACvC,GAAG,EAAE,MAAM,YAAY,CAAC,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,YAAY,CAAC,eAAe,CAAC,GAAG,SAAS,CAAC;IACtD,MAAM,EAAE,WAAW,CAAC;IACpB,qBAAqB,CAAC,EAAE,CACtB,IAAI,EACA;QACE,IAAI,EAAE,OAAO,CAAC;QACd,KAAK,EAAE,KAAK,CAAC;KACd,GACD;QACE,IAAI,EAAE,kBAAkB,CAAC;QACzB,KAAK,EAAE,OAAO,CAAC;KAChB,KACF,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAChC,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AACD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EACrC,IAAI,EAAE,wBAAwB,GAC7B,aAAa,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAkL5C;AAED,eAAO,MAAM,UAAU;;;;;CAKb,CAAC"}
1
+ {"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../../../src/unstable-core-do-not-import/stream/sse.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AASpD,KAAK,SAAS,GAAG,CAAC,KAAK,EAAE,GAAG,KAAK,GAAG,CAAC;AACrC,KAAK,WAAW,GAAG,CAAC,KAAK,EAAE,GAAG,KAAK,GAAG,CAAC;AAEvC;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,wBAAwB,CAAC,MAAM,GAAG,OAAO;IACxD,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC5B,SAAS,EAAE,eAAe,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC;CACrD;AAUD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,GAAG,OAAO,EAChD,IAAI,EAAE,wBAAwB,CAAC,MAAM,CAAC,0BAkGvC;AAED,UAAU,wBAAwB;IAChC,WAAW,EAAE,WAAW,CAAC;CAC1B;AAED,UAAU,wBAAwB,CAAC,KAAK,CAAE,SAAQ,wBAAwB;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,kBAAkB,CAAC,KAAK,CAAC,CAAC;CACjC;AAED,UAAU,yBAA0B,SAAQ,wBAAwB;IAClE,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,UAAU,0BAA2B,SAAQ,wBAAwB;IACnE,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,UAAU,8BAA+B,SAAQ,wBAAwB;IACvE,IAAI,EAAE,YAAY,CAAC;CACpB;AAED,KAAK,oBAAoB,CAAC,KAAK,IAC3B,wBAAwB,CAAC,KAAK,CAAC,GAC/B,yBAAyB,GACzB,0BAA0B,GAC1B,8BAA8B,CAAC;AAEnC,MAAM,WAAW,wBAAwB;IACvC,GAAG,EAAE,MAAM,YAAY,CAAC,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,YAAY,CAAC,eAAe,CAAC,GAAG,SAAS,CAAC;IACtD,MAAM,EAAE,WAAW,CAAC;IACpB,qBAAqB,CAAC,EAAE,CACtB,IAAI,EACA;QACE,IAAI,EAAE,OAAO,CAAC;QACd,KAAK,EAAE,KAAK,CAAC;KACd,GACD;QACE,IAAI,EAAE,kBAAkB,CAAC;QACzB,KAAK,EAAE,OAAO,CAAC;KAChB,KACF,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAChC,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AACD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EACrC,IAAI,EAAE,wBAAwB,GAC7B,aAAa,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAkL5C;AAED,eAAO,MAAM,UAAU;;;;;CAKb,CAAC"}
@@ -29,13 +29,14 @@ const SERIALIZED_ERROR_EVENT = 'serialized-error';
29
29
  if (opts.emitAndEndImmediately) {
30
30
  iterable = asyncIterable.takeWithGrace(iterable, {
31
31
  count: 1,
32
- gracePeriodMs: 1
32
+ gracePeriodMs: 1,
33
+ onCancel: ()=>opts.abortCtrl.abort()
33
34
  });
34
35
  }
35
36
  let maxDurationTimer = null;
36
37
  if (opts.maxDurationMs != null && opts.maxDurationMs > 0 && opts.maxDurationMs !== Infinity) {
37
38
  maxDurationTimer = promiseTimer.createPromiseTimer(opts.maxDurationMs).start();
38
- iterable = asyncIterable.withCancel(iterable, maxDurationTimer.promise);
39
+ iterable = asyncIterable.withCancel(iterable, maxDurationTimer.promise.then(()=>opts.abortCtrl.abort()));
39
40
  }
40
41
  if (ping.enabled && ping.intervalMs !== Infinity && ping.intervalMs > 0) {
41
42
  iterable = withPing.withPing(iterable, ping.intervalMs);
@@ -27,13 +27,14 @@ const SERIALIZED_ERROR_EVENT = 'serialized-error';
27
27
  if (opts.emitAndEndImmediately) {
28
28
  iterable = takeWithGrace(iterable, {
29
29
  count: 1,
30
- gracePeriodMs: 1
30
+ gracePeriodMs: 1,
31
+ onCancel: ()=>opts.abortCtrl.abort()
31
32
  });
32
33
  }
33
34
  let maxDurationTimer = null;
34
35
  if (opts.maxDurationMs != null && opts.maxDurationMs > 0 && opts.maxDurationMs !== Infinity) {
35
36
  maxDurationTimer = createPromiseTimer(opts.maxDurationMs).start();
36
- iterable = withCancel(iterable, maxDurationTimer.promise);
37
+ iterable = withCancel(iterable, maxDurationTimer.promise.then(()=>opts.abortCtrl.abort()));
37
38
  }
38
39
  if (ping.enabled && ping.intervalMs !== Infinity && ping.intervalMs > 0) {
39
40
  iterable = withPing(iterable, ping.intervalMs);
@@ -6,12 +6,13 @@ export declare function withCancel<T>(iterable: AsyncIterable<T>, cancel: Promis
6
6
  interface TakeWithGraceOptions {
7
7
  count: number;
8
8
  gracePeriodMs: number;
9
+ onCancel?: () => void;
9
10
  }
10
11
  /**
11
12
  * Derives a new {@link AsyncGenerator} based of {@link iterable}, that yields its first
12
13
  * {@link count} values. Then, a grace period of {@link gracePeriodMs} is started in which further
13
14
  * values may still come through. After this period, the generator stops.
14
15
  */
15
- export declare function takeWithGrace<T>(iterable: AsyncIterable<T>, { count, gracePeriodMs }: TakeWithGraceOptions): AsyncGenerator<T>;
16
+ export declare function takeWithGrace<T>(iterable: AsyncIterable<T>, { count, gracePeriodMs, onCancel }: TakeWithGraceOptions): AsyncGenerator<T>;
16
17
  export {};
17
18
  //# sourceMappingURL=asyncIterable.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"asyncIterable.d.ts","sourceRoot":"","sources":["../../../../src/unstable-core-do-not-import/stream/utils/asyncIterable.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAuB,UAAU,CAAC,CAAC,EACjC,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,EAC1B,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,GACvB,cAAc,CAAC,CAAC,CAAC,CAcnB;AAED,UAAU,oBAAoB;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAuB,aAAa,CAAC,CAAC,EACpC,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,EAC1B,EAAE,KAAK,EAAE,aAAa,EAAE,EAAE,oBAAoB,GAC7C,cAAc,CAAC,CAAC,CAAC,CAsBnB"}
1
+ {"version":3,"file":"asyncIterable.d.ts","sourceRoot":"","sources":["../../../../src/unstable-core-do-not-import/stream/utils/asyncIterable.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAuB,UAAU,CAAC,CAAC,EACjC,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,EAC1B,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,GACvB,cAAc,CAAC,CAAC,CAAC,CAcnB;AAED,UAAU,oBAAoB;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAuB,aAAa,CAAC,CAAC,EACpC,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,EAC1B,EAAE,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,EAAE,oBAAoB,GACvD,cAAc,CAAC,CAAC,CAAC,CAsBnB"}
@@ -28,7 +28,7 @@ var promiseTimer = require('./promiseTimer.js');
28
28
  * Derives a new {@link AsyncGenerator} based of {@link iterable}, that yields its first
29
29
  * {@link count} values. Then, a grace period of {@link gracePeriodMs} is started in which further
30
30
  * values may still come through. After this period, the generator stops.
31
- */ async function* takeWithGrace(iterable, { count , gracePeriodMs }) {
31
+ */ async function* takeWithGrace(iterable, { count , gracePeriodMs , onCancel }) {
32
32
  const iterator = iterable[Symbol.asyncIterator]();
33
33
  const timer = promiseTimer.createPromiseTimer(gracePeriodMs);
34
34
  try {
@@ -47,7 +47,7 @@ var promiseTimer = require('./promiseTimer.js');
47
47
  }
48
48
  yield result.value;
49
49
  if (--count === 0) {
50
- timer.start();
50
+ timer.start().promise.then(onCancel, utils.noop);
51
51
  }
52
52
  }
53
53
  } finally{
@@ -26,7 +26,7 @@ import { createPromiseTimer } from './promiseTimer.mjs';
26
26
  * Derives a new {@link AsyncGenerator} based of {@link iterable}, that yields its first
27
27
  * {@link count} values. Then, a grace period of {@link gracePeriodMs} is started in which further
28
28
  * values may still come through. After this period, the generator stops.
29
- */ async function* takeWithGrace(iterable, { count , gracePeriodMs }) {
29
+ */ async function* takeWithGrace(iterable, { count , gracePeriodMs , onCancel }) {
30
30
  const iterator = iterable[Symbol.asyncIterator]();
31
31
  const timer = createPromiseTimer(gracePeriodMs);
32
32
  try {
@@ -45,7 +45,7 @@ import { createPromiseTimer } from './promiseTimer.mjs';
45
45
  }
46
46
  yield result.value;
47
47
  if (--count === 0) {
48
- timer.start();
48
+ timer.start().promise.then(onCancel, noop);
49
49
  }
50
50
  }
51
51
  } finally{
@@ -16,7 +16,7 @@ function createPromiseTimer(ms) {
16
16
  return timer;
17
17
  function start() {
18
18
  if (timeout != null) {
19
- throw new Error("PromiseTimer already started.");
19
+ throw new Error('PromiseTimer already started.');
20
20
  }
21
21
  timeout = setTimeout(deferred.resolve, ms);
22
22
  return timer;
@@ -14,7 +14,7 @@ function createPromiseTimer(ms) {
14
14
  return timer;
15
15
  function start() {
16
16
  if (timeout != null) {
17
- throw new Error("PromiseTimer already started.");
17
+ throw new Error('PromiseTimer already started.');
18
18
  }
19
19
  timeout = setTimeout(deferred.resolve, ms);
20
20
  return timer;
@@ -25,5 +25,18 @@ export declare function isAsyncIterable<TValue>(value: unknown): value is AsyncI
25
25
  export declare const run: <TValue>(fn: () => TValue) => TValue;
26
26
  export declare function noop(): void;
27
27
  export declare function identity<T>(it: T): T;
28
+ /**
29
+ * Generic runtime assertion function. Throws, if the condition is not `true`.
30
+ *
31
+ * Can be used as a slightly less dangerous variant of type assertions. Code
32
+ * mistakes would be revealed at runtime then (hopefully during testing).
33
+ */
34
+ export declare function assert(condition: boolean, msg?: string): asserts condition;
35
+ export declare function sleep(ms?: number): Promise<void>;
36
+ /**
37
+ * Ponyfill for
38
+ * [`AbortSignal.any`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/any_static).
39
+ */
40
+ export declare function abortSignalsAnyPonyfill(signals: AbortSignal[]): AbortSignal;
28
41
  export {};
29
42
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/unstable-core-do-not-import/utils.ts"],"names":[],"mappings":"AAAA,gBAAgB;AAChB,eAAO,MAAM,WAAW,eAAW,CAAC;AACpC,MAAM,MAAM,WAAW,GAAG,OAAO,WAAW,CAAC;AAE7C;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACzE,IAAI,EAAE,KAAK,EACX,GAAG,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,EAAE,GACxB,KAAK,CAYP;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzE;AAED,KAAK,KAAK,GAAG,CAAC,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,GAAG,EAAE,OAAO,CAAC,CAAC;AACxE,wBAAgB,UAAU,CAAC,EAAE,EAAE,OAAO,GAAG,EAAE,IAAI,KAAK,CAEnD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChE,GAAG,EAAE,IAAI,GACR,IAAI,CAEN;AAKD,wBAAgB,eAAe,CAAC,MAAM,EACpC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,aAAa,CAAC,MAAM,CAAC,CAIhC;AAED;;GAEG;AACH,eAAO,MAAM,GAAG,GAAI,MAAM,MAAM,MAAM,MAAM,KAAG,MAAc,CAAC;AAG9D,wBAAgB,IAAI,IAAI,IAAI,CAAG;AAE/B,wBAAgB,QAAQ,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,CAEpC"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/unstable-core-do-not-import/utils.ts"],"names":[],"mappings":"AAAA,gBAAgB;AAChB,eAAO,MAAM,WAAW,eAAW,CAAC;AACpC,MAAM,MAAM,WAAW,GAAG,OAAO,WAAW,CAAC;AAE7C;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACzE,IAAI,EAAE,KAAK,EACX,GAAG,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,EAAE,GACxB,KAAK,CAYP;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzE;AAED,KAAK,KAAK,GAAG,CAAC,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,GAAG,EAAE,OAAO,CAAC,CAAC;AACxE,wBAAgB,UAAU,CAAC,EAAE,EAAE,OAAO,GAAG,EAAE,IAAI,KAAK,CAEnD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChE,GAAG,EAAE,IAAI,GACR,IAAI,CAEN;AAKD,wBAAgB,eAAe,CAAC,MAAM,EACpC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,aAAa,CAAC,MAAM,CAAC,CAIhC;AAED;;GAEG;AACH,eAAO,MAAM,GAAG,GAAI,MAAM,MAAM,MAAM,MAAM,KAAG,MAAc,CAAC;AAG9D,wBAAgB,IAAI,IAAI,IAAI,CAAG;AAE/B,wBAAgB,QAAQ,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,CAEpC;AAED;;;;;GAKG;AACH,wBAAgB,MAAM,CACpB,SAAS,EAAE,OAAO,EAClB,GAAG,SAAuB,GACzB,OAAO,CAAC,SAAS,CAInB;AAED,wBAAgB,KAAK,CAAC,EAAE,SAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAE3C;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,WAAW,CAuB3E"}
@@ -43,7 +43,47 @@ function noop() {}
43
43
  function identity(it) {
44
44
  return it;
45
45
  }
46
+ /**
47
+ * Generic runtime assertion function. Throws, if the condition is not `true`.
48
+ *
49
+ * Can be used as a slightly less dangerous variant of type assertions. Code
50
+ * mistakes would be revealed at runtime then (hopefully during testing).
51
+ */ function assert(condition, msg = 'no additional info') {
52
+ if (!condition) {
53
+ throw new Error(`AssertionError: ${msg}`);
54
+ }
55
+ }
56
+ function sleep(ms = 0) {
57
+ return new Promise((res)=>setTimeout(res, ms));
58
+ }
59
+ /**
60
+ * Ponyfill for
61
+ * [`AbortSignal.any`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/any_static).
62
+ */ function abortSignalsAnyPonyfill(signals) {
63
+ if (typeof AbortSignal.any === 'function') {
64
+ return AbortSignal.any(signals);
65
+ }
66
+ const ac = new AbortController();
67
+ for (const signal of signals){
68
+ if (signal.aborted) {
69
+ trigger();
70
+ } else if (!ac.signal.aborted) {
71
+ signal.addEventListener('abort', trigger, {
72
+ once: true
73
+ });
74
+ }
75
+ }
76
+ return ac.signal;
77
+ function trigger() {
78
+ ac.abort();
79
+ for (const signal of signals){
80
+ signal.removeEventListener('abort', trigger);
81
+ }
82
+ }
83
+ }
46
84
 
85
+ exports.abortSignalsAnyPonyfill = abortSignalsAnyPonyfill;
86
+ exports.assert = assert;
47
87
  exports.identity = identity;
48
88
  exports.isAsyncIterable = isAsyncIterable;
49
89
  exports.isFunction = isFunction;
@@ -52,4 +92,5 @@ exports.mergeWithoutOverrides = mergeWithoutOverrides;
52
92
  exports.noop = noop;
53
93
  exports.omitPrototype = omitPrototype;
54
94
  exports.run = run;
95
+ exports.sleep = sleep;
55
96
  exports.unsetMarker = unsetMarker;
@@ -41,5 +41,43 @@ function noop() {}
41
41
  function identity(it) {
42
42
  return it;
43
43
  }
44
+ /**
45
+ * Generic runtime assertion function. Throws, if the condition is not `true`.
46
+ *
47
+ * Can be used as a slightly less dangerous variant of type assertions. Code
48
+ * mistakes would be revealed at runtime then (hopefully during testing).
49
+ */ function assert(condition, msg = 'no additional info') {
50
+ if (!condition) {
51
+ throw new Error(`AssertionError: ${msg}`);
52
+ }
53
+ }
54
+ function sleep(ms = 0) {
55
+ return new Promise((res)=>setTimeout(res, ms));
56
+ }
57
+ /**
58
+ * Ponyfill for
59
+ * [`AbortSignal.any`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/any_static).
60
+ */ function abortSignalsAnyPonyfill(signals) {
61
+ if (typeof AbortSignal.any === 'function') {
62
+ return AbortSignal.any(signals);
63
+ }
64
+ const ac = new AbortController();
65
+ for (const signal of signals){
66
+ if (signal.aborted) {
67
+ trigger();
68
+ } else if (!ac.signal.aborted) {
69
+ signal.addEventListener('abort', trigger, {
70
+ once: true
71
+ });
72
+ }
73
+ }
74
+ return ac.signal;
75
+ function trigger() {
76
+ ac.abort();
77
+ for (const signal of signals){
78
+ signal.removeEventListener('abort', trigger);
79
+ }
80
+ }
81
+ }
44
82
 
45
- export { identity, isAsyncIterable, isFunction, isObject, mergeWithoutOverrides, noop, omitPrototype, run, unsetMarker };
83
+ export { abortSignalsAnyPonyfill, assert, identity, isAsyncIterable, isFunction, isObject, mergeWithoutOverrides, noop, omitPrototype, run, sleep, unsetMarker };
@@ -78,6 +78,8 @@ exports.defaultTransformer = transformer.defaultTransformer;
78
78
  exports.getDataTransformer = transformer.getDataTransformer;
79
79
  exports.transformResult = transformer.transformResult;
80
80
  exports.transformTRPCResponse = transformer.transformTRPCResponse;
81
+ exports.abortSignalsAnyPonyfill = utils.abortSignalsAnyPonyfill;
82
+ exports.assert = utils.assert;
81
83
  exports.identity = utils.identity;
82
84
  exports.isAsyncIterable = utils.isAsyncIterable;
83
85
  exports.isFunction = utils.isFunction;
@@ -86,4 +88,5 @@ exports.mergeWithoutOverrides = utils.mergeWithoutOverrides;
86
88
  exports.noop = utils.noop;
87
89
  exports.omitPrototype = utils.omitPrototype;
88
90
  exports.run = utils.run;
91
+ exports.sleep = utils.sleep;
89
92
  exports.unsetMarker = utils.unsetMarker;
@@ -24,4 +24,4 @@ export { sseHeaders, sseStreamConsumer, sseStreamProducer } from './unstable-cor
24
24
  export { isTrackedEnvelope, sse, tracked } from './unstable-core-do-not-import/stream/tracked.mjs';
25
25
  export { createDeferred } from './unstable-core-do-not-import/stream/utils/createDeferred.mjs';
26
26
  export { defaultTransformer, getDataTransformer, transformResult, transformTRPCResponse } from './unstable-core-do-not-import/transformer.mjs';
27
- export { identity, isAsyncIterable, isFunction, isObject, mergeWithoutOverrides, noop, omitPrototype, run, unsetMarker } from './unstable-core-do-not-import/utils.mjs';
27
+ export { abortSignalsAnyPonyfill, assert, identity, isAsyncIterable, isFunction, isObject, mergeWithoutOverrides, noop, omitPrototype, run, sleep, unsetMarker } from './unstable-core-do-not-import/utils.mjs';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trpc/server",
3
- "version": "11.0.0-rc.589+79fa074bd",
3
+ "version": "11.0.0-rc.590+3621497e1",
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": "79fa074bd4ab01e2eebca47fe41fb1aeea87f24e"
152
+ "gitHead": "3621497e1a831537d3c864074656a09527cefba5"
153
153
  }
@@ -15,7 +15,7 @@ import type { TRPCResponse } from '../rpc';
15
15
  import { isPromise, jsonlStreamProducer } from '../stream/jsonl';
16
16
  import { sseHeaders, sseStreamProducer } from '../stream/sse';
17
17
  import { transformTRPCResponse } from '../transformer';
18
- import { isAsyncIterable, isObject } from '../utils';
18
+ import { abortSignalsAnyPonyfill, assert, isAsyncIterable, isObject } from '../utils';
19
19
  import { getRequestInfo } from './contentType';
20
20
  import { getHTTPStatusCode } from './getHTTPStatusCode';
21
21
  import type {
@@ -260,9 +260,13 @@ export async function resolveResponse<TRouter extends AnyRouter>(
260
260
  });
261
261
  }
262
262
 
263
+ interface RPCResultOk {
264
+ data: unknown;
265
+ abortCtrl?: AbortController;
266
+ }
263
267
  type RPCResult =
264
268
  | [result: null, error: TRPCError]
265
- | [result: unknown, error?: never];
269
+ | [result: RPCResultOk, error?: never];
266
270
  const rpcCalls = info.calls.map(async (call): Promise<RPCResult> => {
267
271
  const proc = call.procedure;
268
272
  try {
@@ -279,21 +283,28 @@ export async function resolveResponse<TRouter extends AnyRouter>(
279
283
  message: `Unsupported ${req.method}-request to ${proc._def.type} procedure at path "${call.path}"`,
280
284
  });
281
285
  }
282
- /* istanbul ignore if -- @preserve */
283
- if (proc._def.type === 'subscription' && info!.isBatchCall) {
284
- throw new TRPCError({
285
- code: 'BAD_REQUEST',
286
- message: `Cannot batch subscription calls`,
287
- });
286
+ let abortCtrl: AbortController | undefined;
287
+ if (proc._def.type === 'subscription') {
288
+ /* istanbul ignore if -- @preserve */
289
+ if (info!.isBatchCall) {
290
+ throw new TRPCError({
291
+ code: 'BAD_REQUEST',
292
+ message: `Cannot batch subscription calls`,
293
+ });
294
+ }
295
+ abortCtrl = new AbortController();
288
296
  }
297
+
289
298
  const data: unknown = await proc({
290
299
  path: call.path,
291
300
  getRawInput: call.getRawInput,
292
301
  ctx,
293
302
  type: proc._def.type,
294
- signal: opts.req.signal,
303
+ signal: abortCtrl
304
+ ? abortSignalsAnyPonyfill([opts.req.signal, abortCtrl.signal])
305
+ : opts.req.signal,
295
306
  });
296
- return [data];
307
+ return [{ data, abortCtrl }];
297
308
  } catch (cause) {
298
309
  const error = getTRPCErrorFromUnknown(cause);
299
310
  const input = call.result();
@@ -314,7 +325,7 @@ export async function resolveResponse<TRouter extends AnyRouter>(
314
325
  // ----------- response handlers -----------
315
326
  if (!info.isBatchCall) {
316
327
  const [call] = info.calls;
317
- const [data, error] = await rpcCalls[0]!;
328
+ const [result, error] = await rpcCalls[0]!;
318
329
 
319
330
  switch (info.type) {
320
331
  case 'unknown':
@@ -323,7 +334,7 @@ export async function resolveResponse<TRouter extends AnyRouter>(
323
334
  // httpLink
324
335
  headers.set('content-type', 'application/json');
325
336
 
326
- if (isDataStream(data)) {
337
+ if (isDataStream(result?.data)) {
327
338
  throw new TRPCError({
328
339
  code: 'UNSUPPORTED_MEDIA_TYPE',
329
340
  message:
@@ -341,7 +352,7 @@ export async function resolveResponse<TRouter extends AnyRouter>(
341
352
  type: info.type,
342
353
  }),
343
354
  }
344
- : { result: { data } };
355
+ : { result: { data: result.data } };
345
356
 
346
357
  const headResponse = initResponse({
347
358
  ctx,
@@ -372,6 +383,11 @@ export async function resolveResponse<TRouter extends AnyRouter>(
372
383
  if (error) {
373
384
  throw error;
374
385
  }
386
+ const { data, abortCtrl } = result;
387
+ assert(
388
+ abortCtrl !== undefined,
389
+ 'subscription type must have an AbortController',
390
+ );
375
391
 
376
392
  if (!isObservable(data) && !isAsyncIterable(data)) {
377
393
  throw new TRPCError({
@@ -388,6 +404,7 @@ export async function resolveResponse<TRouter extends AnyRouter>(
388
404
  const stream = sseStreamProducer({
389
405
  ...config.experimental?.sseSubscriptions,
390
406
  data: dataAsIterable,
407
+ abortCtrl,
391
408
  serialize: (v) => config.transformer.output.serialize(v),
392
409
  formatError(errorOpts) {
393
410
  const error = getTRPCErrorFromUnknown(errorOpts.error);
@@ -481,17 +498,18 @@ export async function resolveResponse<TRouter extends AnyRouter>(
481
498
  }),
482
499
  };
483
500
  }
501
+ const { data } = result;
484
502
 
485
503
  /**
486
504
  * Not very pretty, but we need to wrap nested data in promises
487
505
  * Our stream producer will only resolve top-level async values or async values that are directly nested in another async value
488
506
  */
489
- const data = isObservable(result)
490
- ? observableToAsyncIterable(result)
491
- : Promise.resolve(result);
507
+ const dataAsPromiseOrIterable = isObservable(data)
508
+ ? observableToAsyncIterable(data)
509
+ : Promise.resolve(data);
492
510
  return {
493
511
  result: Promise.resolve({
494
- data,
512
+ data: dataAsPromiseOrIterable,
495
513
  }),
496
514
  };
497
515
  }),
@@ -539,12 +557,12 @@ export async function resolveResponse<TRouter extends AnyRouter>(
539
557
  headers.set('content-type', 'application/json');
540
558
  const results: RPCResult[] = (await Promise.all(rpcCalls)).map(
541
559
  (res): RPCResult => {
542
- const [data, error] = res;
560
+ const [result, error] = res;
543
561
  if (error) {
544
562
  return res;
545
563
  }
546
564
 
547
- if (isDataStream(data)) {
565
+ if (isDataStream(result.data)) {
548
566
  return [
549
567
  null,
550
568
  new TRPCError({
@@ -559,7 +577,7 @@ export async function resolveResponse<TRouter extends AnyRouter>(
559
577
  );
560
578
  const resultAsRPCResponse = results.map(
561
579
  (
562
- [data, error],
580
+ [result, error],
563
581
  index,
564
582
  ): TRPCResponse<unknown, inferRouterError<TRouter>> => {
565
583
  const call = info!.calls[index]!;
@@ -576,7 +594,7 @@ export async function resolveResponse<TRouter extends AnyRouter>(
576
594
  };
577
595
  }
578
596
  return {
579
- result: { data },
597
+ result: { data: result.data },
580
598
  };
581
599
  },
582
600
  );
@@ -32,6 +32,7 @@ export interface PingOptions {
32
32
  export interface SSEStreamProducerOptions<TValue = unknown> {
33
33
  serialize?: Serialize;
34
34
  data: AsyncIterable<TValue>;
35
+ abortCtrl: AbortController;
35
36
  maxDepth?: number;
36
37
  ping?: PingOptions;
37
38
  /**
@@ -61,7 +62,9 @@ type SSEvent = Partial<{
61
62
  *
62
63
  * @see https://html.spec.whatwg.org/multipage/server-sent-events.html
63
64
  */
64
- export function sseStreamProducer<TValue = unknown>(opts: SSEStreamProducerOptions<TValue>) {
65
+ export function sseStreamProducer<TValue = unknown>(
66
+ opts: SSEStreamProducerOptions<TValue>,
67
+ ) {
65
68
  const stream = createReadableStream<SSEvent>();
66
69
  stream.controller.enqueue({ comment: 'connected' });
67
70
 
@@ -74,11 +77,15 @@ export function sseStreamProducer<TValue = unknown>(opts: SSEStreamProducerOptio
74
77
 
75
78
  run(async () => {
76
79
  let iterable: AsyncIterable<TValue | typeof PING_SYM> = opts.data;
77
-
80
+
78
81
  iterable = withCancel(iterable, stream.cancelledPromise);
79
82
 
80
83
  if (opts.emitAndEndImmediately) {
81
- iterable = takeWithGrace(iterable, { count: 1, gracePeriodMs: 1 });
84
+ iterable = takeWithGrace(iterable, {
85
+ count: 1,
86
+ gracePeriodMs: 1,
87
+ onCancel: () => opts.abortCtrl.abort(),
88
+ });
82
89
  }
83
90
 
84
91
  let maxDurationTimer: PromiseTimer | null = null;
@@ -88,7 +95,10 @@ export function sseStreamProducer<TValue = unknown>(opts: SSEStreamProducerOptio
88
95
  opts.maxDurationMs !== Infinity
89
96
  ) {
90
97
  maxDurationTimer = createPromiseTimer(opts.maxDurationMs).start();
91
- iterable = withCancel(iterable, maxDurationTimer.promise);
98
+ iterable = withCancel(
99
+ iterable,
100
+ maxDurationTimer.promise.then(() => opts.abortCtrl.abort()),
101
+ );
92
102
  }
93
103
 
94
104
  if (ping.enabled && ping.intervalMs !== Infinity && ping.intervalMs > 0) {