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

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 (37) hide show
  1. package/dist/bundle-analysis.json +154 -147
  2. package/dist/unstable-core-do-not-import/http/getHTTPStatusCode.d.ts +6 -1
  3. package/dist/unstable-core-do-not-import/http/getHTTPStatusCode.d.ts.map +1 -1
  4. package/dist/unstable-core-do-not-import/http/getHTTPStatusCode.js +27 -0
  5. package/dist/unstable-core-do-not-import/http/getHTTPStatusCode.mjs +24 -1
  6. package/dist/unstable-core-do-not-import/http/resolveResponse.d.ts.map +1 -1
  7. package/dist/unstable-core-do-not-import/http/resolveResponse.js +30 -16
  8. package/dist/unstable-core-do-not-import/http/resolveResponse.mjs +31 -17
  9. package/dist/unstable-core-do-not-import/rpc/codes.d.ts +2 -9
  10. package/dist/unstable-core-do-not-import/rpc/codes.d.ts.map +1 -1
  11. package/dist/unstable-core-do-not-import/stream/sse.d.ts +1 -0
  12. package/dist/unstable-core-do-not-import/stream/sse.d.ts.map +1 -1
  13. package/dist/unstable-core-do-not-import/stream/sse.js +3 -2
  14. package/dist/unstable-core-do-not-import/stream/sse.mjs +3 -2
  15. package/dist/unstable-core-do-not-import/stream/utils/asyncIterable.d.ts +2 -1
  16. package/dist/unstable-core-do-not-import/stream/utils/asyncIterable.d.ts.map +1 -1
  17. package/dist/unstable-core-do-not-import/stream/utils/asyncIterable.js +2 -2
  18. package/dist/unstable-core-do-not-import/stream/utils/asyncIterable.mjs +2 -2
  19. package/dist/unstable-core-do-not-import/stream/utils/promiseTimer.js +1 -1
  20. package/dist/unstable-core-do-not-import/stream/utils/promiseTimer.mjs +1 -1
  21. package/dist/unstable-core-do-not-import/types.d.ts +6 -0
  22. package/dist/unstable-core-do-not-import/types.d.ts.map +1 -1
  23. package/dist/unstable-core-do-not-import/utils.d.ts +13 -0
  24. package/dist/unstable-core-do-not-import/utils.d.ts.map +1 -1
  25. package/dist/unstable-core-do-not-import/utils.js +41 -0
  26. package/dist/unstable-core-do-not-import/utils.mjs +39 -1
  27. package/dist/unstable-core-do-not-import.js +7 -0
  28. package/dist/unstable-core-do-not-import.mjs +2 -2
  29. package/package.json +2 -2
  30. package/src/unstable-core-do-not-import/http/getHTTPStatusCode.ts +35 -3
  31. package/src/unstable-core-do-not-import/http/resolveResponse.ts +39 -21
  32. package/src/unstable-core-do-not-import/rpc/codes.ts +2 -10
  33. package/src/unstable-core-do-not-import/stream/sse.ts +14 -4
  34. package/src/unstable-core-do-not-import/stream/utils/asyncIterable.ts +3 -2
  35. package/src/unstable-core-do-not-import/stream/utils/promiseTimer.ts +1 -1
  36. package/src/unstable-core-do-not-import/types.ts +11 -0
  37. package/src/unstable-core-do-not-import/utils.ts +48 -0
@@ -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 };
@@ -41,8 +41,12 @@ exports.getBatchStreamFormatter = batchStreamFormatter.getBatchStreamFormatter;
41
41
  exports.getRequestInfo = contentType.getRequestInfo;
42
42
  exports.octetInputParser = contentTypeParsers.octetInputParser;
43
43
  exports.formDataToObject = formDataToObject.formDataToObject;
44
+ exports.HTTP_CODE_TO_JSONRPC2 = getHTTPStatusCode.HTTP_CODE_TO_JSONRPC2;
45
+ exports.JSONRPC2_TO_HTTP_CODE = getHTTPStatusCode.JSONRPC2_TO_HTTP_CODE;
44
46
  exports.getHTTPStatusCode = getHTTPStatusCode.getHTTPStatusCode;
45
47
  exports.getHTTPStatusCodeFromError = getHTTPStatusCode.getHTTPStatusCodeFromError;
48
+ exports.getStatusCodeFromKey = getHTTPStatusCode.getStatusCodeFromKey;
49
+ exports.getStatusKeyFromCode = getHTTPStatusCode.getStatusKeyFromCode;
46
50
  exports.parseConnectionParamsFromString = parseConnectionParams.parseConnectionParamsFromString;
47
51
  exports.parseConnectionParamsFromUnknown = parseConnectionParams.parseConnectionParamsFromUnknown;
48
52
  exports.resolveResponse = resolveResponse.resolveResponse;
@@ -78,6 +82,8 @@ exports.defaultTransformer = transformer.defaultTransformer;
78
82
  exports.getDataTransformer = transformer.getDataTransformer;
79
83
  exports.transformResult = transformer.transformResult;
80
84
  exports.transformTRPCResponse = transformer.transformTRPCResponse;
85
+ exports.abortSignalsAnyPonyfill = utils.abortSignalsAnyPonyfill;
86
+ exports.assert = utils.assert;
81
87
  exports.identity = utils.identity;
82
88
  exports.isAsyncIterable = utils.isAsyncIterable;
83
89
  exports.isFunction = utils.isFunction;
@@ -86,4 +92,5 @@ exports.mergeWithoutOverrides = utils.mergeWithoutOverrides;
86
92
  exports.noop = utils.noop;
87
93
  exports.omitPrototype = utils.omitPrototype;
88
94
  exports.run = utils.run;
95
+ exports.sleep = utils.sleep;
89
96
  exports.unsetMarker = utils.unsetMarker;
@@ -6,7 +6,7 @@ export { getBatchStreamFormatter } from './unstable-core-do-not-import/http/batc
6
6
  export { getRequestInfo } from './unstable-core-do-not-import/http/contentType.mjs';
7
7
  export { octetInputParser } from './unstable-core-do-not-import/http/contentTypeParsers.mjs';
8
8
  export { formDataToObject } from './unstable-core-do-not-import/http/formDataToObject.mjs';
9
- export { getHTTPStatusCode, getHTTPStatusCodeFromError } from './unstable-core-do-not-import/http/getHTTPStatusCode.mjs';
9
+ export { HTTP_CODE_TO_JSONRPC2, JSONRPC2_TO_HTTP_CODE, getHTTPStatusCode, getHTTPStatusCodeFromError, getStatusCodeFromKey, getStatusKeyFromCode } from './unstable-core-do-not-import/http/getHTTPStatusCode.mjs';
10
10
  export { parseConnectionParamsFromString, parseConnectionParamsFromUnknown } from './unstable-core-do-not-import/http/parseConnectionParams.mjs';
11
11
  export { resolveResponse } from './unstable-core-do-not-import/http/resolveResponse.mjs';
12
12
  export { toURL } from './unstable-core-do-not-import/http/toURL.mjs';
@@ -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.591+956d65f99",
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": "956d65f99bd9dd4b52343a4f406111079af0ce7f"
153
153
  }
@@ -1,9 +1,10 @@
1
1
  import type { TRPCError } from '../error/TRPCError';
2
2
  import type { TRPC_ERROR_CODES_BY_KEY, TRPCResponse } from '../rpc';
3
3
  import { TRPC_ERROR_CODES_BY_NUMBER } from '../rpc';
4
+ import type { InvertKeyValue, ValueOf } from '../types';
4
5
  import { isObject } from '../utils';
5
6
 
6
- const JSONRPC2_TO_HTTP_CODE: Record<
7
+ export const JSONRPC2_TO_HTTP_CODE: Record<
7
8
  keyof typeof TRPC_ERROR_CODES_BY_KEY,
8
9
  number
9
10
  > = {
@@ -28,11 +29,42 @@ const JSONRPC2_TO_HTTP_CODE: Record<
28
29
  GATEWAY_TIMEOUT: 504,
29
30
  };
30
31
 
31
- function getStatusCodeFromKey(code: keyof typeof TRPC_ERROR_CODES_BY_KEY) {
32
+ export const HTTP_CODE_TO_JSONRPC2: InvertKeyValue<
33
+ typeof JSONRPC2_TO_HTTP_CODE
34
+ > = {
35
+ 400: 'BAD_REQUEST',
36
+ 401: 'UNAUTHORIZED',
37
+ 403: 'FORBIDDEN',
38
+ 404: 'NOT_FOUND',
39
+ 405: 'METHOD_NOT_SUPPORTED',
40
+ 408: 'TIMEOUT',
41
+ 409: 'CONFLICT',
42
+ 412: 'PRECONDITION_FAILED',
43
+ 413: 'PAYLOAD_TOO_LARGE',
44
+ 415: 'UNSUPPORTED_MEDIA_TYPE',
45
+ 422: 'UNPROCESSABLE_CONTENT',
46
+ 429: 'TOO_MANY_REQUESTS',
47
+ 499: 'CLIENT_CLOSED_REQUEST',
48
+ 500: 'INTERNAL_SERVER_ERROR',
49
+ 501: 'NOT_IMPLEMENTED',
50
+ 502: 'BAD_GATEWAY',
51
+ 503: 'SERVICE_UNAVAILABLE',
52
+ 504: 'GATEWAY_TIMEOUT',
53
+ } as const;
54
+
55
+ export function getStatusCodeFromKey(
56
+ code: keyof typeof TRPC_ERROR_CODES_BY_KEY,
57
+ ) {
32
58
  return JSONRPC2_TO_HTTP_CODE[code] ?? 500;
33
59
  }
34
60
 
35
- export function getHTTPStatusCode(json: TRPCResponse | TRPCResponse[]): number {
61
+ export function getStatusKeyFromCode(
62
+ code: keyof typeof HTTP_CODE_TO_JSONRPC2,
63
+ ): ValueOf<typeof HTTP_CODE_TO_JSONRPC2> {
64
+ return HTTP_CODE_TO_JSONRPC2[code] ?? 'INTERNAL_SERVER_ERROR';
65
+ }
66
+
67
+ export function getHTTPStatusCode(json: TRPCResponse | TRPCResponse[]) {
36
68
  const arr = Array.isArray(json) ? json : [json];
37
69
  const httpStatuses = new Set<number>(
38
70
  arr.map((res) => {
@@ -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
  );
@@ -1,4 +1,4 @@
1
- import type { ValueOf } from '../types';
1
+ import type { InvertKeyValue, ValueOf } from '../types';
2
2
 
3
3
  // reference: https://www.jsonrpc.org/specification
4
4
 
@@ -41,16 +41,8 @@ export const TRPC_ERROR_CODES_BY_KEY = {
41
41
  CLIENT_CLOSED_REQUEST: -32099, // 499
42
42
  } as const;
43
43
 
44
- type KeyFromValue<TValue, TType extends Record<PropertyKey, PropertyKey>> = {
45
- [K in keyof TType]: TValue extends TType[K] ? K : never;
46
- }[keyof TType];
47
-
48
- type Invert<TType extends Record<PropertyKey, PropertyKey>> = {
49
- [TValue in TType[keyof TType]]: KeyFromValue<TValue, TType>;
50
- };
51
-
52
44
  // pure
53
- export const TRPC_ERROR_CODES_BY_NUMBER: Invert<
45
+ export const TRPC_ERROR_CODES_BY_NUMBER: InvertKeyValue<
54
46
  typeof TRPC_ERROR_CODES_BY_KEY
55
47
  > = {
56
48
  [-32700]: 'PARSE_ERROR',
@@ -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) {
@@ -27,6 +27,7 @@ export async function* withCancel<T>(
27
27
  interface TakeWithGraceOptions {
28
28
  count: number;
29
29
  gracePeriodMs: number;
30
+ onCancel?: () => void;
30
31
  }
31
32
 
32
33
  /**
@@ -36,7 +37,7 @@ interface TakeWithGraceOptions {
36
37
  */
37
38
  export async function* takeWithGrace<T>(
38
39
  iterable: AsyncIterable<T>,
39
- { count, gracePeriodMs }: TakeWithGraceOptions,
40
+ { count, gracePeriodMs, onCancel }: TakeWithGraceOptions,
40
41
  ): AsyncGenerator<T> {
41
42
  const iterator = iterable[Symbol.asyncIterator]();
42
43
  const timer = createPromiseTimer(gracePeriodMs);
@@ -53,7 +54,7 @@ export async function* takeWithGrace<T>(
53
54
  }
54
55
  yield result.value;
55
56
  if (--count === 0) {
56
- timer.start();
57
+ timer.start().promise.then(onCancel, noop);
57
58
  }
58
59
  }
59
60
  } finally {
@@ -17,7 +17,7 @@ export function createPromiseTimer(ms: number) {
17
17
 
18
18
  function start(): PromiseTimer {
19
19
  if (timeout != null) {
20
- throw new Error("PromiseTimer already started.");
20
+ throw new Error('PromiseTimer already started.');
21
21
  }
22
22
  timeout = setTimeout(deferred.resolve, ms);
23
23
  return timer;
@@ -131,6 +131,17 @@ export type PickFirstDefined<TType, TPick> = undefined extends TType
131
131
  : TPick
132
132
  : TType;
133
133
 
134
+ export type KeyFromValue<
135
+ TValue,
136
+ TType extends Record<PropertyKey, PropertyKey>,
137
+ > = {
138
+ [K in keyof TType]: TValue extends TType[K] ? K : never;
139
+ }[keyof TType];
140
+
141
+ export type InvertKeyValue<TType extends Record<PropertyKey, PropertyKey>> = {
142
+ [TValue in TType[keyof TType]]: KeyFromValue<TValue, TType>;
143
+ };
144
+
134
145
  /**
135
146
  * ================================
136
147
  * tRPC specific types
@@ -68,3 +68,51 @@ export function noop(): void {}
68
68
  export function identity<T>(it: T): T {
69
69
  return it;
70
70
  }
71
+
72
+ /**
73
+ * Generic runtime assertion function. Throws, if the condition is not `true`.
74
+ *
75
+ * Can be used as a slightly less dangerous variant of type assertions. Code
76
+ * mistakes would be revealed at runtime then (hopefully during testing).
77
+ */
78
+ export function assert(
79
+ condition: boolean,
80
+ msg = 'no additional info',
81
+ ): asserts condition {
82
+ if (!condition) {
83
+ throw new Error(`AssertionError: ${msg}`);
84
+ }
85
+ }
86
+
87
+ export function sleep(ms = 0): Promise<void> {
88
+ return new Promise<void>((res) => setTimeout(res, ms));
89
+ }
90
+
91
+ /**
92
+ * Ponyfill for
93
+ * [`AbortSignal.any`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/any_static).
94
+ */
95
+ export function abortSignalsAnyPonyfill(signals: AbortSignal[]): AbortSignal {
96
+ if (typeof AbortSignal.any === 'function') {
97
+ return AbortSignal.any(signals);
98
+ }
99
+
100
+ const ac = new AbortController();
101
+
102
+ for (const signal of signals) {
103
+ if (signal.aborted) {
104
+ trigger();
105
+ } else if (!ac.signal.aborted) {
106
+ signal.addEventListener('abort', trigger, { once: true });
107
+ }
108
+ }
109
+
110
+ return ac.signal;
111
+
112
+ function trigger() {
113
+ ac.abort();
114
+ for (const signal of signals) {
115
+ signal.removeEventListener('abort', trigger);
116
+ }
117
+ }
118
+ }