@zapier/zapier-sdk 0.102.0 → 0.102.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # @zapier/zapier-sdk
2
2
 
3
+ ## 0.102.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 173df80: `ZapierRateLimitError` now surfaces the server's explanatory message — quota used, when the window resets, and how to request a higher limit — instead of the bare string "Rate limited". When the response body is empty or unreadable, the message falls back to "Rate limited" as before.
8
+
9
+ `rateLimit.resetMs` on the error is now a real epoch timestamp when the server sends `X-RateLimit-Reset` as seconds-until-reset (the IETF draft convention) instead of a date in 1970. This also fixes the retry delay computed from that header when `Retry-After` is absent, which previously collapsed to zero and retried immediately.
10
+
11
+ - Updated dependencies [173df80]
12
+ - @zapier/kitcore@0.17.2
13
+
3
14
  ## 0.102.0
4
15
 
5
16
  ### Minor Changes
@@ -1988,7 +1988,7 @@ function parseDeprecationDate(value) {
1988
1988
  }
1989
1989
 
1990
1990
  // src/sdk-version.ts
1991
- var SDK_VERSION = (typeof process !== "undefined" && process.env ? "0.102.0" : void 0) || "unknown";
1991
+ var SDK_VERSION = (typeof process !== "undefined" && process.env ? "0.102.1" : void 0) || "unknown";
1992
1992
 
1993
1993
  // src/utils/open-url.ts
1994
1994
  var nodePrefix = "node:";
@@ -2104,6 +2104,7 @@ var PollApprovalResponseSchema = zod.z.object({
2104
2104
  approval_url: zod.z.string().optional()
2105
2105
  });
2106
2106
  var APPROVAL_MAX_POLLING_INTERVAL_MILLISECONDS = 5e3;
2107
+ var EPOCH_THRESHOLD_SECONDS = 1e9;
2107
2108
  function validateSdkPath(path) {
2108
2109
  if (!path.startsWith("/") || path.startsWith("//")) {
2109
2110
  throw new ZapierValidationError(
@@ -2144,11 +2145,12 @@ function parseRateLimitHeaders(response) {
2144
2145
  }
2145
2146
  const reset = response.headers.get("x-ratelimit-reset");
2146
2147
  if (reset) {
2147
- const resetTimestamp = parseInt(reset, 10);
2148
- if (!isNaN(resetTimestamp)) {
2149
- info.resetMs = resetTimestamp * 1e3;
2148
+ const resetValue = parseInt(reset, 10);
2149
+ if (!isNaN(resetValue)) {
2150
+ const isEpoch = resetValue >= EPOCH_THRESHOLD_SECONDS;
2151
+ info.resetMs = isEpoch ? resetValue * 1e3 : Date.now() + resetValue * 1e3;
2150
2152
  if (info.retryAfterMs === void 0) {
2151
- info.retryAfterMs = Math.max(0, info.resetMs - Date.now());
2153
+ info.retryAfterMs = isEpoch ? Math.max(0, info.resetMs - Date.now()) : Math.max(0, resetValue * 1e3);
2152
2154
  }
2153
2155
  }
2154
2156
  }
@@ -2286,11 +2288,14 @@ var ZapierApiClient = class {
2286
2288
  const rateLimitInfo = parseRateLimitHeaders(response);
2287
2289
  const delayMs = rateLimitInfo.retryAfterMs ?? calculateExponentialBackoffMs(retries + 1);
2288
2290
  if (delayMs > this.maxNetworkRetryDelayMilliseconds || retries >= this.maxNetworkRetries) {
2289
- throw new ZapierRateLimitError("Rate limited", {
2290
- statusCode: 429,
2291
- rateLimit: rateLimitInfo,
2292
- retries
2293
- });
2291
+ throw new ZapierRateLimitError(
2292
+ await this.readRateLimitErrorMessage(response),
2293
+ {
2294
+ statusCode: 429,
2295
+ rateLimit: rateLimitInfo,
2296
+ retries
2297
+ }
2298
+ );
2294
2299
  }
2295
2300
  retries++;
2296
2301
  this.emitEvent("api:rate_limit_retry", {
@@ -2725,6 +2730,17 @@ var ZapierApiClient = class {
2725
2730
  }
2726
2731
  return void 0;
2727
2732
  }
2733
+ // Surface the server's explanatory 429 body (e.g. quota details and how
2734
+ // to get a higher limit) instead of a bare "Rate limited". The body is
2735
+ // only consumed on the terminal throw path, never on retries.
2736
+ async readRateLimitErrorMessage(response) {
2737
+ try {
2738
+ const { data } = await this.parseResult(response);
2739
+ return this.extractErrorMessage(data)?.trim() || "Rate limited";
2740
+ } catch {
2741
+ return "Rate limited";
2742
+ }
2743
+ }
2728
2744
  // Helper to parse API error response
2729
2745
  parseErrorResponse(errorInfo) {
2730
2746
  const fallbackMessage = `HTTP ${errorInfo.status}: ${errorInfo.statusText}`;
@@ -1987,7 +1987,7 @@ function parseDeprecationDate(value) {
1987
1987
  }
1988
1988
 
1989
1989
  // src/sdk-version.ts
1990
- var SDK_VERSION = (typeof process !== "undefined" && process.env ? "0.102.0" : void 0) || "unknown";
1990
+ var SDK_VERSION = (typeof process !== "undefined" && process.env ? "0.102.1" : void 0) || "unknown";
1991
1991
 
1992
1992
  // src/utils/open-url.ts
1993
1993
  var nodePrefix = "node:";
@@ -2103,6 +2103,7 @@ var PollApprovalResponseSchema = z.object({
2103
2103
  approval_url: z.string().optional()
2104
2104
  });
2105
2105
  var APPROVAL_MAX_POLLING_INTERVAL_MILLISECONDS = 5e3;
2106
+ var EPOCH_THRESHOLD_SECONDS = 1e9;
2106
2107
  function validateSdkPath(path) {
2107
2108
  if (!path.startsWith("/") || path.startsWith("//")) {
2108
2109
  throw new ZapierValidationError(
@@ -2143,11 +2144,12 @@ function parseRateLimitHeaders(response) {
2143
2144
  }
2144
2145
  const reset = response.headers.get("x-ratelimit-reset");
2145
2146
  if (reset) {
2146
- const resetTimestamp = parseInt(reset, 10);
2147
- if (!isNaN(resetTimestamp)) {
2148
- info.resetMs = resetTimestamp * 1e3;
2147
+ const resetValue = parseInt(reset, 10);
2148
+ if (!isNaN(resetValue)) {
2149
+ const isEpoch = resetValue >= EPOCH_THRESHOLD_SECONDS;
2150
+ info.resetMs = isEpoch ? resetValue * 1e3 : Date.now() + resetValue * 1e3;
2149
2151
  if (info.retryAfterMs === void 0) {
2150
- info.retryAfterMs = Math.max(0, info.resetMs - Date.now());
2152
+ info.retryAfterMs = isEpoch ? Math.max(0, info.resetMs - Date.now()) : Math.max(0, resetValue * 1e3);
2151
2153
  }
2152
2154
  }
2153
2155
  }
@@ -2285,11 +2287,14 @@ var ZapierApiClient = class {
2285
2287
  const rateLimitInfo = parseRateLimitHeaders(response);
2286
2288
  const delayMs = rateLimitInfo.retryAfterMs ?? calculateExponentialBackoffMs(retries + 1);
2287
2289
  if (delayMs > this.maxNetworkRetryDelayMilliseconds || retries >= this.maxNetworkRetries) {
2288
- throw new ZapierRateLimitError("Rate limited", {
2289
- statusCode: 429,
2290
- rateLimit: rateLimitInfo,
2291
- retries
2292
- });
2290
+ throw new ZapierRateLimitError(
2291
+ await this.readRateLimitErrorMessage(response),
2292
+ {
2293
+ statusCode: 429,
2294
+ rateLimit: rateLimitInfo,
2295
+ retries
2296
+ }
2297
+ );
2293
2298
  }
2294
2299
  retries++;
2295
2300
  this.emitEvent("api:rate_limit_retry", {
@@ -2724,6 +2729,17 @@ var ZapierApiClient = class {
2724
2729
  }
2725
2730
  return void 0;
2726
2731
  }
2732
+ // Surface the server's explanatory 429 body (e.g. quota details and how
2733
+ // to get a higher limit) instead of a bare "Rate limited". The body is
2734
+ // only consumed on the terminal throw path, never on retries.
2735
+ async readRateLimitErrorMessage(response) {
2736
+ try {
2737
+ const { data } = await this.parseResult(response);
2738
+ return this.extractErrorMessage(data)?.trim() || "Rate limited";
2739
+ } catch {
2740
+ return "Rate limited";
2741
+ }
2742
+ }
2727
2743
  // Helper to parse API error response
2728
2744
  parseErrorResponse(errorInfo) {
2729
2745
  const fallbackMessage = `HTTP ${errorInfo.status}: ${errorInfo.statusText}`;