@algolia/client-common 5.56.0 → 5.59.0

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/dist/common.cjs CHANGED
@@ -40,6 +40,7 @@ __export(src_exports, {
40
40
  IndicesInSameAppError: () => IndicesInSameAppError,
41
41
  LogLevelEnum: () => LogLevelEnum,
42
42
  RetryError: () => RetryError,
43
+ StreamRequestError: () => StreamRequestError,
43
44
  createAlgoliaAgent: () => createAlgoliaAgent,
44
45
  createAuth: () => createAuth,
45
46
  createBrowserLocalStorageCache: () => createBrowserLocalStorageCache,
@@ -53,11 +54,19 @@ __export(src_exports, {
53
54
  deserializeFailure: () => deserializeFailure,
54
55
  deserializeSuccess: () => deserializeSuccess,
55
56
  deserializeSuccessWithHttpInfo: () => deserializeSuccessWithHttpInfo,
57
+ generateRequestId: () => generateRequestId,
56
58
  getAlgoliaAgent: () => getAlgoliaAgent,
59
+ getCorrelationId: () => getCorrelationId,
60
+ getLastCorrelationId: () => getLastCorrelationId,
61
+ headersFromError: () => headersFromError,
57
62
  isNetworkError: () => isNetworkError,
63
+ isRateLimited: () => isRateLimited,
64
+ isRateLimitedError: () => isRateLimitedError,
58
65
  isRetryable: () => isRetryable,
59
66
  isSuccess: () => isSuccess,
60
67
  iterSSEEvents: () => iterSSEEvents,
68
+ logWarning: () => logWarning,
69
+ parseRetryAfterMs: () => parseRetryAfterMs,
61
70
  serializeData: () => serializeData,
62
71
  serializeHeaders: () => serializeHeaders,
63
72
  serializeQueryParameters: () => serializeQueryParameters,
@@ -65,7 +74,8 @@ __export(src_exports, {
65
74
  shuffle: () => shuffle,
66
75
  stackFrameWithoutCredentials: () => stackFrameWithoutCredentials,
67
76
  stackTraceWithoutCredentials: () => stackTraceWithoutCredentials,
68
- validateRequired: () => validateRequired
77
+ validateRequired: () => validateRequired,
78
+ withRequestId: () => withRequestId
69
79
  });
70
80
  module.exports = __toCommonJS(src_exports);
71
81
 
@@ -335,6 +345,16 @@ function createNullLogger() {
335
345
  };
336
346
  }
337
347
 
348
+ // src/logger/logWarning.ts
349
+ function logWarning(logger, message) {
350
+ if (logger.warn) {
351
+ void Promise.resolve(logger.warn(message)).catch(() => {
352
+ });
353
+ } else {
354
+ console.warn(message);
355
+ }
356
+ }
357
+
338
358
  // src/sse.ts
339
359
  var MAX_LINE_BUFFER_SIZE = 10 * 1024 * 1024;
340
360
  async function* readableStreamToAsyncIterable(stream) {
@@ -538,38 +558,52 @@ var IndexAlreadyExistsError = class extends AlgoliaError {
538
558
  }
539
559
  };
540
560
  var ErrorWithStackTrace = class extends AlgoliaError {
541
- constructor(message, stackTrace, name) {
542
- super(message, name);
561
+ constructor(message, stackTrace, name, correlationId) {
562
+ super(correlationId ? `${message} (Correlation-ID: ${correlationId})` : message, name);
543
563
  __publicField(this, "stackTrace");
564
+ __publicField(this, "correlationId");
544
565
  this.stackTrace = stackTrace;
566
+ this.correlationId = correlationId;
545
567
  }
546
568
  };
547
569
  var RetryError = class extends ErrorWithStackTrace {
548
- constructor(stackTrace) {
570
+ constructor(stackTrace, correlationId) {
549
571
  super(
550
572
  "Unreachable hosts - your application id may be incorrect. If the error persists, please visit our help center https://alg.li/support-unreachable-hosts or reach out to the Algolia Support team: https://alg.li/support",
551
573
  stackTrace,
552
- "RetryError"
574
+ "RetryError",
575
+ correlationId
553
576
  );
554
577
  }
555
578
  };
556
579
  var ApiError = class extends ErrorWithStackTrace {
557
- constructor(message, status, stackTrace, name = "ApiError") {
558
- super(message, stackTrace, name);
580
+ constructor(message, status, stackTrace, name = "ApiError", correlationId) {
581
+ super(message, stackTrace, name, correlationId);
582
+ __publicField(this, "status");
583
+ this.status = status;
584
+ }
585
+ };
586
+ var StreamRequestError = class extends AlgoliaError {
587
+ constructor(status, body, headers) {
588
+ super(`HTTP ${status}: ${body}`, "StreamRequestError");
559
589
  __publicField(this, "status");
590
+ __publicField(this, "headers");
560
591
  this.status = status;
592
+ this.headers = headers;
561
593
  }
562
594
  };
563
595
  var DeserializationError = class extends AlgoliaError {
564
- constructor(message, response) {
565
- super(message, "DeserializationError");
596
+ constructor(message, response, correlationId) {
597
+ super(correlationId ? `${message} (Correlation-ID: ${correlationId})` : message, "DeserializationError");
566
598
  __publicField(this, "response");
599
+ __publicField(this, "correlationId");
567
600
  this.response = response;
601
+ this.correlationId = correlationId;
568
602
  }
569
603
  };
570
604
  var DetailedApiError = class extends ApiError {
571
- constructor(message, status, error, stackTrace) {
572
- super(message, status, stackTrace, "DetailedApiError");
605
+ constructor(message, status, error, stackTrace, correlationId) {
606
+ super(message, status, stackTrace, "DetailedApiError", correlationId);
573
607
  __publicField(this, "error");
574
608
  this.error = error;
575
609
  }
@@ -629,7 +663,7 @@ function deserializeSuccess(response) {
629
663
  try {
630
664
  return JSON.parse(response.content);
631
665
  } catch (e) {
632
- throw new DeserializationError(e.message, response);
666
+ throw new DeserializationError(e.message, response, getCorrelationId(response.headers));
633
667
  }
634
668
  }
635
669
  function deserializeSuccessWithHttpInfo(response) {
@@ -640,19 +674,76 @@ function deserializeSuccessWithHttpInfo(response) {
640
674
  data: deserializeSuccess(response)
641
675
  };
642
676
  }
643
- function deserializeFailure({ content, status }, stackFrame) {
677
+ function getCorrelationId(headers) {
678
+ if (!headers) {
679
+ return void 0;
680
+ }
681
+ if (headers["correlation-id"] !== void 0) {
682
+ return headers["correlation-id"];
683
+ }
684
+ const header = Object.keys(headers).find((key) => key.toLowerCase() === "correlation-id");
685
+ return header === void 0 ? void 0 : headers[header];
686
+ }
687
+ function getLastCorrelationId(stackTrace) {
688
+ for (let i = stackTrace.length - 1; i >= 0; i--) {
689
+ const correlationId = getCorrelationId(stackTrace[i].response.headers);
690
+ if (correlationId !== void 0) {
691
+ return correlationId;
692
+ }
693
+ }
694
+ return void 0;
695
+ }
696
+ function deserializeFailure(response, stackFrame) {
697
+ const { content, status } = response;
698
+ const correlationId = getCorrelationId(response.headers);
644
699
  try {
645
700
  const parsed = JSON.parse(content);
646
701
  if ("error" in parsed) {
647
- return new DetailedApiError(parsed.message, status, parsed.error, stackFrame);
702
+ return new DetailedApiError(parsed.message, status, parsed.error, stackFrame, correlationId);
648
703
  }
649
- return new ApiError(parsed.message, status, stackFrame);
704
+ return new ApiError(parsed.message, status, stackFrame, "ApiError", correlationId);
650
705
  } catch (e) {
651
706
  }
652
- return new ApiError(content, status, stackFrame);
707
+ return new ApiError(content, status, stackFrame, "ApiError", correlationId);
708
+ }
709
+
710
+ // src/transporter/requestId.ts
711
+ var BASE62_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
712
+ var REQUEST_ID_LENGTH = 11;
713
+ function generateRequestId() {
714
+ var _a;
715
+ let requestId = "";
716
+ if (typeof ((_a = globalThis.crypto) == null ? void 0 : _a.getRandomValues) === "function") {
717
+ const bytes = new Uint8Array(REQUEST_ID_LENGTH);
718
+ globalThis.crypto.getRandomValues(bytes);
719
+ for (const byte of bytes) {
720
+ requestId += BASE62_CHARS[byte % BASE62_CHARS.length];
721
+ }
722
+ return requestId;
723
+ }
724
+ for (let i = 0; i < REQUEST_ID_LENGTH; i++) {
725
+ requestId += BASE62_CHARS[Math.floor(Math.random() * BASE62_CHARS.length)];
726
+ }
727
+ return requestId;
728
+ }
729
+ function hasRequestId(headers, queryParameters) {
730
+ return headers !== void 0 && Object.keys(headers).some((header) => header.toLowerCase() === "request-id") || queryParameters !== void 0 && Object.keys(queryParameters).some((parameter) => parameter.toLowerCase() === "x-algolia-request-id");
731
+ }
732
+ function withRequestId(transporter, requestOptions) {
733
+ if (transporter.requestIdChannel === void 0 || hasRequestId(transporter.baseHeaders, transporter.baseQueryParameters) || hasRequestId(requestOptions == null ? void 0 : requestOptions.headers, requestOptions == null ? void 0 : requestOptions.queryParameters)) {
734
+ return requestOptions;
735
+ }
736
+ if (transporter.requestIdChannel === "headers") {
737
+ return { ...requestOptions, headers: { ...requestOptions == null ? void 0 : requestOptions.headers, "request-id": generateRequestId() } };
738
+ }
739
+ return {
740
+ ...requestOptions,
741
+ queryParameters: { ...requestOptions == null ? void 0 : requestOptions.queryParameters, "x-algolia-request-id": generateRequestId() }
742
+ };
653
743
  }
654
744
 
655
745
  // src/transporter/responses.ts
746
+ var DEFAULT_RATE_LIMIT_WAIT_MS = 1e3;
656
747
  function isNetworkError({ isTimedOut, status }) {
657
748
  return !isTimedOut && ~~status === 0;
658
749
  }
@@ -662,6 +753,37 @@ function isRetryable({ isTimedOut, status }) {
662
753
  function isSuccess({ status }) {
663
754
  return ~~(status / 100) === 2;
664
755
  }
756
+ function isRateLimited({ status }) {
757
+ return ~~status === 429;
758
+ }
759
+ var HTTP_429_MESSAGE = /^HTTP 429(?:\D|$)/;
760
+ function isRateLimitedError(error) {
761
+ if (typeof error === "object" && error !== null && "status" in error) {
762
+ return isRateLimited(error);
763
+ }
764
+ return error instanceof Error && HTTP_429_MESSAGE.test(error.message);
765
+ }
766
+ function headersFromError(error) {
767
+ if (typeof error === "object" && error !== null && "headers" in error) {
768
+ return error.headers;
769
+ }
770
+ return void 0;
771
+ }
772
+ function parseRetryAfterMs(headers) {
773
+ var _a;
774
+ const raw = (_a = headers == null ? void 0 : headers["retry-after"]) == null ? void 0 : _a.trim();
775
+ if (raw === void 0 || raw.length === 0) {
776
+ return DEFAULT_RATE_LIMIT_WAIT_MS;
777
+ }
778
+ if (!/^\d+$/.test(raw)) {
779
+ return DEFAULT_RATE_LIMIT_WAIT_MS;
780
+ }
781
+ const seconds = Number.parseInt(raw, 10);
782
+ if (seconds <= 0) {
783
+ return DEFAULT_RATE_LIMIT_WAIT_MS;
784
+ }
785
+ return seconds * 1e3;
786
+ }
665
787
 
666
788
  // src/transporter/stackTrace.ts
667
789
  function stackTraceWithoutCredentials(stackTrace) {
@@ -694,8 +816,23 @@ function createTransporter({
694
816
  requestsCache,
695
817
  responsesCache,
696
818
  compress,
697
- compression
819
+ compression,
820
+ requestIdChannel,
821
+ maxRateLimitRetries = 3
698
822
  }) {
823
+ function injectRequestId(headers, queryParameters) {
824
+ if (requestIdChannel === void 0 || headers["request-id"] !== void 0 || Object.keys(queryParameters).some((parameter) => parameter.toLowerCase() === "x-algolia-request-id")) {
825
+ return;
826
+ }
827
+ if (requestIdChannel === "headers") {
828
+ headers["request-id"] = generateRequestId();
829
+ } else {
830
+ queryParameters["x-algolia-request-id"] = generateRequestId();
831
+ }
832
+ }
833
+ const wait = (ms) => new Promise((resolve) => {
834
+ setTimeout(resolve, ms);
835
+ });
699
836
  async function createRetryableOptions(compatibleHosts) {
700
837
  const statefulHosts = await Promise.all(
701
838
  compatibleHosts.map((compatibleHost) => {
@@ -750,11 +887,13 @@ function createTransporter({
750
887
  }
751
888
  }
752
889
  }
890
+ injectRequestId(headers, queryParameters);
753
891
  let timeoutsCount = 0;
892
+ let rateLimitRetriesLeft = maxRateLimitRetries;
754
893
  const retry = async (retryableHosts, getTimeout) => {
755
894
  const host = retryableHosts.pop();
756
895
  if (host === void 0) {
757
- throw new RetryError(stackTraceWithoutCredentials(stackTrace));
896
+ throw new RetryError(stackTraceWithoutCredentials(stackTrace), getLastCorrelationId(stackTrace));
758
897
  }
759
898
  const timeout = { ...timeouts, ...requestOptions.timeouts };
760
899
  const payload = {
@@ -776,6 +915,19 @@ function createTransporter({
776
915
  return stackFrame;
777
916
  };
778
917
  const response = await requester.send(payload);
918
+ if (isRateLimited(response) && rateLimitRetriesLeft > 0) {
919
+ rateLimitRetriesLeft--;
920
+ retryableHosts.push(host);
921
+ const stackFrame = pushToStackTrace(response);
922
+ const waitMs = parseRetryAfterMs(response.headers);
923
+ logger.info("Retryable failure", {
924
+ ...stackFrameWithoutCredentials(stackFrame),
925
+ wait: waitMs,
926
+ rateLimitRetriesLeft
927
+ });
928
+ await wait(waitMs);
929
+ return retry(retryableHosts, getTimeout);
930
+ }
779
931
  if (isRetryable(response)) {
780
932
  const stackFrame = pushToStackTrace(response);
781
933
  if (response.isTimedOut) {
@@ -878,6 +1030,7 @@ function createTransporter({
878
1030
  }
879
1031
  }
880
1032
  }
1033
+ injectRequestId(headers, queryParameters);
881
1034
  const isRead = request.useReadTransporter || request.method === "GET";
882
1035
  const compatibleHosts = hosts.filter(
883
1036
  (host2) => host2.accept === "readWrite" || (isRead ? host2.accept === "read" : host2.accept === "write")
@@ -896,8 +1049,28 @@ function createTransporter({
896
1049
  connectTimeout: timeout.connect,
897
1050
  responseTimeout: isRead ? timeout.read : timeout.write
898
1051
  };
899
- const stream = await requester.sendStream(payload);
900
- yield* iterSSEEvents(stream);
1052
+ let rateLimitRetriesLeft = maxRateLimitRetries;
1053
+ while (true) {
1054
+ let stream;
1055
+ try {
1056
+ stream = await requester.sendStream(payload);
1057
+ } catch (error) {
1058
+ if (isRateLimitedError(error) && rateLimitRetriesLeft > 0) {
1059
+ rateLimitRetriesLeft--;
1060
+ const waitMs = parseRetryAfterMs(headersFromError(error));
1061
+ logger.info("Retryable failure", {
1062
+ wait: waitMs,
1063
+ rateLimitRetriesLeft,
1064
+ host
1065
+ });
1066
+ await wait(waitMs);
1067
+ continue;
1068
+ }
1069
+ throw error;
1070
+ }
1071
+ yield* iterSSEEvents(stream);
1072
+ return;
1073
+ }
901
1074
  }
902
1075
  return {
903
1076
  hostsCache,
@@ -907,7 +1080,9 @@ function createTransporter({
907
1080
  algoliaAgent,
908
1081
  baseHeaders,
909
1082
  baseQueryParameters,
1083
+ requestIdChannel,
910
1084
  hosts,
1085
+ maxRateLimitRetries,
911
1086
  request: createRequest,
912
1087
  requestWithHttpInfo: createRequestWithHttpInfo,
913
1088
  requestStream,
@@ -949,6 +1124,7 @@ function validateRequired(field, method, value) {
949
1124
  IndicesInSameAppError,
950
1125
  LogLevelEnum,
951
1126
  RetryError,
1127
+ StreamRequestError,
952
1128
  createAlgoliaAgent,
953
1129
  createAuth,
954
1130
  createBrowserLocalStorageCache,
@@ -962,11 +1138,19 @@ function validateRequired(field, method, value) {
962
1138
  deserializeFailure,
963
1139
  deserializeSuccess,
964
1140
  deserializeSuccessWithHttpInfo,
1141
+ generateRequestId,
965
1142
  getAlgoliaAgent,
1143
+ getCorrelationId,
1144
+ getLastCorrelationId,
1145
+ headersFromError,
966
1146
  isNetworkError,
1147
+ isRateLimited,
1148
+ isRateLimitedError,
967
1149
  isRetryable,
968
1150
  isSuccess,
969
1151
  iterSSEEvents,
1152
+ logWarning,
1153
+ parseRetryAfterMs,
970
1154
  serializeData,
971
1155
  serializeHeaders,
972
1156
  serializeQueryParameters,
@@ -974,6 +1158,7 @@ function validateRequired(field, method, value) {
974
1158
  shuffle,
975
1159
  stackFrameWithoutCredentials,
976
1160
  stackTraceWithoutCredentials,
977
- validateRequired
1161
+ validateRequired,
1162
+ withRequestId
978
1163
  });
979
1164
  //# sourceMappingURL=common.cjs.map