@orpc/client 2.0.0-beta.2 → 2.0.0-beta.20

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.
@@ -1,6 +1,7 @@
1
- import { toArray, value, splitInHalf, stringifyJSON, isAsyncIteratorObject, defer, loadBytes, allAbortSignal, replicateAsyncIterator, replicateReadableStream, override, AsyncIteratorClass, sleep } from '@orpc/shared';
2
- import { parseStandardUrl, getEventMeta, flattenStandardHeader } from '@standardserver/core';
1
+ import { toArray, value, splitInHalf, stringifyJSON, isAsyncIteratorObject, defer, loadBytes, allAbortSignal, replicateAsyncIterator, replicateReadableStream, isCompressibleContentType, override, AsyncIteratorClass, sleep, AbortError, anyAbortSignal } from '@orpc/shared';
2
+ import { parseStandardUrl, flattenStandardHeader, generateContentDisposition, getEventMeta } from '@standardserver/core';
3
3
  import { ClientPeer, isServerPeerSendMessage, decodePeerMessage } from '@standardserver/peer';
4
+ import { toFetchHeaders, toStandardBody } from '@standardserver/fetch';
4
5
  import { C as COMMON_ERROR_STATUS_MAP } from '../shared/client.Dnfj8jnT.mjs';
5
6
 
6
7
  class BatchLinkPluginError extends TypeError {
@@ -226,6 +227,10 @@ async function decodeLengthPrefixedStream(stream, peer) {
226
227
  while (buffer.length >= 4) {
227
228
  const view = new DataView(buffer.buffer, buffer.byteOffset, 4);
228
229
  const length = view.getUint32(0, false);
230
+ if (length === 0) {
231
+ buffer = buffer.subarray(4);
232
+ continue;
233
+ }
229
234
  if (buffer.length < 4 + length) {
230
235
  break;
231
236
  }
@@ -387,8 +392,229 @@ function shouldDedupe(items) {
387
392
  return items.length >= 2;
388
393
  }
389
394
 
390
- class RetryLinkPluginInvalidEventIteratorRetryResponse extends Error {
395
+ const AVG_BYTES_PER_CHAR = 1.2;
396
+ class RequestCompressionLinkPlugin {
397
+ name = "~request-compression";
398
+ /**
399
+ * Compression should be done after batching, to compress the final request
400
+ */
401
+ after = ["~batch"];
402
+ encoding;
403
+ threshold;
404
+ constructor(options = {}) {
405
+ this.encoding = options.encoding ?? "gzip";
406
+ this.threshold = options.threshold ?? 1024;
407
+ }
408
+ init(options) {
409
+ const transportInterceptor = async ({ next, ...interceptorOptions }) => {
410
+ const request = interceptorOptions.request;
411
+ const contentEncoding = flattenStandardHeader(request.headers["content-encoding"])?.trim()?.toLowerCase();
412
+ if (contentEncoding !== void 0) {
413
+ return next();
414
+ }
415
+ if (request.body instanceof ReadableStream) {
416
+ const contentLength = Number(flattenStandardHeader(request.headers["content-length"]));
417
+ if ((!Number.isFinite(contentLength) || contentLength >= this.threshold) && isCompressibleContentType(flattenStandardHeader(request.headers["content-type"]))) {
418
+ const compressedStream = request.body.pipeThrough(new CompressionStream(this.encoding));
419
+ return next({
420
+ ...interceptorOptions,
421
+ request: {
422
+ ...interceptorOptions.request,
423
+ body: compressedStream,
424
+ headers: {
425
+ ...request.headers,
426
+ "standard-server": "octet-stream",
427
+ "content-length": [],
428
+ "content-encoding": this.encoding
429
+ }
430
+ }
431
+ });
432
+ }
433
+ } else if (request.body instanceof Blob) {
434
+ if ((!Number.isFinite(request.body.size) || request.body.size >= this.threshold) && isCompressibleContentType(request.body.type)) {
435
+ const compressedStream = request.body.stream().pipeThrough(new CompressionStream(this.encoding));
436
+ const contentDisposition = request.headers["content-disposition"] ?? generateContentDisposition(
437
+ request.body instanceof File ? request.body.name : "blob"
438
+ );
439
+ return next({
440
+ ...interceptorOptions,
441
+ request: {
442
+ ...interceptorOptions.request,
443
+ body: compressedStream,
444
+ headers: {
445
+ ...request.headers,
446
+ "standard-server": "file",
447
+ "content-type": request.body.type,
448
+ "content-length": [],
449
+ "content-disposition": contentDisposition,
450
+ "content-encoding": this.encoding
451
+ }
452
+ }
453
+ });
454
+ }
455
+ } else if (request.body instanceof FormData) {
456
+ const PART_OVERHEAD = 64;
457
+ let contentLength = 0;
458
+ for (const [key, value] of request.body) {
459
+ contentLength += PART_OVERHEAD + key.length;
460
+ if (value instanceof Blob) {
461
+ if (!Number.isFinite(value.size)) {
462
+ if (!isCompressibleContentType(value.type)) {
463
+ contentLength = -Infinity;
464
+ break;
465
+ }
466
+ contentLength = Infinity;
467
+ } else {
468
+ contentLength += isCompressibleContentType(value.type) ? value.size : -value.size;
469
+ }
470
+ } else {
471
+ contentLength += value.length * AVG_BYTES_PER_CHAR;
472
+ }
473
+ }
474
+ if (contentLength >= this.threshold) {
475
+ const res = new Response(request.body);
476
+ const compressedStream = res.body.pipeThrough(new CompressionStream(this.encoding));
477
+ return next({
478
+ ...interceptorOptions,
479
+ request: {
480
+ ...interceptorOptions.request,
481
+ body: compressedStream,
482
+ headers: {
483
+ ...request.headers,
484
+ "standard-server": [],
485
+ "content-type": res.headers.get("content-type"),
486
+ "content-length": [],
487
+ "content-encoding": this.encoding
488
+ }
489
+ }
490
+ });
491
+ }
492
+ } else if (request.body instanceof URLSearchParams) {
493
+ const string = request.body.toString();
494
+ if (string.length * AVG_BYTES_PER_CHAR >= this.threshold) {
495
+ const compressedStream = new Blob([string]).stream().pipeThrough(new CompressionStream(this.encoding));
496
+ return next({
497
+ ...interceptorOptions,
498
+ request: {
499
+ ...interceptorOptions.request,
500
+ body: compressedStream,
501
+ headers: {
502
+ ...request.headers,
503
+ "standard-server": [],
504
+ "content-type": "application/x-www-form-urlencoded",
505
+ "content-length": [],
506
+ "content-encoding": this.encoding
507
+ }
508
+ }
509
+ });
510
+ }
511
+ } else if (request.body !== void 0 && !isAsyncIteratorObject(request.body)) {
512
+ const string = stringifyJSON(request.body);
513
+ if (string.length * AVG_BYTES_PER_CHAR >= this.threshold) {
514
+ const compressedStream = new Blob([string]).stream().pipeThrough(new CompressionStream(this.encoding));
515
+ return next({
516
+ ...interceptorOptions,
517
+ request: {
518
+ ...interceptorOptions.request,
519
+ body: compressedStream,
520
+ headers: {
521
+ ...request.headers,
522
+ "standard-server": [],
523
+ "content-type": "application/json",
524
+ "content-length": [],
525
+ "content-encoding": this.encoding
526
+ }
527
+ }
528
+ });
529
+ }
530
+ }
531
+ return next();
532
+ };
533
+ return {
534
+ ...options,
535
+ transportInterceptors: [
536
+ ...toArray(options.transportInterceptors),
537
+ transportInterceptor
538
+ ]
539
+ };
540
+ }
541
+ }
542
+
543
+ class ResponseCompressionLinkPlugin {
544
+ name = "~response-compression";
545
+ /**
546
+ * Decompression should wrap the final batch response instead of sub-responses.
547
+ */
548
+ after = ["~batch"];
549
+ encodings;
550
+ constructor(options = {}) {
551
+ this.encodings = options.encodings ?? ["gzip", "deflate"];
552
+ }
553
+ init(options) {
554
+ const acceptEncodingHeader = this.encodings.join(", ");
555
+ const transportInterceptor = async ({ next, ...interceptorOptions }) => {
556
+ const response = await next({
557
+ ...interceptorOptions,
558
+ request: {
559
+ ...interceptorOptions.request,
560
+ headers: {
561
+ ...interceptorOptions.request.headers,
562
+ "accept-encoding": acceptEncodingHeader
563
+ }
564
+ }
565
+ });
566
+ const encodings = parseContentEncodings(
567
+ flattenStandardHeader(response.headers["content-encoding"])
568
+ );
569
+ if (encodings.length === 0 || !encodings.every(isSupportedEncoding)) {
570
+ return response;
571
+ }
572
+ const decompressedHeaders = {
573
+ ...response.headers,
574
+ "content-length": void 0,
575
+ "content-encoding": void 0
576
+ };
577
+ return {
578
+ ...response,
579
+ headers: decompressedHeaders,
580
+ async resolveBody(hint) {
581
+ const stream = await response.resolveBody("octet-stream");
582
+ if (!(stream instanceof ReadableStream)) {
583
+ return stream;
584
+ }
585
+ let decompressedStream = stream;
586
+ for (let i = encodings.length - 1; i >= 0; i--) {
587
+ decompressedStream = decompressedStream.pipeThrough(
588
+ new DecompressionStream(encodings[i])
589
+ );
590
+ }
591
+ const fetchResponse = new Response(decompressedStream, {
592
+ headers: toFetchHeaders(decompressedHeaders)
593
+ });
594
+ return toStandardBody(fetchResponse, { hint });
595
+ }
596
+ };
597
+ };
598
+ return {
599
+ ...options,
600
+ transportInterceptors: [
601
+ ...toArray(options.transportInterceptors),
602
+ transportInterceptor
603
+ ]
604
+ };
605
+ }
391
606
  }
607
+ const SUPPORTED_ENCODINGS = ["gzip", "deflate", "deflate-raw"];
608
+ function isSupportedEncoding(encoding) {
609
+ return SUPPORTED_ENCODINGS.includes(encoding);
610
+ }
611
+ function parseContentEncodings(header) {
612
+ if (header === void 0) {
613
+ return [];
614
+ }
615
+ return header.split(",").map((part) => part.trim().toLowerCase());
616
+ }
617
+
392
618
  class RetryLinkPlugin {
393
619
  defaultRetry;
394
620
  defaultRetryDelay;
@@ -484,13 +710,13 @@ class RetryLinkPlugin {
484
710
  const meta = getEventMeta(error);
485
711
  lastEventId = meta?.id ?? lastEventId;
486
712
  lastEventRetry = meta?.retry ?? lastEventRetry;
487
- const maybeEventIterator = await callNext({ error });
488
- if (!isAsyncIteratorObject(maybeEventIterator)) {
489
- throw new RetryLinkPluginInvalidEventIteratorRetryResponse(
490
- "RetryLinkPlugin: Expected an Event Iterator, got a non-Event Iterator"
713
+ const asyncIteratorObject = await callNext({ error });
714
+ if (!isAsyncIteratorObject(asyncIteratorObject)) {
715
+ throw new TypeError(
716
+ "RetryLinkPlugin: Expected an AsyncIteratorObject, got a different type."
491
717
  );
492
718
  }
493
- current = maybeEventIterator;
719
+ current = asyncIteratorObject;
494
720
  if (isIteratorAborted) {
495
721
  await current.return?.();
496
722
  throw error;
@@ -571,4 +797,35 @@ function parseRetryAfterHeader(value2) {
571
797
  return void 0;
572
798
  }
573
799
 
574
- export { BatchLinkPlugin, BatchLinkPluginError, DedupeLinkPlugin, RetryAfterLinkPlugin, RetryLinkPlugin, RetryLinkPluginInvalidEventIteratorRetryResponse };
800
+ class TimeoutLinkPlugin {
801
+ timeout;
802
+ name = "~timeout";
803
+ /**
804
+ * Should abort if the total retry time exceeds the configured timeout
805
+ */
806
+ after = ["~retry"];
807
+ constructor(options) {
808
+ this.timeout = options.timeout;
809
+ }
810
+ init(options) {
811
+ const interceptor = async (interceptorOptions) => {
812
+ const timeoutMs = value(this.timeout, interceptorOptions);
813
+ if (timeoutMs === null || timeoutMs === void 0) {
814
+ return interceptorOptions.next();
815
+ }
816
+ const controller = new AbortController();
817
+ const timeoutId = setTimeout(() => {
818
+ controller.abort(new AbortError(`Request timed out after ${timeoutMs}ms`));
819
+ }, timeoutMs);
820
+ const signal = anyAbortSignal([interceptorOptions.signal, controller.signal]);
821
+ try {
822
+ return await interceptorOptions.next({ ...interceptorOptions, signal });
823
+ } finally {
824
+ clearTimeout(timeoutId);
825
+ }
826
+ };
827
+ return { ...options, interceptors: [interceptor, ...toArray(options.interceptors)] };
828
+ }
829
+ }
830
+
831
+ export { BatchLinkPlugin, BatchLinkPluginError, DedupeLinkPlugin, RequestCompressionLinkPlugin, ResponseCompressionLinkPlugin, RetryAfterLinkPlugin, RetryLinkPlugin, TimeoutLinkPlugin };
@@ -1,6 +1,6 @@
1
1
  import { Promisable, OrderablePlugin, Interceptor } from '@orpc/shared';
2
2
  import { StandardRequest, StandardLazyResponse } from '@standardserver/core';
3
- import { C as ClientContext, a as ClientOptions, A as AnyORPCError, b as ClientLink } from './client.8ug8I-zu.mjs';
3
+ import { C as ClientContext, a as ClientOptions, A as AnyORPCError, b as ClientLink } from './client.BBZBQID8.mjs';
4
4
 
5
5
  type StandardLinkCodecDecodedResponse = {
6
6
  kind: 'output';
@@ -163,5 +163,5 @@ interface ORPCErrorJSON<TCode extends string, TData> extends Pick<ORPCError<TCod
163
163
  type AnyORPCError = ORPCError<any, any>;
164
164
  type AnyORPCErrorJSON = ORPCErrorJSON<any, any>;
165
165
 
166
- export { ORPCError as c, COMMON_ERROR_STATUS_MAP as j };
167
- export type { AnyORPCError as A, ClientContext as C, FriendlyClientOptions as F, InferClientContext as I, NestedClient as N, ORPCErrorCode as O, ClientOptions as a, ClientLink as b, ORPCErrorJSON as d, AnyNestedClient as e, InferClientError as f, Client as g, ClientRest as h, AnyORPCErrorJSON as i, InferClientBodyInputs as k, InferClientBodyOutputs as l, InferClientErrors as m, InferClientInputs as n, InferClientOutputs as o, ORPCErrorOptions as p };
166
+ export { ORPCError as g, COMMON_ERROR_STATUS_MAP as j };
167
+ export type { AnyORPCError as A, ClientContext as C, FriendlyClientOptions as F, InferClientContext as I, NestedClient as N, ORPCErrorCode as O, ClientOptions as a, ClientLink as b, AnyNestedClient as c, InferClientError as d, Client as e, ClientRest as f, ORPCErrorJSON as h, AnyORPCErrorJSON as i, InferClientBodyInputs as k, InferClientBodyOutputs as l, InferClientErrors as m, InferClientInputs as n, InferClientOutputs as o, ORPCErrorOptions as p };
@@ -163,5 +163,5 @@ interface ORPCErrorJSON<TCode extends string, TData> extends Pick<ORPCError<TCod
163
163
  type AnyORPCError = ORPCError<any, any>;
164
164
  type AnyORPCErrorJSON = ORPCErrorJSON<any, any>;
165
165
 
166
- export { ORPCError as c, COMMON_ERROR_STATUS_MAP as j };
167
- export type { AnyORPCError as A, ClientContext as C, FriendlyClientOptions as F, InferClientContext as I, NestedClient as N, ORPCErrorCode as O, ClientOptions as a, ClientLink as b, ORPCErrorJSON as d, AnyNestedClient as e, InferClientError as f, Client as g, ClientRest as h, AnyORPCErrorJSON as i, InferClientBodyInputs as k, InferClientBodyOutputs as l, InferClientErrors as m, InferClientInputs as n, InferClientOutputs as o, ORPCErrorOptions as p };
166
+ export { ORPCError as g, COMMON_ERROR_STATUS_MAP as j };
167
+ export type { AnyORPCError as A, ClientContext as C, FriendlyClientOptions as F, InferClientContext as I, NestedClient as N, ORPCErrorCode as O, ClientOptions as a, ClientLink as b, AnyNestedClient as c, InferClientError as d, Client as e, ClientRest as f, ORPCErrorJSON as h, AnyORPCErrorJSON as i, InferClientBodyInputs as k, InferClientBodyOutputs as l, InferClientErrors as m, InferClientInputs as n, InferClientOutputs as o, ORPCErrorOptions as p };
@@ -1,6 +1,6 @@
1
1
  import { Promisable, OrderablePlugin, Interceptor } from '@orpc/shared';
2
2
  import { StandardRequest, StandardLazyResponse } from '@standardserver/core';
3
- import { C as ClientContext, a as ClientOptions, A as AnyORPCError, b as ClientLink } from './client.8ug8I-zu.js';
3
+ import { C as ClientContext, a as ClientOptions, A as AnyORPCError, b as ClientLink } from './client.BBZBQID8.js';
4
4
 
5
5
  type StandardLinkCodecDecodedResponse = {
6
6
  kind: 'output';
@@ -2,7 +2,7 @@ import { sortPlugins, runWithSpan, ORPC_NAME, isAsyncIteratorObject, override, t
2
2
  import { mergeStandardHeaders, parseStandardUrl } from '@standardserver/core';
3
3
  import { toStandardHeaders } from '@standardserver/fetch';
4
4
  import { O as ORPCError } from './client.Dnfj8jnT.mjs';
5
- import { R as RPCSerializer, i as isORPCErrorJson, c as createORPCErrorFromJson } from './client.DMXKFDyV.mjs';
5
+ import { R as RPCSerializer, i as isORPCErrorJson, c as createORPCErrorFromJson } from './client.DqYwRDUO.mjs';
6
6
 
7
7
  class CompositeStandardLinkPlugin {
8
8
  name = "~composite";
@@ -38,7 +38,7 @@ class StandardLink {
38
38
  span?.setAttribute("rpc.system", ORPC_NAME);
39
39
  span?.setAttribute("rpc.method", path.join("."));
40
40
  if (isAsyncIteratorObject(input)) {
41
- input = override(input, traceAsyncIterator("consume_event_iterator_input", input));
41
+ input = override(input, traceAsyncIterator("consume_async_iterator_object_input", input));
42
42
  }
43
43
  return intercept(this.interceptors, { ...options, path, input }, async ({ path: path2, input: input2, ...options2 }) => {
44
44
  const otel = getOpenTelemetryConfig();
@@ -80,7 +80,7 @@ class StandardLink {
80
80
  }
81
81
  const output = decodedResult.output;
82
82
  if (isAsyncIteratorObject(output)) {
83
- return override(output, traceAsyncIterator("consume_event_iterator_output", output));
83
+ return override(output, traceAsyncIterator("consume_async_iterator_object_output", output));
84
84
  }
85
85
  return output;
86
86
  });
@@ -139,7 +139,7 @@ class RPCLinkCodec {
139
139
  };
140
140
  }
141
141
  async decodeResponse(response) {
142
- const isOk = response.status >= 200 && response.status < 400;
142
+ const isOk = response.status < 400;
143
143
  const body = await response.resolveBody();
144
144
  const deserialized = await (async () => {
145
145
  try {
@@ -40,7 +40,7 @@ function cloneORPCError(error) {
40
40
  return cloned;
41
41
  }
42
42
 
43
- function wrapEventIteratorPreservingMeta(iterator, { mapResult, mapError, ...rest }) {
43
+ function wrapAsyncIteratorPreservingEventMeta(iterator, { mapResult, mapError, ...rest }) {
44
44
  return wrapAsyncIterator(iterator, {
45
45
  ...rest,
46
46
  mapResult: mapResult && (async (result) => {
@@ -271,7 +271,7 @@ class RPCSerializer {
271
271
  return data;
272
272
  }
273
273
  if (isAsyncIteratorObject(data)) {
274
- return wrapEventIteratorPreservingMeta(data, {
274
+ return wrapAsyncIteratorPreservingEventMeta(data, {
275
275
  mapResult: (result) => {
276
276
  if (result.value === void 0) {
277
277
  return result;
@@ -304,7 +304,7 @@ class RPCSerializer {
304
304
  return data;
305
305
  }
306
306
  if (isAsyncIteratorObject(data)) {
307
- return wrapEventIteratorPreservingMeta(data, {
307
+ return wrapAsyncIteratorPreservingEventMeta(data, {
308
308
  mapResult: (result) => {
309
309
  if (result.value === void 0) {
310
310
  return result;
@@ -331,7 +331,7 @@ class RPCSerializer {
331
331
  }
332
332
  const serialized = JSON.parse(data.get("data"));
333
333
  const blobs = [];
334
- for (const [key, value] of data.entries()) {
334
+ for (const [key, value] of data) {
335
335
  if (value instanceof Blob) {
336
336
  blobs[Number(key)] = value;
337
337
  }
@@ -340,4 +340,4 @@ class RPCSerializer {
340
340
  }
341
341
  }
342
342
 
343
- export { RPCSerializer as R, isInferableError as a, RPCJsonSerializer as b, createORPCErrorFromJson as c, cloneORPCError as d, isORPCErrorJson as i, toORPCError as t, wrapEventIteratorPreservingMeta as w };
343
+ export { RPCSerializer as R, isInferableError as a, RPCJsonSerializer as b, createORPCErrorFromJson as c, cloneORPCError as d, isORPCErrorJson as i, toORPCError as t, wrapAsyncIteratorPreservingEventMeta as w };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@orpc/client",
3
3
  "type": "module",
4
- "version": "2.0.0-beta.2",
4
+ "version": "2.0.0-beta.20",
5
5
  "license": "MIT",
6
6
  "homepage": "https://orpc.dev",
7
7
  "repository": {
@@ -50,10 +50,10 @@
50
50
  "dist"
51
51
  ],
52
52
  "dependencies": {
53
- "@standardserver/core": "^0.0.25",
54
- "@standardserver/fetch": "^0.0.25",
55
- "@standardserver/peer": "^0.0.25",
56
- "@orpc/shared": "2.0.0-beta.2"
53
+ "@standardserver/core": "^0.5.0",
54
+ "@standardserver/fetch": "^0.5.0",
55
+ "@standardserver/peer": "^0.5.0",
56
+ "@orpc/shared": "2.0.0-beta.20"
57
57
  },
58
58
  "devDependencies": {
59
59
  "zod": "^4.4.3"