@zapier/zapier-sdk 0.50.0 → 0.52.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.
Files changed (63) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +2 -1
  3. package/dist/api/auth.d.ts +1 -6
  4. package/dist/api/auth.d.ts.map +1 -1
  5. package/dist/api/auth.js +34 -27
  6. package/dist/api/client.d.ts.map +1 -1
  7. package/dist/api/client.js +87 -9
  8. package/dist/api/concurrency.d.ts +28 -0
  9. package/dist/api/concurrency.d.ts.map +1 -0
  10. package/dist/api/concurrency.js +90 -0
  11. package/dist/api/index.d.ts +1 -1
  12. package/dist/api/index.d.ts.map +1 -1
  13. package/dist/api/index.js +1 -1
  14. package/dist/api/schemas.d.ts +3 -3
  15. package/dist/api/types.d.ts +6 -0
  16. package/dist/api/types.d.ts.map +1 -1
  17. package/dist/auth.d.ts +13 -2
  18. package/dist/auth.d.ts.map +1 -1
  19. package/dist/auth.js +95 -11
  20. package/dist/constants.d.ts +16 -0
  21. package/dist/constants.d.ts.map +1 -1
  22. package/dist/constants.js +29 -0
  23. package/dist/experimental.cjs +357 -34
  24. package/dist/experimental.d.mts +28 -28
  25. package/dist/experimental.d.ts +26 -26
  26. package/dist/experimental.mjs +353 -35
  27. package/dist/{index-BQ2ii0Bs.d.mts → index-DcdtPei-.d.mts} +132 -2
  28. package/dist/{index-BQ2ii0Bs.d.ts → index-DcdtPei-.d.ts} +132 -2
  29. package/dist/index.cjs +357 -34
  30. package/dist/index.d.mts +1 -1
  31. package/dist/index.d.ts +2 -1
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +1 -0
  34. package/dist/index.mjs +353 -35
  35. package/dist/plugins/api/index.d.ts.map +1 -1
  36. package/dist/plugins/api/index.js +3 -2
  37. package/dist/plugins/apps/index.d.ts +2 -2
  38. package/dist/plugins/deprecated/inputFields.d.ts +18 -18
  39. package/dist/plugins/getAction/index.d.ts +6 -6
  40. package/dist/plugins/getAction/schemas.d.ts +4 -4
  41. package/dist/plugins/getActionInputFieldsSchema/index.d.ts +5 -5
  42. package/dist/plugins/getActionInputFieldsSchema/schemas.d.ts +4 -4
  43. package/dist/plugins/listActionInputFieldChoices/index.d.ts +5 -5
  44. package/dist/plugins/listActionInputFieldChoices/schemas.d.ts +4 -4
  45. package/dist/plugins/listActionInputFields/index.d.ts +5 -5
  46. package/dist/plugins/listActionInputFields/schemas.d.ts +4 -4
  47. package/dist/plugins/listActions/index.d.ts +3 -3
  48. package/dist/plugins/listActions/schemas.d.ts +4 -4
  49. package/dist/plugins/runAction/index.d.ts +5 -5
  50. package/dist/plugins/runAction/schemas.d.ts +4 -4
  51. package/dist/plugins/triggers/getTriggerInputFieldsSchema/index.d.ts +2 -2
  52. package/dist/plugins/triggers/listTriggerInputFieldChoices/index.d.ts +2 -2
  53. package/dist/plugins/triggers/listTriggerInputFields/index.d.ts +2 -2
  54. package/dist/schemas/Action.d.ts +1 -1
  55. package/dist/sdk.d.ts +52 -52
  56. package/dist/types/properties.d.ts +1 -1
  57. package/dist/types/sdk.d.ts +1 -0
  58. package/dist/types/sdk.d.ts.map +1 -1
  59. package/dist/types/sdk.js +25 -0
  60. package/dist/utils/telemetry.d.ts +11 -0
  61. package/dist/utils/telemetry.d.ts.map +1 -0
  62. package/dist/utils/telemetry.js +19 -0
  63. package/package.json +1 -1
@@ -150,6 +150,21 @@ function parseIntEnvVar(name) {
150
150
  }
151
151
  var ZAPIER_MAX_NETWORK_RETRIES = parseIntEnvVar("ZAPIER_MAX_NETWORK_RETRIES") ?? 3;
152
152
  var ZAPIER_MAX_NETWORK_RETRY_DELAY_MS = parseIntEnvVar("ZAPIER_MAX_NETWORK_RETRY_DELAY_MS") ?? 6e4;
153
+ var MAX_CONCURRENCY_LIMIT = 1e4;
154
+ function parseConcurrencyEnvVar(name) {
155
+ const value = globalThis.process?.env?.[name];
156
+ if (!value) return void 0;
157
+ if (value === "Infinity") return Infinity;
158
+ if (/^[1-9]\d*$/.test(value)) {
159
+ const parsed = parseInt(value, 10);
160
+ if (parsed <= MAX_CONCURRENCY_LIMIT) return parsed;
161
+ }
162
+ console.warn(
163
+ `[zapier-sdk] Invalid value for ${name}: "${value}" (expected positive integer 1-${MAX_CONCURRENCY_LIMIT} or "Infinity")`
164
+ );
165
+ return void 0;
166
+ }
167
+ var ZAPIER_MAX_CONCURRENT_REQUESTS = parseConcurrencyEnvVar("ZAPIER_MAX_CONCURRENT_REQUESTS") ?? 200;
153
168
  function getZapierApprovalMode() {
154
169
  const value = globalThis.process?.env?.ZAPIER_APPROVAL_MODE;
155
170
  if (value === "disabled" || value === "poll" || value === "throw")
@@ -1836,33 +1851,40 @@ function getAuthorizationHeader(token) {
1836
1851
  }
1837
1852
  return `Bearer ${token}`;
1838
1853
  }
1839
- function extractUserIdsFromJwt(token) {
1854
+ function readJwtPayload(token) {
1840
1855
  const parts = parseJwt(token);
1841
- if (!parts) {
1842
- return { customuser_id: null, account_id: null };
1843
- }
1856
+ if (!parts) return null;
1857
+ let payload;
1844
1858
  try {
1845
- const payload = JSON.parse(
1846
- Buffer.from(parts[1], "base64url").toString("utf-8")
1847
- );
1848
- let actualPayload = payload;
1849
- if (payload.sub_type === "service" && payload.njwt) {
1850
- const nestedParts = payload.njwt.split(".");
1851
- if (nestedParts.length === 3) {
1852
- actualPayload = JSON.parse(
1859
+ payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf-8"));
1860
+ } catch {
1861
+ return null;
1862
+ }
1863
+ if (payload["sub_type"] === "service" && typeof payload["njwt"] === "string") {
1864
+ const nestedParts = parseJwt(payload["njwt"]);
1865
+ if (nestedParts) {
1866
+ try {
1867
+ return JSON.parse(
1853
1868
  Buffer.from(nestedParts[1], "base64url").toString("utf-8")
1854
1869
  );
1870
+ } catch {
1855
1871
  }
1856
1872
  }
1857
- const accountId = actualPayload["zap:acc"] != null ? parseInt(String(actualPayload["zap:acc"]), 10) : null;
1858
- const customUserId = actualPayload.sub_type === "customuser" && actualPayload.sub != null ? parseInt(String(actualPayload.sub), 10) : null;
1859
- return {
1860
- customuser_id: customUserId !== null && !isNaN(customUserId) ? customUserId : null,
1861
- account_id: accountId !== null && !isNaN(accountId) ? accountId : null
1862
- };
1863
- } catch {
1873
+ }
1874
+ return payload;
1875
+ }
1876
+ function extractUserIdsFromJwt(token) {
1877
+ const payload = readJwtPayload(token);
1878
+ if (!payload) {
1864
1879
  return { customuser_id: null, account_id: null };
1865
1880
  }
1881
+ const accRaw = payload["zap:acc"];
1882
+ const accountId = accRaw != null ? parseInt(String(accRaw), 10) : null;
1883
+ const customUserId = payload["sub_type"] === "customuser" && payload["sub"] != null ? parseInt(String(payload["sub"]), 10) : null;
1884
+ return {
1885
+ customuser_id: customUserId !== null && !isNaN(customUserId) ? customUserId : null,
1886
+ account_id: accountId !== null && !isNaN(accountId) ? accountId : null
1887
+ };
1866
1888
  }
1867
1889
 
1868
1890
  // src/api/debug.ts
@@ -2170,6 +2192,83 @@ async function pollUntilComplete(options) {
2170
2192
  }
2171
2193
  }
2172
2194
  }
2195
+
2196
+ // src/api/concurrency.ts
2197
+ var NO_OP_RELEASE = () => {
2198
+ };
2199
+ var NO_OP_SEMAPHORE = {
2200
+ acquire: async () => NO_OP_RELEASE,
2201
+ tryAcquire: () => NO_OP_RELEASE
2202
+ };
2203
+ function createSemaphore(maxPermits) {
2204
+ if (maxPermits === Infinity) {
2205
+ return NO_OP_SEMAPHORE;
2206
+ }
2207
+ if (!Number.isInteger(maxPermits) || maxPermits <= 0) {
2208
+ throw new Error(
2209
+ `maxPermits must be a positive integer or Infinity, got: ${maxPermits}`
2210
+ );
2211
+ }
2212
+ let permits = maxPermits;
2213
+ const waiters = [];
2214
+ const release = () => {
2215
+ const next = waiters.shift();
2216
+ if (next) {
2217
+ next.grant();
2218
+ } else {
2219
+ permits++;
2220
+ }
2221
+ };
2222
+ const makeReleaseOnce = () => {
2223
+ let released = false;
2224
+ return () => {
2225
+ if (released) return;
2226
+ released = true;
2227
+ release();
2228
+ };
2229
+ };
2230
+ return {
2231
+ tryAcquire() {
2232
+ if (permits > 0) {
2233
+ permits--;
2234
+ return makeReleaseOnce();
2235
+ }
2236
+ return null;
2237
+ },
2238
+ async acquire(signal) {
2239
+ if (signal?.aborted) {
2240
+ throw signal.reason ?? new DOMException("Aborted", "AbortError");
2241
+ }
2242
+ if (permits > 0) {
2243
+ permits--;
2244
+ return makeReleaseOnce();
2245
+ }
2246
+ return new Promise((resolve2, reject) => {
2247
+ const onAbort = () => {
2248
+ const idx = waiters.indexOf(waiter);
2249
+ if (idx !== -1) {
2250
+ waiters.splice(idx, 1);
2251
+ waiter.cancel(
2252
+ signal?.reason ?? new DOMException("Aborted", "AbortError")
2253
+ );
2254
+ }
2255
+ };
2256
+ const waiter = {
2257
+ grant: () => {
2258
+ signal?.removeEventListener("abort", onAbort);
2259
+ resolve2(makeReleaseOnce());
2260
+ },
2261
+ cancel: (reason) => {
2262
+ signal?.removeEventListener("abort", onAbort);
2263
+ reject(reason);
2264
+ }
2265
+ };
2266
+ signal?.addEventListener("abort", onAbort);
2267
+ waiters.push(waiter);
2268
+ });
2269
+ }
2270
+ };
2271
+ }
2173
2272
  var ClientCredentialsObjectSchema = zod.z.object({
2174
2273
  type: zod.z.enum(["client_credentials"]).optional().meta({ internal: true }),
2175
2274
  clientId: zod.z.string().describe("OAuth client ID for authentication.").meta({ valueHint: "id" }),
@@ -2211,6 +2310,19 @@ function isCredentialsFunction(credentials) {
2211
2310
  return typeof credentials === "function";
2212
2311
  }
2213
2312
 
2313
+ // src/utils/telemetry.ts
2314
+ var emittedOnce = /* @__PURE__ */ new WeakMap();
2315
+ function emitOnce(onEvent, event) {
2316
+ if (!emittedOnce.has(onEvent)) {
2317
+ emittedOnce.set(onEvent, /* @__PURE__ */ new Set());
2318
+ }
2319
+ const fired = emittedOnce.get(onEvent);
2320
+ if (!fired.has(event.type)) {
2321
+ fired.add(event.type);
2322
+ onEvent(event);
2323
+ }
2324
+ }
2325
+
2214
2326
  // src/utils/url-utils.ts
2215
2327
  function getZapierBaseUrl(baseUrl) {
2216
2328
  if (!baseUrl) {
@@ -2433,8 +2545,10 @@ async function resolveCache(options) {
2433
2545
  if (cliLogin?.createCache) {
2434
2546
  try {
2435
2547
  const cache = cliLogin.createCache();
2436
- cachedDefaultCache = cache;
2437
- return cache;
2548
+ if (cache) {
2549
+ cachedDefaultCache = cache;
2550
+ return cache;
2551
+ }
2438
2552
  } catch {
2439
2553
  }
2440
2554
  }
@@ -2446,6 +2560,11 @@ function entryIsValid(entry) {
2446
2560
  if (entry.expiresAt === void 0) return true;
2447
2561
  return entry.expiresAt > Date.now() + TOKEN_EXPIRATION_BUFFER_MS;
2448
2562
  }
2563
+ async function readCachedToken(cacheKey, cache) {
2564
+ const cached = await cache.get(cacheKey);
2565
+ if (cached && entryIsValid(cached)) return cached.value;
2566
+ return void 0;
2567
+ }
2449
2568
  async function invalidateCachedToken(options) {
2450
2569
  const cacheKey = buildCacheKey(options);
2451
2570
  pendingExchanges.delete(cacheKey);
@@ -2559,11 +2678,76 @@ function isCliLoginAvailable() {
2559
2678
  if (cachedCliLogin === void 0) return void 0;
2560
2679
  return cachedCliLogin !== false;
2561
2680
  }
2681
+ function emitAuthResolved(onEvent, mechanism) {
2682
+ if (onEvent) {
2683
+ emitOnce(onEvent, {
2684
+ type: "auth_resolved",
2685
+ payload: { mechanism },
2686
+ timestamp: Date.now()
2687
+ });
2688
+ }
2689
+ }
2690
+ async function getActiveCredentialsFromCli(baseUrl) {
2691
+ const cliLogin = await getCliLogin();
2692
+ return cliLogin?.getActiveCredentials?.({ baseUrl });
2693
+ }
2694
+ async function getStoredClientCredentialsFromCli(baseUrl) {
2695
+ const cliLogin = await getCliLogin();
2696
+ return cliLogin?.getStoredClientCredentials?.({ baseUrl });
2697
+ }
2562
2698
  async function getTokenFromCliLogin(options) {
2563
2699
  const cliLogin = await getCliLogin();
2564
2700
  if (!cliLogin) return void 0;
2565
2701
  return await cliLogin.getToken(options);
2566
2702
  }
2703
+ async function tryStoredClientCredentialToken(options) {
2704
+ const activeCredential = await getActiveCredentialsFromCli(options.baseUrl);
2705
+ if (!activeCredential) return void 0;
2706
+ const resolvedBaseUrl = activeCredential.baseUrl || options.baseUrl || DEFAULT_AUTH_BASE_URL;
2707
+ const mergedScopes = mergeScopes(
2708
+ activeCredential.scopes.join(" "),
2709
+ options.requiredScopes
2710
+ );
2711
+ const cacheKey = buildCacheKey({
2712
+ clientId: activeCredential.clientId,
2713
+ scopes: mergedScopes,
2714
+ baseUrl: resolvedBaseUrl
2715
+ });
2716
+ const cache = await resolveCache(options);
2717
+ const pending = pendingExchanges.get(cacheKey);
2718
+ if (pending) return pending;
2719
+ const cached = await readCachedToken(cacheKey, cache);
2720
+ if (cached !== void 0) {
2721
+ if (options.debug)
2722
+ console.log(
2723
+ `[auth] Using cached token (clientId: ${activeCredential.clientId})`
2724
+ );
2725
+ emitAuthResolved(options.onEvent, "client_credentials");
2726
+ return cached;
2727
+ }
2728
+ const storedCredential = await getStoredClientCredentialsFromCli(resolvedBaseUrl);
2729
+ if (!storedCredential) {
2730
+ await invalidateCachedToken({
2731
+ clientId: activeCredential.clientId,
2732
+ scopes: activeCredential.scopes,
2733
+ baseUrl: resolvedBaseUrl,
2734
+ cache: options.cache
2735
+ });
2736
+ throw new ZapierAuthenticationError(
2737
+ `Stored client credential is missing its secret (clientId: ${activeCredential.clientId}). Run \`zapier-sdk login\` to recreate it.`
2738
+ );
2739
+ }
2740
+ if (options.debug)
2741
+ console.log(
2742
+ `[auth] Using stored client credential (clientId: ${storedCredential.clientId})`
2743
+ );
2744
+ const token = await resolveAuthTokenFromCredentials(
2745
+ storedCredential,
2746
+ options
2747
+ );
2748
+ emitAuthResolved(options.onEvent, "client_credentials");
2749
+ return token;
2750
+ }
2567
2751
  async function resolveAuthToken(options = {}) {
2568
2752
  const credentials = await resolveCredentials({
2569
2753
  credentials: options.credentials,
@@ -2573,14 +2757,24 @@ async function resolveAuthToken(options = {}) {
2573
2757
  if (credentials !== void 0) {
2574
2758
  return resolveAuthTokenFromCredentials(credentials, options);
2575
2759
  }
2576
- return getTokenFromCliLogin({
2760
+ const storedToken = await tryStoredClientCredentialToken(options);
2761
+ if (storedToken !== void 0) return storedToken;
2762
+ if (options.debug) {
2763
+ console.log("[auth] Using JWT (no stored client credential found)");
2764
+ }
2765
+ const jwtToken = await getTokenFromCliLogin({
2577
2766
  onEvent: options.onEvent,
2578
2767
  fetch: options.fetch,
2579
2768
  debug: options.debug
2580
2769
  });
2770
+ if (jwtToken !== void 0) {
2771
+ emitAuthResolved(options.onEvent, "jwt");
2772
+ }
2773
+ return jwtToken;
2581
2774
  }
2582
2775
  async function resolveAuthTokenFromCredentials(credentials, options) {
2583
2776
  if (typeof credentials === "string") {
2777
+ emitAuthResolved(options.onEvent, "token");
2584
2778
  return credentials;
2585
2779
  }
2586
2780
  if (isClientCredentials(credentials)) {
@@ -2593,15 +2787,25 @@ async function resolveAuthTokenFromCredentials(credentials, options) {
2593
2787
  baseUrl: resolvedBaseUrl
2594
2788
  });
2595
2789
  const cache = await resolveCache(options);
2596
- const cached = await cache.get(cacheKey);
2597
- if (cached && entryIsValid(cached)) {
2598
- return cached.value;
2790
+ const cached = await readCachedToken(cacheKey, cache);
2791
+ if (cached !== void 0) {
2792
+ if (options.debug) {
2793
+ console.log(`[auth] Using cached token (clientId: ${clientId})`);
2794
+ }
2795
+ return cached;
2599
2796
  }
2600
2797
  const pending = pendingExchanges.get(cacheKey);
2601
2798
  if (pending) return pending;
2602
2799
  const runLocked = async () => {
2603
- const recheck = await cache.get(cacheKey);
2604
- if (recheck && entryIsValid(recheck)) return recheck.value;
2800
+ const recheck = await readCachedToken(cacheKey, cache);
2801
+ if (recheck !== void 0) {
2802
+ if (options.debug) {
2803
+ console.log(
2804
+ `[auth] Using cached token (clientId: ${clientId}, locked recheck)`
2805
+ );
2806
+ }
2807
+ return recheck;
2808
+ }
2605
2809
  const { accessToken, expiresIn } = await exchangeClientCredentials({
2606
2810
  clientId: credentials.clientId,
2607
2811
  clientSecret: credentials.clientSecret,
@@ -2659,7 +2863,7 @@ async function invalidateCredentialsToken(options) {
2659
2863
  }
2660
2864
 
2661
2865
  // src/sdk-version.ts
2662
- var SDK_VERSION = (typeof process !== "undefined" && process.env ? "0.50.0" : void 0) || "unknown";
2866
+ var SDK_VERSION = (typeof process !== "undefined" && process.env ? "0.52.0" : void 0) || "unknown";
2663
2867
 
2664
2868
  // src/utils/open-url.ts
2665
2869
  var nodePrefix = "node:";
@@ -2869,9 +3073,61 @@ var ZapierApiClient = class {
2869
3073
  await sleep(delayMs, init?.signal ?? void 0);
2870
3074
  }
2871
3075
  };
3076
+ /**
3077
+ * Wrap an outbound HTTP call with the concurrency semaphore. Used by both
3078
+ * `rawFetch` (path-based) and the approval-poll path (absolute URL); each
3079
+ * caller acquires per-attempt, so 429 retry sleep is held but the gap
3080
+ * between approval polls and the human-approval wait are not.
3081
+ *
3082
+ * The release is registered in a finally that wraps the entire post-
3083
+ * acquire flow — including the `wait_end` event emission — so a throwing
3084
+ * `onEvent` handler can never leak a permit.
3085
+ *
3086
+ * Slot lifetime is intentionally tied to "fetch resolves" (headers
3087
+ * received), NOT to "response body fully consumed". WHATWG `fetch()`
3088
+ * resolves once headers are in; the body is still streaming. We rely on
3089
+ * that boundary so streaming responses (SSE, long-running chunked reads)
3090
+ * don't pin a permit for the lifetime of the stream — a single SSE
3091
+ * consumer would otherwise hold one of N slots for as long as the
3092
+ * connection stays open. Do not move the release into a path that awaits
3093
+ * body consumption (e.g. `parseResult` / `response.text()`); doing so
3094
+ * would silently break streaming consumers without failing any of the
3095
+ * short-request tests.
3096
+ */
3097
+ this.withSemaphore = async (context, fn) => {
3098
+ const fastRelease = this.semaphore.tryAcquire();
3099
+ let waitStart = null;
3100
+ let release = fastRelease;
3101
+ if (release === null) {
3102
+ waitStart = Date.now();
3103
+ this.emitEvent("api:concurrency_wait_start", {
3104
+ url: context.url,
3105
+ method: context.method
3106
+ });
3107
+ release = await this.semaphore.acquire(context.signal ?? void 0);
3108
+ }
3109
+ const acquiredRelease = release;
3110
+ try {
3111
+ if (waitStart !== null) {
3112
+ this.emitEvent("api:concurrency_wait_end", {
3113
+ url: context.url,
3114
+ method: context.method,
3115
+ waitedMs: Date.now() - waitStart
3116
+ });
3117
+ }
3118
+ return await fn();
3119
+ } finally {
3120
+ acquiredRelease();
3121
+ }
3122
+ };
2872
3123
  /**
2873
3124
  * Perform a request with auth, header merging, and rate-limit (429) retries.
2874
3125
  * Does NOT handle 403 approval_required — that's routed by `fetch`.
3126
+ *
3127
+ * Concurrency: a semaphore slot is held across the entire call, including
3128
+ * the 429 retry sleep inside `rawFetchUrl`. That keeps backpressure
3129
+ * coherent — when the server is rate-limiting us, we don't dump more
3130
+ * parallelism into the queue.
2875
3131
  */
2876
3132
  this.rawFetch = async (path, init) => {
2877
3133
  if (!path.startsWith("/")) {
@@ -2880,7 +3136,10 @@ var ZapierApiClient = class {
2880
3136
  );
2881
3137
  }
2882
3138
  const { url, pathConfig: pathConfig2 } = this.buildUrl(path, init?.searchParams);
2883
- return this.rawFetchUrl(url, init, pathConfig2);
3139
+ return this.withSemaphore(
3140
+ { url, method: init?.method ?? "GET", signal: init?.signal },
3141
+ () => this.rawFetchUrl(url, init, pathConfig2)
3142
+ );
2884
3143
  };
2885
3144
  /**
2886
3145
  * Approval-aware HTTP fetch.
@@ -2988,6 +3247,15 @@ var ZapierApiClient = class {
2988
3247
  };
2989
3248
  this.maxNetworkRetries = options.maxNetworkRetries ?? ZAPIER_MAX_NETWORK_RETRIES;
2990
3249
  this.maxNetworkRetryDelayMs = options.maxNetworkRetryDelayMs ?? ZAPIER_MAX_NETWORK_RETRY_DELAY_MS;
3250
+ const requested = options.maxConcurrentRequests;
3251
+ const limit = requested === void 0 || Number.isNaN(requested) ? ZAPIER_MAX_CONCURRENT_REQUESTS : requested;
3252
+ if (limit !== Infinity && (!Number.isInteger(limit) || limit < 1 || limit > MAX_CONCURRENCY_LIMIT)) {
3253
+ throw new ZapierConfigurationError(
3254
+ `Invalid maxConcurrentRequests: ${limit} (expected positive integer 1-${MAX_CONCURRENCY_LIMIT} or Infinity)`,
3255
+ { configType: "maxConcurrentRequests" }
3256
+ );
3257
+ }
3258
+ this.semaphore = createSemaphore(limit);
2991
3259
  }
2992
3260
  // Emit an event if onEvent handler is configured
2993
3261
  emitEvent(type, payload) {
@@ -3240,7 +3508,9 @@ var ZapierApiClient = class {
3240
3508
  if (data && typeof data === "object") {
3241
3509
  headers["Content-Type"] = "application/json";
3242
3510
  }
3243
- const wasMissingAuthToken = options.authRequired && await this.getAuthToken({ requiredScopes: options.requiredScopes }) == null;
3511
+ const wasMissingAuthToken = options.authRequired && await this.getAuthToken({
3512
+ requiredScopes: options.requiredScopes
3513
+ }) == null;
3244
3514
  const response = await this.fetch(path, {
3245
3515
  ...options,
3246
3516
  method,
@@ -3363,10 +3633,16 @@ var ZapierApiClient = class {
3363
3633
  // poll_url is an absolute URL supplied by the server, so we use
3364
3634
  // rawFetchUrl directly (skipping path resolution) but still share
3365
3635
  // auth + interactive-header + 429-retry with the rest of the SDK.
3366
- fetchPoll: () => this.rawFetchUrl(approval.poll_url, {
3367
- method: "GET",
3368
- headers: { Accept: "application/json" }
3369
- }),
3636
+ // Each individual poll request goes through the concurrency
3637
+ // semaphore — but we deliberately do not hold a slot across the
3638
+ // sleep between polls or across the human-approval wait.
3639
+ fetchPoll: () => this.withSemaphore(
3640
+ { url: approval.poll_url, method: "GET" },
3641
+ () => this.rawFetchUrl(approval.poll_url, {
3642
+ method: "GET",
3643
+ headers: { Accept: "application/json" }
3644
+ })
3645
+ ),
3370
3646
  timeoutMs,
3371
3647
  isPending: (body2) => {
3372
3648
  const parsed = PollApprovalResponseSchema.safeParse(body2);
@@ -3434,6 +3710,28 @@ var createZapierApi = (options) => {
3434
3710
  });
3435
3711
  };
3436
3712
 
3713
+ // src/api/index.ts
3714
+ function getOrCreateApiClient(config) {
3715
+ const {
3716
+ baseUrl = ZAPIER_BASE_URL,
3717
+ credentials,
3718
+ token,
3719
+ api: providedApi,
3720
+ debug = false,
3721
+ fetch: customFetch
3722
+ } = config;
3723
+ if (providedApi) {
3724
+ return providedApi;
3725
+ }
3726
+ return createZapierApi({
3727
+ baseUrl,
3728
+ credentials,
3729
+ token,
3730
+ debug,
3731
+ fetch: customFetch
3732
+ });
3733
+ }
3734
+
3437
3735
  // src/plugins/api/index.ts
3438
3736
  var apiPlugin = definePlugin(
3439
3737
  (sdk) => {
@@ -3446,6 +3744,7 @@ var apiPlugin = definePlugin(
3446
3744
  debug = false,
3447
3745
  maxNetworkRetries = ZAPIER_MAX_NETWORK_RETRIES,
3448
3746
  maxNetworkRetryDelayMs = ZAPIER_MAX_NETWORK_RETRY_DELAY_MS,
3747
+ maxConcurrentRequests = ZAPIER_MAX_CONCURRENT_REQUESTS,
3449
3748
  approvalTimeoutMs,
3450
3749
  maxApprovalRetries,
3451
3750
  approvalMode,
@@ -3460,6 +3759,7 @@ var apiPlugin = definePlugin(
3460
3759
  onEvent,
3461
3760
  maxNetworkRetries,
3462
3761
  maxNetworkRetryDelayMs,
3762
+ maxConcurrentRequests,
3463
3763
  approvalTimeoutMs,
3464
3764
  maxApprovalRetries,
3465
3765
  approvalMode,
@@ -10165,6 +10465,24 @@ var BaseSdkOptionsSchema = zod.z.object({
10165
10465
  * Default is 60000 (60 seconds).
10166
10466
  */
10167
10467
  maxNetworkRetryDelayMs: zod.z.number().optional().describe("Max delay in ms to wait for retry (default: 60000).").meta({ valueHint: "ms" }),
10468
+ /**
10469
+ * Maximum number of concurrent in-flight HTTP requests per client.
10470
+ * Requests beyond this limit queue in FIFO order until a slot frees.
10471
+ * Pass `Infinity` to disable. Default: 200.
10472
+ *
10473
+ * The description and meta are duplicated across the outer wrapper and
10474
+ * the inner numeric branch because the SDK and CLI doc generators walk
10475
+ * the schema differently — the SDK reader looks at wrappers only, while
10476
+ * the CLI reader recurses into union branches.
10477
+ */
10478
+ maxConcurrentRequests: zod.z.union([
10479
+ zod.z.number().int().min(1).max(MAX_CONCURRENCY_LIMIT).describe(
10480
+ `Max concurrent in-flight HTTP requests (default: 200, max: ${MAX_CONCURRENCY_LIMIT}).`
10481
+ ).meta({ valueHint: "count" }),
10482
+ zod.z.literal(Infinity)
10483
+ ]).optional().describe(
10484
+ `Max concurrent in-flight HTTP requests (default: 200, max: ${MAX_CONCURRENCY_LIMIT}).`
10485
+ ).meta({ valueHint: "count" }),
10168
10486
  approvalTimeoutMs: zod.z.number().optional().describe("Timeout in ms for approval polling. Default: 600000 (10 min).").meta({ valueHint: "ms" }),
10169
10487
  maxApprovalRetries: zod.z.number().optional().describe(
10170
10488
  "Maximum number of sequential approval rounds per request (one per gating policy) before giving up. Default: 2."
@@ -10234,6 +10552,7 @@ exports.LeaseLimitPropertySchema = LeaseLimitPropertySchema;
10234
10552
  exports.LeasePropertySchema = LeasePropertySchema;
10235
10553
  exports.LeaseSecondsPropertySchema = LeaseSecondsPropertySchema;
10236
10554
  exports.LimitPropertySchema = LimitPropertySchema;
10555
+ exports.MAX_CONCURRENCY_LIMIT = MAX_CONCURRENCY_LIMIT;
10237
10556
  exports.MAX_PAGE_LIMIT = MAX_PAGE_LIMIT;
10238
10557
  exports.OffsetPropertySchema = OffsetPropertySchema;
10239
10558
  exports.OutputPropertySchema = OutputPropertySchema;
@@ -10249,6 +10568,7 @@ exports.TablesPropertySchema = TablesPropertySchema;
10249
10568
  exports.TriggerInboxNamePropertySchema = TriggerInboxNamePropertySchema;
10250
10569
  exports.TriggerInboxPropertySchema = TriggerInboxPropertySchema;
10251
10570
  exports.ZAPIER_BASE_URL = ZAPIER_BASE_URL;
10571
+ exports.ZAPIER_MAX_CONCURRENT_REQUESTS = ZAPIER_MAX_CONCURRENT_REQUESTS;
10252
10572
  exports.ZAPIER_MAX_NETWORK_RETRIES = ZAPIER_MAX_NETWORK_RETRIES;
10253
10573
  exports.ZAPIER_MAX_NETWORK_RETRY_DELAY_MS = ZAPIER_MAX_NETWORK_RETRY_DELAY_MS;
10254
10574
  exports.ZapierAbortDrainSignal = ZapierAbortDrainSignal;
@@ -10301,6 +10621,7 @@ exports.createSdk = createSdk;
10301
10621
  exports.createTableFieldsPlugin = createTableFieldsPlugin;
10302
10622
  exports.createTablePlugin = createTablePlugin;
10303
10623
  exports.createTableRecordsPlugin = createTableRecordsPlugin;
10624
+ exports.createZapierApi = createZapierApi;
10304
10625
  exports.createZapierSdk = createZapierSdk2;
10305
10626
  exports.createZapierSdkWithoutRegistry = createZapierSdkWithoutRegistry;
10306
10627
  exports.definePlugin = definePlugin;
@@ -10324,6 +10645,7 @@ exports.getConnectionPlugin = getConnectionPlugin;
10324
10645
  exports.getCpuTime = getCpuTime;
10325
10646
  exports.getCurrentTimestamp = getCurrentTimestamp;
10326
10647
  exports.getMemoryUsage = getMemoryUsage;
10648
+ exports.getOrCreateApiClient = getOrCreateApiClient;
10327
10649
  exports.getOsInfo = getOsInfo;
10328
10650
  exports.getPlatformVersions = getPlatformVersions;
10329
10651
  exports.getPreferredManifestEntryKey = getPreferredManifestEntryKey;
@@ -10358,6 +10680,7 @@ exports.listTableRecordsPlugin = listTableRecordsPlugin;
10358
10680
  exports.listTablesPlugin = listTablesPlugin;
10359
10681
  exports.logDeprecation = logDeprecation;
10360
10682
  exports.manifestPlugin = manifestPlugin;
10683
+ exports.parseConcurrencyEnvVar = parseConcurrencyEnvVar;
10361
10684
  exports.readManifestFromFile = readManifestFromFile;
10362
10685
  exports.registryPlugin = registryPlugin;
10363
10686
  exports.requestPlugin = requestPlugin;