@trpc/server 11.0.0-alpha-tmp-issues-6374.694 → 11.0.0-alpha-tmp-issues-6374.697

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.
@@ -0,0 +1,239 @@
1
+ import { createDeferred } from './createDeferred.mjs';
2
+ import { makeAsyncResource } from './disposable.mjs';
3
+
4
+ function _ts_add_disposable_resource(env, value, async) {
5
+ if (value !== null && value !== void 0) {
6
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
7
+ var dispose, inner;
8
+ {
9
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
10
+ dispose = value[Symbol.asyncDispose];
11
+ }
12
+ if (dispose === void 0) {
13
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
14
+ dispose = value[Symbol.dispose];
15
+ inner = dispose;
16
+ }
17
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
18
+ if (inner) dispose = function() {
19
+ try {
20
+ inner.call(this);
21
+ } catch (e) {
22
+ return Promise.reject(e);
23
+ }
24
+ };
25
+ env.stack.push({
26
+ value: value,
27
+ dispose: dispose,
28
+ async: async
29
+ });
30
+ } else {
31
+ env.stack.push({
32
+ async: true
33
+ });
34
+ }
35
+ return value;
36
+ }
37
+ function _ts_dispose_resources(env) {
38
+ var _SuppressedError = typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) {
39
+ var e = new Error(message);
40
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
41
+ };
42
+ return (_ts_dispose_resources = function _ts_dispose_resources(env) {
43
+ function fail(e) {
44
+ env.error = env.hasError ? new _SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
45
+ env.hasError = true;
46
+ }
47
+ var r, s = 0;
48
+ function next() {
49
+ while(r = env.stack.pop()){
50
+ try {
51
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
52
+ if (r.dispose) {
53
+ var result = r.dispose.call(r.value);
54
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) {
55
+ fail(e);
56
+ return next();
57
+ });
58
+ } else s |= 1;
59
+ } catch (e) {
60
+ fail(e);
61
+ }
62
+ }
63
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
64
+ if (env.hasError) throw env.error;
65
+ }
66
+ return next();
67
+ })(env);
68
+ }
69
+ function createManagedIterator(iterable, onResult) {
70
+ const iterator = iterable[Symbol.asyncIterator]();
71
+ let state = 'idle';
72
+ function cleanup() {
73
+ state = 'done';
74
+ onResult = ()=>{
75
+ // noop
76
+ };
77
+ }
78
+ function pull() {
79
+ if (state !== 'idle') {
80
+ return;
81
+ }
82
+ state = 'pending';
83
+ const next = iterator.next();
84
+ next.then((result)=>{
85
+ if (result.done) {
86
+ state = 'done';
87
+ onResult({
88
+ status: 'return',
89
+ value: result.value
90
+ });
91
+ cleanup();
92
+ return;
93
+ }
94
+ state = 'idle';
95
+ onResult({
96
+ status: 'yield',
97
+ value: result.value
98
+ });
99
+ }).catch((cause)=>{
100
+ onResult({
101
+ status: 'error',
102
+ error: cause
103
+ });
104
+ cleanup();
105
+ });
106
+ }
107
+ return {
108
+ pull,
109
+ destroy: async ()=>{
110
+ cleanup();
111
+ await iterator.return?.();
112
+ }
113
+ };
114
+ }
115
+ /**
116
+ * Creates a new async iterable that merges multiple async iterables into a single stream.
117
+ * Values from the input iterables are yielded in the order they resolve, similar to Promise.race().
118
+ *
119
+ * New iterables can be added dynamically using the returned {@link MergedAsyncIterables.add} method, even after iteration has started.
120
+ *
121
+ * If any of the input iterables throws an error, that error will be propagated through the merged stream.
122
+ * Other iterables will not continue to be processed.
123
+ *
124
+ * @template TYield The type of values yielded by the input iterables
125
+ */ function mergeAsyncIterables() {
126
+ let state = 'idle';
127
+ let flushSignal = createDeferred();
128
+ /**
129
+ * used while {@link state} is `idle`
130
+ */ const iterables = [];
131
+ /**
132
+ * used while {@link state} is `pending`
133
+ */ const iterators = new Set();
134
+ const buffer = [];
135
+ function initIterable(iterable) {
136
+ if (state !== 'pending') {
137
+ // shouldn't happen
138
+ return;
139
+ }
140
+ const iterator = createManagedIterator(iterable, (result)=>{
141
+ if (state !== 'pending') {
142
+ // shouldn't happen
143
+ return;
144
+ }
145
+ switch(result.status){
146
+ case 'yield':
147
+ buffer.push([
148
+ iterator,
149
+ result
150
+ ]);
151
+ break;
152
+ case 'return':
153
+ iterators.delete(iterator);
154
+ break;
155
+ case 'error':
156
+ buffer.push([
157
+ iterator,
158
+ result
159
+ ]);
160
+ iterators.delete(iterator);
161
+ break;
162
+ }
163
+ flushSignal.resolve();
164
+ });
165
+ iterators.add(iterator);
166
+ iterator.pull();
167
+ }
168
+ return {
169
+ add (iterable) {
170
+ switch(state){
171
+ case 'idle':
172
+ iterables.push(iterable);
173
+ break;
174
+ case 'pending':
175
+ initIterable(iterable);
176
+ break;
177
+ }
178
+ },
179
+ async *[Symbol.asyncIterator] () {
180
+ const env = {
181
+ stack: [],
182
+ error: void 0,
183
+ hasError: false
184
+ };
185
+ try {
186
+ if (state !== 'idle') {
187
+ throw new Error('Cannot iterate twice');
188
+ }
189
+ state = 'pending';
190
+ const _finally = _ts_add_disposable_resource(env, makeAsyncResource({}, async ()=>{
191
+ state = 'done';
192
+ const errors = [];
193
+ await Promise.all(Array.from(iterators.values()).map(async (it)=>{
194
+ try {
195
+ await it.destroy();
196
+ } catch (cause) {
197
+ errors.push(cause);
198
+ }
199
+ }));
200
+ buffer.length = 0;
201
+ iterators.clear();
202
+ flushSignal.resolve();
203
+ if (errors.length > 0) {
204
+ throw new AggregateError(errors);
205
+ }
206
+ }), true);
207
+ ;
208
+ while(iterables.length > 0){
209
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
210
+ initIterable(iterables.shift());
211
+ }
212
+ while(iterators.size > 0){
213
+ await flushSignal.promise;
214
+ while(buffer.length > 0){
215
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
216
+ const [iterator, result] = buffer.shift();
217
+ switch(result.status){
218
+ case 'yield':
219
+ yield result.value;
220
+ iterator.pull();
221
+ break;
222
+ case 'error':
223
+ throw result.error;
224
+ }
225
+ }
226
+ flushSignal = createDeferred();
227
+ }
228
+ } catch (e) {
229
+ env.error = e;
230
+ env.hasError = true;
231
+ } finally{
232
+ const result = _ts_dispose_resources(env);
233
+ if (result) await result;
234
+ }
235
+ }
236
+ };
237
+ }
238
+
239
+ export { mergeAsyncIterables };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trpc/server",
3
- "version": "11.0.0-alpha-tmp-issues-6374.694+ce45f1c48",
3
+ "version": "11.0.0-alpha-tmp-issues-6374.697+41465ba29",
4
4
  "description": "The tRPC server library",
5
5
  "author": "KATT",
6
6
  "license": "MIT",
@@ -152,5 +152,5 @@
152
152
  "peerDependencies": {
153
153
  "typescript": ">=5.7.2"
154
154
  },
155
- "gitHead": "ce45f1c48b24261580aabe1793a78876d77e5168"
155
+ "gitHead": "41465ba29ad50fa0a28146f8d8fb85cde796c562"
156
156
  }
@@ -1,9 +1,9 @@
1
- import { Unpromise } from '../../vendor/unpromise';
2
1
  import { isAsyncIterable, isFunction, isObject, run } from '../utils';
3
2
  import { iteratorResource } from './utils/asyncIterable';
4
3
  import type { Deferred } from './utils/createDeferred';
5
4
  import { createDeferred } from './utils/createDeferred';
6
- import { makeAsyncResource, makeResource } from './utils/disposable';
5
+ import { makeResource } from './utils/disposable';
6
+ import { mergeAsyncIterables } from './utils/mergeAsyncIterables';
7
7
  import { readableStreamFrom } from './utils/readableStreamFrom';
8
8
 
9
9
  /**
@@ -128,31 +128,14 @@ async function* createBatchStreamProducer(
128
128
  let counter = 0 as ChunkIndex;
129
129
  const placeholder = 0 as PlaceholderValue;
130
130
 
131
- await using queue = makeAsyncResource(
132
- new Set<{
133
- iterator: AsyncIterator<ChunkData, ChunkData>;
134
- nextPromise: Promise<IteratorResult<ChunkData, ChunkData>>;
135
- }>(),
136
- async () => {
137
- await Promise.all(Array.from(queue).map((it) => it.iterator.return?.()));
138
- },
139
- );
131
+ const mergedIterables = mergeAsyncIterables<ChunkData>();
140
132
  function registerAsync(
141
- callback: (idx: ChunkIndex) => AsyncIterable<ChunkData, ChunkData>,
133
+ callback: (idx: ChunkIndex) => AsyncIterable<ChunkData, void>,
142
134
  ) {
143
135
  const idx = counter++ as ChunkIndex;
144
136
 
145
- const iterator = callback(idx)[Symbol.asyncIterator]();
146
-
147
- const nextPromise = iterator.next();
148
-
149
- nextPromise.catch(() => {
150
- // prevent unhandled promise rejection
151
- });
152
- queue.add({
153
- iterator,
154
- nextPromise,
155
- });
137
+ const iterable = callback(idx);
138
+ mergedIterables.add(iterable);
156
139
 
157
140
  return idx;
158
141
  }
@@ -170,10 +153,10 @@ async function* createBatchStreamProducer(
170
153
  }
171
154
  try {
172
155
  const next = await promise;
173
- return [idx, PROMISE_STATUS_FULFILLED, encode(next, path)];
156
+ yield [idx, PROMISE_STATUS_FULFILLED, encode(next, path)];
174
157
  } catch (cause) {
175
158
  opts.onError?.({ error: cause, path });
176
- return [
159
+ yield [
177
160
  idx,
178
161
  PROMISE_STATUS_REJECTED,
179
162
  opts.formatError?.({ error: cause, path }),
@@ -196,17 +179,15 @@ async function* createBatchStreamProducer(
196
179
  while (true) {
197
180
  const next = await iterator.next();
198
181
  if (next.done) {
199
- return [
200
- idx,
201
- ASYNC_ITERABLE_STATUS_RETURN,
202
- encode(next.value, path),
203
- ];
182
+ yield [idx, ASYNC_ITERABLE_STATUS_RETURN, encode(next.value, path)];
183
+ break;
204
184
  }
205
185
  yield [idx, ASYNC_ITERABLE_STATUS_YIELD, encode(next.value, path)];
206
186
  }
207
187
  } catch (cause) {
208
188
  opts.onError?.({ error: cause, path });
209
- return [
189
+
190
+ yield [
210
191
  idx,
211
192
  ASYNC_ITERABLE_STATUS_ERROR,
212
193
  opts.formatError?.({ error: cause, path }),
@@ -270,21 +251,8 @@ async function* createBatchStreamProducer(
270
251
 
271
252
  yield newHead;
272
253
 
273
- // Process all async iterables in parallel by racing their next values
274
- while (queue.size > 0) {
275
- // Race all iterators to get the next value from any of them
276
- const [entry, res] = await Unpromise.race(
277
- Array.from(queue).map(async (it) => [it, await it.nextPromise] as const),
278
- );
279
-
280
- yield res.value;
281
-
282
- // Remove current iterator and re-add if not done
283
- queue.delete(entry);
284
- if (!res.done) {
285
- entry.nextPromise = entry.iterator.next();
286
- queue.add(entry);
287
- }
254
+ for await (const value of mergedIterables) {
255
+ yield value;
288
256
  }
289
257
  }
290
258
  /**
@@ -0,0 +1,193 @@
1
+ import { createDeferred } from './createDeferred';
2
+ import { makeAsyncResource } from './disposable';
3
+
4
+ type ManagedIteratorResult<TYield, TReturn> =
5
+ | { status: 'yield'; value: TYield }
6
+ | { status: 'return'; value: TReturn }
7
+ | { status: 'error'; error: unknown };
8
+ function createManagedIterator<TYield, TReturn>(
9
+ iterable: AsyncIterable<TYield, TReturn>,
10
+ onResult: (result: ManagedIteratorResult<TYield, TReturn>) => void,
11
+ ) {
12
+ const iterator = iterable[Symbol.asyncIterator]();
13
+ let state: 'idle' | 'pending' | 'done' = 'idle';
14
+
15
+ function cleanup() {
16
+ state = 'done';
17
+ onResult = () => {
18
+ // noop
19
+ };
20
+ }
21
+
22
+ function pull() {
23
+ if (state !== 'idle') {
24
+ return;
25
+ }
26
+ state = 'pending';
27
+
28
+ const next = iterator.next();
29
+ next
30
+ .then((result) => {
31
+ if (result.done) {
32
+ state = 'done';
33
+ onResult({ status: 'return', value: result.value });
34
+ cleanup();
35
+ return;
36
+ }
37
+ state = 'idle';
38
+ onResult({ status: 'yield', value: result.value });
39
+ })
40
+ .catch((cause) => {
41
+ onResult({ status: 'error', error: cause });
42
+ cleanup();
43
+ });
44
+ }
45
+
46
+ return {
47
+ pull,
48
+ destroy: async () => {
49
+ cleanup();
50
+ await iterator.return?.();
51
+ },
52
+ };
53
+ }
54
+ type ManagedIterator<TYield, TReturn> = ReturnType<
55
+ typeof createManagedIterator<TYield, TReturn>
56
+ >;
57
+
58
+ interface MergedAsyncIterables<TYield>
59
+ extends AsyncIterable<TYield, void, unknown> {
60
+ add(iterable: AsyncIterable<TYield>): void;
61
+ }
62
+
63
+ /**
64
+ * Creates a new async iterable that merges multiple async iterables into a single stream.
65
+ * Values from the input iterables are yielded in the order they resolve, similar to Promise.race().
66
+ *
67
+ * New iterables can be added dynamically using the returned {@link MergedAsyncIterables.add} method, even after iteration has started.
68
+ *
69
+ * If any of the input iterables throws an error, that error will be propagated through the merged stream.
70
+ * Other iterables will not continue to be processed.
71
+ *
72
+ * @template TYield The type of values yielded by the input iterables
73
+ */
74
+ export function mergeAsyncIterables<TYield>(): MergedAsyncIterables<TYield> {
75
+ let state: 'idle' | 'pending' | 'done' = 'idle';
76
+ let flushSignal = createDeferred<void>();
77
+
78
+ /**
79
+ * used while {@link state} is `idle`
80
+ */
81
+ const iterables: AsyncIterable<TYield, void, unknown>[] = [];
82
+ /**
83
+ * used while {@link state} is `pending`
84
+ */
85
+ const iterators = new Set<ManagedIterator<TYield, void>>();
86
+
87
+ const buffer: Array<
88
+ [
89
+ iterator: ManagedIterator<TYield, void>,
90
+ result: Exclude<
91
+ ManagedIteratorResult<TYield, void>,
92
+ { status: 'return' }
93
+ >,
94
+ ]
95
+ > = [];
96
+
97
+ function initIterable(iterable: AsyncIterable<TYield, void, unknown>) {
98
+ if (state !== 'pending') {
99
+ // shouldn't happen
100
+ return;
101
+ }
102
+ const iterator = createManagedIterator(iterable, (result) => {
103
+ if (state !== 'pending') {
104
+ // shouldn't happen
105
+ return;
106
+ }
107
+ switch (result.status) {
108
+ case 'yield':
109
+ buffer.push([iterator, result]);
110
+ break;
111
+ case 'return':
112
+ iterators.delete(iterator);
113
+ break;
114
+ case 'error':
115
+ buffer.push([iterator, result]);
116
+ iterators.delete(iterator);
117
+ break;
118
+ }
119
+ flushSignal.resolve();
120
+ });
121
+ iterators.add(iterator);
122
+ iterator.pull();
123
+ }
124
+
125
+ return {
126
+ add(iterable: AsyncIterable<TYield, void, unknown>) {
127
+ switch (state) {
128
+ case 'idle':
129
+ iterables.push(iterable);
130
+ break;
131
+ case 'pending':
132
+ initIterable(iterable);
133
+ break;
134
+ case 'done': {
135
+ // shouldn't happen
136
+ break;
137
+ }
138
+ }
139
+ },
140
+ async *[Symbol.asyncIterator]() {
141
+ if (state !== 'idle') {
142
+ throw new Error('Cannot iterate twice');
143
+ }
144
+ state = 'pending';
145
+
146
+ await using _finally = makeAsyncResource({}, async () => {
147
+ state = 'done';
148
+
149
+ const errors: unknown[] = [];
150
+ await Promise.all(
151
+ Array.from(iterators.values()).map(async (it) => {
152
+ try {
153
+ await it.destroy();
154
+ } catch (cause) {
155
+ errors.push(cause);
156
+ }
157
+ }),
158
+ );
159
+ buffer.length = 0;
160
+ iterators.clear();
161
+ flushSignal.resolve();
162
+
163
+ if (errors.length > 0) {
164
+ throw new AggregateError(errors);
165
+ }
166
+ });
167
+
168
+ while (iterables.length > 0) {
169
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
170
+ initIterable(iterables.shift()!);
171
+ }
172
+
173
+ while (iterators.size > 0) {
174
+ await flushSignal.promise;
175
+
176
+ while (buffer.length > 0) {
177
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
178
+ const [iterator, result] = buffer.shift()!;
179
+
180
+ switch (result.status) {
181
+ case 'yield':
182
+ yield result.value;
183
+ iterator.pull();
184
+ break;
185
+ case 'error':
186
+ throw result.error;
187
+ }
188
+ }
189
+ flushSignal = createDeferred();
190
+ }
191
+ },
192
+ };
193
+ }