@orpc/client 1.14.6 → 2.0.0-beta.10

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 +78 -107
  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 +49 -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 +33 -29
  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 +84 -159
  15. package/dist/index.d.ts +84 -159
  16. package/dist/index.mjs +76 -34
  17. package/dist/plugins/index.d.mts +125 -132
  18. package/dist/plugins/index.d.ts +125 -132
  19. package/dist/plugins/index.mjs +373 -284
  20. package/dist/shared/client.8ug8I-zu.d.mts +167 -0
  21. package/dist/shared/client.8ug8I-zu.d.ts +167 -0
  22. package/dist/shared/client.BN52ep3E.mjs +174 -0
  23. package/dist/shared/client.BdItY5DT.d.mts +111 -0
  24. package/dist/shared/client.BdItY5DT.d.ts +111 -0
  25. package/dist/shared/client.CPF3hX6O.d.ts +96 -0
  26. package/dist/shared/client.Cby_-GGh.d.mts +96 -0
  27. package/dist/shared/client.D3TIIok6.mjs +343 -0
  28. package/dist/shared/client.Dnfj8jnT.mjs +92 -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.CpCa3si8.d.mts +0 -45
  35. package/dist/shared/client.D9eWXdBV.mjs +0 -174
  36. package/dist/shared/client.DLhbktiD.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,400 @@
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.D9eWXdBV.mjs';
1
+ import { toArray, value, splitInHalf, stringifyJSON, isAsyncIteratorObject, defer, loadBytes, allAbortSignal, replicateAsyncIterator, replicateReadableStream, override, AsyncIteratorClass, sleep } from '@orpc/shared';
2
+ import { parseStandardUrl, getEventMeta, flattenStandardHeader } from '@standardserver/core';
3
+ import { ClientPeer, isServerPeerSendMessage, decodePeerMessage } from '@standardserver/peer';
4
+ import { C as COMMON_ERROR_STATUS_MAP } from '../shared/client.Dnfj8jnT.mjs';
5
5
 
6
+ class BatchLinkPluginError extends TypeError {
7
+ }
6
8
  class BatchLinkPlugin {
9
+ name = "~batch";
7
10
  groups;
11
+ filter;
8
12
  maxSize;
13
+ mode;
9
14
  batchUrl;
10
15
  maxUrlLength;
11
16
  batchHeaders;
12
- mapRequestItem;
13
- exclude;
14
- mode;
15
- pending;
16
- order = 5e6;
17
+ mapSubrequest;
18
+ mapSubresponse;
19
+ queue = /* @__PURE__ */ new Map();
17
20
  constructor(options) {
18
21
  this.groups = options.groups;
19
- this.pending = /* @__PURE__ */ new Map();
22
+ this.filter = options.filter ?? (() => true);
20
23
  this.maxSize = options.maxSize ?? 10;
21
- this.maxUrlLength = options.maxUrlLength ?? 2083;
22
24
  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;
25
+ this.batchUrl = options.url ?? ((options2) => {
26
+ const [pathname] = parseStandardUrl(options2[0].request.url);
27
+ return `${pathname}/__batch__`;
28
+ });
29
+ this.maxUrlLength = options.maxUrlLength ?? 2083;
30
+ this.batchHeaders = options.headers ?? (async (options2) => {
31
+ const headersList = options2.map((o) => o.request.headers);
32
+ const commonHeaders = {};
33
+ for (const headers of headersList) {
34
+ for (const [key, value2] of Object.entries(headers)) {
35
+ if (headersList.every((h) => h[key] === value2)) {
36
+ commonHeaders[key] = value2;
37
+ }
29
38
  }
30
39
  }
31
- return headers;
40
+ return commonHeaders;
32
41
  });
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;
42
+ this.mapSubrequest = options.mapSubrequest ?? (({ request }, { headers }) => {
43
+ const subHeaders = { ...request.headers };
44
+ for (const [key, value2] of Object.entries(headers)) {
45
+ if (subHeaders[key] === value2) {
46
+ subHeaders[key] = void 0;
38
47
  }
39
48
  }
40
49
  return {
41
- method: request.method,
42
- url: request.url,
43
- headers,
44
- body: request.body,
45
- signal: request.signal
50
+ ...request,
51
+ headers: subHeaders
46
52
  };
47
53
  });
48
- this.exclude = options.exclude ?? (() => false);
54
+ this.mapSubresponse = (subResponse, batchResponse) => {
55
+ return {
56
+ ...subResponse,
57
+ headers: {
58
+ ...batchResponse.headers,
59
+ // low-priority
60
+ ...subResponse.headers
61
+ }
62
+ };
63
+ };
49
64
  }
50
65
  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();
66
+ const transportInterceptor = async (interceptorOptions) => {
67
+ if (interceptorOptions.request.body instanceof Blob || interceptorOptions.request.body instanceof ReadableStream || isAsyncIteratorObject(interceptorOptions.request.body) || interceptorOptions.request.signal?.aborted || !value(this.filter, interceptorOptions)) {
68
+ return interceptorOptions.next();
70
69
  }
71
- const group = this.groups.find((group2) => group2.condition(options2));
70
+ const group = this.groups.find((group2) => value(group2.condition, interceptorOptions));
72
71
  if (!group) {
73
- return options2.next();
72
+ return interceptorOptions.next();
74
73
  }
75
74
  return new Promise((resolve, reject) => {
76
- this.#enqueueRequest(group, options2, resolve, reject);
77
- defer(() => this.#processPendingBatches());
75
+ const queue = this.queue.get(group) ?? [];
76
+ if (!this.queue.has(group)) {
77
+ this.queue.set(group, queue);
78
+ }
79
+ queue.push([interceptorOptions, resolve, reject]);
80
+ defer(() => this.processPendingBatches());
78
81
  });
79
- });
82
+ };
83
+ return {
84
+ ...options,
85
+ transportInterceptors: [...toArray(options.transportInterceptors), transportInterceptor]
86
+ };
80
87
  }
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
- }
88
- }
89
- async #processPendingBatches() {
90
- const pending = this.pending;
91
- this.pending = /* @__PURE__ */ new Map();
88
+ async processPendingBatches() {
89
+ const pending = new Map(this.queue);
90
+ this.queue.clear();
92
91
  for (const [group, items] of pending) {
93
92
  const getItems = items.filter(([options]) => options.request.method === "GET");
94
93
  const restItems = items.filter(([options]) => options.request.method !== "GET");
95
- this.#executeBatch("GET", group, getItems);
96
- this.#executeBatch("POST", group, restItems);
94
+ this.executeBatch("GET", group, getItems);
95
+ this.executeBatch("POST", group, restItems);
97
96
  }
98
97
  }
99
- async #executeBatch(method, group, groupItems) {
98
+ async executeBatch(method, group, groupItems) {
100
99
  if (!groupItems.length) {
101
100
  return;
102
101
  }
103
- const batchItems = groupItems;
104
- if (batchItems.length === 1) {
105
- batchItems[0][0].next().then(batchItems[0][1]).catch(batchItems[0][2]);
102
+ if (groupItems.length === 1) {
103
+ const [options, resolve, reject] = groupItems[0];
104
+ options.next().then(resolve).catch(reject);
106
105
  return;
107
106
  }
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;
107
+ const subOptionsList = groupItems.map(([options]) => options);
108
+ const maxSize = await value(this.maxSize, subOptionsList);
109
+ if (groupItems.length > maxSize) {
110
+ const [first, second] = splitInHalf(groupItems);
111
+ await Promise.all([
112
+ this.executeBatch(method, group, first),
113
+ this.executeBatch(method, group, second)
114
+ ]);
115
+ return;
116
+ }
117
+ const url = await value(this.batchUrl, subOptionsList);
118
+ const headers = await value(this.batchHeaders, subOptionsList);
119
+ const mode = value(this.mode, subOptionsList);
120
+ let suppressErrorFromCurrentBatch = false;
121
+ const controller = new AbortController();
122
+ const pendingMessages = [];
123
+ let batchResponse;
124
+ const peer = new ClientPeer(async (message) => {
125
+ pendingMessages.push(message);
126
+ if (message.kind === "cancel" && pendingMessages.filter((m) => m.kind === "cancel").length === groupItems.length) {
127
+ controller.abort();
116
128
  }
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
129
+ if (message.kind === "request" && pendingMessages.filter((m) => m.kind === "request").length === groupItems.length) {
130
+ (async () => {
131
+ try {
132
+ const request = {
133
+ url,
134
+ method,
135
+ headers: { ...headers, "orpc-batch": mode },
136
+ signal: controller.signal
137
+ };
138
+ if (method === "GET") {
139
+ const [pathname, search, hash] = parseStandardUrl(url);
140
+ const dataParam = `data=${encodeURIComponent(stringifyJSON(pendingMessages))}`;
141
+ const newUrl = search ? `${pathname}${search}&${dataParam}${hash ?? ""}` : `${pathname}?${dataParam}${hash ?? ""}`;
142
+ const maxUrlLength = await value(this.maxUrlLength, subOptionsList);
143
+ if (newUrl.length > maxUrlLength) {
144
+ const [first, second] = splitInHalf(groupItems);
145
+ suppressErrorFromCurrentBatch = true;
146
+ await Promise.all([
147
+ this.executeBatch(method, group, first),
148
+ this.executeBatch(method, group, second),
149
+ peer.close()
150
+ ]);
151
+ return;
152
+ }
153
+ request.url = newUrl;
154
+ } else {
155
+ request.body = pendingMessages;
156
+ }
157
+ batchResponse = await groupItems[0][0].next({
158
+ ...subOptionsList[0],
159
+ context: value(group.context, subOptionsList),
160
+ path: value(group.path, subOptionsList) ?? [],
161
+ request,
162
+ signal: controller.signal
163
+ });
164
+ const body = await batchResponse.resolveBody();
165
+ if (Array.isArray(body) && body.every((v) => isServerPeerSendMessage(v))) {
166
+ for (const message2 of body) {
167
+ await peer.message(message2);
168
+ }
169
+ } else if (body instanceof Blob) {
170
+ await decodeLengthPrefixedBlob(body, peer);
171
+ } else if (body instanceof ReadableStream) {
172
+ await decodeLengthPrefixedStream(body, peer);
173
+ } else {
174
+ throw new BatchLinkPluginError("Invalid batch response format.");
175
+ }
176
+ await peer.close(new BatchLinkPluginError("Batch response is incomplete."));
177
+ } catch (error) {
178
+ await peer.close(error);
179
+ }
180
+ })();
181
+ }
182
+ });
183
+ groupItems.forEach(([subOptions, resolve, reject]) => {
184
+ peer.request(this.mapSubrequest(subOptions, { url, headers })).then((subResponse) => resolve(this.mapSubresponse(subResponse, batchResponse, subOptions))).catch((error) => {
185
+ if (!suppressErrorFromCurrentBatch) {
186
+ reject(error);
187
+ }
125
188
  });
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;
189
+ });
190
+ }
191
+ }
192
+ async function decodeLengthPrefixedBlob(blob, peer) {
193
+ const buffer = await loadBytes(blob);
194
+ let offset = 0;
195
+ while (offset < buffer.length) {
196
+ if (offset + 4 > buffer.length) {
197
+ throw new BatchLinkPluginError("Invalid batch response: incomplete length header.");
198
+ }
199
+ const view = new DataView(buffer.buffer, buffer.byteOffset + offset, 4);
200
+ const length = view.getUint32(0, false);
201
+ offset += 4;
202
+ if (offset + length > buffer.length) {
203
+ throw new BatchLinkPluginError("Invalid batch response: incomplete message.");
204
+ }
205
+ const messageBytes = buffer.subarray(offset, offset + length);
206
+ offset += length;
207
+ const result = decodePeerMessage(messageBytes);
208
+ if (!result.matched || !isServerPeerSendMessage(result.message)) {
209
+ throw new BatchLinkPluginError("Invalid batch response: invalid message.");
210
+ }
211
+ await peer.message(result.message);
212
+ }
213
+ }
214
+ async function decodeLengthPrefixedStream(stream, peer) {
215
+ const reader = stream.getReader();
216
+ let buffer = new Uint8Array(0);
217
+ try {
218
+ while (true) {
219
+ const { done, value: chunk } = await reader.read();
220
+ if (chunk) {
221
+ const newBuffer = new Uint8Array(buffer.length + chunk.length);
222
+ newBuffer.set(buffer);
223
+ newBuffer.set(chunk, buffer.length);
224
+ buffer = newBuffer;
132
225
  }
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) });
226
+ while (buffer.length >= 4) {
227
+ const view = new DataView(buffer.buffer, buffer.byteOffset, 4);
228
+ const length = view.getUint32(0, false);
229
+ if (buffer.length < 4 + length) {
230
+ break;
145
231
  }
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
- }
232
+ const messageBytes = buffer.subarray(4, 4 + length);
233
+ buffer = buffer.subarray(4 + length);
234
+ const result = decodePeerMessage(messageBytes);
235
+ if (!result.matched || !isServerPeerSendMessage(result.message)) {
236
+ throw new BatchLinkPluginError("Invalid batch response: invalid message.");
153
237
  }
154
- throw err;
238
+ await peer.message(result.message);
155
239
  }
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);
240
+ if (done) {
241
+ break;
160
242
  }
161
243
  }
244
+ } finally {
245
+ reader.releaseLock();
162
246
  }
163
247
  }
164
248
 
165
- class DedupeRequestsPlugin {
166
- #groups;
167
- #filter;
168
- order = 4e6;
169
- // make sure execute before batch plugin
170
- #queue = /* @__PURE__ */ new Map();
249
+ class DedupeLinkPlugin {
250
+ name = "~dedupe";
251
+ before = ["~batch"];
252
+ groups;
253
+ filter;
254
+ queue = /* @__PURE__ */ new Map();
171
255
  constructor(options) {
172
- this.#groups = options.groups;
173
- this.#filter = options.filter ?? (({ request }) => request.method === "GET");
256
+ this.groups = options.groups;
257
+ this.filter = options.filter ?? (({ request }) => request.method === "GET");
174
258
  }
175
259
  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();
260
+ const transportInterceptor = (interceptorOptions) => {
261
+ if (!canDedupeRequest(interceptorOptions.request) || !value(this.filter, interceptorOptions)) {
262
+ return interceptorOptions.next();
180
263
  }
181
- const group = this.#groups.find((group2) => group2.condition(options2));
264
+ const group = this.groups.find((group2) => value(group2.condition, interceptorOptions));
182
265
  if (!group) {
183
- return options2.next();
266
+ return interceptorOptions.next();
184
267
  }
185
268
  return new Promise((resolve, reject) => {
186
- this.#enqueue(group, options2, resolve, reject);
187
- defer(() => this.#dequeue());
269
+ this.enqueue(group, interceptorOptions, resolve, reject);
270
+ defer(() => {
271
+ this.processPendingRequests();
272
+ });
188
273
  });
189
- });
274
+ };
275
+ return {
276
+ ...options,
277
+ transportInterceptors: [...toArray(options.transportInterceptors), transportInterceptor]
278
+ };
190
279
  }
191
- #enqueue(group, options, resolve, reject) {
192
- let queue = this.#queue.get(group);
280
+ enqueue(group, options, resolve, reject) {
281
+ let queue = this.queue.get(group);
193
282
  if (!queue) {
194
- this.#queue.set(group, queue = []);
283
+ queue = [];
284
+ this.queue.set(group, queue);
195
285
  }
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
- });
286
+ const requestKey = createRequestKey(options.path, options.request);
287
+ const matched = queue.find((item) => item.requestKey === requestKey);
211
288
  if (matched) {
289
+ matched.matchedOptions.push(options);
212
290
  matched.signals.push(options.request.signal);
213
291
  matched.resolves.push(resolve);
214
292
  matched.rejects.push(reject);
215
- } else {
216
- queue.push({
217
- options,
218
- signals: [options.request.signal],
219
- resolves: [resolve],
220
- rejects: [reject]
221
- });
293
+ return;
222
294
  }
295
+ queue.push({
296
+ requestKey,
297
+ options,
298
+ matchedOptions: [options],
299
+ signals: [options.request.signal],
300
+ resolves: [resolve],
301
+ rejects: [reject]
302
+ });
223
303
  }
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
- );
304
+ async processPendingRequests() {
305
+ const pending = new Map(this.queue);
306
+ this.queue.clear();
307
+ const executions = [];
308
+ for (const [group, items] of pending) {
309
+ for (const item of items) {
310
+ executions.push(this.execute(group, item));
231
311
  }
232
312
  }
233
- this.#queue.clear();
234
- await Promise.all(promises);
313
+ await Promise.all(executions);
235
314
  }
236
- async #execute(group, options, signals, resolves, rejects) {
315
+ async execute(group, item) {
237
316
  try {
238
- const dedupedRequest = {
239
- ...options.request,
240
- signal: toBatchAbortSignal(signals)
317
+ if (!shouldDedupe(item.matchedOptions)) {
318
+ const response2 = await item.options.next(item.options);
319
+ item.resolves[0]?.(response2);
320
+ return;
321
+ }
322
+ const context = value(group.context, item.matchedOptions);
323
+ const request = {
324
+ ...item.options.request,
325
+ signal: allAbortSignal(item.signals)
241
326
  };
242
- const response = await options.next({
243
- ...options,
244
- request: dedupedRequest,
245
- signal: dedupedRequest.signal,
246
- context: group.context
327
+ const response = await item.options.next({
328
+ ...item.options,
329
+ request,
330
+ signal: request.signal,
331
+ context
247
332
  });
248
- const replicatedResponses = replicateStandardLazyResponse(response, resolves.length);
249
- for (const resolve of resolves) {
250
- resolve(replicatedResponses.shift());
333
+ const replicatedResponses = replicateLazyResponse(response, item.resolves.length);
334
+ for (const resolve of item.resolves) {
335
+ resolve(replicatedResponses.pop());
251
336
  }
252
337
  } catch (error) {
253
- for (const reject of rejects) {
338
+ for (const reject of item.rejects) {
254
339
  reject(error);
255
340
  }
256
341
  }
257
342
  }
258
343
  }
344
+ function canDedupeRequest(request) {
345
+ return !(request.body instanceof Blob || request.body instanceof FormData || request.body instanceof URLSearchParams || request.body instanceof ReadableStream || isAsyncIteratorObject(request.body) || request.signal?.aborted);
346
+ }
347
+ function createRequestKey(path, request) {
348
+ return stringifyJSON({
349
+ path,
350
+ body: request.body,
351
+ headers: request.headers,
352
+ method: request.method,
353
+ url: request.url
354
+ });
355
+ }
356
+ function replicateLazyResponse(response, count) {
357
+ const replicated = [];
358
+ let bodyPromise;
359
+ let replicatedAsyncIterators;
360
+ let replicatedReadableStream;
361
+ for (let i = 0; i < count; i++) {
362
+ let resolvedBody;
363
+ replicated.push({
364
+ ...response,
365
+ resolveBody: async (hint) => {
366
+ if (resolvedBody) {
367
+ return resolvedBody.body;
368
+ }
369
+ bodyPromise ??= response.resolveBody(hint);
370
+ const body = await bodyPromise;
371
+ if (isAsyncIteratorObject(body)) {
372
+ replicatedAsyncIterators ??= replicateAsyncIterator(body, count);
373
+ resolvedBody = { body: replicatedAsyncIterators.pop() };
374
+ } else if (body instanceof ReadableStream) {
375
+ replicatedReadableStream ??= replicateReadableStream(body, count);
376
+ resolvedBody = { body: replicatedReadableStream.pop() };
377
+ } else {
378
+ resolvedBody = { body };
379
+ }
380
+ return resolvedBody.body;
381
+ }
382
+ });
383
+ }
384
+ return replicated;
385
+ }
386
+ function shouldDedupe(items) {
387
+ return items.length >= 2;
388
+ }
259
389
 
260
- class ClientRetryPluginInvalidEventIteratorRetryResponse extends Error {
390
+ class RetryLinkPluginInvalidEventIteratorRetryResponse extends Error {
261
391
  }
262
- class ClientRetryPlugin {
392
+ class RetryLinkPlugin {
263
393
  defaultRetry;
264
394
  defaultRetryDelay;
265
395
  defaultShouldRetry;
266
396
  defaultOnRetry;
267
- order = 18e5;
397
+ name = "~retry";
268
398
  constructor(options = {}) {
269
399
  this.defaultRetry = options.default?.retry ?? 0;
270
400
  this.defaultRetryDelay = options.default?.retryDelay ?? ((o) => o.lastEventRetry ?? 2e3);
@@ -272,33 +402,33 @@ class ClientRetryPlugin {
272
402
  this.defaultOnRetry = options.default?.onRetry;
273
403
  }
274
404
  init(options) {
275
- options.interceptors ??= [];
276
- options.interceptors.push(async (interceptorOptions) => {
405
+ const interceptor = async (interceptorOptions) => {
406
+ const { next, ...callOptions } = interceptorOptions;
277
407
  const maxAttempts = await value(
278
- interceptorOptions.context.retry ?? this.defaultRetry,
279
- interceptorOptions
408
+ callOptions.context.retry ?? this.defaultRetry,
409
+ callOptions
280
410
  );
281
- const retryDelay = interceptorOptions.context.retryDelay ?? this.defaultRetryDelay;
282
- const shouldRetry = interceptorOptions.context.shouldRetry ?? this.defaultShouldRetry;
283
- const onRetry = interceptorOptions.context.onRetry ?? this.defaultOnRetry;
411
+ const retryDelay = callOptions.context.retryDelay ?? this.defaultRetryDelay;
412
+ const shouldRetry = callOptions.context.shouldRetry ?? this.defaultShouldRetry;
413
+ const onRetry = callOptions.context.onRetry ?? this.defaultOnRetry;
284
414
  if (maxAttempts <= 0) {
285
- return interceptorOptions.next();
415
+ return next(callOptions);
286
416
  }
287
- let lastEventId = interceptorOptions.lastEventId;
417
+ let lastEventId = callOptions.lastEventId;
288
418
  let lastEventRetry;
289
419
  let callback;
290
- let attemptIndex = 0;
291
- const next = async (initialError) => {
420
+ let attempt = 1;
421
+ const callNext = async (initialError) => {
292
422
  let currentError = initialError;
293
423
  while (true) {
294
- const updatedInterceptorOptions = { ...interceptorOptions, lastEventId };
424
+ const updatedCallOptions = { ...callOptions, lastEventId };
295
425
  if (currentError) {
296
- if (attemptIndex >= maxAttempts) {
426
+ if (attempt > maxAttempts) {
297
427
  throw currentError.error;
298
428
  }
299
429
  const attemptOptions = {
300
- ...updatedInterceptorOptions,
301
- attemptIndex,
430
+ ...updatedCallOptions,
431
+ attempt,
302
432
  error: currentError.error,
303
433
  lastEventRetry
304
434
  };
@@ -310,16 +440,23 @@ class ClientRetryPlugin {
310
440
  throw currentError.error;
311
441
  }
312
442
  callback = onRetry?.(attemptOptions);
313
- const retryDelayMs = await value(retryDelay, attemptOptions);
314
- await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
315
- attemptIndex++;
316
443
  }
317
444
  try {
445
+ if (currentError) {
446
+ const retryDelayMs = await value(retryDelay, {
447
+ ...updatedCallOptions,
448
+ attempt,
449
+ error: currentError.error,
450
+ lastEventRetry
451
+ });
452
+ await sleep(retryDelayMs, { signal: updatedCallOptions.signal });
453
+ attempt++;
454
+ }
318
455
  currentError = void 0;
319
- return await interceptorOptions.next(updatedInterceptorOptions);
456
+ return await next(updatedCallOptions);
320
457
  } catch (error) {
321
458
  currentError = { error };
322
- if (updatedInterceptorOptions.signal?.aborted) {
459
+ if (updatedCallOptions.signal?.aborted) {
323
460
  throw error;
324
461
  }
325
462
  } finally {
@@ -328,13 +465,13 @@ class ClientRetryPlugin {
328
465
  }
329
466
  }
330
467
  };
331
- const output = await next();
468
+ const output = await callNext();
332
469
  if (!isAsyncIteratorObject(output)) {
333
470
  return output;
334
471
  }
335
472
  let current = output;
336
473
  let isIteratorAborted = false;
337
- return overlayProxy(() => current, new AsyncIteratorClass(
474
+ return override(() => current, new AsyncIteratorClass(
338
475
  async () => {
339
476
  while (true) {
340
477
  try {
@@ -347,10 +484,10 @@ class ClientRetryPlugin {
347
484
  const meta = getEventMeta(error);
348
485
  lastEventId = meta?.id ?? lastEventId;
349
486
  lastEventRetry = meta?.retry ?? lastEventRetry;
350
- const maybeEventIterator = await next({ error });
487
+ const maybeEventIterator = await callNext({ error });
351
488
  if (!isAsyncIteratorObject(maybeEventIterator)) {
352
- throw new ClientRetryPluginInvalidEventIteratorRetryResponse(
353
- "RetryPlugin: Expected an Event Iterator, got a non-Event Iterator"
489
+ throw new RetryLinkPluginInvalidEventIteratorRetryResponse(
490
+ "RetryLinkPlugin: Expected an Event Iterator, got a non-Event Iterator"
354
491
  );
355
492
  }
356
493
  current = maybeEventIterator;
@@ -361,30 +498,30 @@ class ClientRetryPlugin {
361
498
  }
362
499
  }
363
500
  },
364
- async (reason) => {
501
+ async ({ kind }) => {
365
502
  isIteratorAborted = true;
366
- if (reason !== "next") {
503
+ if (kind === "cancelled") {
367
504
  await current.return?.();
368
505
  }
369
506
  }
370
507
  ));
371
- });
508
+ };
509
+ return { ...options, interceptors: [interceptor, ...toArray(options.interceptors)] };
372
510
  }
373
511
  }
374
512
 
375
- class RetryAfterPlugin {
513
+ class RetryAfterLinkPlugin {
376
514
  condition;
377
515
  maxAttempts;
378
516
  timeout;
379
- order = 19e5;
517
+ name = "~retry-after";
380
518
  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);
519
+ this.condition = options.condition ?? ((response) => response.status === COMMON_ERROR_STATUS_MAP.TOO_MANY_REQUESTS || response.status === COMMON_ERROR_STATUS_MAP.SERVICE_UNAVAILABLE);
382
520
  this.maxAttempts = options.maxAttempts ?? 3;
383
521
  this.timeout = options.timeout ?? 5 * 60 * 1e3;
384
522
  }
385
523
  init(options) {
386
- options.clientInterceptors ??= [];
387
- options.clientInterceptors.push(async (interceptorOptions) => {
524
+ const interceptor = async (interceptorOptions) => {
388
525
  const startTime = Date.now();
389
526
  let attemptCount = 0;
390
527
  while (true) {
@@ -393,8 +530,8 @@ class RetryAfterPlugin {
393
530
  if (!value(this.condition, response, interceptorOptions)) {
394
531
  return response;
395
532
  }
396
- const retryAfterHeader = flattenHeader(response.headers["retry-after"]);
397
- const retryAfterMs = this.parseRetryAfterHeader(retryAfterHeader);
533
+ const retryAfterHeader = flattenStandardHeader(response.headers["retry-after"]);
534
+ const retryAfterMs = parseRetryAfterHeader(retryAfterHeader);
398
535
  if (retryAfterMs === void 0) {
399
536
  return response;
400
537
  }
@@ -406,80 +543,32 @@ class RetryAfterPlugin {
406
543
  if (elapsedTime + retryAfterMs > timeoutMs) {
407
544
  return response;
408
545
  }
409
- await this.delayExecution(retryAfterMs, interceptorOptions.signal);
546
+ try {
547
+ await sleep(retryAfterMs, { signal: interceptorOptions.signal });
548
+ } catch {
549
+ }
410
550
  if (interceptorOptions.signal?.aborted) {
411
551
  return response;
412
552
  }
413
553
  }
414
- });
554
+ };
555
+ return { ...options, transportInterceptors: [interceptor, ...toArray(options.transportInterceptors)] };
415
556
  }
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
- }
557
+ }
558
+ function parseRetryAfterHeader(value2) {
559
+ value2 = value2?.trim();
560
+ if (!value2) {
429
561
  return void 0;
430
562
  }
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
- });
449
- }
450
- }
451
-
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;
563
+ const seconds = Number(value2);
564
+ if (Number.isFinite(seconds)) {
565
+ return Math.max(0, seconds * 1e3);
460
566
  }
461
- order = 8e6;
462
- 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();
468
- }
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
- });
567
+ const retryDate = Date.parse(value2);
568
+ if (!Number.isNaN(retryDate)) {
569
+ return Math.max(0, retryDate - Date.now());
482
570
  }
571
+ return void 0;
483
572
  }
484
573
 
485
- export { BatchLinkPlugin, ClientRetryPlugin, ClientRetryPluginInvalidEventIteratorRetryResponse, DedupeRequestsPlugin, RetryAfterPlugin, SimpleCsrfProtectionLinkPlugin };
574
+ export { BatchLinkPlugin, BatchLinkPluginError, DedupeLinkPlugin, RetryAfterLinkPlugin, RetryLinkPlugin, RetryLinkPluginInvalidEventIteratorRetryResponse };