@orpc/client 2.0.0-beta.40 → 2.0.0-beta.41
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 +5 -5
- package/dist/adapters/fetch/index.d.mts +1 -1
- package/dist/adapters/fetch/index.d.ts +1 -1
- package/dist/adapters/fetch/index.mjs +5 -4
- package/dist/adapters/message-port/index.d.mts +1 -1
- package/dist/adapters/message-port/index.d.ts +1 -1
- package/dist/adapters/message-port/index.mjs +10 -5
- package/dist/adapters/standard/index.d.mts +2 -2
- package/dist/adapters/standard/index.d.ts +2 -2
- package/dist/adapters/standard/index.mjs +3 -3
- package/dist/adapters/websocket/index.d.mts +13 -3
- package/dist/adapters/websocket/index.d.ts +13 -3
- package/dist/adapters/websocket/index.mjs +34 -13
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.mjs +3 -3
- package/dist/plugins/index.d.mts +40 -0
- package/dist/plugins/index.d.ts +40 -0
- package/dist/plugins/index.mjs +301 -190
- package/dist/shared/{client.n8K9dFhY.d.mts → client.BPa_Mmuh.d.mts} +1 -1
- package/dist/shared/{client.n8K9dFhY.d.ts → client.BPa_Mmuh.d.ts} +1 -1
- package/dist/shared/{client.BWkR2vPU.d.mts → client.BjbdW5Rj.d.mts} +1 -1
- package/dist/shared/{client.VXsLhJtT.mjs → client.Bstqyaro.mjs} +1 -1
- package/dist/shared/{client.Dtv1JPkU.mjs → client.DD2eNne_.mjs} +1 -0
- package/dist/shared/{client.BnpRXnLi.d.ts → client.DiHfJmHK.d.ts} +1 -1
- package/dist/shared/{client.BYHnU_Z3.mjs → client.DpkGsZt-.mjs} +19 -2
- package/dist/shared/{client.BoXZCzuS.d.ts → client.Dw9_40WO.d.mts} +1 -0
- package/dist/shared/{client.BoXZCzuS.d.mts → client.Dw9_40WO.d.ts} +1 -0
- package/package.json +7 -5
package/dist/plugins/index.mjs
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { toArray, value, splitInHalf, safeEncodeURIComponent, stringifyJSON, once, isAsyncIteratorObject, defer, loadBytes, allAbortSignal, replicateAsyncIterator, replicateReadableStream, isCompressibleContentType, override, AsyncIteratorClass, sleep, AbortError, anyAbortSignal } from '@orpc/shared';
|
|
1
|
+
import { toArray, value, splitInHalf, promiseWithResolvers, safeEncodeURIComponent, stringifyJSON, once, isAsyncIteratorObject, defer, captureAsyncContext, loadBytes, allAbortSignal, runWithSignal, replicateAsyncIterator, replicateReadableStream, wrapAsyncIterator, throwIfAborted, isCompressibleContentType, override, AsyncIteratorClass, sleep, AbortError, anyAbortSignal } from '@orpc/shared';
|
|
2
2
|
import { parseStandardUrl, flattenStandardHeader, generateContentDisposition, getEventMeta } from '@standard-server/core';
|
|
3
3
|
import { ClientPeer, isServerPeerSendMessage, decodePeerMessage } from '@standard-server/peer';
|
|
4
4
|
import { toFetchHeaders, toStandardBody } from '@standard-server/fetch';
|
|
5
|
-
import { C as COMMON_ERROR_STATUS_MAP } from '../shared/client.
|
|
5
|
+
import { C as COMMON_ERROR_STATUS_MAP } from '../shared/client.DD2eNne_.mjs';
|
|
6
6
|
|
|
7
7
|
class BatchLinkPlugin {
|
|
8
8
|
name = "~batch";
|
|
9
9
|
groups;
|
|
10
10
|
filter;
|
|
11
|
+
scope;
|
|
11
12
|
maxSize;
|
|
13
|
+
wait;
|
|
12
14
|
mode;
|
|
13
15
|
batchUrl;
|
|
14
16
|
maxUrlLength;
|
|
@@ -19,7 +21,9 @@ class BatchLinkPlugin {
|
|
|
19
21
|
constructor(options) {
|
|
20
22
|
this.groups = options.groups;
|
|
21
23
|
this.filter = options.filter ?? (() => true);
|
|
24
|
+
this.scope = options.scope ?? (() => void 0);
|
|
22
25
|
this.maxSize = options.maxSize ?? 10;
|
|
26
|
+
this.wait = options.wait ?? 0;
|
|
23
27
|
this.mode = options.mode ?? "streaming";
|
|
24
28
|
this.batchUrl = options.url ?? ((options2) => {
|
|
25
29
|
const [pathname] = parseStandardUrl(options2[0].request.url);
|
|
@@ -65,20 +69,30 @@ class BatchLinkPlugin {
|
|
|
65
69
|
}
|
|
66
70
|
init(options) {
|
|
67
71
|
const transportInterceptor = async (interceptorOptions) => {
|
|
68
|
-
|
|
72
|
+
const { body, signal } = interceptorOptions.request;
|
|
73
|
+
if (body instanceof Blob || body instanceof FormData || body instanceof ReadableStream || isAsyncIteratorObject(body) || signal?.aborted || !value(this.filter, interceptorOptions)) {
|
|
69
74
|
return interceptorOptions.next();
|
|
70
75
|
}
|
|
71
76
|
const group = this.groups.find((group2) => value(group2.condition, interceptorOptions));
|
|
72
77
|
if (!group) {
|
|
73
78
|
return interceptorOptions.next();
|
|
74
79
|
}
|
|
80
|
+
const scope = this.scope(interceptorOptions);
|
|
75
81
|
return new Promise((resolve, reject) => {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
this.queue.set(group, queue);
|
|
82
|
+
if (!this.queue.size) {
|
|
83
|
+
defer(() => this.processPendingBatches(), this.wait);
|
|
79
84
|
}
|
|
80
|
-
queue.
|
|
81
|
-
|
|
85
|
+
let groups = this.queue.get(scope);
|
|
86
|
+
if (!groups) {
|
|
87
|
+
groups = /* @__PURE__ */ new Map();
|
|
88
|
+
this.queue.set(scope, groups);
|
|
89
|
+
}
|
|
90
|
+
let queue = groups.get(group);
|
|
91
|
+
if (!queue) {
|
|
92
|
+
queue = [];
|
|
93
|
+
groups.set(group, queue);
|
|
94
|
+
}
|
|
95
|
+
queue.push([interceptorOptions, resolve, reject, captureAsyncContext()]);
|
|
82
96
|
});
|
|
83
97
|
};
|
|
84
98
|
return {
|
|
@@ -86,120 +100,169 @@ class BatchLinkPlugin {
|
|
|
86
100
|
transportInterceptors: [...toArray(options.transportInterceptors), transportInterceptor]
|
|
87
101
|
};
|
|
88
102
|
}
|
|
89
|
-
|
|
90
|
-
const pending =
|
|
103
|
+
processPendingBatches() {
|
|
104
|
+
const pending = [...this.queue.values()];
|
|
91
105
|
this.queue.clear();
|
|
92
|
-
for (const
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
106
|
+
for (const groups of pending) {
|
|
107
|
+
for (const [group, items] of groups) {
|
|
108
|
+
const getItems = items.filter(([options]) => options.request.method === "GET");
|
|
109
|
+
const queryItems = items.filter(([options]) => options.request.method === "QUERY");
|
|
110
|
+
const unsafeItems = items.filter(([options]) => options.request.method !== "GET" && options.request.method !== "QUERY");
|
|
111
|
+
this.executeBatchInOwnContext("GET", group, getItems);
|
|
112
|
+
this.executeBatchInOwnContext("QUERY", group, queryItems);
|
|
113
|
+
this.executeBatchInOwnContext("POST", group, unsafeItems);
|
|
114
|
+
}
|
|
99
115
|
}
|
|
100
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Runs the batch in the async context of its first request instead of the timer's,
|
|
119
|
+
* so the transport and batch options see that caller's request state.
|
|
120
|
+
* `executeBatch` always runs in the context of its first request.
|
|
121
|
+
*/
|
|
122
|
+
async executeBatchInOwnContext(method, group, groupItems) {
|
|
123
|
+
await groupItems[0]?.[3](() => this.executeBatch(method, group, groupItems));
|
|
124
|
+
}
|
|
101
125
|
async executeBatch(method, group, groupItems) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
options.next().then(resolve).catch(reject);
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
const subOptionsList = groupItems.map(([options]) => options);
|
|
111
|
-
const maxSize = await value(this.maxSize, subOptionsList);
|
|
112
|
-
if (groupItems.length > maxSize) {
|
|
113
|
-
const [first, second] = splitInHalf(groupItems);
|
|
114
|
-
await Promise.all([
|
|
115
|
-
this.executeBatch(method, group, first),
|
|
116
|
-
this.executeBatch(method, group, second)
|
|
117
|
-
]);
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
const url = await value(this.batchUrl, subOptionsList);
|
|
121
|
-
const headers = await value(this.batchHeaders, subOptionsList);
|
|
122
|
-
const mode = value(this.mode, subOptionsList);
|
|
123
|
-
let suppressErrorFromCurrentBatch = false;
|
|
124
|
-
const controller = new AbortController();
|
|
125
|
-
const pendingMessages = [];
|
|
126
|
-
let batchResponse;
|
|
127
|
-
const peer = new ClientPeer(async (message) => {
|
|
128
|
-
pendingMessages.push(message);
|
|
129
|
-
if (message.kind === "cancel" && pendingMessages.filter((m) => m.kind === "cancel").length === groupItems.length) {
|
|
130
|
-
controller.abort();
|
|
126
|
+
try {
|
|
127
|
+
if (groupItems.length === 1) {
|
|
128
|
+
const [options, resolve, reject] = groupItems[0];
|
|
129
|
+
options.next().then(resolve).catch(reject);
|
|
130
|
+
return;
|
|
131
131
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (method === "GET") {
|
|
142
|
-
const [pathname, search, hash] = parseStandardUrl(url);
|
|
143
|
-
const dataParam = `data=${safeEncodeURIComponent(stringifyJSON(pendingMessages))}`;
|
|
144
|
-
const newUrl = search ? `${pathname}${search}&${dataParam}${hash ?? ""}` : `${pathname}?${dataParam}${hash ?? ""}`;
|
|
145
|
-
const maxUrlLength = await value(this.maxUrlLength, subOptionsList);
|
|
146
|
-
if (newUrl.length > maxUrlLength) {
|
|
147
|
-
const [first, second] = splitInHalf(groupItems);
|
|
148
|
-
suppressErrorFromCurrentBatch = true;
|
|
149
|
-
await Promise.all([
|
|
150
|
-
this.executeBatch(method, group, first),
|
|
151
|
-
this.executeBatch(method, group, second),
|
|
152
|
-
peer.close()
|
|
153
|
-
]);
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
request.url = newUrl;
|
|
157
|
-
} else {
|
|
158
|
-
request.body = pendingMessages;
|
|
159
|
-
}
|
|
160
|
-
batchResponse = await groupItems[0][0].next({
|
|
161
|
-
...subOptionsList[0],
|
|
162
|
-
context: value(group.context, subOptionsList),
|
|
163
|
-
path: value(group.path, subOptionsList) ?? [],
|
|
164
|
-
request,
|
|
165
|
-
signal: controller.signal
|
|
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
|
-
}
|
|
177
|
-
const body = await batchResponse.resolveBody();
|
|
178
|
-
if (Array.isArray(body) && body.every((v) => isServerPeerSendMessage(v))) {
|
|
179
|
-
for (const message2 of body) {
|
|
180
|
-
await peer.message(message2);
|
|
181
|
-
}
|
|
182
|
-
} else if (body instanceof Blob) {
|
|
183
|
-
await decodeLengthPrefixedBlob(body, peer);
|
|
184
|
-
} else if (body instanceof ReadableStream) {
|
|
185
|
-
await decodeLengthPrefixedStream(body, peer);
|
|
186
|
-
} else {
|
|
187
|
-
throw new TypeError("Invalid batch response format.");
|
|
188
|
-
}
|
|
189
|
-
await peer.close(new TypeError("Batch response is incomplete."));
|
|
190
|
-
} catch (error) {
|
|
191
|
-
await peer.close(error);
|
|
192
|
-
}
|
|
193
|
-
})();
|
|
132
|
+
const subOptionsList = groupItems.map(([options]) => options);
|
|
133
|
+
const maxSize = await value(this.maxSize, subOptionsList);
|
|
134
|
+
if (groupItems.length > maxSize) {
|
|
135
|
+
const [first, second] = splitInHalf(groupItems);
|
|
136
|
+
await Promise.all([
|
|
137
|
+
this.executeBatch(method, group, first),
|
|
138
|
+
this.executeBatchInOwnContext(method, group, second)
|
|
139
|
+
]);
|
|
140
|
+
return;
|
|
194
141
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
142
|
+
const url = await value(this.batchUrl, subOptionsList);
|
|
143
|
+
const headers = await value(this.batchHeaders, subOptionsList);
|
|
144
|
+
const mode = value(this.mode, subOptionsList);
|
|
145
|
+
let suppressErrorFromCurrentBatch = false;
|
|
146
|
+
const controller = new AbortController();
|
|
147
|
+
const pendingMessages = [];
|
|
148
|
+
const subrequests = groupItems.map(([subOptions]) => this.mapSubrequest(subOptions, { url, headers }));
|
|
149
|
+
let batchResponse;
|
|
150
|
+
let markRequestSent;
|
|
151
|
+
const openRequestIds = /* @__PURE__ */ new Set();
|
|
152
|
+
const cancelledRunningRequestIds = /* @__PURE__ */ new Set();
|
|
153
|
+
let isBatchSent = false;
|
|
154
|
+
const abortIfOnlyCancelledRemain = () => {
|
|
155
|
+
if (openRequestIds.size === 0 && cancelledRunningRequestIds.size > 0) {
|
|
156
|
+
controller.abort();
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
const peer = new ClientPeer(async (message) => {
|
|
160
|
+
pendingMessages.push(message);
|
|
161
|
+
if (message.kind === "request") {
|
|
162
|
+
openRequestIds.add(message.id);
|
|
163
|
+
markRequestSent?.();
|
|
164
|
+
} else if (message.kind === "cancel" && openRequestIds.delete(message.id) && isBatchSent) {
|
|
165
|
+
cancelledRunningRequestIds.add(message.id);
|
|
166
|
+
abortIfOnlyCancelledRemain();
|
|
200
167
|
}
|
|
201
168
|
});
|
|
202
|
-
|
|
169
|
+
for (const [index, [subOptions, resolve, reject]] of groupItems.entries()) {
|
|
170
|
+
const sent = promiseWithResolvers();
|
|
171
|
+
markRequestSent = sent.resolve;
|
|
172
|
+
peer.request(subrequests[index]).then((subResponse) => resolve(this.mapSubresponse(subResponse, batchResponse, subOptions))).catch((error) => {
|
|
173
|
+
if (!suppressErrorFromCurrentBatch) {
|
|
174
|
+
reject(error);
|
|
175
|
+
}
|
|
176
|
+
}).then(sent.resolve);
|
|
177
|
+
await sent.promise;
|
|
178
|
+
}
|
|
179
|
+
if (openRequestIds.size === 0) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
isBatchSent = true;
|
|
183
|
+
try {
|
|
184
|
+
const request = {
|
|
185
|
+
url,
|
|
186
|
+
method,
|
|
187
|
+
headers: { ...headers, "orpc-batch": mode },
|
|
188
|
+
signal: controller.signal
|
|
189
|
+
};
|
|
190
|
+
if (method === "GET") {
|
|
191
|
+
const [pathname, search, hash] = parseStandardUrl(url);
|
|
192
|
+
const dataParam = `data=${safeEncodeURIComponent(stringifyJSON(pendingMessages))}`;
|
|
193
|
+
const newUrl = search ? `${pathname}${search}&${dataParam}${hash ?? ""}` : `${pathname}?${dataParam}${hash ?? ""}`;
|
|
194
|
+
const maxUrlLength = await value(this.maxUrlLength, subOptionsList);
|
|
195
|
+
if (newUrl.length > maxUrlLength) {
|
|
196
|
+
const [first, second] = splitInHalf(groupItems);
|
|
197
|
+
suppressErrorFromCurrentBatch = true;
|
|
198
|
+
await Promise.all([
|
|
199
|
+
this.executeBatch(method, group, first),
|
|
200
|
+
this.executeBatchInOwnContext(method, group, second),
|
|
201
|
+
peer.close()
|
|
202
|
+
]);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
request.url = newUrl;
|
|
206
|
+
} else {
|
|
207
|
+
request.body = pendingMessages;
|
|
208
|
+
}
|
|
209
|
+
batchResponse = await groupItems[0][0].next({
|
|
210
|
+
...subOptionsList[0],
|
|
211
|
+
context: value(group.context, subOptionsList),
|
|
212
|
+
path: value(group.path, subOptionsList) ?? [],
|
|
213
|
+
request,
|
|
214
|
+
signal: controller.signal
|
|
215
|
+
});
|
|
216
|
+
if (batchResponse.status >= 400) {
|
|
217
|
+
const resolveBody = once(() => batchResponse.resolveBody());
|
|
218
|
+
const errorResponse = { ...batchResponse, resolveBody };
|
|
219
|
+
groupItems.forEach(([subOptions, resolve]) => {
|
|
220
|
+
resolve(this.mapSubresponse(errorResponse, batchResponse, subOptions));
|
|
221
|
+
});
|
|
222
|
+
suppressErrorFromCurrentBatch = true;
|
|
223
|
+
await peer.close();
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const body = await batchResponse.resolveBody();
|
|
227
|
+
if (Array.isArray(body) && body.every((v) => isServerPeerSendMessage(v))) {
|
|
228
|
+
for (const message of body) {
|
|
229
|
+
await peer.message(message);
|
|
230
|
+
}
|
|
231
|
+
} else if (body instanceof Blob) {
|
|
232
|
+
await decodeLengthPrefixedBlob(body, peer);
|
|
233
|
+
} else if (body instanceof ReadableStream) {
|
|
234
|
+
await decodeLengthPrefixedStream(body, async (message) => {
|
|
235
|
+
await peer.message(message);
|
|
236
|
+
if (isLastServerMessage(message)) {
|
|
237
|
+
openRequestIds.delete(message.id);
|
|
238
|
+
cancelledRunningRequestIds.delete(message.id);
|
|
239
|
+
abortIfOnlyCancelledRemain();
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
} else {
|
|
243
|
+
throw new TypeError("Invalid batch response format.");
|
|
244
|
+
}
|
|
245
|
+
await peer.close(new TypeError("Batch response is incomplete."));
|
|
246
|
+
} catch (error) {
|
|
247
|
+
await peer.close(error);
|
|
248
|
+
}
|
|
249
|
+
} catch (error) {
|
|
250
|
+
groupItems.forEach(([, , reject]) => reject(error));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function isLastServerMessage(message) {
|
|
255
|
+
switch (message.kind) {
|
|
256
|
+
case "response":
|
|
257
|
+
return message.json.body !== void 0 || message.binary !== void 0 || message.json.headers?.["content-type"] === void 0 && message.json.headers?.["standard-server"] === void 0;
|
|
258
|
+
case "event-stream":
|
|
259
|
+
return message.json.event === "close" || message.json.event === "error";
|
|
260
|
+
case "octet-stream":
|
|
261
|
+
return message.json.close === true;
|
|
262
|
+
case "cancel":
|
|
263
|
+
return true;
|
|
264
|
+
default:
|
|
265
|
+
return false;
|
|
203
266
|
}
|
|
204
267
|
}
|
|
205
268
|
async function decodeLengthPrefixedBlob(blob, peer) {
|
|
@@ -224,7 +287,7 @@ async function decodeLengthPrefixedBlob(blob, peer) {
|
|
|
224
287
|
await peer.message(result.message);
|
|
225
288
|
}
|
|
226
289
|
}
|
|
227
|
-
async function decodeLengthPrefixedStream(stream,
|
|
290
|
+
async function decodeLengthPrefixedStream(stream, receive) {
|
|
228
291
|
const reader = stream.getReader();
|
|
229
292
|
let buffer = new Uint8Array(0);
|
|
230
293
|
try {
|
|
@@ -252,7 +315,7 @@ async function decodeLengthPrefixedStream(stream, peer) {
|
|
|
252
315
|
if (!result.matched || !isServerPeerSendMessage(result.message)) {
|
|
253
316
|
throw new TypeError("Invalid batch response: invalid message.");
|
|
254
317
|
}
|
|
255
|
-
await
|
|
318
|
+
await receive(result.message);
|
|
256
319
|
}
|
|
257
320
|
if (done) {
|
|
258
321
|
break;
|
|
@@ -268,10 +331,14 @@ class DedupeLinkPlugin {
|
|
|
268
331
|
before = ["~batch"];
|
|
269
332
|
groups;
|
|
270
333
|
filter;
|
|
334
|
+
scope;
|
|
335
|
+
wait;
|
|
271
336
|
queue = /* @__PURE__ */ new Map();
|
|
272
337
|
constructor(options) {
|
|
273
338
|
this.groups = options.groups;
|
|
274
339
|
this.filter = options.filter ?? (({ request }) => request.method === "GET" || request.method === "QUERY");
|
|
340
|
+
this.scope = options.scope ?? (() => void 0);
|
|
341
|
+
this.wait = options.wait ?? 0;
|
|
275
342
|
}
|
|
276
343
|
init(options) {
|
|
277
344
|
const transportInterceptor = (interceptorOptions) => {
|
|
@@ -282,78 +349,81 @@ class DedupeLinkPlugin {
|
|
|
282
349
|
if (!group) {
|
|
283
350
|
return interceptorOptions.next();
|
|
284
351
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
this.processPendingRequests();
|
|
289
|
-
}
|
|
290
|
-
|
|
352
|
+
const scope = this.scope(interceptorOptions);
|
|
353
|
+
return runWithSignal(interceptorOptions.request.signal, () => new Promise((resolve, reject) => {
|
|
354
|
+
if (!this.queue.size) {
|
|
355
|
+
defer(() => this.processPendingRequests(), this.wait);
|
|
356
|
+
}
|
|
357
|
+
this.enqueue(scope, group, { options: interceptorOptions, resolve, reject, runInOwnContext: captureAsyncContext() });
|
|
358
|
+
}));
|
|
291
359
|
};
|
|
292
360
|
return {
|
|
293
361
|
...options,
|
|
294
362
|
transportInterceptors: [...toArray(options.transportInterceptors), transportInterceptor]
|
|
295
363
|
};
|
|
296
364
|
}
|
|
297
|
-
enqueue(
|
|
298
|
-
let
|
|
365
|
+
enqueue(scope, group, caller) {
|
|
366
|
+
let groups = this.queue.get(scope);
|
|
367
|
+
if (!groups) {
|
|
368
|
+
groups = /* @__PURE__ */ new Map();
|
|
369
|
+
this.queue.set(scope, groups);
|
|
370
|
+
}
|
|
371
|
+
let queue = groups.get(group);
|
|
299
372
|
if (!queue) {
|
|
300
|
-
queue =
|
|
301
|
-
|
|
373
|
+
queue = /* @__PURE__ */ new Map();
|
|
374
|
+
groups.set(group, queue);
|
|
302
375
|
}
|
|
303
|
-
const requestKey = createRequestKey(options.path, options.request);
|
|
304
|
-
const matched = queue.
|
|
376
|
+
const requestKey = createRequestKey(caller.options.path, caller.options.request);
|
|
377
|
+
const matched = queue.get(requestKey);
|
|
305
378
|
if (matched) {
|
|
306
|
-
matched.
|
|
307
|
-
matched.signals.push(options.request.signal);
|
|
308
|
-
matched.resolves.push(resolve);
|
|
309
|
-
matched.rejects.push(reject);
|
|
379
|
+
matched.push(caller);
|
|
310
380
|
return;
|
|
311
381
|
}
|
|
312
|
-
queue.
|
|
313
|
-
requestKey,
|
|
314
|
-
options,
|
|
315
|
-
matchedOptions: [options],
|
|
316
|
-
signals: [options.request.signal],
|
|
317
|
-
resolves: [resolve],
|
|
318
|
-
rejects: [reject]
|
|
319
|
-
});
|
|
382
|
+
queue.set(requestKey, [caller]);
|
|
320
383
|
}
|
|
321
384
|
async processPendingRequests() {
|
|
322
|
-
const pending =
|
|
385
|
+
const pending = [...this.queue.values()];
|
|
323
386
|
this.queue.clear();
|
|
324
387
|
const executions = [];
|
|
325
|
-
for (const
|
|
326
|
-
for (const
|
|
327
|
-
|
|
388
|
+
for (const groups of pending) {
|
|
389
|
+
for (const [group, items] of groups) {
|
|
390
|
+
for (const callers of items.values()) {
|
|
391
|
+
const activeCallers = callers.filter((caller) => !caller.options.request.signal?.aborted);
|
|
392
|
+
const [first] = activeCallers;
|
|
393
|
+
if (first) {
|
|
394
|
+
executions.push(first.runInOwnContext(() => this.execute(group, activeCallers)));
|
|
395
|
+
}
|
|
396
|
+
}
|
|
328
397
|
}
|
|
329
398
|
}
|
|
330
399
|
await Promise.all(executions);
|
|
331
400
|
}
|
|
332
|
-
async execute(group,
|
|
401
|
+
async execute(group, activeCallers) {
|
|
402
|
+
const first = activeCallers[0];
|
|
403
|
+
const matchedOptions = activeCallers.map((caller) => caller.options);
|
|
333
404
|
try {
|
|
334
|
-
if (!shouldDedupe(
|
|
335
|
-
const response2 = await
|
|
336
|
-
|
|
405
|
+
if (!shouldDedupe(matchedOptions)) {
|
|
406
|
+
const response2 = await first.options.next(first.options);
|
|
407
|
+
first.resolve(response2);
|
|
337
408
|
return;
|
|
338
409
|
}
|
|
339
|
-
const context = value(group.context,
|
|
410
|
+
const context = value(group.context, matchedOptions);
|
|
411
|
+
const signals = matchedOptions.map((options) => options.request.signal);
|
|
340
412
|
const request = {
|
|
341
|
-
...
|
|
342
|
-
signal: allAbortSignal(
|
|
413
|
+
...first.options.request,
|
|
414
|
+
signal: allAbortSignal(signals)
|
|
343
415
|
};
|
|
344
|
-
const response = await
|
|
345
|
-
...
|
|
416
|
+
const response = await first.options.next({
|
|
417
|
+
...first.options,
|
|
346
418
|
request,
|
|
347
419
|
signal: request.signal,
|
|
348
420
|
context
|
|
349
421
|
});
|
|
350
|
-
const replicatedResponses = replicateLazyResponse(response,
|
|
351
|
-
|
|
352
|
-
resolve(replicatedResponses.pop());
|
|
353
|
-
}
|
|
422
|
+
const replicatedResponses = replicateLazyResponse(response, signals);
|
|
423
|
+
activeCallers.forEach((caller, index) => caller.resolve(replicatedResponses[index]));
|
|
354
424
|
} catch (error) {
|
|
355
|
-
for (const
|
|
356
|
-
reject(error);
|
|
425
|
+
for (const caller of activeCallers) {
|
|
426
|
+
caller.reject(error);
|
|
357
427
|
}
|
|
358
428
|
}
|
|
359
429
|
}
|
|
@@ -370,35 +440,76 @@ function createRequestKey(path, request) {
|
|
|
370
440
|
url: request.url
|
|
371
441
|
});
|
|
372
442
|
}
|
|
373
|
-
function replicateLazyResponse(response,
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
let resolvedBody;
|
|
380
|
-
replicated.push({
|
|
381
|
-
...response,
|
|
382
|
-
resolveBody: async (hint) => {
|
|
383
|
-
if (resolvedBody) {
|
|
384
|
-
return resolvedBody.body;
|
|
385
|
-
}
|
|
386
|
-
bodyPromise ??= response.resolveBody(hint);
|
|
387
|
-
const body = await bodyPromise;
|
|
443
|
+
function replicateLazyResponse(response, signals) {
|
|
444
|
+
let bodiesPromise;
|
|
445
|
+
return signals.map((signal, i) => ({
|
|
446
|
+
...response,
|
|
447
|
+
resolveBody: (hint) => runWithSignal(signal, async () => {
|
|
448
|
+
bodiesPromise ??= response.resolveBody(hint).then((body) => {
|
|
388
449
|
if (isAsyncIteratorObject(body)) {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
450
|
+
return replicateAsyncIterator(body, signals.length).map((replica, index) => closeAsyncIteratorOnAbort(replica, signals[index]));
|
|
451
|
+
}
|
|
452
|
+
if (body instanceof ReadableStream) {
|
|
453
|
+
return replicateReadableStream(body, signals.length).map((replica, index) => closeReadableStreamOnAbort(replica, signals[index]));
|
|
454
|
+
}
|
|
455
|
+
return signals.map(() => body);
|
|
456
|
+
});
|
|
457
|
+
return (await bodiesPromise)[i];
|
|
458
|
+
})
|
|
459
|
+
}));
|
|
460
|
+
}
|
|
461
|
+
function closeAsyncIteratorOnAbort(replica, signal) {
|
|
462
|
+
if (!signal) {
|
|
463
|
+
return replica;
|
|
464
|
+
}
|
|
465
|
+
const abort = () => replica.return().catch(() => {
|
|
466
|
+
});
|
|
467
|
+
if (signal.aborted) {
|
|
468
|
+
void abort();
|
|
469
|
+
} else {
|
|
470
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
471
|
+
}
|
|
472
|
+
return wrapAsyncIterator(replica, {
|
|
473
|
+
mapResult(result) {
|
|
474
|
+
throwIfAborted(signal);
|
|
475
|
+
return result;
|
|
476
|
+
},
|
|
477
|
+
onFinish: () => signal.removeEventListener("abort", abort)
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
function closeReadableStreamOnAbort(replica, signal) {
|
|
481
|
+
if (!signal) {
|
|
482
|
+
return replica;
|
|
483
|
+
}
|
|
484
|
+
const reader = replica.getReader();
|
|
485
|
+
const abort = () => reader.cancel(signal.reason).catch(() => {
|
|
486
|
+
});
|
|
487
|
+
if (signal.aborted) {
|
|
488
|
+
void abort();
|
|
489
|
+
} else {
|
|
490
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
491
|
+
}
|
|
492
|
+
return new ReadableStream({
|
|
493
|
+
async pull(controller) {
|
|
494
|
+
try {
|
|
495
|
+
const result = await reader.read();
|
|
496
|
+
throwIfAborted(signal);
|
|
497
|
+
if (result.done) {
|
|
498
|
+
signal.removeEventListener("abort", abort);
|
|
499
|
+
controller.close();
|
|
394
500
|
} else {
|
|
395
|
-
|
|
501
|
+
controller.enqueue(result.value);
|
|
396
502
|
}
|
|
397
|
-
|
|
503
|
+
} catch (error) {
|
|
504
|
+
signal.removeEventListener("abort", abort);
|
|
505
|
+
throw error;
|
|
398
506
|
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
|
|
507
|
+
},
|
|
508
|
+
async cancel(reason) {
|
|
509
|
+
signal.removeEventListener("abort", abort);
|
|
510
|
+
await reader.cancel(reason);
|
|
511
|
+
}
|
|
512
|
+
}, { highWaterMark: 0 });
|
|
402
513
|
}
|
|
403
514
|
function shouldDedupe(items) {
|
|
404
515
|
return items.length >= 2;
|
|
@@ -554,6 +665,7 @@ class RequestCompressionLinkPlugin {
|
|
|
554
665
|
}
|
|
555
666
|
}
|
|
556
667
|
|
|
668
|
+
const MAX_CONTENT_ENCODINGS = 5;
|
|
557
669
|
class ResponseCompressionLinkPlugin {
|
|
558
670
|
name = "~response-compression";
|
|
559
671
|
/**
|
|
@@ -596,6 +708,9 @@ class ResponseCompressionLinkPlugin {
|
|
|
596
708
|
if (!(stream instanceof ReadableStream)) {
|
|
597
709
|
return stream;
|
|
598
710
|
}
|
|
711
|
+
if (encodings.length > MAX_CONTENT_ENCODINGS) {
|
|
712
|
+
throw new TypeError("Too many content encodings.");
|
|
713
|
+
}
|
|
599
714
|
let decompressedStream = stream;
|
|
600
715
|
for (let i = encodings.length - 1; i >= 0; i--) {
|
|
601
716
|
decompressedStream = decompressedStream.pipeThrough(
|
|
@@ -662,6 +777,7 @@ class RetryLinkPlugin {
|
|
|
662
777
|
let currentError = initialError;
|
|
663
778
|
while (true) {
|
|
664
779
|
const updatedCallOptions = { ...callOptions, lastEventId };
|
|
780
|
+
let retryDelayMs = 0;
|
|
665
781
|
if (currentError) {
|
|
666
782
|
if (attempt > maxAttempts) {
|
|
667
783
|
throw currentError.error;
|
|
@@ -679,16 +795,11 @@ class RetryLinkPlugin {
|
|
|
679
795
|
if (!shouldRetryBool) {
|
|
680
796
|
throw currentError.error;
|
|
681
797
|
}
|
|
798
|
+
retryDelayMs = await value(retryDelay, attemptOptions);
|
|
682
799
|
callback = onRetry?.(attemptOptions);
|
|
683
800
|
}
|
|
684
801
|
try {
|
|
685
802
|
if (currentError) {
|
|
686
|
-
const retryDelayMs = await value(retryDelay, {
|
|
687
|
-
...updatedCallOptions,
|
|
688
|
-
attempt,
|
|
689
|
-
error: currentError.error,
|
|
690
|
-
lastEventRetry
|
|
691
|
-
});
|
|
692
803
|
await sleep(retryDelayMs, { signal: updatedCallOptions.signal });
|
|
693
804
|
attempt++;
|
|
694
805
|
}
|
|
@@ -66,7 +66,7 @@ interface RPCJsonSerializerOptions {
|
|
|
66
66
|
* handlers: { url: undefined }
|
|
67
67
|
* ```
|
|
68
68
|
*
|
|
69
|
-
* Built-in type keys: `undefined`, `bigint`, `date`, `nan`, `url`, `set`, `map`.
|
|
69
|
+
* Built-in type keys: `undefined`, `bigint`, `date`, `nan`, `infinity`, `url`, `set`, `map`.
|
|
70
70
|
*/
|
|
71
71
|
handlers?: Record<string, undefined | RPCJsonSerializerHandler> | undefined;
|
|
72
72
|
/**
|
|
@@ -66,7 +66,7 @@ interface RPCJsonSerializerOptions {
|
|
|
66
66
|
* handlers: { url: undefined }
|
|
67
67
|
* ```
|
|
68
68
|
*
|
|
69
|
-
* Built-in type keys: `undefined`, `bigint`, `date`, `nan`, `url`, `set`, `map`.
|
|
69
|
+
* Built-in type keys: `undefined`, `bigint`, `date`, `nan`, `infinity`, `url`, `set`, `map`.
|
|
70
70
|
*/
|
|
71
71
|
handlers?: Record<string, undefined | RPCJsonSerializerHandler> | undefined;
|
|
72
72
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Promisable, OrderablePlugin, Interceptor } from '@orpc/shared';
|
|
2
2
|
import { StandardRequest, StandardLazyResponse } from '@standard-server/core';
|
|
3
|
-
import { C as ClientContext, a as ClientOptions, A as AnyORPCError, b as ClientLink } from './client.
|
|
3
|
+
import { C as ClientContext, a as ClientOptions, A as AnyORPCError, b as ClientLink } from './client.Dw9_40WO.mjs';
|
|
4
4
|
|
|
5
5
|
type StandardLinkCodecDecodedResponse = {
|
|
6
6
|
kind: 'output';
|