@voltro/plugin-webhooks 0.26.0 → 0.28.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 +532 -0
- package/dist/index.d.ts +120 -16
- package/dist/index.js +206 -155
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -6,10 +6,10 @@ import { Context as te, Effect as y, Either as ne, Schema as b } from "effect";
|
|
|
6
6
|
import { and as x, eq as S } from "@voltro/database";
|
|
7
7
|
import { assertPublicUrl as C } from "@voltro/integration-http";
|
|
8
8
|
import { publishServerError as re } from "@voltro/protocol";
|
|
9
|
-
import { createLogger as
|
|
10
|
-
import { Activity as
|
|
9
|
+
import { createLogger as ie } from "@voltro/logger";
|
|
10
|
+
import { Activity as w, DurableClock as T, Workflow as ae } from "@effect/workflow";
|
|
11
11
|
//#region src/eventAudience.ts
|
|
12
|
-
var
|
|
12
|
+
var oe = (e) => e.webhook !== void 0, E = (e) => {
|
|
13
13
|
let t = e.webhook;
|
|
14
14
|
if (t !== void 0) return {
|
|
15
15
|
_tag: "outgoingEvent",
|
|
@@ -19,8 +19,8 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
19
19
|
...t.version === void 0 ? {} : { version: t.version },
|
|
20
20
|
...t.rateLimit === void 0 ? {} : { globalRateLimit: t.rateLimit }
|
|
21
21
|
};
|
|
22
|
-
},
|
|
23
|
-
let t =
|
|
22
|
+
}, se = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, D = (e) => {
|
|
23
|
+
let t = se.exec(e);
|
|
24
24
|
if (!t) throw Error(`invalid duration literal: ${e}`);
|
|
25
25
|
let n = Number(t[1]), r = t[2] ?? "s";
|
|
26
26
|
switch (r) {
|
|
@@ -31,7 +31,7 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
31
31
|
case "d": return n * 864e5;
|
|
32
32
|
default: throw Error(`unreachable unit: ${r}`);
|
|
33
33
|
}
|
|
34
|
-
},
|
|
34
|
+
}, O = [
|
|
35
35
|
408,
|
|
36
36
|
425,
|
|
37
37
|
429,
|
|
@@ -39,16 +39,16 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
39
39
|
502,
|
|
40
40
|
503,
|
|
41
41
|
504
|
|
42
|
-
],
|
|
43
|
-
if (t >= e.maxAttempts || n?.status !== void 0 && !(e.retryOn ??
|
|
42
|
+
], k = (e, t, n) => {
|
|
43
|
+
if (t >= e.maxAttempts || n?.status !== void 0 && !(e.retryOn ?? O).includes(n.status)) return null;
|
|
44
44
|
if (e.honourRetryAfter !== !1 && n?.retryAfterSeconds !== void 0) {
|
|
45
|
-
let t = n.retryAfterSeconds * 1e3, r =
|
|
45
|
+
let t = n.retryAfterSeconds * 1e3, r = D(e.maxDelay), i = Math.min(t, r);
|
|
46
46
|
return {
|
|
47
47
|
delayMs: i,
|
|
48
48
|
baseDelayMs: i
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
|
-
let r =
|
|
51
|
+
let r = D(e.initialDelay), i = D(e.maxDelay), a;
|
|
52
52
|
switch (e.strategy) {
|
|
53
53
|
case "fixed":
|
|
54
54
|
a = r;
|
|
@@ -66,23 +66,23 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
66
66
|
delayMs: Math.max(1, Math.floor(o)),
|
|
67
67
|
baseDelayMs: a
|
|
68
68
|
};
|
|
69
|
-
},
|
|
69
|
+
}, A = () => ({
|
|
70
70
|
strategy: "exponential",
|
|
71
71
|
maxAttempts: 8,
|
|
72
72
|
initialDelay: "5s",
|
|
73
73
|
maxDelay: "1h",
|
|
74
|
-
retryOn:
|
|
74
|
+
retryOn: O,
|
|
75
75
|
honourRetryAfter: !0,
|
|
76
76
|
jitter: "full"
|
|
77
|
-
}),
|
|
77
|
+
}), ce = () => ({
|
|
78
78
|
strategy: "fixed",
|
|
79
79
|
maxAttempts: 120,
|
|
80
80
|
initialDelay: "30s",
|
|
81
81
|
maxDelay: "30s",
|
|
82
|
-
retryOn:
|
|
82
|
+
retryOn: O,
|
|
83
83
|
honourRetryAfter: !0,
|
|
84
84
|
jitter: "full"
|
|
85
|
-
}),
|
|
85
|
+
}), j = class extends te.Tag("@voltro/webhooks/WebhooksService")() {}, le = 32, ue = 32, M = () => _(le).toString("hex"), N = (e) => e.events ?? (e.event === void 0 ? [] : [e.event]), P = (e) => {
|
|
86
86
|
let t;
|
|
87
87
|
try {
|
|
88
88
|
t = new URL(e.url);
|
|
@@ -104,13 +104,26 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
104
104
|
reason: e.message
|
|
105
105
|
});
|
|
106
106
|
}
|
|
107
|
-
|
|
107
|
+
let n = N(e);
|
|
108
|
+
if (n.length === 0) throw new p({
|
|
108
109
|
field: "event",
|
|
109
110
|
reason: "event id required"
|
|
110
111
|
});
|
|
111
|
-
if (e.
|
|
112
|
+
if (e.event !== void 0 && e.events !== void 0) throw new p({
|
|
113
|
+
field: "event",
|
|
114
|
+
reason: "pass `event` OR `events`, not both — the row set would be ambiguous"
|
|
115
|
+
});
|
|
116
|
+
if (n.some((e) => e.length === 0)) throw new p({
|
|
117
|
+
field: "events",
|
|
118
|
+
reason: "event id required"
|
|
119
|
+
});
|
|
120
|
+
if (new Set(n).size !== n.length) throw new p({
|
|
121
|
+
field: "events",
|
|
122
|
+
reason: "duplicate event — one row per event, so a repeat would deliver twice"
|
|
123
|
+
});
|
|
124
|
+
if (e.secret !== void 0 && e.secret.length < ue) throw new p({
|
|
112
125
|
field: "secret",
|
|
113
|
-
reason: `secret must be at least ${
|
|
126
|
+
reason: `secret must be at least ${ue} chars`
|
|
114
127
|
});
|
|
115
128
|
if (e.retry !== void 0) {
|
|
116
129
|
if (e.retry.maxAttempts < 1) throw new p({
|
|
@@ -130,13 +143,13 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
130
143
|
field: "autoDisableAfter",
|
|
131
144
|
reason: `autoDisableAfter must be a positive integer, got ${e.autoDisableAfter}`
|
|
132
145
|
});
|
|
133
|
-
},
|
|
146
|
+
}, F = (e, t, n, r) => ({
|
|
134
147
|
id: v(),
|
|
135
|
-
event: e.event,
|
|
148
|
+
event: n ?? e.event ?? "",
|
|
136
149
|
url: e.url,
|
|
137
|
-
secret: e.secret ??
|
|
150
|
+
secret: r ?? e.secret ?? M(),
|
|
138
151
|
signing: e.signing ?? t?.defaultSigning ?? c(),
|
|
139
|
-
retry: e.retry ?? t?.defaultRetry ??
|
|
152
|
+
retry: e.retry ?? t?.defaultRetry ?? A(),
|
|
140
153
|
filter: e.filter ?? null,
|
|
141
154
|
scope: e.scope ?? null,
|
|
142
155
|
headers: e.headers ?? null,
|
|
@@ -149,14 +162,14 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
149
162
|
autoDisableReason: null,
|
|
150
163
|
payloadVersion: e.payloadVersion ?? t?.version ?? 1,
|
|
151
164
|
description: e.description ?? null
|
|
152
|
-
}),
|
|
165
|
+
}), de = (e) => e instanceof Date ? e.getTime() : typeof e == "string" ? Date.parse(e) : 0, I = (e) => e instanceof Date ? e.toISOString() : typeof e == "string" ? e : null, fe = (e) => {
|
|
153
166
|
if (typeof e != "string") return e;
|
|
154
167
|
try {
|
|
155
168
|
return JSON.parse(e);
|
|
156
169
|
} catch {
|
|
157
170
|
return e;
|
|
158
171
|
}
|
|
159
|
-
},
|
|
172
|
+
}, L = (e) => ({
|
|
160
173
|
id: String(e.id),
|
|
161
174
|
deliveryId: String(e.deliveryId),
|
|
162
175
|
targetId: String(e.targetId),
|
|
@@ -167,22 +180,22 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
167
180
|
responseStatus: e.responseStatus ?? null,
|
|
168
181
|
errorMessage: e.errorMessage ?? null,
|
|
169
182
|
latencyMs: e.latencyMs ?? null,
|
|
170
|
-
scheduledAt:
|
|
171
|
-
nextAttemptAt:
|
|
172
|
-
createdAt:
|
|
173
|
-
}),
|
|
183
|
+
scheduledAt: I(e.scheduledAt),
|
|
184
|
+
nextAttemptAt: I(e.nextAttemptAt),
|
|
185
|
+
createdAt: I(e.createdAt)
|
|
186
|
+
}), pe = (e, t) => e === t ? "current" : e < t ? "behind" : "ahead", R = (e, t) => {
|
|
174
187
|
if (e === null) return !0;
|
|
175
188
|
let n = { payload: t };
|
|
176
|
-
for (let [t, r] of Object.entries(e)) if (!
|
|
189
|
+
for (let [t, r] of Object.entries(e)) if (!he(me(n, t), r)) return !1;
|
|
177
190
|
return !0;
|
|
178
|
-
},
|
|
191
|
+
}, me = (e, t) => {
|
|
179
192
|
let n = t.split("."), r = e;
|
|
180
193
|
for (let e of n) {
|
|
181
194
|
if (typeof r != "object" || !r) return;
|
|
182
195
|
r = r[e];
|
|
183
196
|
}
|
|
184
197
|
return r;
|
|
185
|
-
},
|
|
198
|
+
}, he = (e, t) => {
|
|
186
199
|
if (typeof t == "object" && t && !Array.isArray(t)) {
|
|
187
200
|
let n = t;
|
|
188
201
|
if ("eq" in n) return e === n.eq;
|
|
@@ -193,37 +206,58 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
193
206
|
if ("in" in n && Array.isArray(n.in)) return n.in.includes(e);
|
|
194
207
|
}
|
|
195
208
|
return e === t;
|
|
196
|
-
},
|
|
197
|
-
let
|
|
198
|
-
|
|
209
|
+
}, z = (e, t) => e !== null && Object.entries(t).every(([t, n]) => e[t] === n), ge = async (e, t) => {
|
|
210
|
+
let n = (await e.store.query({
|
|
211
|
+
table: "_voltro_webhook_targets",
|
|
212
|
+
predicate: void 0,
|
|
213
|
+
order: [{
|
|
214
|
+
column: "createdAt",
|
|
215
|
+
direction: "asc"
|
|
216
|
+
}],
|
|
217
|
+
take: 1e3,
|
|
218
|
+
skip: void 0,
|
|
219
|
+
projection: void 0
|
|
220
|
+
})).filter((e) => z(e.scope, t));
|
|
221
|
+
if (n.length === 0) return;
|
|
222
|
+
let r = new Set(n.map((e) => e.secret));
|
|
223
|
+
if (r.size > 1) throw Error(`webhooks.subscribe: the scope ${JSON.stringify(t)} matches ${n.length} targets with ${r.size} different secrets, so it is not one endpoint. Pass \`secret\` explicitly, or narrow the scope.`);
|
|
224
|
+
return [...r][0];
|
|
225
|
+
}, _e = (e, t, n = {}) => {
|
|
226
|
+
let r = new Map((n.events ?? []).map((e) => [e.id, e])), i = {
|
|
199
227
|
subscribe: async (t) => {
|
|
200
|
-
|
|
201
|
-
let n =
|
|
202
|
-
|
|
203
|
-
...
|
|
228
|
+
P(t);
|
|
229
|
+
let n = N(t), i = t.secret === void 0 && t.scope !== void 0 ? await ge(e, t.scope) : void 0, a = t.secret ?? i ?? M(), o = n.map((e) => F(t, r.get(e), e, a));
|
|
230
|
+
for (let t of o) await e.store.insert("_voltro_webhook_targets", {
|
|
231
|
+
...t,
|
|
204
232
|
createdAt: /* @__PURE__ */ new Date(),
|
|
205
233
|
updatedAt: /* @__PURE__ */ new Date()
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
234
|
+
});
|
|
235
|
+
let s = o[0];
|
|
236
|
+
return {
|
|
237
|
+
id: s.id,
|
|
238
|
+
event: s.event,
|
|
239
|
+
url: s.url,
|
|
240
|
+
secret: s.secret,
|
|
241
|
+
signing: s.signing,
|
|
242
|
+
retry: s.retry,
|
|
243
|
+
...o.length > 1 ? { targets: o.map((e) => ({
|
|
244
|
+
id: e.id,
|
|
245
|
+
event: e.event
|
|
246
|
+
})) } : {}
|
|
213
247
|
};
|
|
214
248
|
},
|
|
215
|
-
emit: async (n, i) => {
|
|
216
|
-
let
|
|
217
|
-
if (
|
|
218
|
-
let e = b.decodeUnknownEither(
|
|
249
|
+
emit: async (n, i, a) => {
|
|
250
|
+
let o = typeof n == "object" && n.kind === "event" ? E(n) : void 0, s = typeof n == "string" ? r.get(n) : o ?? n, c = typeof n == "string" ? n : n.id ?? n.name;
|
|
251
|
+
if (s !== void 0) {
|
|
252
|
+
let e = b.decodeUnknownEither(s.payload)(i, { errors: "all" });
|
|
219
253
|
if (ne.isLeft(e)) throw new u({
|
|
220
|
-
event:
|
|
254
|
+
event: c,
|
|
221
255
|
issues: e.left.message
|
|
222
256
|
});
|
|
223
257
|
}
|
|
224
|
-
let
|
|
258
|
+
let l = v(), d = a?.tenantId, f = (await e.store.query({
|
|
225
259
|
table: "_voltro_webhook_targets",
|
|
226
|
-
predicate: S("event",
|
|
260
|
+
predicate: d == null ? S("event", c) : x(S("event", c), S("tenantId", d)),
|
|
227
261
|
order: [{
|
|
228
262
|
column: "createdAt",
|
|
229
263
|
direction: "asc"
|
|
@@ -231,16 +265,16 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
231
265
|
take: 1e3,
|
|
232
266
|
skip: void 0,
|
|
233
267
|
projection: void 0
|
|
234
|
-
})).filter((e) =>
|
|
235
|
-
for (let n of
|
|
268
|
+
})).filter((e) => R(e.filter, i)), p = [], m = JSON.stringify(i);
|
|
269
|
+
for (let n of f) {
|
|
236
270
|
let r = v();
|
|
237
271
|
n.active ? (await t({
|
|
238
272
|
deliveryId: r,
|
|
239
273
|
targetId: n.id,
|
|
240
|
-
event:
|
|
241
|
-
eventId:
|
|
242
|
-
payloadJson:
|
|
243
|
-
}),
|
|
274
|
+
event: c,
|
|
275
|
+
eventId: l,
|
|
276
|
+
payloadJson: m
|
|
277
|
+
}), p.push({
|
|
244
278
|
targetId: n.id,
|
|
245
279
|
deliveryId: r,
|
|
246
280
|
status: "dispatched"
|
|
@@ -249,23 +283,23 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
249
283
|
deliveryId: r,
|
|
250
284
|
targetId: n.id,
|
|
251
285
|
tenantId: n.tenantId ?? null,
|
|
252
|
-
event:
|
|
253
|
-
eventId:
|
|
286
|
+
event: c,
|
|
287
|
+
eventId: l,
|
|
254
288
|
attempt: 1,
|
|
255
289
|
status: "pending",
|
|
256
|
-
payload:
|
|
290
|
+
payload: m,
|
|
257
291
|
scheduledAt: /* @__PURE__ */ new Date(),
|
|
258
292
|
createdAt: /* @__PURE__ */ new Date(),
|
|
259
293
|
updatedAt: /* @__PURE__ */ new Date()
|
|
260
|
-
}),
|
|
294
|
+
}), p.push({
|
|
261
295
|
targetId: n.id,
|
|
262
296
|
deliveryId: r,
|
|
263
297
|
status: "queued"
|
|
264
298
|
}));
|
|
265
299
|
}
|
|
266
300
|
return {
|
|
267
|
-
eventId:
|
|
268
|
-
deliveries:
|
|
301
|
+
eventId: l,
|
|
302
|
+
deliveries: p
|
|
269
303
|
};
|
|
270
304
|
},
|
|
271
305
|
replay: async (n) => {
|
|
@@ -290,52 +324,68 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
290
324
|
attemptEpoch: Date.now()
|
|
291
325
|
});
|
|
292
326
|
},
|
|
327
|
+
resolveTargets: async (t) => {
|
|
328
|
+
if (typeof t == "string") return [t];
|
|
329
|
+
let n = (await e.store.query({
|
|
330
|
+
table: "_voltro_webhook_targets",
|
|
331
|
+
predicate: void 0,
|
|
332
|
+
order: void 0,
|
|
333
|
+
take: 1e3,
|
|
334
|
+
skip: void 0,
|
|
335
|
+
projection: void 0
|
|
336
|
+
})).filter((e) => z(e.scope, t.scope)).map((e) => e.id);
|
|
337
|
+
if (n.length === 0) throw Error(`webhooks: no target matches scope ${JSON.stringify(t.scope)} — the operation would have silently done nothing`);
|
|
338
|
+
return n;
|
|
339
|
+
},
|
|
293
340
|
pauseTarget: async (t) => {
|
|
294
|
-
await e.store.update("_voltro_webhook_targets",
|
|
341
|
+
for (let n of await i.resolveTargets(t)) await e.store.update("_voltro_webhook_targets", n, {
|
|
295
342
|
active: !1,
|
|
296
343
|
updatedAt: /* @__PURE__ */ new Date()
|
|
297
344
|
});
|
|
298
345
|
},
|
|
299
346
|
resumeTarget: async (n) => {
|
|
300
|
-
await
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
347
|
+
for (let r of await i.resolveTargets(n)) {
|
|
348
|
+
await e.store.update("_voltro_webhook_targets", r, {
|
|
349
|
+
active: !0,
|
|
350
|
+
consecutiveFailures: 0,
|
|
351
|
+
autoDisabledAt: null,
|
|
352
|
+
autoDisableReason: null,
|
|
353
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
354
|
+
});
|
|
355
|
+
let n = await e.store.query({
|
|
356
|
+
table: "_voltro_webhook_deliveries",
|
|
357
|
+
predicate: x(S("targetId", r), S("status", "pending")),
|
|
358
|
+
order: [{
|
|
359
|
+
column: "createdAt",
|
|
360
|
+
direction: "asc"
|
|
361
|
+
}],
|
|
362
|
+
take: 1e4,
|
|
363
|
+
skip: void 0,
|
|
364
|
+
projection: void 0
|
|
365
|
+
}), i = Date.now();
|
|
366
|
+
for (let e of n) await t({
|
|
367
|
+
deliveryId: e.deliveryId,
|
|
368
|
+
targetId: r,
|
|
369
|
+
event: e.event,
|
|
370
|
+
eventId: e.eventId ?? v(),
|
|
371
|
+
payloadJson: typeof e.payload == "string" ? e.payload : JSON.stringify(e.payload),
|
|
372
|
+
attemptEpoch: i
|
|
373
|
+
});
|
|
374
|
+
}
|
|
326
375
|
},
|
|
327
376
|
deleteTarget: async (t) => {
|
|
328
|
-
await e.store.delete("_voltro_webhook_targets",
|
|
377
|
+
for (let n of await i.resolveTargets(t)) await e.store.delete("_voltro_webhook_targets", n), await e.store.deleteMany("_voltro_webhook_deliveries", { where: x(S("targetId", n), S("status", "pending")) });
|
|
329
378
|
},
|
|
330
379
|
rotateSecret: async (t) => {
|
|
331
|
-
let n =
|
|
332
|
-
|
|
380
|
+
let n = M();
|
|
381
|
+
for (let r of await i.resolveTargets(t)) await e.store.update("_voltro_webhook_targets", r, {
|
|
333
382
|
secret: n,
|
|
334
383
|
updatedAt: /* @__PURE__ */ new Date()
|
|
335
|
-
})
|
|
384
|
+
});
|
|
385
|
+
return { secret: n };
|
|
336
386
|
},
|
|
337
387
|
updateTarget: async (t, n) => {
|
|
338
|
-
if (n.url !== void 0 &&
|
|
388
|
+
if (n.url !== void 0 && P({
|
|
339
389
|
event: "x",
|
|
340
390
|
url: n.url
|
|
341
391
|
}), n.rateLimitPerMinute !== void 0 && n.rateLimitPerMinute !== null && (!Number.isInteger(n.rateLimitPerMinute) || n.rateLimitPerMinute < 1)) throw Error("updateTarget: rateLimitPerMinute must be a positive integer or null");
|
|
@@ -350,7 +400,7 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
350
400
|
"autoDisableAfter",
|
|
351
401
|
"format"
|
|
352
402
|
]) n[e] !== void 0 && (r[e] = n[e]);
|
|
353
|
-
await e.store.update("_voltro_webhook_targets",
|
|
403
|
+
for (let n of await i.resolveTargets(t)) await e.store.update("_voltro_webhook_targets", n, r);
|
|
354
404
|
},
|
|
355
405
|
testTarget: async (n, r) => {
|
|
356
406
|
let i = (await e.store.query({
|
|
@@ -389,7 +439,7 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
389
439
|
}), { deliveryId: a };
|
|
390
440
|
},
|
|
391
441
|
listDeliveries: async (t) => {
|
|
392
|
-
let n = await e.store.query({
|
|
442
|
+
let n = t?.scope === void 0 ? void 0 : new Set(await i.resolveTargets({ scope: t.scope })), r = await e.store.query({
|
|
393
443
|
table: "_voltro_webhook_deliveries",
|
|
394
444
|
predicate: t?.targetId === void 0 ? void 0 : S("targetId", t.targetId),
|
|
395
445
|
order: [{
|
|
@@ -399,8 +449,8 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
399
449
|
take: Math.min((t?.limit ?? 200) * (t?.status !== void 0 || t?.since !== void 0 ? 10 : 1), 5e3),
|
|
400
450
|
skip: void 0,
|
|
401
451
|
projection: void 0
|
|
402
|
-
}),
|
|
403
|
-
return n.filter((e) => t?.status === void 0 || e.status === t.status).filter((e) =>
|
|
452
|
+
}), a = t?.since?.getTime();
|
|
453
|
+
return r.filter((e) => n === void 0 || n.has(String(e.targetId))).filter((e) => t?.status === void 0 || e.status === t.status).filter((e) => a === void 0 || de(e.createdAt) >= a).slice(0, t?.limit ?? 200).map(L);
|
|
404
454
|
},
|
|
405
455
|
getDelivery: async (t) => {
|
|
406
456
|
let n = (await e.store.query({
|
|
@@ -412,8 +462,8 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
412
462
|
projection: void 0
|
|
413
463
|
}))[0];
|
|
414
464
|
return n === void 0 ? null : {
|
|
415
|
-
...
|
|
416
|
-
payload:
|
|
465
|
+
...L(n),
|
|
466
|
+
payload: fe(n.payload),
|
|
417
467
|
responseBody: n.responseBody ?? null
|
|
418
468
|
};
|
|
419
469
|
},
|
|
@@ -452,15 +502,16 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
452
502
|
}));
|
|
453
503
|
}
|
|
454
504
|
};
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
505
|
+
return i;
|
|
506
|
+
}, ve = y.gen(function* () {
|
|
507
|
+
return yield* j;
|
|
508
|
+
}), ye = (e) => {
|
|
458
509
|
if (e.webhooks === void 0) throw Error("useWebhooks: `ctx.webhooks` is not set. Add @voltro/plugin-webhooks to your app.config.ts plugin list and declare at least one *.webhook.tsx file.");
|
|
459
510
|
return e.webhooks;
|
|
460
|
-
},
|
|
511
|
+
}, be = 1e4, B = class {
|
|
461
512
|
capacity;
|
|
462
513
|
cache = /* @__PURE__ */ new Map();
|
|
463
|
-
constructor(e =
|
|
514
|
+
constructor(e = be) {
|
|
464
515
|
this.capacity = e;
|
|
465
516
|
}
|
|
466
517
|
evictExpired() {
|
|
@@ -506,9 +557,9 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
506
557
|
status: e,
|
|
507
558
|
contentType: "application/json; charset=utf-8",
|
|
508
559
|
body: JSON.stringify(t)
|
|
509
|
-
}),
|
|
560
|
+
}), xe = (e, t) => {
|
|
510
561
|
let n = e.provider, r = e.signature ?? n?.signature ?? null, i = e.idempotency ?? n?.idempotency ?? null, o = e.bodyType ?? n?.bodyType ?? "json", s = t.idempotencyCache ?? H(), c = W(i?.ttl), l = t.log ?? ((e) => {
|
|
511
|
-
|
|
562
|
+
ie({ scope: `webhook:${e.webhookId}` }).debug("incoming webhook", {
|
|
512
563
|
status: e.status,
|
|
513
564
|
signatureOk: e.signatureOk,
|
|
514
565
|
idempotency: e.idempotency,
|
|
@@ -667,7 +718,7 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
667
718
|
}), n;
|
|
668
719
|
}
|
|
669
720
|
};
|
|
670
|
-
},
|
|
721
|
+
}, Se = (e) => `/webhooks/${e}`, K = "_voltro_webhook_rate_windows", q = 6e4, Ce = (e, t) => `${e}@${t}`, J = async (e, t, n) => (await e.query({
|
|
671
722
|
table: "_voltro_webhook_rate_windows",
|
|
672
723
|
predicate: x(S("scope", t), S("bucket", n)),
|
|
673
724
|
order: [],
|
|
@@ -680,7 +731,7 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
680
731
|
let i = await J(e, t.key, n);
|
|
681
732
|
if (i === null) {
|
|
682
733
|
await e.insertIgnore(K, {
|
|
683
|
-
id:
|
|
734
|
+
id: Ce(t.key, n),
|
|
684
735
|
scope: t.key,
|
|
685
736
|
bucket: n,
|
|
686
737
|
count: 0,
|
|
@@ -696,7 +747,7 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
696
747
|
}, { where: x(S("id", i.id), S("count", i.count)) }) === 1) return !0;
|
|
697
748
|
}
|
|
698
749
|
throw Error(`webhook rate window: contention on (${t.key}, ${n}) exceeded ${Y} attempts`);
|
|
699
|
-
},
|
|
750
|
+
}, we = async (e, t, n) => {
|
|
700
751
|
for (let r = 0; r < Y; r++) {
|
|
701
752
|
r > 0 && await new Promise((e) => setTimeout(e, r));
|
|
702
753
|
let i = await J(e, t, n);
|
|
@@ -706,7 +757,7 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
706
757
|
}, { where: x(S("id", i.id), S("count", i.count)) }) === 1) return;
|
|
707
758
|
}
|
|
708
759
|
throw Error(`webhook rate window: release contention on (${t}) exceeded ${Y} attempts`);
|
|
709
|
-
},
|
|
760
|
+
}, Te = async (e, t, n, r = q) => {
|
|
710
761
|
if (t.length === 0) return {
|
|
711
762
|
acquired: !0,
|
|
712
763
|
retryInMs: 0
|
|
@@ -717,7 +768,7 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
717
768
|
a.push(o);
|
|
718
769
|
continue;
|
|
719
770
|
}
|
|
720
|
-
for (let t of a) await
|
|
771
|
+
for (let t of a) await we(e, t.key, i);
|
|
721
772
|
return {
|
|
722
773
|
acquired: !1,
|
|
723
774
|
retryInMs: (i + 1) * r - n
|
|
@@ -727,7 +778,7 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
727
778
|
acquired: !0,
|
|
728
779
|
retryInMs: 0
|
|
729
780
|
};
|
|
730
|
-
},
|
|
781
|
+
}, Ee = (e, t) => {
|
|
731
782
|
if (e === "json") return {
|
|
732
783
|
contentType: "application/json",
|
|
733
784
|
bytes: Buffer.from(t, "utf8")
|
|
@@ -743,40 +794,40 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
743
794
|
}
|
|
744
795
|
return e === "form" ? {
|
|
745
796
|
contentType: "application/x-www-form-urlencoded",
|
|
746
|
-
bytes: Buffer.from(
|
|
797
|
+
bytes: Buffer.from(De(n), "utf8")
|
|
747
798
|
} : {
|
|
748
799
|
contentType: "application/xml",
|
|
749
|
-
bytes: Buffer.from(
|
|
800
|
+
bytes: Buffer.from(ke(n), "utf8")
|
|
750
801
|
};
|
|
751
|
-
},
|
|
802
|
+
}, De = (e) => {
|
|
752
803
|
if (typeof e != "object" || !e || Array.isArray(e)) throw new d({
|
|
753
804
|
format: "form",
|
|
754
|
-
reason: `form encoding requires a JSON object at the top level, got ${
|
|
805
|
+
reason: `form encoding requires a JSON object at the top level, got ${Me(e)}`
|
|
755
806
|
});
|
|
756
807
|
let t = new URLSearchParams();
|
|
757
|
-
for (let [n, r] of Object.entries(e))
|
|
808
|
+
for (let [n, r] of Object.entries(e)) Z(t, n, r);
|
|
758
809
|
return t.toString();
|
|
759
|
-
},
|
|
810
|
+
}, Z = (e, t, n) => {
|
|
760
811
|
if (n === null) {
|
|
761
812
|
e.append(t, "");
|
|
762
813
|
return;
|
|
763
814
|
}
|
|
764
815
|
if (Array.isArray(n)) {
|
|
765
|
-
n.forEach((n, r) =>
|
|
816
|
+
n.forEach((n, r) => Z(e, `${t}[${r}]`, n));
|
|
766
817
|
return;
|
|
767
818
|
}
|
|
768
819
|
if (typeof n == "object") {
|
|
769
|
-
for (let [r, i] of Object.entries(n))
|
|
820
|
+
for (let [r, i] of Object.entries(n)) Z(e, `${t}[${r}]`, i);
|
|
770
821
|
return;
|
|
771
822
|
}
|
|
772
823
|
e.append(t, String(n));
|
|
773
|
-
},
|
|
774
|
-
if (!
|
|
824
|
+
}, Oe = /^[A-Za-z_][A-Za-z0-9_.-]*$/, ke = (e) => `<?xml version="1.0" encoding="UTF-8"?>${Q("webhook", e)}`, Q = (e, t) => {
|
|
825
|
+
if (!Oe.test(e)) throw new d({
|
|
775
826
|
format: "xml",
|
|
776
827
|
reason: `object key ${JSON.stringify(e)} is not a valid XML element name`
|
|
777
828
|
});
|
|
778
|
-
return `<${e}>${
|
|
779
|
-
},
|
|
829
|
+
return `<${e}>${Ae(e, t)}</${e}>`;
|
|
830
|
+
}, Ae = (e, t) => t == null ? "" : Array.isArray(t) ? t.map((e) => Q("item", e)).join("") : typeof t == "object" ? Object.entries(t).map(([e, t]) => Q(e, t)).join("") : je(String(t)), je = (e) => e.replace(/[&<>"']/g, (e) => {
|
|
780
831
|
switch (e) {
|
|
781
832
|
case "&": return "&";
|
|
782
833
|
case "<": return "<";
|
|
@@ -784,17 +835,17 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
784
835
|
case "\"": return """;
|
|
785
836
|
default: return "'";
|
|
786
837
|
}
|
|
787
|
-
}),
|
|
838
|
+
}), Me = (e) => e === null ? "null" : Array.isArray(e) ? "array" : typeof e, Ne = "_voltro_webhook_targets", Pe = 32, Fe = async (e, t) => (await e.query({
|
|
788
839
|
table: "_voltro_webhook_targets",
|
|
789
840
|
predicate: S("id", t),
|
|
790
841
|
order: [],
|
|
791
842
|
take: 1,
|
|
792
843
|
skip: void 0,
|
|
793
844
|
projection: void 0
|
|
794
|
-
}))[0] ?? null,
|
|
795
|
-
for (let a = 0; a <
|
|
845
|
+
}))[0] ?? null, $ = async (e, t, n, r, i = /* @__PURE__ */ new Date()) => {
|
|
846
|
+
for (let a = 0; a < Pe; a++) {
|
|
796
847
|
a > 0 && await new Promise((e) => setTimeout(e, a));
|
|
797
|
-
let o = await
|
|
848
|
+
let o = await Fe(e, t);
|
|
798
849
|
if (o === null) return {
|
|
799
850
|
consecutiveFailures: 0,
|
|
800
851
|
autoDisabled: !1
|
|
@@ -819,24 +870,24 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
819
870
|
autoDisabled: u
|
|
820
871
|
};
|
|
821
872
|
}
|
|
822
|
-
throw Error(`webhook auto-disable: streak CAS contention on target ${t} exceeded ${
|
|
823
|
-
},
|
|
873
|
+
throw Error(`webhook auto-disable: streak CAS contention on target ${t} exceeded ${Pe} attempts`);
|
|
874
|
+
}, Ie = b.Struct({
|
|
824
875
|
status: b.Number,
|
|
825
876
|
body: b.String,
|
|
826
877
|
latencyMs: b.Number,
|
|
827
878
|
retryAfterSec: b.Union(b.Number, b.Undefined),
|
|
828
879
|
transportError: b.Union(b.String, b.Undefined),
|
|
829
880
|
encodeError: b.Union(b.String, b.Undefined)
|
|
830
|
-
}),
|
|
881
|
+
}), Le = b.Struct({
|
|
831
882
|
outcome: b.Literal("succeeded", "failed", "retry"),
|
|
832
883
|
delayMs: b.Union(b.Number, b.Undefined),
|
|
833
884
|
reason: b.Union(b.String, b.Undefined),
|
|
834
885
|
autoDisabled: b.Union(b.Boolean, b.Undefined),
|
|
835
886
|
consecutiveFailures: b.Union(b.Number, b.Undefined)
|
|
836
|
-
}),
|
|
887
|
+
}), Re = b.Struct({
|
|
837
888
|
acquired: b.Boolean,
|
|
838
889
|
retryInMs: b.Number
|
|
839
|
-
}),
|
|
890
|
+
}), ze = ae.make({
|
|
840
891
|
name: "voltro.deliverWebhook",
|
|
841
892
|
payload: {
|
|
842
893
|
deliveryId: b.String,
|
|
@@ -851,10 +902,10 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
851
902
|
attempts: b.Number
|
|
852
903
|
}),
|
|
853
904
|
idempotencyKey: ({ deliveryId: e, attemptEpoch: t }) => `voltro.deliverWebhook:${e}:${t ?? 0}`
|
|
854
|
-
}),
|
|
855
|
-
let { deliveryId: i, targetId: a, event: s, eventId: c, payloadJson: l } = n, u =
|
|
905
|
+
}), Be = (e, t = {}) => (n, r) => {
|
|
906
|
+
let { deliveryId: i, targetId: a, event: s, eventId: c, payloadJson: l } = n, u = ie({ scope: `webhook:${i}` });
|
|
856
907
|
return y.gen(function* () {
|
|
857
|
-
let n = yield*
|
|
908
|
+
let n = yield* w.make({
|
|
858
909
|
name: "fetch-target",
|
|
859
910
|
success: b.Union(b.Null, b.Struct({
|
|
860
911
|
id: b.String,
|
|
@@ -914,7 +965,7 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
914
965
|
...o
|
|
915
966
|
});
|
|
916
967
|
};
|
|
917
|
-
if (n.active !== !0) return yield*
|
|
968
|
+
if (n.active !== !0) return yield* w.make({
|
|
918
969
|
name: "record-paused-pending",
|
|
919
970
|
success: b.Void,
|
|
920
971
|
execute: y.tryPromise({
|
|
@@ -935,11 +986,11 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
935
986
|
for (let t = 1; t <= p.maxAttempts; t++) {
|
|
936
987
|
let f = `${i}:${t}`;
|
|
937
988
|
if (_.length > 0) for (let n = 0;; n++) {
|
|
938
|
-
let i = yield*
|
|
989
|
+
let i = yield* w.make({
|
|
939
990
|
name: `rate-acquire-${t}-${n}`,
|
|
940
|
-
success:
|
|
991
|
+
success: Re,
|
|
941
992
|
execute: y.tryPromise({
|
|
942
|
-
try: () =>
|
|
993
|
+
try: () => Te(e.store, _, Date.now(), ee),
|
|
943
994
|
catch: (e) => (u.debug("rate-acquire raw error", {
|
|
944
995
|
targetId: a,
|
|
945
996
|
event: s,
|
|
@@ -949,7 +1000,7 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
949
1000
|
}).pipe(y.orDie)
|
|
950
1001
|
});
|
|
951
1002
|
if (i.acquired) break;
|
|
952
|
-
yield*
|
|
1003
|
+
yield* w.make({
|
|
953
1004
|
name: `record-rate-deferral-${t}-${n}`,
|
|
954
1005
|
success: b.Void,
|
|
955
1006
|
execute: y.tryPromise({
|
|
@@ -959,12 +1010,12 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
959
1010
|
}),
|
|
960
1011
|
catch: (e) => /* @__PURE__ */ Error(`record-rate-deferral failed: ${e.message}`)
|
|
961
1012
|
}).pipe(y.orDie)
|
|
962
|
-
}), yield*
|
|
1013
|
+
}), yield* T.sleep({
|
|
963
1014
|
name: `rate-window-sleep-${t}-${n}`,
|
|
964
1015
|
duration: `${i.retryInMs} millis`
|
|
965
1016
|
});
|
|
966
1017
|
}
|
|
967
|
-
yield*
|
|
1018
|
+
yield* w.make({
|
|
968
1019
|
name: `record-attempt-start-${t}`,
|
|
969
1020
|
success: b.Void,
|
|
970
1021
|
execute: y.tryPromise({
|
|
@@ -976,13 +1027,13 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
976
1027
|
catch: (e) => /* @__PURE__ */ Error(`record-attempt-start failed: ${e.message}`)
|
|
977
1028
|
}).pipe(y.orDie)
|
|
978
1029
|
});
|
|
979
|
-
let g = yield*
|
|
1030
|
+
let g = yield* w.make({
|
|
980
1031
|
name: `sign-and-post-${t}`,
|
|
981
|
-
success:
|
|
1032
|
+
success: Ie,
|
|
982
1033
|
execute: y.promise(async () => {
|
|
983
1034
|
let e = Date.now(), r;
|
|
984
1035
|
try {
|
|
985
|
-
r =
|
|
1036
|
+
r = Ee(n.format, l);
|
|
986
1037
|
} catch (t) {
|
|
987
1038
|
if (t instanceof d) return {
|
|
988
1039
|
status: 0,
|
|
@@ -1030,12 +1081,12 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
1030
1081
|
};
|
|
1031
1082
|
}
|
|
1032
1083
|
})
|
|
1033
|
-
}), v = yield*
|
|
1084
|
+
}), v = yield* w.make({
|
|
1034
1085
|
name: `classify-and-record-${t}`,
|
|
1035
|
-
success:
|
|
1086
|
+
success: Le,
|
|
1036
1087
|
execute: y.tryPromise({
|
|
1037
1088
|
try: async () => {
|
|
1038
|
-
let r = g.status >= 200 && g.status < 300, i = g.transportError !== void 0, o = g.encodeError !== void 0, s = (t, n) =>
|
|
1089
|
+
let r = g.status >= 200 && g.status < 300, i = g.transportError !== void 0, o = g.encodeError !== void 0, s = (t, n) => $(e.store, a, t, n);
|
|
1039
1090
|
if (r) {
|
|
1040
1091
|
await e.store.update("_voltro_webhook_deliveries", f, {
|
|
1041
1092
|
status: "succeeded",
|
|
@@ -1053,7 +1104,7 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
1053
1104
|
consecutiveFailures: t.consecutiveFailures
|
|
1054
1105
|
};
|
|
1055
1106
|
}
|
|
1056
|
-
let c = o ? null :
|
|
1107
|
+
let c = o ? null : k(p, t, {
|
|
1057
1108
|
...i ? {} : { status: g.status },
|
|
1058
1109
|
...g.retryAfterSec === void 0 ? {} : { retryAfterSeconds: g.retryAfterSec }
|
|
1059
1110
|
});
|
|
@@ -1113,7 +1164,7 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
1113
1164
|
finalStatus: "failed",
|
|
1114
1165
|
attempts: t
|
|
1115
1166
|
};
|
|
1116
|
-
v.delayMs !== void 0 && (yield*
|
|
1167
|
+
v.delayMs !== void 0 && (yield* T.sleep({
|
|
1117
1168
|
name: `retry-sleep-${t}`,
|
|
1118
1169
|
duration: `${v.delayMs} millis`
|
|
1119
1170
|
}));
|
|
@@ -1125,4 +1176,4 @@ var ae = (e) => e.webhook !== void 0, D = (e) => {
|
|
|
1125
1176
|
});
|
|
1126
1177
|
};
|
|
1127
1178
|
//#endregion
|
|
1128
|
-
export { B as IdempotencyCache, q as RATE_WINDOW_MS, K as RATE_WINDOW_TABLE,
|
|
1179
|
+
export { B as IdempotencyCache, q as RATE_WINDOW_MS, K as RATE_WINDOW_TABLE, Ne as TARGETS_TABLE, l as WebhookDeliveryNotFound, u as WebhookPayloadInvalid, d as WebhookPayloadUnrepresentable, f as WebhookPayloadVersionInvalid, p as WebhookSubscribeInvalid, j as WebhooksService, m as _voltroWebhookDeliveriesTable, h as _voltroWebhookRateWindowsTable, ee as _voltroWebhookTargetsTable, Te as acquireRateSlots, E as asOutgoingEvent, Be as buildDeliverWebhookExecute, _e as buildWebhooksService, pe as compareVersions, X as consumeRateSlot, Se as defaultIncomingPath, c as defaultOutgoingSignature, A as defaultRetryPolicy, s as defineIncomingWebhook, t as defineWebhookProvider, ze as deliverWebhookWorkflow, Ee as encodePayload, N as eventsOf, ce as fastRetryPolicy, M as generateSecret, H as getIdempotencyCache, i as githubSignature, oe as hasWebhookAudience, r as isWebhookDescriptor, R as matchesFilter, xe as mountIncomingWebhook, k as nextRetry, D as parseDuration, W as parseTtl, $ as recordDeliveryOutcome, we as releaseRateSlot, F as resolveSubscribe, o as signPayload, n as slackSignature, e as stripeSignature, ye as useWebhooks, ve as useWebhooksEffect, P as validateSubscribe, a as verifySignature, g as webhookTables };
|