@trpc/server 11.0.0-rc.630 → 11.0.0-rc.633
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/@trpc/server/http.d.ts +1 -1
- package/dist/@trpc/server/http.d.ts.map +1 -1
- package/dist/adapters/aws-lambda/index.js +1 -0
- package/dist/adapters/aws-lambda/index.mjs +1 -0
- package/dist/adapters/express.js +1 -0
- package/dist/adapters/express.mjs +1 -0
- package/dist/adapters/fastify/fastifyRequestHandler.js +2 -1
- package/dist/adapters/fastify/fastifyRequestHandler.mjs +2 -1
- package/dist/adapters/fetch/fetchRequestHandler.js +1 -0
- package/dist/adapters/fetch/fetchRequestHandler.mjs +1 -0
- package/dist/adapters/next-app-dir/nextAppDirCaller.js +1 -0
- package/dist/adapters/next-app-dir/nextAppDirCaller.mjs +1 -0
- package/dist/adapters/next-app-dir/notFound.js +1 -0
- package/dist/adapters/next-app-dir/notFound.mjs +1 -0
- package/dist/adapters/next-app-dir/redirect.js +1 -0
- package/dist/adapters/next-app-dir/redirect.mjs +1 -0
- package/dist/adapters/next.js +1 -0
- package/dist/adapters/next.mjs +1 -0
- package/dist/adapters/node-http/incomingMessageToRequest.d.ts +1 -1
- package/dist/adapters/node-http/incomingMessageToRequest.d.ts.map +1 -1
- package/dist/adapters/node-http/incomingMessageToRequest.js +7 -5
- package/dist/adapters/node-http/incomingMessageToRequest.mjs +7 -5
- package/dist/adapters/node-http/nodeHTTPRequestHandler.d.ts.map +1 -1
- package/dist/adapters/node-http/nodeHTTPRequestHandler.js +9 -42
- package/dist/adapters/node-http/nodeHTTPRequestHandler.mjs +9 -42
- package/dist/adapters/node-http/writeResponse.d.ts +18 -0
- package/dist/adapters/node-http/writeResponse.d.ts.map +1 -0
- package/dist/adapters/node-http/writeResponse.js +80 -0
- package/dist/adapters/node-http/writeResponse.mjs +77 -0
- package/dist/adapters/standalone.js +1 -0
- package/dist/adapters/standalone.mjs +1 -0
- package/dist/adapters/ws.js +1 -0
- package/dist/adapters/ws.mjs +1 -0
- package/dist/bundle-analysis.json +195 -168
- package/dist/http.js +1 -2
- package/dist/http.mjs +1 -1
- package/dist/index.js +1 -0
- package/dist/index.mjs +1 -0
- package/dist/rpc.js +1 -0
- package/dist/rpc.mjs +1 -0
- package/dist/shared.js +1 -0
- package/dist/shared.mjs +1 -0
- package/dist/unstable-core-do-not-import/http/isAbortError.d.ts +4 -0
- package/dist/unstable-core-do-not-import/http/isAbortError.d.ts.map +1 -0
- package/dist/unstable-core-do-not-import/http/isAbortError.js +9 -0
- package/dist/unstable-core-do-not-import/http/isAbortError.mjs +7 -0
- package/dist/unstable-core-do-not-import/http/resolveResponse.d.ts.map +1 -1
- package/dist/unstable-core-do-not-import/http/resolveResponse.js +3 -3
- package/dist/unstable-core-do-not-import/http/resolveResponse.mjs +3 -3
- package/dist/unstable-core-do-not-import/initTRPC.js +2 -2
- package/dist/unstable-core-do-not-import/initTRPC.mjs +2 -2
- package/dist/unstable-core-do-not-import/rootConfig.d.ts +14 -14
- package/dist/unstable-core-do-not-import/rootConfig.d.ts.map +1 -1
- package/dist/unstable-core-do-not-import/rpc/envelopes.d.ts +7 -10
- package/dist/unstable-core-do-not-import/rpc/envelopes.d.ts.map +1 -1
- package/dist/unstable-core-do-not-import/stream/jsonl.d.ts +6 -9
- package/dist/unstable-core-do-not-import/stream/jsonl.d.ts.map +1 -1
- package/dist/unstable-core-do-not-import/stream/jsonl.js +75 -124
- package/dist/unstable-core-do-not-import/stream/jsonl.mjs +76 -125
- package/dist/unstable-core-do-not-import/stream/sse.d.ts +11 -1
- package/dist/unstable-core-do-not-import/stream/sse.d.ts.map +1 -1
- package/dist/unstable-core-do-not-import/stream/sse.js +154 -86
- package/dist/unstable-core-do-not-import/stream/sse.mjs +155 -87
- package/dist/unstable-core-do-not-import/stream/utils/asyncIterable.d.ts +10 -10
- package/dist/unstable-core-do-not-import/stream/utils/asyncIterable.d.ts.map +1 -1
- package/dist/unstable-core-do-not-import/stream/utils/asyncIterable.js +47 -34
- package/dist/unstable-core-do-not-import/stream/utils/asyncIterable.mjs +47 -34
- package/dist/unstable-core-do-not-import/stream/utils/createReadableStream.d.ts +0 -4
- package/dist/unstable-core-do-not-import/stream/utils/createReadableStream.d.ts.map +1 -1
- package/dist/unstable-core-do-not-import/stream/utils/createReadableStream.js +0 -11
- package/dist/unstable-core-do-not-import/stream/utils/createReadableStream.mjs +1 -11
- package/dist/unstable-core-do-not-import/stream/utils/disposablePromiseTimer.d.ts +6 -0
- package/dist/unstable-core-do-not-import/stream/utils/disposablePromiseTimer.d.ts.map +1 -0
- package/dist/unstable-core-do-not-import/stream/utils/disposablePromiseTimer.js +28 -0
- package/dist/unstable-core-do-not-import/stream/utils/disposablePromiseTimer.mjs +25 -0
- package/dist/unstable-core-do-not-import/stream/utils/withPing.d.ts.map +1 -1
- package/dist/unstable-core-do-not-import/stream/utils/withPing.js +17 -17
- package/dist/unstable-core-do-not-import/stream/utils/withPing.mjs +17 -17
- package/dist/unstable-core-do-not-import/stream/utils/withRefCount.d.ts +17 -0
- package/dist/unstable-core-do-not-import/stream/utils/withRefCount.d.ts.map +1 -0
- package/dist/unstable-core-do-not-import/stream/utils/withRefCount.js +59 -0
- package/dist/unstable-core-do-not-import/stream/utils/withRefCount.mjs +57 -0
- package/dist/unstable-core-do-not-import/transformer.d.ts +1 -4
- package/dist/unstable-core-do-not-import/transformer.d.ts.map +1 -1
- package/dist/unstable-core-do-not-import.d.ts +2 -2
- package/dist/unstable-core-do-not-import.d.ts.map +1 -1
- package/dist/unstable-core-do-not-import.js +2 -2
- package/dist/unstable-core-do-not-import.mjs +1 -1
- package/package.json +3 -3
- package/src/@trpc/server/http.ts +0 -1
- package/src/adapters/fastify/fastifyRequestHandler.ts +1 -1
- package/src/adapters/node-http/incomingMessageToRequest.ts +8 -4
- package/src/adapters/node-http/nodeHTTPRequestHandler.ts +8 -46
- package/src/adapters/node-http/writeResponse.ts +91 -0
- package/src/unstable-core-do-not-import/http/isAbortError.ts +7 -0
- package/src/unstable-core-do-not-import/http/resolveResponse.ts +3 -4
- package/src/unstable-core-do-not-import/initTRPC.ts +1 -1
- package/src/unstable-core-do-not-import/rootConfig.ts +17 -17
- package/src/unstable-core-do-not-import/rpc/envelopes.ts +7 -12
- package/src/unstable-core-do-not-import/stream/jsonl.ts +85 -154
- package/src/unstable-core-do-not-import/stream/sse.ts +179 -92
- package/src/unstable-core-do-not-import/stream/utils/asyncIterable.ts +58 -37
- package/src/unstable-core-do-not-import/stream/utils/createReadableStream.ts +0 -13
- package/src/unstable-core-do-not-import/stream/utils/disposablePromiseTimer.ts +27 -0
- package/src/unstable-core-do-not-import/stream/utils/withPing.ts +31 -19
- package/src/unstable-core-do-not-import/stream/utils/withRefCount.ts +93 -0
- package/src/unstable-core-do-not-import.ts +2 -2
- package/dist/unstable-core-do-not-import/http/batchStreamFormatter.d.ts +0 -24
- package/dist/unstable-core-do-not-import/http/batchStreamFormatter.d.ts.map +0 -1
- package/dist/unstable-core-do-not-import/http/batchStreamFormatter.js +0 -32
- package/dist/unstable-core-do-not-import/http/batchStreamFormatter.mjs +0 -30
- package/dist/unstable-core-do-not-import/stream/utils/promiseTimer.d.ts +0 -8
- package/dist/unstable-core-do-not-import/stream/utils/promiseTimer.d.ts.map +0 -1
- package/dist/unstable-core-do-not-import/stream/utils/promiseTimer.js +0 -38
- package/dist/unstable-core-do-not-import/stream/utils/promiseTimer.mjs +0 -36
- package/src/unstable-core-do-not-import/http/batchStreamFormatter.ts +0 -29
- package/src/unstable-core-do-not-import/stream/utils/promiseTimer.ts +0 -40
|
@@ -1,13 +1,13 @@
|
|
|
1
|
+
import { Unpromise } from '../../vendor/unpromise';
|
|
1
2
|
import { getTRPCErrorFromUnknown } from '../error/TRPCError';
|
|
3
|
+
import { isAbortError } from '../http/isAbortError';
|
|
2
4
|
import type { MaybePromise } from '../types';
|
|
3
5
|
import { identity, run } from '../utils';
|
|
4
6
|
import type { EventSourceLike } from './sse.types';
|
|
5
7
|
import type { inferTrackedOutput } from './tracked';
|
|
6
8
|
import { isTrackedEnvelope } from './tracked';
|
|
7
|
-
import { takeWithGrace,
|
|
9
|
+
import { takeWithGrace, withMaxDuration } from './utils/asyncIterable';
|
|
8
10
|
import { createReadableStream } from './utils/createReadableStream';
|
|
9
|
-
import type { PromiseTimer } from './utils/promiseTimer';
|
|
10
|
-
import { createPromiseTimer } from './utils/promiseTimer';
|
|
11
11
|
import { PING_SYM, withPing } from './utils/withPing';
|
|
12
12
|
|
|
13
13
|
type Serialize = (value: any) => any;
|
|
@@ -50,6 +50,7 @@ export interface SSEStreamProducerOptions<TValue = unknown> {
|
|
|
50
50
|
formatError?: (opts: { error: unknown }) => unknown;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
const PING_EVENT = 'ping';
|
|
53
54
|
const SERIALIZED_ERROR_EVENT = 'serialized-error';
|
|
54
55
|
|
|
55
56
|
type SSEvent = Partial<{
|
|
@@ -80,27 +81,23 @@ export function sseStreamProducer<TValue = unknown>(
|
|
|
80
81
|
|
|
81
82
|
let iterable: AsyncIterable<TValue | typeof PING_SYM> = opts.data;
|
|
82
83
|
|
|
83
|
-
iterable = withCancel(iterable, stream.cancelledPromise);
|
|
84
|
-
|
|
85
84
|
if (opts.emitAndEndImmediately) {
|
|
86
85
|
iterable = takeWithGrace(iterable, {
|
|
87
86
|
count: 1,
|
|
88
87
|
gracePeriodMs: 1,
|
|
89
|
-
|
|
88
|
+
abortCtrl: opts.abortCtrl,
|
|
90
89
|
});
|
|
91
90
|
}
|
|
92
91
|
|
|
93
|
-
let maxDurationTimer: PromiseTimer | null = null;
|
|
94
92
|
if (
|
|
95
|
-
opts.maxDurationMs
|
|
93
|
+
opts.maxDurationMs &&
|
|
96
94
|
opts.maxDurationMs > 0 &&
|
|
97
95
|
opts.maxDurationMs !== Infinity
|
|
98
96
|
) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
);
|
|
97
|
+
iterable = withMaxDuration(iterable, {
|
|
98
|
+
maxDurationMs: opts.maxDurationMs,
|
|
99
|
+
abortCtrl: opts.abortCtrl,
|
|
100
|
+
});
|
|
104
101
|
}
|
|
105
102
|
|
|
106
103
|
if (ping.enabled && ping.intervalMs !== Infinity && ping.intervalMs > 0) {
|
|
@@ -115,7 +112,7 @@ export function sseStreamProducer<TValue = unknown>(
|
|
|
115
112
|
|
|
116
113
|
for await (value of iterable) {
|
|
117
114
|
if (value === PING_SYM) {
|
|
118
|
-
stream.controller.enqueue({
|
|
115
|
+
stream.controller.enqueue({ event: PING_EVENT, data: '' });
|
|
119
116
|
continue;
|
|
120
117
|
}
|
|
121
118
|
|
|
@@ -133,20 +130,24 @@ export function sseStreamProducer<TValue = unknown>(
|
|
|
133
130
|
chunk = null;
|
|
134
131
|
}
|
|
135
132
|
} catch (err) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
// So, a user error in any case.
|
|
140
|
-
const error = getTRPCErrorFromUnknown(err);
|
|
141
|
-
const data = opts.formatError?.({ error }) ?? null;
|
|
142
|
-
stream.controller.enqueue({
|
|
143
|
-
event: SERIALIZED_ERROR_EVENT,
|
|
144
|
-
data: JSON.stringify(serialize(data)),
|
|
145
|
-
});
|
|
133
|
+
if (isAbortError(err)) {
|
|
134
|
+
// ignore abort errors, send any other errors
|
|
135
|
+
return;
|
|
146
136
|
}
|
|
137
|
+
// `err` must be caused by `opts.data`, `JSON.stringify` or `serialize`.
|
|
138
|
+
// So, a user error in any case.
|
|
139
|
+
const error = getTRPCErrorFromUnknown(err);
|
|
140
|
+
const data = opts.formatError?.({ error }) ?? null;
|
|
141
|
+
stream.controller.enqueue({
|
|
142
|
+
event: SERIALIZED_ERROR_EVENT,
|
|
143
|
+
data: JSON.stringify(serialize(data)),
|
|
144
|
+
});
|
|
147
145
|
} finally {
|
|
148
|
-
|
|
149
|
-
|
|
146
|
+
try {
|
|
147
|
+
stream.controller.close();
|
|
148
|
+
} catch {
|
|
149
|
+
// ignore
|
|
150
|
+
}
|
|
150
151
|
}
|
|
151
152
|
}).catch((err) => {
|
|
152
153
|
// should not be reached; just in case...
|
|
@@ -200,12 +201,23 @@ interface ConsumerStreamResultConnecting<TConfig extends ConsumerConfig>
|
|
|
200
201
|
type: 'connecting';
|
|
201
202
|
event: EventSourceLike.EventOf<TConfig['EventSource']> | null;
|
|
202
203
|
}
|
|
204
|
+
interface ConsumerStreamResultTimeout<TConfig extends ConsumerConfig>
|
|
205
|
+
extends ConsumerStreamResultBase<TConfig> {
|
|
206
|
+
type: 'timeout';
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
interface ConsumerStreamResultPing<TConfig extends ConsumerConfig>
|
|
210
|
+
extends ConsumerStreamResultBase<TConfig> {
|
|
211
|
+
type: 'ping';
|
|
212
|
+
}
|
|
203
213
|
|
|
204
214
|
type ConsumerStreamResult<TConfig extends ConsumerConfig> =
|
|
205
215
|
| ConsumerStreamResultData<TConfig>
|
|
206
216
|
| ConsumerStreamResultError<TConfig>
|
|
207
217
|
| ConsumerStreamResultOpened<TConfig>
|
|
208
|
-
| ConsumerStreamResultConnecting<TConfig
|
|
218
|
+
| ConsumerStreamResultConnecting<TConfig>
|
|
219
|
+
| ConsumerStreamResultTimeout<TConfig>
|
|
220
|
+
| ConsumerStreamResultPing<TConfig>;
|
|
209
221
|
|
|
210
222
|
export interface SSEStreamConsumerOptions<TConfig extends ConsumerConfig> {
|
|
211
223
|
url: () => MaybePromise<string>;
|
|
@@ -215,6 +227,10 @@ export interface SSEStreamConsumerOptions<TConfig extends ConsumerConfig> {
|
|
|
215
227
|
signal: AbortSignal;
|
|
216
228
|
deserialize?: Deserialize;
|
|
217
229
|
EventSource: TConfig['EventSource'];
|
|
230
|
+
/**
|
|
231
|
+
* Reconnect after inactivity in milliseconds
|
|
232
|
+
*/
|
|
233
|
+
reconnectAfterInactivityMs?: number;
|
|
218
234
|
}
|
|
219
235
|
|
|
220
236
|
interface ConsumerConfig {
|
|
@@ -223,6 +239,31 @@ interface ConsumerConfig {
|
|
|
223
239
|
EventSource: EventSourceLike.AnyConstructor;
|
|
224
240
|
}
|
|
225
241
|
|
|
242
|
+
async function withTimeout<T>(opts: {
|
|
243
|
+
promise: Promise<T>;
|
|
244
|
+
timeoutMs: number;
|
|
245
|
+
onTimeout: () => Promise<NoInfer<T>>;
|
|
246
|
+
}): Promise<T> {
|
|
247
|
+
let timeoutId: ReturnType<typeof setTimeout>;
|
|
248
|
+
|
|
249
|
+
const timeoutPromise = new Promise<null>((resolve) => {
|
|
250
|
+
timeoutId = setTimeout(() => {
|
|
251
|
+
resolve(null);
|
|
252
|
+
}, opts.timeoutMs);
|
|
253
|
+
});
|
|
254
|
+
let res;
|
|
255
|
+
try {
|
|
256
|
+
res = await Unpromise.race([opts.promise, timeoutPromise]);
|
|
257
|
+
} finally {
|
|
258
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
259
|
+
clearTimeout(timeoutId!);
|
|
260
|
+
}
|
|
261
|
+
if (res === null) {
|
|
262
|
+
return await opts.onTimeout();
|
|
263
|
+
}
|
|
264
|
+
return res;
|
|
265
|
+
}
|
|
266
|
+
|
|
226
267
|
/**
|
|
227
268
|
* @see https://html.spec.whatwg.org/multipage/server-sent-events.html
|
|
228
269
|
*/
|
|
@@ -235,99 +276,145 @@ export function sseStreamConsumer<TConfig extends ConsumerConfig>(
|
|
|
235
276
|
|
|
236
277
|
let _es: InstanceType<TConfig['EventSource']> | null = null;
|
|
237
278
|
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
type: 'connecting',
|
|
248
|
-
eventSource: _es,
|
|
249
|
-
event: null,
|
|
250
|
-
});
|
|
251
|
-
eventSource.addEventListener('open', () => {
|
|
279
|
+
const createStream = () =>
|
|
280
|
+
new ReadableStream<ConsumerStreamResult<TConfig>>({
|
|
281
|
+
async start(controller) {
|
|
282
|
+
const [url, init] = await Promise.all([opts.url(), opts.init()]);
|
|
283
|
+
const eventSource = (_es = new opts.EventSource(
|
|
284
|
+
url,
|
|
285
|
+
init,
|
|
286
|
+
) as InstanceType<TConfig['EventSource']>);
|
|
287
|
+
|
|
252
288
|
controller.enqueue({
|
|
253
|
-
type: '
|
|
254
|
-
eventSource,
|
|
289
|
+
type: 'connecting',
|
|
290
|
+
eventSource: _es,
|
|
291
|
+
event: null,
|
|
292
|
+
});
|
|
293
|
+
eventSource.addEventListener('open', () => {
|
|
294
|
+
controller.enqueue({
|
|
295
|
+
type: 'opened',
|
|
296
|
+
eventSource,
|
|
297
|
+
});
|
|
255
298
|
});
|
|
256
|
-
});
|
|
257
299
|
|
|
258
|
-
|
|
259
|
-
|
|
300
|
+
eventSource.addEventListener(SERIALIZED_ERROR_EVENT, (_msg) => {
|
|
301
|
+
const msg = _msg as EventSourceLike.MessageEvent;
|
|
260
302
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
303
|
+
controller.enqueue({
|
|
304
|
+
type: 'serialized-error',
|
|
305
|
+
error: deserialize(JSON.parse(msg.data)),
|
|
306
|
+
eventSource,
|
|
307
|
+
});
|
|
265
308
|
});
|
|
266
|
-
|
|
267
|
-
eventSource.addEventListener('error', (event) => {
|
|
268
|
-
if (eventSource.readyState === EventSource.CLOSED) {
|
|
269
|
-
controller.error(event);
|
|
270
|
-
} else {
|
|
309
|
+
eventSource.addEventListener(PING_EVENT, () => {
|
|
271
310
|
controller.enqueue({
|
|
272
|
-
type: '
|
|
311
|
+
type: 'ping',
|
|
273
312
|
eventSource,
|
|
274
|
-
event,
|
|
275
313
|
});
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
314
|
+
});
|
|
315
|
+
eventSource.addEventListener('error', (event) => {
|
|
316
|
+
if (eventSource.readyState === EventSource.CLOSED) {
|
|
317
|
+
controller.error(event);
|
|
318
|
+
} else {
|
|
319
|
+
controller.enqueue({
|
|
320
|
+
type: 'connecting',
|
|
321
|
+
eventSource,
|
|
322
|
+
event,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
eventSource.addEventListener('message', (_msg) => {
|
|
327
|
+
const msg = _msg as EventSourceLike.MessageEvent;
|
|
328
|
+
|
|
329
|
+
const chunk = deserialize(JSON.parse(msg.data));
|
|
280
330
|
|
|
281
|
-
|
|
331
|
+
const def: SSEvent = {
|
|
332
|
+
data: chunk,
|
|
333
|
+
};
|
|
334
|
+
if (msg.lastEventId) {
|
|
335
|
+
def.id = msg.lastEventId;
|
|
336
|
+
}
|
|
337
|
+
controller.enqueue({
|
|
338
|
+
type: 'data',
|
|
339
|
+
data: def as inferTrackedOutput<TConfig['data']>,
|
|
340
|
+
eventSource,
|
|
341
|
+
});
|
|
342
|
+
});
|
|
282
343
|
|
|
283
|
-
const
|
|
284
|
-
|
|
344
|
+
const onAbort = () => {
|
|
345
|
+
controller.close();
|
|
346
|
+
eventSource.close();
|
|
285
347
|
};
|
|
286
|
-
if (
|
|
287
|
-
|
|
348
|
+
if (signal.aborted) {
|
|
349
|
+
onAbort();
|
|
350
|
+
} else {
|
|
351
|
+
signal.addEventListener('abort', onAbort);
|
|
288
352
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
});
|
|
353
|
+
},
|
|
354
|
+
cancel() {
|
|
355
|
+
_es?.close();
|
|
356
|
+
},
|
|
357
|
+
});
|
|
295
358
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
},
|
|
309
|
-
});
|
|
359
|
+
const getNewStreamAndReader = () => {
|
|
360
|
+
const stream = createStream();
|
|
361
|
+
const reader = stream.getReader();
|
|
362
|
+
|
|
363
|
+
return {
|
|
364
|
+
reader,
|
|
365
|
+
cancel: () => {
|
|
366
|
+
reader.releaseLock();
|
|
367
|
+
return stream.cancel();
|
|
368
|
+
},
|
|
369
|
+
};
|
|
370
|
+
};
|
|
310
371
|
return {
|
|
311
372
|
[Symbol.asyncIterator]() {
|
|
312
|
-
|
|
373
|
+
let stream = getNewStreamAndReader();
|
|
313
374
|
|
|
314
375
|
const iterator: AsyncIterator<ConsumerStreamResult<TConfig>> = {
|
|
315
376
|
async next() {
|
|
316
|
-
|
|
377
|
+
let promise = stream.reader.read();
|
|
378
|
+
|
|
379
|
+
if (opts.reconnectAfterInactivityMs) {
|
|
380
|
+
promise = withTimeout({
|
|
381
|
+
promise,
|
|
382
|
+
timeoutMs: opts.reconnectAfterInactivityMs,
|
|
383
|
+
onTimeout: async () => {
|
|
384
|
+
// Close and release old reader
|
|
385
|
+
await stream.cancel();
|
|
386
|
+
|
|
387
|
+
// Create new reader
|
|
388
|
+
stream = getNewStreamAndReader();
|
|
389
|
+
|
|
390
|
+
return {
|
|
391
|
+
value: {
|
|
392
|
+
type: 'timeout',
|
|
393
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
394
|
+
eventSource: _es!,
|
|
395
|
+
},
|
|
396
|
+
done: false,
|
|
397
|
+
};
|
|
398
|
+
},
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const result = await promise;
|
|
317
403
|
|
|
318
|
-
|
|
404
|
+
// console.debug('result', result, 'done', result.done);
|
|
405
|
+
if (result.done) {
|
|
319
406
|
return {
|
|
320
|
-
value:
|
|
407
|
+
value: result.value,
|
|
321
408
|
done: true,
|
|
322
409
|
};
|
|
323
410
|
}
|
|
324
411
|
return {
|
|
325
|
-
value:
|
|
412
|
+
value: result.value,
|
|
326
413
|
done: false,
|
|
327
414
|
};
|
|
328
415
|
},
|
|
329
416
|
async return() {
|
|
330
|
-
|
|
417
|
+
await stream.cancel();
|
|
331
418
|
return {
|
|
332
419
|
value: undefined,
|
|
333
420
|
done: true,
|
|
@@ -1,40 +1,47 @@
|
|
|
1
1
|
import { Unpromise } from '../../../vendor/unpromise';
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
disposablePromiseTimer,
|
|
4
|
+
disposablePromiseTimerResult,
|
|
5
|
+
} from './disposablePromiseTimer';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
|
-
* Derives a new {@link AsyncGenerator} based
|
|
7
|
-
* passed {@link cancel} promise.
|
|
8
|
+
* Derives a new {@link AsyncGenerator} based on {@link iterable}, that automatically stops after the specified duration.
|
|
8
9
|
*/
|
|
9
|
-
export async function*
|
|
10
|
+
export async function* withMaxDuration<T>(
|
|
10
11
|
iterable: AsyncIterable<T>,
|
|
11
|
-
|
|
12
|
+
opts: { maxDurationMs: number; abortCtrl: AbortController },
|
|
12
13
|
): AsyncGenerator<T> {
|
|
13
|
-
const cancelPromise = cancel.then(noop);
|
|
14
14
|
const iterator = iterable[Symbol.asyncIterator]();
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
15
|
+
|
|
16
|
+
const timer = disposablePromiseTimer(opts.maxDurationMs);
|
|
17
|
+
try {
|
|
18
|
+
const timerPromise = timer.start();
|
|
19
|
+
|
|
20
|
+
// declaration outside the loop for garbage collection reasons
|
|
21
|
+
let result: null | IteratorResult<T> | typeof disposablePromiseTimerResult;
|
|
22
|
+
|
|
23
|
+
while (true) {
|
|
24
|
+
result = await Unpromise.race([iterator.next(), timerPromise]);
|
|
25
|
+
if (result === disposablePromiseTimerResult) {
|
|
26
|
+
// cancelled due to timeout
|
|
27
|
+
opts.abortCtrl.abort();
|
|
28
|
+
const res = await iterator.return?.();
|
|
29
|
+
return res?.value;
|
|
30
|
+
}
|
|
31
|
+
if (result.done) {
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
yield result.value;
|
|
35
|
+
// free up reference for garbage collection
|
|
36
|
+
result = null;
|
|
25
37
|
}
|
|
26
|
-
|
|
27
|
-
//
|
|
28
|
-
|
|
38
|
+
} finally {
|
|
39
|
+
// dispose timer
|
|
40
|
+
// Shouldn't be needed, but build breaks with `using` keyword
|
|
41
|
+
timer[Symbol.dispose]();
|
|
29
42
|
}
|
|
30
43
|
}
|
|
31
44
|
|
|
32
|
-
interface TakeWithGraceOptions {
|
|
33
|
-
count: number;
|
|
34
|
-
gracePeriodMs: number;
|
|
35
|
-
onCancel: () => void;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
45
|
/**
|
|
39
46
|
* Derives a new {@link AsyncGenerator} based of {@link iterable}, that yields its first
|
|
40
47
|
* {@link count} values. Then, a grace period of {@link gracePeriodMs} is started in which further
|
|
@@ -42,31 +49,45 @@ interface TakeWithGraceOptions {
|
|
|
42
49
|
*/
|
|
43
50
|
export async function* takeWithGrace<T>(
|
|
44
51
|
iterable: AsyncIterable<T>,
|
|
45
|
-
|
|
52
|
+
opts: {
|
|
53
|
+
count: number;
|
|
54
|
+
gracePeriodMs: number;
|
|
55
|
+
abortCtrl: AbortController;
|
|
56
|
+
},
|
|
46
57
|
): AsyncGenerator<T> {
|
|
47
58
|
const iterator = iterable[Symbol.asyncIterator]();
|
|
48
|
-
|
|
59
|
+
|
|
60
|
+
// declaration outside the loop for garbage collection reasons
|
|
61
|
+
let result: null | IteratorResult<T> | typeof disposablePromiseTimerResult;
|
|
62
|
+
|
|
63
|
+
const timer = disposablePromiseTimer(opts.gracePeriodMs);
|
|
49
64
|
try {
|
|
50
|
-
|
|
51
|
-
|
|
65
|
+
let count = opts.count;
|
|
66
|
+
|
|
67
|
+
let timerPromise = new Promise<typeof disposablePromiseTimerResult>(() => {
|
|
68
|
+
// never resolves
|
|
69
|
+
});
|
|
70
|
+
|
|
52
71
|
while (true) {
|
|
53
|
-
result = await Unpromise.race([iterator.next(),
|
|
54
|
-
if (result
|
|
72
|
+
result = await Unpromise.race([iterator.next(), timerPromise]);
|
|
73
|
+
if (result === disposablePromiseTimerResult) {
|
|
55
74
|
// cancelled
|
|
56
|
-
await iterator.return?.();
|
|
57
|
-
|
|
75
|
+
const res = await iterator.return?.();
|
|
76
|
+
return res?.value;
|
|
58
77
|
}
|
|
59
78
|
if (result.done) {
|
|
60
|
-
|
|
79
|
+
return result.value;
|
|
61
80
|
}
|
|
62
81
|
yield result.value;
|
|
63
82
|
if (--count === 0) {
|
|
64
|
-
timer.start()
|
|
83
|
+
timerPromise = timer.start();
|
|
84
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
85
|
+
timerPromise.then(() => opts.abortCtrl.abort());
|
|
65
86
|
}
|
|
66
87
|
// free up reference for garbage collection
|
|
67
88
|
result = null;
|
|
68
89
|
}
|
|
69
90
|
} finally {
|
|
70
|
-
timer.
|
|
91
|
+
timer[Symbol.dispose]();
|
|
71
92
|
}
|
|
72
93
|
}
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
import { createDeferred } from './createDeferred';
|
|
2
|
-
|
|
3
|
-
// ---------- utils
|
|
4
|
-
|
|
5
|
-
const cancelledStreamSymbol = Symbol();
|
|
6
1
|
/**
|
|
7
2
|
* One-off readable stream
|
|
8
3
|
*/
|
|
@@ -10,14 +5,12 @@ export function createReadableStream<TValue = unknown>() {
|
|
|
10
5
|
let controller: ReadableStreamDefaultController<TValue> =
|
|
11
6
|
null as unknown as ReadableStreamDefaultController<TValue>;
|
|
12
7
|
|
|
13
|
-
const deferred = createDeferred<typeof cancelledStreamSymbol>();
|
|
14
8
|
let cancelled = false;
|
|
15
9
|
const readable = new ReadableStream<TValue>({
|
|
16
10
|
start(c) {
|
|
17
11
|
controller = c;
|
|
18
12
|
},
|
|
19
13
|
cancel() {
|
|
20
|
-
deferred.resolve(cancelledStreamSymbol);
|
|
21
14
|
cancelled = true;
|
|
22
15
|
},
|
|
23
16
|
});
|
|
@@ -25,14 +18,8 @@ export function createReadableStream<TValue = unknown>() {
|
|
|
25
18
|
return {
|
|
26
19
|
readable,
|
|
27
20
|
controller,
|
|
28
|
-
cancelledPromise: deferred.promise,
|
|
29
21
|
cancelled() {
|
|
30
22
|
return cancelled;
|
|
31
23
|
},
|
|
32
24
|
} as const;
|
|
33
25
|
}
|
|
34
|
-
export function isCancelledStreamResult(
|
|
35
|
-
v: unknown,
|
|
36
|
-
): v is typeof cancelledStreamSymbol {
|
|
37
|
-
return v === cancelledStreamSymbol;
|
|
38
|
-
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// @ts-expect-error polyfill
|
|
2
|
+
Symbol.dispose ??= Symbol();
|
|
3
|
+
|
|
4
|
+
export const disposablePromiseTimerResult = Symbol();
|
|
5
|
+
export function disposablePromiseTimer(ms: number) {
|
|
6
|
+
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
7
|
+
|
|
8
|
+
return {
|
|
9
|
+
start() {
|
|
10
|
+
if (timer) {
|
|
11
|
+
throw new Error('Timer already started');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const promise = new Promise<typeof disposablePromiseTimerResult>(
|
|
15
|
+
(resolve) => {
|
|
16
|
+
timer = setTimeout(() => resolve(disposablePromiseTimerResult), ms);
|
|
17
|
+
},
|
|
18
|
+
);
|
|
19
|
+
return promise;
|
|
20
|
+
},
|
|
21
|
+
[Symbol.dispose]: () => {
|
|
22
|
+
if (timer) {
|
|
23
|
+
clearTimeout(timer);
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { Unpromise } from '../../../vendor/unpromise';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
disposablePromiseTimer,
|
|
4
|
+
disposablePromiseTimerResult,
|
|
5
|
+
} from './disposablePromiseTimer';
|
|
3
6
|
|
|
4
7
|
export const PING_SYM = Symbol('ping');
|
|
5
8
|
|
|
6
|
-
const PING_RESULT: IteratorResult<typeof PING_SYM> = {
|
|
7
|
-
value: PING_SYM,
|
|
8
|
-
done: false,
|
|
9
|
-
};
|
|
10
|
-
|
|
11
9
|
/**
|
|
12
10
|
* Derives a new {@link AsyncGenerator} based of {@link iterable}, that yields {@link PING_SYM}
|
|
13
11
|
* whenever no value has been yielded for {@link pingIntervalMs}.
|
|
@@ -16,24 +14,38 @@ export async function* withPing<TValue>(
|
|
|
16
14
|
iterable: AsyncIterable<TValue>,
|
|
17
15
|
pingIntervalMs: number,
|
|
18
16
|
): AsyncGenerator<TValue | typeof PING_SYM> {
|
|
19
|
-
const timer = createPromiseTimer(pingIntervalMs);
|
|
20
17
|
const iterator = iterable[Symbol.asyncIterator]();
|
|
21
18
|
// declaration outside the loop for garbage collection reasons
|
|
22
|
-
let result:
|
|
19
|
+
let result:
|
|
20
|
+
| null
|
|
21
|
+
| IteratorResult<TValue>
|
|
22
|
+
| typeof disposablePromiseTimerResult;
|
|
23
|
+
|
|
24
|
+
let nextPromise = iterator.next();
|
|
23
25
|
while (true) {
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
+
const pingPromise = disposablePromiseTimer(pingIntervalMs);
|
|
27
|
+
|
|
26
28
|
try {
|
|
27
|
-
result = await Unpromise.race([nextPromise, pingPromise]);
|
|
29
|
+
result = await Unpromise.race([nextPromise, pingPromise.start()]);
|
|
30
|
+
|
|
31
|
+
if (result === disposablePromiseTimerResult) {
|
|
32
|
+
// cancelled
|
|
33
|
+
|
|
34
|
+
yield PING_SYM;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (result.done) {
|
|
39
|
+
return result.value;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
nextPromise = iterator.next();
|
|
43
|
+
yield result.value;
|
|
44
|
+
|
|
45
|
+
// free up reference for garbage collection
|
|
46
|
+
result = null;
|
|
28
47
|
} finally {
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
if (result.done) {
|
|
32
|
-
return result.value;
|
|
48
|
+
pingPromise[Symbol.dispose]();
|
|
33
49
|
}
|
|
34
|
-
yield result.value;
|
|
35
|
-
timer.reset();
|
|
36
|
-
// free up reference for garbage collection
|
|
37
|
-
result = null;
|
|
38
50
|
}
|
|
39
51
|
}
|