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