@zapier/zapier-sdk 0.94.0 → 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,18 @@
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
+
10
+ ## 0.94.1
11
+
12
+ ### Patch Changes
13
+
14
+ - 59a8180: `fetch()` now populates`ZapierRelayError.retryable` (boolean) and `ZapierRelayError.errors` (structured error code array) when the proxy provides machine-readable error metadata.
15
+
3
16
  ## 0.94.0
4
17
 
5
18
  ### Minor 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
 
@@ -4511,6 +4511,7 @@ var ZapierRelayError = class extends ZapierError {
4511
4511
  super(message, options);
4512
4512
  this.name = "ZapierRelayError";
4513
4513
  this.code = "ZAPIER_RELAY_ERROR";
4514
+ if (options.retryable !== void 0) this.retryable = options.retryable;
4514
4515
  }
4515
4516
  };
4516
4517
  function hasZapierErrorCode(value, code) {
@@ -6191,7 +6192,7 @@ function parseDeprecationDate(value) {
6191
6192
  }
6192
6193
 
6193
6194
  // src/sdk-version.ts
6194
- var SDK_VERSION = (typeof process !== "undefined" && process.env ? "0.94.0" : void 0) || "unknown";
6195
+ var SDK_VERSION = (typeof process !== "undefined" && process.env ? "0.95.0" : void 0) || "unknown";
6195
6196
 
6196
6197
  // src/utils/open-url.ts
6197
6198
  var nodePrefix = "node:";
@@ -6300,7 +6301,11 @@ var PollApprovalResponseSchema = z.object({
6300
6301
  status: ApprovalStatusSchema,
6301
6302
  approval_id: z.string().optional(),
6302
6303
  mode: ApprovalModeSchema.optional(),
6303
- 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()
6304
6309
  });
6305
6310
  var APPROVAL_MAX_POLLING_INTERVAL_MILLISECONDS = 5e3;
6306
6311
  function validateSdkPath(path) {
@@ -7310,6 +7315,7 @@ var ZapierApiClient = class {
7310
7315
  await openApproval(approval.approval_url);
7311
7316
  }
7312
7317
  const timeoutMs = this.options.approvalTimeoutMilliseconds ?? this.options.approvalTimeoutMs ?? DEFAULT_APPROVAL_TIMEOUT_MILLISECONDS;
7318
+ const approvalDeadline = Date.now() + timeoutMs;
7313
7319
  let streamAbortController;
7314
7320
  let streamPromise;
7315
7321
  let removeStreamAbortListener;
@@ -7331,75 +7337,87 @@ var ZapierApiClient = class {
7331
7337
  emitEvent: (type, payload) => this.emitEvent(type, payload)
7332
7338
  });
7333
7339
  }
7334
- let rawPollResult;
7335
- try {
7336
- rawPollResult = await pollUntilComplete({
7337
- // poll_url is an absolute URL supplied by the server, so we use
7338
- // rawFetchUrl directly (skipping path resolution) but still share
7339
- // auth + interactive-header + 429-retry with the rest of the SDK.
7340
- // Each individual poll request goes through the concurrency
7341
- // semaphore — but we deliberately do not hold a slot across the
7342
- // sleep between polls or across the human-approval wait.
7343
- fetchPoll: () => this.withSemaphore(
7344
- { url: approval.poll_url, method: "GET", signal },
7345
- () => this.rawFetchUrl(approval.poll_url, {
7346
- method: "GET",
7347
- headers: { Accept: "application/json" },
7348
- signal
7349
- })
7350
- ),
7351
- timeoutMs,
7352
- maxPollingIntervalMs: APPROVAL_MAX_POLLING_INTERVAL_MILLISECONDS,
7353
- signal,
7354
- isPending: (body2) => {
7355
- const parsed = PollApprovalResponseSchema.safeParse(body2);
7356
- return parsed.success && parsed.data.status === "pending_approval";
7357
- }
7358
- });
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)
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
7364
7356
  });
7365
- 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
+ );
7366
7377
  }
7367
- this.emitEvent("approval:timeout", {
7368
- 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";
7369
7386
  });
7370
- throw new ZapierApprovalError(
7371
- `Approval timed out after ${timeoutMs / 1e3} seconds`,
7372
- {
7373
- approvalId: approval.id,
7374
- approvalUrl: approval.approval_url,
7375
- pollUrl: approval.poll_url,
7376
- streamUrl: approval.stream_url,
7377
- status: "timeout",
7378
- cause: err
7379
- }
7380
- );
7381
7387
  } finally {
7382
7388
  removeStreamAbortListener?.();
7383
7389
  streamAbortController?.abort();
7384
7390
  await streamPromise;
7385
7391
  }
7386
- const pollParse = PollApprovalResponseSchema.safeParse(rawPollResult);
7387
- if (!pollParse.success) {
7388
- const bodyPreview = typeof rawPollResult === "string" ? rawPollResult : JSON.stringify(rawPollResult);
7389
- 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", {
7390
7411
  approvalId: approval.id,
7391
- message: `Failed to parse approval poll response: ${bodyPreview}`
7412
+ approvalUrl: escalationUrl
7392
7413
  });
7393
- throw new ZapierApiError(
7394
- `Failed to parse approval poll response: ${bodyPreview}`,
7395
- {
7396
- statusCode: 0,
7397
- cause: pollParse.error,
7398
- response: rawPollResult
7399
- }
7400
- );
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));
7401
7420
  }
7402
- const pollResult = pollParse.data;
7403
7421
  if (pollResult.status === "denied") {
7404
7422
  this.emitEvent("approval:denied", {
7405
7423
  approvalId: approval.id,
@@ -8459,8 +8477,20 @@ var fetchPlugin = defineMethod({
8459
8477
  });
8460
8478
  const relayError = result.headers.get("x-relay-error");
8461
8479
  if (relayError) {
8480
+ const relayErrorCode = result.headers.get("x-relay-error-code");
8481
+ const retryableHeader = result.headers.get("x-relay-error-retryable");
8482
+ const retryable = retryableHeader === "true" ? true : retryableHeader === "false" ? false : void 0;
8462
8483
  throw new ZapierRelayError(relayError, {
8463
- statusCode: result.status
8484
+ statusCode: result.status,
8485
+ retryable,
8486
+ errors: relayErrorCode ? [
8487
+ {
8488
+ status: result.status,
8489
+ code: relayErrorCode,
8490
+ title: "API Proxy Error",
8491
+ detail: relayError
8492
+ }
8493
+ ] : void 0
8464
8494
  });
8465
8495
  }
8466
8496
  return result;
@@ -4513,6 +4513,7 @@ var ZapierRelayError = class extends ZapierError {
4513
4513
  super(message, options);
4514
4514
  this.name = "ZapierRelayError";
4515
4515
  this.code = "ZAPIER_RELAY_ERROR";
4516
+ if (options.retryable !== void 0) this.retryable = options.retryable;
4516
4517
  }
4517
4518
  };
4518
4519
  function hasZapierErrorCode(value, code) {
@@ -6193,7 +6194,7 @@ function parseDeprecationDate(value) {
6193
6194
  }
6194
6195
 
6195
6196
  // src/sdk-version.ts
6196
- var SDK_VERSION = (typeof process !== "undefined" && process.env ? "0.94.0" : void 0) || "unknown";
6197
+ var SDK_VERSION = (typeof process !== "undefined" && process.env ? "0.95.0" : void 0) || "unknown";
6197
6198
 
6198
6199
  // src/utils/open-url.ts
6199
6200
  var nodePrefix = "node:";
@@ -6302,7 +6303,11 @@ var PollApprovalResponseSchema = zod.z.object({
6302
6303
  status: ApprovalStatusSchema,
6303
6304
  approval_id: zod.z.string().optional(),
6304
6305
  mode: ApprovalModeSchema.optional(),
6305
- 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()
6306
6311
  });
6307
6312
  var APPROVAL_MAX_POLLING_INTERVAL_MILLISECONDS = 5e3;
6308
6313
  function validateSdkPath(path) {
@@ -7312,6 +7317,7 @@ var ZapierApiClient = class {
7312
7317
  await openApproval(approval.approval_url);
7313
7318
  }
7314
7319
  const timeoutMs = this.options.approvalTimeoutMilliseconds ?? this.options.approvalTimeoutMs ?? DEFAULT_APPROVAL_TIMEOUT_MILLISECONDS;
7320
+ const approvalDeadline = Date.now() + timeoutMs;
7315
7321
  let streamAbortController;
7316
7322
  let streamPromise;
7317
7323
  let removeStreamAbortListener;
@@ -7333,75 +7339,87 @@ var ZapierApiClient = class {
7333
7339
  emitEvent: (type, payload) => this.emitEvent(type, payload)
7334
7340
  });
7335
7341
  }
7336
- let rawPollResult;
7337
- try {
7338
- rawPollResult = await pollUntilComplete({
7339
- // poll_url is an absolute URL supplied by the server, so we use
7340
- // rawFetchUrl directly (skipping path resolution) but still share
7341
- // auth + interactive-header + 429-retry with the rest of the SDK.
7342
- // Each individual poll request goes through the concurrency
7343
- // semaphore — but we deliberately do not hold a slot across the
7344
- // sleep between polls or across the human-approval wait.
7345
- fetchPoll: () => this.withSemaphore(
7346
- { url: approval.poll_url, method: "GET", signal },
7347
- () => this.rawFetchUrl(approval.poll_url, {
7348
- method: "GET",
7349
- headers: { Accept: "application/json" },
7350
- signal
7351
- })
7352
- ),
7353
- timeoutMs,
7354
- maxPollingIntervalMs: APPROVAL_MAX_POLLING_INTERVAL_MILLISECONDS,
7355
- signal,
7356
- isPending: (body2) => {
7357
- const parsed = PollApprovalResponseSchema.safeParse(body2);
7358
- return parsed.success && parsed.data.status === "pending_approval";
7359
- }
7360
- });
7361
- } catch (err) {
7362
- if (!isZapierTimeoutError(err)) {
7363
- this.emitEvent("approval:error", {
7364
- approvalId: approval.id,
7365
- 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
7366
7358
  });
7367
- 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
+ );
7368
7379
  }
7369
- this.emitEvent("approval:timeout", {
7370
- 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";
7371
7388
  });
7372
- throw new ZapierApprovalError(
7373
- `Approval timed out after ${timeoutMs / 1e3} seconds`,
7374
- {
7375
- approvalId: approval.id,
7376
- approvalUrl: approval.approval_url,
7377
- pollUrl: approval.poll_url,
7378
- streamUrl: approval.stream_url,
7379
- status: "timeout",
7380
- cause: err
7381
- }
7382
- );
7383
7389
  } finally {
7384
7390
  removeStreamAbortListener?.();
7385
7391
  streamAbortController?.abort();
7386
7392
  await streamPromise;
7387
7393
  }
7388
- const pollParse = PollApprovalResponseSchema.safeParse(rawPollResult);
7389
- if (!pollParse.success) {
7390
- const bodyPreview = typeof rawPollResult === "string" ? rawPollResult : JSON.stringify(rawPollResult);
7391
- 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", {
7392
7413
  approvalId: approval.id,
7393
- message: `Failed to parse approval poll response: ${bodyPreview}`
7414
+ approvalUrl: escalationUrl
7394
7415
  });
7395
- throw new ZapierApiError(
7396
- `Failed to parse approval poll response: ${bodyPreview}`,
7397
- {
7398
- statusCode: 0,
7399
- cause: pollParse.error,
7400
- response: rawPollResult
7401
- }
7402
- );
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));
7403
7422
  }
7404
- const pollResult = pollParse.data;
7405
7423
  if (pollResult.status === "denied") {
7406
7424
  this.emitEvent("approval:denied", {
7407
7425
  approvalId: approval.id,
@@ -8461,8 +8479,20 @@ var fetchPlugin = defineMethod({
8461
8479
  });
8462
8480
  const relayError = result.headers.get("x-relay-error");
8463
8481
  if (relayError) {
8482
+ const relayErrorCode = result.headers.get("x-relay-error-code");
8483
+ const retryableHeader = result.headers.get("x-relay-error-retryable");
8484
+ const retryable = retryableHeader === "true" ? true : retryableHeader === "false" ? false : void 0;
8464
8485
  throw new ZapierRelayError(relayError, {
8465
- statusCode: result.status
8486
+ statusCode: result.status,
8487
+ retryable,
8488
+ errors: relayErrorCode ? [
8489
+ {
8490
+ status: result.status,
8491
+ code: relayErrorCode,
8492
+ title: "API Proxy Error",
8493
+ detail: relayError
8494
+ }
8495
+ ] : void 0
8466
8496
  });
8467
8497
  }
8468
8498
  return result;