@orpc/client 2.0.0-beta.40 → 2.0.0-beta.42
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 +302 -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,170 @@ 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
|
+
const outgoingMessages = pendingMessages.filter((message) => openRequestIds.has(message.id));
|
|
184
|
+
try {
|
|
185
|
+
const request = {
|
|
186
|
+
url,
|
|
187
|
+
method,
|
|
188
|
+
headers: { ...headers, "orpc-batch": mode },
|
|
189
|
+
signal: controller.signal
|
|
190
|
+
};
|
|
191
|
+
if (method === "GET") {
|
|
192
|
+
const [pathname, search, hash] = parseStandardUrl(url);
|
|
193
|
+
const dataParam = `data=${safeEncodeURIComponent(stringifyJSON(outgoingMessages))}`;
|
|
194
|
+
const newUrl = search ? `${pathname}${search}&${dataParam}${hash ?? ""}` : `${pathname}?${dataParam}${hash ?? ""}`;
|
|
195
|
+
const maxUrlLength = await value(this.maxUrlLength, subOptionsList);
|
|
196
|
+
if (newUrl.length > maxUrlLength) {
|
|
197
|
+
const [first, second] = splitInHalf(groupItems);
|
|
198
|
+
suppressErrorFromCurrentBatch = true;
|
|
199
|
+
await Promise.all([
|
|
200
|
+
this.executeBatch(method, group, first),
|
|
201
|
+
this.executeBatchInOwnContext(method, group, second),
|
|
202
|
+
peer.close()
|
|
203
|
+
]);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
request.url = newUrl;
|
|
207
|
+
} else {
|
|
208
|
+
request.body = outgoingMessages;
|
|
209
|
+
}
|
|
210
|
+
batchResponse = await groupItems[0][0].next({
|
|
211
|
+
...subOptionsList[0],
|
|
212
|
+
context: value(group.context, subOptionsList),
|
|
213
|
+
path: value(group.path, subOptionsList) ?? [],
|
|
214
|
+
request,
|
|
215
|
+
signal: controller.signal
|
|
216
|
+
});
|
|
217
|
+
if (batchResponse.status >= 400) {
|
|
218
|
+
const resolveBody = once(() => batchResponse.resolveBody());
|
|
219
|
+
const errorResponse = { ...batchResponse, resolveBody };
|
|
220
|
+
groupItems.forEach(([subOptions, resolve]) => {
|
|
221
|
+
resolve(this.mapSubresponse(errorResponse, batchResponse, subOptions));
|
|
222
|
+
});
|
|
223
|
+
suppressErrorFromCurrentBatch = true;
|
|
224
|
+
await peer.close();
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const body = await batchResponse.resolveBody();
|
|
228
|
+
if (Array.isArray(body) && body.every((v) => isServerPeerSendMessage(v))) {
|
|
229
|
+
for (const message of body) {
|
|
230
|
+
await peer.message(message);
|
|
231
|
+
}
|
|
232
|
+
} else if (body instanceof Blob) {
|
|
233
|
+
await decodeLengthPrefixedBlob(body, peer);
|
|
234
|
+
} else if (body instanceof ReadableStream) {
|
|
235
|
+
await decodeLengthPrefixedStream(body, async (message) => {
|
|
236
|
+
await peer.message(message);
|
|
237
|
+
if (isLastServerMessage(message)) {
|
|
238
|
+
openRequestIds.delete(message.id);
|
|
239
|
+
cancelledRunningRequestIds.delete(message.id);
|
|
240
|
+
abortIfOnlyCancelledRemain();
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
} else {
|
|
244
|
+
throw new TypeError("Invalid batch response format.");
|
|
245
|
+
}
|
|
246
|
+
await peer.close(new TypeError("Batch response is incomplete."));
|
|
247
|
+
} catch (error) {
|
|
248
|
+
await peer.close(error);
|
|
249
|
+
}
|
|
250
|
+
} catch (error) {
|
|
251
|
+
groupItems.forEach(([, , reject]) => reject(error));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function isLastServerMessage(message) {
|
|
256
|
+
switch (message.kind) {
|
|
257
|
+
case "response":
|
|
258
|
+
return message.json.body !== void 0 || message.binary !== void 0 || message.json.headers?.["content-type"] === void 0 && message.json.headers?.["standard-server"] === void 0;
|
|
259
|
+
case "event-stream":
|
|
260
|
+
return message.json.event === "close" || message.json.event === "error";
|
|
261
|
+
case "octet-stream":
|
|
262
|
+
return message.json.close === true;
|
|
263
|
+
case "cancel":
|
|
264
|
+
return true;
|
|
265
|
+
default:
|
|
266
|
+
return false;
|
|
203
267
|
}
|
|
204
268
|
}
|
|
205
269
|
async function decodeLengthPrefixedBlob(blob, peer) {
|
|
@@ -224,7 +288,7 @@ async function decodeLengthPrefixedBlob(blob, peer) {
|
|
|
224
288
|
await peer.message(result.message);
|
|
225
289
|
}
|
|
226
290
|
}
|
|
227
|
-
async function decodeLengthPrefixedStream(stream,
|
|
291
|
+
async function decodeLengthPrefixedStream(stream, receive) {
|
|
228
292
|
const reader = stream.getReader();
|
|
229
293
|
let buffer = new Uint8Array(0);
|
|
230
294
|
try {
|
|
@@ -252,7 +316,7 @@ async function decodeLengthPrefixedStream(stream, peer) {
|
|
|
252
316
|
if (!result.matched || !isServerPeerSendMessage(result.message)) {
|
|
253
317
|
throw new TypeError("Invalid batch response: invalid message.");
|
|
254
318
|
}
|
|
255
|
-
await
|
|
319
|
+
await receive(result.message);
|
|
256
320
|
}
|
|
257
321
|
if (done) {
|
|
258
322
|
break;
|
|
@@ -268,10 +332,14 @@ class DedupeLinkPlugin {
|
|
|
268
332
|
before = ["~batch"];
|
|
269
333
|
groups;
|
|
270
334
|
filter;
|
|
335
|
+
scope;
|
|
336
|
+
wait;
|
|
271
337
|
queue = /* @__PURE__ */ new Map();
|
|
272
338
|
constructor(options) {
|
|
273
339
|
this.groups = options.groups;
|
|
274
340
|
this.filter = options.filter ?? (({ request }) => request.method === "GET" || request.method === "QUERY");
|
|
341
|
+
this.scope = options.scope ?? (() => void 0);
|
|
342
|
+
this.wait = options.wait ?? 0;
|
|
275
343
|
}
|
|
276
344
|
init(options) {
|
|
277
345
|
const transportInterceptor = (interceptorOptions) => {
|
|
@@ -282,78 +350,81 @@ class DedupeLinkPlugin {
|
|
|
282
350
|
if (!group) {
|
|
283
351
|
return interceptorOptions.next();
|
|
284
352
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
this.processPendingRequests();
|
|
289
|
-
}
|
|
290
|
-
|
|
353
|
+
const scope = this.scope(interceptorOptions);
|
|
354
|
+
return runWithSignal(interceptorOptions.request.signal, () => new Promise((resolve, reject) => {
|
|
355
|
+
if (!this.queue.size) {
|
|
356
|
+
defer(() => this.processPendingRequests(), this.wait);
|
|
357
|
+
}
|
|
358
|
+
this.enqueue(scope, group, { options: interceptorOptions, resolve, reject, runInOwnContext: captureAsyncContext() });
|
|
359
|
+
}));
|
|
291
360
|
};
|
|
292
361
|
return {
|
|
293
362
|
...options,
|
|
294
363
|
transportInterceptors: [...toArray(options.transportInterceptors), transportInterceptor]
|
|
295
364
|
};
|
|
296
365
|
}
|
|
297
|
-
enqueue(
|
|
298
|
-
let
|
|
366
|
+
enqueue(scope, group, caller) {
|
|
367
|
+
let groups = this.queue.get(scope);
|
|
368
|
+
if (!groups) {
|
|
369
|
+
groups = /* @__PURE__ */ new Map();
|
|
370
|
+
this.queue.set(scope, groups);
|
|
371
|
+
}
|
|
372
|
+
let queue = groups.get(group);
|
|
299
373
|
if (!queue) {
|
|
300
|
-
queue =
|
|
301
|
-
|
|
374
|
+
queue = /* @__PURE__ */ new Map();
|
|
375
|
+
groups.set(group, queue);
|
|
302
376
|
}
|
|
303
|
-
const requestKey = createRequestKey(options.path, options.request);
|
|
304
|
-
const matched = queue.
|
|
377
|
+
const requestKey = createRequestKey(caller.options.path, caller.options.request);
|
|
378
|
+
const matched = queue.get(requestKey);
|
|
305
379
|
if (matched) {
|
|
306
|
-
matched.
|
|
307
|
-
matched.signals.push(options.request.signal);
|
|
308
|
-
matched.resolves.push(resolve);
|
|
309
|
-
matched.rejects.push(reject);
|
|
380
|
+
matched.push(caller);
|
|
310
381
|
return;
|
|
311
382
|
}
|
|
312
|
-
queue.
|
|
313
|
-
requestKey,
|
|
314
|
-
options,
|
|
315
|
-
matchedOptions: [options],
|
|
316
|
-
signals: [options.request.signal],
|
|
317
|
-
resolves: [resolve],
|
|
318
|
-
rejects: [reject]
|
|
319
|
-
});
|
|
383
|
+
queue.set(requestKey, [caller]);
|
|
320
384
|
}
|
|
321
385
|
async processPendingRequests() {
|
|
322
|
-
const pending =
|
|
386
|
+
const pending = [...this.queue.values()];
|
|
323
387
|
this.queue.clear();
|
|
324
388
|
const executions = [];
|
|
325
|
-
for (const
|
|
326
|
-
for (const
|
|
327
|
-
|
|
389
|
+
for (const groups of pending) {
|
|
390
|
+
for (const [group, items] of groups) {
|
|
391
|
+
for (const callers of items.values()) {
|
|
392
|
+
const activeCallers = callers.filter((caller) => !caller.options.request.signal?.aborted);
|
|
393
|
+
const [first] = activeCallers;
|
|
394
|
+
if (first) {
|
|
395
|
+
executions.push(first.runInOwnContext(() => this.execute(group, activeCallers)));
|
|
396
|
+
}
|
|
397
|
+
}
|
|
328
398
|
}
|
|
329
399
|
}
|
|
330
400
|
await Promise.all(executions);
|
|
331
401
|
}
|
|
332
|
-
async execute(group,
|
|
402
|
+
async execute(group, activeCallers) {
|
|
403
|
+
const first = activeCallers[0];
|
|
404
|
+
const matchedOptions = activeCallers.map((caller) => caller.options);
|
|
333
405
|
try {
|
|
334
|
-
if (!shouldDedupe(
|
|
335
|
-
const response2 = await
|
|
336
|
-
|
|
406
|
+
if (!shouldDedupe(matchedOptions)) {
|
|
407
|
+
const response2 = await first.options.next(first.options);
|
|
408
|
+
first.resolve(response2);
|
|
337
409
|
return;
|
|
338
410
|
}
|
|
339
|
-
const context = value(group.context,
|
|
411
|
+
const context = value(group.context, matchedOptions);
|
|
412
|
+
const signals = matchedOptions.map((options) => options.request.signal);
|
|
340
413
|
const request = {
|
|
341
|
-
...
|
|
342
|
-
signal: allAbortSignal(
|
|
414
|
+
...first.options.request,
|
|
415
|
+
signal: allAbortSignal(signals)
|
|
343
416
|
};
|
|
344
|
-
const response = await
|
|
345
|
-
...
|
|
417
|
+
const response = await first.options.next({
|
|
418
|
+
...first.options,
|
|
346
419
|
request,
|
|
347
420
|
signal: request.signal,
|
|
348
421
|
context
|
|
349
422
|
});
|
|
350
|
-
const replicatedResponses = replicateLazyResponse(response,
|
|
351
|
-
|
|
352
|
-
resolve(replicatedResponses.pop());
|
|
353
|
-
}
|
|
423
|
+
const replicatedResponses = replicateLazyResponse(response, signals);
|
|
424
|
+
activeCallers.forEach((caller, index) => caller.resolve(replicatedResponses[index]));
|
|
354
425
|
} catch (error) {
|
|
355
|
-
for (const
|
|
356
|
-
reject(error);
|
|
426
|
+
for (const caller of activeCallers) {
|
|
427
|
+
caller.reject(error);
|
|
357
428
|
}
|
|
358
429
|
}
|
|
359
430
|
}
|
|
@@ -370,35 +441,76 @@ function createRequestKey(path, request) {
|
|
|
370
441
|
url: request.url
|
|
371
442
|
});
|
|
372
443
|
}
|
|
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;
|
|
444
|
+
function replicateLazyResponse(response, signals) {
|
|
445
|
+
let bodiesPromise;
|
|
446
|
+
return signals.map((signal, i) => ({
|
|
447
|
+
...response,
|
|
448
|
+
resolveBody: (hint) => runWithSignal(signal, async () => {
|
|
449
|
+
bodiesPromise ??= response.resolveBody(hint).then((body) => {
|
|
388
450
|
if (isAsyncIteratorObject(body)) {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
451
|
+
return replicateAsyncIterator(body, signals.length).map((replica, index) => closeAsyncIteratorOnAbort(replica, signals[index]));
|
|
452
|
+
}
|
|
453
|
+
if (body instanceof ReadableStream) {
|
|
454
|
+
return replicateReadableStream(body, signals.length).map((replica, index) => closeReadableStreamOnAbort(replica, signals[index]));
|
|
455
|
+
}
|
|
456
|
+
return signals.map(() => body);
|
|
457
|
+
});
|
|
458
|
+
return (await bodiesPromise)[i];
|
|
459
|
+
})
|
|
460
|
+
}));
|
|
461
|
+
}
|
|
462
|
+
function closeAsyncIteratorOnAbort(replica, signal) {
|
|
463
|
+
if (!signal) {
|
|
464
|
+
return replica;
|
|
465
|
+
}
|
|
466
|
+
const abort = () => replica.return().catch(() => {
|
|
467
|
+
});
|
|
468
|
+
if (signal.aborted) {
|
|
469
|
+
void abort();
|
|
470
|
+
} else {
|
|
471
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
472
|
+
}
|
|
473
|
+
return wrapAsyncIterator(replica, {
|
|
474
|
+
mapResult(result) {
|
|
475
|
+
throwIfAborted(signal);
|
|
476
|
+
return result;
|
|
477
|
+
},
|
|
478
|
+
onFinish: () => signal.removeEventListener("abort", abort)
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
function closeReadableStreamOnAbort(replica, signal) {
|
|
482
|
+
if (!signal) {
|
|
483
|
+
return replica;
|
|
484
|
+
}
|
|
485
|
+
const reader = replica.getReader();
|
|
486
|
+
const abort = () => reader.cancel(signal.reason).catch(() => {
|
|
487
|
+
});
|
|
488
|
+
if (signal.aborted) {
|
|
489
|
+
void abort();
|
|
490
|
+
} else {
|
|
491
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
492
|
+
}
|
|
493
|
+
return new ReadableStream({
|
|
494
|
+
async pull(controller) {
|
|
495
|
+
try {
|
|
496
|
+
const result = await reader.read();
|
|
497
|
+
throwIfAborted(signal);
|
|
498
|
+
if (result.done) {
|
|
499
|
+
signal.removeEventListener("abort", abort);
|
|
500
|
+
controller.close();
|
|
394
501
|
} else {
|
|
395
|
-
|
|
502
|
+
controller.enqueue(result.value);
|
|
396
503
|
}
|
|
397
|
-
|
|
504
|
+
} catch (error) {
|
|
505
|
+
signal.removeEventListener("abort", abort);
|
|
506
|
+
throw error;
|
|
398
507
|
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
|
|
508
|
+
},
|
|
509
|
+
async cancel(reason) {
|
|
510
|
+
signal.removeEventListener("abort", abort);
|
|
511
|
+
await reader.cancel(reason);
|
|
512
|
+
}
|
|
513
|
+
}, { highWaterMark: 0 });
|
|
402
514
|
}
|
|
403
515
|
function shouldDedupe(items) {
|
|
404
516
|
return items.length >= 2;
|
|
@@ -554,6 +666,7 @@ class RequestCompressionLinkPlugin {
|
|
|
554
666
|
}
|
|
555
667
|
}
|
|
556
668
|
|
|
669
|
+
const MAX_CONTENT_ENCODINGS = 5;
|
|
557
670
|
class ResponseCompressionLinkPlugin {
|
|
558
671
|
name = "~response-compression";
|
|
559
672
|
/**
|
|
@@ -596,6 +709,9 @@ class ResponseCompressionLinkPlugin {
|
|
|
596
709
|
if (!(stream instanceof ReadableStream)) {
|
|
597
710
|
return stream;
|
|
598
711
|
}
|
|
712
|
+
if (encodings.length > MAX_CONTENT_ENCODINGS) {
|
|
713
|
+
throw new TypeError("Too many content encodings.");
|
|
714
|
+
}
|
|
599
715
|
let decompressedStream = stream;
|
|
600
716
|
for (let i = encodings.length - 1; i >= 0; i--) {
|
|
601
717
|
decompressedStream = decompressedStream.pipeThrough(
|
|
@@ -662,6 +778,7 @@ class RetryLinkPlugin {
|
|
|
662
778
|
let currentError = initialError;
|
|
663
779
|
while (true) {
|
|
664
780
|
const updatedCallOptions = { ...callOptions, lastEventId };
|
|
781
|
+
let retryDelayMs = 0;
|
|
665
782
|
if (currentError) {
|
|
666
783
|
if (attempt > maxAttempts) {
|
|
667
784
|
throw currentError.error;
|
|
@@ -679,16 +796,11 @@ class RetryLinkPlugin {
|
|
|
679
796
|
if (!shouldRetryBool) {
|
|
680
797
|
throw currentError.error;
|
|
681
798
|
}
|
|
799
|
+
retryDelayMs = await value(retryDelay, attemptOptions);
|
|
682
800
|
callback = onRetry?.(attemptOptions);
|
|
683
801
|
}
|
|
684
802
|
try {
|
|
685
803
|
if (currentError) {
|
|
686
|
-
const retryDelayMs = await value(retryDelay, {
|
|
687
|
-
...updatedCallOptions,
|
|
688
|
-
attempt,
|
|
689
|
-
error: currentError.error,
|
|
690
|
-
lastEventRetry
|
|
691
|
-
});
|
|
692
804
|
await sleep(retryDelayMs, { signal: updatedCallOptions.signal });
|
|
693
805
|
attempt++;
|
|
694
806
|
}
|
|
@@ -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';
|