@orpc/client 0.0.0-next.28aa60a → 0.0.0-next.29a3f6d

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.
Files changed (34) hide show
  1. package/README.md +28 -22
  2. package/dist/adapters/fetch/index.d.mts +31 -15
  3. package/dist/adapters/fetch/index.d.ts +31 -15
  4. package/dist/adapters/fetch/index.mjs +26 -18
  5. package/dist/adapters/message-port/index.d.mts +59 -0
  6. package/dist/adapters/message-port/index.d.ts +59 -0
  7. package/dist/adapters/message-port/index.mjs +72 -0
  8. package/dist/adapters/standard/index.d.mts +9 -103
  9. package/dist/adapters/standard/index.d.ts +9 -103
  10. package/dist/adapters/standard/index.mjs +3 -3
  11. package/dist/adapters/websocket/index.d.mts +29 -0
  12. package/dist/adapters/websocket/index.d.ts +29 -0
  13. package/dist/adapters/websocket/index.mjs +46 -0
  14. package/dist/index.d.mts +107 -27
  15. package/dist/index.d.ts +107 -27
  16. package/dist/index.mjs +84 -36
  17. package/dist/plugins/index.d.mts +165 -25
  18. package/dist/plugins/index.d.ts +165 -25
  19. package/dist/plugins/index.mjs +340 -61
  20. package/dist/shared/{client.CjUckqXO.mjs → client.4ylqzC6U.mjs} +107 -47
  21. package/dist/shared/client.BG98rYdO.d.ts +45 -0
  22. package/dist/shared/client.BOYsZIRq.d.mts +29 -0
  23. package/dist/shared/client.BOYsZIRq.d.ts +29 -0
  24. package/dist/shared/client.BWo4eUTU.mjs +208 -0
  25. package/dist/shared/client.Bwgm6dgk.d.mts +45 -0
  26. package/dist/shared/client.CVVVqf1Y.d.ts +91 -0
  27. package/dist/shared/client._Y_enhib.d.mts +91 -0
  28. package/package.json +16 -5
  29. package/dist/shared/client.BacCdg3F.mjs +0 -172
  30. package/dist/shared/client.CupM8eRP.d.mts +0 -30
  31. package/dist/shared/client.CupM8eRP.d.ts +0 -30
  32. package/dist/shared/client.CvnV7_uV.mjs +0 -12
  33. package/dist/shared/client.DrOAzyMB.d.mts +0 -45
  34. package/dist/shared/client.aGal-uGY.d.ts +0 -45
@@ -1,6 +1,260 @@
1
- export { C as CompositeClientPlugin } from '../shared/client.CvnV7_uV.mjs';
2
- import { isAsyncIteratorObject, value } from '@orpc/shared';
3
- import { getEventMeta } from '@orpc/standard-server';
1
+ import { isAsyncIteratorObject, defer, value, splitInHalf, toArray, stringifyJSON, AsyncIteratorClass } from '@orpc/shared';
2
+ import { toBatchRequest, parseBatchResponse, toBatchAbortSignal } from '@orpc/standard-server/batch';
3
+ import { replicateStandardLazyResponse, getEventMeta } from '@orpc/standard-server';
4
+
5
+ class BatchLinkPlugin {
6
+ groups;
7
+ maxSize;
8
+ batchUrl;
9
+ maxUrlLength;
10
+ batchHeaders;
11
+ mapRequestItem;
12
+ exclude;
13
+ mode;
14
+ pending;
15
+ order = 5e6;
16
+ constructor(options) {
17
+ this.groups = options.groups;
18
+ this.pending = /* @__PURE__ */ new Map();
19
+ this.maxSize = options.maxSize ?? 10;
20
+ this.maxUrlLength = options.maxUrlLength ?? 2083;
21
+ this.mode = options.mode ?? "streaming";
22
+ this.batchUrl = options.url ?? (([options2]) => `${options2.request.url.origin}${options2.request.url.pathname}/__batch__`);
23
+ this.batchHeaders = options.headers ?? (([options2, ...rest]) => {
24
+ const headers = {};
25
+ for (const [key, value2] of Object.entries(options2.request.headers)) {
26
+ if (rest.every((item) => item.request.headers[key] === value2)) {
27
+ headers[key] = value2;
28
+ }
29
+ }
30
+ return headers;
31
+ });
32
+ this.mapRequestItem = options.mapRequestItem ?? (({ request, batchHeaders }) => {
33
+ const headers = {};
34
+ for (const [key, value2] of Object.entries(request.headers)) {
35
+ if (batchHeaders[key] !== value2) {
36
+ headers[key] = value2;
37
+ }
38
+ }
39
+ return {
40
+ method: request.method,
41
+ url: request.url,
42
+ headers,
43
+ body: request.body,
44
+ signal: request.signal
45
+ };
46
+ });
47
+ this.exclude = options.exclude ?? (() => false);
48
+ }
49
+ init(options) {
50
+ options.clientInterceptors ??= [];
51
+ options.clientInterceptors.push((options2) => {
52
+ if (options2.request.headers["x-orpc-batch"] !== "1") {
53
+ return options2.next();
54
+ }
55
+ return options2.next({
56
+ ...options2,
57
+ request: {
58
+ ...options2.request,
59
+ headers: {
60
+ ...options2.request.headers,
61
+ "x-orpc-batch": void 0
62
+ }
63
+ }
64
+ });
65
+ });
66
+ options.clientInterceptors.push((options2) => {
67
+ if (this.exclude(options2) || options2.request.body instanceof Blob || options2.request.body instanceof FormData || isAsyncIteratorObject(options2.request.body) || options2.request.signal?.aborted) {
68
+ return options2.next();
69
+ }
70
+ const group = this.groups.find((group2) => group2.condition(options2));
71
+ if (!group) {
72
+ return options2.next();
73
+ }
74
+ return new Promise((resolve, reject) => {
75
+ this.#enqueueRequest(group, options2, resolve, reject);
76
+ defer(() => this.#processPendingBatches());
77
+ });
78
+ });
79
+ }
80
+ #enqueueRequest(group, options, resolve, reject) {
81
+ const items = this.pending.get(group);
82
+ if (items) {
83
+ items.push([options, resolve, reject]);
84
+ } else {
85
+ this.pending.set(group, [[options, resolve, reject]]);
86
+ }
87
+ }
88
+ async #processPendingBatches() {
89
+ const pending = this.pending;
90
+ this.pending = /* @__PURE__ */ new Map();
91
+ for (const [group, items] of pending) {
92
+ const getItems = items.filter(([options]) => options.request.method === "GET");
93
+ const restItems = items.filter(([options]) => options.request.method !== "GET");
94
+ this.#executeBatch("GET", group, getItems);
95
+ this.#executeBatch("POST", group, restItems);
96
+ }
97
+ }
98
+ async #executeBatch(method, group, groupItems) {
99
+ if (!groupItems.length) {
100
+ return;
101
+ }
102
+ const batchItems = groupItems;
103
+ if (batchItems.length === 1) {
104
+ batchItems[0][0].next().then(batchItems[0][1]).catch(batchItems[0][2]);
105
+ return;
106
+ }
107
+ try {
108
+ const options = batchItems.map(([options2]) => options2);
109
+ const maxSize = await value(this.maxSize, options);
110
+ if (batchItems.length > maxSize) {
111
+ const [first, second] = splitInHalf(batchItems);
112
+ this.#executeBatch(method, group, first);
113
+ this.#executeBatch(method, group, second);
114
+ return;
115
+ }
116
+ const batchUrl = new URL(await value(this.batchUrl, options));
117
+ const batchHeaders = await value(this.batchHeaders, options);
118
+ const mappedItems = batchItems.map(([options2]) => this.mapRequestItem({ ...options2, batchUrl, batchHeaders }));
119
+ const batchRequest = toBatchRequest({
120
+ method,
121
+ url: batchUrl,
122
+ headers: batchHeaders,
123
+ requests: mappedItems
124
+ });
125
+ const maxUrlLength = await value(this.maxUrlLength, options);
126
+ if (batchRequest.url.toString().length > maxUrlLength) {
127
+ const [first, second] = splitInHalf(batchItems);
128
+ this.#executeBatch(method, group, first);
129
+ this.#executeBatch(method, group, second);
130
+ return;
131
+ }
132
+ const mode = value(this.mode, options);
133
+ try {
134
+ const lazyResponse = await options[0].next({
135
+ request: { ...batchRequest, headers: { ...batchRequest.headers, "x-orpc-batch": mode } },
136
+ signal: batchRequest.signal,
137
+ context: group.context,
138
+ input: group.input,
139
+ path: toArray(group.path)
140
+ });
141
+ const parsed = parseBatchResponse({ ...lazyResponse, body: await lazyResponse.body() });
142
+ for await (const item of parsed) {
143
+ batchItems[item.index]?.[1]({ ...item, body: () => Promise.resolve(item.body) });
144
+ }
145
+ } catch (err) {
146
+ if (batchRequest.signal?.aborted && batchRequest.signal.reason === err) {
147
+ for (const [{ signal }, , reject] of batchItems) {
148
+ if (signal?.aborted) {
149
+ reject(signal.reason);
150
+ }
151
+ }
152
+ }
153
+ throw err;
154
+ }
155
+ throw new Error("Something went wrong make batch response not contains enough responses. This can be a bug please report it.");
156
+ } catch (error) {
157
+ for (const [, , reject] of batchItems) {
158
+ reject(error);
159
+ }
160
+ }
161
+ }
162
+ }
163
+
164
+ class DedupeRequestsPlugin {
165
+ #groups;
166
+ #filter;
167
+ order = 4e6;
168
+ // make sure execute before batch plugin
169
+ #queue = /* @__PURE__ */ new Map();
170
+ constructor(options) {
171
+ this.#groups = options.groups;
172
+ this.#filter = options.filter ?? (({ request }) => request.method === "GET");
173
+ }
174
+ init(options) {
175
+ options.clientInterceptors ??= [];
176
+ options.clientInterceptors.push((options2) => {
177
+ if (options2.request.body instanceof Blob || options2.request.body instanceof FormData || options2.request.body instanceof URLSearchParams || isAsyncIteratorObject(options2.request.body) || !this.#filter(options2)) {
178
+ return options2.next();
179
+ }
180
+ const group = this.#groups.find((group2) => group2.condition(options2));
181
+ if (!group) {
182
+ return options2.next();
183
+ }
184
+ return new Promise((resolve, reject) => {
185
+ this.#enqueue(group, options2, resolve, reject);
186
+ defer(() => this.#dequeue());
187
+ });
188
+ });
189
+ }
190
+ #enqueue(group, options, resolve, reject) {
191
+ let queue = this.#queue.get(group);
192
+ if (!queue) {
193
+ this.#queue.set(group, queue = []);
194
+ }
195
+ const matched = queue.find((item) => {
196
+ const requestString1 = stringifyJSON({
197
+ body: item.options.request.body,
198
+ headers: item.options.request.headers,
199
+ method: item.options.request.method,
200
+ url: item.options.request.url
201
+ });
202
+ const requestString2 = stringifyJSON({
203
+ body: options.request.body,
204
+ headers: options.request.headers,
205
+ method: options.request.method,
206
+ url: options.request.url
207
+ });
208
+ return requestString1 === requestString2;
209
+ });
210
+ if (matched) {
211
+ matched.signals.push(options.request.signal);
212
+ matched.resolves.push(resolve);
213
+ matched.rejects.push(reject);
214
+ } else {
215
+ queue.push({
216
+ options,
217
+ signals: [options.request.signal],
218
+ resolves: [resolve],
219
+ rejects: [reject]
220
+ });
221
+ }
222
+ }
223
+ async #dequeue() {
224
+ const promises = [];
225
+ for (const [group, items] of this.#queue) {
226
+ for (const { options, signals, resolves, rejects } of items) {
227
+ promises.push(
228
+ this.#execute(group, options, signals, resolves, rejects)
229
+ );
230
+ }
231
+ }
232
+ this.#queue.clear();
233
+ await Promise.all(promises);
234
+ }
235
+ async #execute(group, options, signals, resolves, rejects) {
236
+ try {
237
+ const dedupedRequest = {
238
+ ...options.request,
239
+ signal: toBatchAbortSignal(signals)
240
+ };
241
+ const response = await options.next({
242
+ ...options,
243
+ request: dedupedRequest,
244
+ signal: dedupedRequest.signal,
245
+ context: group.context
246
+ });
247
+ const replicatedResponses = replicateStandardLazyResponse(response, resolves.length);
248
+ for (const resolve of resolves) {
249
+ resolve(replicatedResponses.shift());
250
+ }
251
+ } catch (error) {
252
+ for (const reject of rejects) {
253
+ reject(error);
254
+ }
255
+ }
256
+ }
257
+ }
4
258
 
5
259
  class ClientRetryPluginInvalidEventIteratorRetryResponse extends Error {
6
260
  }
@@ -11,78 +265,64 @@ class ClientRetryPlugin {
11
265
  defaultOnRetry;
12
266
  constructor(options = {}) {
13
267
  this.defaultRetry = options.default?.retry ?? 0;
14
- this.defaultRetryDelay = options.default?.retryDelay ?? ((o) => o.eventIteratorLastRetry ?? 2e3);
268
+ this.defaultRetryDelay = options.default?.retryDelay ?? ((o) => o.lastEventRetry ?? 2e3);
15
269
  this.defaultShouldRetry = options.default?.shouldRetry ?? true;
16
270
  this.defaultOnRetry = options.default?.onRetry;
17
271
  }
18
272
  init(options) {
19
273
  options.interceptors ??= [];
20
274
  options.interceptors.push(async (interceptorOptions) => {
21
- const maxAttempts = interceptorOptions.options.context.retry ?? this.defaultRetry;
22
- const retryDelay = interceptorOptions.options.context.retryDelay ?? this.defaultRetryDelay;
23
- const shouldRetry = interceptorOptions.options.context.shouldRetry ?? this.defaultShouldRetry;
24
- const onRetry = interceptorOptions.options.context.onRetry ?? this.defaultOnRetry;
275
+ const maxAttempts = await value(
276
+ interceptorOptions.context.retry ?? this.defaultRetry,
277
+ interceptorOptions
278
+ );
279
+ const retryDelay = interceptorOptions.context.retryDelay ?? this.defaultRetryDelay;
280
+ const shouldRetry = interceptorOptions.context.shouldRetry ?? this.defaultShouldRetry;
281
+ const onRetry = interceptorOptions.context.onRetry ?? this.defaultOnRetry;
25
282
  if (maxAttempts <= 0) {
26
283
  return interceptorOptions.next();
27
284
  }
28
- let eventIteratorLastEventId = interceptorOptions.options.lastEventId;
29
- let eventIteratorLastRetry;
30
- let unsubscribe;
285
+ let lastEventId = interceptorOptions.lastEventId;
286
+ let lastEventRetry;
287
+ let callback;
31
288
  let attemptIndex = 0;
32
- const next = async (initial) => {
33
- let current = initial;
289
+ const next = async (initialError) => {
290
+ let currentError = initialError;
34
291
  while (true) {
35
- if (current) {
292
+ const updatedInterceptorOptions = { ...interceptorOptions, lastEventId };
293
+ if (currentError) {
36
294
  if (attemptIndex >= maxAttempts) {
37
- throw current.error;
295
+ throw currentError.error;
38
296
  }
39
297
  const attemptOptions = {
298
+ ...updatedInterceptorOptions,
40
299
  attemptIndex,
41
- error: current.error,
42
- eventIteratorLastEventId,
43
- eventIteratorLastRetry
300
+ error: currentError.error,
301
+ lastEventRetry
44
302
  };
45
303
  const shouldRetryBool = await value(
46
304
  shouldRetry,
47
- attemptOptions,
48
- interceptorOptions.options,
49
- interceptorOptions.path,
50
- interceptorOptions.input
305
+ attemptOptions
51
306
  );
52
307
  if (!shouldRetryBool) {
53
- throw current.error;
308
+ throw currentError.error;
54
309
  }
55
- unsubscribe = onRetry?.(
56
- attemptOptions,
57
- interceptorOptions.options,
58
- interceptorOptions.path,
59
- interceptorOptions.input
60
- );
61
- const retryDelayMs = await value(
62
- retryDelay,
63
- attemptOptions,
64
- interceptorOptions.options,
65
- interceptorOptions.path,
66
- interceptorOptions.input
67
- );
310
+ callback = onRetry?.(attemptOptions);
311
+ const retryDelayMs = await value(retryDelay, attemptOptions);
68
312
  await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
69
313
  attemptIndex++;
70
314
  }
71
315
  try {
72
- const newClientOptions = { ...interceptorOptions.options, lastEventId: eventIteratorLastEventId };
73
- const output2 = await interceptorOptions.next({
74
- ...interceptorOptions,
75
- options: newClientOptions
76
- });
77
- return output2;
316
+ currentError = void 0;
317
+ return await interceptorOptions.next(updatedInterceptorOptions);
78
318
  } catch (error) {
79
- if (interceptorOptions.options.signal?.aborted === true) {
319
+ currentError = { error };
320
+ if (updatedInterceptorOptions.signal?.aborted) {
80
321
  throw error;
81
322
  }
82
- current = { error };
83
323
  } finally {
84
- unsubscribe?.();
85
- unsubscribe = void 0;
324
+ callback?.(!currentError);
325
+ callback = void 0;
86
326
  }
87
327
  }
88
328
  };
@@ -90,23 +330,21 @@ class ClientRetryPlugin {
90
330
  if (!isAsyncIteratorObject(output)) {
91
331
  return output;
92
332
  }
93
- return async function* () {
94
- let current = output;
95
- try {
333
+ let current = output;
334
+ let isIteratorAborted = false;
335
+ return new AsyncIteratorClass(
336
+ async () => {
96
337
  while (true) {
97
338
  try {
98
339
  const item = await current.next();
99
340
  const meta = getEventMeta(item.value);
100
- eventIteratorLastEventId = meta?.id ?? eventIteratorLastEventId;
101
- eventIteratorLastRetry = meta?.retry ?? eventIteratorLastRetry;
102
- if (item.done) {
103
- return item.value;
104
- }
105
- yield item.value;
341
+ lastEventId = meta?.id ?? lastEventId;
342
+ lastEventRetry = meta?.retry ?? lastEventRetry;
343
+ return item;
106
344
  } catch (error) {
107
345
  const meta = getEventMeta(error);
108
- eventIteratorLastEventId = meta?.id ?? eventIteratorLastEventId;
109
- eventIteratorLastRetry = meta?.retry ?? eventIteratorLastRetry;
346
+ lastEventId = meta?.id ?? lastEventId;
347
+ lastEventRetry = meta?.retry ?? lastEventRetry;
110
348
  const maybeEventIterator = await next({ error });
111
349
  if (!isAsyncIteratorObject(maybeEventIterator)) {
112
350
  throw new ClientRetryPluginInvalidEventIteratorRetryResponse(
@@ -114,14 +352,55 @@ class ClientRetryPlugin {
114
352
  );
115
353
  }
116
354
  current = maybeEventIterator;
355
+ if (isIteratorAborted) {
356
+ await current.return?.();
357
+ throw error;
358
+ }
117
359
  }
118
360
  }
119
- } finally {
120
- await current.return?.();
361
+ },
362
+ async (reason) => {
363
+ isIteratorAborted = true;
364
+ if (reason !== "next") {
365
+ await current.return?.();
366
+ }
367
+ }
368
+ );
369
+ });
370
+ }
371
+ }
372
+
373
+ class SimpleCsrfProtectionLinkPlugin {
374
+ headerName;
375
+ headerValue;
376
+ exclude;
377
+ constructor(options = {}) {
378
+ this.headerName = options.headerName ?? "x-csrf-token";
379
+ this.headerValue = options.headerValue ?? "orpc";
380
+ this.exclude = options.exclude ?? false;
381
+ }
382
+ order = 8e6;
383
+ init(options) {
384
+ options.clientInterceptors ??= [];
385
+ options.clientInterceptors.push(async (options2) => {
386
+ const excluded = await value(this.exclude, options2);
387
+ if (excluded) {
388
+ return options2.next();
389
+ }
390
+ const headerName = await value(this.headerName, options2);
391
+ const headerValue = await value(this.headerValue, options2);
392
+ return options2.next({
393
+ ...options2,
394
+ request: {
395
+ ...options2.request,
396
+ headers: {
397
+ ...options2.request.headers,
398
+ [headerName]: headerValue
399
+ }
121
400
  }
122
- }();
401
+ });
123
402
  });
124
403
  }
125
404
  }
126
405
 
127
- export { ClientRetryPlugin, ClientRetryPluginInvalidEventIteratorRetryResponse };
406
+ export { BatchLinkPlugin, ClientRetryPlugin, ClientRetryPluginInvalidEventIteratorRetryResponse, DedupeRequestsPlugin, SimpleCsrfProtectionLinkPlugin };