@orpc/client 2.0.0-beta.4 → 2.0.0-beta.40
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 +97 -94
- package/dist/adapters/fetch/index.d.mts +16 -33
- package/dist/adapters/fetch/index.d.ts +16 -33
- package/dist/adapters/fetch/index.mjs +32 -30
- package/dist/adapters/message-port/index.d.mts +13 -8
- package/dist/adapters/message-port/index.d.ts +13 -8
- package/dist/adapters/message-port/index.mjs +22 -17
- package/dist/adapters/standard/index.d.mts +9 -10
- package/dist/adapters/standard/index.d.ts +9 -10
- package/dist/adapters/standard/index.mjs +5 -5
- package/dist/adapters/websocket/index.d.mts +21 -16
- package/dist/adapters/websocket/index.d.ts +21 -16
- package/dist/adapters/websocket/index.mjs +14 -14
- package/dist/index.d.mts +62 -61
- package/dist/index.d.ts +62 -61
- package/dist/index.mjs +47 -54
- package/dist/plugins/index.d.mts +134 -13
- package/dist/plugins/index.d.ts +134 -13
- package/dist/plugins/index.mjs +298 -27
- package/dist/shared/{client.Cby_-GGh.d.mts → client.BWkR2vPU.d.mts} +2 -2
- package/dist/shared/client.BYHnU_Z3.mjs +469 -0
- package/dist/shared/{client.CPF3hX6O.d.ts → client.BnpRXnLi.d.ts} +2 -2
- package/dist/shared/{client.8ug8I-zu.d.mts → client.BoXZCzuS.d.mts} +56 -16
- package/dist/shared/{client.8ug8I-zu.d.ts → client.BoXZCzuS.d.ts} +56 -16
- package/dist/shared/{client.Dnfj8jnT.mjs → client.Dtv1JPkU.mjs} +19 -15
- package/dist/shared/{client.DXhchJ84.mjs → client.VXsLhJtT.mjs} +25 -30
- package/dist/shared/{client.BdItY5DT.d.mts → client.n8K9dFhY.d.mts} +25 -4
- package/dist/shared/{client.BdItY5DT.d.ts → client.n8K9dFhY.d.ts} +25 -4
- package/package.json +20 -8
- package/dist/shared/client.DMXKFDyV.mjs +0 -343
package/dist/plugins/index.mjs
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { toArray, value, splitInHalf, stringifyJSON, isAsyncIteratorObject, defer, loadBytes, allAbortSignal, replicateAsyncIterator, replicateReadableStream, override, AsyncIteratorClass, sleep } from '@orpc/shared';
|
|
2
|
-
import { parseStandardUrl,
|
|
3
|
-
import { ClientPeer, isServerPeerSendMessage, decodePeerMessage } from '@
|
|
4
|
-
import {
|
|
1
|
+
import { toArray, value, splitInHalf, safeEncodeURIComponent, stringifyJSON, once, isAsyncIteratorObject, defer, loadBytes, allAbortSignal, replicateAsyncIterator, replicateReadableStream, isCompressibleContentType, override, AsyncIteratorClass, sleep, AbortError, anyAbortSignal } from '@orpc/shared';
|
|
2
|
+
import { parseStandardUrl, flattenStandardHeader, generateContentDisposition, getEventMeta } from '@standard-server/core';
|
|
3
|
+
import { ClientPeer, isServerPeerSendMessage, decodePeerMessage } from '@standard-server/peer';
|
|
4
|
+
import { toFetchHeaders, toStandardBody } from '@standard-server/fetch';
|
|
5
|
+
import { C as COMMON_ERROR_STATUS_MAP } from '../shared/client.Dtv1JPkU.mjs';
|
|
5
6
|
|
|
6
|
-
class BatchLinkPluginError extends TypeError {
|
|
7
|
-
}
|
|
8
7
|
class BatchLinkPlugin {
|
|
9
8
|
name = "~batch";
|
|
10
9
|
groups;
|
|
@@ -31,7 +30,8 @@ class BatchLinkPlugin {
|
|
|
31
30
|
const headersList = options2.map((o) => o.request.headers);
|
|
32
31
|
const commonHeaders = {};
|
|
33
32
|
for (const headers of headersList) {
|
|
34
|
-
for (const
|
|
33
|
+
for (const key of Object.keys(headers)) {
|
|
34
|
+
const value2 = headers[key];
|
|
35
35
|
if (headersList.every((h) => h[key] === value2)) {
|
|
36
36
|
commonHeaders[key] = value2;
|
|
37
37
|
}
|
|
@@ -41,7 +41,8 @@ class BatchLinkPlugin {
|
|
|
41
41
|
});
|
|
42
42
|
this.mapSubrequest = options.mapSubrequest ?? (({ request }, { headers }) => {
|
|
43
43
|
const subHeaders = { ...request.headers };
|
|
44
|
-
for (const
|
|
44
|
+
for (const key of Object.keys(headers)) {
|
|
45
|
+
const value2 = headers[key];
|
|
45
46
|
if (subHeaders[key] === value2) {
|
|
46
47
|
subHeaders[key] = void 0;
|
|
47
48
|
}
|
|
@@ -51,7 +52,7 @@ class BatchLinkPlugin {
|
|
|
51
52
|
headers: subHeaders
|
|
52
53
|
};
|
|
53
54
|
});
|
|
54
|
-
this.mapSubresponse = (subResponse, batchResponse) => {
|
|
55
|
+
this.mapSubresponse = options.mapSubresponse ?? ((subResponse, batchResponse) => {
|
|
55
56
|
return {
|
|
56
57
|
...subResponse,
|
|
57
58
|
headers: {
|
|
@@ -60,7 +61,7 @@ class BatchLinkPlugin {
|
|
|
60
61
|
...subResponse.headers
|
|
61
62
|
}
|
|
62
63
|
};
|
|
63
|
-
};
|
|
64
|
+
});
|
|
64
65
|
}
|
|
65
66
|
init(options) {
|
|
66
67
|
const transportInterceptor = async (interceptorOptions) => {
|
|
@@ -90,9 +91,11 @@ class BatchLinkPlugin {
|
|
|
90
91
|
this.queue.clear();
|
|
91
92
|
for (const [group, items] of pending) {
|
|
92
93
|
const getItems = items.filter(([options]) => options.request.method === "GET");
|
|
93
|
-
const
|
|
94
|
+
const queryItems = items.filter(([options]) => options.request.method === "QUERY");
|
|
95
|
+
const unsafeItems = items.filter(([options]) => options.request.method !== "GET" && options.request.method !== "QUERY");
|
|
94
96
|
this.executeBatch("GET", group, getItems);
|
|
95
|
-
this.executeBatch("
|
|
97
|
+
this.executeBatch("QUERY", group, queryItems);
|
|
98
|
+
this.executeBatch("POST", group, unsafeItems);
|
|
96
99
|
}
|
|
97
100
|
}
|
|
98
101
|
async executeBatch(method, group, groupItems) {
|
|
@@ -137,7 +140,7 @@ class BatchLinkPlugin {
|
|
|
137
140
|
};
|
|
138
141
|
if (method === "GET") {
|
|
139
142
|
const [pathname, search, hash] = parseStandardUrl(url);
|
|
140
|
-
const dataParam = `data=${
|
|
143
|
+
const dataParam = `data=${safeEncodeURIComponent(stringifyJSON(pendingMessages))}`;
|
|
141
144
|
const newUrl = search ? `${pathname}${search}&${dataParam}${hash ?? ""}` : `${pathname}?${dataParam}${hash ?? ""}`;
|
|
142
145
|
const maxUrlLength = await value(this.maxUrlLength, subOptionsList);
|
|
143
146
|
if (newUrl.length > maxUrlLength) {
|
|
@@ -161,6 +164,16 @@ class BatchLinkPlugin {
|
|
|
161
164
|
request,
|
|
162
165
|
signal: controller.signal
|
|
163
166
|
});
|
|
167
|
+
if (batchResponse.status >= 400) {
|
|
168
|
+
suppressErrorFromCurrentBatch = true;
|
|
169
|
+
const resolveBody = once(() => batchResponse.resolveBody());
|
|
170
|
+
const errorResponse = { ...batchResponse, resolveBody };
|
|
171
|
+
groupItems.forEach(([subOptions, resolve]) => {
|
|
172
|
+
resolve(this.mapSubresponse(errorResponse, batchResponse, subOptions));
|
|
173
|
+
});
|
|
174
|
+
await peer.close();
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
164
177
|
const body = await batchResponse.resolveBody();
|
|
165
178
|
if (Array.isArray(body) && body.every((v) => isServerPeerSendMessage(v))) {
|
|
166
179
|
for (const message2 of body) {
|
|
@@ -171,9 +184,9 @@ class BatchLinkPlugin {
|
|
|
171
184
|
} else if (body instanceof ReadableStream) {
|
|
172
185
|
await decodeLengthPrefixedStream(body, peer);
|
|
173
186
|
} else {
|
|
174
|
-
throw new
|
|
187
|
+
throw new TypeError("Invalid batch response format.");
|
|
175
188
|
}
|
|
176
|
-
await peer.close(new
|
|
189
|
+
await peer.close(new TypeError("Batch response is incomplete."));
|
|
177
190
|
} catch (error) {
|
|
178
191
|
await peer.close(error);
|
|
179
192
|
}
|
|
@@ -194,19 +207,19 @@ async function decodeLengthPrefixedBlob(blob, peer) {
|
|
|
194
207
|
let offset = 0;
|
|
195
208
|
while (offset < buffer.length) {
|
|
196
209
|
if (offset + 4 > buffer.length) {
|
|
197
|
-
throw new
|
|
210
|
+
throw new TypeError("Invalid batch response: incomplete length header.");
|
|
198
211
|
}
|
|
199
212
|
const view = new DataView(buffer.buffer, buffer.byteOffset + offset, 4);
|
|
200
213
|
const length = view.getUint32(0, false);
|
|
201
214
|
offset += 4;
|
|
202
215
|
if (offset + length > buffer.length) {
|
|
203
|
-
throw new
|
|
216
|
+
throw new TypeError("Invalid batch response: incomplete message.");
|
|
204
217
|
}
|
|
205
218
|
const messageBytes = buffer.subarray(offset, offset + length);
|
|
206
219
|
offset += length;
|
|
207
220
|
const result = decodePeerMessage(messageBytes);
|
|
208
221
|
if (!result.matched || !isServerPeerSendMessage(result.message)) {
|
|
209
|
-
throw new
|
|
222
|
+
throw new TypeError("Invalid batch response: invalid message.");
|
|
210
223
|
}
|
|
211
224
|
await peer.message(result.message);
|
|
212
225
|
}
|
|
@@ -226,6 +239,10 @@ async function decodeLengthPrefixedStream(stream, peer) {
|
|
|
226
239
|
while (buffer.length >= 4) {
|
|
227
240
|
const view = new DataView(buffer.buffer, buffer.byteOffset, 4);
|
|
228
241
|
const length = view.getUint32(0, false);
|
|
242
|
+
if (length === 0) {
|
|
243
|
+
buffer = buffer.subarray(4);
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
229
246
|
if (buffer.length < 4 + length) {
|
|
230
247
|
break;
|
|
231
248
|
}
|
|
@@ -233,7 +250,7 @@ async function decodeLengthPrefixedStream(stream, peer) {
|
|
|
233
250
|
buffer = buffer.subarray(4 + length);
|
|
234
251
|
const result = decodePeerMessage(messageBytes);
|
|
235
252
|
if (!result.matched || !isServerPeerSendMessage(result.message)) {
|
|
236
|
-
throw new
|
|
253
|
+
throw new TypeError("Invalid batch response: invalid message.");
|
|
237
254
|
}
|
|
238
255
|
await peer.message(result.message);
|
|
239
256
|
}
|
|
@@ -254,7 +271,7 @@ class DedupeLinkPlugin {
|
|
|
254
271
|
queue = /* @__PURE__ */ new Map();
|
|
255
272
|
constructor(options) {
|
|
256
273
|
this.groups = options.groups;
|
|
257
|
-
this.filter = options.filter ?? (({ request }) => request.method === "GET");
|
|
274
|
+
this.filter = options.filter ?? (({ request }) => request.method === "GET" || request.method === "QUERY");
|
|
258
275
|
}
|
|
259
276
|
init(options) {
|
|
260
277
|
const transportInterceptor = (interceptorOptions) => {
|
|
@@ -387,8 +404,231 @@ function shouldDedupe(items) {
|
|
|
387
404
|
return items.length >= 2;
|
|
388
405
|
}
|
|
389
406
|
|
|
390
|
-
|
|
407
|
+
const AVG_BYTES_PER_CHAR = 1.2;
|
|
408
|
+
class RequestCompressionLinkPlugin {
|
|
409
|
+
name = "~request-compression";
|
|
410
|
+
/**
|
|
411
|
+
* Compression should be done after batching, to compress the final request
|
|
412
|
+
*/
|
|
413
|
+
after = ["~batch"];
|
|
414
|
+
encoding;
|
|
415
|
+
threshold;
|
|
416
|
+
isCompressibleContentType;
|
|
417
|
+
constructor(options = {}) {
|
|
418
|
+
this.encoding = options.encoding ?? "gzip";
|
|
419
|
+
this.threshold = options.threshold ?? 1024;
|
|
420
|
+
this.isCompressibleContentType = options.isCompressibleContentType ?? isCompressibleContentType;
|
|
421
|
+
}
|
|
422
|
+
init(options) {
|
|
423
|
+
const transportInterceptor = async ({ next, ...interceptorOptions }) => {
|
|
424
|
+
const request = interceptorOptions.request;
|
|
425
|
+
const contentEncoding = flattenStandardHeader(request.headers["content-encoding"])?.trim()?.toLowerCase();
|
|
426
|
+
if (contentEncoding !== void 0) {
|
|
427
|
+
return next();
|
|
428
|
+
}
|
|
429
|
+
if (request.body instanceof ReadableStream) {
|
|
430
|
+
const contentLength = Number(flattenStandardHeader(request.headers["content-length"]));
|
|
431
|
+
if ((!Number.isFinite(contentLength) || contentLength >= this.threshold) && this.isCompressibleContentType(flattenStandardHeader(request.headers["content-type"]), interceptorOptions)) {
|
|
432
|
+
const compressedStream = request.body.pipeThrough(new CompressionStream(this.encoding));
|
|
433
|
+
return next({
|
|
434
|
+
...interceptorOptions,
|
|
435
|
+
request: {
|
|
436
|
+
...interceptorOptions.request,
|
|
437
|
+
body: compressedStream,
|
|
438
|
+
headers: {
|
|
439
|
+
...request.headers,
|
|
440
|
+
"standard-server": "octet-stream",
|
|
441
|
+
"content-length": [],
|
|
442
|
+
"content-encoding": this.encoding
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
} else if (request.body instanceof Blob) {
|
|
448
|
+
if ((!Number.isFinite(request.body.size) || request.body.size >= this.threshold) && this.isCompressibleContentType(request.body.type, interceptorOptions)) {
|
|
449
|
+
const compressedStream = request.body.stream().pipeThrough(new CompressionStream(this.encoding));
|
|
450
|
+
const contentDisposition = request.headers["content-disposition"] ?? generateContentDisposition(
|
|
451
|
+
request.body instanceof File ? request.body.name : "blob"
|
|
452
|
+
);
|
|
453
|
+
return next({
|
|
454
|
+
...interceptorOptions,
|
|
455
|
+
request: {
|
|
456
|
+
...interceptorOptions.request,
|
|
457
|
+
body: compressedStream,
|
|
458
|
+
headers: {
|
|
459
|
+
...request.headers,
|
|
460
|
+
"standard-server": "file",
|
|
461
|
+
"content-type": request.body.type,
|
|
462
|
+
"content-length": [],
|
|
463
|
+
"content-disposition": contentDisposition,
|
|
464
|
+
"content-encoding": this.encoding
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
} else if (request.body instanceof FormData) {
|
|
470
|
+
const PART_OVERHEAD = 64;
|
|
471
|
+
let contentLength = 0;
|
|
472
|
+
for (const [key, value] of request.body) {
|
|
473
|
+
contentLength += PART_OVERHEAD + key.length;
|
|
474
|
+
if (value instanceof Blob) {
|
|
475
|
+
if (!Number.isFinite(value.size)) {
|
|
476
|
+
if (!this.isCompressibleContentType(value.type, interceptorOptions)) {
|
|
477
|
+
contentLength = -Infinity;
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
480
|
+
contentLength = Infinity;
|
|
481
|
+
} else {
|
|
482
|
+
contentLength += this.isCompressibleContentType(value.type, interceptorOptions) ? value.size : -value.size;
|
|
483
|
+
}
|
|
484
|
+
} else {
|
|
485
|
+
contentLength += value.length * AVG_BYTES_PER_CHAR;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
if (contentLength >= this.threshold) {
|
|
489
|
+
const res = new Response(request.body);
|
|
490
|
+
const compressedStream = res.body.pipeThrough(new CompressionStream(this.encoding));
|
|
491
|
+
return next({
|
|
492
|
+
...interceptorOptions,
|
|
493
|
+
request: {
|
|
494
|
+
...interceptorOptions.request,
|
|
495
|
+
body: compressedStream,
|
|
496
|
+
headers: {
|
|
497
|
+
...request.headers,
|
|
498
|
+
"standard-server": [],
|
|
499
|
+
"content-type": res.headers.get("content-type"),
|
|
500
|
+
"content-length": [],
|
|
501
|
+
"content-encoding": this.encoding
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
} else if (request.body instanceof URLSearchParams) {
|
|
507
|
+
const string = request.body.toString();
|
|
508
|
+
if (string.length * AVG_BYTES_PER_CHAR >= this.threshold) {
|
|
509
|
+
const compressedStream = new Blob([string]).stream().pipeThrough(new CompressionStream(this.encoding));
|
|
510
|
+
return next({
|
|
511
|
+
...interceptorOptions,
|
|
512
|
+
request: {
|
|
513
|
+
...interceptorOptions.request,
|
|
514
|
+
body: compressedStream,
|
|
515
|
+
headers: {
|
|
516
|
+
...request.headers,
|
|
517
|
+
"standard-server": [],
|
|
518
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
519
|
+
"content-length": [],
|
|
520
|
+
"content-encoding": this.encoding
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
} else if (request.body !== void 0 && !isAsyncIteratorObject(request.body)) {
|
|
526
|
+
const string = stringifyJSON(request.body);
|
|
527
|
+
if (string.length * AVG_BYTES_PER_CHAR >= this.threshold) {
|
|
528
|
+
const compressedStream = new Blob([string]).stream().pipeThrough(new CompressionStream(this.encoding));
|
|
529
|
+
return next({
|
|
530
|
+
...interceptorOptions,
|
|
531
|
+
request: {
|
|
532
|
+
...interceptorOptions.request,
|
|
533
|
+
body: compressedStream,
|
|
534
|
+
headers: {
|
|
535
|
+
...request.headers,
|
|
536
|
+
"standard-server": [],
|
|
537
|
+
"content-type": "application/json",
|
|
538
|
+
"content-length": [],
|
|
539
|
+
"content-encoding": this.encoding
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
return next();
|
|
546
|
+
};
|
|
547
|
+
return {
|
|
548
|
+
...options,
|
|
549
|
+
transportInterceptors: [
|
|
550
|
+
...toArray(options.transportInterceptors),
|
|
551
|
+
transportInterceptor
|
|
552
|
+
]
|
|
553
|
+
};
|
|
554
|
+
}
|
|
391
555
|
}
|
|
556
|
+
|
|
557
|
+
class ResponseCompressionLinkPlugin {
|
|
558
|
+
name = "~response-compression";
|
|
559
|
+
/**
|
|
560
|
+
* Decompression should wrap the final batch response instead of sub-responses.
|
|
561
|
+
*/
|
|
562
|
+
after = ["~batch"];
|
|
563
|
+
encodings;
|
|
564
|
+
constructor(options = {}) {
|
|
565
|
+
this.encodings = options.encodings ?? ["gzip", "deflate"];
|
|
566
|
+
}
|
|
567
|
+
init(options) {
|
|
568
|
+
const acceptEncodingHeader = this.encodings.join(", ");
|
|
569
|
+
const transportInterceptor = async ({ next, ...interceptorOptions }) => {
|
|
570
|
+
const response = await next({
|
|
571
|
+
...interceptorOptions,
|
|
572
|
+
request: {
|
|
573
|
+
...interceptorOptions.request,
|
|
574
|
+
headers: {
|
|
575
|
+
...interceptorOptions.request.headers,
|
|
576
|
+
"accept-encoding": acceptEncodingHeader
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
});
|
|
580
|
+
const encodings = parseContentEncodings(
|
|
581
|
+
flattenStandardHeader(response.headers["content-encoding"])
|
|
582
|
+
);
|
|
583
|
+
if (encodings.length === 0 || !encodings.every(isSupportedEncoding)) {
|
|
584
|
+
return response;
|
|
585
|
+
}
|
|
586
|
+
const decompressedHeaders = {
|
|
587
|
+
...response.headers,
|
|
588
|
+
"content-length": void 0,
|
|
589
|
+
"content-encoding": void 0
|
|
590
|
+
};
|
|
591
|
+
return {
|
|
592
|
+
...response,
|
|
593
|
+
headers: decompressedHeaders,
|
|
594
|
+
async resolveBody(hint) {
|
|
595
|
+
const stream = await response.resolveBody("octet-stream");
|
|
596
|
+
if (!(stream instanceof ReadableStream)) {
|
|
597
|
+
return stream;
|
|
598
|
+
}
|
|
599
|
+
let decompressedStream = stream;
|
|
600
|
+
for (let i = encodings.length - 1; i >= 0; i--) {
|
|
601
|
+
decompressedStream = decompressedStream.pipeThrough(
|
|
602
|
+
new DecompressionStream(encodings[i])
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
const fetchResponse = new Response(decompressedStream, {
|
|
606
|
+
headers: toFetchHeaders(decompressedHeaders)
|
|
607
|
+
});
|
|
608
|
+
return toStandardBody(fetchResponse, { hint });
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
};
|
|
612
|
+
return {
|
|
613
|
+
...options,
|
|
614
|
+
transportInterceptors: [
|
|
615
|
+
...toArray(options.transportInterceptors),
|
|
616
|
+
transportInterceptor
|
|
617
|
+
]
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
const SUPPORTED_ENCODINGS = ["gzip", "deflate", "deflate-raw"];
|
|
622
|
+
function isSupportedEncoding(encoding) {
|
|
623
|
+
return SUPPORTED_ENCODINGS.includes(encoding);
|
|
624
|
+
}
|
|
625
|
+
function parseContentEncodings(header) {
|
|
626
|
+
if (header === void 0) {
|
|
627
|
+
return [];
|
|
628
|
+
}
|
|
629
|
+
return header.split(",").map((part) => part.trim().toLowerCase());
|
|
630
|
+
}
|
|
631
|
+
|
|
392
632
|
class RetryLinkPlugin {
|
|
393
633
|
defaultRetry;
|
|
394
634
|
defaultRetryDelay;
|
|
@@ -484,13 +724,13 @@ class RetryLinkPlugin {
|
|
|
484
724
|
const meta = getEventMeta(error);
|
|
485
725
|
lastEventId = meta?.id ?? lastEventId;
|
|
486
726
|
lastEventRetry = meta?.retry ?? lastEventRetry;
|
|
487
|
-
const
|
|
488
|
-
if (!isAsyncIteratorObject(
|
|
489
|
-
throw new
|
|
490
|
-
"RetryLinkPlugin: Expected an
|
|
727
|
+
const asyncIteratorObject = await callNext({ error });
|
|
728
|
+
if (!isAsyncIteratorObject(asyncIteratorObject)) {
|
|
729
|
+
throw new TypeError(
|
|
730
|
+
"RetryLinkPlugin: Expected an AsyncIteratorObject, got a different type."
|
|
491
731
|
);
|
|
492
732
|
}
|
|
493
|
-
current =
|
|
733
|
+
current = asyncIteratorObject;
|
|
494
734
|
if (isIteratorAborted) {
|
|
495
735
|
await current.return?.();
|
|
496
736
|
throw error;
|
|
@@ -571,4 +811,35 @@ function parseRetryAfterHeader(value2) {
|
|
|
571
811
|
return void 0;
|
|
572
812
|
}
|
|
573
813
|
|
|
574
|
-
|
|
814
|
+
class TimeoutLinkPlugin {
|
|
815
|
+
timeout;
|
|
816
|
+
name = "~timeout";
|
|
817
|
+
/**
|
|
818
|
+
* Should abort if the total retry time exceeds the configured timeout
|
|
819
|
+
*/
|
|
820
|
+
after = ["~retry"];
|
|
821
|
+
constructor(options) {
|
|
822
|
+
this.timeout = options.timeout;
|
|
823
|
+
}
|
|
824
|
+
init(options) {
|
|
825
|
+
const interceptor = async (interceptorOptions) => {
|
|
826
|
+
const timeoutMs = value(this.timeout, interceptorOptions);
|
|
827
|
+
if (timeoutMs === null || timeoutMs === void 0) {
|
|
828
|
+
return interceptorOptions.next();
|
|
829
|
+
}
|
|
830
|
+
const controller = new AbortController();
|
|
831
|
+
const timeoutId = setTimeout(() => {
|
|
832
|
+
controller.abort(new AbortError(`Request timed out after ${timeoutMs}ms`));
|
|
833
|
+
}, timeoutMs);
|
|
834
|
+
const signal = anyAbortSignal([interceptorOptions.signal, controller.signal]);
|
|
835
|
+
try {
|
|
836
|
+
return await interceptorOptions.next({ ...interceptorOptions, signal });
|
|
837
|
+
} finally {
|
|
838
|
+
clearTimeout(timeoutId);
|
|
839
|
+
}
|
|
840
|
+
};
|
|
841
|
+
return { ...options, interceptors: [interceptor, ...toArray(options.interceptors)] };
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
export { BatchLinkPlugin, RetryLinkPlugin as ClientRetryPlugin, DedupeLinkPlugin, DedupeLinkPlugin as DedupeRequestsPlugin, RequestCompressionLinkPlugin, ResponseCompressionLinkPlugin, RetryAfterLinkPlugin, RetryAfterLinkPlugin as RetryAfterPlugin, RetryLinkPlugin, TimeoutLinkPlugin };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Promisable, OrderablePlugin, Interceptor } from '@orpc/shared';
|
|
2
|
-
import { StandardRequest, StandardLazyResponse } from '@
|
|
3
|
-
import { C as ClientContext, a as ClientOptions, A as AnyORPCError, b as ClientLink } from './client.
|
|
2
|
+
import { StandardRequest, StandardLazyResponse } from '@standard-server/core';
|
|
3
|
+
import { C as ClientContext, a as ClientOptions, A as AnyORPCError, b as ClientLink } from './client.BoXZCzuS.mjs';
|
|
4
4
|
|
|
5
5
|
type StandardLinkCodecDecodedResponse = {
|
|
6
6
|
kind: 'output';
|