@orpc/client 1.14.9 → 1.14.11

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 (38) hide show
  1. package/README.md +52 -68
  2. package/dist/adapters/fetch/index.d.mts +54 -35
  3. package/dist/adapters/fetch/index.d.ts +54 -35
  4. package/dist/adapters/fetch/index.mjs +74 -27
  5. package/dist/adapters/message-port/index.d.mts +25 -30
  6. package/dist/adapters/message-port/index.d.ts +25 -30
  7. package/dist/adapters/message-port/index.mjs +39 -30
  8. package/dist/adapters/standard/index.d.mts +59 -9
  9. package/dist/adapters/standard/index.d.ts +59 -9
  10. package/dist/adapters/standard/index.mjs +5 -5
  11. package/dist/adapters/websocket/index.d.mts +113 -21
  12. package/dist/adapters/websocket/index.d.ts +113 -21
  13. package/dist/adapters/websocket/index.mjs +95 -37
  14. package/dist/index.d.mts +75 -184
  15. package/dist/index.d.ts +75 -184
  16. package/dist/index.mjs +74 -63
  17. package/dist/plugins/index.d.mts +191 -122
  18. package/dist/plugins/index.d.ts +191 -122
  19. package/dist/plugins/index.mjs +627 -281
  20. package/dist/shared/client.8f4DNmdE.d.mts +96 -0
  21. package/dist/shared/client.BBZBQID8.d.mts +167 -0
  22. package/dist/shared/client.BBZBQID8.d.ts +167 -0
  23. package/dist/shared/client.BMKYqpdy.d.ts +96 -0
  24. package/dist/shared/client.BRJnOJ0R.mjs +174 -0
  25. package/dist/shared/client.BdItY5DT.d.mts +111 -0
  26. package/dist/shared/client.BdItY5DT.d.ts +111 -0
  27. package/dist/shared/client.Dnfj8jnT.mjs +92 -0
  28. package/dist/shared/client.DqYwRDUO.mjs +343 -0
  29. package/package.json +7 -7
  30. package/dist/shared/client.2jUAqzYU.d.ts +0 -45
  31. package/dist/shared/client.B3pNRBih.d.ts +0 -91
  32. package/dist/shared/client.BFAVy68H.d.mts +0 -91
  33. package/dist/shared/client.BLtwTQUg.mjs +0 -40
  34. package/dist/shared/client.CFQL4ewg.mjs +0 -174
  35. package/dist/shared/client.CpCa3si8.d.mts +0 -45
  36. package/dist/shared/client.D2nvAALa.mjs +0 -404
  37. package/dist/shared/client.i2uoJbEp.d.mts +0 -83
  38. package/dist/shared/client.i2uoJbEp.d.ts +0 -83
@@ -1,270 +1,626 @@
1
- import { isAsyncIteratorObject, defer, value, splitInHalf, toArray, stringifyJSON, overlayProxy, AsyncIteratorClass } from '@orpc/shared';
2
- import { toBatchRequest, parseBatchResponse, toBatchAbortSignal } from '@orpc/standard-server/batch';
3
- import { replicateStandardLazyResponse, getEventMeta, flattenHeader } from '@orpc/standard-server';
4
- import { C as COMMON_ORPC_ERROR_DEFS } from '../shared/client.CFQL4ewg.mjs';
1
+ import { toArray, value, splitInHalf, stringifyJSON, isAsyncIteratorObject, defer, loadBytes, allAbortSignal, replicateAsyncIterator, replicateReadableStream, isCompressibleContentType, override, AsyncIteratorClass, sleep, AbortError, anyAbortSignal } from '@orpc/shared';
2
+ import { parseStandardUrl, flattenStandardHeader, generateContentDisposition, getEventMeta } from '@standardserver/core';
3
+ import { ClientPeer, isServerPeerSendMessage, decodePeerMessage } from '@standardserver/peer';
4
+ import { toFetchHeaders, toStandardBody } from '@standardserver/fetch';
5
+ import { C as COMMON_ERROR_STATUS_MAP } from '../shared/client.Dnfj8jnT.mjs';
5
6
 
7
+ class BatchLinkPluginError extends TypeError {
8
+ }
6
9
  class BatchLinkPlugin {
10
+ name = "~batch";
7
11
  groups;
12
+ filter;
8
13
  maxSize;
14
+ mode;
9
15
  batchUrl;
10
16
  maxUrlLength;
11
17
  batchHeaders;
12
- mapRequestItem;
13
- exclude;
14
- mode;
15
- pending;
16
- order = 5e6;
18
+ mapSubrequest;
19
+ mapSubresponse;
20
+ queue = /* @__PURE__ */ new Map();
17
21
  constructor(options) {
18
22
  this.groups = options.groups;
19
- this.pending = /* @__PURE__ */ new Map();
23
+ this.filter = options.filter ?? (() => true);
20
24
  this.maxSize = options.maxSize ?? 10;
21
- this.maxUrlLength = options.maxUrlLength ?? 2083;
22
25
  this.mode = options.mode ?? "streaming";
23
- this.batchUrl = options.url ?? (([options2]) => `${options2.request.url.origin}${options2.request.url.pathname}/__batch__`);
24
- this.batchHeaders = options.headers ?? (([options2, ...rest]) => {
25
- const headers = {};
26
- for (const [key, value2] of Object.entries(options2.request.headers)) {
27
- if (rest.every((item) => item.request.headers[key] === value2)) {
28
- headers[key] = value2;
26
+ this.batchUrl = options.url ?? ((options2) => {
27
+ const [pathname] = parseStandardUrl(options2[0].request.url);
28
+ return `${pathname}/__batch__`;
29
+ });
30
+ this.maxUrlLength = options.maxUrlLength ?? 2083;
31
+ this.batchHeaders = options.headers ?? (async (options2) => {
32
+ const headersList = options2.map((o) => o.request.headers);
33
+ const commonHeaders = {};
34
+ for (const headers of headersList) {
35
+ for (const [key, value2] of Object.entries(headers)) {
36
+ if (headersList.every((h) => h[key] === value2)) {
37
+ commonHeaders[key] = value2;
38
+ }
29
39
  }
30
40
  }
31
- return headers;
41
+ return commonHeaders;
32
42
  });
33
- this.mapRequestItem = options.mapRequestItem ?? (({ request, batchHeaders }) => {
34
- const headers = {};
35
- for (const [key, value2] of Object.entries(request.headers)) {
36
- if (batchHeaders[key] !== value2) {
37
- headers[key] = value2;
43
+ this.mapSubrequest = options.mapSubrequest ?? (({ request }, { headers }) => {
44
+ const subHeaders = { ...request.headers };
45
+ for (const [key, value2] of Object.entries(headers)) {
46
+ if (subHeaders[key] === value2) {
47
+ subHeaders[key] = void 0;
38
48
  }
39
49
  }
40
50
  return {
41
- method: request.method,
42
- url: request.url,
43
- headers,
44
- body: request.body,
45
- signal: request.signal
51
+ ...request,
52
+ headers: subHeaders
46
53
  };
47
54
  });
48
- this.exclude = options.exclude ?? (() => false);
55
+ this.mapSubresponse = (subResponse, batchResponse) => {
56
+ return {
57
+ ...subResponse,
58
+ headers: {
59
+ ...batchResponse.headers,
60
+ // low-priority
61
+ ...subResponse.headers
62
+ }
63
+ };
64
+ };
49
65
  }
50
66
  init(options) {
51
- options.clientInterceptors ??= [];
52
- options.clientInterceptors.push((options2) => {
53
- if (options2.request.headers["x-orpc-batch"] !== "1") {
54
- return options2.next();
55
- }
56
- return options2.next({
57
- ...options2,
58
- request: {
59
- ...options2.request,
60
- headers: {
61
- ...options2.request.headers,
62
- "x-orpc-batch": void 0
63
- }
64
- }
65
- });
66
- });
67
- options.clientInterceptors.push((options2) => {
68
- if (this.exclude(options2) || options2.request.body instanceof Blob || options2.request.body instanceof FormData || isAsyncIteratorObject(options2.request.body) || options2.request.signal?.aborted) {
69
- return options2.next();
67
+ 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)) {
69
+ return interceptorOptions.next();
70
70
  }
71
- const group = this.groups.find((group2) => group2.condition(options2));
71
+ const group = this.groups.find((group2) => value(group2.condition, interceptorOptions));
72
72
  if (!group) {
73
- return options2.next();
73
+ return interceptorOptions.next();
74
74
  }
75
75
  return new Promise((resolve, reject) => {
76
- this.#enqueueRequest(group, options2, resolve, reject);
77
- defer(() => this.#processPendingBatches());
76
+ const queue = this.queue.get(group) ?? [];
77
+ if (!this.queue.has(group)) {
78
+ this.queue.set(group, queue);
79
+ }
80
+ queue.push([interceptorOptions, resolve, reject]);
81
+ defer(() => this.processPendingBatches());
78
82
  });
79
- });
80
- }
81
- #enqueueRequest(group, options, resolve, reject) {
82
- const items = this.pending.get(group);
83
- if (items) {
84
- items.push([options, resolve, reject]);
85
- } else {
86
- this.pending.set(group, [[options, resolve, reject]]);
87
- }
83
+ };
84
+ return {
85
+ ...options,
86
+ transportInterceptors: [...toArray(options.transportInterceptors), transportInterceptor]
87
+ };
88
88
  }
89
- async #processPendingBatches() {
90
- const pending = this.pending;
91
- this.pending = /* @__PURE__ */ new Map();
89
+ async processPendingBatches() {
90
+ const pending = new Map(this.queue);
91
+ this.queue.clear();
92
92
  for (const [group, items] of pending) {
93
93
  const getItems = items.filter(([options]) => options.request.method === "GET");
94
94
  const restItems = items.filter(([options]) => options.request.method !== "GET");
95
- this.#executeBatch("GET", group, getItems);
96
- this.#executeBatch("POST", group, restItems);
95
+ this.executeBatch("GET", group, getItems);
96
+ this.executeBatch("POST", group, restItems);
97
97
  }
98
98
  }
99
- async #executeBatch(method, group, groupItems) {
99
+ async executeBatch(method, group, groupItems) {
100
100
  if (!groupItems.length) {
101
101
  return;
102
102
  }
103
- const batchItems = groupItems;
104
- if (batchItems.length === 1) {
105
- batchItems[0][0].next().then(batchItems[0][1]).catch(batchItems[0][2]);
103
+ if (groupItems.length === 1) {
104
+ const [options, resolve, reject] = groupItems[0];
105
+ options.next().then(resolve).catch(reject);
106
106
  return;
107
107
  }
108
- try {
109
- const options = batchItems.map(([options2]) => options2);
110
- const maxSize = await value(this.maxSize, options);
111
- if (batchItems.length > maxSize) {
112
- const [first, second] = splitInHalf(batchItems);
113
- this.#executeBatch(method, group, first);
114
- this.#executeBatch(method, group, second);
115
- return;
108
+ const subOptionsList = groupItems.map(([options]) => options);
109
+ const maxSize = await value(this.maxSize, subOptionsList);
110
+ if (groupItems.length > maxSize) {
111
+ const [first, second] = splitInHalf(groupItems);
112
+ await Promise.all([
113
+ this.executeBatch(method, group, first),
114
+ this.executeBatch(method, group, second)
115
+ ]);
116
+ return;
117
+ }
118
+ const url = await value(this.batchUrl, subOptionsList);
119
+ const headers = await value(this.batchHeaders, subOptionsList);
120
+ const mode = value(this.mode, subOptionsList);
121
+ let suppressErrorFromCurrentBatch = false;
122
+ const controller = new AbortController();
123
+ const pendingMessages = [];
124
+ let batchResponse;
125
+ const peer = new ClientPeer(async (message) => {
126
+ pendingMessages.push(message);
127
+ if (message.kind === "cancel" && pendingMessages.filter((m) => m.kind === "cancel").length === groupItems.length) {
128
+ controller.abort();
116
129
  }
117
- const batchUrl = new URL(await value(this.batchUrl, options));
118
- const batchHeaders = await value(this.batchHeaders, options);
119
- const mappedItems = batchItems.map(([options2]) => this.mapRequestItem({ ...options2, batchUrl, batchHeaders }));
120
- const batchRequest = toBatchRequest({
121
- method,
122
- url: batchUrl,
123
- headers: batchHeaders,
124
- requests: mappedItems
130
+ if (message.kind === "request" && pendingMessages.filter((m) => m.kind === "request").length === groupItems.length) {
131
+ (async () => {
132
+ try {
133
+ const request = {
134
+ url,
135
+ method,
136
+ headers: { ...headers, "orpc-batch": mode },
137
+ signal: controller.signal
138
+ };
139
+ if (method === "GET") {
140
+ const [pathname, search, hash] = parseStandardUrl(url);
141
+ const dataParam = `data=${encodeURIComponent(stringifyJSON(pendingMessages))}`;
142
+ const newUrl = search ? `${pathname}${search}&${dataParam}${hash ?? ""}` : `${pathname}?${dataParam}${hash ?? ""}`;
143
+ const maxUrlLength = await value(this.maxUrlLength, subOptionsList);
144
+ if (newUrl.length > maxUrlLength) {
145
+ const [first, second] = splitInHalf(groupItems);
146
+ suppressErrorFromCurrentBatch = true;
147
+ await Promise.all([
148
+ this.executeBatch(method, group, first),
149
+ this.executeBatch(method, group, second),
150
+ peer.close()
151
+ ]);
152
+ return;
153
+ }
154
+ request.url = newUrl;
155
+ } else {
156
+ request.body = pendingMessages;
157
+ }
158
+ batchResponse = await groupItems[0][0].next({
159
+ ...subOptionsList[0],
160
+ context: value(group.context, subOptionsList),
161
+ path: value(group.path, subOptionsList) ?? [],
162
+ request,
163
+ signal: controller.signal
164
+ });
165
+ const body = await batchResponse.resolveBody();
166
+ if (Array.isArray(body) && body.every((v) => isServerPeerSendMessage(v))) {
167
+ for (const message2 of body) {
168
+ await peer.message(message2);
169
+ }
170
+ } else if (body instanceof Blob) {
171
+ await decodeLengthPrefixedBlob(body, peer);
172
+ } else if (body instanceof ReadableStream) {
173
+ await decodeLengthPrefixedStream(body, peer);
174
+ } else {
175
+ throw new BatchLinkPluginError("Invalid batch response format.");
176
+ }
177
+ await peer.close(new BatchLinkPluginError("Batch response is incomplete."));
178
+ } catch (error) {
179
+ await peer.close(error);
180
+ }
181
+ })();
182
+ }
183
+ });
184
+ groupItems.forEach(([subOptions, resolve, reject]) => {
185
+ peer.request(this.mapSubrequest(subOptions, { url, headers })).then((subResponse) => resolve(this.mapSubresponse(subResponse, batchResponse, subOptions))).catch((error) => {
186
+ if (!suppressErrorFromCurrentBatch) {
187
+ reject(error);
188
+ }
125
189
  });
126
- const maxUrlLength = await value(this.maxUrlLength, options);
127
- if (batchRequest.url.toString().length > maxUrlLength) {
128
- const [first, second] = splitInHalf(batchItems);
129
- this.#executeBatch(method, group, first);
130
- this.#executeBatch(method, group, second);
131
- return;
190
+ });
191
+ }
192
+ }
193
+ async function decodeLengthPrefixedBlob(blob, peer) {
194
+ const buffer = await loadBytes(blob);
195
+ let offset = 0;
196
+ while (offset < buffer.length) {
197
+ if (offset + 4 > buffer.length) {
198
+ throw new BatchLinkPluginError("Invalid batch response: incomplete length header.");
199
+ }
200
+ const view = new DataView(buffer.buffer, buffer.byteOffset + offset, 4);
201
+ const length = view.getUint32(0, false);
202
+ offset += 4;
203
+ if (offset + length > buffer.length) {
204
+ throw new BatchLinkPluginError("Invalid batch response: incomplete message.");
205
+ }
206
+ const messageBytes = buffer.subarray(offset, offset + length);
207
+ offset += length;
208
+ const result = decodePeerMessage(messageBytes);
209
+ if (!result.matched || !isServerPeerSendMessage(result.message)) {
210
+ throw new BatchLinkPluginError("Invalid batch response: invalid message.");
211
+ }
212
+ await peer.message(result.message);
213
+ }
214
+ }
215
+ async function decodeLengthPrefixedStream(stream, peer) {
216
+ const reader = stream.getReader();
217
+ let buffer = new Uint8Array(0);
218
+ try {
219
+ while (true) {
220
+ const { done, value: chunk } = await reader.read();
221
+ if (chunk) {
222
+ const newBuffer = new Uint8Array(buffer.length + chunk.length);
223
+ newBuffer.set(buffer);
224
+ newBuffer.set(chunk, buffer.length);
225
+ buffer = newBuffer;
132
226
  }
133
- const mode = value(this.mode, options);
134
- try {
135
- const lazyResponse = await options[0].next({
136
- request: { ...batchRequest, headers: { ...batchRequest.headers, "x-orpc-batch": mode } },
137
- signal: batchRequest.signal,
138
- context: group.context,
139
- input: group.input,
140
- path: toArray(group.path)
141
- });
142
- const parsed = parseBatchResponse({ ...lazyResponse, body: await lazyResponse.body() });
143
- for await (const item of parsed) {
144
- batchItems[item.index]?.[1]({ ...item, body: () => Promise.resolve(item.body) });
227
+ while (buffer.length >= 4) {
228
+ const view = new DataView(buffer.buffer, buffer.byteOffset, 4);
229
+ const length = view.getUint32(0, false);
230
+ if (length === 0) {
231
+ buffer = buffer.subarray(4);
232
+ continue;
145
233
  }
146
- } catch (err) {
147
- if (batchRequest.signal?.aborted && batchRequest.signal.reason === err) {
148
- for (const [{ signal }, , reject] of batchItems) {
149
- if (signal?.aborted) {
150
- reject(signal.reason);
151
- }
152
- }
234
+ if (buffer.length < 4 + length) {
235
+ break;
236
+ }
237
+ const messageBytes = buffer.subarray(4, 4 + length);
238
+ buffer = buffer.subarray(4 + length);
239
+ const result = decodePeerMessage(messageBytes);
240
+ if (!result.matched || !isServerPeerSendMessage(result.message)) {
241
+ throw new BatchLinkPluginError("Invalid batch response: invalid message.");
153
242
  }
154
- throw err;
243
+ await peer.message(result.message);
155
244
  }
156
- throw new Error("Something went wrong make batch response not contains enough responses. This can be a bug please report it.");
157
- } catch (error) {
158
- for (const [, , reject] of batchItems) {
159
- reject(error);
245
+ if (done) {
246
+ break;
160
247
  }
161
248
  }
249
+ } finally {
250
+ reader.releaseLock();
162
251
  }
163
252
  }
164
253
 
165
- class DedupeRequestsPlugin {
166
- #groups;
167
- #filter;
168
- order = 4e6;
169
- // make sure execute before batch plugin
170
- #queue = /* @__PURE__ */ new Map();
254
+ class DedupeLinkPlugin {
255
+ name = "~dedupe";
256
+ before = ["~batch"];
257
+ groups;
258
+ filter;
259
+ queue = /* @__PURE__ */ new Map();
171
260
  constructor(options) {
172
- this.#groups = options.groups;
173
- this.#filter = options.filter ?? (({ request }) => request.method === "GET");
261
+ this.groups = options.groups;
262
+ this.filter = options.filter ?? (({ request }) => request.method === "GET");
174
263
  }
175
264
  init(options) {
176
- options.clientInterceptors ??= [];
177
- options.clientInterceptors.push((options2) => {
178
- if (options2.request.body instanceof Blob || options2.request.body instanceof FormData || options2.request.body instanceof URLSearchParams || isAsyncIteratorObject(options2.request.body) || !this.#filter(options2)) {
179
- return options2.next();
265
+ const transportInterceptor = (interceptorOptions) => {
266
+ if (!canDedupeRequest(interceptorOptions.request) || !value(this.filter, interceptorOptions)) {
267
+ return interceptorOptions.next();
180
268
  }
181
- const group = this.#groups.find((group2) => group2.condition(options2));
269
+ const group = this.groups.find((group2) => value(group2.condition, interceptorOptions));
182
270
  if (!group) {
183
- return options2.next();
271
+ return interceptorOptions.next();
184
272
  }
185
273
  return new Promise((resolve, reject) => {
186
- this.#enqueue(group, options2, resolve, reject);
187
- defer(() => this.#dequeue());
274
+ this.enqueue(group, interceptorOptions, resolve, reject);
275
+ defer(() => {
276
+ this.processPendingRequests();
277
+ });
188
278
  });
189
- });
279
+ };
280
+ return {
281
+ ...options,
282
+ transportInterceptors: [...toArray(options.transportInterceptors), transportInterceptor]
283
+ };
190
284
  }
191
- #enqueue(group, options, resolve, reject) {
192
- let queue = this.#queue.get(group);
285
+ enqueue(group, options, resolve, reject) {
286
+ let queue = this.queue.get(group);
193
287
  if (!queue) {
194
- this.#queue.set(group, queue = []);
288
+ queue = [];
289
+ this.queue.set(group, queue);
195
290
  }
196
- const matched = queue.find((item) => {
197
- const requestString1 = stringifyJSON({
198
- body: item.options.request.body,
199
- headers: item.options.request.headers,
200
- method: item.options.request.method,
201
- url: item.options.request.url
202
- });
203
- const requestString2 = stringifyJSON({
204
- body: options.request.body,
205
- headers: options.request.headers,
206
- method: options.request.method,
207
- url: options.request.url
208
- });
209
- return requestString1 === requestString2;
210
- });
291
+ const requestKey = createRequestKey(options.path, options.request);
292
+ const matched = queue.find((item) => item.requestKey === requestKey);
211
293
  if (matched) {
294
+ matched.matchedOptions.push(options);
212
295
  matched.signals.push(options.request.signal);
213
296
  matched.resolves.push(resolve);
214
297
  matched.rejects.push(reject);
215
- } else {
216
- queue.push({
217
- options,
218
- signals: [options.request.signal],
219
- resolves: [resolve],
220
- rejects: [reject]
221
- });
298
+ return;
222
299
  }
300
+ queue.push({
301
+ requestKey,
302
+ options,
303
+ matchedOptions: [options],
304
+ signals: [options.request.signal],
305
+ resolves: [resolve],
306
+ rejects: [reject]
307
+ });
223
308
  }
224
- async #dequeue() {
225
- const promises = [];
226
- for (const [group, items] of this.#queue) {
227
- for (const { options, signals, resolves, rejects } of items) {
228
- promises.push(
229
- this.#execute(group, options, signals, resolves, rejects)
230
- );
309
+ async processPendingRequests() {
310
+ const pending = new Map(this.queue);
311
+ this.queue.clear();
312
+ const executions = [];
313
+ for (const [group, items] of pending) {
314
+ for (const item of items) {
315
+ executions.push(this.execute(group, item));
231
316
  }
232
317
  }
233
- this.#queue.clear();
234
- await Promise.all(promises);
318
+ await Promise.all(executions);
235
319
  }
236
- async #execute(group, options, signals, resolves, rejects) {
320
+ async execute(group, item) {
237
321
  try {
238
- const dedupedRequest = {
239
- ...options.request,
240
- signal: toBatchAbortSignal(signals)
322
+ if (!shouldDedupe(item.matchedOptions)) {
323
+ const response2 = await item.options.next(item.options);
324
+ item.resolves[0]?.(response2);
325
+ return;
326
+ }
327
+ const context = value(group.context, item.matchedOptions);
328
+ const request = {
329
+ ...item.options.request,
330
+ signal: allAbortSignal(item.signals)
241
331
  };
242
- const response = await options.next({
243
- ...options,
244
- request: dedupedRequest,
245
- signal: dedupedRequest.signal,
246
- context: group.context
332
+ const response = await item.options.next({
333
+ ...item.options,
334
+ request,
335
+ signal: request.signal,
336
+ context
247
337
  });
248
- const replicatedResponses = replicateStandardLazyResponse(response, resolves.length);
249
- for (const resolve of resolves) {
250
- resolve(replicatedResponses.shift());
338
+ const replicatedResponses = replicateLazyResponse(response, item.resolves.length);
339
+ for (const resolve of item.resolves) {
340
+ resolve(replicatedResponses.pop());
251
341
  }
252
342
  } catch (error) {
253
- for (const reject of rejects) {
343
+ for (const reject of item.rejects) {
254
344
  reject(error);
255
345
  }
256
346
  }
257
347
  }
258
348
  }
349
+ function canDedupeRequest(request) {
350
+ return !(request.body instanceof Blob || request.body instanceof FormData || request.body instanceof URLSearchParams || request.body instanceof ReadableStream || isAsyncIteratorObject(request.body) || request.signal?.aborted);
351
+ }
352
+ function createRequestKey(path, request) {
353
+ return stringifyJSON({
354
+ path,
355
+ body: request.body,
356
+ headers: request.headers,
357
+ method: request.method,
358
+ url: request.url
359
+ });
360
+ }
361
+ function replicateLazyResponse(response, count) {
362
+ const replicated = [];
363
+ let bodyPromise;
364
+ let replicatedAsyncIterators;
365
+ let replicatedReadableStream;
366
+ for (let i = 0; i < count; i++) {
367
+ let resolvedBody;
368
+ replicated.push({
369
+ ...response,
370
+ resolveBody: async (hint) => {
371
+ if (resolvedBody) {
372
+ return resolvedBody.body;
373
+ }
374
+ bodyPromise ??= response.resolveBody(hint);
375
+ const body = await bodyPromise;
376
+ if (isAsyncIteratorObject(body)) {
377
+ replicatedAsyncIterators ??= replicateAsyncIterator(body, count);
378
+ resolvedBody = { body: replicatedAsyncIterators.pop() };
379
+ } else if (body instanceof ReadableStream) {
380
+ replicatedReadableStream ??= replicateReadableStream(body, count);
381
+ resolvedBody = { body: replicatedReadableStream.pop() };
382
+ } else {
383
+ resolvedBody = { body };
384
+ }
385
+ return resolvedBody.body;
386
+ }
387
+ });
388
+ }
389
+ return replicated;
390
+ }
391
+ function shouldDedupe(items) {
392
+ return items.length >= 2;
393
+ }
394
+
395
+ const AVG_BYTES_PER_CHAR = 1.2;
396
+ class RequestCompressionLinkPlugin {
397
+ name = "~request-compression";
398
+ /**
399
+ * Compression should be done after batching, to compress the final request
400
+ */
401
+ after = ["~batch"];
402
+ encoding;
403
+ threshold;
404
+ constructor(options = {}) {
405
+ this.encoding = options.encoding ?? "gzip";
406
+ this.threshold = options.threshold ?? 1024;
407
+ }
408
+ init(options) {
409
+ const transportInterceptor = async ({ next, ...interceptorOptions }) => {
410
+ const request = interceptorOptions.request;
411
+ const contentEncoding = flattenStandardHeader(request.headers["content-encoding"])?.trim()?.toLowerCase();
412
+ if (contentEncoding !== void 0) {
413
+ return next();
414
+ }
415
+ if (request.body instanceof ReadableStream) {
416
+ const contentLength = Number(flattenStandardHeader(request.headers["content-length"]));
417
+ if ((!Number.isFinite(contentLength) || contentLength >= this.threshold) && isCompressibleContentType(flattenStandardHeader(request.headers["content-type"]))) {
418
+ const compressedStream = request.body.pipeThrough(new CompressionStream(this.encoding));
419
+ return next({
420
+ ...interceptorOptions,
421
+ request: {
422
+ ...interceptorOptions.request,
423
+ body: compressedStream,
424
+ headers: {
425
+ ...request.headers,
426
+ "standard-server": "octet-stream",
427
+ "content-length": [],
428
+ "content-encoding": this.encoding
429
+ }
430
+ }
431
+ });
432
+ }
433
+ } else if (request.body instanceof Blob) {
434
+ if ((!Number.isFinite(request.body.size) || request.body.size >= this.threshold) && isCompressibleContentType(request.body.type)) {
435
+ const compressedStream = request.body.stream().pipeThrough(new CompressionStream(this.encoding));
436
+ const contentDisposition = request.headers["content-disposition"] ?? generateContentDisposition(
437
+ request.body instanceof File ? request.body.name : "blob"
438
+ );
439
+ return next({
440
+ ...interceptorOptions,
441
+ request: {
442
+ ...interceptorOptions.request,
443
+ body: compressedStream,
444
+ headers: {
445
+ ...request.headers,
446
+ "standard-server": "file",
447
+ "content-type": request.body.type,
448
+ "content-length": [],
449
+ "content-disposition": contentDisposition,
450
+ "content-encoding": this.encoding
451
+ }
452
+ }
453
+ });
454
+ }
455
+ } else if (request.body instanceof FormData) {
456
+ const PART_OVERHEAD = 64;
457
+ let contentLength = 0;
458
+ for (const [key, value] of request.body) {
459
+ contentLength += PART_OVERHEAD + key.length;
460
+ if (value instanceof Blob) {
461
+ if (!Number.isFinite(value.size)) {
462
+ if (!isCompressibleContentType(value.type)) {
463
+ contentLength = -Infinity;
464
+ break;
465
+ }
466
+ contentLength = Infinity;
467
+ } else {
468
+ contentLength += isCompressibleContentType(value.type) ? value.size : -value.size;
469
+ }
470
+ } else {
471
+ contentLength += value.length * AVG_BYTES_PER_CHAR;
472
+ }
473
+ }
474
+ if (contentLength >= this.threshold) {
475
+ const res = new Response(request.body);
476
+ const compressedStream = res.body.pipeThrough(new CompressionStream(this.encoding));
477
+ return next({
478
+ ...interceptorOptions,
479
+ request: {
480
+ ...interceptorOptions.request,
481
+ body: compressedStream,
482
+ headers: {
483
+ ...request.headers,
484
+ "standard-server": [],
485
+ "content-type": res.headers.get("content-type"),
486
+ "content-length": [],
487
+ "content-encoding": this.encoding
488
+ }
489
+ }
490
+ });
491
+ }
492
+ } else if (request.body instanceof URLSearchParams) {
493
+ const string = request.body.toString();
494
+ if (string.length * AVG_BYTES_PER_CHAR >= this.threshold) {
495
+ const compressedStream = new Blob([string]).stream().pipeThrough(new CompressionStream(this.encoding));
496
+ return next({
497
+ ...interceptorOptions,
498
+ request: {
499
+ ...interceptorOptions.request,
500
+ body: compressedStream,
501
+ headers: {
502
+ ...request.headers,
503
+ "standard-server": [],
504
+ "content-type": "application/x-www-form-urlencoded",
505
+ "content-length": [],
506
+ "content-encoding": this.encoding
507
+ }
508
+ }
509
+ });
510
+ }
511
+ } else if (request.body !== void 0 && !isAsyncIteratorObject(request.body)) {
512
+ const string = stringifyJSON(request.body);
513
+ if (string.length * AVG_BYTES_PER_CHAR >= this.threshold) {
514
+ const compressedStream = new Blob([string]).stream().pipeThrough(new CompressionStream(this.encoding));
515
+ return next({
516
+ ...interceptorOptions,
517
+ request: {
518
+ ...interceptorOptions.request,
519
+ body: compressedStream,
520
+ headers: {
521
+ ...request.headers,
522
+ "standard-server": [],
523
+ "content-type": "application/json",
524
+ "content-length": [],
525
+ "content-encoding": this.encoding
526
+ }
527
+ }
528
+ });
529
+ }
530
+ }
531
+ return next();
532
+ };
533
+ return {
534
+ ...options,
535
+ transportInterceptors: [
536
+ ...toArray(options.transportInterceptors),
537
+ transportInterceptor
538
+ ]
539
+ };
540
+ }
541
+ }
259
542
 
260
- class ClientRetryPluginInvalidEventIteratorRetryResponse extends Error {
543
+ class ResponseCompressionLinkPlugin {
544
+ name = "~response-compression";
545
+ /**
546
+ * Decompression should wrap the final batch response instead of sub-responses.
547
+ */
548
+ after = ["~batch"];
549
+ encodings;
550
+ constructor(options = {}) {
551
+ this.encodings = options.encodings ?? ["gzip", "deflate"];
552
+ }
553
+ init(options) {
554
+ const acceptEncodingHeader = this.encodings.join(", ");
555
+ const transportInterceptor = async ({ next, ...interceptorOptions }) => {
556
+ const response = await next({
557
+ ...interceptorOptions,
558
+ request: {
559
+ ...interceptorOptions.request,
560
+ headers: {
561
+ ...interceptorOptions.request.headers,
562
+ "accept-encoding": acceptEncodingHeader
563
+ }
564
+ }
565
+ });
566
+ const encodings = parseContentEncodings(
567
+ flattenStandardHeader(response.headers["content-encoding"])
568
+ );
569
+ if (encodings.length === 0 || !encodings.every(isSupportedEncoding)) {
570
+ return response;
571
+ }
572
+ const decompressedHeaders = {
573
+ ...response.headers,
574
+ "content-length": void 0,
575
+ "content-encoding": void 0
576
+ };
577
+ return {
578
+ ...response,
579
+ headers: decompressedHeaders,
580
+ async resolveBody(hint) {
581
+ const stream = await response.resolveBody("octet-stream");
582
+ if (!(stream instanceof ReadableStream)) {
583
+ return stream;
584
+ }
585
+ let decompressedStream = stream;
586
+ for (let i = encodings.length - 1; i >= 0; i--) {
587
+ decompressedStream = decompressedStream.pipeThrough(
588
+ new DecompressionStream(encodings[i])
589
+ );
590
+ }
591
+ const fetchResponse = new Response(decompressedStream, {
592
+ headers: toFetchHeaders(decompressedHeaders)
593
+ });
594
+ return toStandardBody(fetchResponse, { hint });
595
+ }
596
+ };
597
+ };
598
+ return {
599
+ ...options,
600
+ transportInterceptors: [
601
+ ...toArray(options.transportInterceptors),
602
+ transportInterceptor
603
+ ]
604
+ };
605
+ }
261
606
  }
262
- class ClientRetryPlugin {
607
+ const SUPPORTED_ENCODINGS = ["gzip", "deflate", "deflate-raw"];
608
+ function isSupportedEncoding(encoding) {
609
+ return SUPPORTED_ENCODINGS.includes(encoding);
610
+ }
611
+ function parseContentEncodings(header) {
612
+ if (header === void 0) {
613
+ return [];
614
+ }
615
+ return header.split(",").map((part) => part.trim().toLowerCase());
616
+ }
617
+
618
+ class RetryLinkPlugin {
263
619
  defaultRetry;
264
620
  defaultRetryDelay;
265
621
  defaultShouldRetry;
266
622
  defaultOnRetry;
267
- order = 18e5;
623
+ name = "~retry";
268
624
  constructor(options = {}) {
269
625
  this.defaultRetry = options.default?.retry ?? 0;
270
626
  this.defaultRetryDelay = options.default?.retryDelay ?? ((o) => o.lastEventRetry ?? 2e3);
@@ -272,33 +628,33 @@ class ClientRetryPlugin {
272
628
  this.defaultOnRetry = options.default?.onRetry;
273
629
  }
274
630
  init(options) {
275
- options.interceptors ??= [];
276
- options.interceptors.push(async (interceptorOptions) => {
631
+ const interceptor = async (interceptorOptions) => {
632
+ const { next, ...callOptions } = interceptorOptions;
277
633
  const maxAttempts = await value(
278
- interceptorOptions.context.retry ?? this.defaultRetry,
279
- interceptorOptions
634
+ callOptions.context.retry ?? this.defaultRetry,
635
+ callOptions
280
636
  );
281
- const retryDelay = interceptorOptions.context.retryDelay ?? this.defaultRetryDelay;
282
- const shouldRetry = interceptorOptions.context.shouldRetry ?? this.defaultShouldRetry;
283
- const onRetry = interceptorOptions.context.onRetry ?? this.defaultOnRetry;
637
+ const retryDelay = callOptions.context.retryDelay ?? this.defaultRetryDelay;
638
+ const shouldRetry = callOptions.context.shouldRetry ?? this.defaultShouldRetry;
639
+ const onRetry = callOptions.context.onRetry ?? this.defaultOnRetry;
284
640
  if (maxAttempts <= 0) {
285
- return interceptorOptions.next();
641
+ return next(callOptions);
286
642
  }
287
- let lastEventId = interceptorOptions.lastEventId;
643
+ let lastEventId = callOptions.lastEventId;
288
644
  let lastEventRetry;
289
645
  let callback;
290
- let attemptIndex = 0;
291
- const next = async (initialError) => {
646
+ let attempt = 1;
647
+ const callNext = async (initialError) => {
292
648
  let currentError = initialError;
293
649
  while (true) {
294
- const updatedInterceptorOptions = { ...interceptorOptions, lastEventId };
650
+ const updatedCallOptions = { ...callOptions, lastEventId };
295
651
  if (currentError) {
296
- if (attemptIndex >= maxAttempts) {
652
+ if (attempt > maxAttempts) {
297
653
  throw currentError.error;
298
654
  }
299
655
  const attemptOptions = {
300
- ...updatedInterceptorOptions,
301
- attemptIndex,
656
+ ...updatedCallOptions,
657
+ attempt,
302
658
  error: currentError.error,
303
659
  lastEventRetry
304
660
  };
@@ -310,16 +666,23 @@ class ClientRetryPlugin {
310
666
  throw currentError.error;
311
667
  }
312
668
  callback = onRetry?.(attemptOptions);
313
- const retryDelayMs = await value(retryDelay, attemptOptions);
314
- await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
315
- attemptIndex++;
316
669
  }
317
670
  try {
671
+ if (currentError) {
672
+ const retryDelayMs = await value(retryDelay, {
673
+ ...updatedCallOptions,
674
+ attempt,
675
+ error: currentError.error,
676
+ lastEventRetry
677
+ });
678
+ await sleep(retryDelayMs, { signal: updatedCallOptions.signal });
679
+ attempt++;
680
+ }
318
681
  currentError = void 0;
319
- return await interceptorOptions.next(updatedInterceptorOptions);
682
+ return await next(updatedCallOptions);
320
683
  } catch (error) {
321
684
  currentError = { error };
322
- if (updatedInterceptorOptions.signal?.aborted) {
685
+ if (updatedCallOptions.signal?.aborted) {
323
686
  throw error;
324
687
  }
325
688
  } finally {
@@ -328,13 +691,13 @@ class ClientRetryPlugin {
328
691
  }
329
692
  }
330
693
  };
331
- const output = await next();
694
+ const output = await callNext();
332
695
  if (!isAsyncIteratorObject(output)) {
333
696
  return output;
334
697
  }
335
698
  let current = output;
336
699
  let isIteratorAborted = false;
337
- return overlayProxy(() => current, new AsyncIteratorClass(
700
+ return override(() => current, new AsyncIteratorClass(
338
701
  async () => {
339
702
  while (true) {
340
703
  try {
@@ -347,13 +710,13 @@ class ClientRetryPlugin {
347
710
  const meta = getEventMeta(error);
348
711
  lastEventId = meta?.id ?? lastEventId;
349
712
  lastEventRetry = meta?.retry ?? lastEventRetry;
350
- const maybeEventIterator = await next({ error });
351
- if (!isAsyncIteratorObject(maybeEventIterator)) {
352
- throw new ClientRetryPluginInvalidEventIteratorRetryResponse(
353
- "RetryPlugin: Expected an Event Iterator, got a non-Event Iterator"
713
+ const asyncIteratorObject = await callNext({ error });
714
+ if (!isAsyncIteratorObject(asyncIteratorObject)) {
715
+ throw new TypeError(
716
+ "RetryLinkPlugin: Expected an AsyncIteratorObject, got a different type."
354
717
  );
355
718
  }
356
- current = maybeEventIterator;
719
+ current = asyncIteratorObject;
357
720
  if (isIteratorAborted) {
358
721
  await current.return?.();
359
722
  throw error;
@@ -361,30 +724,30 @@ class ClientRetryPlugin {
361
724
  }
362
725
  }
363
726
  },
364
- async (reason) => {
727
+ async ({ kind }) => {
365
728
  isIteratorAborted = true;
366
- if (reason !== "next") {
729
+ if (kind === "cancelled") {
367
730
  await current.return?.();
368
731
  }
369
732
  }
370
733
  ));
371
- });
734
+ };
735
+ return { ...options, interceptors: [interceptor, ...toArray(options.interceptors)] };
372
736
  }
373
737
  }
374
738
 
375
- class RetryAfterPlugin {
739
+ class RetryAfterLinkPlugin {
376
740
  condition;
377
741
  maxAttempts;
378
742
  timeout;
379
- order = 19e5;
743
+ name = "~retry-after";
380
744
  constructor(options = {}) {
381
- this.condition = options.condition ?? ((response) => response.status === COMMON_ORPC_ERROR_DEFS.TOO_MANY_REQUESTS.status || response.status === COMMON_ORPC_ERROR_DEFS.SERVICE_UNAVAILABLE.status);
745
+ this.condition = options.condition ?? ((response) => response.status === COMMON_ERROR_STATUS_MAP.TOO_MANY_REQUESTS || response.status === COMMON_ERROR_STATUS_MAP.SERVICE_UNAVAILABLE);
382
746
  this.maxAttempts = options.maxAttempts ?? 3;
383
747
  this.timeout = options.timeout ?? 5 * 60 * 1e3;
384
748
  }
385
749
  init(options) {
386
- options.clientInterceptors ??= [];
387
- options.clientInterceptors.push(async (interceptorOptions) => {
750
+ const interceptor = async (interceptorOptions) => {
388
751
  const startTime = Date.now();
389
752
  let attemptCount = 0;
390
753
  while (true) {
@@ -393,8 +756,8 @@ class RetryAfterPlugin {
393
756
  if (!value(this.condition, response, interceptorOptions)) {
394
757
  return response;
395
758
  }
396
- const retryAfterHeader = flattenHeader(response.headers["retry-after"]);
397
- const retryAfterMs = this.parseRetryAfterHeader(retryAfterHeader);
759
+ const retryAfterHeader = flattenStandardHeader(response.headers["retry-after"]);
760
+ const retryAfterMs = parseRetryAfterHeader(retryAfterHeader);
398
761
  if (retryAfterMs === void 0) {
399
762
  return response;
400
763
  }
@@ -406,80 +769,63 @@ class RetryAfterPlugin {
406
769
  if (elapsedTime + retryAfterMs > timeoutMs) {
407
770
  return response;
408
771
  }
409
- await this.delayExecution(retryAfterMs, interceptorOptions.signal);
772
+ try {
773
+ await sleep(retryAfterMs, { signal: interceptorOptions.signal });
774
+ } catch {
775
+ }
410
776
  if (interceptorOptions.signal?.aborted) {
411
777
  return response;
412
778
  }
413
779
  }
414
- });
780
+ };
781
+ return { ...options, transportInterceptors: [interceptor, ...toArray(options.transportInterceptors)] };
415
782
  }
416
- parseRetryAfterHeader(value2) {
417
- value2 = value2?.trim();
418
- if (!value2) {
419
- return void 0;
420
- }
421
- const seconds = Number(value2);
422
- if (Number.isFinite(seconds)) {
423
- return Math.max(0, seconds * 1e3);
424
- }
425
- const retryDate = Date.parse(value2);
426
- if (!Number.isNaN(retryDate)) {
427
- return Math.max(0, retryDate - Date.now());
428
- }
783
+ }
784
+ function parseRetryAfterHeader(value2) {
785
+ value2 = value2?.trim();
786
+ if (!value2) {
429
787
  return void 0;
430
788
  }
431
- delayExecution(ms, signal) {
432
- return new Promise((resolve) => {
433
- if (signal?.aborted) {
434
- resolve();
435
- return;
436
- }
437
- let timeout;
438
- const onAbort = () => {
439
- clearTimeout(timeout);
440
- timeout = void 0;
441
- resolve();
442
- };
443
- signal?.addEventListener("abort", onAbort, { once: true });
444
- timeout = setTimeout(() => {
445
- signal?.removeEventListener("abort", onAbort);
446
- resolve();
447
- }, ms);
448
- });
789
+ const seconds = Number(value2);
790
+ if (Number.isFinite(seconds)) {
791
+ return Math.max(0, seconds * 1e3);
792
+ }
793
+ const retryDate = Date.parse(value2);
794
+ if (!Number.isNaN(retryDate)) {
795
+ return Math.max(0, retryDate - Date.now());
449
796
  }
797
+ return void 0;
450
798
  }
451
799
 
452
- class SimpleCsrfProtectionLinkPlugin {
453
- headerName;
454
- headerValue;
455
- exclude;
456
- constructor(options = {}) {
457
- this.headerName = options.headerName ?? "x-csrf-token";
458
- this.headerValue = options.headerValue ?? "orpc";
459
- this.exclude = options.exclude ?? false;
800
+ class TimeoutLinkPlugin {
801
+ timeout;
802
+ name = "~timeout";
803
+ /**
804
+ * Should abort if the total retry time exceeds the configured timeout
805
+ */
806
+ after = ["~retry"];
807
+ constructor(options) {
808
+ this.timeout = options.timeout;
460
809
  }
461
- order = 8e6;
462
810
  init(options) {
463
- options.clientInterceptors ??= [];
464
- options.clientInterceptors.push(async (options2) => {
465
- const excluded = await value(this.exclude, options2);
466
- if (excluded) {
467
- return options2.next();
811
+ const interceptor = async (interceptorOptions) => {
812
+ const timeoutMs = value(this.timeout, interceptorOptions);
813
+ if (timeoutMs === null || timeoutMs === void 0) {
814
+ return interceptorOptions.next();
468
815
  }
469
- const headerName = await value(this.headerName, options2);
470
- const headerValue = await value(this.headerValue, options2);
471
- return options2.next({
472
- ...options2,
473
- request: {
474
- ...options2.request,
475
- headers: {
476
- ...options2.request.headers,
477
- [headerName]: headerValue
478
- }
479
- }
480
- });
481
- });
816
+ const controller = new AbortController();
817
+ const timeoutId = setTimeout(() => {
818
+ controller.abort(new AbortError(`Request timed out after ${timeoutMs}ms`));
819
+ }, timeoutMs);
820
+ const signal = anyAbortSignal([interceptorOptions.signal, controller.signal]);
821
+ try {
822
+ return await interceptorOptions.next({ ...interceptorOptions, signal });
823
+ } finally {
824
+ clearTimeout(timeoutId);
825
+ }
826
+ };
827
+ return { ...options, interceptors: [interceptor, ...toArray(options.interceptors)] };
482
828
  }
483
829
  }
484
830
 
485
- export { BatchLinkPlugin, ClientRetryPlugin, ClientRetryPluginInvalidEventIteratorRetryResponse, DedupeRequestsPlugin, RetryAfterPlugin, SimpleCsrfProtectionLinkPlugin };
831
+ export { BatchLinkPlugin, BatchLinkPluginError, DedupeLinkPlugin, RequestCompressionLinkPlugin, ResponseCompressionLinkPlugin, RetryAfterLinkPlugin, RetryLinkPlugin, TimeoutLinkPlugin };