@zapier/zapier-sdk 0.102.4 → 0.104.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.
@@ -706,7 +706,6 @@ function createDebugFetch(options) {
706
706
  // src/utils/retry-utils.ts
707
707
  var MAX_CONSECUTIVE_ERRORS = 3;
708
708
  var BASE_ERROR_BACKOFF_MILLISECONDS = 1e3;
709
- var BASE_EXPONENTIAL_BACKOFF_MILLISECONDS = 1e3;
710
709
  var JITTER_FACTOR = 0.5;
711
710
  function calculateErrorBackoffMs(baseInterval, errorCount) {
712
711
  const jitter = Math.random() * JITTER_FACTOR * baseInterval;
@@ -717,11 +716,6 @@ function calculateErrorBackoffMs(baseInterval, errorCount) {
717
716
  );
718
717
  return Math.floor(baseInterval + jitter + errorBackoff);
719
718
  }
720
- function calculateExponentialBackoffMs(attempt, baseDelayMs = BASE_EXPONENTIAL_BACKOFF_MILLISECONDS) {
721
- const baseDelay = baseDelayMs * Math.pow(2, attempt - 1);
722
- const jitter = Math.random() * JITTER_FACTOR * baseDelay;
723
- return Math.floor(baseDelay + jitter);
724
- }
725
719
  function sleep(ms, signal) {
726
720
  if (!signal) {
727
721
  return new Promise((resolve2) => setTimeout(resolve2, ms));
@@ -1076,7 +1070,114 @@ var sdkOptionsPluginRef = kitcore.declareOptionalProperty({
1076
1070
  id: SDK_OPTIONS_ID
1077
1071
  });
1078
1072
 
1073
+ // src/api/rate-limit.ts
1074
+ var EPOCH_THRESHOLD_SECONDS = 1e9;
1075
+ function parseRateLimitHeaders(response) {
1076
+ const info = {};
1077
+ const retryAfter = response.headers.get("retry-after");
1078
+ if (retryAfter) {
1079
+ const seconds = parseInt(retryAfter, 10);
1080
+ if (!isNaN(seconds)) {
1081
+ info.retryAfterMs = Math.max(0, seconds * 1e3);
1082
+ } else {
1083
+ const date = Date.parse(retryAfter);
1084
+ if (!isNaN(date)) {
1085
+ info.retryAfterMs = Math.max(0, date - Date.now());
1086
+ }
1087
+ }
1088
+ }
1089
+ const reset = response.headers.get("x-ratelimit-reset");
1090
+ if (reset) {
1091
+ const resetValue = parseInt(reset, 10);
1092
+ if (!isNaN(resetValue)) {
1093
+ const isEpoch = resetValue >= EPOCH_THRESHOLD_SECONDS;
1094
+ info.resetMs = isEpoch ? resetValue * 1e3 : Date.now() + resetValue * 1e3;
1095
+ if (info.retryAfterMs === void 0) {
1096
+ info.retryAfterMs = isEpoch ? Math.max(0, info.resetMs - Date.now()) : Math.max(0, resetValue * 1e3);
1097
+ }
1098
+ }
1099
+ }
1100
+ const limit = response.headers.get("x-ratelimit-limit");
1101
+ if (limit) {
1102
+ const limitNum = parseInt(limit, 10);
1103
+ if (!isNaN(limitNum)) {
1104
+ info.limit = limitNum;
1105
+ }
1106
+ }
1107
+ const remaining = response.headers.get("x-ratelimit-remaining");
1108
+ if (remaining) {
1109
+ const remainingNum = parseInt(remaining, 10);
1110
+ if (!isNaN(remainingNum)) {
1111
+ info.remaining = remainingNum;
1112
+ }
1113
+ }
1114
+ return info;
1115
+ }
1116
+
1117
+ // src/utils/type-guard-utils.ts
1118
+ function isPlainObject(value) {
1119
+ if (typeof value !== "object" || value === null) return false;
1120
+ const proto = Object.getPrototypeOf(value);
1121
+ return proto === Object.prototype || proto === null;
1122
+ }
1123
+ function isPromiseLike(value) {
1124
+ return (typeof value === "object" || typeof value === "function") && value !== null && "then" in value && typeof value.then === "function";
1125
+ }
1126
+
1127
+ // src/plugins/transport/retry-events.ts
1128
+ var retryCounts = /* @__PURE__ */ new WeakMap();
1129
+ function retriesFor(request) {
1130
+ return retryCounts.get(request) ?? 0;
1131
+ }
1132
+ function createRetryObserver({
1133
+ onEvent,
1134
+ maxNetworkRetries
1135
+ }) {
1136
+ return ({ request, attemptNumber, delayMilliseconds, response }) => {
1137
+ retryCounts.set(request, attemptNumber);
1138
+ if (!onEvent || response?.status !== 429) return;
1139
+ const observed = onEvent({
1140
+ type: "api:rate_limit_retry",
1141
+ payload: {
1142
+ retry: attemptNumber,
1143
+ maxNetworkRetries,
1144
+ delayMs: delayMilliseconds,
1145
+ path: request.url,
1146
+ method: request.method ?? "GET",
1147
+ rateLimit: parseRateLimitHeaders(response)
1148
+ },
1149
+ timestamp: Date.now()
1150
+ });
1151
+ if (isPromiseLike(observed)) {
1152
+ void Promise.resolve(observed).catch(() => {
1153
+ });
1154
+ }
1155
+ };
1156
+ }
1157
+
1079
1158
  // src/plugins/transport/options.ts
1159
+ var RETRYABLE_STATUSES = [429, 500, 502, 503, 504];
1160
+ var NON_IDEMPOTENT_RETRYABLE_STATUSES = [429];
1161
+ function resolveRetryHttpRequestOptions({
1162
+ maxNetworkRetries,
1163
+ maxNetworkRetryDelayMilliseconds,
1164
+ onEvent
1165
+ }) {
1166
+ const retries = maxNetworkRetries ?? ZAPIER_MAX_NETWORK_RETRIES;
1167
+ return {
1168
+ maxAttempts: retries + 1,
1169
+ maxDelayMilliseconds: maxNetworkRetryDelayMilliseconds ?? ZAPIER_MAX_NETWORK_RETRY_DELAY_MILLISECONDS,
1170
+ retryStatuses: RETRYABLE_STATUSES,
1171
+ nonIdempotentRetryStatuses: NON_IDEMPOTENT_RETRYABLE_STATUSES,
1172
+ onRetry: createRetryObserver({ onEvent, maxNetworkRetries: retries })
1173
+ };
1174
+ }
1175
+ function resolveNetworkRetryDelayMilliseconds({
1176
+ maxNetworkRetryDelaySeconds,
1177
+ maxNetworkRetryDelayMs
1178
+ }) {
1179
+ return maxNetworkRetryDelaySeconds != null ? maxNetworkRetryDelaySeconds * 1e3 : maxNetworkRetryDelayMs;
1180
+ }
1080
1181
  function resolveTransportFetch({
1081
1182
  fetch: customFetch,
1082
1183
  debug = false
@@ -1104,15 +1205,29 @@ var httpFetchPlugin = kitcore.defineProperty({
1104
1205
  // src/plugins/transport/standalone.ts
1105
1206
  function createZapierSendHttpRequest({
1106
1207
  fetch: customFetch,
1107
- debug
1208
+ debug,
1209
+ maxNetworkRetries,
1210
+ maxNetworkRetryDelayMilliseconds,
1211
+ onEvent
1108
1212
  }) {
1109
1213
  const sdk = kitcore.createSdk(
1110
1214
  kitcore.definePlugin({
1111
1215
  name: "zapierStandaloneHttpTransport",
1112
- imports: [kitcore.sendHttpRequestPlugin, httpFetchPlugin],
1216
+ imports: [kitcore.sendHttpRequestPlugin, kitcore.retryHttpRequestPlugin, httpFetchPlugin],
1113
1217
  exports: [kitcore.sendHttpRequestPlugin]
1114
1218
  }),
1115
- { configuration: { [SDK_OPTIONS_ID]: { fetch: customFetch, debug } } }
1219
+ {
1220
+ configuration: {
1221
+ [SDK_OPTIONS_ID]: { fetch: customFetch, debug },
1222
+ // Filled by configuration rather than the graph's property plugin: the
1223
+ // caller here holds client-shaped ms options, not SDK options.
1224
+ [kitcore.RETRY_HTTP_REQUEST_OPTIONS_ID]: resolveRetryHttpRequestOptions({
1225
+ maxNetworkRetries,
1226
+ maxNetworkRetryDelayMilliseconds,
1227
+ onEvent
1228
+ })
1229
+ }
1230
+ }
1116
1231
  );
1117
1232
  return sdk.sendHttpRequest;
1118
1233
  }
@@ -1770,13 +1885,6 @@ async function invalidateCredentialsToken(options) {
1770
1885
  });
1771
1886
  }
1772
1887
  }
1773
-
1774
- // src/utils/type-guard-utils.ts
1775
- function isPlainObject(value) {
1776
- if (typeof value !== "object" || value === null) return false;
1777
- const proto = Object.getPrototypeOf(value);
1778
- return proto === Object.prototype || proto === null;
1779
- }
1780
1888
  var callerContext = kitcore.createAsyncContext();
1781
1889
  function runWithCallerContext(context, fn) {
1782
1890
  let parent;
@@ -2022,9 +2130,6 @@ function sniffDeprecationNotice({
2022
2130
  }
2023
2131
  }
2024
2132
  }
2025
- function isPromiseLike(value) {
2026
- return (typeof value === "object" || typeof value === "function") && value !== null && "then" in value && typeof value.then === "function";
2027
- }
2028
2133
  function parseDeprecationDate(value) {
2029
2134
  if (!value) return void 0;
2030
2135
  const match = /^@(-?\d+)$/.exec(value.trim());
@@ -2032,8 +2137,528 @@ function parseDeprecationDate(value) {
2032
2137
  return Number(match[1]) * 1e3;
2033
2138
  }
2034
2139
 
2140
+ // src/api/routing/config.ts
2141
+ var pathConfig = {
2142
+ // e.g. /relay -> https://sdkapi.zapier.com/api/v0/sdk/relay/...
2143
+ "/relay": {
2144
+ authHeader: "X-Relay-Authorization",
2145
+ pathPrefix: "/api/v0/sdk/relay",
2146
+ omitDeprecationMessaging: true
2147
+ },
2148
+ // The concrete gateway form of the relay route. Callers that pass the
2149
+ // already-prefixed path reach the same third-party upstreams, so it must
2150
+ // classify as relay too; without this entry it would match nothing and
2151
+ // sniff deprecation headers off a relay response.
2152
+ "/api/v0/sdk/relay": {
2153
+ override: { enabled: false },
2154
+ omitDeprecationMessaging: true
2155
+ },
2156
+ // Concrete sdkapi routes that do not live behind /api/v0/sdk/<service>.
2157
+ "/api/v0": {},
2158
+ // e.g. /zapier -> https://sdkapi.zapier.com/api/v0/sdk/zapier/...
2159
+ "/zapier": {
2160
+ authHeader: "Authorization",
2161
+ pathPrefix: "/api/v0/sdk/zapier"
2162
+ },
2163
+ // e.g. /tables -> https://sdkapi.zapier.com/api/v0/sdk/tables/...
2164
+ "/tables": {
2165
+ authHeader: "Authorization",
2166
+ pathPrefix: "/api/v0/sdk/tables"
2167
+ },
2168
+ // e.g. /trigger-inbox -> https://sdkapi.zapier.com/api/v0/sdk/trigger-inbox/...
2169
+ "/trigger-inbox": {
2170
+ authHeader: "Authorization",
2171
+ pathPrefix: "/api/v0/sdk/trigger-inbox"
2172
+ },
2173
+ // e.g. /sdkdurableapi -> https://sdkapi.zapier.com/api/v0/sdk/sdkdurableapi/...
2174
+ // sdkapi proxies to the sdkdurableapi backend.
2175
+ "/sdkdurableapi": {
2176
+ authHeader: "Authorization",
2177
+ pathPrefix: "/api/v0/sdk/sdkdurableapi"
2178
+ },
2179
+ // e.g. /durableworkflowzaps -> https://sdkapi.zapier.com/api/v0/sdk/durableworkflowzaps/...
2180
+ // sdkapi proxies to the durableworkflowzaps backend.
2181
+ "/durableworkflowzaps": {
2182
+ authHeader: "Authorization",
2183
+ pathPrefix: "/api/v0/sdk/durableworkflowzaps"
2184
+ },
2185
+ // e.g. /code-substrate-runner -> https://sdkapi.zapier.com/api/v0/sdk/code-substrate-runner/...
2186
+ // sdkapi proxies to the code-substrate-runner backend.
2187
+ "/code-substrate-runner": {
2188
+ authHeader: "Authorization",
2189
+ pathPrefix: "/api/v0/sdk/code-substrate-runner"
2190
+ },
2191
+ // e.g. /code-substrate-workflows -> https://sdkapi.zapier.com/api/v0/sdk/code-substrate-workflows/...
2192
+ // sdkapi proxies to the code-substrate-workflows backend.
2193
+ "/code-substrate-workflows": {
2194
+ authHeader: "Authorization",
2195
+ pathPrefix: "/api/v0/sdk/code-substrate-workflows"
2196
+ },
2197
+ // e.g. /code-substrate-analyzer/validations ->
2198
+ // https://api.zapier.com/code-substrate-analyzer/v0/validations
2199
+ "/code-substrate-analyzer": {
2200
+ authHeader: "Authorization",
2201
+ pathPrefix: "/code-substrate-analyzer/v0",
2202
+ subdomain: "api",
2203
+ override: { pathPrefixMode: "preserve" }
2204
+ },
2205
+ // e.g. /forms/v0/forms -> https://api.zapier.com/forms/v0/forms
2206
+ // The Forms API is registered on the Public API Gateway and has no sdkapi
2207
+ // proxy route, so it goes straight to the gateway. Its governance metadata
2208
+ // rewrites /forms/v0/... to the backend's /api/forms/v0/..., which is why no
2209
+ // pathPrefix is applied here.
2210
+ "/forms": {
2211
+ authHeader: "Authorization",
2212
+ subdomain: "api"
2213
+ }
2214
+ };
2215
+
2216
+ // src/api/routing/default.ts
2217
+ function resolveDefaultRoute({
2218
+ path,
2219
+ baseUrl
2220
+ }) {
2221
+ const matchingPathEntries = findPathConfigEntries({ path });
2222
+ const routingMatch = matchingPathEntries[0];
2223
+ let finalPath = path;
2224
+ if (routingMatch?.config.pathPrefix) {
2225
+ const pathWithoutPrefix = path.slice(routingMatch.configPath.length) || "/";
2226
+ finalPath = `${routingMatch.config.pathPrefix}${pathWithoutPrefix}`;
2227
+ }
2228
+ const zapierBaseUrl = getZapierBaseUrl(baseUrl);
2229
+ if (zapierBaseUrl === baseUrl.replace(/\/$/, "")) {
2230
+ const originalBaseUrl = new URL(baseUrl);
2231
+ const subdomain = routingMatch?.config.subdomain ?? "sdkapi";
2232
+ const finalBaseUrl = `https://${subdomain}.${originalBaseUrl.hostname}`;
2233
+ return buildRouteResult({
2234
+ url: new URL(finalPath, finalBaseUrl),
2235
+ matchingPathEntries,
2236
+ routingMatch
2237
+ });
2238
+ }
2239
+ const parsedBaseUrl = new URL(baseUrl);
2240
+ const basePath = parsedBaseUrl.pathname.replace(/\/$/, "");
2241
+ return buildRouteResult({
2242
+ url: new URL(basePath + finalPath, parsedBaseUrl.origin),
2243
+ matchingPathEntries,
2244
+ routingMatch
2245
+ });
2246
+ }
2247
+ function buildRouteResult({
2248
+ url,
2249
+ matchingPathEntries,
2250
+ routingMatch
2251
+ }) {
2252
+ const resolvedPathEntries = findPathConfigEntries({
2253
+ path: url.pathname,
2254
+ matchResolvedGatewayPath: true
2255
+ });
2256
+ const deprecationPathMatches = [
2257
+ ...matchingPathEntries,
2258
+ ...resolvedPathEntries
2259
+ ];
2260
+ const canSendDeprecationMessaging = deprecationPathMatches.length > 0 && deprecationPathMatches.every(
2261
+ ({ config }) => config.omitDeprecationMessaging !== true
2262
+ );
2263
+ return {
2264
+ url,
2265
+ pathConfig: routingMatch?.config,
2266
+ canSendDeprecationMessaging
2267
+ };
2268
+ }
2269
+ function findPathConfigEntries({
2270
+ path,
2271
+ matchResolvedGatewayPath = false
2272
+ }) {
2273
+ const pathSegments = path.split("/").filter(Boolean);
2274
+ return Object.entries(pathConfig).filter(([configPath, config]) => {
2275
+ if (!matchResolvedGatewayPath) {
2276
+ return path === configPath || path.startsWith(`${configPath}/`);
2277
+ }
2278
+ const prefixSegments = (config.pathPrefix ?? configPath).split("/").filter(Boolean);
2279
+ return pathSegments.some(
2280
+ (_, startIndex) => prefixSegments.every(
2281
+ (segment, offset) => pathSegments[startIndex + offset] === segment
2282
+ )
2283
+ );
2284
+ }).map(([configPath, config]) => ({ configPath, config }));
2285
+ }
2286
+
2287
+ // src/api/routing/overrides.ts
2288
+ var ROUTE_ORIGIN_ENV_PREFIX = "ZAPIER_ROUTE_ORIGIN_";
2289
+ function createRoutingConfig({
2290
+ pathConfig: pathConfig2
2291
+ }) {
2292
+ const routeByEnvName = new Map(
2293
+ Object.entries(pathConfig2).filter(([, routeConfig]) => isRouteOverrideEnabled({ routeConfig })).map(([route]) => [encodeRouteOriginEnvName({ route }), route])
2294
+ );
2295
+ return { pathConfig: pathConfig2, routeByEnvName };
2296
+ }
2297
+ function encodeRouteOriginEnvName({ route }) {
2298
+ return `${ROUTE_ORIGIN_ENV_PREFIX}${route.slice(1).split("/").map((segment) => segment.replace(/-/g, "_").toUpperCase()).join("__")}`;
2299
+ }
2300
+ function createRouteOverrideEntry({
2301
+ route,
2302
+ source,
2303
+ sourceReference,
2304
+ origin
2305
+ }) {
2306
+ return {
2307
+ route,
2308
+ source,
2309
+ sourceReference,
2310
+ origin: parseRouteOverrideOrigin({
2311
+ origin,
2312
+ sourceLabel: describeRouteOverrideSource({ source, sourceReference })
2313
+ })
2314
+ };
2315
+ }
2316
+ function describeRouteOverrideSource({
2317
+ source,
2318
+ sourceReference
2319
+ }) {
2320
+ return source === "environment-variable" ? `environment variable ${sourceReference}` : `routeOverrides[${JSON.stringify(sourceReference)}]`;
2321
+ }
2322
+ function readOptionRouteOverrideEntries({
2323
+ routeOverrides,
2324
+ routingConfig: routingConfig2
2325
+ }) {
2326
+ const entries = [];
2327
+ if (!routeOverrides) {
2328
+ return entries;
2329
+ }
2330
+ for (const [route, origin] of Object.entries(routeOverrides)) {
2331
+ assertSupportedRouteOverride({ route, routingConfig: routingConfig2 });
2332
+ entries.push(
2333
+ createRouteOverrideEntry({
2334
+ route,
2335
+ source: "sdk-option",
2336
+ sourceReference: route,
2337
+ origin
2338
+ })
2339
+ );
2340
+ }
2341
+ return entries;
2342
+ }
2343
+ function buildRouteOverrideMap({
2344
+ environmentEntries,
2345
+ optionEntries
2346
+ }) {
2347
+ const routeOverrideMap = /* @__PURE__ */ new Map();
2348
+ for (const entry of environmentEntries) {
2349
+ routeOverrideMap.set(entry.route, entry);
2350
+ }
2351
+ for (const entry of optionEntries) {
2352
+ routeOverrideMap.set(entry.route, entry);
2353
+ }
2354
+ return routeOverrideMap;
2355
+ }
2356
+ function collectRouteOverrideSources({
2357
+ routingOptions,
2358
+ environment,
2359
+ routingConfig: routingConfig2
2360
+ }) {
2361
+ try {
2362
+ const optionEntries = routingOptions?.routeOverrideEntries ?? [];
2363
+ const environmentEntries = readEnvironmentRouteOverrideEntries({
2364
+ environment,
2365
+ routingConfig: routingConfig2
2366
+ });
2367
+ if (optionEntries.length === 0 && environmentEntries.length === 0) {
2368
+ return void 0;
2369
+ }
2370
+ return { optionEntries, environmentEntries };
2371
+ } catch (error) {
2372
+ if (isZapierError(error) && error.code === "ZAPIER_CONFIGURATION_ERROR") {
2373
+ throw error;
2374
+ }
2375
+ return void 0;
2376
+ }
2377
+ }
2378
+ function findRouteOverrideMatch({
2379
+ path,
2380
+ routeOverrideMap,
2381
+ routingConfig: routingConfig2
2382
+ }) {
2383
+ let bestMatch;
2384
+ const { pathname } = splitPathReference({ path });
2385
+ for (const override of routeOverrideMap.values()) {
2386
+ const { route } = override;
2387
+ for (const candidate of getRouteCandidates({
2388
+ route,
2389
+ override,
2390
+ routingConfig: routingConfig2
2391
+ })) {
2392
+ if (!pathnameMatchesPrefix({
2393
+ pathname,
2394
+ prefix: candidate.matchedPrefix
2395
+ })) {
2396
+ continue;
2397
+ }
2398
+ if (!bestMatch || compareRouteOverrideMatches(candidate, bestMatch) < 0) {
2399
+ bestMatch = candidate;
2400
+ }
2401
+ }
2402
+ }
2403
+ return bestMatch;
2404
+ }
2405
+ function resolveOverrideRoute({
2406
+ overrideMatch,
2407
+ path
2408
+ }) {
2409
+ const { matchedPrefix, routeConfig, override } = overrideMatch;
2410
+ const directPath = deriveDirectPath({ path, matchedPrefix, routeConfig });
2411
+ return {
2412
+ url: buildRouteOverrideUrl({ origin: override.origin, path: directPath }),
2413
+ pathConfig: routeConfig,
2414
+ canSendDeprecationMessaging: false
2415
+ };
2416
+ }
2417
+ function deriveDirectPath({
2418
+ path,
2419
+ matchedPrefix,
2420
+ routeConfig
2421
+ }) {
2422
+ const { pathPrefix } = routeConfig;
2423
+ if (!pathPrefix) {
2424
+ return path;
2425
+ }
2426
+ const remainingPath = path.slice(matchedPrefix.length) || "/";
2427
+ const override = routeConfig.override;
2428
+ const pathPrefixMode = override?.enabled === false ? "strip" : override?.pathPrefixMode ?? "strip";
2429
+ return pathPrefixMode === "preserve" ? `${pathPrefix}${remainingPath}` : remainingPath;
2430
+ }
2431
+ function readEnvironmentRouteOverrideEntries({
2432
+ environment,
2433
+ routingConfig: routingConfig2
2434
+ }) {
2435
+ const entries = [];
2436
+ if (!environment) {
2437
+ return entries;
2438
+ }
2439
+ for (const [envName, route] of routingConfig2.routeByEnvName) {
2440
+ let value;
2441
+ try {
2442
+ if (!Object.prototype.hasOwnProperty.call(environment, envName)) {
2443
+ continue;
2444
+ }
2445
+ value = environment[envName];
2446
+ } catch {
2447
+ continue;
2448
+ }
2449
+ if (isUnsetEnvironmentValue(value)) {
2450
+ continue;
2451
+ }
2452
+ entries.push(
2453
+ createRouteOverrideEntry({
2454
+ route,
2455
+ source: "environment-variable",
2456
+ sourceReference: envName,
2457
+ origin: value
2458
+ })
2459
+ );
2460
+ }
2461
+ return entries;
2462
+ }
2463
+ function isUnsetEnvironmentValue(value) {
2464
+ if (value === void 0 || value === null) {
2465
+ return true;
2466
+ }
2467
+ return typeof value === "string" && value.trim() === "";
2468
+ }
2469
+ function assertSupportedRouteOverride({
2470
+ route,
2471
+ routingConfig: routingConfig2
2472
+ }) {
2473
+ if (!Object.prototype.hasOwnProperty.call(routingConfig2.pathConfig, route)) {
2474
+ throw new ZapierConfigurationError(
2475
+ `Route override is not supported for ${route}`,
2476
+ { configType: "routeOverrides" }
2477
+ );
2478
+ }
2479
+ const routeConfig = routingConfig2.pathConfig[route];
2480
+ if (!isRouteOverrideEnabled({ routeConfig })) {
2481
+ throw new ZapierConfigurationError(
2482
+ `Route override is disabled for ${route}`,
2483
+ { configType: "routeOverrides" }
2484
+ );
2485
+ }
2486
+ }
2487
+ function parseRouteOverrideOrigin({
2488
+ origin,
2489
+ sourceLabel
2490
+ }) {
2491
+ if (typeof origin !== "string") {
2492
+ throw new ZapierConfigurationError(
2493
+ `Invalid route override origin for ${sourceLabel}`,
2494
+ { configType: "routeOverrides" }
2495
+ );
2496
+ }
2497
+ let url;
2498
+ try {
2499
+ url = new URL(origin);
2500
+ } catch {
2501
+ throw new ZapierConfigurationError(
2502
+ `Invalid route override origin for ${sourceLabel}`,
2503
+ { configType: "routeOverrides" }
2504
+ );
2505
+ }
2506
+ const includesOnlyOrigin = url.username === "" && url.password === "" && url.pathname === "/" && url.search === "" && url.hash === "";
2507
+ if (!includesOnlyOrigin) {
2508
+ throw new ZapierConfigurationError(
2509
+ `Invalid route override origin for ${sourceLabel}`,
2510
+ { configType: "routeOverrides" }
2511
+ );
2512
+ }
2513
+ if (url.protocol === "https:") {
2514
+ return url.origin;
2515
+ }
2516
+ if (url.protocol === "http:" && isLoopbackHost(url.hostname)) {
2517
+ return url.origin;
2518
+ }
2519
+ throw new ZapierConfigurationError(
2520
+ `Invalid route override origin for ${sourceLabel}: expected https, or http on a loopback host`,
2521
+ { configType: "routeOverrides" }
2522
+ );
2523
+ }
2524
+ function isLoopbackHost(hostname) {
2525
+ return hostname === "localhost" || hostname === "[::1]" || // pii:allow
2526
+ // RFC 6761 reserves .localhost names for loopback.
2527
+ hostname.endsWith(".localhost") || /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(hostname);
2528
+ }
2529
+ function compareRouteOverrideMatches(a, b) {
2530
+ return b.matchedPrefix.length - a.matchedPrefix.length || Number(b.isExactRouteSpelling) - Number(a.isExactRouteSpelling);
2531
+ }
2532
+ function getRouteCandidates({
2533
+ route,
2534
+ override,
2535
+ routingConfig: routingConfig2
2536
+ }) {
2537
+ const routeConfig = routingConfig2.pathConfig[route];
2538
+ if (!routeConfig || !isRouteOverrideEnabled({ routeConfig })) {
2539
+ return [];
2540
+ }
2541
+ const candidates = [
2542
+ {
2543
+ route,
2544
+ matchedPrefix: route,
2545
+ isExactRouteSpelling: true,
2546
+ routeConfig,
2547
+ override
2548
+ }
2549
+ ];
2550
+ if (routeConfig.pathPrefix && routeConfig.pathPrefix !== route) {
2551
+ candidates.push({
2552
+ route,
2553
+ matchedPrefix: routeConfig.pathPrefix,
2554
+ isExactRouteSpelling: false,
2555
+ routeConfig,
2556
+ override
2557
+ });
2558
+ }
2559
+ return candidates;
2560
+ }
2561
+ function buildRouteOverrideUrl({
2562
+ origin,
2563
+ path
2564
+ }) {
2565
+ const url = new URL(origin);
2566
+ const { pathname, search, hash } = splitPathReference({ path });
2567
+ url.pathname = pathname;
2568
+ url.search = search;
2569
+ url.hash = hash;
2570
+ return url;
2571
+ }
2572
+ function splitPathReference({ path }) {
2573
+ const hashStart = path.indexOf("#");
2574
+ const pathWithoutHash = hashStart === -1 ? path : path.slice(0, hashStart);
2575
+ const searchStart = pathWithoutHash.indexOf("?");
2576
+ return {
2577
+ pathname: searchStart === -1 ? pathWithoutHash : pathWithoutHash.slice(0, searchStart),
2578
+ search: searchStart === -1 ? "" : pathWithoutHash.slice(searchStart),
2579
+ hash: hashStart === -1 ? "" : path.slice(hashStart)
2580
+ };
2581
+ }
2582
+ function pathnameMatchesPrefix({
2583
+ pathname,
2584
+ prefix
2585
+ }) {
2586
+ return pathname === prefix || pathname.startsWith(`${prefix}/`);
2587
+ }
2588
+ function isRouteOverrideEnabled({
2589
+ routeConfig
2590
+ }) {
2591
+ return routeConfig.override?.enabled !== false;
2592
+ }
2593
+
2594
+ // src/api/routing/index.ts
2595
+ var routingConfig = createRoutingConfig({ pathConfig });
2596
+ function parseRoutingOptions({
2597
+ options
2598
+ }) {
2599
+ try {
2600
+ return {
2601
+ routeOverrideEntries: readOptionRouteOverrideEntries({
2602
+ routeOverrides: options.routeOverrides,
2603
+ routingConfig
2604
+ })
2605
+ };
2606
+ } catch (error) {
2607
+ if (isZapierError(error) && error.code === "ZAPIER_CONFIGURATION_ERROR") {
2608
+ throw error;
2609
+ }
2610
+ throw new ZapierConfigurationError("Failed to read SDK routing options", {
2611
+ configType: "routeOverrides",
2612
+ cause: error
2613
+ });
2614
+ }
2615
+ }
2616
+ function resolveRoute({
2617
+ path,
2618
+ baseUrl,
2619
+ routingOptions = { routeOverrideEntries: [] },
2620
+ environment,
2621
+ debugLog
2622
+ }) {
2623
+ const overrideSources = collectRouteOverrideSources({
2624
+ routingOptions,
2625
+ environment,
2626
+ routingConfig
2627
+ });
2628
+ if (!overrideSources) {
2629
+ return resolveDefaultRoute({ path, baseUrl });
2630
+ }
2631
+ const routeOverrideMap = buildRouteOverrideMap(overrideSources);
2632
+ const overrideMatch = findRouteOverrideMatch({
2633
+ path,
2634
+ routeOverrideMap,
2635
+ routingConfig
2636
+ });
2637
+ if (overrideMatch) {
2638
+ const resolved = resolveOverrideRoute({ overrideMatch, path });
2639
+ logRouteOverride({ path, resolved, overrideMatch, debugLog });
2640
+ return resolved;
2641
+ }
2642
+ return resolveDefaultRoute({ path, baseUrl });
2643
+ }
2644
+ function logRouteOverride({
2645
+ path,
2646
+ resolved,
2647
+ overrideMatch,
2648
+ debugLog
2649
+ }) {
2650
+ if (!debugLog) {
2651
+ return;
2652
+ }
2653
+ const { route, override } = overrideMatch;
2654
+ const source = describeRouteOverrideSource(override);
2655
+ debugLog(
2656
+ `Route override: ${path} -> ${resolved.url.toString()} (route ${route} via ${source})`
2657
+ );
2658
+ }
2659
+
2035
2660
  // src/sdk-version.ts
2036
- var SDK_VERSION = (typeof process !== "undefined" && process.env ? "0.102.4" : void 0) || "unknown";
2661
+ var SDK_VERSION = (typeof process !== "undefined" && process.env ? "0.104.0" : void 0) || "unknown";
2037
2662
 
2038
2663
  // src/utils/open-url.ts
2039
2664
  var nodePrefix = "node:";
@@ -2149,7 +2774,6 @@ var PollApprovalResponseSchema = zod.z.object({
2149
2774
  approval_url: zod.z.string().optional()
2150
2775
  });
2151
2776
  var APPROVAL_MAX_POLLING_INTERVAL_MILLISECONDS = 5e3;
2152
- var EPOCH_THRESHOLD_SECONDS = 1e9;
2153
2777
  function validateSdkPath(path) {
2154
2778
  if (!path.startsWith("/") || path.startsWith("//")) {
2155
2779
  throw new ZapierValidationError(
@@ -2157,146 +2781,17 @@ function validateSdkPath(path) {
2157
2781
  );
2158
2782
  }
2159
2783
  }
2160
- function findPathConfigEntries({
2161
- path,
2162
- matchResolvedGatewayPath = false
2163
- }) {
2164
- const pathSegments = path.split("/").filter(Boolean);
2165
- return Object.entries(pathConfig).filter(([configPath, config]) => {
2166
- if (!matchResolvedGatewayPath) {
2167
- return path === configPath || path.startsWith(`${configPath}/`);
2168
- }
2169
- const prefixSegments = (config.pathPrefix ?? configPath).split("/").filter(Boolean);
2170
- return pathSegments.some(
2171
- (_, startIndex) => prefixSegments.every(
2172
- (segment, offset) => pathSegments[startIndex + offset] === segment
2173
- )
2174
- );
2175
- }).map(([configPath, config]) => ({ configPath, config }));
2176
- }
2177
- function parseRateLimitHeaders(response) {
2178
- const info = {};
2179
- const retryAfter = response.headers.get("retry-after");
2180
- if (retryAfter) {
2181
- const seconds = parseInt(retryAfter, 10);
2182
- if (!isNaN(seconds)) {
2183
- info.retryAfterMs = seconds * 1e3;
2184
- } else {
2185
- const date = Date.parse(retryAfter);
2186
- if (!isNaN(date)) {
2187
- info.retryAfterMs = Math.max(0, date - Date.now());
2188
- }
2189
- }
2190
- }
2191
- const reset = response.headers.get("x-ratelimit-reset");
2192
- if (reset) {
2193
- const resetValue = parseInt(reset, 10);
2194
- if (!isNaN(resetValue)) {
2195
- const isEpoch = resetValue >= EPOCH_THRESHOLD_SECONDS;
2196
- info.resetMs = isEpoch ? resetValue * 1e3 : Date.now() + resetValue * 1e3;
2197
- if (info.retryAfterMs === void 0) {
2198
- info.retryAfterMs = isEpoch ? Math.max(0, info.resetMs - Date.now()) : Math.max(0, resetValue * 1e3);
2199
- }
2200
- }
2201
- }
2202
- const limit = response.headers.get("x-ratelimit-limit");
2203
- if (limit) {
2204
- const limitNum = parseInt(limit, 10);
2205
- if (!isNaN(limitNum)) {
2206
- info.limit = limitNum;
2207
- }
2208
- }
2209
- const remaining = response.headers.get("x-ratelimit-remaining");
2210
- if (remaining) {
2211
- const remainingNum = parseInt(remaining, 10);
2212
- if (!isNaN(remainingNum)) {
2213
- info.remaining = remainingNum;
2214
- }
2215
- }
2216
- return info;
2217
- }
2218
- var pathConfig = {
2219
- // e.g. /relay -> https://sdkapi.zapier.com/api/v0/sdk/relay/...
2220
- "/relay": {
2221
- authHeader: "X-Relay-Authorization",
2222
- pathPrefix: "/api/v0/sdk/relay",
2223
- omitDeprecationMessaging: true
2224
- },
2225
- // The concrete gateway form of the relay route. Callers that pass the
2226
- // already-prefixed path reach the same third-party upstreams, so it must
2227
- // classify as relay too; without this entry it would match nothing and
2228
- // sniff deprecation headers off a relay response.
2229
- "/api/v0/sdk/relay": {
2230
- omitDeprecationMessaging: true
2231
- },
2232
- // Concrete sdkapi routes that do not live behind /api/v0/sdk/<service>.
2233
- "/api/v0": {},
2234
- // e.g. /zapier -> https://sdkapi.zapier.com/api/v0/sdk/zapier/...
2235
- "/zapier": {
2236
- authHeader: "Authorization",
2237
- pathPrefix: "/api/v0/sdk/zapier"
2238
- },
2239
- // e.g. /tables -> https://sdkapi.zapier.com/api/v0/sdk/tables/...
2240
- "/tables": {
2241
- authHeader: "Authorization",
2242
- pathPrefix: "/api/v0/sdk/tables"
2243
- },
2244
- // e.g. /trigger-inbox -> https://sdkapi.zapier.com/api/v0/sdk/trigger-inbox/...
2245
- "/trigger-inbox": {
2246
- authHeader: "Authorization",
2247
- pathPrefix: "/api/v0/sdk/trigger-inbox"
2248
- },
2249
- // e.g. /sdkdurableapi -> https://sdkapi.zapier.com/api/v0/sdk/sdkdurableapi/...
2250
- // sdkapi proxies to the sdkdurableapi backend.
2251
- "/sdkdurableapi": {
2252
- authHeader: "Authorization",
2253
- pathPrefix: "/api/v0/sdk/sdkdurableapi"
2254
- },
2255
- // e.g. /durableworkflowzaps -> https://sdkapi.zapier.com/api/v0/sdk/durableworkflowzaps/...
2256
- // sdkapi proxies to the durableworkflowzaps backend.
2257
- "/durableworkflowzaps": {
2258
- authHeader: "Authorization",
2259
- pathPrefix: "/api/v0/sdk/durableworkflowzaps"
2260
- },
2261
- // e.g. /code-substrate-runner -> https://sdkapi.zapier.com/api/v0/sdk/code-substrate-runner/...
2262
- // sdkapi proxies to the code-substrate-runner backend.
2263
- "/code-substrate-runner": {
2264
- authHeader: "Authorization",
2265
- pathPrefix: "/api/v0/sdk/code-substrate-runner"
2266
- },
2267
- // e.g. /code-substrate-workflows -> https://sdkapi.zapier.com/api/v0/sdk/code-substrate-workflows/...
2268
- // sdkapi proxies to the code-substrate-workflows backend.
2269
- "/code-substrate-workflows": {
2270
- authHeader: "Authorization",
2271
- pathPrefix: "/api/v0/sdk/code-substrate-workflows"
2272
- },
2273
- // e.g. /code-substrate-analyzer/validations ->
2274
- // https://api.zapier.com/code-substrate-analyzer/v0/validations
2275
- "/code-substrate-analyzer": {
2276
- authHeader: "Authorization",
2277
- pathPrefix: "/code-substrate-analyzer/v0",
2278
- subdomain: "api"
2279
- },
2280
- // e.g. /forms/v0/forms -> https://api.zapier.com/forms/v0/forms
2281
- // The Forms API is registered on the Public API Gateway and has no sdkapi
2282
- // proxy route, so it goes straight to the gateway. Its governance metadata
2283
- // rewrites /forms/v0/... to the backend's /api/forms/v0/..., which is why no
2284
- // pathPrefix is applied here.
2285
- "/forms": {
2286
- authHeader: "Authorization",
2287
- subdomain: "api"
2288
- }
2289
- };
2290
2784
  var ZapierApiClient = class {
2291
2785
  constructor(options) {
2292
2786
  this.options = options;
2293
2787
  /**
2294
2788
  * Perform a request against an already-resolved URL.
2295
2789
  *
2296
- * Does auth, header merging, and 429 retry all the cross-cutting
2297
- * concerns that every Zapier-bound HTTP call needs. Callers that have a
2298
- * path (e.g. `/relay/...`) should use `rawFetch` instead, which does
2299
- * path URL resolution and delegates here.
2790
+ * Does auth and header merging, and converts a terminal 429 into
2791
+ * `ZapierRateLimitError` the cross-cutting concerns that every
2792
+ * Zapier-bound HTTP call needs and that the transport does not own. Callers
2793
+ * that have a path (e.g. `/relay/...`) should use `rawFetch` instead, which
2794
+ * does path → URL resolution and delegates here.
2300
2795
  *
2301
2796
  * Exposed as a separate helper so call sites with a server-supplied
2302
2797
  * absolute URL (e.g. an approval poll URL) can still share the same
@@ -2330,47 +2825,30 @@ var ZapierApiClient = class {
2330
2825
  resource: _resource,
2331
2826
  ...wireInit
2332
2827
  } = fetchInit;
2333
- let retries = 0;
2334
- while (true) {
2335
- const response = await this.options.sendHttpRequest({
2336
- ...wireInit,
2337
- // Set the resolved URL and merged headers after the spread so caller
2338
- // values cannot override them.
2339
- url,
2340
- headers: Object.fromEntries(mergedHeaders)
2341
- });
2342
- if (response.status !== 429) {
2343
- return response;
2344
- }
2345
- const rateLimitInfo = parseRateLimitHeaders(response);
2346
- const delayMs = rateLimitInfo.retryAfterMs ?? calculateExponentialBackoffMs(retries + 1);
2347
- if (delayMs > this.maxNetworkRetryDelayMilliseconds || retries >= this.maxNetworkRetries) {
2348
- throw new ZapierRateLimitError(
2349
- await this.readRateLimitErrorMessage(response),
2350
- {
2351
- statusCode: 429,
2352
- rateLimit: rateLimitInfo,
2353
- retries
2354
- }
2355
- );
2356
- }
2357
- retries++;
2358
- this.emitEvent("api:rate_limit_retry", {
2359
- retry: retries,
2360
- maxNetworkRetries: this.maxNetworkRetries,
2361
- delayMs,
2362
- path: url,
2363
- method: init?.method ?? "GET",
2364
- rateLimit: rateLimitInfo
2365
- });
2366
- await sleep(delayMs, init?.signal ?? void 0);
2828
+ const request = {
2829
+ ...wireInit,
2830
+ url,
2831
+ headers: Object.fromEntries(mergedHeaders)
2832
+ };
2833
+ const response = await this.options.sendHttpRequest(request);
2834
+ if (response.status !== 429) {
2835
+ return response;
2367
2836
  }
2837
+ throw new ZapierRateLimitError(
2838
+ await this.readRateLimitErrorMessage(response),
2839
+ {
2840
+ statusCode: 429,
2841
+ rateLimit: parseRateLimitHeaders(response),
2842
+ retries: retriesFor(request)
2843
+ }
2844
+ );
2368
2845
  };
2369
2846
  /**
2370
2847
  * Wrap an outbound HTTP call with the concurrency semaphore. Used by both
2371
2848
  * `rawFetch` (path-based) and the approval-poll path (absolute URL); each
2372
- * caller acquires per-attempt, so 429 retry sleep is held but the gap
2373
- * between approval polls and the human-approval wait are not.
2849
+ * caller acquires per request, so the transport's retry sleeps are held
2850
+ * inside the permit but the gap between approval polls and the
2851
+ * human-approval wait are not.
2374
2852
  *
2375
2853
  * The release is registered in a finally that wraps the entire post-
2376
2854
  * acquire flow — including the `wait_end` event emission — so a throwing
@@ -2620,8 +3098,6 @@ var ZapierApiClient = class {
2620
3098
  signal: options.signal
2621
3099
  });
2622
3100
  };
2623
- this.maxNetworkRetries = options.maxNetworkRetries ?? ZAPIER_MAX_NETWORK_RETRIES;
2624
- this.maxNetworkRetryDelayMilliseconds = options.maxNetworkRetryDelayMilliseconds ?? options.maxNetworkRetryDelayMs ?? ZAPIER_MAX_NETWORK_RETRY_DELAY_MILLISECONDS;
2625
3101
  const requested = options.maxConcurrentRequests;
2626
3102
  const limit = requested === void 0 || Number.isNaN(requested) ? ZAPIER_MAX_CONCURRENT_REQUESTS : requested;
2627
3103
  if (limit !== Infinity && (!Number.isInteger(limit) || limit < 1 || limit > MAX_CONCURRENCY_LIMIT)) {
@@ -2831,43 +3307,13 @@ var ZapierApiClient = class {
2831
3307
  }
2832
3308
  // Apply any special routing logic for configured paths.
2833
3309
  applyPathConfiguration(path) {
2834
- const matchingPathEntries = findPathConfigEntries({ path });
2835
- const routingMatch = matchingPathEntries[0];
2836
- const buildResult = (url2) => {
2837
- const resolvedPathEntries = findPathConfigEntries({
2838
- path: url2.pathname,
2839
- matchResolvedGatewayPath: true
2840
- });
2841
- const deprecationPathMatches = [
2842
- ...matchingPathEntries,
2843
- ...resolvedPathEntries
2844
- ];
2845
- const canSendDeprecationMessaging = deprecationPathMatches.length > 0 && deprecationPathMatches.every(
2846
- ({ config }) => config.omitDeprecationMessaging !== true
2847
- );
2848
- return {
2849
- url: url2,
2850
- pathConfig: routingMatch?.config,
2851
- canSendDeprecationMessaging
2852
- };
2853
- };
2854
- let finalPath = path;
2855
- if (routingMatch?.config.pathPrefix) {
2856
- const pathWithoutPrefix = path.slice(routingMatch.configPath.length) || "/";
2857
- finalPath = `${routingMatch.config.pathPrefix}${pathWithoutPrefix}`;
2858
- }
2859
- const zapierBaseUrl = getZapierBaseUrl(this.options.baseUrl);
2860
- if (zapierBaseUrl === this.options.baseUrl.replace(/\/$/, "")) {
2861
- const originalBaseUrl = new URL(this.options.baseUrl);
2862
- const subdomain = routingMatch?.config.subdomain ?? "sdkapi";
2863
- const finalBaseUrl = `https://${subdomain}.${originalBaseUrl.hostname}`;
2864
- const url2 = new URL(finalPath, finalBaseUrl);
2865
- return buildResult(url2);
2866
- }
2867
- const baseUrl = new URL(this.options.baseUrl);
2868
- const basePath = baseUrl.pathname.replace(/\/$/, "");
2869
- const url = new URL(basePath + finalPath, baseUrl.origin);
2870
- return buildResult(url);
3310
+ return resolveRoute({
3311
+ path,
3312
+ baseUrl: this.options.baseUrl,
3313
+ routingOptions: this.options.routingOptions,
3314
+ environment: globalThis.process?.env,
3315
+ debugLog: this.options.debugLog
3316
+ });
2871
3317
  }
2872
3318
  // Helper to build full URLs and return routing info
2873
3319
  buildUrl(path, searchParams) {
@@ -3396,6 +3842,7 @@ var ZapierApiClient = class {
3396
3842
  };
3397
3843
  var createZapierApi = (options) => {
3398
3844
  const { debug = false, fetch: originalFetch = globalThis.fetch } = options;
3845
+ const routingOptions = parseRoutingOptions({ options });
3399
3846
  const debugLog = createDebugLogger(debug);
3400
3847
  const debugFetch = createDebugFetch({ originalFetch, debugLog });
3401
3848
  return new ZapierApiClient({
@@ -3406,7 +3853,15 @@ var createZapierApi = (options) => {
3406
3853
  fetch: debugFetch,
3407
3854
  // Built from the caller's own `fetch`, not `debugFetch`: the pipeline wraps
3408
3855
  // for debug itself, and passing the wrapped one would log twice.
3409
- sendHttpRequest: options.sendHttpRequest ?? createZapierSendHttpRequest({ fetch: options.fetch, debug })
3856
+ sendHttpRequest: options.sendHttpRequest ?? createZapierSendHttpRequest({
3857
+ fetch: options.fetch,
3858
+ debug,
3859
+ onEvent: options.onEvent,
3860
+ maxNetworkRetries: options.maxNetworkRetries,
3861
+ maxNetworkRetryDelayMilliseconds: options.maxNetworkRetryDelayMilliseconds ?? options.maxNetworkRetryDelayMs
3862
+ }),
3863
+ debugLog,
3864
+ routingOptions
3410
3865
  });
3411
3866
  };
3412
3867
 
@@ -3443,10 +3898,30 @@ function getOrCreateApiClient(config) {
3443
3898
  callerPackage
3444
3899
  });
3445
3900
  }
3901
+ var retryHttpRequestOptionsPlugin = kitcore.defineProperty({
3902
+ namespace: "kitcore",
3903
+ name: "retryHttpRequestOptions",
3904
+ imports: [sdkOptionsPluginRef],
3905
+ setup: ({ imports }) => resolveRetryHttpRequestOptions({
3906
+ maxNetworkRetries: imports.sdkOptions?.maxNetworkRetries,
3907
+ maxNetworkRetryDelayMilliseconds: resolveNetworkRetryDelayMilliseconds(
3908
+ imports.sdkOptions ?? {}
3909
+ ),
3910
+ onEvent: imports.sdkOptions?.onEvent
3911
+ }),
3912
+ get: ({ state }) => state
3913
+ });
3914
+
3915
+ // src/plugins/transport/index.ts
3446
3916
  var zapierHttpTransportPlugin = kitcore.definePlugin({
3447
3917
  namespace: "zapier",
3448
3918
  name: "httpTransport",
3449
- imports: [kitcore.sendHttpRequestPlugin, httpFetchPlugin],
3919
+ imports: [
3920
+ kitcore.sendHttpRequestPlugin,
3921
+ kitcore.retryHttpRequestPlugin,
3922
+ retryHttpRequestOptionsPlugin,
3923
+ httpFetchPlugin
3924
+ ],
3450
3925
  exports: [kitcore.sendHttpRequestPlugin]
3451
3926
  });
3452
3927
 
@@ -3495,7 +3970,8 @@ var apiPlugin = kitcore.defineProperty({
3495
3970
  maxApprovalRetries,
3496
3971
  approvalMode,
3497
3972
  openAutoModeApprovalsInBrowser,
3498
- callerPackage
3973
+ callerPackage,
3974
+ routeOverrides
3499
3975
  } = imports.sdkOptions ?? {};
3500
3976
  return createZapierApi({
3501
3977
  baseUrl,
@@ -3505,13 +3981,17 @@ var apiPlugin = kitcore.defineProperty({
3505
3981
  fetch: customFetch,
3506
3982
  onEvent,
3507
3983
  maxNetworkRetries,
3508
- maxNetworkRetryDelayMilliseconds: (maxNetworkRetryDelaySeconds != null ? maxNetworkRetryDelaySeconds * 1e3 : maxNetworkRetryDelayMs) ?? ZAPIER_MAX_NETWORK_RETRY_DELAY_MILLISECONDS,
3984
+ maxNetworkRetryDelayMilliseconds: resolveNetworkRetryDelayMilliseconds({
3985
+ maxNetworkRetryDelaySeconds,
3986
+ maxNetworkRetryDelayMs
3987
+ }) ?? ZAPIER_MAX_NETWORK_RETRY_DELAY_MILLISECONDS,
3509
3988
  maxConcurrentRequests,
3510
3989
  approvalTimeoutMilliseconds: approvalTimeoutSeconds != null ? approvalTimeoutSeconds * 1e3 : approvalTimeoutMs,
3511
3990
  maxApprovalRetries,
3512
3991
  approvalMode,
3513
3992
  openAutoModeApprovalsInBrowser,
3514
3993
  callerPackage,
3994
+ routeOverrides,
3515
3995
  // Inject the graph-composed transport so host `dispatchHttpRequest` wraps
3516
3996
  // apply.
3517
3997
  sendHttpRequest: imports.sendHttpRequest
@@ -11906,19 +12386,23 @@ var BaseSdkOptionsSchema = zod.z.object({
11906
12386
  ),
11907
12387
  debug: zod.z.boolean().optional().describe("Enable debug logging."),
11908
12388
  baseUrl: zod.z.string().optional().describe("Base URL for Zapier API endpoints.").meta({ valueHint: "url" }),
12389
+ routeOverrides: zod.z.record(zod.z.string(), zod.z.string()).optional().describe("Maps SDK route prefixes to direct origins.").meta({ internal: true }),
11909
12390
  trackingBaseUrl: zod.z.string().optional().describe("Base URL for Zapier tracking endpoints.").meta({ valueHint: "url" }),
11910
12391
  /**
11911
- * Maximum number of retries for rate-limited requests (429 responses).
12392
+ * Maximum number of retries for rate-limited requests (429 responses) and,
12393
+ * on idempotent methods, retryable server errors (500, 502, 503, 504).
11912
12394
  * Set to 0 to disable retries. Default is 3.
11913
12395
  */
11914
- maxNetworkRetries: zod.z.number().optional().describe("Max retries for rate-limited requests (default: 3).").meta({ valueHint: "count" }),
12396
+ maxNetworkRetries: zod.z.number().optional().describe(
12397
+ "Max retries for rate-limited and server-error responses (default: 3)."
12398
+ ).meta({ valueHint: "count" }),
11915
12399
  /**
11916
- * Maximum delay in seconds to wait for a rate-limit retry.
12400
+ * Maximum delay in seconds to wait between network retries.
11917
12401
  * If the server requests a longer delay, the request fails immediately.
11918
12402
  * Default is 60 (60 seconds).
11919
12403
  */
11920
12404
  maxNetworkRetryDelaySeconds: zod.z.number().optional().describe(
11921
- "Max delay in seconds to wait for a rate-limit retry (default: 60)."
12405
+ "Max delay in seconds to wait between network retries (default: 60)."
11922
12406
  ).meta({ valueHint: "seconds" }),
11923
12407
  /** @deprecated Use `maxNetworkRetryDelaySeconds` instead. */
11924
12408
  maxNetworkRetryDelayMs: zod.z.number().optional().describe("Max delay in ms to wait for retry (default: 60000).").meta({ valueHint: "ms", deprecated: true }),