@zapier/zapier-sdk 0.94.1 → 0.95.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 CHANGED
@@ -1,5 +1,12 @@
1
1
  # @zapier/zapier-sdk
2
2
 
3
+ ## 0.95.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 80dc74a: Auto-mode approvals that are escalated to a human reviewer now open the approval URL in the browser automatically and continue polling until the reviewer decides.
8
+ - 6e5189c: Added a `manual` option (experimental) to `publishWorkflowVersion`, exposed in the CLI as `--manual` on `publish-workflow-version`. Pass `manual: true` to publish an on-demand, triggerless workflow version explicitly; pass `trigger` for a triggered one. The two are mutually exclusive — sending both is rejected with a 400.
9
+
3
10
  ## 0.94.1
4
11
 
5
12
  ### Patch Changes
package/README.md CHANGED
@@ -2019,11 +2019,12 @@ Publish a new version of a durable workflow. Enables the workflow by default.
2019
2019
  | ​ ↳ `ignoreOpenDrafts` | `boolean` | ❌ | — | — | Publish even though the workflow has open draft(s). Without this, the API rejects a direct publish with a 409 while any draft is open, since publishing the draft later would ship its stale content over this version. |
2020
2020
  | ​ ↳ `connections` | `object` | ❌ | — | — | Map of connection aliases to Zapier connections used by the workflow. Pass `null` to clear an existing binding. |
2021
2021
  | ​ ↳ `appVersions` | `object` | ❌ | — | — | Map of app keys to pinned app implementation/version used by the workflow. Pass `null` to clear an existing binding. |
2022
- | ​ ↳ `trigger` | `object` | ❌ | — | — | Trigger configuration. When provided, the workflow subscribes to a Zapier trigger; omit for webhook-only workflows. |
2022
+ | ​ ↳ `trigger` | `object` | ❌ | — | — | Trigger configuration. When provided, the workflow subscribes to a Zapier trigger; for an on-demand, triggerless workflow, omit this and pass `manual: true` instead. |
2023
2023
  | ​   ↳ `selectedApi` | `string` | ❌ | — | — | Zapier app/API identifier (e.g. 'GoogleSheetsAPI'). Required when a trigger is configured. |
2024
2024
  | ​   ↳ `action` | `string` | ✅ | — | — | Trigger action key (e.g. 'new_row') |
2025
2025
  | ​   ↳ `authenticationId` | `string` | ❌ | — | — | Connection ID for the trigger source. Omit or pass null for no-auth triggers (e.g. Schedule by Zapier). |
2026
2026
  | ​   ↳ `params` | `object` | ❌ | — | — | Trigger parameters as a JSON object |
2027
+ | ​ ↳ `manual` | `boolean` | ❌ | — | — | Declare this an on-demand, triggerless workflow version. Pass `manual: true` only when omitting `trigger`; passing both is a contradiction the API rejects with a 400. |
2027
2028
 
2028
2029
  **Returns:** `Promise<WorkflowVersionItem>`
2029
2030
 
@@ -6192,7 +6192,7 @@ function parseDeprecationDate(value) {
6192
6192
  }
6193
6193
 
6194
6194
  // src/sdk-version.ts
6195
- var SDK_VERSION = (typeof process !== "undefined" && process.env ? "0.94.1" : void 0) || "unknown";
6195
+ var SDK_VERSION = (typeof process !== "undefined" && process.env ? "0.95.0" : void 0) || "unknown";
6196
6196
 
6197
6197
  // src/utils/open-url.ts
6198
6198
  var nodePrefix = "node:";
@@ -6301,7 +6301,11 @@ var PollApprovalResponseSchema = z.object({
6301
6301
  status: ApprovalStatusSchema,
6302
6302
  approval_id: z.string().optional(),
6303
6303
  mode: ApprovalModeSchema.optional(),
6304
- reason: z.string().optional()
6304
+ reason: z.string().optional(),
6305
+ // Present when an auto-mode agent escalated the approval to a human reviewer.
6306
+ // The SDK uses review_status to detect escalation and approval_url to open the browser.
6307
+ review_status: z.enum(["in_review", "escalated"]).optional(),
6308
+ approval_url: z.string().optional()
6305
6309
  });
6306
6310
  var APPROVAL_MAX_POLLING_INTERVAL_MILLISECONDS = 5e3;
6307
6311
  function validateSdkPath(path) {
@@ -7311,6 +7315,7 @@ var ZapierApiClient = class {
7311
7315
  await openApproval(approval.approval_url);
7312
7316
  }
7313
7317
  const timeoutMs = this.options.approvalTimeoutMilliseconds ?? this.options.approvalTimeoutMs ?? DEFAULT_APPROVAL_TIMEOUT_MILLISECONDS;
7318
+ const approvalDeadline = Date.now() + timeoutMs;
7314
7319
  let streamAbortController;
7315
7320
  let streamPromise;
7316
7321
  let removeStreamAbortListener;
@@ -7332,75 +7337,87 @@ var ZapierApiClient = class {
7332
7337
  emitEvent: (type, payload) => this.emitEvent(type, payload)
7333
7338
  });
7334
7339
  }
7335
- let rawPollResult;
7336
- try {
7337
- rawPollResult = await pollUntilComplete({
7338
- // poll_url is an absolute URL supplied by the server, so we use
7339
- // rawFetchUrl directly (skipping path resolution) but still share
7340
- // auth + interactive-header + 429-retry with the rest of the SDK.
7341
- // Each individual poll request goes through the concurrency
7342
- // semaphore — but we deliberately do not hold a slot across the
7343
- // sleep between polls or across the human-approval wait.
7344
- fetchPoll: () => this.withSemaphore(
7345
- { url: approval.poll_url, method: "GET", signal },
7346
- () => this.rawFetchUrl(approval.poll_url, {
7347
- method: "GET",
7348
- headers: { Accept: "application/json" },
7349
- signal
7350
- })
7351
- ),
7352
- timeoutMs,
7353
- maxPollingIntervalMs: APPROVAL_MAX_POLLING_INTERVAL_MILLISECONDS,
7354
- signal,
7355
- isPending: (body2) => {
7356
- const parsed = PollApprovalResponseSchema.safeParse(body2);
7357
- return parsed.success && parsed.data.status === "pending_approval";
7358
- }
7359
- });
7360
- } catch (err) {
7361
- if (!isZapierTimeoutError(err)) {
7362
- this.emitEvent("approval:error", {
7363
- approvalId: approval.id,
7364
- message: err instanceof Error ? err.message : String(err)
7340
+ const fetchApprovalPoll = () => this.withSemaphore(
7341
+ { url: approval.poll_url, method: "GET", signal },
7342
+ () => this.rawFetchUrl(approval.poll_url, {
7343
+ method: "GET",
7344
+ headers: { Accept: "application/json" },
7345
+ signal
7346
+ })
7347
+ );
7348
+ const pollApprovalUntilComplete = async (isPending, deadlineMs = approvalDeadline) => {
7349
+ try {
7350
+ return await pollUntilComplete({
7351
+ fetchPoll: fetchApprovalPoll,
7352
+ timeoutMs: Math.max(1, deadlineMs - Date.now()),
7353
+ maxPollingIntervalMs: APPROVAL_MAX_POLLING_INTERVAL_MILLISECONDS,
7354
+ signal,
7355
+ isPending
7365
7356
  });
7366
- throw err;
7357
+ } catch (err) {
7358
+ if (!isZapierTimeoutError(err)) {
7359
+ this.emitEvent("approval:error", {
7360
+ approvalId: approval.id,
7361
+ message: err instanceof Error ? err.message : String(err)
7362
+ });
7363
+ throw err;
7364
+ }
7365
+ this.emitEvent("approval:timeout", { approvalId: approval.id });
7366
+ throw new ZapierApprovalError(
7367
+ `Approval timed out after ${timeoutMs / 1e3} seconds`,
7368
+ {
7369
+ approvalId: approval.id,
7370
+ approvalUrl: approval.approval_url,
7371
+ pollUrl: approval.poll_url,
7372
+ streamUrl: approval.stream_url,
7373
+ status: "timeout",
7374
+ cause: err
7375
+ }
7376
+ );
7367
7377
  }
7368
- this.emitEvent("approval:timeout", {
7369
- approvalId: approval.id
7378
+ };
7379
+ let rawPollResult;
7380
+ try {
7381
+ rawPollResult = await pollApprovalUntilComplete((body2) => {
7382
+ const parsed = PollApprovalResponseSchema.safeParse(body2);
7383
+ if (!parsed.success) return false;
7384
+ if (parsed.data.review_status === "escalated") return false;
7385
+ return parsed.data.status === "pending_approval";
7370
7386
  });
7371
- throw new ZapierApprovalError(
7372
- `Approval timed out after ${timeoutMs / 1e3} seconds`,
7373
- {
7374
- approvalId: approval.id,
7375
- approvalUrl: approval.approval_url,
7376
- pollUrl: approval.poll_url,
7377
- streamUrl: approval.stream_url,
7378
- status: "timeout",
7379
- cause: err
7380
- }
7381
- );
7382
7387
  } finally {
7383
7388
  removeStreamAbortListener?.();
7384
7389
  streamAbortController?.abort();
7385
7390
  await streamPromise;
7386
7391
  }
7387
- const pollParse = PollApprovalResponseSchema.safeParse(rawPollResult);
7388
- if (!pollParse.success) {
7389
- const bodyPreview = typeof rawPollResult === "string" ? rawPollResult : JSON.stringify(rawPollResult);
7390
- this.emitEvent("approval:error", {
7392
+ const parsePollResult = (raw) => {
7393
+ const parsed = PollApprovalResponseSchema.safeParse(raw);
7394
+ if (!parsed.success) {
7395
+ const bodyPreview = typeof raw === "string" ? raw : JSON.stringify(raw);
7396
+ this.emitEvent("approval:error", {
7397
+ approvalId: approval.id,
7398
+ message: `Failed to parse approval poll response: ${bodyPreview}`
7399
+ });
7400
+ throw new ZapierApiError(
7401
+ `Failed to parse approval poll response: ${bodyPreview}`,
7402
+ { statusCode: 0, cause: parsed.error, response: raw }
7403
+ );
7404
+ }
7405
+ return parsed.data;
7406
+ };
7407
+ const pollResult = parsePollResult(rawPollResult);
7408
+ if (pollResult.review_status === "escalated") {
7409
+ const escalationUrl = pollResult.approval_url ?? approval.approval_url;
7410
+ this.emitEvent("approval:escalated", {
7391
7411
  approvalId: approval.id,
7392
- message: `Failed to parse approval poll response: ${bodyPreview}`
7412
+ approvalUrl: escalationUrl
7393
7413
  });
7394
- throw new ZapierApiError(
7395
- `Failed to parse approval poll response: ${bodyPreview}`,
7396
- {
7397
- statusCode: 0,
7398
- cause: pollParse.error,
7399
- response: rawPollResult
7400
- }
7401
- );
7414
+ await openApproval(escalationUrl);
7415
+ const humanRaw = await pollApprovalUntilComplete((body2) => {
7416
+ const parsed = PollApprovalResponseSchema.safeParse(body2);
7417
+ return parsed.success && parsed.data.status === "pending_approval";
7418
+ });
7419
+ Object.assign(pollResult, parsePollResult(humanRaw));
7402
7420
  }
7403
- const pollResult = pollParse.data;
7404
7421
  if (pollResult.status === "denied") {
7405
7422
  this.emitEvent("approval:denied", {
7406
7423
  approvalId: approval.id,
@@ -6194,7 +6194,7 @@ function parseDeprecationDate(value) {
6194
6194
  }
6195
6195
 
6196
6196
  // src/sdk-version.ts
6197
- var SDK_VERSION = (typeof process !== "undefined" && process.env ? "0.94.1" : void 0) || "unknown";
6197
+ var SDK_VERSION = (typeof process !== "undefined" && process.env ? "0.95.0" : void 0) || "unknown";
6198
6198
 
6199
6199
  // src/utils/open-url.ts
6200
6200
  var nodePrefix = "node:";
@@ -6303,7 +6303,11 @@ var PollApprovalResponseSchema = zod.z.object({
6303
6303
  status: ApprovalStatusSchema,
6304
6304
  approval_id: zod.z.string().optional(),
6305
6305
  mode: ApprovalModeSchema.optional(),
6306
- reason: zod.z.string().optional()
6306
+ reason: zod.z.string().optional(),
6307
+ // Present when an auto-mode agent escalated the approval to a human reviewer.
6308
+ // The SDK uses review_status to detect escalation and approval_url to open the browser.
6309
+ review_status: zod.z.enum(["in_review", "escalated"]).optional(),
6310
+ approval_url: zod.z.string().optional()
6307
6311
  });
6308
6312
  var APPROVAL_MAX_POLLING_INTERVAL_MILLISECONDS = 5e3;
6309
6313
  function validateSdkPath(path) {
@@ -7313,6 +7317,7 @@ var ZapierApiClient = class {
7313
7317
  await openApproval(approval.approval_url);
7314
7318
  }
7315
7319
  const timeoutMs = this.options.approvalTimeoutMilliseconds ?? this.options.approvalTimeoutMs ?? DEFAULT_APPROVAL_TIMEOUT_MILLISECONDS;
7320
+ const approvalDeadline = Date.now() + timeoutMs;
7316
7321
  let streamAbortController;
7317
7322
  let streamPromise;
7318
7323
  let removeStreamAbortListener;
@@ -7334,75 +7339,87 @@ var ZapierApiClient = class {
7334
7339
  emitEvent: (type, payload) => this.emitEvent(type, payload)
7335
7340
  });
7336
7341
  }
7337
- let rawPollResult;
7338
- try {
7339
- rawPollResult = await pollUntilComplete({
7340
- // poll_url is an absolute URL supplied by the server, so we use
7341
- // rawFetchUrl directly (skipping path resolution) but still share
7342
- // auth + interactive-header + 429-retry with the rest of the SDK.
7343
- // Each individual poll request goes through the concurrency
7344
- // semaphore — but we deliberately do not hold a slot across the
7345
- // sleep between polls or across the human-approval wait.
7346
- fetchPoll: () => this.withSemaphore(
7347
- { url: approval.poll_url, method: "GET", signal },
7348
- () => this.rawFetchUrl(approval.poll_url, {
7349
- method: "GET",
7350
- headers: { Accept: "application/json" },
7351
- signal
7352
- })
7353
- ),
7354
- timeoutMs,
7355
- maxPollingIntervalMs: APPROVAL_MAX_POLLING_INTERVAL_MILLISECONDS,
7356
- signal,
7357
- isPending: (body2) => {
7358
- const parsed = PollApprovalResponseSchema.safeParse(body2);
7359
- return parsed.success && parsed.data.status === "pending_approval";
7360
- }
7361
- });
7362
- } catch (err) {
7363
- if (!isZapierTimeoutError(err)) {
7364
- this.emitEvent("approval:error", {
7365
- approvalId: approval.id,
7366
- message: err instanceof Error ? err.message : String(err)
7342
+ const fetchApprovalPoll = () => this.withSemaphore(
7343
+ { url: approval.poll_url, method: "GET", signal },
7344
+ () => this.rawFetchUrl(approval.poll_url, {
7345
+ method: "GET",
7346
+ headers: { Accept: "application/json" },
7347
+ signal
7348
+ })
7349
+ );
7350
+ const pollApprovalUntilComplete = async (isPending, deadlineMs = approvalDeadline) => {
7351
+ try {
7352
+ return await pollUntilComplete({
7353
+ fetchPoll: fetchApprovalPoll,
7354
+ timeoutMs: Math.max(1, deadlineMs - Date.now()),
7355
+ maxPollingIntervalMs: APPROVAL_MAX_POLLING_INTERVAL_MILLISECONDS,
7356
+ signal,
7357
+ isPending
7367
7358
  });
7368
- throw err;
7359
+ } catch (err) {
7360
+ if (!isZapierTimeoutError(err)) {
7361
+ this.emitEvent("approval:error", {
7362
+ approvalId: approval.id,
7363
+ message: err instanceof Error ? err.message : String(err)
7364
+ });
7365
+ throw err;
7366
+ }
7367
+ this.emitEvent("approval:timeout", { approvalId: approval.id });
7368
+ throw new ZapierApprovalError(
7369
+ `Approval timed out after ${timeoutMs / 1e3} seconds`,
7370
+ {
7371
+ approvalId: approval.id,
7372
+ approvalUrl: approval.approval_url,
7373
+ pollUrl: approval.poll_url,
7374
+ streamUrl: approval.stream_url,
7375
+ status: "timeout",
7376
+ cause: err
7377
+ }
7378
+ );
7369
7379
  }
7370
- this.emitEvent("approval:timeout", {
7371
- approvalId: approval.id
7380
+ };
7381
+ let rawPollResult;
7382
+ try {
7383
+ rawPollResult = await pollApprovalUntilComplete((body2) => {
7384
+ const parsed = PollApprovalResponseSchema.safeParse(body2);
7385
+ if (!parsed.success) return false;
7386
+ if (parsed.data.review_status === "escalated") return false;
7387
+ return parsed.data.status === "pending_approval";
7372
7388
  });
7373
- throw new ZapierApprovalError(
7374
- `Approval timed out after ${timeoutMs / 1e3} seconds`,
7375
- {
7376
- approvalId: approval.id,
7377
- approvalUrl: approval.approval_url,
7378
- pollUrl: approval.poll_url,
7379
- streamUrl: approval.stream_url,
7380
- status: "timeout",
7381
- cause: err
7382
- }
7383
- );
7384
7389
  } finally {
7385
7390
  removeStreamAbortListener?.();
7386
7391
  streamAbortController?.abort();
7387
7392
  await streamPromise;
7388
7393
  }
7389
- const pollParse = PollApprovalResponseSchema.safeParse(rawPollResult);
7390
- if (!pollParse.success) {
7391
- const bodyPreview = typeof rawPollResult === "string" ? rawPollResult : JSON.stringify(rawPollResult);
7392
- this.emitEvent("approval:error", {
7394
+ const parsePollResult = (raw) => {
7395
+ const parsed = PollApprovalResponseSchema.safeParse(raw);
7396
+ if (!parsed.success) {
7397
+ const bodyPreview = typeof raw === "string" ? raw : JSON.stringify(raw);
7398
+ this.emitEvent("approval:error", {
7399
+ approvalId: approval.id,
7400
+ message: `Failed to parse approval poll response: ${bodyPreview}`
7401
+ });
7402
+ throw new ZapierApiError(
7403
+ `Failed to parse approval poll response: ${bodyPreview}`,
7404
+ { statusCode: 0, cause: parsed.error, response: raw }
7405
+ );
7406
+ }
7407
+ return parsed.data;
7408
+ };
7409
+ const pollResult = parsePollResult(rawPollResult);
7410
+ if (pollResult.review_status === "escalated") {
7411
+ const escalationUrl = pollResult.approval_url ?? approval.approval_url;
7412
+ this.emitEvent("approval:escalated", {
7393
7413
  approvalId: approval.id,
7394
- message: `Failed to parse approval poll response: ${bodyPreview}`
7414
+ approvalUrl: escalationUrl
7395
7415
  });
7396
- throw new ZapierApiError(
7397
- `Failed to parse approval poll response: ${bodyPreview}`,
7398
- {
7399
- statusCode: 0,
7400
- cause: pollParse.error,
7401
- response: rawPollResult
7402
- }
7403
- );
7416
+ await openApproval(escalationUrl);
7417
+ const humanRaw = await pollApprovalUntilComplete((body2) => {
7418
+ const parsed = PollApprovalResponseSchema.safeParse(body2);
7419
+ return parsed.success && parsed.data.status === "pending_approval";
7420
+ });
7421
+ Object.assign(pollResult, parsePollResult(humanRaw));
7404
7422
  }
7405
- const pollResult = pollParse.data;
7406
7423
  if (pollResult.status === "denied") {
7407
7424
  this.emitEvent("approval:denied", {
7408
7425
  approvalId: approval.id,