@zapier/zapier-sdk 0.69.2 → 0.70.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 (50) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +33 -23
  3. package/dist/api/client.d.ts.map +1 -1
  4. package/dist/api/client.js +10 -1
  5. package/dist/api/error-classification.d.ts +7 -0
  6. package/dist/api/error-classification.d.ts.map +1 -1
  7. package/dist/api/error-classification.js +15 -2
  8. package/dist/api/index.d.ts +1 -1
  9. package/dist/api/index.d.ts.map +1 -1
  10. package/dist/api/sse-parser.d.ts +34 -0
  11. package/dist/api/sse-parser.d.ts.map +1 -1
  12. package/dist/api/sse-parser.js +28 -0
  13. package/dist/api/types.d.ts +9 -1
  14. package/dist/api/types.d.ts.map +1 -1
  15. package/dist/auth.d.ts.map +1 -1
  16. package/dist/auth.js +2 -2
  17. package/dist/experimental.cjs +174 -35
  18. package/dist/experimental.d.mts +34 -2
  19. package/dist/experimental.d.ts +32 -0
  20. package/dist/experimental.d.ts.map +1 -1
  21. package/dist/experimental.mjs +174 -35
  22. package/dist/{index-DuFFW71E.d.mts → index-C0bQ5snd.d.mts} +33 -1
  23. package/dist/{index-DuFFW71E.d.ts → index-C0bQ5snd.d.ts} +33 -1
  24. package/dist/index.cjs +32 -5
  25. package/dist/index.d.mts +1 -1
  26. package/dist/index.d.ts +1 -1
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.mjs +32 -5
  29. package/dist/plugins/codeSubstrate/createWorkflow/index.d.ts +3 -0
  30. package/dist/plugins/codeSubstrate/createWorkflow/index.d.ts.map +1 -1
  31. package/dist/plugins/codeSubstrate/createWorkflow/index.js +3 -0
  32. package/dist/plugins/codeSubstrate/createWorkflow/schemas.d.ts +3 -0
  33. package/dist/plugins/codeSubstrate/createWorkflow/schemas.d.ts.map +1 -1
  34. package/dist/plugins/codeSubstrate/createWorkflow/schemas.js +11 -0
  35. package/dist/plugins/codeSubstrate/publishWorkflowVersion/index.d.ts +13 -0
  36. package/dist/plugins/codeSubstrate/publishWorkflowVersion/index.d.ts.map +1 -1
  37. package/dist/plugins/codeSubstrate/publishWorkflowVersion/index.js +9 -0
  38. package/dist/plugins/codeSubstrate/publishWorkflowVersion/schemas.d.ts +13 -0
  39. package/dist/plugins/codeSubstrate/publishWorkflowVersion/schemas.d.ts.map +1 -1
  40. package/dist/plugins/codeSubstrate/publishWorkflowVersion/schemas.js +52 -0
  41. package/dist/plugins/triggers/drainTriggerInbox/index.d.ts +4 -2
  42. package/dist/plugins/triggers/drainTriggerInbox/index.d.ts.map +1 -1
  43. package/dist/plugins/triggers/drainTriggerInbox/index.js +39 -6
  44. package/dist/plugins/triggers/watchTriggerInbox/index.d.ts +4 -2
  45. package/dist/plugins/triggers/watchTriggerInbox/index.d.ts.map +1 -1
  46. package/dist/plugins/triggers/watchTriggerInbox/index.js +100 -16
  47. package/dist/plugins/triggers/watchTriggerInbox/sse.d.ts +8 -6
  48. package/dist/plugins/triggers/watchTriggerInbox/sse.d.ts.map +1 -1
  49. package/dist/plugins/triggers/watchTriggerInbox/sse.js +11 -13
  50. package/package.json +1 -1
@@ -8,6 +8,30 @@ import { resolveTriggerInboxId } from "../utils";
8
8
  import { triggersDefaults } from "../shared";
9
9
  import { isAbortError } from "../../../utils/abort-utils";
10
10
  export { ZapierAbortDrainSignal, ZapierReleaseTriggerMessageSignal, } from "./schemas";
11
+ /**
12
+ * Drain errors that must NOT be retried, by error identity: a fail-fast handler
13
+ * error (the user's own failure — retrying would re-run a handler that already
14
+ * chose to stop) and an `initialization_failure` (a permanent inbox setup
15
+ * problem). The watch loop retries transient infrastructure errors but consults
16
+ * this set to leave these fatal. A WeakSet rather than a marker property because
17
+ * handler errors are the user's own values and must reach their catch
18
+ * unmodified. `markNonRetryableDrainError` no-ops on a non-object throw (it
19
+ * can't be weakly held), but that case is still fatal without this set: the
20
+ * watch loop classifies any non-object thrown value as non-retryable on its own,
21
+ * since the API client only ever throws Error objects, so a primitive can only
22
+ * be a user fail-fast throw. Permanent HTTP errors are non-retryable too, but
23
+ * the caller classifies those by status code, not via this set.
24
+ */
25
+ const nonRetryableDrainErrors = new WeakSet();
26
+ export function markNonRetryableDrainError(err) {
27
+ if (typeof err === "object" && err !== null) {
28
+ nonRetryableDrainErrors.add(err);
29
+ }
30
+ return err;
31
+ }
32
+ export function isNonRetryableDrainError(err) {
33
+ return (typeof err === "object" && err !== null && nonRetryableDrainErrors.has(err));
34
+ }
11
35
  /**
12
36
  * Server returns 400 lease_expired when a UUIDv7 timestamp is in the
13
37
  * past. The API atomically quarantines messages that have hit
@@ -23,13 +47,17 @@ function isLeaseExpiredError(err) {
23
47
  /**
24
48
  * One drain pass through `runBatchedDrainPipeline`. Used directly by
25
49
  * `drainTriggerInbox` (single pass) and as the inner loop body of
26
- * `watchTriggerInbox` (called repeatedly with poll backoff between
27
- * empty passes).
50
+ * `watchTriggerInbox` (called once per drain request, with bounded error
51
+ * backoff between passes that fail with a transient error).
28
52
  */
29
53
  export async function runDrainPass(options) {
30
54
  const { sdk, inboxId, onMessage, concurrency, leaseLimit, leaseSeconds, maxMessages, releaseOnError, continueOnError, onError, signal, } = options;
31
55
  let firstFetch = options.firstFetch;
32
56
  let abortedFromCallback = false;
57
+ // A boolean rather than a `firstHandlerError !== undefined` sentinel so a
58
+ // handler that throws a falsy value (`throw undefined` / `throw null`) is still
59
+ // captured and surfaced as fatal, not silently swallowed.
60
+ let handlerErrorCaptured = false;
33
61
  let firstHandlerError = undefined;
34
62
  const outcomes = await runBatchedDrainPipeline({
35
63
  concurrency,
@@ -59,7 +87,7 @@ export async function runDrainPass(options) {
59
87
  if (lease.results.length === 0) {
60
88
  if (firstFetch &&
61
89
  lease.inbox_attributes.status === "initialization_failure") {
62
- throw new ZapierApiError(`Trigger inbox ${inboxId} is in initialization_failure state — inspect via getTriggerInbox.`);
90
+ throw markNonRetryableDrainError(new ZapierApiError(`Trigger inbox ${inboxId} is in initialization_failure state — inspect via getTriggerInbox.`));
63
91
  }
64
92
  firstFetch = false;
65
93
  return "exhausted";
@@ -108,8 +136,10 @@ export async function runDrainPass(options) {
108
136
  // Fail-fast: capture the first real error and tell the
109
137
  // pipeline to abort. continueOnError keeps it running.
110
138
  if (!continueOnError && !(err instanceof ZapierSignal)) {
111
- if (firstHandlerError === undefined)
139
+ if (!handlerErrorCaptured) {
112
140
  firstHandlerError = err;
141
+ handlerErrorCaptured = true;
142
+ }
113
143
  abort = true;
114
144
  }
115
145
  return { value: message, action, abort };
@@ -142,8 +172,11 @@ export async function runDrainPass(options) {
142
172
  }
143
173
  },
144
174
  });
145
- if (firstHandlerError !== undefined)
146
- throw firstHandlerError;
175
+ // Mark so the watch loop treats this as fatal and never retries it (a
176
+ // fail-fast handler error means the user already chose to stop).
177
+ if (handlerErrorCaptured) {
178
+ throw markNonRetryableDrainError(firstHandlerError);
179
+ }
147
180
  return { abortedFromCallback, processed: outcomes.length };
148
181
  }
149
182
  /**
@@ -26,8 +26,10 @@ export declare function createDrainLatch(): DrainLatch;
26
26
  * `maxDrainIntervalSeconds` seconds (default: 300) to guarantee
27
27
  * forward progress if SSE events are missed or the connection drops
28
28
  * undetected. Resolves cleanly on `signal` abort or when a callback
29
- * throws `ZapierAbortDrainSignal`; rejects on a fatal error or a
30
- * fail-fast handler error.
29
+ * throws `ZapierAbortDrainSignal`. Transient drain failures (5xx, 429,
30
+ * network blips) are retried indefinitely with bounded backoff until they
31
+ * succeed or the watch is aborted; it rejects only on a fail-fast handler
32
+ * error, an `initialization_failure`, or a permanent HTTP error.
31
33
  */
32
34
  export declare const watchTriggerInboxPlugin: (sdk: {
33
35
  context: {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/plugins/triggers/watchTriggerInbox/index.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,wBAAwB,EAC9B,MAAM,8BAA8B,CAAC;AAetC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AA2BzD,MAAM,WAAW,UAAU;IACzB,8EAA8E;IAC9E,OAAO,IAAI,IAAI,CAAC;IAChB,sEAAsE;IACtE,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,IAAI,UAAU,CA2B7C;AAoLD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAMnB;QAAE,OAAO,EAAE,cAAc,CAAA;KAAE;;;;;;iCAI7B,wBAAwB,KAChC,OAAO,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiHnB,CAAC;AAEF,MAAM,MAAM,+BAA+B,GAAG,UAAU,CACtD,OAAO,uBAAuB,CAC/B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/plugins/triggers/watchTriggerInbox/index.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,wBAAwB,EAC9B,MAAM,8BAA8B,CAAC;AAoBtC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAoCzD,MAAM,WAAW,UAAU;IACzB,8EAA8E;IAC9E,OAAO,IAAI,IAAI,CAAC;IAChB,sEAAsE;IACtE,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,IAAI,UAAU,CA2B7C;AAqQD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAMnB;QAAE,OAAO,EAAE,cAAc,CAAA;KAAE;;;;;;iCAI7B,wBAAwB,KAChC,OAAO,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuHnB,CAAC;AAEF,MAAM,MAAM,+BAA+B,GAAG,UAAU,CACtD,OAAO,uBAAuB,CAC/B,CAAC"}
@@ -1,11 +1,11 @@
1
1
  import { definePlugin } from "kitcore";
2
2
  import { WatchTriggerInboxSchema, } from "../drainTriggerInbox/schemas";
3
- import { requireOnMessage, resolveConcurrencyAndLease, runDrainPass, } from "../drainTriggerInbox";
3
+ import { requireOnMessage, resolveConcurrencyAndLease, runDrainPass, isNonRetryableDrainError, } from "../drainTriggerInbox";
4
4
  import { resolveTriggerInboxId } from "../utils";
5
5
  import { triggerInboxResolver } from "../../../resolvers";
6
6
  import { triggersDefaults } from "../shared";
7
7
  import { isAbortError, combineAbortSignals } from "../../../utils/abort-utils";
8
- import { sleep } from "../../../utils/retry-utils";
8
+ import { sleep, calculateErrorBackoffMs, BASE_ERROR_BACKOFF_MS, } from "../../../utils/retry-utils";
9
9
  import { isPermanentHttpError } from "../../../api";
10
10
  import { ZapierError } from "../../../types/errors";
11
11
  import { readInboxEvents } from "./sse";
@@ -22,6 +22,14 @@ const DEFAULT_SAFETY_DRAIN_INTERVAL_MS = 300000; // 5 minutes
22
22
  // recovery from routine server-side cycling (e.g. JWT expiry); a stream that
23
23
  // closes sooner is treated as unstable and escalates like any other.
24
24
  const SSE_HEALTHY_CONNECTION_MS = 5000;
25
+ // Consecutive transient drain failures escalate the backoff via
26
+ // calculateErrorBackoffMs, whose error term saturates at errorCount 4 (capped at
27
+ // 2x the base interval). Past that the delay no longer grows, so we cap the
28
+ // counter that feeds the backoff there, holding the steady-state ~3.5 s retry
29
+ // cadence. (The separate, intentionally uncapped errorAttempts counter in
30
+ // drainRunner is what the diagnostics report; its unbounded growth is harmless
31
+ // since it only feeds a log string, never the backoff math.)
32
+ const ERROR_BACKOFF_CAP = 4;
25
33
  /**
26
34
  * Coalescing wake latch between the drain producers (SSE connect, SSE frames,
27
35
  * the safety timer, abort) and the single drain consumer. A `request()` that
@@ -61,11 +69,29 @@ export function createDrainLatch() {
61
69
  /**
62
70
  * Sole caller of `runDrainPass`, so drains are serial and never overlap. Waits
63
71
  * for a request, drains the inbox to empty, repeats. The first pass carries
64
- * `firstFetch: true` (preserving the initialization_failure check). Returns how
65
- * the watch ended so the caller can resolve or re-throw.
72
+ * `firstFetch: true` (preserving the initialization_failure check).
73
+ *
74
+ * Transient infrastructure failures (5xx, 429, network blips on lease / ack /
75
+ * release) are retried indefinitely with bounded backoff until they succeed or
76
+ * the watch is aborted — a watch is a "consume until aborted" loop, so a blip (or
77
+ * even a multi-minute outage) must not kill it, mirroring the SSE loop's
78
+ * resilience. The backoff escalates with consecutive failures and resets on the
79
+ * first success. Once retries saturate the backoff, a single warning prints to
80
+ * stderr (re-armed on the next success), mirroring the SSE loop's degraded-state
81
+ * notice; per-retry diagnostics print only under `debug`. A fail-fast handler
82
+ * error, an `initialization_failure`, or a permanent HTTP error is fatal
83
+ * immediately. Returns how the watch ended so the caller can resolve or re-throw.
66
84
  */
67
- async function drainRunner({ drainOptions, drainRequest, signal, }) {
85
+ async function drainRunner({ drainOptions, drainRequest, signal, inboxId, debug, }) {
68
86
  let firstFetch = true;
87
+ let consecutiveErrors = 0;
88
+ // Uncapped, unlike consecutiveErrors: reports the true consecutive-failure count
89
+ // in diagnostics so a long outage doesn't freeze the logged attempt at the
90
+ // backoff cap. Reset on a successful pass.
91
+ let errorAttempts = 0;
92
+ // Mirrors the SSE loop's `degraded`: warn once when retries saturate the
93
+ // backoff, re-armed (silently) on the first successful pass.
94
+ let drainDegraded = false;
69
95
  while (!signal.aborted) {
70
96
  await drainRequest.waitForRequest();
71
97
  if (signal.aborted)
@@ -78,19 +104,69 @@ async function drainRunner({ drainOptions, drainRequest, signal, }) {
78
104
  }));
79
105
  }
80
106
  catch (error) {
81
- return { kind: "error", error };
107
+ // Abort is intent, not failure — surface it as a clean stop, uncounted.
108
+ // `signal.aborted` alone is the predicate: every abort of THIS watch has
109
+ // set it by the time the catch runs, so an abort-shaped error arriving
110
+ // without it can only be someone else's (e.g. the user's onMessage leaking
111
+ // its own aborted fetch) — that's a fail-fast handler error and must
112
+ // reject below, not resolve the watch as a clean stop.
113
+ if (signal.aborted)
114
+ return { kind: "aborted" };
115
+ // Fatal, never retried: fail-fast handler errors and initialization_failure
116
+ // are tagged non-retryable; permanent HTTP (4xx — auth / permission /
117
+ // not-found) won't fix itself; and a non-object thrown value can only be a
118
+ // user onMessage fail-fast throw (the API client only throws Error objects),
119
+ // which is the user's own failure. Everything else (5xx, 429, network blip)
120
+ // is transient and retried below.
121
+ const isNonObjectThrow = typeof error !== "object" || error === null;
122
+ if (isNonObjectThrow ||
123
+ isNonRetryableDrainError(error) ||
124
+ isPermanentHttpError(error)) {
125
+ return { kind: "error", error };
126
+ }
127
+ // Transient: retry indefinitely (a watch runs until aborted). The counter
128
+ // only escalates the backoff and is capped where the backoff saturates, so
129
+ // a long outage can't grow it unbounded; a successful pass resets it below.
130
+ consecutiveErrors = Math.min(consecutiveErrors + 1, ERROR_BACKOFF_CAP);
131
+ errorAttempts += 1;
132
+ const delay = calculateErrorBackoffMs(BASE_ERROR_BACKOFF_MS, consecutiveErrors);
133
+ const statusCode = errorStatusCode(error);
134
+ const httpPart = statusCode !== undefined ? ` (HTTP ${statusCode})` : "";
135
+ // Once retries saturate the backoff the watch is making no forward
136
+ // progress, so surface it once on stderr even without `debug` (re-armed on
137
+ // recovery), mirroring how the SSE loop warns when real-time wake-ups pause.
138
+ if (!drainDegraded && consecutiveErrors >= ERROR_BACKOFF_CAP) {
139
+ console.warn(`[zapier-sdk] Draining inbox ${inboxId}${httpPart} is failing ` +
140
+ `repeatedly: ${errorMessage(error)}. Continuing to retry with backoff.`);
141
+ drainDegraded = true;
142
+ }
143
+ if (debug) {
144
+ console.error(`[zapier-sdk] Retrying drain for inbox ${inboxId} ` +
145
+ `(attempt ${errorAttempts}, retry in ${delay}ms)${httpPart}: ${errorMessage(error)}`);
146
+ }
147
+ await sleep(delay, signal);
148
+ if (signal.aborted)
149
+ return { kind: "aborted" };
150
+ // Re-arm the latch so the loop drains again even without an external
151
+ // wake-up (SSE frame / safety timer). firstFetch stays set so a retry of
152
+ // the first pass still runs the initialization_failure check.
153
+ drainRequest.request();
154
+ continue;
82
155
  }
83
156
  firstFetch = false;
157
+ consecutiveErrors = 0;
158
+ errorAttempts = 0;
159
+ drainDegraded = false;
84
160
  if (abortedFromCallback)
85
161
  return { kind: "abortedFromCallback" };
86
162
  }
87
163
  return { kind: "aborted" };
88
164
  }
89
165
  /** statusCode off any ZapierError (ZapierApiError, ZapierApprovalError, …). */
90
- function sseErrorStatusCode(err) {
166
+ function errorStatusCode(err) {
91
167
  return err instanceof ZapierError ? err.statusCode : undefined;
92
168
  }
93
- function sseErrorMessage(err) {
169
+ function errorMessage(err) {
94
170
  return err instanceof Error ? err.message : String(err);
95
171
  }
96
172
  /**
@@ -152,8 +228,8 @@ async function sseLoop({ api, inboxId, drainRequest, safetyDrainMs, signal, debu
152
228
  return;
153
229
  if (isPermanentHttpError(err)) {
154
230
  if (!degraded) {
155
- const statusCode = sseErrorStatusCode(err);
156
- const errorMsg = sseErrorMessage(err);
231
+ const statusCode = errorStatusCode(err);
232
+ const errorMsg = errorMessage(err);
157
233
  const httpPart = statusCode !== undefined ? ` (HTTP ${statusCode})` : "";
158
234
  console.warn(`[zapier-sdk] Real-time wake-ups for inbox ${inboxId}${httpPart} ` +
159
235
  `paused: ${errorMsg}. Falling back to the periodic safety drain.`);
@@ -176,8 +252,8 @@ async function sseLoop({ api, inboxId, drainRequest, safetyDrainMs, signal, debu
176
252
  const delay = SSE_RECONNECT_BACKOFF_MS[Math.min(attempt, SSE_RECONNECT_BACKOFF_MS.length - 1)];
177
253
  attempt = Math.min(attempt + 1, SSE_RECONNECT_BACKOFF_MS.length - 1);
178
254
  if (transientError !== undefined && debug) {
179
- const statusCode = sseErrorStatusCode(transientError);
180
- const errorMsg = sseErrorMessage(transientError);
255
+ const statusCode = errorStatusCode(transientError);
256
+ const errorMsg = errorMessage(transientError);
181
257
  const httpPart = statusCode !== undefined ? ` (HTTP ${statusCode})` : "";
182
258
  console.error(`[zapier-sdk] Reconnecting real-time wake-ups for inbox ${inboxId} ` +
183
259
  `(attempt ${attempt}, retry in ${delay}ms)${httpPart}: ${errorMsg}`);
@@ -209,8 +285,10 @@ async function safetyTimerLoop({ safetyDrainMs, drainRequest, signal, }) {
209
285
  * `maxDrainIntervalSeconds` seconds (default: 300) to guarantee
210
286
  * forward progress if SSE events are missed or the connection drops
211
287
  * undetected. Resolves cleanly on `signal` abort or when a callback
212
- * throws `ZapierAbortDrainSignal`; rejects on a fatal error or a
213
- * fail-fast handler error.
288
+ * throws `ZapierAbortDrainSignal`. Transient drain failures (5xx, 429,
289
+ * network blips) are retried indefinitely with bounded backoff until they
290
+ * succeed or the watch is aborted; it rejects only on a fail-fast handler
291
+ * error, an `initialization_failure`, or a permanent HTTP error.
214
292
  */
215
293
  export const watchTriggerInboxPlugin = definePlugin((sdk) => {
216
294
  async function watchTriggerInbox(options) {
@@ -265,7 +343,13 @@ export const watchTriggerInboxPlugin = definePlugin((sdk) => {
265
343
  // in the inbox even if SSE never connects (e.g. a permanent 4xx). SSE and
266
344
  // the safety timer then layer on additional catch-up drains.
267
345
  drainRequest.request();
268
- const runnerDone = drainRunner({ drainOptions, drainRequest, signal });
346
+ const runnerDone = drainRunner({
347
+ drainOptions,
348
+ drainRequest,
349
+ signal,
350
+ inboxId,
351
+ debug,
352
+ });
269
353
  const sseDone = sseLoop({
270
354
  api: sdk.context.api,
271
355
  inboxId,
@@ -300,7 +384,7 @@ export const watchTriggerInboxPlugin = definePlugin((sdk) => {
300
384
  watchTriggerInbox: {
301
385
  ...triggersDefaults,
302
386
  type: "create",
303
- description: "Continuously consume a trigger inbox: drain currently-available messages via onMessage, then subscribe to SSE notifications for new arrivals until aborted. A periodic safety drain runs every maxDrainIntervalSeconds (default: 300) to guarantee forward progress if SSE events are missed. Resolves cleanly on signal abort or ZapierAbortDrainSignal from a handler; rejects on fatal SDK errors or fail-fast handler errors. Real-time wake-up health is reported on stderr: a warning when wake-ups pause and the watch falls back to the safety drain, plus (with debug) transient reconnect notices.",
387
+ description: "Continuously consume a trigger inbox: drain currently-available messages via onMessage, then subscribe to SSE notifications for new arrivals until aborted. A periodic safety drain runs every maxDrainIntervalSeconds (default: 300) to guarantee forward progress if SSE events are missed. Resolves cleanly on signal abort or ZapierAbortDrainSignal from a handler. Transient drain failures (5xx, 429, network blips) retry indefinitely with bounded backoff until they succeed or the watch is aborted; it rejects on a fail-fast handler error, an initialization_failure, or a permanent HTTP error. Real-time wake-up health is reported on stderr: a warning when wake-ups pause and the watch falls back to the safety drain, plus (with debug) transient reconnect notices. Persistent drain failures likewise warn once on stderr while bounded-backoff retries continue.",
304
388
  itemType: "void",
305
389
  // See drainTriggerInbox: override the doc generator's default
306
390
  // suffix so the rendered return type is Promise<void>, not
@@ -5,9 +5,10 @@ interface InboxSseEvent {
5
5
  }
6
6
  /**
7
7
  * Streams the trigger inbox events endpoint and yields the notification frames
8
- * for this inbox. The transport (SSE connection, parsing, reconnect-able error
9
- * mapping, abort/cleanup) lives in `api.fetchStream`; this only adds the inbox
10
- * URL and the `inbox_id` filter.
8
+ * for this inbox. The transport (SSE connection, JSON parsing, reconnect-able
9
+ * error mapping, abort/cleanup) lives in `api.fetchJsonStream`; this only adds
10
+ * the inbox URL and the `inbox_id` filter, skipping any frame whose data was
11
+ * not valid JSON.
11
12
  *
12
13
  * `onOpen` fires once the connection is live (response ok, body present) and
13
14
  * before the first frame. The endpoint is edge-triggered with no replay, so a
@@ -16,9 +17,10 @@ interface InboxSseEvent {
16
17
  *
17
18
  * The generator returns (without throwing) when the signal is aborted, when
18
19
  * the server closes the connection (JWT expiry, clean shutdown), or when the
19
- * body is empty. For a non-ok response `api.fetchStream` throws a `ZapierError`
20
- * carrying the HTTP `statusCode`; `isPermanentHttpError` classifies it so the
21
- * caller can tell permanent (4xx) failures from transient ones.
20
+ * body is empty. For a non-ok response `api.fetchJsonStream` throws a
21
+ * `ZapierError` carrying the HTTP `statusCode`; `isPermanentHttpError`
22
+ * classifies it so the caller can tell permanent (4xx) failures from transient
23
+ * ones.
22
24
  */
23
25
  export declare function readInboxEvents({ api, inboxId, signal, onOpen, }: {
24
26
  api: ApiClient;
@@ -1 +1 @@
1
- {"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../../../../src/plugins/triggers/watchTriggerInbox/sse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,UAAU,aAAa;IACrB,QAAQ,EAAE,MAAM,CAAC;IAIjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAuB,eAAe,CAAC,EACrC,GAAG,EACH,OAAO,EACP,MAAM,EACN,MAAM,GACP,EAAE;IACD,GAAG,EAAE,SAAS,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB,GAAG,cAAc,CAAC,aAAa,CAAC,CAyBhC"}
1
+ {"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../../../../src/plugins/triggers/watchTriggerInbox/sse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,UAAU,aAAa;IACrB,QAAQ,EAAE,MAAM,CAAC;IAIjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAuB,eAAe,CAAC,EACrC,GAAG,EACH,OAAO,EACP,MAAM,EACN,MAAM,GACP,EAAE;IACD,GAAG,EAAE,SAAS,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB,GAAG,cAAc,CAAC,aAAa,CAAC,CAqBhC"}
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Streams the trigger inbox events endpoint and yields the notification frames
3
- * for this inbox. The transport (SSE connection, parsing, reconnect-able error
4
- * mapping, abort/cleanup) lives in `api.fetchStream`; this only adds the inbox
5
- * URL and the `inbox_id` filter.
3
+ * for this inbox. The transport (SSE connection, JSON parsing, reconnect-able
4
+ * error mapping, abort/cleanup) lives in `api.fetchJsonStream`; this only adds
5
+ * the inbox URL and the `inbox_id` filter, skipping any frame whose data was
6
+ * not valid JSON.
6
7
  *
7
8
  * `onOpen` fires once the connection is live (response ok, body present) and
8
9
  * before the first frame. The endpoint is edge-triggered with no replay, so a
@@ -11,19 +12,16 @@
11
12
  *
12
13
  * The generator returns (without throwing) when the signal is aborted, when
13
14
  * the server closes the connection (JWT expiry, clean shutdown), or when the
14
- * body is empty. For a non-ok response `api.fetchStream` throws a `ZapierError`
15
- * carrying the HTTP `statusCode`; `isPermanentHttpError` classifies it so the
16
- * caller can tell permanent (4xx) failures from transient ones.
15
+ * body is empty. For a non-ok response `api.fetchJsonStream` throws a
16
+ * `ZapierError` carrying the HTTP `statusCode`; `isPermanentHttpError`
17
+ * classifies it so the caller can tell permanent (4xx) failures from transient
18
+ * ones.
17
19
  */
18
20
  export async function* readInboxEvents({ api, inboxId, signal, onOpen, }) {
19
- for await (const message of api.fetchStream(`/trigger-inbox/api/v1/inboxes/${encodeURIComponent(inboxId)}/events`, { method: "GET", signal, authRequired: true, onOpen })) {
20
- let parsed;
21
- try {
22
- parsed = JSON.parse(message.data);
23
- }
24
- catch {
21
+ for await (const message of api.fetchJsonStream(`/trigger-inbox/api/v1/inboxes/${encodeURIComponent(inboxId)}/events`, { method: "GET", signal, authRequired: true, onOpen })) {
22
+ if (!message.parsed)
25
23
  continue;
26
- }
24
+ const parsed = message.data;
27
25
  if (typeof parsed === "object" &&
28
26
  parsed !== null &&
29
27
  typeof parsed.inbox_id === "string" &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zapier/zapier-sdk",
3
- "version": "0.69.2",
3
+ "version": "0.70.0",
4
4
  "description": "Complete Zapier SDK - combines all Zapier SDK packages",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.mjs",