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