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

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 +301 -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,169 @@ 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
+ try {
184
+ const request = {
185
+ url,
186
+ method,
187
+ headers: { ...headers, "orpc-batch": mode },
188
+ signal: controller.signal
189
+ };
190
+ if (method === "GET") {
191
+ const [pathname, search, hash] = parseStandardUrl(url);
192
+ const dataParam = `data=${safeEncodeURIComponent(stringifyJSON(pendingMessages))}`;
193
+ const newUrl = search ? `${pathname}${search}&${dataParam}${hash ?? ""}` : `${pathname}?${dataParam}${hash ?? ""}`;
194
+ const maxUrlLength = await value(this.maxUrlLength, subOptionsList);
195
+ if (newUrl.length > maxUrlLength) {
196
+ const [first, second] = splitInHalf(groupItems);
197
+ suppressErrorFromCurrentBatch = true;
198
+ await Promise.all([
199
+ this.executeBatch(method, group, first),
200
+ this.executeBatchInOwnContext(method, group, second),
201
+ peer.close()
202
+ ]);
203
+ return;
204
+ }
205
+ request.url = newUrl;
206
+ } else {
207
+ request.body = pendingMessages;
208
+ }
209
+ batchResponse = await groupItems[0][0].next({
210
+ ...subOptionsList[0],
211
+ context: value(group.context, subOptionsList),
212
+ path: value(group.path, subOptionsList) ?? [],
213
+ request,
214
+ signal: controller.signal
215
+ });
216
+ if (batchResponse.status >= 400) {
217
+ const resolveBody = once(() => batchResponse.resolveBody());
218
+ const errorResponse = { ...batchResponse, resolveBody };
219
+ groupItems.forEach(([subOptions, resolve]) => {
220
+ resolve(this.mapSubresponse(errorResponse, batchResponse, subOptions));
221
+ });
222
+ suppressErrorFromCurrentBatch = true;
223
+ await peer.close();
224
+ return;
225
+ }
226
+ const body = await batchResponse.resolveBody();
227
+ if (Array.isArray(body) && body.every((v) => isServerPeerSendMessage(v))) {
228
+ for (const message of body) {
229
+ await peer.message(message);
230
+ }
231
+ } else if (body instanceof Blob) {
232
+ await decodeLengthPrefixedBlob(body, peer);
233
+ } else if (body instanceof ReadableStream) {
234
+ await decodeLengthPrefixedStream(body, async (message) => {
235
+ await peer.message(message);
236
+ if (isLastServerMessage(message)) {
237
+ openRequestIds.delete(message.id);
238
+ cancelledRunningRequestIds.delete(message.id);
239
+ abortIfOnlyCancelledRemain();
240
+ }
241
+ });
242
+ } else {
243
+ throw new TypeError("Invalid batch response format.");
244
+ }
245
+ await peer.close(new TypeError("Batch response is incomplete."));
246
+ } catch (error) {
247
+ await peer.close(error);
248
+ }
249
+ } catch (error) {
250
+ groupItems.forEach(([, , reject]) => reject(error));
251
+ }
252
+ }
253
+ }
254
+ function isLastServerMessage(message) {
255
+ switch (message.kind) {
256
+ case "response":
257
+ return message.json.body !== void 0 || message.binary !== void 0 || message.json.headers?.["content-type"] === void 0 && message.json.headers?.["standard-server"] === void 0;
258
+ case "event-stream":
259
+ return message.json.event === "close" || message.json.event === "error";
260
+ case "octet-stream":
261
+ return message.json.close === true;
262
+ case "cancel":
263
+ return true;
264
+ default:
265
+ return false;
203
266
  }
204
267
  }
205
268
  async function decodeLengthPrefixedBlob(blob, peer) {
@@ -224,7 +287,7 @@ async function decodeLengthPrefixedBlob(blob, peer) {
224
287
  await peer.message(result.message);
225
288
  }
226
289
  }
227
- async function decodeLengthPrefixedStream(stream, peer) {
290
+ async function decodeLengthPrefixedStream(stream, receive) {
228
291
  const reader = stream.getReader();
229
292
  let buffer = new Uint8Array(0);
230
293
  try {
@@ -252,7 +315,7 @@ async function decodeLengthPrefixedStream(stream, peer) {
252
315
  if (!result.matched || !isServerPeerSendMessage(result.message)) {
253
316
  throw new TypeError("Invalid batch response: invalid message.");
254
317
  }
255
- await peer.message(result.message);
318
+ await receive(result.message);
256
319
  }
257
320
  if (done) {
258
321
  break;
@@ -268,10 +331,14 @@ class DedupeLinkPlugin {
268
331
  before = ["~batch"];
269
332
  groups;
270
333
  filter;
334
+ scope;
335
+ wait;
271
336
  queue = /* @__PURE__ */ new Map();
272
337
  constructor(options) {
273
338
  this.groups = options.groups;
274
339
  this.filter = options.filter ?? (({ request }) => request.method === "GET" || request.method === "QUERY");
340
+ this.scope = options.scope ?? (() => void 0);
341
+ this.wait = options.wait ?? 0;
275
342
  }
276
343
  init(options) {
277
344
  const transportInterceptor = (interceptorOptions) => {
@@ -282,78 +349,81 @@ class DedupeLinkPlugin {
282
349
  if (!group) {
283
350
  return interceptorOptions.next();
284
351
  }
285
- return new Promise((resolve, reject) => {
286
- this.enqueue(group, interceptorOptions, resolve, reject);
287
- defer(() => {
288
- this.processPendingRequests();
289
- });
290
- });
352
+ const scope = this.scope(interceptorOptions);
353
+ return runWithSignal(interceptorOptions.request.signal, () => new Promise((resolve, reject) => {
354
+ if (!this.queue.size) {
355
+ defer(() => this.processPendingRequests(), this.wait);
356
+ }
357
+ this.enqueue(scope, group, { options: interceptorOptions, resolve, reject, runInOwnContext: captureAsyncContext() });
358
+ }));
291
359
  };
292
360
  return {
293
361
  ...options,
294
362
  transportInterceptors: [...toArray(options.transportInterceptors), transportInterceptor]
295
363
  };
296
364
  }
297
- enqueue(group, options, resolve, reject) {
298
- let queue = this.queue.get(group);
365
+ enqueue(scope, group, caller) {
366
+ let groups = this.queue.get(scope);
367
+ if (!groups) {
368
+ groups = /* @__PURE__ */ new Map();
369
+ this.queue.set(scope, groups);
370
+ }
371
+ let queue = groups.get(group);
299
372
  if (!queue) {
300
- queue = [];
301
- this.queue.set(group, queue);
373
+ queue = /* @__PURE__ */ new Map();
374
+ groups.set(group, queue);
302
375
  }
303
- const requestKey = createRequestKey(options.path, options.request);
304
- const matched = queue.find((item) => item.requestKey === requestKey);
376
+ const requestKey = createRequestKey(caller.options.path, caller.options.request);
377
+ const matched = queue.get(requestKey);
305
378
  if (matched) {
306
- matched.matchedOptions.push(options);
307
- matched.signals.push(options.request.signal);
308
- matched.resolves.push(resolve);
309
- matched.rejects.push(reject);
379
+ matched.push(caller);
310
380
  return;
311
381
  }
312
- queue.push({
313
- requestKey,
314
- options,
315
- matchedOptions: [options],
316
- signals: [options.request.signal],
317
- resolves: [resolve],
318
- rejects: [reject]
319
- });
382
+ queue.set(requestKey, [caller]);
320
383
  }
321
384
  async processPendingRequests() {
322
- const pending = new Map(this.queue);
385
+ const pending = [...this.queue.values()];
323
386
  this.queue.clear();
324
387
  const executions = [];
325
- for (const [group, items] of pending) {
326
- for (const item of items) {
327
- executions.push(this.execute(group, item));
388
+ for (const groups of pending) {
389
+ for (const [group, items] of groups) {
390
+ for (const callers of items.values()) {
391
+ const activeCallers = callers.filter((caller) => !caller.options.request.signal?.aborted);
392
+ const [first] = activeCallers;
393
+ if (first) {
394
+ executions.push(first.runInOwnContext(() => this.execute(group, activeCallers)));
395
+ }
396
+ }
328
397
  }
329
398
  }
330
399
  await Promise.all(executions);
331
400
  }
332
- async execute(group, item) {
401
+ async execute(group, activeCallers) {
402
+ const first = activeCallers[0];
403
+ const matchedOptions = activeCallers.map((caller) => caller.options);
333
404
  try {
334
- if (!shouldDedupe(item.matchedOptions)) {
335
- const response2 = await item.options.next(item.options);
336
- item.resolves[0]?.(response2);
405
+ if (!shouldDedupe(matchedOptions)) {
406
+ const response2 = await first.options.next(first.options);
407
+ first.resolve(response2);
337
408
  return;
338
409
  }
339
- const context = value(group.context, item.matchedOptions);
410
+ const context = value(group.context, matchedOptions);
411
+ const signals = matchedOptions.map((options) => options.request.signal);
340
412
  const request = {
341
- ...item.options.request,
342
- signal: allAbortSignal(item.signals)
413
+ ...first.options.request,
414
+ signal: allAbortSignal(signals)
343
415
  };
344
- const response = await item.options.next({
345
- ...item.options,
416
+ const response = await first.options.next({
417
+ ...first.options,
346
418
  request,
347
419
  signal: request.signal,
348
420
  context
349
421
  });
350
- const replicatedResponses = replicateLazyResponse(response, item.resolves.length);
351
- for (const resolve of item.resolves) {
352
- resolve(replicatedResponses.pop());
353
- }
422
+ const replicatedResponses = replicateLazyResponse(response, signals);
423
+ activeCallers.forEach((caller, index) => caller.resolve(replicatedResponses[index]));
354
424
  } catch (error) {
355
- for (const reject of item.rejects) {
356
- reject(error);
425
+ for (const caller of activeCallers) {
426
+ caller.reject(error);
357
427
  }
358
428
  }
359
429
  }
@@ -370,35 +440,76 @@ function createRequestKey(path, request) {
370
440
  url: request.url
371
441
  });
372
442
  }
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;
443
+ function replicateLazyResponse(response, signals) {
444
+ let bodiesPromise;
445
+ return signals.map((signal, i) => ({
446
+ ...response,
447
+ resolveBody: (hint) => runWithSignal(signal, async () => {
448
+ bodiesPromise ??= response.resolveBody(hint).then((body) => {
388
449
  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() };
450
+ return replicateAsyncIterator(body, signals.length).map((replica, index) => closeAsyncIteratorOnAbort(replica, signals[index]));
451
+ }
452
+ if (body instanceof ReadableStream) {
453
+ return replicateReadableStream(body, signals.length).map((replica, index) => closeReadableStreamOnAbort(replica, signals[index]));
454
+ }
455
+ return signals.map(() => body);
456
+ });
457
+ return (await bodiesPromise)[i];
458
+ })
459
+ }));
460
+ }
461
+ function closeAsyncIteratorOnAbort(replica, signal) {
462
+ if (!signal) {
463
+ return replica;
464
+ }
465
+ const abort = () => replica.return().catch(() => {
466
+ });
467
+ if (signal.aborted) {
468
+ void abort();
469
+ } else {
470
+ signal.addEventListener("abort", abort, { once: true });
471
+ }
472
+ return wrapAsyncIterator(replica, {
473
+ mapResult(result) {
474
+ throwIfAborted(signal);
475
+ return result;
476
+ },
477
+ onFinish: () => signal.removeEventListener("abort", abort)
478
+ });
479
+ }
480
+ function closeReadableStreamOnAbort(replica, signal) {
481
+ if (!signal) {
482
+ return replica;
483
+ }
484
+ const reader = replica.getReader();
485
+ const abort = () => reader.cancel(signal.reason).catch(() => {
486
+ });
487
+ if (signal.aborted) {
488
+ void abort();
489
+ } else {
490
+ signal.addEventListener("abort", abort, { once: true });
491
+ }
492
+ return new ReadableStream({
493
+ async pull(controller) {
494
+ try {
495
+ const result = await reader.read();
496
+ throwIfAborted(signal);
497
+ if (result.done) {
498
+ signal.removeEventListener("abort", abort);
499
+ controller.close();
394
500
  } else {
395
- resolvedBody = { body };
501
+ controller.enqueue(result.value);
396
502
  }
397
- return resolvedBody.body;
503
+ } catch (error) {
504
+ signal.removeEventListener("abort", abort);
505
+ throw error;
398
506
  }
399
- });
400
- }
401
- return replicated;
507
+ },
508
+ async cancel(reason) {
509
+ signal.removeEventListener("abort", abort);
510
+ await reader.cancel(reason);
511
+ }
512
+ }, { highWaterMark: 0 });
402
513
  }
403
514
  function shouldDedupe(items) {
404
515
  return items.length >= 2;
@@ -554,6 +665,7 @@ class RequestCompressionLinkPlugin {
554
665
  }
555
666
  }
556
667
 
668
+ const MAX_CONTENT_ENCODINGS = 5;
557
669
  class ResponseCompressionLinkPlugin {
558
670
  name = "~response-compression";
559
671
  /**
@@ -596,6 +708,9 @@ class ResponseCompressionLinkPlugin {
596
708
  if (!(stream instanceof ReadableStream)) {
597
709
  return stream;
598
710
  }
711
+ if (encodings.length > MAX_CONTENT_ENCODINGS) {
712
+ throw new TypeError("Too many content encodings.");
713
+ }
599
714
  let decompressedStream = stream;
600
715
  for (let i = encodings.length - 1; i >= 0; i--) {
601
716
  decompressedStream = decompressedStream.pipeThrough(
@@ -662,6 +777,7 @@ class RetryLinkPlugin {
662
777
  let currentError = initialError;
663
778
  while (true) {
664
779
  const updatedCallOptions = { ...callOptions, lastEventId };
780
+ let retryDelayMs = 0;
665
781
  if (currentError) {
666
782
  if (attempt > maxAttempts) {
667
783
  throw currentError.error;
@@ -679,16 +795,11 @@ class RetryLinkPlugin {
679
795
  if (!shouldRetryBool) {
680
796
  throw currentError.error;
681
797
  }
798
+ retryDelayMs = await value(retryDelay, attemptOptions);
682
799
  callback = onRetry?.(attemptOptions);
683
800
  }
684
801
  try {
685
802
  if (currentError) {
686
- const retryDelayMs = await value(retryDelay, {
687
- ...updatedCallOptions,
688
- attempt,
689
- error: currentError.error,
690
- lastEventRetry
691
- });
692
803
  await sleep(retryDelayMs, { signal: updatedCallOptions.signal });
693
804
  attempt++;
694
805
  }
@@ -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';