@zapier/zapier-sdk 0.68.0 → 0.69.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/AGENTS.md +321 -0
- package/CHANGELOG.md +15 -0
- package/CLAUDE.md +3 -319
- package/README.md +17 -17
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +108 -3
- package/dist/api/error-classification.d.ts +12 -0
- package/dist/api/error-classification.d.ts.map +1 -0
- package/dist/api/error-classification.js +18 -0
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +3 -0
- package/dist/api/sse-parser.d.ts +17 -0
- package/dist/api/sse-parser.d.ts.map +1 -0
- package/dist/api/sse-parser.js +67 -0
- package/dist/api/types.d.ts +16 -0
- package/dist/api/types.d.ts.map +1 -1
- package/dist/experimental.cjs +356 -67
- package/dist/experimental.d.mts +2 -2
- package/dist/experimental.mjs +356 -67
- package/dist/{index-oRnHsPn5.d.mts → index-DC31DAP2.d.mts} +20 -1
- package/dist/{index-oRnHsPn5.d.ts → index-DC31DAP2.d.ts} +20 -1
- package/dist/index.cjs +131 -7
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +131 -7
- package/dist/plugins/triggers/drainTriggerInbox/schemas.js +2 -2
- package/dist/plugins/triggers/watchTriggerInbox/index.d.ts +31 -6
- package/dist/plugins/triggers/watchTriggerInbox/index.d.ts.map +1 -1
- package/dist/plugins/triggers/watchTriggerInbox/index.js +272 -65
- package/dist/plugins/triggers/watchTriggerInbox/sse.d.ts +30 -0
- package/dist/plugins/triggers/watchTriggerInbox/sse.d.ts.map +1 -0
- package/dist/plugins/triggers/watchTriggerInbox/sse.js +38 -0
- package/package.json +2 -1
|
@@ -4,46 +4,213 @@ import { requireOnMessage, resolveConcurrencyAndLease, runDrainPass, } from "../
|
|
|
4
4
|
import { resolveTriggerInboxId } from "../utils";
|
|
5
5
|
import { triggerInboxResolver } from "../../../resolvers";
|
|
6
6
|
import { triggersDefaults } from "../shared";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
7
|
+
import { isAbortError, combineAbortSignals } from "../../../utils/abort-utils";
|
|
8
|
+
import { sleep } from "../../../utils/retry-utils";
|
|
9
|
+
import { isPermanentHttpError } from "../../../api";
|
|
10
|
+
import { ZapierError } from "../../../types/errors";
|
|
11
|
+
import { readInboxEvents } from "./sse";
|
|
12
|
+
// First entry is the floor, not 0: a server that closes the stream immediately
|
|
13
|
+
// (instant EOF, empty body) must not be able to reconnect with no delay, or it
|
|
14
|
+
// busy-loops. Repeated instant closes escalate through the later entries (up to
|
|
15
|
+
// 5 s); only a clean end after a healthy, long-lived connection resets to the
|
|
16
|
+
// floor — see SSE_HEALTHY_CONNECTION_MS.
|
|
17
|
+
const SSE_RECONNECT_BACKOFF_MS = [500, 1000, 2000, 5000];
|
|
18
|
+
const DEFAULT_SAFETY_DRAIN_INTERVAL_MS = 300000; // 5 minutes
|
|
19
|
+
// A clean stream end only resets the reconnect backoff to the floor if the
|
|
20
|
+
// connection stayed open at least this long. A connection that lived longer
|
|
21
|
+
// than our longest reconnect backoff is "healthy", so resetting gives fast
|
|
22
|
+
// recovery from routine server-side cycling (e.g. JWT expiry); a stream that
|
|
23
|
+
// closes sooner is treated as unstable and escalates like any other.
|
|
24
|
+
const SSE_HEALTHY_CONNECTION_MS = 5000;
|
|
25
|
+
/**
|
|
26
|
+
* Coalescing wake latch between the drain producers (SSE connect, SSE frames,
|
|
27
|
+
* the safety timer, abort) and the single drain consumer. A `request()` that
|
|
28
|
+
* lands while the consumer is mid-drain is not lost: `pending` stays set, so
|
|
29
|
+
* the consumer's next `waitForRequest()` returns immediately and runs one more
|
|
30
|
+
* drain. Mirrors `createWaiter` in drainTriggerInbox/pipeline.ts.
|
|
31
|
+
*
|
|
32
|
+
* Exported for unit testing the coalescing / no-lost-wakeup semantics.
|
|
33
|
+
*/
|
|
34
|
+
export function createDrainLatch() {
|
|
35
|
+
let pending = false;
|
|
36
|
+
const make = () => {
|
|
37
|
+
let resolve;
|
|
38
|
+
const promise = new Promise((r) => {
|
|
39
|
+
resolve = r;
|
|
40
|
+
});
|
|
41
|
+
return { promise, resolve };
|
|
42
|
+
};
|
|
43
|
+
let current = make();
|
|
44
|
+
return {
|
|
45
|
+
request() {
|
|
46
|
+
pending = true;
|
|
47
|
+
const prev = current;
|
|
48
|
+
current = make();
|
|
49
|
+
prev.resolve();
|
|
50
|
+
},
|
|
51
|
+
async waitForRequest() {
|
|
52
|
+
if (pending) {
|
|
53
|
+
pending = false;
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
await current.promise;
|
|
57
|
+
pending = false;
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Sole caller of `runDrainPass`, so drains are serial and never overlap. Waits
|
|
63
|
+
* 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.
|
|
66
|
+
*/
|
|
67
|
+
async function drainRunner({ drainOptions, drainRequest, signal, }) {
|
|
68
|
+
let firstFetch = true;
|
|
69
|
+
while (!signal.aborted) {
|
|
70
|
+
await drainRequest.waitForRequest();
|
|
71
|
+
if (signal.aborted)
|
|
72
|
+
return { kind: "aborted" };
|
|
73
|
+
let abortedFromCallback = false;
|
|
74
|
+
try {
|
|
75
|
+
({ abortedFromCallback } = await runDrainPass({
|
|
76
|
+
...drainOptions,
|
|
77
|
+
firstFetch,
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
return { kind: "error", error };
|
|
82
|
+
}
|
|
83
|
+
firstFetch = false;
|
|
84
|
+
if (abortedFromCallback)
|
|
85
|
+
return { kind: "abortedFromCallback" };
|
|
86
|
+
}
|
|
87
|
+
return { kind: "aborted" };
|
|
88
|
+
}
|
|
89
|
+
/** statusCode off any ZapierError (ZapierApiError, ZapierApprovalError, …). */
|
|
90
|
+
function sseErrorStatusCode(err) {
|
|
91
|
+
return err instanceof ZapierError ? err.statusCode : undefined;
|
|
21
92
|
}
|
|
22
|
-
function
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
93
|
+
function sseErrorMessage(err) {
|
|
94
|
+
return err instanceof Error ? err.message : String(err);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Holds one persistent SSE connection open and requests a drain on connect
|
|
98
|
+
* (catch-up) and on every frame, keeping the socket open across drains so no
|
|
99
|
+
* notification window is missed. Reconnects with backoff on clean end or
|
|
100
|
+
* transient error. A 4xx is permanent for now (auth/permission/bad request);
|
|
101
|
+
* rather than hammer the endpoint or abandon SSE, it waits a full safety
|
|
102
|
+
* interval before retrying so a transient misconfiguration still self-heals,
|
|
103
|
+
* while the independent safety timer keeps draining. (429 never lands here —
|
|
104
|
+
* api.fetch retries it upstream as ZapierRateLimitError, treated as transient.)
|
|
105
|
+
*
|
|
106
|
+
* Failures are printed to stderr (never stdout, so `--json` NDJSON stays clean):
|
|
107
|
+
* permanent degradation warns once on entering the degraded state (silent on
|
|
108
|
+
* the per-safety-interval retries, warning again only after a reconnect then
|
|
109
|
+
* re-failure); transient reconnects are dim diagnostics printed only under
|
|
110
|
+
* `debug`. Clean stream ends (e.g. routine JWT-expiry cycling) print nothing,
|
|
111
|
+
* so a reconnect notice always means an error broke the stream.
|
|
112
|
+
*/
|
|
113
|
+
async function sseLoop({ api, inboxId, drainRequest, safetyDrainMs, signal, debug, }) {
|
|
114
|
+
let attempt = 0;
|
|
115
|
+
// Whether we've already warned about the current permanent-failure spell.
|
|
116
|
+
// Warn once on entering it; stay silent on retries; re-arm on a successful
|
|
117
|
+
// reconnect (onOpen) — the only way out, since a permanent 4xx throws before
|
|
118
|
+
// onOpen fires. Without this a standing 4xx prints the same warning every
|
|
119
|
+
// safety interval forever.
|
|
120
|
+
let degraded = false;
|
|
121
|
+
while (!signal.aborted) {
|
|
122
|
+
let connected = false;
|
|
123
|
+
let connectedAt = 0;
|
|
124
|
+
let transientError;
|
|
125
|
+
try {
|
|
126
|
+
for await (const _event of readInboxEvents({
|
|
127
|
+
api,
|
|
128
|
+
inboxId,
|
|
129
|
+
signal,
|
|
130
|
+
// SSE is edge-triggered with no replay, so draining on connect is the
|
|
131
|
+
// only way to pick up messages that arrived before this connection —
|
|
132
|
+
// including the window left by a prior disconnect.
|
|
133
|
+
onOpen: () => {
|
|
134
|
+
connected = true;
|
|
135
|
+
connectedAt = Date.now();
|
|
136
|
+
degraded = false;
|
|
137
|
+
drainRequest.request();
|
|
138
|
+
},
|
|
139
|
+
})) {
|
|
140
|
+
drainRequest.request();
|
|
141
|
+
}
|
|
142
|
+
// Clean stream end. Reset to the floor only if the connection stayed open
|
|
143
|
+
// long enough to count as healthy (e.g. routine JWT-expiry cycling), so a
|
|
144
|
+
// server that accepts the connection and immediately closes it escalates
|
|
145
|
+
// through the backoff below instead of reconnecting at a fixed 500 ms.
|
|
146
|
+
if (connected && Date.now() - connectedAt >= SSE_HEALTHY_CONNECTION_MS) {
|
|
147
|
+
attempt = 0;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
if (signal.aborted || isAbortError(err))
|
|
29
152
|
return;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
153
|
+
if (isPermanentHttpError(err)) {
|
|
154
|
+
if (!degraded) {
|
|
155
|
+
const statusCode = sseErrorStatusCode(err);
|
|
156
|
+
const errorMsg = sseErrorMessage(err);
|
|
157
|
+
const httpPart = statusCode !== undefined ? ` (HTTP ${statusCode})` : "";
|
|
158
|
+
console.warn(`[zapier-sdk] Real-time wake-ups for inbox ${inboxId}${httpPart} ` +
|
|
159
|
+
`paused: ${errorMsg}. Falling back to the periodic safety drain.`);
|
|
160
|
+
degraded = true;
|
|
161
|
+
}
|
|
162
|
+
attempt = 0;
|
|
163
|
+
if (signal.aborted)
|
|
164
|
+
return;
|
|
165
|
+
await sleep(safetyDrainMs, signal);
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
// A connection that opened then dropped proves the endpoint is reachable,
|
|
169
|
+
// so reset to the floor; a failed connect grows the backoff instead.
|
|
170
|
+
if (connected)
|
|
171
|
+
attempt = 0;
|
|
172
|
+
transientError = err;
|
|
173
|
+
}
|
|
174
|
+
if (signal.aborted)
|
|
175
|
+
return;
|
|
176
|
+
const delay = SSE_RECONNECT_BACKOFF_MS[Math.min(attempt, SSE_RECONNECT_BACKOFF_MS.length - 1)];
|
|
177
|
+
attempt = Math.min(attempt + 1, SSE_RECONNECT_BACKOFF_MS.length - 1);
|
|
178
|
+
if (transientError !== undefined && debug) {
|
|
179
|
+
const statusCode = sseErrorStatusCode(transientError);
|
|
180
|
+
const errorMsg = sseErrorMessage(transientError);
|
|
181
|
+
const httpPart = statusCode !== undefined ? ` (HTTP ${statusCode})` : "";
|
|
182
|
+
console.error(`[zapier-sdk] Reconnecting real-time wake-ups for inbox ${inboxId} ` +
|
|
183
|
+
`(attempt ${attempt}, retry in ${delay}ms)${httpPart}: ${errorMsg}`);
|
|
184
|
+
}
|
|
185
|
+
await sleep(delay, signal);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Requests a drain every `safetyDrainMs`, independent of SSE state. Guarantees
|
|
190
|
+
* forward progress when SSE frames are missed, the connection drops undetected,
|
|
191
|
+
* or SSE is degraded. The timer is ref'd (like the prior poll loop) so the
|
|
192
|
+
* watch keeps the process alive until aborted — that is what makes this a real
|
|
193
|
+
* backstop even when SSE is parked on a permanent failure with no open socket.
|
|
194
|
+
*/
|
|
195
|
+
async function safetyTimerLoop({ safetyDrainMs, drainRequest, signal, }) {
|
|
196
|
+
while (!signal.aborted) {
|
|
197
|
+
await sleep(safetyDrainMs, signal);
|
|
198
|
+
if (signal.aborted)
|
|
199
|
+
return;
|
|
200
|
+
drainRequest.request();
|
|
201
|
+
}
|
|
38
202
|
}
|
|
39
203
|
/**
|
|
40
204
|
* `watchTriggerInbox` is the continuous-consumption variant of
|
|
41
205
|
* `drainTriggerInbox`. Same options surface, same callback shape;
|
|
42
|
-
* the difference is that
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* `
|
|
46
|
-
*
|
|
206
|
+
* the difference is that after draining the inbox, it subscribes to
|
|
207
|
+
* a Server-Sent Events stream to wake on new-message notifications
|
|
208
|
+
* rather than polling. A periodic safety drain fires every
|
|
209
|
+
* `maxDrainIntervalSeconds` seconds (default: 300) to guarantee
|
|
210
|
+
* forward progress if SSE events are missed or the connection drops
|
|
211
|
+
* 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.
|
|
47
214
|
*/
|
|
48
215
|
export const watchTriggerInboxPlugin = definePlugin((sdk) => {
|
|
49
216
|
async function watchTriggerInbox(options) {
|
|
@@ -53,38 +220,78 @@ export const watchTriggerInboxPlugin = definePlugin((sdk) => {
|
|
|
53
220
|
api: sdk.context.api,
|
|
54
221
|
inbox: options.inbox,
|
|
55
222
|
});
|
|
56
|
-
|
|
223
|
+
if (options.signal?.aborted)
|
|
224
|
+
return;
|
|
225
|
+
const safetyDrainMs = options.maxDrainIntervalSeconds !== undefined
|
|
57
226
|
? options.maxDrainIntervalSeconds * 1000
|
|
58
|
-
:
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
227
|
+
: DEFAULT_SAFETY_DRAIN_INTERVAL_MS;
|
|
228
|
+
// Internal stop lets the drain runner's terminal states (a handler's
|
|
229
|
+
// ZapierAbortDrainSignal, a fatal error) tear down the background SSE and
|
|
230
|
+
// safety loops. Combined with the caller's signal so either stops all.
|
|
231
|
+
const stop = new AbortController();
|
|
232
|
+
const combined = combineAbortSignals({
|
|
233
|
+
handles: [
|
|
234
|
+
...(options.signal
|
|
235
|
+
? [{ signal: options.signal, dispose: () => { } }]
|
|
236
|
+
: []),
|
|
237
|
+
{ signal: stop.signal, dispose: () => { } },
|
|
238
|
+
],
|
|
239
|
+
});
|
|
240
|
+
const signal = combined?.signal ?? stop.signal;
|
|
241
|
+
const drainRequest = createDrainLatch();
|
|
242
|
+
// Abort wakes the idle drain runner so it observes the abort and exits;
|
|
243
|
+
// the latch otherwise only resolves on a drain request.
|
|
244
|
+
signal.addEventListener("abort", () => drainRequest.request(), {
|
|
245
|
+
once: true,
|
|
246
|
+
});
|
|
247
|
+
// Real-time health is printed to stderr by the SSE loop (warnings always,
|
|
248
|
+
// reconnect diagnostics only under `debug`), so every consumer surfaces it
|
|
249
|
+
// without wiring anything — the watcher never routes this through onEvent.
|
|
250
|
+
const debug = sdk.context.options.debug === true;
|
|
251
|
+
const drainOptions = {
|
|
252
|
+
sdk,
|
|
253
|
+
inboxId,
|
|
254
|
+
onMessage,
|
|
255
|
+
concurrency,
|
|
256
|
+
leaseLimit,
|
|
257
|
+
leaseSeconds: options.leaseSeconds,
|
|
258
|
+
maxMessages: undefined,
|
|
259
|
+
releaseOnError: options.releaseOnError ?? false,
|
|
260
|
+
continueOnError: options.continueOnError ?? false,
|
|
261
|
+
onError: options.onError,
|
|
262
|
+
signal,
|
|
263
|
+
};
|
|
264
|
+
// Unconditional initial drain (firstFetch: true): handle anything already
|
|
265
|
+
// in the inbox even if SSE never connects (e.g. a permanent 4xx). SSE and
|
|
266
|
+
// the safety timer then layer on additional catch-up drains.
|
|
267
|
+
drainRequest.request();
|
|
268
|
+
const runnerDone = drainRunner({ drainOptions, drainRequest, signal });
|
|
269
|
+
const sseDone = sseLoop({
|
|
270
|
+
api: sdk.context.api,
|
|
271
|
+
inboxId,
|
|
272
|
+
drainRequest,
|
|
273
|
+
safetyDrainMs,
|
|
274
|
+
signal,
|
|
275
|
+
debug,
|
|
276
|
+
}).catch(() => { });
|
|
277
|
+
const safetyDone = safetyTimerLoop({
|
|
278
|
+
safetyDrainMs,
|
|
279
|
+
drainRequest,
|
|
280
|
+
signal,
|
|
281
|
+
}).catch(() => { });
|
|
282
|
+
let end;
|
|
283
|
+
try {
|
|
284
|
+
end = await runnerDone;
|
|
285
|
+
}
|
|
286
|
+
finally {
|
|
287
|
+
// Stop the producers and wait for them to unwind so no fetch or timer
|
|
288
|
+
// outlives this call, then release the combined-signal listeners.
|
|
289
|
+
stop.abort();
|
|
290
|
+
await Promise.all([sseDone, safetyDone]);
|
|
291
|
+
combined?.dispose();
|
|
87
292
|
}
|
|
293
|
+
if (end.kind === "error")
|
|
294
|
+
throw end.error;
|
|
88
295
|
}
|
|
89
296
|
return {
|
|
90
297
|
watchTriggerInbox,
|
|
@@ -93,7 +300,7 @@ export const watchTriggerInboxPlugin = definePlugin((sdk) => {
|
|
|
93
300
|
watchTriggerInbox: {
|
|
94
301
|
...triggersDefaults,
|
|
95
302
|
type: "create",
|
|
96
|
-
description: "Continuously consume a trigger inbox: drain currently-available messages via onMessage, then
|
|
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.",
|
|
97
304
|
itemType: "void",
|
|
98
305
|
// See drainTriggerInbox: override the doc generator's default
|
|
99
306
|
// suffix so the rendered return type is Promise<void>, not
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ApiClient } from "../../../api";
|
|
2
|
+
interface InboxSseEvent {
|
|
3
|
+
inbox_id: string;
|
|
4
|
+
count?: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
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.
|
|
11
|
+
*
|
|
12
|
+
* `onOpen` fires once the connection is live (response ok, body present) and
|
|
13
|
+
* before the first frame. The endpoint is edge-triggered with no replay, so a
|
|
14
|
+
* caller must drain on `onOpen` to pick up messages that arrived before this
|
|
15
|
+
* connection existed — a frame alone never covers that window.
|
|
16
|
+
*
|
|
17
|
+
* The generator returns (without throwing) when the signal is aborted, when
|
|
18
|
+
* 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.
|
|
22
|
+
*/
|
|
23
|
+
export declare function readInboxEvents({ api, inboxId, signal, onOpen, }: {
|
|
24
|
+
api: ApiClient;
|
|
25
|
+
inboxId: string;
|
|
26
|
+
signal?: AbortSignal;
|
|
27
|
+
onOpen?: () => void;
|
|
28
|
+
}): AsyncGenerator<InboxSseEvent>;
|
|
29
|
+
export {};
|
|
30
|
+
//# sourceMappingURL=sse.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
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.
|
|
6
|
+
*
|
|
7
|
+
* `onOpen` fires once the connection is live (response ok, body present) and
|
|
8
|
+
* before the first frame. The endpoint is edge-triggered with no replay, so a
|
|
9
|
+
* caller must drain on `onOpen` to pick up messages that arrived before this
|
|
10
|
+
* connection existed — a frame alone never covers that window.
|
|
11
|
+
*
|
|
12
|
+
* The generator returns (without throwing) when the signal is aborted, when
|
|
13
|
+
* 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.
|
|
17
|
+
*/
|
|
18
|
+
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 {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (typeof parsed === "object" &&
|
|
28
|
+
parsed !== null &&
|
|
29
|
+
typeof parsed.inbox_id === "string" &&
|
|
30
|
+
// Only wake on a frame for this inbox. Case-insensitive: the endpoint
|
|
31
|
+
// echoes the canonical lowercase UUID, but resolveTriggerInboxId passes
|
|
32
|
+
// a UUID-shaped `inbox` through unchanged, so its casing may differ.
|
|
33
|
+
parsed.inbox_id.toLowerCase() ===
|
|
34
|
+
inboxId.toLowerCase()) {
|
|
35
|
+
yield parsed;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zapier/zapier-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.69.0",
|
|
4
4
|
"description": "Complete Zapier SDK - combines all Zapier SDK packages",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
"files": [
|
|
62
62
|
"dist",
|
|
63
63
|
"README.md",
|
|
64
|
+
"AGENTS.md",
|
|
64
65
|
"CLAUDE.md",
|
|
65
66
|
"CHANGELOG.md"
|
|
66
67
|
],
|