@trpc/server 11.0.0-rc.630 → 11.0.0-rc.632
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 +85 -0
- package/dist/adapters/node-http/writeResponse.mjs +82 -0
- package/dist/adapters/standalone.js +1 -0
- package/dist/adapters/standalone.mjs +1 -0
- package/dist/adapters/ws.js +2 -1
- package/dist/adapters/ws.mjs +2 -1
- package/dist/bundle-analysis.json +146 -119
- package/dist/http.js +1 -2
- package/dist/http.mjs +1 -1
- package/dist/index.js +2 -1
- package/dist/index.mjs +2 -1
- 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/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.map +1 -1
- package/dist/unstable-core-do-not-import/stream/sse.js +25 -21
- package/dist/unstable-core-do-not-import/stream/sse.mjs +26 -22
- 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.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 +96 -0
- package/src/unstable-core-do-not-import/http/isAbortError.ts +7 -0
- package/src/unstable-core-do-not-import/stream/jsonl.ts +85 -154
- package/src/unstable-core-do-not-import/stream/sse.ts +24 -25
- 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,12 +1,8 @@
|
|
|
1
|
-
import { Unpromise } from '../../vendor/unpromise';
|
|
2
|
-
import { getTRPCErrorFromUnknown } from '../error/TRPCError';
|
|
3
1
|
import { isAsyncIterable, isFunction, isObject, run } from '../utils';
|
|
4
2
|
import type { Deferred } from './utils/createDeferred';
|
|
5
3
|
import { createDeferred } from './utils/createDeferred';
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
isCancelledStreamResult,
|
|
9
|
-
} from './utils/createReadableStream';
|
|
4
|
+
import { createReadableStream } from './utils/createReadableStream';
|
|
5
|
+
import { withRefCount } from './utils/withRefCount';
|
|
10
6
|
|
|
11
7
|
/**
|
|
12
8
|
* A subset of the standard ReadableStream properties needed by tRPC internally.
|
|
@@ -58,31 +54,31 @@ type ChunkDefinition = [
|
|
|
58
54
|
type: ChunkValueType,
|
|
59
55
|
chunkId: ChunkIndex,
|
|
60
56
|
];
|
|
61
|
-
type
|
|
57
|
+
type EncodedValue = [
|
|
62
58
|
// data
|
|
63
59
|
[unknown] | [],
|
|
64
60
|
// chunk descriptions
|
|
65
61
|
...ChunkDefinition[],
|
|
66
62
|
];
|
|
67
63
|
|
|
68
|
-
type Head = Record<string,
|
|
64
|
+
type Head = Record<string, EncodedValue>;
|
|
69
65
|
type PromiseChunk =
|
|
70
66
|
| [
|
|
71
67
|
chunkIndex: ChunkIndex,
|
|
72
68
|
status: PROMISE_STATUS_FULFILLED,
|
|
73
|
-
value:
|
|
69
|
+
value: EncodedValue,
|
|
74
70
|
]
|
|
75
71
|
| [chunkIndex: ChunkIndex, status: PROMISE_STATUS_REJECTED, error: unknown];
|
|
76
72
|
type IterableChunk =
|
|
77
73
|
| [
|
|
78
74
|
chunkIndex: ChunkIndex,
|
|
79
75
|
status: ASYNC_ITERABLE_STATUS_RETURN,
|
|
80
|
-
value:
|
|
76
|
+
value: EncodedValue,
|
|
81
77
|
]
|
|
82
78
|
| [
|
|
83
79
|
chunkIndex: ChunkIndex,
|
|
84
80
|
status: ASYNC_ITERABLE_STATUS_VALUE,
|
|
85
|
-
value:
|
|
81
|
+
value: EncodedValue,
|
|
86
82
|
]
|
|
87
83
|
| [
|
|
88
84
|
chunkIndex: ChunkIndex,
|
|
@@ -127,42 +123,38 @@ function createBatchStreamProducer(opts: ProducerOptions) {
|
|
|
127
123
|
const placeholder = 0 as PlaceholderValue;
|
|
128
124
|
|
|
129
125
|
const stream = createReadableStream<ChunkData>();
|
|
130
|
-
const pending = new Set<ChunkIndex>()
|
|
131
|
-
|
|
132
|
-
function maybeClose() {
|
|
133
|
-
if (pending.size === 0 && !stream.cancelled()) {
|
|
126
|
+
const pending = withRefCount(new Set<ChunkIndex>(), () => {
|
|
127
|
+
if (!stream.cancelled()) {
|
|
134
128
|
stream.controller.close();
|
|
135
129
|
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const maybeEnqueue = (chunk: ChunkData) => {
|
|
133
|
+
if (!stream.cancelled()) {
|
|
134
|
+
stream.controller.enqueue(chunk);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
function encodePromise(promise: Promise<unknown>, path: (string | number)[]) {
|
|
142
139
|
const error = checkMaxDepth(path);
|
|
143
140
|
if (error) {
|
|
144
|
-
promise
|
|
145
|
-
|
|
141
|
+
// Catch any errors from the original promise to ensure they're reported
|
|
142
|
+
promise.catch((cause) => {
|
|
143
|
+
opts.onError?.({ error: cause, path });
|
|
146
144
|
});
|
|
145
|
+
// Replace the promise with a rejected one containing the max depth error
|
|
147
146
|
promise = Promise.reject(error);
|
|
148
147
|
}
|
|
149
148
|
const idx = counter++ as ChunkIndex;
|
|
150
149
|
pending.add(idx);
|
|
151
150
|
|
|
152
|
-
|
|
151
|
+
promise
|
|
153
152
|
.then((it) => {
|
|
154
|
-
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
stream.controller.enqueue([
|
|
158
|
-
idx,
|
|
159
|
-
PROMISE_STATUS_FULFILLED,
|
|
160
|
-
dehydrate(it, path),
|
|
161
|
-
]);
|
|
153
|
+
maybeEnqueue([idx, PROMISE_STATUS_FULFILLED, encode(it, path)]);
|
|
162
154
|
})
|
|
163
155
|
.catch((cause) => {
|
|
164
156
|
opts.onError?.({ error: cause, path });
|
|
165
|
-
|
|
157
|
+
maybeEnqueue([
|
|
166
158
|
idx,
|
|
167
159
|
PROMISE_STATUS_REJECTED,
|
|
168
160
|
opts.formatError?.({ error: cause, path }),
|
|
@@ -170,78 +162,55 @@ function createBatchStreamProducer(opts: ProducerOptions) {
|
|
|
170
162
|
})
|
|
171
163
|
.finally(() => {
|
|
172
164
|
pending.delete(idx);
|
|
173
|
-
maybeClose();
|
|
174
165
|
});
|
|
175
166
|
return idx;
|
|
176
167
|
}
|
|
177
|
-
function
|
|
168
|
+
function encodeAsyncIterable(
|
|
178
169
|
iterable: AsyncIterable<unknown>,
|
|
179
170
|
path: (string | number)[],
|
|
180
171
|
) {
|
|
181
|
-
const error = checkMaxDepth(path);
|
|
182
|
-
if (error) {
|
|
183
|
-
iterable = {
|
|
184
|
-
[Symbol.asyncIterator]() {
|
|
185
|
-
throw error;
|
|
186
|
-
},
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
172
|
const idx = counter++ as ChunkIndex;
|
|
190
173
|
pending.add(idx);
|
|
191
174
|
run(async () => {
|
|
175
|
+
const error = checkMaxDepth(path);
|
|
176
|
+
if (error) {
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
192
179
|
const iterator = iterable[Symbol.asyncIterator]();
|
|
193
180
|
|
|
194
181
|
while (true) {
|
|
195
|
-
|
|
196
|
-
iterator.
|
|
197
|
-
|
|
198
|
-
]);
|
|
199
|
-
|
|
200
|
-
if (next instanceof Error) {
|
|
201
|
-
opts.onError?.({ error: next, path });
|
|
202
|
-
|
|
203
|
-
stream.controller.enqueue([
|
|
204
|
-
idx,
|
|
205
|
-
ASYNC_ITERABLE_STATUS_ERROR,
|
|
206
|
-
opts.formatError?.({ error: next, path }),
|
|
207
|
-
]);
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
if (isCancelledStreamResult(next)) {
|
|
211
|
-
await iterator.return?.();
|
|
212
|
-
break;
|
|
182
|
+
if (stream.cancelled()) {
|
|
183
|
+
const res = await iterator.return?.();
|
|
184
|
+
return res?.value;
|
|
213
185
|
}
|
|
186
|
+
const next = await iterator.next();
|
|
187
|
+
|
|
214
188
|
if (next.done) {
|
|
215
|
-
|
|
189
|
+
maybeEnqueue([
|
|
216
190
|
idx,
|
|
217
191
|
ASYNC_ITERABLE_STATUS_RETURN,
|
|
218
|
-
|
|
192
|
+
encode(next.value, path),
|
|
219
193
|
]);
|
|
220
194
|
break;
|
|
221
195
|
}
|
|
222
|
-
|
|
196
|
+
maybeEnqueue([
|
|
223
197
|
idx,
|
|
224
198
|
ASYNC_ITERABLE_STATUS_VALUE,
|
|
225
|
-
|
|
199
|
+
encode(next.value, path),
|
|
226
200
|
]);
|
|
227
201
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
cause,
|
|
240
|
-
},
|
|
241
|
-
),
|
|
242
|
-
path,
|
|
202
|
+
})
|
|
203
|
+
.catch((cause) => {
|
|
204
|
+
opts.onError?.({ error: cause, path });
|
|
205
|
+
maybeEnqueue([
|
|
206
|
+
idx,
|
|
207
|
+
ASYNC_ITERABLE_STATUS_ERROR,
|
|
208
|
+
opts.formatError?.({ error: cause, path }),
|
|
209
|
+
]);
|
|
210
|
+
})
|
|
211
|
+
.finally(() => {
|
|
212
|
+
pending.delete(idx);
|
|
243
213
|
});
|
|
244
|
-
});
|
|
245
214
|
return idx;
|
|
246
215
|
}
|
|
247
216
|
function checkMaxDepth(path: (string | number)[]) {
|
|
@@ -250,12 +219,12 @@ function createBatchStreamProducer(opts: ProducerOptions) {
|
|
|
250
219
|
}
|
|
251
220
|
return null;
|
|
252
221
|
}
|
|
253
|
-
function
|
|
222
|
+
function encodeAsync(
|
|
254
223
|
value: unknown,
|
|
255
224
|
path: (string | number)[],
|
|
256
225
|
): null | [type: ChunkValueType, chunkId: ChunkIndex] {
|
|
257
226
|
if (isPromise(value)) {
|
|
258
|
-
return [CHUNK_VALUE_TYPE_PROMISE,
|
|
227
|
+
return [CHUNK_VALUE_TYPE_PROMISE, encodePromise(value, path)];
|
|
259
228
|
}
|
|
260
229
|
if (isAsyncIterable(value)) {
|
|
261
230
|
if (opts.maxDepth && path.length >= opts.maxDepth) {
|
|
@@ -263,29 +232,26 @@ function createBatchStreamProducer(opts: ProducerOptions) {
|
|
|
263
232
|
}
|
|
264
233
|
return [
|
|
265
234
|
CHUNK_VALUE_TYPE_ASYNC_ITERABLE,
|
|
266
|
-
|
|
235
|
+
encodeAsyncIterable(value, path),
|
|
267
236
|
];
|
|
268
237
|
}
|
|
269
238
|
return null;
|
|
270
239
|
}
|
|
271
|
-
function
|
|
272
|
-
value: unknown,
|
|
273
|
-
path: (string | number)[],
|
|
274
|
-
): DehydratedValue {
|
|
240
|
+
function encode(value: unknown, path: (string | number)[]): EncodedValue {
|
|
275
241
|
if (value === undefined) {
|
|
276
242
|
return [[]];
|
|
277
243
|
}
|
|
278
244
|
if (!isObject(value)) {
|
|
279
245
|
return [[value]];
|
|
280
246
|
}
|
|
281
|
-
const reg =
|
|
247
|
+
const reg = encodeAsync(value, path);
|
|
282
248
|
if (reg) {
|
|
283
249
|
return [[placeholder], [null, ...reg]];
|
|
284
250
|
}
|
|
285
251
|
const newObj = {} as Record<string, unknown>;
|
|
286
252
|
const asyncValues: ChunkDefinition[] = [];
|
|
287
253
|
for (const [key, item] of Object.entries(value)) {
|
|
288
|
-
const transformed =
|
|
254
|
+
const transformed = encodeAsync(item, [...path, key]);
|
|
289
255
|
if (!transformed) {
|
|
290
256
|
newObj[key] = item;
|
|
291
257
|
continue;
|
|
@@ -298,8 +264,9 @@ function createBatchStreamProducer(opts: ProducerOptions) {
|
|
|
298
264
|
|
|
299
265
|
const newHead: Head = {};
|
|
300
266
|
for (const [key, item] of Object.entries(data)) {
|
|
301
|
-
newHead[key] =
|
|
267
|
+
newHead[key] = encode(item, [key]);
|
|
302
268
|
}
|
|
269
|
+
pending.activate();
|
|
303
270
|
|
|
304
271
|
return [newHead, stream.readable] as const;
|
|
305
272
|
}
|
|
@@ -440,7 +407,7 @@ export async function jsonlStreamConsumer<THead>(opts: {
|
|
|
440
407
|
/**
|
|
441
408
|
* This `AbortController` will be triggered when there are no more listeners to the stream.
|
|
442
409
|
*/
|
|
443
|
-
abortController: AbortController
|
|
410
|
+
abortController: AbortController;
|
|
444
411
|
}) {
|
|
445
412
|
const { deserialize = (v) => v } = opts;
|
|
446
413
|
|
|
@@ -458,46 +425,26 @@ export async function jsonlStreamConsumer<THead>(opts: {
|
|
|
458
425
|
|
|
459
426
|
type ControllerChunk = ChunkData | StreamInterruptedError;
|
|
460
427
|
type ChunkController = ReadableStreamDefaultController<ControllerChunk>;
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
const chunkDeferred = new Map<ChunkIndex, Deferred<ControllerWrapper>>();
|
|
466
|
-
const controllers = new Map<ChunkIndex, ControllerWrapper>();
|
|
467
|
-
|
|
468
|
-
const maybeAbort = () => {
|
|
469
|
-
if (
|
|
470
|
-
chunkDeferred.size === 0 &&
|
|
471
|
-
Array.from(controllers.values()).every((it) => it.returned)
|
|
472
|
-
) {
|
|
473
|
-
// nothing is listening to the stream anymore
|
|
428
|
+
|
|
429
|
+
const controllers = withRefCount(
|
|
430
|
+
new Map<ChunkIndex, ChunkController>(),
|
|
431
|
+
() => {
|
|
474
432
|
opts.abortController?.abort();
|
|
475
|
-
}
|
|
476
|
-
|
|
433
|
+
},
|
|
434
|
+
);
|
|
477
435
|
|
|
478
|
-
function
|
|
436
|
+
function decodeChunkDefinition(value: ChunkDefinition) {
|
|
479
437
|
const [_path, type, chunkId] = value;
|
|
480
438
|
|
|
481
|
-
const
|
|
482
|
-
|
|
483
|
-
const wrapper: ControllerWrapper = {
|
|
484
|
-
controller,
|
|
485
|
-
returned: false,
|
|
486
|
-
};
|
|
487
|
-
controllers.set(chunkId, wrapper);
|
|
439
|
+
const stream = createReadableStream<ChunkData>();
|
|
488
440
|
|
|
489
|
-
|
|
490
|
-
const deferred = chunkDeferred.get(chunkId);
|
|
491
|
-
if (deferred) {
|
|
492
|
-
deferred.resolve(wrapper);
|
|
493
|
-
chunkDeferred.delete(chunkId);
|
|
494
|
-
}
|
|
441
|
+
controllers.set(chunkId, stream.controller);
|
|
495
442
|
|
|
496
443
|
switch (type) {
|
|
497
444
|
case CHUNK_VALUE_TYPE_PROMISE: {
|
|
498
445
|
return new Promise((resolve, reject) => {
|
|
499
446
|
// listen for next value in the stream
|
|
500
|
-
const reader = readable.getReader();
|
|
447
|
+
const reader = stream.readable.getReader();
|
|
501
448
|
reader
|
|
502
449
|
.read()
|
|
503
450
|
.then((it) => {
|
|
@@ -513,7 +460,7 @@ export async function jsonlStreamConsumer<THead>(opts: {
|
|
|
513
460
|
const [_chunkId, status, data] = value as PromiseChunk;
|
|
514
461
|
switch (status) {
|
|
515
462
|
case PROMISE_STATUS_FULFILLED:
|
|
516
|
-
resolve(
|
|
463
|
+
resolve(decode(data));
|
|
517
464
|
|
|
518
465
|
break;
|
|
519
466
|
case PROMISE_STATUS_REJECTED:
|
|
@@ -525,17 +472,14 @@ export async function jsonlStreamConsumer<THead>(opts: {
|
|
|
525
472
|
})
|
|
526
473
|
.catch(reject)
|
|
527
474
|
.finally(() => {
|
|
528
|
-
// reader.releaseLock();
|
|
529
475
|
controllers.delete(chunkId);
|
|
530
|
-
|
|
531
|
-
maybeAbort();
|
|
532
476
|
});
|
|
533
477
|
});
|
|
534
478
|
}
|
|
535
479
|
case CHUNK_VALUE_TYPE_ASYNC_ITERABLE: {
|
|
536
480
|
return {
|
|
537
481
|
[Symbol.asyncIterator]: () => {
|
|
538
|
-
const reader = readable.getReader();
|
|
482
|
+
const reader = stream.readable.getReader();
|
|
539
483
|
const iterator: AsyncIterator<unknown> = {
|
|
540
484
|
next: async () => {
|
|
541
485
|
const { done, value } = await reader.read();
|
|
@@ -544,7 +488,6 @@ export async function jsonlStreamConsumer<THead>(opts: {
|
|
|
544
488
|
}
|
|
545
489
|
if (done) {
|
|
546
490
|
controllers.delete(chunkId);
|
|
547
|
-
maybeAbort();
|
|
548
491
|
return {
|
|
549
492
|
done: true,
|
|
550
493
|
value: undefined,
|
|
@@ -557,18 +500,16 @@ export async function jsonlStreamConsumer<THead>(opts: {
|
|
|
557
500
|
case ASYNC_ITERABLE_STATUS_VALUE:
|
|
558
501
|
return {
|
|
559
502
|
done: false,
|
|
560
|
-
value:
|
|
503
|
+
value: decode(data),
|
|
561
504
|
};
|
|
562
505
|
case ASYNC_ITERABLE_STATUS_RETURN:
|
|
563
506
|
controllers.delete(chunkId);
|
|
564
|
-
maybeAbort();
|
|
565
507
|
return {
|
|
566
508
|
done: true,
|
|
567
|
-
value:
|
|
509
|
+
value: decode(data),
|
|
568
510
|
};
|
|
569
511
|
case ASYNC_ITERABLE_STATUS_ERROR:
|
|
570
512
|
controllers.delete(chunkId);
|
|
571
|
-
maybeAbort();
|
|
572
513
|
throw (
|
|
573
514
|
opts.formatError?.({ error: data }) ??
|
|
574
515
|
new AsyncError(data)
|
|
@@ -576,8 +517,7 @@ export async function jsonlStreamConsumer<THead>(opts: {
|
|
|
576
517
|
}
|
|
577
518
|
},
|
|
578
519
|
return: async () => {
|
|
579
|
-
|
|
580
|
-
maybeAbort();
|
|
520
|
+
controllers.delete(chunkId);
|
|
581
521
|
return {
|
|
582
522
|
done: true,
|
|
583
523
|
value: undefined,
|
|
@@ -591,18 +531,18 @@ export async function jsonlStreamConsumer<THead>(opts: {
|
|
|
591
531
|
}
|
|
592
532
|
}
|
|
593
533
|
|
|
594
|
-
function
|
|
534
|
+
function decode(value: EncodedValue): unknown {
|
|
595
535
|
const [[data], ...asyncProps] = value;
|
|
596
536
|
|
|
597
537
|
for (const value of asyncProps) {
|
|
598
538
|
const [key] = value;
|
|
599
|
-
const
|
|
539
|
+
const decoded = decodeChunkDefinition(value);
|
|
600
540
|
|
|
601
541
|
if (key === null) {
|
|
602
|
-
return
|
|
542
|
+
return decoded;
|
|
603
543
|
}
|
|
604
544
|
|
|
605
|
-
(data as any)[key] =
|
|
545
|
+
(data as any)[key] = decoded;
|
|
606
546
|
}
|
|
607
547
|
return data;
|
|
608
548
|
}
|
|
@@ -611,11 +551,7 @@ export async function jsonlStreamConsumer<THead>(opts: {
|
|
|
611
551
|
const error = new StreamInterruptedError(reason);
|
|
612
552
|
|
|
613
553
|
headDeferred?.reject(error);
|
|
614
|
-
for (const
|
|
615
|
-
deferred.reject(error);
|
|
616
|
-
}
|
|
617
|
-
chunkDeferred.clear();
|
|
618
|
-
for (const { controller } of controllers.values()) {
|
|
554
|
+
for (const controller of controllers.values()) {
|
|
619
555
|
controller.enqueue(error);
|
|
620
556
|
controller.close();
|
|
621
557
|
}
|
|
@@ -629,26 +565,21 @@ export async function jsonlStreamConsumer<THead>(opts: {
|
|
|
629
565
|
const head = chunkOrHead as Record<number | string, unknown>;
|
|
630
566
|
|
|
631
567
|
for (const [key, value] of Object.entries(chunkOrHead)) {
|
|
632
|
-
const parsed =
|
|
568
|
+
const parsed = decode(value as any);
|
|
633
569
|
head[key] = parsed;
|
|
634
570
|
}
|
|
635
571
|
headDeferred.resolve(head as THead);
|
|
636
572
|
headDeferred = null;
|
|
573
|
+
|
|
574
|
+
controllers.activate();
|
|
637
575
|
return;
|
|
638
576
|
}
|
|
639
577
|
const chunk = chunkOrHead as ChunkData;
|
|
640
578
|
const [idx] = chunk;
|
|
641
|
-
let wrapper = controllers.get(idx);
|
|
642
|
-
if (!wrapper) {
|
|
643
|
-
let deferred = chunkDeferred.get(idx);
|
|
644
|
-
if (!deferred) {
|
|
645
|
-
deferred = createDeferred();
|
|
646
|
-
chunkDeferred.set(idx, deferred);
|
|
647
|
-
}
|
|
648
579
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
580
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
581
|
+
const controller = controllers.get(idx)!;
|
|
582
|
+
controller.enqueue(chunk);
|
|
652
583
|
},
|
|
653
584
|
close: closeOrAbort,
|
|
654
585
|
abort: closeOrAbort,
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { getTRPCErrorFromUnknown } from '../error/TRPCError';
|
|
2
|
+
import { isAbortError } from '../http/isAbortError';
|
|
2
3
|
import type { MaybePromise } from '../types';
|
|
3
4
|
import { identity, run } from '../utils';
|
|
4
5
|
import type { EventSourceLike } from './sse.types';
|
|
5
6
|
import type { inferTrackedOutput } from './tracked';
|
|
6
7
|
import { isTrackedEnvelope } from './tracked';
|
|
7
|
-
import { takeWithGrace,
|
|
8
|
+
import { takeWithGrace, withMaxDuration } from './utils/asyncIterable';
|
|
8
9
|
import { createReadableStream } from './utils/createReadableStream';
|
|
9
|
-
import type { PromiseTimer } from './utils/promiseTimer';
|
|
10
|
-
import { createPromiseTimer } from './utils/promiseTimer';
|
|
11
10
|
import { PING_SYM, withPing } from './utils/withPing';
|
|
12
11
|
|
|
13
12
|
type Serialize = (value: any) => any;
|
|
@@ -80,27 +79,23 @@ export function sseStreamProducer<TValue = unknown>(
|
|
|
80
79
|
|
|
81
80
|
let iterable: AsyncIterable<TValue | typeof PING_SYM> = opts.data;
|
|
82
81
|
|
|
83
|
-
iterable = withCancel(iterable, stream.cancelledPromise);
|
|
84
|
-
|
|
85
82
|
if (opts.emitAndEndImmediately) {
|
|
86
83
|
iterable = takeWithGrace(iterable, {
|
|
87
84
|
count: 1,
|
|
88
85
|
gracePeriodMs: 1,
|
|
89
|
-
|
|
86
|
+
abortCtrl: opts.abortCtrl,
|
|
90
87
|
});
|
|
91
88
|
}
|
|
92
89
|
|
|
93
|
-
let maxDurationTimer: PromiseTimer | null = null;
|
|
94
90
|
if (
|
|
95
|
-
opts.maxDurationMs
|
|
91
|
+
opts.maxDurationMs &&
|
|
96
92
|
opts.maxDurationMs > 0 &&
|
|
97
93
|
opts.maxDurationMs !== Infinity
|
|
98
94
|
) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
);
|
|
95
|
+
iterable = withMaxDuration(iterable, {
|
|
96
|
+
maxDurationMs: opts.maxDurationMs,
|
|
97
|
+
abortCtrl: opts.abortCtrl,
|
|
98
|
+
});
|
|
104
99
|
}
|
|
105
100
|
|
|
106
101
|
if (ping.enabled && ping.intervalMs !== Infinity && ping.intervalMs > 0) {
|
|
@@ -133,20 +128,24 @@ export function sseStreamProducer<TValue = unknown>(
|
|
|
133
128
|
chunk = null;
|
|
134
129
|
}
|
|
135
130
|
} 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
|
-
});
|
|
131
|
+
if (isAbortError(err)) {
|
|
132
|
+
// ignore abort errors, send any other errors
|
|
133
|
+
return;
|
|
146
134
|
}
|
|
135
|
+
// `err` must be caused by `opts.data`, `JSON.stringify` or `serialize`.
|
|
136
|
+
// So, a user error in any case.
|
|
137
|
+
const error = getTRPCErrorFromUnknown(err);
|
|
138
|
+
const data = opts.formatError?.({ error }) ?? null;
|
|
139
|
+
stream.controller.enqueue({
|
|
140
|
+
event: SERIALIZED_ERROR_EVENT,
|
|
141
|
+
data: JSON.stringify(serialize(data)),
|
|
142
|
+
});
|
|
147
143
|
} finally {
|
|
148
|
-
|
|
149
|
-
|
|
144
|
+
try {
|
|
145
|
+
stream.controller.close();
|
|
146
|
+
} catch {
|
|
147
|
+
// ignore
|
|
148
|
+
}
|
|
150
149
|
}
|
|
151
150
|
}).catch((err) => {
|
|
152
151
|
// should not be reached; just in case...
|
|
@@ -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
|
}
|