@trpc/server 11.0.0-rc.569 → 11.0.0-rc.576

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 (34) hide show
  1. package/dist/bundle-analysis.json +129 -80
  2. package/dist/unstable-core-do-not-import/stream/sse.d.ts +3 -3
  3. package/dist/unstable-core-do-not-import/stream/sse.d.ts.map +1 -1
  4. package/dist/unstable-core-do-not-import/stream/sse.js +53 -65
  5. package/dist/unstable-core-do-not-import/stream/sse.mjs +55 -67
  6. package/dist/unstable-core-do-not-import/stream/utils/asyncIterable.d.ts +17 -0
  7. package/dist/unstable-core-do-not-import/stream/utils/asyncIterable.d.ts.map +1 -0
  8. package/dist/unstable-core-do-not-import/stream/utils/asyncIterable.js +59 -0
  9. package/dist/unstable-core-do-not-import/stream/utils/asyncIterable.mjs +56 -0
  10. package/dist/unstable-core-do-not-import/stream/utils/createDeferred.d.ts +0 -11
  11. package/dist/unstable-core-do-not-import/stream/utils/createDeferred.d.ts.map +1 -1
  12. package/dist/unstable-core-do-not-import/stream/utils/createDeferred.js +0 -29
  13. package/dist/unstable-core-do-not-import/stream/utils/createDeferred.mjs +1 -29
  14. package/dist/unstable-core-do-not-import/stream/utils/promiseTimer.d.ts +8 -0
  15. package/dist/unstable-core-do-not-import/stream/utils/promiseTimer.d.ts.map +1 -0
  16. package/dist/unstable-core-do-not-import/stream/utils/promiseTimer.js +38 -0
  17. package/dist/unstable-core-do-not-import/stream/utils/promiseTimer.mjs +36 -0
  18. package/dist/unstable-core-do-not-import/stream/utils/withPing.d.ts +7 -0
  19. package/dist/unstable-core-do-not-import/stream/utils/withPing.d.ts.map +1 -0
  20. package/dist/unstable-core-do-not-import/stream/utils/withPing.js +37 -0
  21. package/dist/unstable-core-do-not-import/stream/utils/withPing.mjs +34 -0
  22. package/dist/unstable-core-do-not-import/utils.d.ts +2 -0
  23. package/dist/unstable-core-do-not-import/utils.d.ts.map +1 -1
  24. package/dist/unstable-core-do-not-import/utils.js +7 -0
  25. package/dist/unstable-core-do-not-import/utils.mjs +6 -1
  26. package/dist/unstable-core-do-not-import.js +2 -1
  27. package/dist/unstable-core-do-not-import.mjs +2 -2
  28. package/package.json +2 -2
  29. package/src/unstable-core-do-not-import/stream/sse.ts +60 -91
  30. package/src/unstable-core-do-not-import/stream/utils/asyncIterable.ts +62 -0
  31. package/src/unstable-core-do-not-import/stream/utils/createDeferred.ts +0 -36
  32. package/src/unstable-core-do-not-import/stream/utils/promiseTimer.ts +40 -0
  33. package/src/unstable-core-do-not-import/stream/utils/withPing.ts +35 -0
  34. package/src/unstable-core-do-not-import/utils.ts +7 -0
@@ -0,0 +1,34 @@
1
+ import { createPromiseTimer } from './promiseTimer.mjs';
2
+
3
+ const PING_SYM = Symbol('ping');
4
+ const PING_RESULT = {
5
+ value: PING_SYM,
6
+ done: false
7
+ };
8
+ /**
9
+ * Derives a new {@link AsyncGenerator} based of {@link iterable}, that yields {@link PING_SYM}
10
+ * whenever no value has been yielded for {@link pingIntervalMs}.
11
+ */ async function* withPing(iterable, pingIntervalMs) {
12
+ const timer = createPromiseTimer(pingIntervalMs);
13
+ const iterator = iterable[Symbol.asyncIterator]();
14
+ while(true){
15
+ const nextPromise = iterator.next();
16
+ const pingPromise = timer.start().promise.then(()=>PING_RESULT);
17
+ let result;
18
+ try {
19
+ result = await Promise.race([
20
+ nextPromise,
21
+ pingPromise
22
+ ]);
23
+ } finally{
24
+ timer.clear();
25
+ }
26
+ if (result.done) {
27
+ return result.value;
28
+ }
29
+ yield result.value;
30
+ timer.reset();
31
+ }
32
+ }
33
+
34
+ export { PING_SYM, withPing };
@@ -23,5 +23,7 @@ export declare function isAsyncIterable<TValue>(value: unknown): value is AsyncI
23
23
  * Run an IIFE
24
24
  */
25
25
  export declare const run: <TValue>(fn: () => TValue) => TValue;
26
+ export declare function noop(): void;
27
+ export declare function identity<T>(it: T): T;
26
28
  export {};
27
29
  //# 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"}
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"}
@@ -38,11 +38,18 @@ function isAsyncIterable(value) {
38
38
  /**
39
39
  * Run an IIFE
40
40
  */ const run = (fn)=>fn();
41
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
42
+ function noop() {}
43
+ function identity(it) {
44
+ return it;
45
+ }
41
46
 
47
+ exports.identity = identity;
42
48
  exports.isAsyncIterable = isAsyncIterable;
43
49
  exports.isFunction = isFunction;
44
50
  exports.isObject = isObject;
45
51
  exports.mergeWithoutOverrides = mergeWithoutOverrides;
52
+ exports.noop = noop;
46
53
  exports.omitPrototype = omitPrototype;
47
54
  exports.run = run;
48
55
  exports.unsetMarker = unsetMarker;
@@ -36,5 +36,10 @@ function isAsyncIterable(value) {
36
36
  /**
37
37
  * Run an IIFE
38
38
  */ const run = (fn)=>fn();
39
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
40
+ function noop() {}
41
+ function identity(it) {
42
+ return it;
43
+ }
39
44
 
40
- export { isAsyncIterable, isFunction, isObject, mergeWithoutOverrides, omitPrototype, run, unsetMarker };
45
+ export { identity, isAsyncIterable, isFunction, isObject, mergeWithoutOverrides, noop, omitPrototype, run, unsetMarker };
@@ -74,15 +74,16 @@ exports.isTrackedEnvelope = tracked.isTrackedEnvelope;
74
74
  exports.sse = tracked.sse;
75
75
  exports.tracked = tracked.tracked;
76
76
  exports.createDeferred = createDeferred.createDeferred;
77
- exports.createTimeoutPromise = createDeferred.createTimeoutPromise;
78
77
  exports.defaultTransformer = transformer.defaultTransformer;
79
78
  exports.getDataTransformer = transformer.getDataTransformer;
80
79
  exports.transformResult = transformer.transformResult;
81
80
  exports.transformTRPCResponse = transformer.transformTRPCResponse;
81
+ exports.identity = utils.identity;
82
82
  exports.isAsyncIterable = utils.isAsyncIterable;
83
83
  exports.isFunction = utils.isFunction;
84
84
  exports.isObject = utils.isObject;
85
85
  exports.mergeWithoutOverrides = utils.mergeWithoutOverrides;
86
+ exports.noop = utils.noop;
86
87
  exports.omitPrototype = utils.omitPrototype;
87
88
  exports.run = utils.run;
88
89
  exports.unsetMarker = utils.unsetMarker;
@@ -22,6 +22,6 @@ export { parseTRPCMessage } from './unstable-core-do-not-import/rpc/parseTRPCMes
22
22
  export { isPromise, jsonlStreamConsumer, jsonlStreamProducer } from './unstable-core-do-not-import/stream/jsonl.mjs';
23
23
  export { sseHeaders, sseStreamConsumer, sseStreamProducer } from './unstable-core-do-not-import/stream/sse.mjs';
24
24
  export { isTrackedEnvelope, sse, tracked } from './unstable-core-do-not-import/stream/tracked.mjs';
25
- export { createDeferred, createTimeoutPromise } from './unstable-core-do-not-import/stream/utils/createDeferred.mjs';
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 { isAsyncIterable, isFunction, isObject, mergeWithoutOverrides, omitPrototype, run, unsetMarker } from './unstable-core-do-not-import/utils.mjs';
27
+ export { identity, isAsyncIterable, isFunction, isObject, mergeWithoutOverrides, noop, omitPrototype, run, 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.569+8049266de",
3
+ "version": "11.0.0-rc.576+deed81c1b",
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": "8049266dee912f1d98542f6baf750ee11b45b5ad"
152
+ "gitHead": "deed81c1b24b29bb028075af56cef3b3329e2a8e"
153
153
  }
@@ -1,10 +1,14 @@
1
1
  import { getTRPCErrorFromUnknown } from '../error/TRPCError';
2
2
  import type { MaybePromise } from '../types';
3
- import { run } from '../utils';
3
+ import { identity, run } from '../utils';
4
4
  import type { inferTrackedOutput } from './tracked';
5
5
  import { isTrackedEnvelope } from './tracked';
6
- import { createDeferred, createTimeoutPromise } from './utils/createDeferred';
6
+ import { takeWithGrace, withCancel } from './utils/asyncIterable';
7
+ import { createDeferred } from './utils/createDeferred';
7
8
  import { createReadableStream } from './utils/createReadableStream';
9
+ import type { PromiseTimer } from './utils/promiseTimer';
10
+ import { createPromiseTimer } from './utils/promiseTimer';
11
+ import { PING_SYM, withPing } from './utils/withPing';
8
12
 
9
13
  type Serialize = (value: any) => any;
10
14
  type Deserialize = (value: any) => any;
@@ -25,9 +29,9 @@ export interface PingOptions {
25
29
  intervalMs?: number;
26
30
  }
27
31
 
28
- export interface SSEStreamProducerOptions {
32
+ export interface SSEStreamProducerOptions<TValue = unknown> {
29
33
  serialize?: Serialize;
30
- data: AsyncIterable<unknown>;
34
+ data: AsyncIterable<TValue>;
31
35
  maxDepth?: number;
32
36
  ping?: PingOptions;
33
37
  /**
@@ -57,13 +61,11 @@ type SSEvent = Partial<{
57
61
  *
58
62
  * @see https://html.spec.whatwg.org/multipage/server-sent-events.html
59
63
  */
60
- export function sseStreamProducer(opts: SSEStreamProducerOptions) {
64
+ export function sseStreamProducer<TValue = unknown>(opts: SSEStreamProducerOptions<TValue>) {
61
65
  const stream = createReadableStream<SSEvent>();
62
- stream.controller.enqueue({
63
- comment: 'connected',
64
- });
66
+ stream.controller.enqueue({ comment: 'connected' });
65
67
 
66
- const { serialize = (v) => v } = opts;
68
+ const { serialize = identity } = opts;
67
69
 
68
70
  const ping: Required<PingOptions> = {
69
71
  enabled: opts.ping?.enabled ?? false,
@@ -71,96 +73,63 @@ export function sseStreamProducer(opts: SSEStreamProducerOptions) {
71
73
  };
72
74
 
73
75
  run(async () => {
74
- const iterator = opts.data[Symbol.asyncIterator]();
75
-
76
- const closedPromise = stream.cancelledPromise.then(() => 'closed' as const);
77
- const maxDurationPromise = createTimeoutPromise(
78
- opts.maxDurationMs ?? Infinity,
79
- 'maxDuration' as const,
80
- );
81
-
82
- let nextPromise = iterator.next();
83
-
84
- while (true) {
85
- const pingPromise = createTimeoutPromise(
86
- ping.enabled ? ping.intervalMs : Infinity,
87
- 'ping' as const,
88
- );
89
- const next = await Promise.race([
90
- nextPromise.catch((err) => {
91
- if (err instanceof Error && err.name === 'AbortError') {
92
- // Ensures that aborting the request doesn't throw an error
93
- return 'aborted' as const;
94
- }
95
- return getTRPCErrorFromUnknown(err);
96
- }),
97
- pingPromise.promise,
98
- closedPromise,
99
- maxDurationPromise.promise,
100
- ]);
101
-
102
- pingPromise.clear();
103
- if (next === 'aborted') {
104
- break;
105
- }
106
- if (next === 'closed') {
107
- break;
108
- }
109
- if (next === 'maxDuration') {
110
- break;
111
- }
76
+ let iterable: AsyncIterable<TValue | typeof PING_SYM> = opts.data;
77
+
78
+ iterable = withCancel(iterable, stream.cancelledPromise);
112
79
 
113
- if (next === 'ping') {
114
- stream.controller.enqueue({
115
- comment: 'ping',
116
- });
117
- continue;
118
- }
80
+ if (opts.emitAndEndImmediately) {
81
+ iterable = takeWithGrace(iterable, { count: 1, gracePeriodMs: 1 });
82
+ }
119
83
 
120
- if (next instanceof Error) {
121
- const data = opts.formatError
122
- ? opts.formatError({ error: next })
123
- : null;
124
- stream.controller.enqueue({
125
- event: SERIALIZED_ERROR_EVENT,
126
- data: JSON.stringify(serialize(data)),
127
- });
128
- break;
129
- }
130
- if (next.done) {
131
- break;
132
- }
84
+ let maxDurationTimer: PromiseTimer | null = null;
85
+ if (
86
+ opts.maxDurationMs != null &&
87
+ opts.maxDurationMs > 0 &&
88
+ opts.maxDurationMs !== Infinity
89
+ ) {
90
+ maxDurationTimer = createPromiseTimer(opts.maxDurationMs).start();
91
+ iterable = withCancel(iterable, maxDurationTimer.promise);
92
+ }
133
93
 
134
- const value = next.value;
94
+ if (ping.enabled && ping.intervalMs !== Infinity && ping.intervalMs > 0) {
95
+ iterable = withPing(iterable, ping.intervalMs);
96
+ }
135
97
 
136
- const chunk: SSEvent = isTrackedEnvelope(value)
137
- ? {
138
- id: value[0],
139
- data: value[1],
140
- }
141
- : {
142
- data: value,
143
- };
144
- if ('data' in chunk) {
145
- chunk.data = JSON.stringify(serialize(chunk.data));
146
- }
98
+ try {
99
+ for await (const value of iterable) {
100
+ if (value === PING_SYM) {
101
+ stream.controller.enqueue({ comment: 'ping' });
102
+ continue;
103
+ }
147
104
 
148
- stream.controller.enqueue(chunk);
105
+ const chunk: SSEvent = isTrackedEnvelope(value)
106
+ ? { id: value[0], data: value[1] }
107
+ : { data: value };
108
+ if ('data' in chunk) {
109
+ chunk.data = JSON.stringify(serialize(chunk.data));
110
+ }
149
111
 
150
- if (opts.emitAndEndImmediately) {
151
- // end the stream in the next tick so that we can send a few more events from the queue
152
- setTimeout(maxDurationPromise.resolve, 1);
112
+ stream.controller.enqueue(chunk);
153
113
  }
154
-
155
- nextPromise = iterator.next();
156
- }
157
- maxDurationPromise.clear();
158
- await iterator.return?.();
159
- try {
114
+ } catch (err) {
115
+ // ignore abort errors, send any other errors
116
+ if (!(err instanceof Error) || err.name !== 'AbortError') {
117
+ // `err` must be caused by `opts.data`, `JSON.stringify` or `serialize`.
118
+ // So, a user error in any case.
119
+ const error = getTRPCErrorFromUnknown(err);
120
+ const data = opts.formatError?.({ error }) ?? null;
121
+ stream.controller.enqueue({
122
+ event: SERIALIZED_ERROR_EVENT,
123
+ data: JSON.stringify(serialize(data)),
124
+ });
125
+ }
126
+ } finally {
127
+ maxDurationTimer?.clear();
160
128
  stream.controller.close();
161
- } catch {}
162
- }).catch((error) => {
163
- return stream.controller.error(error);
129
+ }
130
+ }).catch((err) => {
131
+ // should not be reached; just in case...
132
+ stream.controller.error(err);
164
133
  });
165
134
 
166
135
  return stream.readable.pipeThrough(
@@ -0,0 +1,62 @@
1
+ import { noop } from '../../utils';
2
+ import { createPromiseTimer } from './promiseTimer';
3
+
4
+ /**
5
+ * Derives a new {@link AsyncGenerator} based of {@link iterable}, that automatically stops with the
6
+ * passed {@link cancel} promise.
7
+ */
8
+ export async function* withCancel<T>(
9
+ iterable: AsyncIterable<T>,
10
+ cancel: Promise<unknown>,
11
+ ): AsyncGenerator<T> {
12
+ const cancelPromise = cancel.then(noop);
13
+ const iterator = iterable[Symbol.asyncIterator]();
14
+ while (true) {
15
+ const result = await Promise.race([iterator.next(), cancelPromise]);
16
+ if (result == null) {
17
+ await iterator.return?.();
18
+ break;
19
+ }
20
+ if (result.done) {
21
+ break;
22
+ }
23
+ yield result.value;
24
+ }
25
+ }
26
+
27
+ interface TakeWithGraceOptions {
28
+ count: number;
29
+ gracePeriodMs: number;
30
+ }
31
+
32
+ /**
33
+ * Derives a new {@link AsyncGenerator} based of {@link iterable}, that yields its first
34
+ * {@link count} values. Then, a grace period of {@link gracePeriodMs} is started in which further
35
+ * values may still come through. After this period, the generator stops.
36
+ */
37
+ export async function* takeWithGrace<T>(
38
+ iterable: AsyncIterable<T>,
39
+ { count, gracePeriodMs }: TakeWithGraceOptions,
40
+ ): AsyncGenerator<T> {
41
+ const iterator = iterable[Symbol.asyncIterator]();
42
+ const timer = createPromiseTimer(gracePeriodMs);
43
+ try {
44
+ while (true) {
45
+ const result = await Promise.race([iterator.next(), timer.promise]);
46
+ if (result == null) {
47
+ // cancelled
48
+ await iterator.return?.();
49
+ break;
50
+ }
51
+ if (result.done) {
52
+ break;
53
+ }
54
+ yield result.value;
55
+ if (--count === 0) {
56
+ timer.start();
57
+ }
58
+ }
59
+ } finally {
60
+ timer.clear();
61
+ }
62
+ }
@@ -10,39 +10,3 @@ export function createDeferred<TValue>() {
10
10
  return { promise, resolve: resolve!, reject: reject! };
11
11
  }
12
12
  export type Deferred<TValue> = ReturnType<typeof createDeferred<TValue>>;
13
-
14
- export const createTimeoutPromise = <TValue>(
15
- timeoutMs: number,
16
- value: TValue,
17
- ) => {
18
- let deferred = createDeferred<TValue>();
19
- deferred = deferred as typeof deferred & { clear: () => void };
20
-
21
- let timeout: ReturnType<typeof setTimeout> | null = null;
22
- const clear = () => {
23
- if (timeout) {
24
- clearTimeout(timeout);
25
- timeout = null;
26
- }
27
- };
28
- const resolve = () => {
29
- deferred.resolve(value);
30
- clear();
31
- };
32
- if (timeoutMs !== Infinity) {
33
- timeout = setTimeout(resolve, timeoutMs);
34
- timeout.unref?.();
35
- }
36
-
37
- return {
38
- promise: deferred.promise,
39
- /**
40
- * Clear the timeout without resolving the promise
41
- */
42
- clear,
43
- /**
44
- * Resolve the promise with the value
45
- */
46
- resolve,
47
- };
48
- };
@@ -0,0 +1,40 @@
1
+ import { createDeferred } from './createDeferred';
2
+
3
+ export function createPromiseTimer(ms: number) {
4
+ let deferred = createDeferred<void>();
5
+ let timeout: ReturnType<typeof setTimeout> | null = null;
6
+
7
+ const timer = {
8
+ get promise() {
9
+ return deferred.promise;
10
+ },
11
+
12
+ start,
13
+ reset,
14
+ clear,
15
+ };
16
+ return timer;
17
+
18
+ function start(): PromiseTimer {
19
+ if (timeout != null) {
20
+ throw new Error("PromiseTimer already started.");
21
+ }
22
+ timeout = setTimeout(deferred.resolve, ms);
23
+ return timer;
24
+ }
25
+
26
+ function reset(): PromiseTimer {
27
+ clear();
28
+ deferred = createDeferred();
29
+ return timer;
30
+ }
31
+
32
+ function clear(): PromiseTimer {
33
+ if (timeout != null) {
34
+ clearTimeout(timeout);
35
+ timeout = null;
36
+ }
37
+ return timer;
38
+ }
39
+ }
40
+ export type PromiseTimer = ReturnType<typeof createPromiseTimer>;
@@ -0,0 +1,35 @@
1
+ import { createPromiseTimer } from './promiseTimer';
2
+
3
+ export const PING_SYM = Symbol('ping');
4
+
5
+ const PING_RESULT: IteratorResult<typeof PING_SYM> = {
6
+ value: PING_SYM,
7
+ done: false,
8
+ };
9
+
10
+ /**
11
+ * Derives a new {@link AsyncGenerator} based of {@link iterable}, that yields {@link PING_SYM}
12
+ * whenever no value has been yielded for {@link pingIntervalMs}.
13
+ */
14
+ export async function* withPing<TValue>(
15
+ iterable: AsyncIterable<TValue>,
16
+ pingIntervalMs: number,
17
+ ): AsyncGenerator<TValue | typeof PING_SYM> {
18
+ const timer = createPromiseTimer(pingIntervalMs);
19
+ const iterator = iterable[Symbol.asyncIterator]();
20
+ while (true) {
21
+ const nextPromise = iterator.next();
22
+ const pingPromise = timer.start().promise.then(() => PING_RESULT);
23
+ let result: IteratorResult<TValue | typeof PING_SYM>;
24
+ try {
25
+ result = await Promise.race([nextPromise, pingPromise]);
26
+ } finally {
27
+ timer.clear();
28
+ }
29
+ if (result.done) {
30
+ return result.value;
31
+ }
32
+ yield result.value;
33
+ timer.reset();
34
+ }
35
+ }
@@ -61,3 +61,10 @@ export function isAsyncIterable<TValue>(
61
61
  * Run an IIFE
62
62
  */
63
63
  export const run = <TValue>(fn: () => TValue): TValue => fn();
64
+
65
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
66
+ export function noop(): void {}
67
+
68
+ export function identity<T>(it: T): T {
69
+ return it;
70
+ }