@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.
- package/README.md +68 -52
- package/dist/adapters/fetch/index.d.mts +35 -54
- package/dist/adapters/fetch/index.d.ts +35 -54
- package/dist/adapters/fetch/index.mjs +27 -74
- package/dist/adapters/message-port/index.d.mts +30 -25
- package/dist/adapters/message-port/index.d.ts +30 -25
- package/dist/adapters/message-port/index.mjs +30 -39
- package/dist/adapters/standard/index.d.mts +9 -59
- package/dist/adapters/standard/index.d.ts +9 -59
- package/dist/adapters/standard/index.mjs +5 -5
- package/dist/adapters/websocket/index.d.mts +21 -113
- package/dist/adapters/websocket/index.d.ts +21 -113
- package/dist/adapters/websocket/index.mjs +37 -95
- package/dist/index.d.mts +184 -75
- package/dist/index.d.ts +184 -75
- package/dist/index.mjs +63 -74
- package/dist/plugins/index.d.mts +122 -191
- package/dist/plugins/index.d.ts +122 -191
- package/dist/plugins/index.mjs +281 -627
- package/dist/shared/client.2jUAqzYU.d.ts +45 -0
- package/dist/shared/client.B3pNRBih.d.ts +91 -0
- package/dist/shared/client.BFAVy68H.d.mts +91 -0
- package/dist/shared/client.BLtwTQUg.mjs +40 -0
- package/dist/shared/client.C1VUaWTu.mjs +404 -0
- package/dist/shared/client.CpCa3si8.d.mts +45 -0
- package/dist/shared/client.DrCRv_sG.mjs +174 -0
- package/dist/shared/client.i2uoJbEp.d.mts +83 -0
- package/dist/shared/client.i2uoJbEp.d.ts +83 -0
- package/package.json +7 -7
- package/dist/shared/client.8f4DNmdE.d.mts +0 -96
- package/dist/shared/client.BBZBQID8.d.mts +0 -167
- package/dist/shared/client.BBZBQID8.d.ts +0 -167
- package/dist/shared/client.BMKYqpdy.d.ts +0 -96
- package/dist/shared/client.BRJnOJ0R.mjs +0 -174
- package/dist/shared/client.BdItY5DT.d.mts +0 -111
- package/dist/shared/client.BdItY5DT.d.ts +0 -111
- package/dist/shared/client.Dnfj8jnT.mjs +0 -92
- package/dist/shared/client.DqYwRDUO.mjs +0 -343
package/dist/plugins/index.mjs
CHANGED
|
@@ -1,626 +1,270 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
mapRequestItem;
|
|
13
|
+
exclude;
|
|
14
|
+
mode;
|
|
15
|
+
pending;
|
|
16
|
+
order = 5e6;
|
|
21
17
|
constructor(options) {
|
|
22
18
|
this.groups = options.groups;
|
|
23
|
-
this.
|
|
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.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
31
|
+
return headers;
|
|
42
32
|
});
|
|
43
|
-
this.
|
|
44
|
-
const
|
|
45
|
-
for (const [key, value2] of Object.entries(headers)) {
|
|
46
|
-
if (
|
|
47
|
-
|
|
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
|
-
|
|
52
|
-
|
|
41
|
+
method: request.method,
|
|
42
|
+
url: request.url,
|
|
43
|
+
headers,
|
|
44
|
+
body: request.body,
|
|
45
|
+
signal: request.signal
|
|
53
46
|
};
|
|
54
47
|
});
|
|
55
|
-
this.
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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) =>
|
|
71
|
+
const group = this.groups.find((group2) => group2.condition(options2));
|
|
72
72
|
if (!group) {
|
|
73
|
-
return
|
|
73
|
+
return options2.next();
|
|
74
74
|
}
|
|
75
75
|
return new Promise((resolve, reject) => {
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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 =
|
|
91
|
-
this.
|
|
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
|
|
96
|
-
this
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
154
|
+
throw err;
|
|
244
155
|
}
|
|
245
|
-
|
|
246
|
-
|
|
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
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
|
262
|
-
this
|
|
172
|
+
this.#groups = options.groups;
|
|
173
|
+
this.#filter = options.filter ?? (({ request }) => request.method === "GET");
|
|
263
174
|
}
|
|
264
175
|
init(options) {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
|
181
|
+
const group = this.#groups.find((group2) => group2.condition(options2));
|
|
270
182
|
if (!group) {
|
|
271
|
-
return
|
|
183
|
+
return options2.next();
|
|
272
184
|
}
|
|
273
185
|
return new Promise((resolve, reject) => {
|
|
274
|
-
this
|
|
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
|
|
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
|
|
292
|
-
|
|
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
|
-
|
|
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
|
|
310
|
-
const
|
|
311
|
-
this
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
-
|
|
233
|
+
this.#queue.clear();
|
|
234
|
+
await Promise.all(promises);
|
|
319
235
|
}
|
|
320
|
-
async execute(group,
|
|
236
|
+
async #execute(group, options, signals, resolves, rejects) {
|
|
321
237
|
try {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
|
333
|
-
...
|
|
334
|
-
request,
|
|
335
|
-
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 =
|
|
339
|
-
for (const resolve of
|
|
340
|
-
resolve(replicatedResponses.
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
632
|
-
|
|
275
|
+
options.interceptors ??= [];
|
|
276
|
+
options.interceptors.push(async (interceptorOptions) => {
|
|
633
277
|
const maxAttempts = await value(
|
|
634
|
-
|
|
635
|
-
|
|
278
|
+
interceptorOptions.context.retry ?? this.defaultRetry,
|
|
279
|
+
interceptorOptions
|
|
636
280
|
);
|
|
637
|
-
const retryDelay =
|
|
638
|
-
const shouldRetry =
|
|
639
|
-
const onRetry =
|
|
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(
|
|
285
|
+
return interceptorOptions.next();
|
|
642
286
|
}
|
|
643
|
-
let lastEventId =
|
|
287
|
+
let lastEventId = interceptorOptions.lastEventId;
|
|
644
288
|
let lastEventRetry;
|
|
645
289
|
let callback;
|
|
646
|
-
let
|
|
647
|
-
const
|
|
290
|
+
let attemptIndex = 0;
|
|
291
|
+
const next = async (initialError) => {
|
|
648
292
|
let currentError = initialError;
|
|
649
293
|
while (true) {
|
|
650
|
-
const
|
|
294
|
+
const updatedInterceptorOptions = { ...interceptorOptions, lastEventId };
|
|
651
295
|
if (currentError) {
|
|
652
|
-
if (
|
|
296
|
+
if (attemptIndex >= maxAttempts) {
|
|
653
297
|
throw currentError.error;
|
|
654
298
|
}
|
|
655
299
|
const attemptOptions = {
|
|
656
|
-
...
|
|
657
|
-
|
|
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(
|
|
319
|
+
return await interceptorOptions.next(updatedInterceptorOptions);
|
|
683
320
|
} catch (error) {
|
|
684
321
|
currentError = { error };
|
|
685
|
-
if (
|
|
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
|
|
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
|
|
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
|
|
714
|
-
if (!isAsyncIteratorObject(
|
|
715
|
-
throw new
|
|
716
|
-
"
|
|
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 =
|
|
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 (
|
|
364
|
+
async (reason) => {
|
|
728
365
|
isIteratorAborted = true;
|
|
729
|
-
if (
|
|
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
|
|
375
|
+
class RetryAfterPlugin {
|
|
740
376
|
condition;
|
|
741
377
|
maxAttempts;
|
|
742
378
|
timeout;
|
|
743
|
-
|
|
379
|
+
order = 19e5;
|
|
744
380
|
constructor(options = {}) {
|
|
745
|
-
this.condition = options.condition ?? ((response) => response.status ===
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
785
|
-
|
|
786
|
-
|
|
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
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
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
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
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
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
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
|
-
|
|
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,
|
|
485
|
+
export { BatchLinkPlugin, ClientRetryPlugin, ClientRetryPluginInvalidEventIteratorRetryResponse, DedupeRequestsPlugin, RetryAfterPlugin, SimpleCsrfProtectionLinkPlugin };
|