@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.
- package/CHANGELOG.md +16 -0
- package/README.md +2 -1
- package/dist/api/auth.d.ts +1 -6
- package/dist/api/auth.d.ts.map +1 -1
- package/dist/api/auth.js +34 -27
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +87 -9
- package/dist/api/concurrency.d.ts +28 -0
- package/dist/api/concurrency.d.ts.map +1 -0
- package/dist/api/concurrency.js +90 -0
- package/dist/api/index.d.ts +1 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +1 -1
- package/dist/api/schemas.d.ts +3 -3
- package/dist/api/types.d.ts +6 -0
- package/dist/api/types.d.ts.map +1 -1
- package/dist/auth.d.ts +13 -2
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +95 -11
- package/dist/constants.d.ts +16 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +29 -0
- package/dist/experimental.cjs +357 -34
- package/dist/experimental.d.mts +28 -28
- package/dist/experimental.d.ts +26 -26
- package/dist/experimental.mjs +353 -35
- package/dist/{index-BQ2ii0Bs.d.mts → index-DcdtPei-.d.mts} +132 -2
- package/dist/{index-BQ2ii0Bs.d.ts → index-DcdtPei-.d.ts} +132 -2
- package/dist/index.cjs +357 -34
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.mjs +353 -35
- package/dist/plugins/api/index.d.ts.map +1 -1
- package/dist/plugins/api/index.js +3 -2
- package/dist/plugins/apps/index.d.ts +2 -2
- package/dist/plugins/deprecated/inputFields.d.ts +18 -18
- package/dist/plugins/getAction/index.d.ts +6 -6
- package/dist/plugins/getAction/schemas.d.ts +4 -4
- package/dist/plugins/getActionInputFieldsSchema/index.d.ts +5 -5
- package/dist/plugins/getActionInputFieldsSchema/schemas.d.ts +4 -4
- package/dist/plugins/listActionInputFieldChoices/index.d.ts +5 -5
- package/dist/plugins/listActionInputFieldChoices/schemas.d.ts +4 -4
- package/dist/plugins/listActionInputFields/index.d.ts +5 -5
- package/dist/plugins/listActionInputFields/schemas.d.ts +4 -4
- package/dist/plugins/listActions/index.d.ts +3 -3
- package/dist/plugins/listActions/schemas.d.ts +4 -4
- package/dist/plugins/runAction/index.d.ts +5 -5
- package/dist/plugins/runAction/schemas.d.ts +4 -4
- package/dist/plugins/triggers/getTriggerInputFieldsSchema/index.d.ts +2 -2
- package/dist/plugins/triggers/listTriggerInputFieldChoices/index.d.ts +2 -2
- package/dist/plugins/triggers/listTriggerInputFields/index.d.ts +2 -2
- package/dist/schemas/Action.d.ts +1 -1
- package/dist/sdk.d.ts +52 -52
- package/dist/types/properties.d.ts +1 -1
- package/dist/types/sdk.d.ts +1 -0
- package/dist/types/sdk.d.ts.map +1 -1
- package/dist/types/sdk.js +25 -0
- package/dist/utils/telemetry.d.ts +11 -0
- package/dist/utils/telemetry.d.ts.map +1 -0
- package/dist/utils/telemetry.js +19 -0
- package/package.json +1 -1
package/dist/experimental.cjs
CHANGED
|
@@ -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
|
|
1854
|
+
function readJwtPayload(token) {
|
|
1840
1855
|
const parts = parseJwt(token);
|
|
1841
|
-
if (!parts)
|
|
1842
|
-
|
|
1843
|
-
}
|
|
1856
|
+
if (!parts) return null;
|
|
1857
|
+
let payload;
|
|
1844
1858
|
try {
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
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
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
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
|
-
|
|
2437
|
-
|
|
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
|
-
|
|
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
|
|
2597
|
-
if (cached
|
|
2598
|
-
|
|
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
|
|
2604
|
-
if (recheck
|
|
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.
|
|
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.
|
|
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({
|
|
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
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
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;
|