@voltro/plugin-webhooks 0.22.1 → 0.24.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 +427 -0
- package/dist/index.d.ts +177 -0
- package/dist/index.js +134 -134
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { a as e, c as t, i as n, l as r, n as i, o as a, r as o, s, t as c, u as l } from "./signing-BOUxHdAo.js";
|
|
2
2
|
import { WebhookDeliveryNotFound as u, WebhookPayloadInvalid as d, WebhookPayloadUnrepresentable as f, WebhookPayloadVersionInvalid as p, WebhookSubscribeInvalid as m } from "./errors.js";
|
|
3
|
-
import {
|
|
4
|
-
import { randomBytes as
|
|
5
|
-
import { Context as
|
|
6
|
-
import { and as
|
|
7
|
-
import { assertPublicUrl as
|
|
8
|
-
import { publishServerError as
|
|
9
|
-
import { createLogger as
|
|
10
|
-
import { Activity as E, DurableClock as D, Workflow as
|
|
3
|
+
import { _voltroWebhookDeliveriesTable as h, _voltroWebhookRateWindowsTable as g, _voltroWebhookTargetsTable as _, webhookTables as v } from "./mixin.js";
|
|
4
|
+
import { randomBytes as y, randomUUID as b } from "node:crypto";
|
|
5
|
+
import { Context as ee, Effect as x, Either as te, Schema as S } from "effect";
|
|
6
|
+
import { and as C, eq as w } from "@voltro/database";
|
|
7
|
+
import { assertPublicUrl as T } from "@voltro/integration-http";
|
|
8
|
+
import { publishServerError as ne } from "@voltro/protocol";
|
|
9
|
+
import { createLogger as re } from "@voltro/logger";
|
|
10
|
+
import { Activity as E, DurableClock as D, Workflow as ie } from "@effect/workflow";
|
|
11
11
|
//#region src/retry.ts
|
|
12
|
-
var
|
|
13
|
-
let t =
|
|
12
|
+
var ae = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
13
|
+
let t = ae.exec(e);
|
|
14
14
|
if (!t) throw Error(`invalid duration literal: ${e}`);
|
|
15
15
|
let n = Number(t[1]), r = t[2] ?? "s";
|
|
16
16
|
switch (r) {
|
|
@@ -64,7 +64,7 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
64
64
|
retryOn: k,
|
|
65
65
|
honourRetryAfter: !0,
|
|
66
66
|
jitter: "full"
|
|
67
|
-
}),
|
|
67
|
+
}), oe = () => ({
|
|
68
68
|
strategy: "fixed",
|
|
69
69
|
maxAttempts: 120,
|
|
70
70
|
initialDelay: "30s",
|
|
@@ -72,7 +72,7 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
72
72
|
retryOn: k,
|
|
73
73
|
honourRetryAfter: !0,
|
|
74
74
|
jitter: "full"
|
|
75
|
-
}), M = class extends
|
|
75
|
+
}), M = class extends ee.Tag("@voltro/webhooks/WebhooksService")() {}, se = 32, N = 32, P = () => y(se).toString("hex"), F = (e) => {
|
|
76
76
|
let t;
|
|
77
77
|
try {
|
|
78
78
|
t = new URL(e.url);
|
|
@@ -87,7 +87,7 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
87
87
|
reason: `url protocol must be http or https, got ${t.protocol}`
|
|
88
88
|
});
|
|
89
89
|
if (process.env.NODE_ENV === "production") try {
|
|
90
|
-
|
|
90
|
+
T(e.url);
|
|
91
91
|
} catch (e) {
|
|
92
92
|
throw new m({
|
|
93
93
|
field: "url",
|
|
@@ -121,7 +121,7 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
121
121
|
reason: `autoDisableAfter must be a positive integer, got ${e.autoDisableAfter}`
|
|
122
122
|
});
|
|
123
123
|
}, I = (e, t) => ({
|
|
124
|
-
id:
|
|
124
|
+
id: b(),
|
|
125
125
|
event: e.event,
|
|
126
126
|
url: e.url,
|
|
127
127
|
secret: e.secret ?? P(),
|
|
@@ -138,19 +138,19 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
138
138
|
autoDisableReason: null,
|
|
139
139
|
payloadVersion: e.payloadVersion ?? t?.version ?? 1,
|
|
140
140
|
description: e.description ?? null
|
|
141
|
-
}),
|
|
141
|
+
}), ce = (e, t) => e === t ? "current" : e < t ? "behind" : "ahead", L = (e, t) => {
|
|
142
142
|
if (e === null) return !0;
|
|
143
143
|
let n = { payload: t };
|
|
144
|
-
for (let [t, r] of Object.entries(e)) if (!
|
|
144
|
+
for (let [t, r] of Object.entries(e)) if (!ue(le(n, t), r)) return !1;
|
|
145
145
|
return !0;
|
|
146
|
-
},
|
|
146
|
+
}, le = (e, t) => {
|
|
147
147
|
let n = t.split("."), r = e;
|
|
148
148
|
for (let e of n) {
|
|
149
149
|
if (typeof r != "object" || !r) return;
|
|
150
150
|
r = r[e];
|
|
151
151
|
}
|
|
152
152
|
return r;
|
|
153
|
-
},
|
|
153
|
+
}, ue = (e, t) => {
|
|
154
154
|
if (typeof t == "object" && t && !Array.isArray(t)) {
|
|
155
155
|
let n = t;
|
|
156
156
|
if ("eq" in n) return e === n.eq;
|
|
@@ -161,7 +161,7 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
161
161
|
if ("in" in n && Array.isArray(n.in)) return n.in.includes(e);
|
|
162
162
|
}
|
|
163
163
|
return e === t;
|
|
164
|
-
},
|
|
164
|
+
}, de = (e, t, n = {}) => {
|
|
165
165
|
let r = new Map((n.events ?? []).map((e) => [e.id, e]));
|
|
166
166
|
return {
|
|
167
167
|
subscribe: async (t) => {
|
|
@@ -183,15 +183,15 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
183
183
|
emit: async (n, i) => {
|
|
184
184
|
let a = typeof n == "string" ? r.get(n) : n, o = typeof n == "string" ? n : n.id;
|
|
185
185
|
if (a !== void 0) {
|
|
186
|
-
let e =
|
|
187
|
-
if (
|
|
186
|
+
let e = S.decodeUnknownEither(a.payload)(i, { errors: "all" });
|
|
187
|
+
if (te.isLeft(e)) throw new d({
|
|
188
188
|
event: o,
|
|
189
189
|
issues: e.left.message
|
|
190
190
|
});
|
|
191
191
|
}
|
|
192
|
-
let s =
|
|
192
|
+
let s = b(), c = (await e.store.query({
|
|
193
193
|
table: "_voltro_webhook_targets",
|
|
194
|
-
predicate:
|
|
194
|
+
predicate: w("event", o),
|
|
195
195
|
order: [{
|
|
196
196
|
column: "createdAt",
|
|
197
197
|
direction: "asc"
|
|
@@ -201,7 +201,7 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
201
201
|
projection: void 0
|
|
202
202
|
})).filter((e) => L(e.filter, i)), l = [], u = JSON.stringify(i);
|
|
203
203
|
for (let n of c) {
|
|
204
|
-
let r =
|
|
204
|
+
let r = b();
|
|
205
205
|
n.active ? (await t({
|
|
206
206
|
deliveryId: r,
|
|
207
207
|
targetId: n.id,
|
|
@@ -239,7 +239,7 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
239
239
|
replay: async (n) => {
|
|
240
240
|
let r = (await e.store.query({
|
|
241
241
|
table: "_voltro_webhook_deliveries",
|
|
242
|
-
predicate:
|
|
242
|
+
predicate: w("deliveryId", n),
|
|
243
243
|
order: [{
|
|
244
244
|
column: "attempt",
|
|
245
245
|
direction: "asc"
|
|
@@ -253,7 +253,7 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
253
253
|
deliveryId: r.deliveryId,
|
|
254
254
|
targetId: r.targetId,
|
|
255
255
|
event: r.event,
|
|
256
|
-
eventId: r.eventId ??
|
|
256
|
+
eventId: r.eventId ?? b(),
|
|
257
257
|
payloadJson: typeof r.payload == "string" ? r.payload : JSON.stringify(r.payload),
|
|
258
258
|
attemptEpoch: Date.now()
|
|
259
259
|
});
|
|
@@ -274,7 +274,7 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
274
274
|
});
|
|
275
275
|
let r = await e.store.query({
|
|
276
276
|
table: "_voltro_webhook_deliveries",
|
|
277
|
-
predicate:
|
|
277
|
+
predicate: C(w("targetId", n), w("status", "pending")),
|
|
278
278
|
order: [{
|
|
279
279
|
column: "createdAt",
|
|
280
280
|
direction: "asc"
|
|
@@ -287,13 +287,13 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
287
287
|
deliveryId: e.deliveryId,
|
|
288
288
|
targetId: n,
|
|
289
289
|
event: e.event,
|
|
290
|
-
eventId: e.eventId ??
|
|
290
|
+
eventId: e.eventId ?? b(),
|
|
291
291
|
payloadJson: typeof e.payload == "string" ? e.payload : JSON.stringify(e.payload),
|
|
292
292
|
attemptEpoch: i
|
|
293
293
|
});
|
|
294
294
|
},
|
|
295
295
|
deleteTarget: async (t) => {
|
|
296
|
-
await e.store.delete("_voltro_webhook_targets", t), await e.store.deleteMany("_voltro_webhook_deliveries", { where:
|
|
296
|
+
await e.store.delete("_voltro_webhook_targets", t), await e.store.deleteMany("_voltro_webhook_deliveries", { where: C(w("targetId", t), w("status", "pending")) });
|
|
297
297
|
},
|
|
298
298
|
rotateSecret: async (t) => {
|
|
299
299
|
let n = P();
|
|
@@ -311,7 +311,7 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
311
311
|
},
|
|
312
312
|
listTargets: async (t) => (await e.store.query({
|
|
313
313
|
table: "_voltro_webhook_targets",
|
|
314
|
-
predicate: t === void 0 ? void 0 :
|
|
314
|
+
predicate: t === void 0 ? void 0 : w("event", t),
|
|
315
315
|
order: [{
|
|
316
316
|
column: "createdAt",
|
|
317
317
|
direction: "desc"
|
|
@@ -333,15 +333,15 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
333
333
|
autoDisableReason: e.autoDisableReason
|
|
334
334
|
}))
|
|
335
335
|
};
|
|
336
|
-
},
|
|
336
|
+
}, fe = x.gen(function* () {
|
|
337
337
|
return yield* M;
|
|
338
|
-
}),
|
|
338
|
+
}), pe = (e) => {
|
|
339
339
|
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.");
|
|
340
340
|
return e.webhooks;
|
|
341
|
-
},
|
|
341
|
+
}, me = 1e4, R = class {
|
|
342
342
|
capacity;
|
|
343
343
|
cache = /* @__PURE__ */ new Map();
|
|
344
|
-
constructor(e =
|
|
344
|
+
constructor(e = me) {
|
|
345
345
|
this.capacity = e;
|
|
346
346
|
}
|
|
347
347
|
evictExpired() {
|
|
@@ -387,9 +387,9 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
387
387
|
status: e,
|
|
388
388
|
contentType: "application/json; charset=utf-8",
|
|
389
389
|
body: JSON.stringify(t)
|
|
390
|
-
}),
|
|
390
|
+
}), he = (e, t) => {
|
|
391
391
|
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 ?? B(), c = H(i?.ttl), l = t.log ?? ((e) => {
|
|
392
|
-
|
|
392
|
+
re({ scope: `webhook:${e.webhookId}` }).debug("incoming webhook", {
|
|
393
393
|
status: e.status,
|
|
394
394
|
signatureOk: e.signatureOk,
|
|
395
395
|
idempotency: e.idempotency,
|
|
@@ -462,7 +462,7 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
462
462
|
}
|
|
463
463
|
let p;
|
|
464
464
|
try {
|
|
465
|
-
p =
|
|
465
|
+
p = S.decodeUnknownSync(e.payload)(f);
|
|
466
466
|
} catch (t) {
|
|
467
467
|
let n = U(422, {
|
|
468
468
|
error: "schema validation failed",
|
|
@@ -540,7 +540,7 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
540
540
|
idempotency: h,
|
|
541
541
|
durationMs: Date.now() - u,
|
|
542
542
|
errorMessage: t.message
|
|
543
|
-
}),
|
|
543
|
+
}), ne({
|
|
544
544
|
error: t,
|
|
545
545
|
source: "webhook",
|
|
546
546
|
name: e.id,
|
|
@@ -548,9 +548,9 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
548
548
|
}), n;
|
|
549
549
|
}
|
|
550
550
|
};
|
|
551
|
-
},
|
|
551
|
+
}, ge = (e) => `/webhooks/${e}`, W = "_voltro_webhook_rate_windows", G = 6e4, _e = (e, t) => `${e}@${t}`, K = async (e, t, n) => (await e.query({
|
|
552
552
|
table: "_voltro_webhook_rate_windows",
|
|
553
|
-
predicate:
|
|
553
|
+
predicate: C(w("scope", t), w("bucket", n)),
|
|
554
554
|
order: [],
|
|
555
555
|
take: 1,
|
|
556
556
|
skip: void 0,
|
|
@@ -561,7 +561,7 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
561
561
|
let i = await K(e, t.key, n);
|
|
562
562
|
if (i === null) {
|
|
563
563
|
await e.insertIgnore(W, {
|
|
564
|
-
id:
|
|
564
|
+
id: _e(t.key, n),
|
|
565
565
|
scope: t.key,
|
|
566
566
|
bucket: n,
|
|
567
567
|
count: 0,
|
|
@@ -574,7 +574,7 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
574
574
|
if (await e.updateMany("_voltro_webhook_rate_windows", {
|
|
575
575
|
count: i.count + 1,
|
|
576
576
|
updatedAt: /* @__PURE__ */ new Date()
|
|
577
|
-
}, { where:
|
|
577
|
+
}, { where: C(w("id", i.id), w("count", i.count)) }) === 1) return !0;
|
|
578
578
|
}
|
|
579
579
|
throw Error(`webhook rate window: contention on (${t.key}, ${n}) exceeded ${q} attempts`);
|
|
580
580
|
}, Y = async (e, t, n) => {
|
|
@@ -584,7 +584,7 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
584
584
|
if (i === null || i.count <= 0 || await e.updateMany("_voltro_webhook_rate_windows", {
|
|
585
585
|
count: i.count - 1,
|
|
586
586
|
updatedAt: /* @__PURE__ */ new Date()
|
|
587
|
-
}, { where:
|
|
587
|
+
}, { where: C(w("id", i.id), w("count", i.count)) }) === 1) return;
|
|
588
588
|
}
|
|
589
589
|
throw Error(`webhook rate window: release contention on (${t}) exceeded ${q} attempts`);
|
|
590
590
|
}, X = async (e, t, n, r = G) => {
|
|
@@ -624,15 +624,15 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
624
624
|
}
|
|
625
625
|
return e === "form" ? {
|
|
626
626
|
contentType: "application/x-www-form-urlencoded",
|
|
627
|
-
bytes: Buffer.from(
|
|
627
|
+
bytes: Buffer.from(ve(n), "utf8")
|
|
628
628
|
} : {
|
|
629
629
|
contentType: "application/xml",
|
|
630
|
-
bytes: Buffer.from(
|
|
630
|
+
bytes: Buffer.from(be(n), "utf8")
|
|
631
631
|
};
|
|
632
|
-
},
|
|
632
|
+
}, ve = (e) => {
|
|
633
633
|
if (typeof e != "object" || !e || Array.isArray(e)) throw new f({
|
|
634
634
|
format: "form",
|
|
635
|
-
reason: `form encoding requires a JSON object at the top level, got ${
|
|
635
|
+
reason: `form encoding requires a JSON object at the top level, got ${Ce(e)}`
|
|
636
636
|
});
|
|
637
637
|
let t = new URLSearchParams();
|
|
638
638
|
for (let [n, r] of Object.entries(e)) Q(t, n, r);
|
|
@@ -651,13 +651,13 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
651
651
|
return;
|
|
652
652
|
}
|
|
653
653
|
e.append(t, String(n));
|
|
654
|
-
},
|
|
655
|
-
if (!
|
|
654
|
+
}, ye = /^[A-Za-z_][A-Za-z0-9_.-]*$/, be = (e) => `<?xml version="1.0" encoding="UTF-8"?>${$("webhook", e)}`, $ = (e, t) => {
|
|
655
|
+
if (!ye.test(e)) throw new f({
|
|
656
656
|
format: "xml",
|
|
657
657
|
reason: `object key ${JSON.stringify(e)} is not a valid XML element name`
|
|
658
658
|
});
|
|
659
|
-
return `<${e}>${
|
|
660
|
-
},
|
|
659
|
+
return `<${e}>${xe(e, t)}</${e}>`;
|
|
660
|
+
}, xe = (e, t) => t == null ? "" : Array.isArray(t) ? t.map((e) => $("item", e)).join("") : typeof t == "object" ? Object.entries(t).map(([e, t]) => $(e, t)).join("") : Se(String(t)), Se = (e) => e.replace(/[&<>"']/g, (e) => {
|
|
661
661
|
switch (e) {
|
|
662
662
|
case "&": return "&";
|
|
663
663
|
case "<": return "<";
|
|
@@ -665,17 +665,17 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
665
665
|
case "\"": return """;
|
|
666
666
|
default: return "'";
|
|
667
667
|
}
|
|
668
|
-
}),
|
|
668
|
+
}), Ce = (e) => e === null ? "null" : Array.isArray(e) ? "array" : typeof e, we = "_voltro_webhook_targets", Te = 32, Ee = async (e, t) => (await e.query({
|
|
669
669
|
table: "_voltro_webhook_targets",
|
|
670
|
-
predicate:
|
|
670
|
+
predicate: w("id", t),
|
|
671
671
|
order: [],
|
|
672
672
|
take: 1,
|
|
673
673
|
skip: void 0,
|
|
674
674
|
projection: void 0
|
|
675
|
-
}))[0] ?? null,
|
|
676
|
-
for (let a = 0; a <
|
|
675
|
+
}))[0] ?? null, De = async (e, t, n, r, i = /* @__PURE__ */ new Date()) => {
|
|
676
|
+
for (let a = 0; a < Te; a++) {
|
|
677
677
|
a > 0 && await new Promise((e) => setTimeout(e, a));
|
|
678
|
-
let o = await
|
|
678
|
+
let o = await Ee(e, t);
|
|
679
679
|
if (o === null) return {
|
|
680
680
|
consecutiveFailures: 0,
|
|
681
681
|
autoDisabled: !1
|
|
@@ -685,7 +685,7 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
685
685
|
if (s === 0 || await e.updateMany("_voltro_webhook_targets", {
|
|
686
686
|
consecutiveFailures: 0,
|
|
687
687
|
updatedAt: i
|
|
688
|
-
}, { where:
|
|
688
|
+
}, { where: C(w("id", t), w("consecutiveFailures", s)) }) === 1) return {
|
|
689
689
|
consecutiveFailures: 0,
|
|
690
690
|
autoDisabled: !1
|
|
691
691
|
};
|
|
@@ -695,68 +695,68 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
695
695
|
consecutiveFailures: c,
|
|
696
696
|
updatedAt: i
|
|
697
697
|
};
|
|
698
|
-
if (u && (d.active = !1, d.autoDisabledAt = i, d.autoDisableReason = r.slice(0, 500)), await e.updateMany("_voltro_webhook_targets", d, { where:
|
|
698
|
+
if (u && (d.active = !1, d.autoDisabledAt = i, d.autoDisableReason = r.slice(0, 500)), await e.updateMany("_voltro_webhook_targets", d, { where: C(w("id", t), w("consecutiveFailures", s)) }) === 1) return {
|
|
699
699
|
consecutiveFailures: c,
|
|
700
700
|
autoDisabled: u
|
|
701
701
|
};
|
|
702
702
|
}
|
|
703
|
-
throw Error(`webhook auto-disable: streak CAS contention on target ${t} exceeded ${
|
|
704
|
-
},
|
|
705
|
-
status:
|
|
706
|
-
body:
|
|
707
|
-
latencyMs:
|
|
708
|
-
retryAfterSec:
|
|
709
|
-
transportError:
|
|
710
|
-
encodeError:
|
|
711
|
-
}),
|
|
712
|
-
outcome:
|
|
713
|
-
delayMs:
|
|
714
|
-
reason:
|
|
715
|
-
autoDisabled:
|
|
716
|
-
consecutiveFailures:
|
|
717
|
-
}),
|
|
718
|
-
acquired:
|
|
719
|
-
retryInMs:
|
|
720
|
-
}),
|
|
703
|
+
throw Error(`webhook auto-disable: streak CAS contention on target ${t} exceeded ${Te} attempts`);
|
|
704
|
+
}, Oe = S.Struct({
|
|
705
|
+
status: S.Number,
|
|
706
|
+
body: S.String,
|
|
707
|
+
latencyMs: S.Number,
|
|
708
|
+
retryAfterSec: S.Union(S.Number, S.Undefined),
|
|
709
|
+
transportError: S.Union(S.String, S.Undefined),
|
|
710
|
+
encodeError: S.Union(S.String, S.Undefined)
|
|
711
|
+
}), ke = S.Struct({
|
|
712
|
+
outcome: S.Literal("succeeded", "failed", "retry"),
|
|
713
|
+
delayMs: S.Union(S.Number, S.Undefined),
|
|
714
|
+
reason: S.Union(S.String, S.Undefined),
|
|
715
|
+
autoDisabled: S.Union(S.Boolean, S.Undefined),
|
|
716
|
+
consecutiveFailures: S.Union(S.Number, S.Undefined)
|
|
717
|
+
}), Ae = S.Struct({
|
|
718
|
+
acquired: S.Boolean,
|
|
719
|
+
retryInMs: S.Number
|
|
720
|
+
}), je = ie.make({
|
|
721
721
|
name: "voltro.deliverWebhook",
|
|
722
722
|
payload: {
|
|
723
|
-
deliveryId:
|
|
724
|
-
targetId:
|
|
725
|
-
event:
|
|
726
|
-
eventId:
|
|
727
|
-
payloadJson:
|
|
728
|
-
attemptEpoch:
|
|
723
|
+
deliveryId: S.String,
|
|
724
|
+
targetId: S.String,
|
|
725
|
+
event: S.String,
|
|
726
|
+
eventId: S.String,
|
|
727
|
+
payloadJson: S.String,
|
|
728
|
+
attemptEpoch: S.optionalWith(S.Number, { default: () => 0 })
|
|
729
729
|
},
|
|
730
|
-
success:
|
|
731
|
-
finalStatus:
|
|
732
|
-
attempts:
|
|
730
|
+
success: S.Struct({
|
|
731
|
+
finalStatus: S.Literal("succeeded", "failed", "deferred"),
|
|
732
|
+
attempts: S.Number
|
|
733
733
|
}),
|
|
734
734
|
idempotencyKey: ({ deliveryId: e, attemptEpoch: t }) => `voltro.deliverWebhook:${e}:${t ?? 0}`
|
|
735
|
-
}),
|
|
736
|
-
let { deliveryId: i, targetId: a, event: s, eventId: c, payloadJson: l } = n, u =
|
|
737
|
-
return
|
|
735
|
+
}), Me = (e, t = {}) => (n, r) => {
|
|
736
|
+
let { deliveryId: i, targetId: a, event: s, eventId: c, payloadJson: l } = n, u = re({ scope: `webhook:${i}` });
|
|
737
|
+
return x.gen(function* () {
|
|
738
738
|
let n = yield* E.make({
|
|
739
739
|
name: "fetch-target",
|
|
740
|
-
success:
|
|
741
|
-
id:
|
|
742
|
-
event:
|
|
743
|
-
url:
|
|
744
|
-
secret:
|
|
745
|
-
signing:
|
|
746
|
-
retry:
|
|
747
|
-
headers:
|
|
748
|
-
key:
|
|
749
|
-
value:
|
|
750
|
-
}),
|
|
751
|
-
rateLimitPerMinute:
|
|
752
|
-
active:
|
|
753
|
-
format:
|
|
754
|
-
tenantId:
|
|
740
|
+
success: S.Union(S.Null, S.Struct({
|
|
741
|
+
id: S.String,
|
|
742
|
+
event: S.String,
|
|
743
|
+
url: S.String,
|
|
744
|
+
secret: S.String,
|
|
745
|
+
signing: S.Any,
|
|
746
|
+
retry: S.Any,
|
|
747
|
+
headers: S.Union(S.Record({
|
|
748
|
+
key: S.String,
|
|
749
|
+
value: S.String
|
|
750
|
+
}), S.Null),
|
|
751
|
+
rateLimitPerMinute: S.Union(S.Number, S.Null),
|
|
752
|
+
active: S.Boolean,
|
|
753
|
+
format: S.Literal("json", "form", "xml"),
|
|
754
|
+
tenantId: S.Union(S.String, S.Null)
|
|
755
755
|
})),
|
|
756
|
-
execute:
|
|
756
|
+
execute: x.tryPromise({
|
|
757
757
|
try: async () => (await e.store.query({
|
|
758
758
|
table: "_voltro_webhook_targets",
|
|
759
|
-
predicate:
|
|
759
|
+
predicate: w("id", a),
|
|
760
760
|
order: [{
|
|
761
761
|
column: "createdAt",
|
|
762
762
|
direction: "asc"
|
|
@@ -770,7 +770,7 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
770
770
|
event: s,
|
|
771
771
|
cause: String(e)
|
|
772
772
|
}), /* @__PURE__ */ Error(`fetch-target failed: ${e.message}`))
|
|
773
|
-
}).pipe(
|
|
773
|
+
}).pipe(x.orDie)
|
|
774
774
|
});
|
|
775
775
|
if (n === null) return {
|
|
776
776
|
finalStatus: "failed",
|
|
@@ -797,11 +797,11 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
797
797
|
};
|
|
798
798
|
if (n.active !== !0) return yield* E.make({
|
|
799
799
|
name: "record-paused-pending",
|
|
800
|
-
success:
|
|
801
|
-
execute:
|
|
800
|
+
success: S.Void,
|
|
801
|
+
execute: x.tryPromise({
|
|
802
802
|
try: () => r(`${i}:1`, 1, { status: "pending" }),
|
|
803
803
|
catch: (e) => /* @__PURE__ */ Error(`record-paused-pending failed: ${e.message}`)
|
|
804
|
-
}).pipe(
|
|
804
|
+
}).pipe(x.orDie)
|
|
805
805
|
}), {
|
|
806
806
|
finalStatus: "deferred",
|
|
807
807
|
attempts: 0
|
|
@@ -818,8 +818,8 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
818
818
|
if (v.length > 0) for (let n = 0;; n++) {
|
|
819
819
|
let i = yield* E.make({
|
|
820
820
|
name: `rate-acquire-${t}-${n}`,
|
|
821
|
-
success:
|
|
822
|
-
execute:
|
|
821
|
+
success: Ae,
|
|
822
|
+
execute: x.tryPromise({
|
|
823
823
|
try: () => X(e.store, v, Date.now(), g),
|
|
824
824
|
catch: (e) => (u.debug("rate-acquire raw error", {
|
|
825
825
|
targetId: a,
|
|
@@ -827,19 +827,19 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
827
827
|
attempt: t,
|
|
828
828
|
cause: String(e)
|
|
829
829
|
}), /* @__PURE__ */ Error(`rate-acquire failed: ${e.message}`))
|
|
830
|
-
}).pipe(
|
|
830
|
+
}).pipe(x.orDie)
|
|
831
831
|
});
|
|
832
832
|
if (i.acquired) break;
|
|
833
833
|
yield* E.make({
|
|
834
834
|
name: `record-rate-deferral-${t}-${n}`,
|
|
835
|
-
success:
|
|
836
|
-
execute:
|
|
835
|
+
success: S.Void,
|
|
836
|
+
execute: x.tryPromise({
|
|
837
837
|
try: () => r(d, t, {
|
|
838
838
|
status: "pending",
|
|
839
839
|
nextAttemptAt: new Date(Date.now() + i.retryInMs)
|
|
840
840
|
}),
|
|
841
841
|
catch: (e) => /* @__PURE__ */ Error(`record-rate-deferral failed: ${e.message}`)
|
|
842
|
-
}).pipe(
|
|
842
|
+
}).pipe(x.orDie)
|
|
843
843
|
}), yield* D.sleep({
|
|
844
844
|
name: `rate-window-sleep-${t}-${n}`,
|
|
845
845
|
duration: `${i.retryInMs} millis`
|
|
@@ -847,20 +847,20 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
847
847
|
}
|
|
848
848
|
yield* E.make({
|
|
849
849
|
name: `record-attempt-start-${t}`,
|
|
850
|
-
success:
|
|
851
|
-
execute:
|
|
850
|
+
success: S.Void,
|
|
851
|
+
execute: x.tryPromise({
|
|
852
852
|
try: () => r(d, t, {
|
|
853
853
|
status: "inFlight",
|
|
854
854
|
nextAttemptAt: null,
|
|
855
855
|
scheduledAt: /* @__PURE__ */ new Date()
|
|
856
856
|
}),
|
|
857
857
|
catch: (e) => /* @__PURE__ */ Error(`record-attempt-start failed: ${e.message}`)
|
|
858
|
-
}).pipe(
|
|
858
|
+
}).pipe(x.orDie)
|
|
859
859
|
});
|
|
860
860
|
let _ = yield* E.make({
|
|
861
861
|
name: `sign-and-post-${t}`,
|
|
862
|
-
success:
|
|
863
|
-
execute:
|
|
862
|
+
success: Oe,
|
|
863
|
+
execute: x.promise(async () => {
|
|
864
864
|
let e = Date.now(), r;
|
|
865
865
|
try {
|
|
866
866
|
r = Z(n.format, l);
|
|
@@ -876,7 +876,7 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
876
876
|
throw t;
|
|
877
877
|
}
|
|
878
878
|
let a = Buffer.from(r.bytes), { headerName: u, headerValue: d } = o(m, a, n.secret);
|
|
879
|
-
process.env.NODE_ENV === "production" &&
|
|
879
|
+
process.env.NODE_ENV === "production" && T(n.url);
|
|
880
880
|
try {
|
|
881
881
|
let o = await fetch(n.url, {
|
|
882
882
|
method: "POST",
|
|
@@ -911,12 +911,12 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
911
911
|
};
|
|
912
912
|
}
|
|
913
913
|
})
|
|
914
|
-
}),
|
|
914
|
+
}), y = yield* E.make({
|
|
915
915
|
name: `classify-and-record-${t}`,
|
|
916
|
-
success:
|
|
917
|
-
execute:
|
|
916
|
+
success: ke,
|
|
917
|
+
execute: x.tryPromise({
|
|
918
918
|
try: async () => {
|
|
919
|
-
let r = _.status >= 200 && _.status < 300, i = _.transportError !== void 0, o = _.encodeError !== void 0, s = (t, n) =>
|
|
919
|
+
let r = _.status >= 200 && _.status < 300, i = _.transportError !== void 0, o = _.encodeError !== void 0, s = (t, n) => De(e.store, a, t, n);
|
|
920
920
|
if (r) {
|
|
921
921
|
await e.store.update("_voltro_webhook_deliveries", d, {
|
|
922
922
|
status: "succeeded",
|
|
@@ -979,24 +979,24 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
979
979
|
attempt: t,
|
|
980
980
|
cause: String(e)
|
|
981
981
|
}), /* @__PURE__ */ Error(`classify-and-record failed: ${e.message}`))
|
|
982
|
-
}).pipe(
|
|
982
|
+
}).pipe(x.orDie)
|
|
983
983
|
});
|
|
984
|
-
if (
|
|
984
|
+
if (y.autoDisabled === !0 && u.warn("target auto-disabled after consecutive failures", {
|
|
985
985
|
targetId: a,
|
|
986
986
|
event: s,
|
|
987
|
-
consecutiveFailures:
|
|
988
|
-
reason:
|
|
989
|
-
}),
|
|
987
|
+
consecutiveFailures: y.consecutiveFailures ?? null,
|
|
988
|
+
reason: y.reason ?? null
|
|
989
|
+
}), y.outcome === "succeeded") return {
|
|
990
990
|
finalStatus: "succeeded",
|
|
991
991
|
attempts: t
|
|
992
992
|
};
|
|
993
|
-
if (
|
|
993
|
+
if (y.outcome === "failed") return {
|
|
994
994
|
finalStatus: "failed",
|
|
995
995
|
attempts: t
|
|
996
996
|
};
|
|
997
|
-
|
|
997
|
+
y.delayMs !== void 0 && (yield* D.sleep({
|
|
998
998
|
name: `retry-sleep-${t}`,
|
|
999
|
-
duration: `${
|
|
999
|
+
duration: `${y.delayMs} millis`
|
|
1000
1000
|
}));
|
|
1001
1001
|
}
|
|
1002
1002
|
return {
|
|
@@ -1006,4 +1006,4 @@ var ne = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, O = (e) => {
|
|
|
1006
1006
|
});
|
|
1007
1007
|
};
|
|
1008
1008
|
//#endregion
|
|
1009
|
-
export { R as IdempotencyCache, G as RATE_WINDOW_MS, W as RATE_WINDOW_TABLE,
|
|
1009
|
+
export { R as IdempotencyCache, G as RATE_WINDOW_MS, W as RATE_WINDOW_TABLE, we as TARGETS_TABLE, u as WebhookDeliveryNotFound, d as WebhookPayloadInvalid, f as WebhookPayloadUnrepresentable, p as WebhookPayloadVersionInvalid, m as WebhookSubscribeInvalid, M as WebhooksService, h as _voltroWebhookDeliveriesTable, g as _voltroWebhookRateWindowsTable, _ as _voltroWebhookTargetsTable, X as acquireRateSlots, Me as buildDeliverWebhookExecute, de as buildWebhooksService, ce as compareVersions, J as consumeRateSlot, ge as defaultIncomingPath, c as defaultOutgoingSignature, j as defaultRetryPolicy, s as defineIncomingWebhook, t as defineOutgoingEvent, r as defineWebhookProvider, je as deliverWebhookWorkflow, Z as encodePayload, oe as fastRetryPolicy, P as generateSecret, B as getIdempotencyCache, i as githubSignature, l as isWebhookDescriptor, L as matchesFilter, he as mountIncomingWebhook, A as nextRetry, O as parseDuration, H as parseTtl, De as recordDeliveryOutcome, Y as releaseRateSlot, I as resolveSubscribe, o as signPayload, n as slackSignature, e as stripeSignature, pe as useWebhooks, fe as useWebhooksEffect, F as validateSubscribe, a as verifySignature, v as webhookTables };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/plugin-webhooks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"description": "Webhooks plugin — first-class outgoing (multi-target, durable-workflow delivery, HMAC signing, retry policies, rate limits) + incoming (signature verification, idempotency, provider templates for Stripe / GitHub / Slack / generic).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"voltro",
|
|
@@ -48,12 +48,12 @@
|
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"@effect/workflow": "^0.19.0",
|
|
51
|
-
"@voltro/database": "0.
|
|
52
|
-
"@voltro/integration-http": "0.
|
|
53
|
-
"@voltro/logger": "0.
|
|
54
|
-
"@voltro/plugin-multitenancy": "0.
|
|
55
|
-
"@voltro/protocol": "0.
|
|
56
|
-
"@voltro/runtime": "0.
|
|
51
|
+
"@voltro/database": "0.24.0",
|
|
52
|
+
"@voltro/integration-http": "0.24.0",
|
|
53
|
+
"@voltro/logger": "0.24.0",
|
|
54
|
+
"@voltro/plugin-multitenancy": "0.24.0",
|
|
55
|
+
"@voltro/protocol": "0.24.0",
|
|
56
|
+
"@voltro/runtime": "0.24.0"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"effect": "^3.22.0"
|