@voltro/plugin-webhooks 0.29.0 → 0.30.1
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 +448 -0
- package/dist/index.d.ts +170 -16
- package/dist/index.js +148 -109
- package/dist/mixin.d.ts +79 -2
- package/dist/mixin.js +11 -3
- package/dist/providers/index.d.ts +2 -6
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -1,15 +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 } from "./signing-DUG4JWwk.js";
|
|
2
2
|
import { WebhookDeliveryNotFound as l, WebhookPayloadInvalid as u, WebhookPayloadUnrepresentable as d, WebhookPayloadVersionInvalid as f, WebhookSubscribeInvalid as p } from "./errors.js";
|
|
3
|
-
import { _voltroWebhookDeliveriesTable as m,
|
|
3
|
+
import { _voltroWebhookDeliveriesTable as m, _voltroWebhookEventStatsTable as h, _voltroWebhookRateWindowsTable as ee, _voltroWebhookTargetsTable as g, webhookTables as _ } from "./mixin.js";
|
|
4
4
|
import { randomBytes as v, randomUUID as y } from "node:crypto";
|
|
5
|
-
import { Context as
|
|
5
|
+
import { Context as te, Effect as b, Either as ne, Schema as x } from "effect";
|
|
6
6
|
import { and as S, eq as C } from "@voltro/database";
|
|
7
7
|
import { assertPublicUrl as w } from "@voltro/integration-http";
|
|
8
|
-
import { publishServerError as
|
|
8
|
+
import { publishServerError as re } from "@voltro/protocol";
|
|
9
9
|
import { createLogger as T } from "@voltro/logger";
|
|
10
|
-
import { Activity as E, DurableClock as D, Workflow as
|
|
10
|
+
import { Activity as E, DurableClock as D, Workflow as ie } from "@effect/workflow";
|
|
11
|
+
import { defineOutboxHandler as ae } from "@voltro/runtime";
|
|
11
12
|
//#region src/eventAudience.ts
|
|
12
|
-
var
|
|
13
|
+
var oe = (e) => e.webhook !== void 0, O = (e) => {
|
|
13
14
|
let t = e.webhook;
|
|
14
15
|
if (t !== void 0) return {
|
|
15
16
|
_tag: "outgoingEvent",
|
|
@@ -19,8 +20,8 @@ var ie = (e) => e.webhook !== void 0, O = (e) => {
|
|
|
19
20
|
...t.version === void 0 ? {} : { version: t.version },
|
|
20
21
|
...t.rateLimit === void 0 ? {} : { globalRateLimit: t.rateLimit }
|
|
21
22
|
};
|
|
22
|
-
},
|
|
23
|
-
let t =
|
|
23
|
+
}, se = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, k = (e) => {
|
|
24
|
+
let t = se.exec(e);
|
|
24
25
|
if (!t) throw Error(`invalid duration literal: ${e}`);
|
|
25
26
|
let n = Number(t[1]), r = t[2] ?? "s";
|
|
26
27
|
switch (r) {
|
|
@@ -39,7 +40,7 @@ var ie = (e) => e.webhook !== void 0, O = (e) => {
|
|
|
39
40
|
502,
|
|
40
41
|
503,
|
|
41
42
|
504
|
|
42
|
-
],
|
|
43
|
+
], ce = (e, t, n) => {
|
|
43
44
|
if (t >= e.maxAttempts || n?.status !== void 0 && !(e.retryOn ?? A).includes(n.status)) return null;
|
|
44
45
|
if (e.honourRetryAfter !== !1 && n?.retryAfterSeconds !== void 0) {
|
|
45
46
|
let t = n.retryAfterSeconds * 1e3, r = k(e.maxDelay), i = Math.min(t, r);
|
|
@@ -66,7 +67,7 @@ var ie = (e) => e.webhook !== void 0, O = (e) => {
|
|
|
66
67
|
delayMs: Math.max(1, Math.floor(o)),
|
|
67
68
|
baseDelayMs: a
|
|
68
69
|
};
|
|
69
|
-
},
|
|
70
|
+
}, le = () => ({
|
|
70
71
|
strategy: "exponential",
|
|
71
72
|
maxAttempts: 8,
|
|
72
73
|
initialDelay: "5s",
|
|
@@ -74,7 +75,7 @@ var ie = (e) => e.webhook !== void 0, O = (e) => {
|
|
|
74
75
|
retryOn: A,
|
|
75
76
|
honourRetryAfter: !0,
|
|
76
77
|
jitter: "full"
|
|
77
|
-
}),
|
|
78
|
+
}), ue = () => ({
|
|
78
79
|
strategy: "fixed",
|
|
79
80
|
maxAttempts: 120,
|
|
80
81
|
initialDelay: "30s",
|
|
@@ -82,7 +83,7 @@ var ie = (e) => e.webhook !== void 0, O = (e) => {
|
|
|
82
83
|
retryOn: A,
|
|
83
84
|
honourRetryAfter: !0,
|
|
84
85
|
jitter: "full"
|
|
85
|
-
}), j = class extends
|
|
86
|
+
}), j = class extends te.Tag("@voltro/webhooks/WebhooksService")() {}, de = 32, M = 32, N = () => v(de).toString("hex"), P = (e) => e.events ?? (e.event === void 0 ? [] : [e.event]), F = (e) => {
|
|
86
87
|
let t;
|
|
87
88
|
try {
|
|
88
89
|
t = new URL(e.url);
|
|
@@ -145,11 +146,12 @@ var ie = (e) => e.webhook !== void 0, O = (e) => {
|
|
|
145
146
|
});
|
|
146
147
|
}, I = (e, t, n, r) => ({
|
|
147
148
|
id: y(),
|
|
149
|
+
tenantId: e.tenantId ?? null,
|
|
148
150
|
event: n ?? e.event ?? "",
|
|
149
151
|
url: e.url,
|
|
150
152
|
secret: r ?? e.secret ?? N(),
|
|
151
153
|
signing: e.signing ?? t?.defaultSigning ?? c(),
|
|
152
|
-
retry: e.retry ?? t?.defaultRetry ??
|
|
154
|
+
retry: e.retry ?? t?.defaultRetry ?? le(),
|
|
153
155
|
filter: e.filter ?? null,
|
|
154
156
|
scope: e.scope ?? null,
|
|
155
157
|
headers: e.headers ?? null,
|
|
@@ -162,7 +164,7 @@ var ie = (e) => e.webhook !== void 0, O = (e) => {
|
|
|
162
164
|
autoDisableReason: null,
|
|
163
165
|
payloadVersion: e.payloadVersion ?? t?.version ?? 1,
|
|
164
166
|
description: e.description ?? null
|
|
165
|
-
}),
|
|
167
|
+
}), fe = (e) => e instanceof Date ? e.getTime() : typeof e == "string" ? Date.parse(e) : 0, L = (e) => e instanceof Date ? e.toISOString() : typeof e == "string" ? e : null, pe = (e) => {
|
|
166
168
|
if (typeof e != "string") return e;
|
|
167
169
|
try {
|
|
168
170
|
return JSON.parse(e);
|
|
@@ -183,19 +185,19 @@ var ie = (e) => e.webhook !== void 0, O = (e) => {
|
|
|
183
185
|
scheduledAt: L(e.scheduledAt),
|
|
184
186
|
nextAttemptAt: L(e.nextAttemptAt),
|
|
185
187
|
createdAt: L(e.createdAt)
|
|
186
|
-
}),
|
|
188
|
+
}), me = (e, t) => e === t ? "current" : e < t ? "behind" : "ahead", z = (e, t) => {
|
|
187
189
|
if (e === null) return !0;
|
|
188
190
|
let n = { payload: t };
|
|
189
|
-
for (let [t, r] of Object.entries(e)) if (!
|
|
191
|
+
for (let [t, r] of Object.entries(e)) if (!ge(he(n, t), r)) return !1;
|
|
190
192
|
return !0;
|
|
191
|
-
},
|
|
193
|
+
}, he = (e, t) => {
|
|
192
194
|
let n = t.split("."), r = e;
|
|
193
195
|
for (let e of n) {
|
|
194
196
|
if (typeof r != "object" || !r) return;
|
|
195
197
|
r = r[e];
|
|
196
198
|
}
|
|
197
199
|
return r;
|
|
198
|
-
},
|
|
200
|
+
}, ge = (e, t) => {
|
|
199
201
|
if (typeof t == "object" && t && !Array.isArray(t)) {
|
|
200
202
|
let n = t;
|
|
201
203
|
if ("eq" in n) return e === n.eq;
|
|
@@ -223,17 +225,43 @@ var ie = (e) => e.webhook !== void 0, O = (e) => {
|
|
|
223
225
|
if (n.push(...i), i.length < V) return n;
|
|
224
226
|
}
|
|
225
227
|
throw Error(`webhooks: more than ${H * V} rows in \`_voltro_webhook_targets\` — refusing to answer a scope lookup from a partial scan. A truncated scan mints a fresh secret for a live endpoint (breaking its signatures) or reports that a real scope matches nothing.`);
|
|
226
|
-
},
|
|
228
|
+
}, _e = async (e, t) => {
|
|
227
229
|
let n = (await U(e)).filter((e) => B(e.scope, t));
|
|
228
230
|
if (n.length === 0) return;
|
|
229
231
|
let r = new Set(n.map((e) => e.secret));
|
|
230
232
|
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.`);
|
|
231
233
|
return [...r][0];
|
|
232
|
-
},
|
|
234
|
+
}, ve = async (e, t) => {
|
|
235
|
+
try {
|
|
236
|
+
let n = await e.store.query({
|
|
237
|
+
table: "_voltro_webhook_event_stats",
|
|
238
|
+
predicate: C("event", t),
|
|
239
|
+
order: [],
|
|
240
|
+
take: 1,
|
|
241
|
+
skip: void 0,
|
|
242
|
+
projection: void 0
|
|
243
|
+
}), r = /* @__PURE__ */ new Date(), i = n[0];
|
|
244
|
+
if (i === void 0) {
|
|
245
|
+
await e.store.insert("_voltro_webhook_event_stats", {
|
|
246
|
+
event: t,
|
|
247
|
+
emitCount: 1,
|
|
248
|
+
lastEmitAt: r,
|
|
249
|
+
createdAt: r,
|
|
250
|
+
updatedAt: r
|
|
251
|
+
});
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
await e.store.update("_voltro_webhook_event_stats", i.id, {
|
|
255
|
+
emitCount: (i.emitCount ?? 0) + 1,
|
|
256
|
+
lastEmitAt: r,
|
|
257
|
+
updatedAt: r
|
|
258
|
+
});
|
|
259
|
+
} catch {}
|
|
260
|
+
}, ye = (e, t, n = {}) => {
|
|
233
261
|
let r = new Map((n.events ?? []).map((e) => [e.id, e])), i = {
|
|
234
262
|
subscribe: async (t) => {
|
|
235
263
|
F(t);
|
|
236
|
-
let n = P(t), i = t.secret === void 0 && t.scope !== void 0 ? await
|
|
264
|
+
let n = P(t), i = t.secret === void 0 && t.scope !== void 0 ? await _e(e, t.scope) : void 0, a = t.secret ?? i ?? N(), o = n.map((e) => I(t, r.get(e), e, a));
|
|
237
265
|
for (let t of o) await e.store.insert("_voltro_webhook_targets", {
|
|
238
266
|
...t,
|
|
239
267
|
createdAt: /* @__PURE__ */ new Date(),
|
|
@@ -257,21 +285,23 @@ var ie = (e) => e.webhook !== void 0, O = (e) => {
|
|
|
257
285
|
let o = typeof n == "object" && n.kind === "event" ? O(n) : void 0, s = typeof n == "string" ? r.get(n) : o ?? n, c = typeof n == "string" ? n : n.id ?? n.name;
|
|
258
286
|
if (s !== void 0) {
|
|
259
287
|
let e = x.decodeUnknownEither(s.payload)(i, { errors: "all" });
|
|
260
|
-
if (
|
|
288
|
+
if (ne.isLeft(e)) throw new u({
|
|
261
289
|
event: c,
|
|
262
290
|
issues: e.left.message
|
|
263
291
|
});
|
|
264
292
|
}
|
|
265
|
-
let l = y(), d = a?.tenantId, f =
|
|
266
|
-
|
|
293
|
+
let l = y(), d = a?.tenantId, f = await U(e, d == null ? C("event", c) : S(C("event", c), C("tenantId", d)));
|
|
294
|
+
await ve(e, c);
|
|
295
|
+
let p = f.filter((e) => z(e.filter, i)), m = [], h = JSON.stringify(i);
|
|
296
|
+
for (let n of p) {
|
|
267
297
|
let r = y();
|
|
268
298
|
n.active ? (await t({
|
|
269
299
|
deliveryId: r,
|
|
270
300
|
targetId: n.id,
|
|
271
301
|
event: c,
|
|
272
302
|
eventId: l,
|
|
273
|
-
payloadJson:
|
|
274
|
-
}),
|
|
303
|
+
payloadJson: h
|
|
304
|
+
}), m.push({
|
|
275
305
|
targetId: n.id,
|
|
276
306
|
deliveryId: r,
|
|
277
307
|
status: "dispatched"
|
|
@@ -284,11 +314,11 @@ var ie = (e) => e.webhook !== void 0, O = (e) => {
|
|
|
284
314
|
eventId: l,
|
|
285
315
|
attempt: 1,
|
|
286
316
|
status: "pending",
|
|
287
|
-
payload:
|
|
317
|
+
payload: h,
|
|
288
318
|
scheduledAt: /* @__PURE__ */ new Date(),
|
|
289
319
|
createdAt: /* @__PURE__ */ new Date(),
|
|
290
320
|
updatedAt: /* @__PURE__ */ new Date()
|
|
291
|
-
}),
|
|
321
|
+
}), m.push({
|
|
292
322
|
targetId: n.id,
|
|
293
323
|
deliveryId: r,
|
|
294
324
|
status: "queued"
|
|
@@ -296,7 +326,7 @@ var ie = (e) => e.webhook !== void 0, O = (e) => {
|
|
|
296
326
|
}
|
|
297
327
|
return {
|
|
298
328
|
eventId: l,
|
|
299
|
-
deliveries:
|
|
329
|
+
deliveries: m
|
|
300
330
|
};
|
|
301
331
|
},
|
|
302
332
|
replay: async (n) => {
|
|
@@ -440,7 +470,7 @@ var ie = (e) => e.webhook !== void 0, O = (e) => {
|
|
|
440
470
|
skip: void 0,
|
|
441
471
|
projection: void 0
|
|
442
472
|
}), a = t?.since?.getTime();
|
|
443
|
-
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 ||
|
|
473
|
+
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 || fe(e.createdAt) >= a).slice(0, t?.limit ?? 200).map(R);
|
|
444
474
|
},
|
|
445
475
|
getDelivery: async (t) => {
|
|
446
476
|
let n = (await e.store.query({
|
|
@@ -453,7 +483,7 @@ var ie = (e) => e.webhook !== void 0, O = (e) => {
|
|
|
453
483
|
}))[0];
|
|
454
484
|
return n === void 0 ? null : {
|
|
455
485
|
...R(n),
|
|
456
|
-
payload:
|
|
486
|
+
payload: pe(n.payload),
|
|
457
487
|
responseBody: n.responseBody ?? null
|
|
458
488
|
};
|
|
459
489
|
},
|
|
@@ -493,15 +523,15 @@ var ie = (e) => e.webhook !== void 0, O = (e) => {
|
|
|
493
523
|
}
|
|
494
524
|
};
|
|
495
525
|
return i;
|
|
496
|
-
},
|
|
526
|
+
}, be = b.gen(function* () {
|
|
497
527
|
return yield* j;
|
|
498
|
-
}),
|
|
528
|
+
}), xe = (e) => {
|
|
499
529
|
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.");
|
|
500
530
|
return e.webhooks;
|
|
501
|
-
},
|
|
531
|
+
}, Se = 1e4, W = class {
|
|
502
532
|
capacity;
|
|
503
533
|
cache = /* @__PURE__ */ new Map();
|
|
504
|
-
constructor(e =
|
|
534
|
+
constructor(e = Se) {
|
|
505
535
|
this.capacity = e;
|
|
506
536
|
}
|
|
507
537
|
evictExpired() {
|
|
@@ -547,7 +577,7 @@ var ie = (e) => e.webhook !== void 0, O = (e) => {
|
|
|
547
577
|
status: e,
|
|
548
578
|
contentType: "application/json; charset=utf-8",
|
|
549
579
|
body: JSON.stringify(t)
|
|
550
|
-
}),
|
|
580
|
+
}), Ce = (e, t) => {
|
|
551
581
|
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 ?? K(), c = J(i?.ttl), l = t.log ?? ((e) => {
|
|
552
582
|
T({ scope: `webhook:${e.webhookId}` }).debug("incoming webhook", {
|
|
553
583
|
status: e.status,
|
|
@@ -700,7 +730,7 @@ var ie = (e) => e.webhook !== void 0, O = (e) => {
|
|
|
700
730
|
idempotency: h,
|
|
701
731
|
durationMs: Date.now() - u,
|
|
702
732
|
errorMessage: t.message
|
|
703
|
-
}),
|
|
733
|
+
}), re({
|
|
704
734
|
error: t,
|
|
705
735
|
source: "webhook",
|
|
706
736
|
name: e.id,
|
|
@@ -708,20 +738,20 @@ var ie = (e) => e.webhook !== void 0, O = (e) => {
|
|
|
708
738
|
}), n;
|
|
709
739
|
}
|
|
710
740
|
};
|
|
711
|
-
},
|
|
741
|
+
}, we = (e) => `/webhooks/${e}`, Te = "_voltro_webhook_rate_windows", Ee = 6e4, De = (e, t) => `${e}@${t}`, Oe = async (e, t, n) => (await e.query({
|
|
712
742
|
table: "_voltro_webhook_rate_windows",
|
|
713
743
|
predicate: S(C("scope", t), C("bucket", n)),
|
|
714
744
|
order: [],
|
|
715
745
|
take: 1,
|
|
716
746
|
skip: void 0,
|
|
717
747
|
projection: void 0
|
|
718
|
-
}))[0] ?? null,
|
|
719
|
-
for (let r = 0; r <
|
|
748
|
+
}))[0] ?? null, X = 32, ke = async (e, t, n) => {
|
|
749
|
+
for (let r = 0; r < X; r++) {
|
|
720
750
|
r > 0 && await new Promise((e) => setTimeout(e, r));
|
|
721
|
-
let i = await
|
|
751
|
+
let i = await Oe(e, t.key, n);
|
|
722
752
|
if (i === null) {
|
|
723
|
-
await e.insertIgnore(
|
|
724
|
-
id:
|
|
753
|
+
await e.insertIgnore(Te, {
|
|
754
|
+
id: De(t.key, n),
|
|
725
755
|
scope: t.key,
|
|
726
756
|
bucket: n,
|
|
727
757
|
count: 0,
|
|
@@ -736,29 +766,29 @@ var ie = (e) => e.webhook !== void 0, O = (e) => {
|
|
|
736
766
|
updatedAt: /* @__PURE__ */ new Date()
|
|
737
767
|
}, { where: S(C("id", i.id), C("count", i.count)) }) === 1) return !0;
|
|
738
768
|
}
|
|
739
|
-
throw Error(`webhook rate window: contention on (${t.key}, ${n}) exceeded ${
|
|
740
|
-
},
|
|
741
|
-
for (let r = 0; r <
|
|
769
|
+
throw Error(`webhook rate window: contention on (${t.key}, ${n}) exceeded ${X} attempts`);
|
|
770
|
+
}, Ae = async (e, t, n) => {
|
|
771
|
+
for (let r = 0; r < X; r++) {
|
|
742
772
|
r > 0 && await new Promise((e) => setTimeout(e, r));
|
|
743
|
-
let i = await
|
|
773
|
+
let i = await Oe(e, t, n);
|
|
744
774
|
if (i === null || i.count <= 0 || await e.updateMany("_voltro_webhook_rate_windows", {
|
|
745
775
|
count: i.count - 1,
|
|
746
776
|
updatedAt: /* @__PURE__ */ new Date()
|
|
747
777
|
}, { where: S(C("id", i.id), C("count", i.count)) }) === 1) return;
|
|
748
778
|
}
|
|
749
|
-
throw Error(`webhook rate window: release contention on (${t}) exceeded ${
|
|
750
|
-
},
|
|
779
|
+
throw Error(`webhook rate window: release contention on (${t}) exceeded ${X} attempts`);
|
|
780
|
+
}, je = async (e, t, n, r = Ee) => {
|
|
751
781
|
if (t.length === 0) return {
|
|
752
782
|
acquired: !0,
|
|
753
783
|
retryInMs: 0
|
|
754
784
|
};
|
|
755
785
|
let i = Math.floor(n / r), a = [];
|
|
756
786
|
for (let o of t) {
|
|
757
|
-
if (await
|
|
787
|
+
if (await ke(e, o, i)) {
|
|
758
788
|
a.push(o);
|
|
759
789
|
continue;
|
|
760
790
|
}
|
|
761
|
-
for (let t of a) await
|
|
791
|
+
for (let t of a) await Ae(e, t.key, i);
|
|
762
792
|
return {
|
|
763
793
|
acquired: !1,
|
|
764
794
|
retryInMs: (i + 1) * r - n
|
|
@@ -768,7 +798,7 @@ var ie = (e) => e.webhook !== void 0, O = (e) => {
|
|
|
768
798
|
acquired: !0,
|
|
769
799
|
retryInMs: 0
|
|
770
800
|
};
|
|
771
|
-
},
|
|
801
|
+
}, Me = (e, t) => {
|
|
772
802
|
if (e === "json") return {
|
|
773
803
|
contentType: "application/json",
|
|
774
804
|
bytes: Buffer.from(t, "utf8")
|
|
@@ -784,40 +814,40 @@ var ie = (e) => e.webhook !== void 0, O = (e) => {
|
|
|
784
814
|
}
|
|
785
815
|
return e === "form" ? {
|
|
786
816
|
contentType: "application/x-www-form-urlencoded",
|
|
787
|
-
bytes: Buffer.from(
|
|
817
|
+
bytes: Buffer.from(Ne(n), "utf8")
|
|
788
818
|
} : {
|
|
789
819
|
contentType: "application/xml",
|
|
790
|
-
bytes: Buffer.from(
|
|
820
|
+
bytes: Buffer.from(Fe(n), "utf8")
|
|
791
821
|
};
|
|
792
|
-
},
|
|
822
|
+
}, Ne = (e) => {
|
|
793
823
|
if (typeof e != "object" || !e || Array.isArray(e)) throw new d({
|
|
794
824
|
format: "form",
|
|
795
|
-
reason: `form encoding requires a JSON object at the top level, got ${
|
|
825
|
+
reason: `form encoding requires a JSON object at the top level, got ${Re(e)}`
|
|
796
826
|
});
|
|
797
827
|
let t = new URLSearchParams();
|
|
798
|
-
for (let [n, r] of Object.entries(e))
|
|
828
|
+
for (let [n, r] of Object.entries(e)) Z(t, n, r);
|
|
799
829
|
return t.toString();
|
|
800
|
-
},
|
|
830
|
+
}, Z = (e, t, n) => {
|
|
801
831
|
if (n === null) {
|
|
802
832
|
e.append(t, "");
|
|
803
833
|
return;
|
|
804
834
|
}
|
|
805
835
|
if (Array.isArray(n)) {
|
|
806
|
-
n.forEach((n, r) =>
|
|
836
|
+
n.forEach((n, r) => Z(e, `${t}[${r}]`, n));
|
|
807
837
|
return;
|
|
808
838
|
}
|
|
809
839
|
if (typeof n == "object") {
|
|
810
|
-
for (let [r, i] of Object.entries(n))
|
|
840
|
+
for (let [r, i] of Object.entries(n)) Z(e, `${t}[${r}]`, i);
|
|
811
841
|
return;
|
|
812
842
|
}
|
|
813
843
|
e.append(t, String(n));
|
|
814
|
-
},
|
|
815
|
-
if (!
|
|
844
|
+
}, Pe = /^[A-Za-z_][A-Za-z0-9_.-]*$/, Fe = (e) => `<?xml version="1.0" encoding="UTF-8"?>${Q("webhook", e)}`, Q = (e, t) => {
|
|
845
|
+
if (!Pe.test(e)) throw new d({
|
|
816
846
|
format: "xml",
|
|
817
847
|
reason: `object key ${JSON.stringify(e)} is not a valid XML element name`
|
|
818
848
|
});
|
|
819
|
-
return `<${e}>${
|
|
820
|
-
},
|
|
849
|
+
return `<${e}>${Ie(e, t)}</${e}>`;
|
|
850
|
+
}, Ie = (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("") : Le(String(t)), Le = (e) => e.replace(/[&<>"']/g, (e) => {
|
|
821
851
|
switch (e) {
|
|
822
852
|
case "&": return "&";
|
|
823
853
|
case "<": return "<";
|
|
@@ -825,17 +855,17 @@ var ie = (e) => e.webhook !== void 0, O = (e) => {
|
|
|
825
855
|
case "\"": return """;
|
|
826
856
|
default: return "'";
|
|
827
857
|
}
|
|
828
|
-
}),
|
|
858
|
+
}), Re = (e) => e === null ? "null" : Array.isArray(e) ? "array" : typeof e, ze = "_voltro_webhook_targets", Be = 32, Ve = async (e, t) => (await e.query({
|
|
829
859
|
table: "_voltro_webhook_targets",
|
|
830
860
|
predicate: C("id", t),
|
|
831
861
|
order: [],
|
|
832
862
|
take: 1,
|
|
833
863
|
skip: void 0,
|
|
834
864
|
projection: void 0
|
|
835
|
-
}))[0] ?? null,
|
|
836
|
-
for (let a = 0; a <
|
|
865
|
+
}))[0] ?? null, He = async (e, t, n, r, i = /* @__PURE__ */ new Date()) => {
|
|
866
|
+
for (let a = 0; a < Be; a++) {
|
|
837
867
|
a > 0 && await new Promise((e) => setTimeout(e, a));
|
|
838
|
-
let o = await
|
|
868
|
+
let o = await Ve(e, t);
|
|
839
869
|
if (o === null) return {
|
|
840
870
|
consecutiveFailures: 0,
|
|
841
871
|
autoDisabled: !1
|
|
@@ -860,24 +890,24 @@ var ie = (e) => e.webhook !== void 0, O = (e) => {
|
|
|
860
890
|
autoDisabled: u
|
|
861
891
|
};
|
|
862
892
|
}
|
|
863
|
-
throw Error(`webhook auto-disable: streak CAS contention on target ${t} exceeded ${
|
|
864
|
-
},
|
|
893
|
+
throw Error(`webhook auto-disable: streak CAS contention on target ${t} exceeded ${Be} attempts`);
|
|
894
|
+
}, Ue = x.Struct({
|
|
865
895
|
status: x.Number,
|
|
866
896
|
body: x.String,
|
|
867
897
|
latencyMs: x.Number,
|
|
868
898
|
retryAfterSec: x.Union(x.Number, x.Undefined),
|
|
869
899
|
transportError: x.Union(x.String, x.Undefined),
|
|
870
900
|
encodeError: x.Union(x.String, x.Undefined)
|
|
871
|
-
}),
|
|
901
|
+
}), We = x.Struct({
|
|
872
902
|
outcome: x.Literal("succeeded", "failed", "retry"),
|
|
873
903
|
delayMs: x.Union(x.Number, x.Undefined),
|
|
874
904
|
reason: x.Union(x.String, x.Undefined),
|
|
875
905
|
autoDisabled: x.Union(x.Boolean, x.Undefined),
|
|
876
906
|
consecutiveFailures: x.Union(x.Number, x.Undefined)
|
|
877
|
-
}),
|
|
907
|
+
}), Ge = x.Struct({
|
|
878
908
|
acquired: x.Boolean,
|
|
879
909
|
retryInMs: x.Number
|
|
880
|
-
}),
|
|
910
|
+
}), Ke = ie.make({
|
|
881
911
|
name: "voltro.deliverWebhook",
|
|
882
912
|
payload: {
|
|
883
913
|
deliveryId: x.String,
|
|
@@ -892,7 +922,7 @@ var ie = (e) => e.webhook !== void 0, O = (e) => {
|
|
|
892
922
|
attempts: x.Number
|
|
893
923
|
}),
|
|
894
924
|
idempotencyKey: ({ deliveryId: e, attemptEpoch: t }) => `voltro.deliverWebhook:${e}:${t ?? 0}`
|
|
895
|
-
}),
|
|
925
|
+
}), qe = (e, t = {}) => (n, r) => {
|
|
896
926
|
let { deliveryId: i, targetId: a, event: s, eventId: c, payloadJson: l } = n, u = T({ scope: `webhook:${i}` });
|
|
897
927
|
return b.gen(function* () {
|
|
898
928
|
let n = yield* E.make({
|
|
@@ -966,21 +996,21 @@ var ie = (e) => e.webhook !== void 0, O = (e) => {
|
|
|
966
996
|
finalStatus: "deferred",
|
|
967
997
|
attempts: 0
|
|
968
998
|
};
|
|
969
|
-
let f = (e) => typeof e == "string" ? JSON.parse(e) : e, p = f(n.retry), m = f(n.signing), h = n.headers === null ? null : f(n.headers),
|
|
999
|
+
let f = (e) => typeof e == "string" ? JSON.parse(e) : e, p = f(n.retry), m = f(n.signing), h = n.headers === null ? null : f(n.headers), ee = t.rateWindowMs ?? 6e4, g = t.events?.find((e) => e.id === s)?.globalRateLimit, _ = [...typeof n.rateLimitPerMinute == "number" && n.rateLimitPerMinute > 0 ? [{
|
|
970
1000
|
key: `target:${n.id}`,
|
|
971
1001
|
limit: n.rateLimitPerMinute
|
|
972
|
-
}] : [], ...
|
|
1002
|
+
}] : [], ...g !== void 0 && g.perMinute > 0 ? [{
|
|
973
1003
|
key: `event:${s}`,
|
|
974
|
-
limit:
|
|
1004
|
+
limit: g.perMinute
|
|
975
1005
|
}] : []];
|
|
976
1006
|
for (let t = 1; t <= p.maxAttempts; t++) {
|
|
977
1007
|
let f = `${i}:${t}`;
|
|
978
|
-
if (
|
|
1008
|
+
if (_.length > 0) for (let n = 0;; n++) {
|
|
979
1009
|
let i = yield* E.make({
|
|
980
1010
|
name: `rate-acquire-${t}-${n}`,
|
|
981
|
-
success:
|
|
1011
|
+
success: Ge,
|
|
982
1012
|
execute: b.tryPromise({
|
|
983
|
-
try: () =>
|
|
1013
|
+
try: () => je(e.store, _, Date.now(), ee),
|
|
984
1014
|
catch: (e) => (u.debug("rate-acquire raw error", {
|
|
985
1015
|
targetId: a,
|
|
986
1016
|
event: s,
|
|
@@ -1017,13 +1047,13 @@ var ie = (e) => e.webhook !== void 0, O = (e) => {
|
|
|
1017
1047
|
catch: (e) => /* @__PURE__ */ Error(`record-attempt-start failed: ${e.message}`)
|
|
1018
1048
|
}).pipe(b.orDie)
|
|
1019
1049
|
});
|
|
1020
|
-
let
|
|
1050
|
+
let g = yield* E.make({
|
|
1021
1051
|
name: `sign-and-post-${t}`,
|
|
1022
|
-
success:
|
|
1052
|
+
success: Ue,
|
|
1023
1053
|
execute: b.promise(async () => {
|
|
1024
1054
|
let e = Date.now(), r;
|
|
1025
1055
|
try {
|
|
1026
|
-
r =
|
|
1056
|
+
r = Me(n.format, l);
|
|
1027
1057
|
} catch (t) {
|
|
1028
1058
|
if (t instanceof d) return {
|
|
1029
1059
|
status: 0,
|
|
@@ -1071,18 +1101,18 @@ var ie = (e) => e.webhook !== void 0, O = (e) => {
|
|
|
1071
1101
|
};
|
|
1072
1102
|
}
|
|
1073
1103
|
})
|
|
1074
|
-
}),
|
|
1104
|
+
}), v = yield* E.make({
|
|
1075
1105
|
name: `classify-and-record-${t}`,
|
|
1076
|
-
success:
|
|
1106
|
+
success: We,
|
|
1077
1107
|
execute: b.tryPromise({
|
|
1078
1108
|
try: async () => {
|
|
1079
|
-
let r =
|
|
1109
|
+
let r = g.status >= 200 && g.status < 300, i = g.transportError !== void 0, o = g.encodeError !== void 0, s = (t, n) => He(e.store, a, t, n);
|
|
1080
1110
|
if (r) {
|
|
1081
1111
|
await e.store.update("_voltro_webhook_deliveries", f, {
|
|
1082
1112
|
status: "succeeded",
|
|
1083
|
-
responseStatus:
|
|
1084
|
-
responseBody:
|
|
1085
|
-
latencyMs:
|
|
1113
|
+
responseStatus: g.status,
|
|
1114
|
+
responseBody: g.body,
|
|
1115
|
+
latencyMs: g.latencyMs,
|
|
1086
1116
|
updatedAt: /* @__PURE__ */ new Date()
|
|
1087
1117
|
});
|
|
1088
1118
|
let t = await s(!0, "");
|
|
@@ -1094,18 +1124,18 @@ var ie = (e) => e.webhook !== void 0, O = (e) => {
|
|
|
1094
1124
|
consecutiveFailures: t.consecutiveFailures
|
|
1095
1125
|
};
|
|
1096
1126
|
}
|
|
1097
|
-
let c = o ? null :
|
|
1098
|
-
...i ? {} : { status:
|
|
1099
|
-
...
|
|
1127
|
+
let c = o ? null : ce(p, t, {
|
|
1128
|
+
...i ? {} : { status: g.status },
|
|
1129
|
+
...g.retryAfterSec === void 0 ? {} : { retryAfterSeconds: g.retryAfterSec }
|
|
1100
1130
|
});
|
|
1101
1131
|
if (c === null) {
|
|
1102
|
-
let t = o ? `encode (${n.format}): ${
|
|
1132
|
+
let t = o ? `encode (${n.format}): ${g.encodeError}` : i ? `transport: ${g.transportError}` : `http ${g.status}: ${g.body.slice(0, 200)}`;
|
|
1103
1133
|
await e.store.update("_voltro_webhook_deliveries", f, {
|
|
1104
1134
|
status: "failed",
|
|
1105
|
-
responseStatus: i || o ? null :
|
|
1106
|
-
responseBody: i || o ? null :
|
|
1107
|
-
errorMessage: o ? t :
|
|
1108
|
-
latencyMs:
|
|
1135
|
+
responseStatus: i || o ? null : g.status,
|
|
1136
|
+
responseBody: i || o ? null : g.body,
|
|
1137
|
+
errorMessage: o ? t : g.transportError ?? null,
|
|
1138
|
+
latencyMs: g.latencyMs,
|
|
1109
1139
|
updatedAt: /* @__PURE__ */ new Date()
|
|
1110
1140
|
});
|
|
1111
1141
|
let r = await s(!1, t);
|
|
@@ -1119,10 +1149,10 @@ var ie = (e) => e.webhook !== void 0, O = (e) => {
|
|
|
1119
1149
|
}
|
|
1120
1150
|
return await e.store.update("_voltro_webhook_deliveries", f, {
|
|
1121
1151
|
status: "retryScheduled",
|
|
1122
|
-
responseStatus: i ? null :
|
|
1123
|
-
responseBody: i ? null :
|
|
1124
|
-
errorMessage:
|
|
1125
|
-
latencyMs:
|
|
1152
|
+
responseStatus: i ? null : g.status,
|
|
1153
|
+
responseBody: i ? null : g.body,
|
|
1154
|
+
errorMessage: g.transportError ?? null,
|
|
1155
|
+
latencyMs: g.latencyMs,
|
|
1126
1156
|
nextAttemptAt: new Date(Date.now() + c.delayMs),
|
|
1127
1157
|
updatedAt: /* @__PURE__ */ new Date()
|
|
1128
1158
|
}), {
|
|
@@ -1141,22 +1171,22 @@ var ie = (e) => e.webhook !== void 0, O = (e) => {
|
|
|
1141
1171
|
}), /* @__PURE__ */ Error(`classify-and-record failed: ${e.message}`))
|
|
1142
1172
|
}).pipe(b.orDie)
|
|
1143
1173
|
});
|
|
1144
|
-
if (
|
|
1174
|
+
if (v.autoDisabled === !0 && u.warn("target auto-disabled after consecutive failures", {
|
|
1145
1175
|
targetId: a,
|
|
1146
1176
|
event: s,
|
|
1147
|
-
consecutiveFailures:
|
|
1148
|
-
reason:
|
|
1149
|
-
}),
|
|
1177
|
+
consecutiveFailures: v.consecutiveFailures ?? null,
|
|
1178
|
+
reason: v.reason ?? null
|
|
1179
|
+
}), v.outcome === "succeeded") return {
|
|
1150
1180
|
finalStatus: "succeeded",
|
|
1151
1181
|
attempts: t
|
|
1152
1182
|
};
|
|
1153
|
-
if (
|
|
1183
|
+
if (v.outcome === "failed") return {
|
|
1154
1184
|
finalStatus: "failed",
|
|
1155
1185
|
attempts: t
|
|
1156
1186
|
};
|
|
1157
|
-
|
|
1187
|
+
v.delayMs !== void 0 && (yield* D.sleep({
|
|
1158
1188
|
name: `retry-sleep-${t}`,
|
|
1159
|
-
duration: `${
|
|
1189
|
+
duration: `${v.delayMs} millis`
|
|
1160
1190
|
}));
|
|
1161
1191
|
}
|
|
1162
1192
|
return {
|
|
@@ -1164,6 +1194,15 @@ var ie = (e) => e.webhook !== void 0, O = (e) => {
|
|
|
1164
1194
|
attempts: p.maxAttempts
|
|
1165
1195
|
};
|
|
1166
1196
|
});
|
|
1167
|
-
}
|
|
1197
|
+
}, $ = "voltro.webhook.emit", Je = (e) => typeof e.event == "string", Ye = (e) => ae({
|
|
1198
|
+
effect: $,
|
|
1199
|
+
maxAttempts: 1,
|
|
1200
|
+
handler: async ({ payload: t }) => {
|
|
1201
|
+
if (!Je(t)) throw Error(`${$}: row payload has no \`event\` name — cannot resolve a descriptor. Got keys: ${Object.keys(t).join(", ") || "(none)"}`);
|
|
1202
|
+
let n = e();
|
|
1203
|
+
if (n === void 0) throw Error(`${$}: the outgoing-webhooks service is not available in this process. An emit was enqueued by a build that had the plugin registered; this one does not.`);
|
|
1204
|
+
await n.emit(t.event, t.payload, { tenantId: t.tenantId });
|
|
1205
|
+
}
|
|
1206
|
+
});
|
|
1168
1207
|
//#endregion
|
|
1169
|
-
export { W as IdempotencyCache,
|
|
1208
|
+
export { W as IdempotencyCache, Ee as RATE_WINDOW_MS, Te as RATE_WINDOW_TABLE, ze as TARGETS_TABLE, $ as WEBHOOK_EMIT_EFFECT, l as WebhookDeliveryNotFound, u as WebhookPayloadInvalid, d as WebhookPayloadUnrepresentable, f as WebhookPayloadVersionInvalid, p as WebhookSubscribeInvalid, j as WebhooksService, m as _voltroWebhookDeliveriesTable, h as _voltroWebhookEventStatsTable, ee as _voltroWebhookRateWindowsTable, g as _voltroWebhookTargetsTable, je as acquireRateSlots, O as asOutgoingEvent, qe as buildDeliverWebhookExecute, ye as buildWebhooksService, me as compareVersions, ke as consumeRateSlot, we as defaultIncomingPath, c as defaultOutgoingSignature, le as defaultRetryPolicy, s as defineIncomingWebhook, t as defineWebhookProvider, Ke as deliverWebhookWorkflow, Me as encodePayload, P as eventsOf, ue as fastRetryPolicy, N as generateSecret, K as getIdempotencyCache, i as githubSignature, oe as hasWebhookAudience, r as isWebhookDescriptor, Ye as makeDeferredEmitHandler, z as matchesFilter, Ce as mountIncomingWebhook, ce as nextRetry, k as parseDuration, J as parseTtl, He as recordDeliveryOutcome, Ae as releaseRateSlot, I as resolveSubscribe, o as signPayload, n as slackSignature, e as stripeSignature, xe as useWebhooks, be as useWebhooksEffect, F as validateSubscribe, a as verifySignature, _ as webhookTables };
|