@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.
- package/dist/bundle-analysis.json +129 -80
- package/dist/unstable-core-do-not-import/stream/sse.d.ts +3 -3
- package/dist/unstable-core-do-not-import/stream/sse.d.ts.map +1 -1
- package/dist/unstable-core-do-not-import/stream/sse.js +53 -65
- package/dist/unstable-core-do-not-import/stream/sse.mjs +55 -67
- package/dist/unstable-core-do-not-import/stream/utils/asyncIterable.d.ts +17 -0
- package/dist/unstable-core-do-not-import/stream/utils/asyncIterable.d.ts.map +1 -0
- package/dist/unstable-core-do-not-import/stream/utils/asyncIterable.js +59 -0
- package/dist/unstable-core-do-not-import/stream/utils/asyncIterable.mjs +56 -0
- package/dist/unstable-core-do-not-import/stream/utils/createDeferred.d.ts +0 -11
- package/dist/unstable-core-do-not-import/stream/utils/createDeferred.d.ts.map +1 -1
- package/dist/unstable-core-do-not-import/stream/utils/createDeferred.js +0 -29
- package/dist/unstable-core-do-not-import/stream/utils/createDeferred.mjs +1 -29
- package/dist/unstable-core-do-not-import/stream/utils/promiseTimer.d.ts +8 -0
- package/dist/unstable-core-do-not-import/stream/utils/promiseTimer.d.ts.map +1 -0
- package/dist/unstable-core-do-not-import/stream/utils/promiseTimer.js +38 -0
- package/dist/unstable-core-do-not-import/stream/utils/promiseTimer.mjs +36 -0
- package/dist/unstable-core-do-not-import/stream/utils/withPing.d.ts +7 -0
- package/dist/unstable-core-do-not-import/stream/utils/withPing.d.ts.map +1 -0
- package/dist/unstable-core-do-not-import/stream/utils/withPing.js +37 -0
- package/dist/unstable-core-do-not-import/stream/utils/withPing.mjs +34 -0
- package/dist/unstable-core-do-not-import/utils.d.ts +2 -0
- package/dist/unstable-core-do-not-import/utils.d.ts.map +1 -1
- package/dist/unstable-core-do-not-import/utils.js +7 -0
- package/dist/unstable-core-do-not-import/utils.mjs +6 -1
- package/dist/unstable-core-do-not-import.js +2 -1
- package/dist/unstable-core-do-not-import.mjs +2 -2
- package/package.json +2 -2
- package/src/unstable-core-do-not-import/stream/sse.ts +60 -91
- package/src/unstable-core-do-not-import/stream/utils/asyncIterable.ts +62 -0
- package/src/unstable-core-do-not-import/stream/utils/createDeferred.ts +0 -36
- package/src/unstable-core-do-not-import/stream/utils/promiseTimer.ts +40 -0
- package/src/unstable-core-do-not-import/stream/utils/withPing.ts +35 -0
- 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
|
|
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.
|
|
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": "
|
|
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 {
|
|
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<
|
|
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 =
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
});
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
80
|
+
if (opts.emitAndEndImmediately) {
|
|
81
|
+
iterable = takeWithGrace(iterable, { count: 1, gracePeriodMs: 1 });
|
|
82
|
+
}
|
|
119
83
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
94
|
+
if (ping.enabled && ping.intervalMs !== Infinity && ping.intervalMs > 0) {
|
|
95
|
+
iterable = withPing(iterable, ping.intervalMs);
|
|
96
|
+
}
|
|
135
97
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
}
|
|
162
|
-
}).catch((
|
|
163
|
-
|
|
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
|
+
}
|