@orpc/client 1.14.11 → 1.14.12

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 +68 -52
  2. package/dist/adapters/fetch/index.d.mts +35 -54
  3. package/dist/adapters/fetch/index.d.ts +35 -54
  4. package/dist/adapters/fetch/index.mjs +27 -74
  5. package/dist/adapters/message-port/index.d.mts +30 -25
  6. package/dist/adapters/message-port/index.d.ts +30 -25
  7. package/dist/adapters/message-port/index.mjs +30 -39
  8. package/dist/adapters/standard/index.d.mts +9 -59
  9. package/dist/adapters/standard/index.d.ts +9 -59
  10. package/dist/adapters/standard/index.mjs +5 -5
  11. package/dist/adapters/websocket/index.d.mts +21 -113
  12. package/dist/adapters/websocket/index.d.ts +21 -113
  13. package/dist/adapters/websocket/index.mjs +37 -95
  14. package/dist/index.d.mts +184 -75
  15. package/dist/index.d.ts +184 -75
  16. package/dist/index.mjs +63 -74
  17. package/dist/plugins/index.d.mts +122 -191
  18. package/dist/plugins/index.d.ts +122 -191
  19. package/dist/plugins/index.mjs +281 -627
  20. package/dist/shared/client.2jUAqzYU.d.ts +45 -0
  21. package/dist/shared/client.B3pNRBih.d.ts +91 -0
  22. package/dist/shared/client.BFAVy68H.d.mts +91 -0
  23. package/dist/shared/client.BLtwTQUg.mjs +40 -0
  24. package/dist/shared/client.C1VUaWTu.mjs +404 -0
  25. package/dist/shared/client.CpCa3si8.d.mts +45 -0
  26. package/dist/shared/client.DrCRv_sG.mjs +174 -0
  27. package/dist/shared/client.i2uoJbEp.d.mts +83 -0
  28. package/dist/shared/client.i2uoJbEp.d.ts +83 -0
  29. package/package.json +7 -7
  30. package/dist/shared/client.8f4DNmdE.d.mts +0 -96
  31. package/dist/shared/client.BBZBQID8.d.mts +0 -167
  32. package/dist/shared/client.BBZBQID8.d.ts +0 -167
  33. package/dist/shared/client.BMKYqpdy.d.ts +0 -96
  34. package/dist/shared/client.BRJnOJ0R.mjs +0 -174
  35. package/dist/shared/client.BdItY5DT.d.mts +0 -111
  36. package/dist/shared/client.BdItY5DT.d.ts +0 -111
  37. package/dist/shared/client.Dnfj8jnT.mjs +0 -92
  38. package/dist/shared/client.DqYwRDUO.mjs +0 -343
@@ -1,626 +1,270 @@
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';
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.DrCRv_sG.mjs';
6
5
 
7
- class BatchLinkPluginError extends TypeError {
8
- }
9
6
  class BatchLinkPlugin {
10
- name = "~batch";
11
7
  groups;
12
- filter;
13
8
  maxSize;
14
- mode;
15
9
  batchUrl;
16
10
  maxUrlLength;
17
11
  batchHeaders;
18
- mapSubrequest;
19
- mapSubresponse;
20
- queue = /* @__PURE__ */ new Map();
12
+ mapRequestItem;
13
+ exclude;
14
+ mode;
15
+ pending;
16
+ order = 5e6;
21
17
  constructor(options) {
22
18
  this.groups = options.groups;
23
- this.filter = options.filter ?? (() => true);
19
+ this.pending = /* @__PURE__ */ new Map();
24
20
  this.maxSize = options.maxSize ?? 10;
25
- this.mode = options.mode ?? "streaming";
26
- this.batchUrl = options.url ?? ((options2) => {
27
- const [pathname] = parseStandardUrl(options2[0].request.url);
28
- return `${pathname}/__batch__`;
29
- });
30
21
  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
- }
22
+ 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;
39
29
  }
40
30
  }
41
- return commonHeaders;
31
+ return headers;
42
32
  });
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;
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;
48
38
  }
49
39
  }
50
40
  return {
51
- ...request,
52
- headers: subHeaders
41
+ method: request.method,
42
+ url: request.url,
43
+ headers,
44
+ body: request.body,
45
+ signal: request.signal
53
46
  };
54
47
  });
55
- this.mapSubresponse = (subResponse, batchResponse) => {
56
- return {
57
- ...subResponse,
58
- headers: {
59
- ...batchResponse.headers,
60
- // low-priority
61
- ...subResponse.headers
62
- }
63
- };
64
- };
48
+ this.exclude = options.exclude ?? (() => false);
65
49
  }
66
50
  init(options) {
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();
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();
70
70
  }
71
- const group = this.groups.find((group2) => value(group2.condition, interceptorOptions));
71
+ const group = this.groups.find((group2) => group2.condition(options2));
72
72
  if (!group) {
73
- return interceptorOptions.next();
73
+ return options2.next();
74
74
  }
75
75
  return new Promise((resolve, reject) => {
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());
76
+ this.#enqueueRequest(group, options2, resolve, reject);
77
+ defer(() => this.#processPendingBatches());
82
78
  });
83
- };
84
- return {
85
- ...options,
86
- transportInterceptors: [...toArray(options.transportInterceptors), transportInterceptor]
87
- };
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
+ }
88
88
  }
89
- async processPendingBatches() {
90
- const pending = new Map(this.queue);
91
- this.queue.clear();
89
+ async #processPendingBatches() {
90
+ const pending = this.pending;
91
+ this.pending = /* @__PURE__ */ new Map();
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
- if (groupItems.length === 1) {
104
- const [options, resolve, reject] = groupItems[0];
105
- options.next().then(resolve).catch(reject);
106
- return;
107
- }
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
- ]);
103
+ const batchItems = groupItems;
104
+ if (batchItems.length === 1) {
105
+ batchItems[0][0].next().then(batchItems[0][1]).catch(batchItems[0][2]);
116
106
  return;
117
107
  }
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();
129
- }
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
- })();
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;
182
116
  }
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
- }
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
189
125
  });
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;
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;
226
132
  }
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;
233
- }
234
- if (buffer.length < 4 + length) {
235
- break;
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) });
236
145
  }
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.");
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
+ }
242
153
  }
243
- await peer.message(result.message);
154
+ throw err;
244
155
  }
245
- if (done) {
246
- break;
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);
247
160
  }
248
161
  }
249
- } finally {
250
- reader.releaseLock();
251
162
  }
252
163
  }
253
164
 
254
- class DedupeLinkPlugin {
255
- name = "~dedupe";
256
- before = ["~batch"];
257
- groups;
258
- filter;
259
- queue = /* @__PURE__ */ new Map();
165
+ class DedupeRequestsPlugin {
166
+ #groups;
167
+ #filter;
168
+ order = 4e6;
169
+ // make sure execute before batch plugin
170
+ #queue = /* @__PURE__ */ new Map();
260
171
  constructor(options) {
261
- this.groups = options.groups;
262
- this.filter = options.filter ?? (({ request }) => request.method === "GET");
172
+ this.#groups = options.groups;
173
+ this.#filter = options.filter ?? (({ request }) => request.method === "GET");
263
174
  }
264
175
  init(options) {
265
- const transportInterceptor = (interceptorOptions) => {
266
- if (!canDedupeRequest(interceptorOptions.request) || !value(this.filter, interceptorOptions)) {
267
- return interceptorOptions.next();
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();
268
180
  }
269
- const group = this.groups.find((group2) => value(group2.condition, interceptorOptions));
181
+ const group = this.#groups.find((group2) => group2.condition(options2));
270
182
  if (!group) {
271
- return interceptorOptions.next();
183
+ return options2.next();
272
184
  }
273
185
  return new Promise((resolve, reject) => {
274
- this.enqueue(group, interceptorOptions, resolve, reject);
275
- defer(() => {
276
- this.processPendingRequests();
277
- });
186
+ this.#enqueue(group, options2, resolve, reject);
187
+ defer(() => this.#dequeue());
278
188
  });
279
- };
280
- return {
281
- ...options,
282
- transportInterceptors: [...toArray(options.transportInterceptors), transportInterceptor]
283
- };
189
+ });
284
190
  }
285
- enqueue(group, options, resolve, reject) {
286
- let queue = this.queue.get(group);
191
+ #enqueue(group, options, resolve, reject) {
192
+ let queue = this.#queue.get(group);
287
193
  if (!queue) {
288
- queue = [];
289
- this.queue.set(group, queue);
194
+ this.#queue.set(group, queue = []);
290
195
  }
291
- const requestKey = createRequestKey(options.path, options.request);
292
- const matched = queue.find((item) => item.requestKey === requestKey);
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
+ });
293
211
  if (matched) {
294
- matched.matchedOptions.push(options);
295
212
  matched.signals.push(options.request.signal);
296
213
  matched.resolves.push(resolve);
297
214
  matched.rejects.push(reject);
298
- return;
215
+ } else {
216
+ queue.push({
217
+ options,
218
+ signals: [options.request.signal],
219
+ resolves: [resolve],
220
+ rejects: [reject]
221
+ });
299
222
  }
300
- queue.push({
301
- requestKey,
302
- options,
303
- matchedOptions: [options],
304
- signals: [options.request.signal],
305
- resolves: [resolve],
306
- rejects: [reject]
307
- });
308
223
  }
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));
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
+ );
316
231
  }
317
232
  }
318
- await Promise.all(executions);
233
+ this.#queue.clear();
234
+ await Promise.all(promises);
319
235
  }
320
- async execute(group, item) {
236
+ async #execute(group, options, signals, resolves, rejects) {
321
237
  try {
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)
238
+ const dedupedRequest = {
239
+ ...options.request,
240
+ signal: toBatchAbortSignal(signals)
331
241
  };
332
- const response = await item.options.next({
333
- ...item.options,
334
- request,
335
- signal: request.signal,
336
- context
242
+ const response = await options.next({
243
+ ...options,
244
+ request: dedupedRequest,
245
+ signal: dedupedRequest.signal,
246
+ context: group.context
337
247
  });
338
- const replicatedResponses = replicateLazyResponse(response, item.resolves.length);
339
- for (const resolve of item.resolves) {
340
- resolve(replicatedResponses.pop());
248
+ const replicatedResponses = replicateStandardLazyResponse(response, resolves.length);
249
+ for (const resolve of resolves) {
250
+ resolve(replicatedResponses.shift());
341
251
  }
342
252
  } catch (error) {
343
- for (const reject of item.rejects) {
253
+ for (const reject of rejects) {
344
254
  reject(error);
345
255
  }
346
256
  }
347
257
  }
348
258
  }
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
- }
542
259
 
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
- }
260
+ class ClientRetryPluginInvalidEventIteratorRetryResponse extends Error {
606
261
  }
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 {
262
+ class ClientRetryPlugin {
619
263
  defaultRetry;
620
264
  defaultRetryDelay;
621
265
  defaultShouldRetry;
622
266
  defaultOnRetry;
623
- name = "~retry";
267
+ order = 18e5;
624
268
  constructor(options = {}) {
625
269
  this.defaultRetry = options.default?.retry ?? 0;
626
270
  this.defaultRetryDelay = options.default?.retryDelay ?? ((o) => o.lastEventRetry ?? 2e3);
@@ -628,33 +272,33 @@ class RetryLinkPlugin {
628
272
  this.defaultOnRetry = options.default?.onRetry;
629
273
  }
630
274
  init(options) {
631
- const interceptor = async (interceptorOptions) => {
632
- const { next, ...callOptions } = interceptorOptions;
275
+ options.interceptors ??= [];
276
+ options.interceptors.push(async (interceptorOptions) => {
633
277
  const maxAttempts = await value(
634
- callOptions.context.retry ?? this.defaultRetry,
635
- callOptions
278
+ interceptorOptions.context.retry ?? this.defaultRetry,
279
+ interceptorOptions
636
280
  );
637
- const retryDelay = callOptions.context.retryDelay ?? this.defaultRetryDelay;
638
- const shouldRetry = callOptions.context.shouldRetry ?? this.defaultShouldRetry;
639
- const onRetry = callOptions.context.onRetry ?? this.defaultOnRetry;
281
+ const retryDelay = interceptorOptions.context.retryDelay ?? this.defaultRetryDelay;
282
+ const shouldRetry = interceptorOptions.context.shouldRetry ?? this.defaultShouldRetry;
283
+ const onRetry = interceptorOptions.context.onRetry ?? this.defaultOnRetry;
640
284
  if (maxAttempts <= 0) {
641
- return next(callOptions);
285
+ return interceptorOptions.next();
642
286
  }
643
- let lastEventId = callOptions.lastEventId;
287
+ let lastEventId = interceptorOptions.lastEventId;
644
288
  let lastEventRetry;
645
289
  let callback;
646
- let attempt = 1;
647
- const callNext = async (initialError) => {
290
+ let attemptIndex = 0;
291
+ const next = async (initialError) => {
648
292
  let currentError = initialError;
649
293
  while (true) {
650
- const updatedCallOptions = { ...callOptions, lastEventId };
294
+ const updatedInterceptorOptions = { ...interceptorOptions, lastEventId };
651
295
  if (currentError) {
652
- if (attempt > maxAttempts) {
296
+ if (attemptIndex >= maxAttempts) {
653
297
  throw currentError.error;
654
298
  }
655
299
  const attemptOptions = {
656
- ...updatedCallOptions,
657
- attempt,
300
+ ...updatedInterceptorOptions,
301
+ attemptIndex,
658
302
  error: currentError.error,
659
303
  lastEventRetry
660
304
  };
@@ -666,23 +310,16 @@ class RetryLinkPlugin {
666
310
  throw currentError.error;
667
311
  }
668
312
  callback = onRetry?.(attemptOptions);
313
+ const retryDelayMs = await value(retryDelay, attemptOptions);
314
+ await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
315
+ attemptIndex++;
669
316
  }
670
317
  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
- }
681
318
  currentError = void 0;
682
- return await next(updatedCallOptions);
319
+ return await interceptorOptions.next(updatedInterceptorOptions);
683
320
  } catch (error) {
684
321
  currentError = { error };
685
- if (updatedCallOptions.signal?.aborted) {
322
+ if (updatedInterceptorOptions.signal?.aborted) {
686
323
  throw error;
687
324
  }
688
325
  } finally {
@@ -691,13 +328,13 @@ class RetryLinkPlugin {
691
328
  }
692
329
  }
693
330
  };
694
- const output = await callNext();
331
+ const output = await next();
695
332
  if (!isAsyncIteratorObject(output)) {
696
333
  return output;
697
334
  }
698
335
  let current = output;
699
336
  let isIteratorAborted = false;
700
- return override(() => current, new AsyncIteratorClass(
337
+ return overlayProxy(() => current, new AsyncIteratorClass(
701
338
  async () => {
702
339
  while (true) {
703
340
  try {
@@ -710,13 +347,13 @@ class RetryLinkPlugin {
710
347
  const meta = getEventMeta(error);
711
348
  lastEventId = meta?.id ?? lastEventId;
712
349
  lastEventRetry = meta?.retry ?? lastEventRetry;
713
- const asyncIteratorObject = await callNext({ error });
714
- if (!isAsyncIteratorObject(asyncIteratorObject)) {
715
- throw new TypeError(
716
- "RetryLinkPlugin: Expected an AsyncIteratorObject, got a different type."
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"
717
354
  );
718
355
  }
719
- current = asyncIteratorObject;
356
+ current = maybeEventIterator;
720
357
  if (isIteratorAborted) {
721
358
  await current.return?.();
722
359
  throw error;
@@ -724,30 +361,30 @@ class RetryLinkPlugin {
724
361
  }
725
362
  }
726
363
  },
727
- async ({ kind }) => {
364
+ async (reason) => {
728
365
  isIteratorAborted = true;
729
- if (kind === "cancelled") {
366
+ if (reason !== "next") {
730
367
  await current.return?.();
731
368
  }
732
369
  }
733
370
  ));
734
- };
735
- return { ...options, interceptors: [interceptor, ...toArray(options.interceptors)] };
371
+ });
736
372
  }
737
373
  }
738
374
 
739
- class RetryAfterLinkPlugin {
375
+ class RetryAfterPlugin {
740
376
  condition;
741
377
  maxAttempts;
742
378
  timeout;
743
- name = "~retry-after";
379
+ order = 19e5;
744
380
  constructor(options = {}) {
745
- this.condition = options.condition ?? ((response) => response.status === COMMON_ERROR_STATUS_MAP.TOO_MANY_REQUESTS || response.status === COMMON_ERROR_STATUS_MAP.SERVICE_UNAVAILABLE);
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);
746
382
  this.maxAttempts = options.maxAttempts ?? 3;
747
383
  this.timeout = options.timeout ?? 5 * 60 * 1e3;
748
384
  }
749
385
  init(options) {
750
- const interceptor = async (interceptorOptions) => {
386
+ options.clientInterceptors ??= [];
387
+ options.clientInterceptors.push(async (interceptorOptions) => {
751
388
  const startTime = Date.now();
752
389
  let attemptCount = 0;
753
390
  while (true) {
@@ -756,8 +393,8 @@ class RetryAfterLinkPlugin {
756
393
  if (!value(this.condition, response, interceptorOptions)) {
757
394
  return response;
758
395
  }
759
- const retryAfterHeader = flattenStandardHeader(response.headers["retry-after"]);
760
- const retryAfterMs = parseRetryAfterHeader(retryAfterHeader);
396
+ const retryAfterHeader = flattenHeader(response.headers["retry-after"]);
397
+ const retryAfterMs = this.parseRetryAfterHeader(retryAfterHeader);
761
398
  if (retryAfterMs === void 0) {
762
399
  return response;
763
400
  }
@@ -769,63 +406,80 @@ class RetryAfterLinkPlugin {
769
406
  if (elapsedTime + retryAfterMs > timeoutMs) {
770
407
  return response;
771
408
  }
772
- try {
773
- await sleep(retryAfterMs, { signal: interceptorOptions.signal });
774
- } catch {
775
- }
409
+ await this.delayExecution(retryAfterMs, interceptorOptions.signal);
776
410
  if (interceptorOptions.signal?.aborted) {
777
411
  return response;
778
412
  }
779
413
  }
780
- };
781
- return { ...options, transportInterceptors: [interceptor, ...toArray(options.transportInterceptors)] };
414
+ });
782
415
  }
783
- }
784
- function parseRetryAfterHeader(value2) {
785
- value2 = value2?.trim();
786
- if (!value2) {
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
+ }
787
429
  return void 0;
788
430
  }
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());
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
+ });
796
449
  }
797
- return void 0;
798
450
  }
799
451
 
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;
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;
809
460
  }
461
+ order = 8e6;
810
462
  init(options) {
811
- const interceptor = async (interceptorOptions) => {
812
- const timeoutMs = value(this.timeout, interceptorOptions);
813
- if (timeoutMs === null || timeoutMs === void 0) {
814
- return interceptorOptions.next();
815
- }
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);
463
+ options.clientInterceptors ??= [];
464
+ options.clientInterceptors.push(async (options2) => {
465
+ const excluded = await value(this.exclude, options2);
466
+ if (excluded) {
467
+ return options2.next();
825
468
  }
826
- };
827
- return { ...options, interceptors: [interceptor, ...toArray(options.interceptors)] };
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
+ });
828
482
  }
829
483
  }
830
484
 
831
- export { BatchLinkPlugin, BatchLinkPluginError, DedupeLinkPlugin, RequestCompressionLinkPlugin, ResponseCompressionLinkPlugin, RetryAfterLinkPlugin, RetryLinkPlugin, TimeoutLinkPlugin };
485
+ export { BatchLinkPlugin, ClientRetryPlugin, ClientRetryPluginInvalidEventIteratorRetryResponse, DedupeRequestsPlugin, RetryAfterPlugin, SimpleCsrfProtectionLinkPlugin };