@orpc/client 2.0.0-beta.40 → 2.0.0-beta.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/README.md +5 -5
  2. package/dist/adapters/fetch/index.d.mts +1 -1
  3. package/dist/adapters/fetch/index.d.ts +1 -1
  4. package/dist/adapters/fetch/index.mjs +5 -4
  5. package/dist/adapters/message-port/index.d.mts +1 -1
  6. package/dist/adapters/message-port/index.d.ts +1 -1
  7. package/dist/adapters/message-port/index.mjs +10 -5
  8. package/dist/adapters/standard/index.d.mts +2 -2
  9. package/dist/adapters/standard/index.d.ts +2 -2
  10. package/dist/adapters/standard/index.mjs +3 -3
  11. package/dist/adapters/websocket/index.d.mts +13 -3
  12. package/dist/adapters/websocket/index.d.ts +13 -3
  13. package/dist/adapters/websocket/index.mjs +34 -13
  14. package/dist/index.d.mts +3 -3
  15. package/dist/index.d.ts +3 -3
  16. package/dist/index.mjs +3 -3
  17. package/dist/plugins/index.d.mts +40 -0
  18. package/dist/plugins/index.d.ts +40 -0
  19. package/dist/plugins/index.mjs +302 -190
  20. package/dist/shared/{client.n8K9dFhY.d.mts → client.BPa_Mmuh.d.mts} +1 -1
  21. package/dist/shared/{client.n8K9dFhY.d.ts → client.BPa_Mmuh.d.ts} +1 -1
  22. package/dist/shared/{client.BWkR2vPU.d.mts → client.BjbdW5Rj.d.mts} +1 -1
  23. package/dist/shared/{client.VXsLhJtT.mjs → client.Bstqyaro.mjs} +1 -1
  24. package/dist/shared/{client.Dtv1JPkU.mjs → client.DD2eNne_.mjs} +1 -0
  25. package/dist/shared/{client.BnpRXnLi.d.ts → client.DiHfJmHK.d.ts} +1 -1
  26. package/dist/shared/{client.BYHnU_Z3.mjs → client.DpkGsZt-.mjs} +19 -2
  27. package/dist/shared/{client.BoXZCzuS.d.ts → client.Dw9_40WO.d.mts} +1 -0
  28. package/dist/shared/{client.BoXZCzuS.d.mts → client.Dw9_40WO.d.ts} +1 -0
  29. package/package.json +7 -5
@@ -1,14 +1,16 @@
1
- import { toArray, value, splitInHalf, safeEncodeURIComponent, stringifyJSON, once, isAsyncIteratorObject, defer, loadBytes, allAbortSignal, replicateAsyncIterator, replicateReadableStream, isCompressibleContentType, override, AsyncIteratorClass, sleep, AbortError, anyAbortSignal } from '@orpc/shared';
1
+ import { toArray, value, splitInHalf, promiseWithResolvers, safeEncodeURIComponent, stringifyJSON, once, isAsyncIteratorObject, defer, captureAsyncContext, loadBytes, allAbortSignal, runWithSignal, replicateAsyncIterator, replicateReadableStream, wrapAsyncIterator, throwIfAborted, isCompressibleContentType, override, AsyncIteratorClass, sleep, AbortError, anyAbortSignal } from '@orpc/shared';
2
2
  import { parseStandardUrl, flattenStandardHeader, generateContentDisposition, getEventMeta } from '@standard-server/core';
3
3
  import { ClientPeer, isServerPeerSendMessage, decodePeerMessage } from '@standard-server/peer';
4
4
  import { toFetchHeaders, toStandardBody } from '@standard-server/fetch';
5
- import { C as COMMON_ERROR_STATUS_MAP } from '../shared/client.Dtv1JPkU.mjs';
5
+ import { C as COMMON_ERROR_STATUS_MAP } from '../shared/client.DD2eNne_.mjs';
6
6
 
7
7
  class BatchLinkPlugin {
8
8
  name = "~batch";
9
9
  groups;
10
10
  filter;
11
+ scope;
11
12
  maxSize;
13
+ wait;
12
14
  mode;
13
15
  batchUrl;
14
16
  maxUrlLength;
@@ -19,7 +21,9 @@ class BatchLinkPlugin {
19
21
  constructor(options) {
20
22
  this.groups = options.groups;
21
23
  this.filter = options.filter ?? (() => true);
24
+ this.scope = options.scope ?? (() => void 0);
22
25
  this.maxSize = options.maxSize ?? 10;
26
+ this.wait = options.wait ?? 0;
23
27
  this.mode = options.mode ?? "streaming";
24
28
  this.batchUrl = options.url ?? ((options2) => {
25
29
  const [pathname] = parseStandardUrl(options2[0].request.url);
@@ -65,20 +69,30 @@ class BatchLinkPlugin {
65
69
  }
66
70
  init(options) {
67
71
  const transportInterceptor = async (interceptorOptions) => {
68
- if (interceptorOptions.request.body instanceof Blob || interceptorOptions.request.body instanceof ReadableStream || isAsyncIteratorObject(interceptorOptions.request.body) || interceptorOptions.request.signal?.aborted || !value(this.filter, interceptorOptions)) {
72
+ const { body, signal } = interceptorOptions.request;
73
+ if (body instanceof Blob || body instanceof FormData || body instanceof ReadableStream || isAsyncIteratorObject(body) || signal?.aborted || !value(this.filter, interceptorOptions)) {
69
74
  return interceptorOptions.next();
70
75
  }
71
76
  const group = this.groups.find((group2) => value(group2.condition, interceptorOptions));
72
77
  if (!group) {
73
78
  return interceptorOptions.next();
74
79
  }
80
+ const scope = this.scope(interceptorOptions);
75
81
  return new Promise((resolve, reject) => {
76
- const queue = this.queue.get(group) ?? [];
77
- if (!this.queue.has(group)) {
78
- this.queue.set(group, queue);
82
+ if (!this.queue.size) {
83
+ defer(() => this.processPendingBatches(), this.wait);
79
84
  }
80
- queue.push([interceptorOptions, resolve, reject]);
81
- defer(() => this.processPendingBatches());
85
+ let groups = this.queue.get(scope);
86
+ if (!groups) {
87
+ groups = /* @__PURE__ */ new Map();
88
+ this.queue.set(scope, groups);
89
+ }
90
+ let queue = groups.get(group);
91
+ if (!queue) {
92
+ queue = [];
93
+ groups.set(group, queue);
94
+ }
95
+ queue.push([interceptorOptions, resolve, reject, captureAsyncContext()]);
82
96
  });
83
97
  };
84
98
  return {
@@ -86,120 +100,170 @@ class BatchLinkPlugin {
86
100
  transportInterceptors: [...toArray(options.transportInterceptors), transportInterceptor]
87
101
  };
88
102
  }
89
- async processPendingBatches() {
90
- const pending = new Map(this.queue);
103
+ processPendingBatches() {
104
+ const pending = [...this.queue.values()];
91
105
  this.queue.clear();
92
- for (const [group, items] of pending) {
93
- const getItems = items.filter(([options]) => options.request.method === "GET");
94
- const queryItems = items.filter(([options]) => options.request.method === "QUERY");
95
- const unsafeItems = items.filter(([options]) => options.request.method !== "GET" && options.request.method !== "QUERY");
96
- this.executeBatch("GET", group, getItems);
97
- this.executeBatch("QUERY", group, queryItems);
98
- this.executeBatch("POST", group, unsafeItems);
106
+ for (const groups of pending) {
107
+ for (const [group, items] of groups) {
108
+ const getItems = items.filter(([options]) => options.request.method === "GET");
109
+ const queryItems = items.filter(([options]) => options.request.method === "QUERY");
110
+ const unsafeItems = items.filter(([options]) => options.request.method !== "GET" && options.request.method !== "QUERY");
111
+ this.executeBatchInOwnContext("GET", group, getItems);
112
+ this.executeBatchInOwnContext("QUERY", group, queryItems);
113
+ this.executeBatchInOwnContext("POST", group, unsafeItems);
114
+ }
99
115
  }
100
116
  }
117
+ /**
118
+ * Runs the batch in the async context of its first request instead of the timer's,
119
+ * so the transport and batch options see that caller's request state.
120
+ * `executeBatch` always runs in the context of its first request.
121
+ */
122
+ async executeBatchInOwnContext(method, group, groupItems) {
123
+ await groupItems[0]?.[3](() => this.executeBatch(method, group, groupItems));
124
+ }
101
125
  async executeBatch(method, group, groupItems) {
102
- if (!groupItems.length) {
103
- return;
104
- }
105
- if (groupItems.length === 1) {
106
- const [options, resolve, reject] = groupItems[0];
107
- options.next().then(resolve).catch(reject);
108
- return;
109
- }
110
- const subOptionsList = groupItems.map(([options]) => options);
111
- const maxSize = await value(this.maxSize, subOptionsList);
112
- if (groupItems.length > maxSize) {
113
- const [first, second] = splitInHalf(groupItems);
114
- await Promise.all([
115
- this.executeBatch(method, group, first),
116
- this.executeBatch(method, group, second)
117
- ]);
118
- return;
119
- }
120
- const url = await value(this.batchUrl, subOptionsList);
121
- const headers = await value(this.batchHeaders, subOptionsList);
122
- const mode = value(this.mode, subOptionsList);
123
- let suppressErrorFromCurrentBatch = false;
124
- const controller = new AbortController();
125
- const pendingMessages = [];
126
- let batchResponse;
127
- const peer = new ClientPeer(async (message) => {
128
- pendingMessages.push(message);
129
- if (message.kind === "cancel" && pendingMessages.filter((m) => m.kind === "cancel").length === groupItems.length) {
130
- controller.abort();
126
+ try {
127
+ if (groupItems.length === 1) {
128
+ const [options, resolve, reject] = groupItems[0];
129
+ options.next().then(resolve).catch(reject);
130
+ return;
131
131
  }
132
- if (message.kind === "request" && pendingMessages.filter((m) => m.kind === "request").length === groupItems.length) {
133
- (async () => {
134
- try {
135
- const request = {
136
- url,
137
- method,
138
- headers: { ...headers, "orpc-batch": mode },
139
- signal: controller.signal
140
- };
141
- if (method === "GET") {
142
- const [pathname, search, hash] = parseStandardUrl(url);
143
- const dataParam = `data=${safeEncodeURIComponent(stringifyJSON(pendingMessages))}`;
144
- const newUrl = search ? `${pathname}${search}&${dataParam}${hash ?? ""}` : `${pathname}?${dataParam}${hash ?? ""}`;
145
- const maxUrlLength = await value(this.maxUrlLength, subOptionsList);
146
- if (newUrl.length > maxUrlLength) {
147
- const [first, second] = splitInHalf(groupItems);
148
- suppressErrorFromCurrentBatch = true;
149
- await Promise.all([
150
- this.executeBatch(method, group, first),
151
- this.executeBatch(method, group, second),
152
- peer.close()
153
- ]);
154
- return;
155
- }
156
- request.url = newUrl;
157
- } else {
158
- request.body = pendingMessages;
159
- }
160
- batchResponse = await groupItems[0][0].next({
161
- ...subOptionsList[0],
162
- context: value(group.context, subOptionsList),
163
- path: value(group.path, subOptionsList) ?? [],
164
- request,
165
- signal: controller.signal
166
- });
167
- if (batchResponse.status >= 400) {
168
- suppressErrorFromCurrentBatch = true;
169
- const resolveBody = once(() => batchResponse.resolveBody());
170
- const errorResponse = { ...batchResponse, resolveBody };
171
- groupItems.forEach(([subOptions, resolve]) => {
172
- resolve(this.mapSubresponse(errorResponse, batchResponse, subOptions));
173
- });
174
- await peer.close();
175
- return;
176
- }
177
- const body = await batchResponse.resolveBody();
178
- if (Array.isArray(body) && body.every((v) => isServerPeerSendMessage(v))) {
179
- for (const message2 of body) {
180
- await peer.message(message2);
181
- }
182
- } else if (body instanceof Blob) {
183
- await decodeLengthPrefixedBlob(body, peer);
184
- } else if (body instanceof ReadableStream) {
185
- await decodeLengthPrefixedStream(body, peer);
186
- } else {
187
- throw new TypeError("Invalid batch response format.");
188
- }
189
- await peer.close(new TypeError("Batch response is incomplete."));
190
- } catch (error) {
191
- await peer.close(error);
192
- }
193
- })();
132
+ const subOptionsList = groupItems.map(([options]) => options);
133
+ const maxSize = await value(this.maxSize, subOptionsList);
134
+ if (groupItems.length > maxSize) {
135
+ const [first, second] = splitInHalf(groupItems);
136
+ await Promise.all([
137
+ this.executeBatch(method, group, first),
138
+ this.executeBatchInOwnContext(method, group, second)
139
+ ]);
140
+ return;
194
141
  }
195
- });
196
- groupItems.forEach(([subOptions, resolve, reject]) => {
197
- peer.request(this.mapSubrequest(subOptions, { url, headers })).then((subResponse) => resolve(this.mapSubresponse(subResponse, batchResponse, subOptions))).catch((error) => {
198
- if (!suppressErrorFromCurrentBatch) {
199
- reject(error);
142
+ const url = await value(this.batchUrl, subOptionsList);
143
+ const headers = await value(this.batchHeaders, subOptionsList);
144
+ const mode = value(this.mode, subOptionsList);
145
+ let suppressErrorFromCurrentBatch = false;
146
+ const controller = new AbortController();
147
+ const pendingMessages = [];
148
+ const subrequests = groupItems.map(([subOptions]) => this.mapSubrequest(subOptions, { url, headers }));
149
+ let batchResponse;
150
+ let markRequestSent;
151
+ const openRequestIds = /* @__PURE__ */ new Set();
152
+ const cancelledRunningRequestIds = /* @__PURE__ */ new Set();
153
+ let isBatchSent = false;
154
+ const abortIfOnlyCancelledRemain = () => {
155
+ if (openRequestIds.size === 0 && cancelledRunningRequestIds.size > 0) {
156
+ controller.abort();
157
+ }
158
+ };
159
+ const peer = new ClientPeer(async (message) => {
160
+ pendingMessages.push(message);
161
+ if (message.kind === "request") {
162
+ openRequestIds.add(message.id);
163
+ markRequestSent?.();
164
+ } else if (message.kind === "cancel" && openRequestIds.delete(message.id) && isBatchSent) {
165
+ cancelledRunningRequestIds.add(message.id);
166
+ abortIfOnlyCancelledRemain();
200
167
  }
201
168
  });
202
- });
169
+ for (const [index, [subOptions, resolve, reject]] of groupItems.entries()) {
170
+ const sent = promiseWithResolvers();
171
+ markRequestSent = sent.resolve;
172
+ peer.request(subrequests[index]).then((subResponse) => resolve(this.mapSubresponse(subResponse, batchResponse, subOptions))).catch((error) => {
173
+ if (!suppressErrorFromCurrentBatch) {
174
+ reject(error);
175
+ }
176
+ }).then(sent.resolve);
177
+ await sent.promise;
178
+ }
179
+ if (openRequestIds.size === 0) {
180
+ return;
181
+ }
182
+ isBatchSent = true;
183
+ const outgoingMessages = pendingMessages.filter((message) => openRequestIds.has(message.id));
184
+ try {
185
+ const request = {
186
+ url,
187
+ method,
188
+ headers: { ...headers, "orpc-batch": mode },
189
+ signal: controller.signal
190
+ };
191
+ if (method === "GET") {
192
+ const [pathname, search, hash] = parseStandardUrl(url);
193
+ const dataParam = `data=${safeEncodeURIComponent(stringifyJSON(outgoingMessages))}`;
194
+ const newUrl = search ? `${pathname}${search}&${dataParam}${hash ?? ""}` : `${pathname}?${dataParam}${hash ?? ""}`;
195
+ const maxUrlLength = await value(this.maxUrlLength, subOptionsList);
196
+ if (newUrl.length > maxUrlLength) {
197
+ const [first, second] = splitInHalf(groupItems);
198
+ suppressErrorFromCurrentBatch = true;
199
+ await Promise.all([
200
+ this.executeBatch(method, group, first),
201
+ this.executeBatchInOwnContext(method, group, second),
202
+ peer.close()
203
+ ]);
204
+ return;
205
+ }
206
+ request.url = newUrl;
207
+ } else {
208
+ request.body = outgoingMessages;
209
+ }
210
+ batchResponse = await groupItems[0][0].next({
211
+ ...subOptionsList[0],
212
+ context: value(group.context, subOptionsList),
213
+ path: value(group.path, subOptionsList) ?? [],
214
+ request,
215
+ signal: controller.signal
216
+ });
217
+ if (batchResponse.status >= 400) {
218
+ const resolveBody = once(() => batchResponse.resolveBody());
219
+ const errorResponse = { ...batchResponse, resolveBody };
220
+ groupItems.forEach(([subOptions, resolve]) => {
221
+ resolve(this.mapSubresponse(errorResponse, batchResponse, subOptions));
222
+ });
223
+ suppressErrorFromCurrentBatch = true;
224
+ await peer.close();
225
+ return;
226
+ }
227
+ const body = await batchResponse.resolveBody();
228
+ if (Array.isArray(body) && body.every((v) => isServerPeerSendMessage(v))) {
229
+ for (const message of body) {
230
+ await peer.message(message);
231
+ }
232
+ } else if (body instanceof Blob) {
233
+ await decodeLengthPrefixedBlob(body, peer);
234
+ } else if (body instanceof ReadableStream) {
235
+ await decodeLengthPrefixedStream(body, async (message) => {
236
+ await peer.message(message);
237
+ if (isLastServerMessage(message)) {
238
+ openRequestIds.delete(message.id);
239
+ cancelledRunningRequestIds.delete(message.id);
240
+ abortIfOnlyCancelledRemain();
241
+ }
242
+ });
243
+ } else {
244
+ throw new TypeError("Invalid batch response format.");
245
+ }
246
+ await peer.close(new TypeError("Batch response is incomplete."));
247
+ } catch (error) {
248
+ await peer.close(error);
249
+ }
250
+ } catch (error) {
251
+ groupItems.forEach(([, , reject]) => reject(error));
252
+ }
253
+ }
254
+ }
255
+ function isLastServerMessage(message) {
256
+ switch (message.kind) {
257
+ case "response":
258
+ return message.json.body !== void 0 || message.binary !== void 0 || message.json.headers?.["content-type"] === void 0 && message.json.headers?.["standard-server"] === void 0;
259
+ case "event-stream":
260
+ return message.json.event === "close" || message.json.event === "error";
261
+ case "octet-stream":
262
+ return message.json.close === true;
263
+ case "cancel":
264
+ return true;
265
+ default:
266
+ return false;
203
267
  }
204
268
  }
205
269
  async function decodeLengthPrefixedBlob(blob, peer) {
@@ -224,7 +288,7 @@ async function decodeLengthPrefixedBlob(blob, peer) {
224
288
  await peer.message(result.message);
225
289
  }
226
290
  }
227
- async function decodeLengthPrefixedStream(stream, peer) {
291
+ async function decodeLengthPrefixedStream(stream, receive) {
228
292
  const reader = stream.getReader();
229
293
  let buffer = new Uint8Array(0);
230
294
  try {
@@ -252,7 +316,7 @@ async function decodeLengthPrefixedStream(stream, peer) {
252
316
  if (!result.matched || !isServerPeerSendMessage(result.message)) {
253
317
  throw new TypeError("Invalid batch response: invalid message.");
254
318
  }
255
- await peer.message(result.message);
319
+ await receive(result.message);
256
320
  }
257
321
  if (done) {
258
322
  break;
@@ -268,10 +332,14 @@ class DedupeLinkPlugin {
268
332
  before = ["~batch"];
269
333
  groups;
270
334
  filter;
335
+ scope;
336
+ wait;
271
337
  queue = /* @__PURE__ */ new Map();
272
338
  constructor(options) {
273
339
  this.groups = options.groups;
274
340
  this.filter = options.filter ?? (({ request }) => request.method === "GET" || request.method === "QUERY");
341
+ this.scope = options.scope ?? (() => void 0);
342
+ this.wait = options.wait ?? 0;
275
343
  }
276
344
  init(options) {
277
345
  const transportInterceptor = (interceptorOptions) => {
@@ -282,78 +350,81 @@ class DedupeLinkPlugin {
282
350
  if (!group) {
283
351
  return interceptorOptions.next();
284
352
  }
285
- return new Promise((resolve, reject) => {
286
- this.enqueue(group, interceptorOptions, resolve, reject);
287
- defer(() => {
288
- this.processPendingRequests();
289
- });
290
- });
353
+ const scope = this.scope(interceptorOptions);
354
+ return runWithSignal(interceptorOptions.request.signal, () => new Promise((resolve, reject) => {
355
+ if (!this.queue.size) {
356
+ defer(() => this.processPendingRequests(), this.wait);
357
+ }
358
+ this.enqueue(scope, group, { options: interceptorOptions, resolve, reject, runInOwnContext: captureAsyncContext() });
359
+ }));
291
360
  };
292
361
  return {
293
362
  ...options,
294
363
  transportInterceptors: [...toArray(options.transportInterceptors), transportInterceptor]
295
364
  };
296
365
  }
297
- enqueue(group, options, resolve, reject) {
298
- let queue = this.queue.get(group);
366
+ enqueue(scope, group, caller) {
367
+ let groups = this.queue.get(scope);
368
+ if (!groups) {
369
+ groups = /* @__PURE__ */ new Map();
370
+ this.queue.set(scope, groups);
371
+ }
372
+ let queue = groups.get(group);
299
373
  if (!queue) {
300
- queue = [];
301
- this.queue.set(group, queue);
374
+ queue = /* @__PURE__ */ new Map();
375
+ groups.set(group, queue);
302
376
  }
303
- const requestKey = createRequestKey(options.path, options.request);
304
- const matched = queue.find((item) => item.requestKey === requestKey);
377
+ const requestKey = createRequestKey(caller.options.path, caller.options.request);
378
+ const matched = queue.get(requestKey);
305
379
  if (matched) {
306
- matched.matchedOptions.push(options);
307
- matched.signals.push(options.request.signal);
308
- matched.resolves.push(resolve);
309
- matched.rejects.push(reject);
380
+ matched.push(caller);
310
381
  return;
311
382
  }
312
- queue.push({
313
- requestKey,
314
- options,
315
- matchedOptions: [options],
316
- signals: [options.request.signal],
317
- resolves: [resolve],
318
- rejects: [reject]
319
- });
383
+ queue.set(requestKey, [caller]);
320
384
  }
321
385
  async processPendingRequests() {
322
- const pending = new Map(this.queue);
386
+ const pending = [...this.queue.values()];
323
387
  this.queue.clear();
324
388
  const executions = [];
325
- for (const [group, items] of pending) {
326
- for (const item of items) {
327
- executions.push(this.execute(group, item));
389
+ for (const groups of pending) {
390
+ for (const [group, items] of groups) {
391
+ for (const callers of items.values()) {
392
+ const activeCallers = callers.filter((caller) => !caller.options.request.signal?.aborted);
393
+ const [first] = activeCallers;
394
+ if (first) {
395
+ executions.push(first.runInOwnContext(() => this.execute(group, activeCallers)));
396
+ }
397
+ }
328
398
  }
329
399
  }
330
400
  await Promise.all(executions);
331
401
  }
332
- async execute(group, item) {
402
+ async execute(group, activeCallers) {
403
+ const first = activeCallers[0];
404
+ const matchedOptions = activeCallers.map((caller) => caller.options);
333
405
  try {
334
- if (!shouldDedupe(item.matchedOptions)) {
335
- const response2 = await item.options.next(item.options);
336
- item.resolves[0]?.(response2);
406
+ if (!shouldDedupe(matchedOptions)) {
407
+ const response2 = await first.options.next(first.options);
408
+ first.resolve(response2);
337
409
  return;
338
410
  }
339
- const context = value(group.context, item.matchedOptions);
411
+ const context = value(group.context, matchedOptions);
412
+ const signals = matchedOptions.map((options) => options.request.signal);
340
413
  const request = {
341
- ...item.options.request,
342
- signal: allAbortSignal(item.signals)
414
+ ...first.options.request,
415
+ signal: allAbortSignal(signals)
343
416
  };
344
- const response = await item.options.next({
345
- ...item.options,
417
+ const response = await first.options.next({
418
+ ...first.options,
346
419
  request,
347
420
  signal: request.signal,
348
421
  context
349
422
  });
350
- const replicatedResponses = replicateLazyResponse(response, item.resolves.length);
351
- for (const resolve of item.resolves) {
352
- resolve(replicatedResponses.pop());
353
- }
423
+ const replicatedResponses = replicateLazyResponse(response, signals);
424
+ activeCallers.forEach((caller, index) => caller.resolve(replicatedResponses[index]));
354
425
  } catch (error) {
355
- for (const reject of item.rejects) {
356
- reject(error);
426
+ for (const caller of activeCallers) {
427
+ caller.reject(error);
357
428
  }
358
429
  }
359
430
  }
@@ -370,35 +441,76 @@ function createRequestKey(path, request) {
370
441
  url: request.url
371
442
  });
372
443
  }
373
- function replicateLazyResponse(response, count) {
374
- const replicated = [];
375
- let bodyPromise;
376
- let replicatedAsyncIterators;
377
- let replicatedReadableStream;
378
- for (let i = 0; i < count; i++) {
379
- let resolvedBody;
380
- replicated.push({
381
- ...response,
382
- resolveBody: async (hint) => {
383
- if (resolvedBody) {
384
- return resolvedBody.body;
385
- }
386
- bodyPromise ??= response.resolveBody(hint);
387
- const body = await bodyPromise;
444
+ function replicateLazyResponse(response, signals) {
445
+ let bodiesPromise;
446
+ return signals.map((signal, i) => ({
447
+ ...response,
448
+ resolveBody: (hint) => runWithSignal(signal, async () => {
449
+ bodiesPromise ??= response.resolveBody(hint).then((body) => {
388
450
  if (isAsyncIteratorObject(body)) {
389
- replicatedAsyncIterators ??= replicateAsyncIterator(body, count);
390
- resolvedBody = { body: replicatedAsyncIterators.pop() };
391
- } else if (body instanceof ReadableStream) {
392
- replicatedReadableStream ??= replicateReadableStream(body, count);
393
- resolvedBody = { body: replicatedReadableStream.pop() };
451
+ return replicateAsyncIterator(body, signals.length).map((replica, index) => closeAsyncIteratorOnAbort(replica, signals[index]));
452
+ }
453
+ if (body instanceof ReadableStream) {
454
+ return replicateReadableStream(body, signals.length).map((replica, index) => closeReadableStreamOnAbort(replica, signals[index]));
455
+ }
456
+ return signals.map(() => body);
457
+ });
458
+ return (await bodiesPromise)[i];
459
+ })
460
+ }));
461
+ }
462
+ function closeAsyncIteratorOnAbort(replica, signal) {
463
+ if (!signal) {
464
+ return replica;
465
+ }
466
+ const abort = () => replica.return().catch(() => {
467
+ });
468
+ if (signal.aborted) {
469
+ void abort();
470
+ } else {
471
+ signal.addEventListener("abort", abort, { once: true });
472
+ }
473
+ return wrapAsyncIterator(replica, {
474
+ mapResult(result) {
475
+ throwIfAborted(signal);
476
+ return result;
477
+ },
478
+ onFinish: () => signal.removeEventListener("abort", abort)
479
+ });
480
+ }
481
+ function closeReadableStreamOnAbort(replica, signal) {
482
+ if (!signal) {
483
+ return replica;
484
+ }
485
+ const reader = replica.getReader();
486
+ const abort = () => reader.cancel(signal.reason).catch(() => {
487
+ });
488
+ if (signal.aborted) {
489
+ void abort();
490
+ } else {
491
+ signal.addEventListener("abort", abort, { once: true });
492
+ }
493
+ return new ReadableStream({
494
+ async pull(controller) {
495
+ try {
496
+ const result = await reader.read();
497
+ throwIfAborted(signal);
498
+ if (result.done) {
499
+ signal.removeEventListener("abort", abort);
500
+ controller.close();
394
501
  } else {
395
- resolvedBody = { body };
502
+ controller.enqueue(result.value);
396
503
  }
397
- return resolvedBody.body;
504
+ } catch (error) {
505
+ signal.removeEventListener("abort", abort);
506
+ throw error;
398
507
  }
399
- });
400
- }
401
- return replicated;
508
+ },
509
+ async cancel(reason) {
510
+ signal.removeEventListener("abort", abort);
511
+ await reader.cancel(reason);
512
+ }
513
+ }, { highWaterMark: 0 });
402
514
  }
403
515
  function shouldDedupe(items) {
404
516
  return items.length >= 2;
@@ -554,6 +666,7 @@ class RequestCompressionLinkPlugin {
554
666
  }
555
667
  }
556
668
 
669
+ const MAX_CONTENT_ENCODINGS = 5;
557
670
  class ResponseCompressionLinkPlugin {
558
671
  name = "~response-compression";
559
672
  /**
@@ -596,6 +709,9 @@ class ResponseCompressionLinkPlugin {
596
709
  if (!(stream instanceof ReadableStream)) {
597
710
  return stream;
598
711
  }
712
+ if (encodings.length > MAX_CONTENT_ENCODINGS) {
713
+ throw new TypeError("Too many content encodings.");
714
+ }
599
715
  let decompressedStream = stream;
600
716
  for (let i = encodings.length - 1; i >= 0; i--) {
601
717
  decompressedStream = decompressedStream.pipeThrough(
@@ -662,6 +778,7 @@ class RetryLinkPlugin {
662
778
  let currentError = initialError;
663
779
  while (true) {
664
780
  const updatedCallOptions = { ...callOptions, lastEventId };
781
+ let retryDelayMs = 0;
665
782
  if (currentError) {
666
783
  if (attempt > maxAttempts) {
667
784
  throw currentError.error;
@@ -679,16 +796,11 @@ class RetryLinkPlugin {
679
796
  if (!shouldRetryBool) {
680
797
  throw currentError.error;
681
798
  }
799
+ retryDelayMs = await value(retryDelay, attemptOptions);
682
800
  callback = onRetry?.(attemptOptions);
683
801
  }
684
802
  try {
685
803
  if (currentError) {
686
- const retryDelayMs = await value(retryDelay, {
687
- ...updatedCallOptions,
688
- attempt,
689
- error: currentError.error,
690
- lastEventRetry
691
- });
692
804
  await sleep(retryDelayMs, { signal: updatedCallOptions.signal });
693
805
  attempt++;
694
806
  }
@@ -66,7 +66,7 @@ interface RPCJsonSerializerOptions {
66
66
  * handlers: { url: undefined }
67
67
  * ```
68
68
  *
69
- * Built-in type keys: `undefined`, `bigint`, `date`, `nan`, `url`, `set`, `map`.
69
+ * Built-in type keys: `undefined`, `bigint`, `date`, `nan`, `infinity`, `url`, `set`, `map`.
70
70
  */
71
71
  handlers?: Record<string, undefined | RPCJsonSerializerHandler> | undefined;
72
72
  /**
@@ -66,7 +66,7 @@ interface RPCJsonSerializerOptions {
66
66
  * handlers: { url: undefined }
67
67
  * ```
68
68
  *
69
- * Built-in type keys: `undefined`, `bigint`, `date`, `nan`, `url`, `set`, `map`.
69
+ * Built-in type keys: `undefined`, `bigint`, `date`, `nan`, `infinity`, `url`, `set`, `map`.
70
70
  */
71
71
  handlers?: Record<string, undefined | RPCJsonSerializerHandler> | undefined;
72
72
  /**
@@ -1,6 +1,6 @@
1
1
  import { Promisable, OrderablePlugin, Interceptor } from '@orpc/shared';
2
2
  import { StandardRequest, StandardLazyResponse } from '@standard-server/core';
3
- import { C as ClientContext, a as ClientOptions, A as AnyORPCError, b as ClientLink } from './client.BoXZCzuS.mjs';
3
+ import { C as ClientContext, a as ClientOptions, A as AnyORPCError, b as ClientLink } from './client.Dw9_40WO.mjs';
4
4
 
5
5
  type StandardLinkCodecDecodedResponse = {
6
6
  kind: 'output';