@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.
- package/CHANGELOG.md +34 -0
- package/README.md +33 -23
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +10 -1
- package/dist/api/error-classification.d.ts +7 -0
- package/dist/api/error-classification.d.ts.map +1 -1
- package/dist/api/error-classification.js +15 -2
- package/dist/api/index.d.ts +1 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/sse-parser.d.ts +34 -0
- package/dist/api/sse-parser.d.ts.map +1 -1
- package/dist/api/sse-parser.js +28 -0
- package/dist/api/types.d.ts +9 -1
- package/dist/api/types.d.ts.map +1 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +2 -2
- package/dist/experimental.cjs +174 -35
- package/dist/experimental.d.mts +34 -2
- package/dist/experimental.d.ts +32 -0
- package/dist/experimental.d.ts.map +1 -1
- package/dist/experimental.mjs +174 -35
- package/dist/{index-DuFFW71E.d.mts → index-C0bQ5snd.d.mts} +33 -1
- package/dist/{index-DuFFW71E.d.ts → index-C0bQ5snd.d.ts} +33 -1
- package/dist/index.cjs +32 -5
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +32 -5
- package/dist/plugins/codeSubstrate/createWorkflow/index.d.ts +3 -0
- package/dist/plugins/codeSubstrate/createWorkflow/index.d.ts.map +1 -1
- package/dist/plugins/codeSubstrate/createWorkflow/index.js +3 -0
- package/dist/plugins/codeSubstrate/createWorkflow/schemas.d.ts +3 -0
- package/dist/plugins/codeSubstrate/createWorkflow/schemas.d.ts.map +1 -1
- package/dist/plugins/codeSubstrate/createWorkflow/schemas.js +11 -0
- package/dist/plugins/codeSubstrate/publishWorkflowVersion/index.d.ts +13 -0
- package/dist/plugins/codeSubstrate/publishWorkflowVersion/index.d.ts.map +1 -1
- package/dist/plugins/codeSubstrate/publishWorkflowVersion/index.js +9 -0
- package/dist/plugins/codeSubstrate/publishWorkflowVersion/schemas.d.ts +13 -0
- package/dist/plugins/codeSubstrate/publishWorkflowVersion/schemas.d.ts.map +1 -1
- package/dist/plugins/codeSubstrate/publishWorkflowVersion/schemas.js +52 -0
- package/dist/plugins/triggers/drainTriggerInbox/index.d.ts +4 -2
- package/dist/plugins/triggers/drainTriggerInbox/index.d.ts.map +1 -1
- package/dist/plugins/triggers/drainTriggerInbox/index.js +39 -6
- package/dist/plugins/triggers/watchTriggerInbox/index.d.ts +4 -2
- package/dist/plugins/triggers/watchTriggerInbox/index.d.ts.map +1 -1
- package/dist/plugins/triggers/watchTriggerInbox/index.js +100 -16
- package/dist/plugins/triggers/watchTriggerInbox/sse.d.ts +8 -6
- package/dist/plugins/triggers/watchTriggerInbox/sse.d.ts.map +1 -1
- package/dist/plugins/triggers/watchTriggerInbox/sse.js +11 -13
- 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
|
|
27
|
-
*
|
|
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 (
|
|
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
|
-
|
|
146
|
-
|
|
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
|
|
30
|
-
*
|
|
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;
|
|
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).
|
|
65
|
-
*
|
|
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
|
-
|
|
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
|
|
166
|
+
function errorStatusCode(err) {
|
|
91
167
|
return err instanceof ZapierError ? err.statusCode : undefined;
|
|
92
168
|
}
|
|
93
|
-
function
|
|
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 =
|
|
156
|
-
const errorMsg =
|
|
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 =
|
|
180
|
-
const errorMsg =
|
|
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
|
|
213
|
-
*
|
|
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({
|
|
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
|
|
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
|
|
9
|
-
* mapping, abort/cleanup) lives in `api.
|
|
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.
|
|
20
|
-
* carrying the HTTP `statusCode`; `isPermanentHttpError`
|
|
21
|
-
* caller can tell permanent (4xx) failures from transient
|
|
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
|
|
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
|
|
4
|
-
* mapping, abort/cleanup) lives in `api.
|
|
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.
|
|
15
|
-
* carrying the HTTP `statusCode`; `isPermanentHttpError`
|
|
16
|
-
* caller can tell permanent (4xx) failures from transient
|
|
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.
|
|
20
|
-
|
|
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" &&
|