@voltro/plugin-webhooks 0.85.0 → 0.87.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/dist/index.js CHANGED
@@ -1,1304 +1,17 @@
1
- import { C as e, E as t, S as n, T as r, _ as i, a, b as o, c as s, d as c, f as l, g as u, h as d, i as f, l as p, m, n as h, o as g, p as _, r as v, s as y, t as ee, u as te, v as ne, w as re, x as ie, y as ae } from "./signing-DeHObNv6.js";
2
- import { WebhookDeliveryNotFound as oe, WebhookPayloadInvalid as se, WebhookPayloadUnrepresentable as b, WebhookPayloadVersionInvalid as x, WebhookSubscribeInvalid as S, WebhookTargetNotFound as C } from "./errors.js";
3
- import { _voltroWebhookDeliveriesTable as ce, _voltroWebhookEventStatsTable as le, _voltroWebhookRateWindowsTable as ue, _voltroWebhookTargetsTable as de, webhookTables as fe } from "./mixin.js";
4
- import { Context as pe, Effect as w, Result as me, Schema as T } from "effect";
5
- import { definePlugin as he, publishServerError as ge } from "@voltro/protocol";
6
- import { randomBytes as _e, randomUUID as E } from "node:crypto";
7
- import { and as D, eq as O } from "@voltro/database";
8
- import { assertPublicUrl as k } from "@voltro/integration-http";
9
- import { createLogger as A } from "@voltro/logger";
10
- import { Activity as j, DurableClock as ve, Workflow as ye } from "effect/workflow";
11
- import { defineOutboxHandler as be } from "@voltro/runtime";
12
- //#region src/plugin.ts
13
- var xe = ["network:outbound:*"], M = [{
14
- destination: "runtime:registered webhook targets (_voltro_webhook_targets)",
15
- purpose: "event deliveries, HMAC-signed",
16
- data: ["content", "metadata"],
17
- disable: "remove or pause the targets; an emit then delivers nowhere"
18
- }], Se = (e = {}) => he({
19
- name: e.name ? `@voltro/plugin-webhooks#${e.name}` : "@voltro/plugin-webhooks",
20
- description: "Outgoing webhooks (multi-target, durable-workflow delivery, HMAC signing, retry policies, rate limits) + incoming (signature verification, idempotency, provider templates). Declares wildcard outbound network access because targets are user-configured URLs.",
21
- permissions: xe,
22
- egress: M,
23
- onActivate: (e) => w.sync(() => {
24
- e.logger.info("webhooks active", { outbound: "network:outbound:* (subscriber-configured target URLs — audit visibility, not a restriction)" });
25
- })
26
- }), Ce = (e) => e.webhook !== void 0, N = (e) => {
27
- let t = e.webhook;
28
- if (t !== void 0) return {
29
- _tag: "outgoingEvent",
30
- id: e.name,
31
- payload: e.payload,
32
- ...t.description === void 0 ? {} : { description: t.description },
33
- ...t.version === void 0 ? {} : { version: t.version },
34
- ...t.rateLimit === void 0 ? {} : { globalRateLimit: t.rateLimit }
35
- };
36
- }, we = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, P = (e) => {
37
- let t = we.exec(e);
38
- if (!t) throw Error(`invalid duration literal: ${e}`);
39
- let n = Number(t[1]), r = t[2] ?? "s";
40
- switch (r) {
41
- case "ms": return n;
42
- case "s": return n * 1e3;
43
- case "m": return n * 6e4;
44
- case "h": return n * 36e5;
45
- case "d": return n * 864e5;
46
- default: throw Error(`unreachable unit: ${r}`);
47
- }
48
- }, F = [
49
- 408,
50
- 425,
51
- 429,
52
- 500,
53
- 502,
54
- 503,
55
- 504
56
- ], Te = (e, t, n) => {
57
- if (t >= e.maxAttempts || n?.status !== void 0 && !(e.retryOn ?? F).includes(n.status)) return null;
58
- if (e.honourRetryAfter !== !1 && n?.retryAfterSeconds !== void 0) {
59
- let t = n.retryAfterSeconds * 1e3, r = P(e.maxDelay), i = Math.min(t, r);
60
- return {
61
- delayMs: i,
62
- baseDelayMs: i
63
- };
64
- }
65
- let r = P(e.initialDelay), i = P(e.maxDelay), a;
66
- switch (e.strategy) {
67
- case "fixed":
68
- a = r;
69
- break;
70
- case "linear":
71
- a = r * t;
72
- break;
73
- case "exponential": a = r * 2 ** (t - 1);
74
- }
75
- a = Math.min(a, i);
76
- let o = a;
77
- return e.jitter === "full" && (o = Math.random() * a), {
78
- delayMs: Math.max(1, Math.floor(o)),
79
- baseDelayMs: a
80
- };
81
- }, Ee = () => ({
82
- strategy: "exponential",
83
- maxAttempts: 8,
84
- initialDelay: "5s",
85
- maxDelay: "1h",
86
- retryOn: F,
87
- honourRetryAfter: !0,
88
- jitter: "full"
89
- }), De = () => ({
90
- strategy: "fixed",
91
- maxAttempts: 120,
92
- initialDelay: "30s",
93
- maxDelay: "30s",
94
- retryOn: F,
95
- honourRetryAfter: !0,
96
- jitter: "full"
97
- }), Oe = class extends pe.Service()("@voltro/webhooks/WebhooksService") {}, ke = 32, Ae = 32, je = () => _e(ke).toString("hex"), I = (e) => e.events ?? (e.event === void 0 ? [] : [e.event]), L = (e, t) => {
98
- if (e !== void 0 && t?._tag === "standardWebhooks" && !ne(e)) throw new S({
99
- field: "secret",
100
- reason: "a standard-webhooks target needs the spec's secret serialization (\"whsec_\" + base64 of 24..64 random bytes) — mint one with generateStandardWebhooksSecret(), or omit `secret` and let subscribe mint it. If the incompatible key is inherited, rotate the endpoint secret and update the receiver before adding the event"
101
- });
102
- }, R = (e) => {
103
- let t;
104
- try {
105
- t = new URL(e.url);
106
- } catch (e) {
107
- throw new S({
108
- field: "url",
109
- reason: `invalid subscriber url: ${e.message}`
110
- });
111
- }
112
- if (t.protocol !== "http:" && t.protocol !== "https:") throw new S({
113
- field: "url",
114
- reason: `url protocol must be http or https, got ${t.protocol}`
115
- });
116
- if (process.env.NODE_ENV === "production") try {
117
- k(e.url);
118
- } catch (e) {
119
- throw new S({
120
- field: "url",
121
- reason: e.message
122
- });
123
- }
124
- let n = I(e);
125
- if (n.length === 0) throw new S({
126
- field: "event",
127
- reason: "event id required"
128
- });
129
- if (e.event !== void 0 && e.events !== void 0) throw new S({
130
- field: "event",
131
- reason: "pass `event` OR `events`, not both — the row set would be ambiguous"
132
- });
133
- if (n.some((e) => e.length === 0)) throw new S({
134
- field: "events",
135
- reason: "event id required"
136
- });
137
- if (new Set(n).size !== n.length) throw new S({
138
- field: "events",
139
- reason: "duplicate event — one row per event, so a repeat would deliver twice"
140
- });
141
- if (e.secret !== void 0 && e.secret.length < Ae) throw new S({
142
- field: "secret",
143
- reason: `secret must be at least ${Ae} chars`
144
- });
145
- if (L(e.secret, e.signing), e.retry !== void 0) {
146
- if (e.retry.maxAttempts < 1) throw new S({
147
- field: "retry",
148
- reason: "retry.maxAttempts must be ≥ 1"
149
- });
150
- if (e.retry.maxAttempts > 1e3) throw new S({
151
- field: "retry",
152
- reason: "retry.maxAttempts must be ≤ 1000"
153
- });
154
- }
155
- if (e.rateLimitPerMinute !== void 0 && e.rateLimitPerMinute < 1) throw new S({
156
- field: "rateLimitPerMinute",
157
- reason: "rateLimitPerMinute must be ≥ 1"
158
- });
159
- if (e.autoDisableAfter !== void 0 && (e.autoDisableAfter < 1 || !Number.isInteger(e.autoDisableAfter))) throw new S({
160
- field: "autoDisableAfter",
161
- reason: `autoDisableAfter must be a positive integer, got ${e.autoDisableAfter}`
162
- });
163
- }, z = (e, t, n, r) => {
164
- let a = e.signing ?? t?.defaultSigning ?? ee(), o = a._tag === "standardWebhooks" ? i : je, s = r ?? e.secret ?? o();
165
- return L(s, a), {
166
- id: E(),
167
- tenantId: e.tenantId ?? null,
168
- event: n ?? e.event ?? "",
169
- url: e.url,
170
- secret: s,
171
- signing: a,
172
- retry: e.retry ?? t?.defaultRetry ?? Ee(),
173
- filter: e.filter ?? null,
174
- scope: e.scope ?? null,
175
- headers: e.headers ?? null,
176
- rateLimitPerMinute: e.rateLimitPerMinute ?? null,
177
- active: !0,
178
- format: e.format ?? "json",
179
- autoDisableAfter: e.autoDisableAfter ?? null,
180
- consecutiveFailures: 0,
181
- autoDisabledAt: null,
182
- autoDisableReason: null,
183
- payloadVersion: e.payloadVersion ?? t?.version ?? 1,
184
- description: e.description ?? null
185
- };
186
- }, Me = (e) => e instanceof Date ? e.getTime() : typeof e == "string" ? Date.parse(e) : 0, B = (e) => e instanceof Date ? e.toISOString() : typeof e == "string" ? e : null, Ne = (e) => {
187
- if (typeof e != "string") return e;
188
- try {
189
- return JSON.parse(e);
190
- } catch {
191
- return e;
192
- }
193
- }, V = (e) => ({
194
- id: String(e.id),
195
- deliveryId: String(e.deliveryId),
196
- targetId: String(e.targetId),
197
- event: String(e.event),
198
- eventId: e.eventId ?? null,
199
- attempt: Number(e.attempt ?? 0),
200
- status: e.status,
201
- responseStatus: e.responseStatus ?? null,
202
- errorMessage: e.errorMessage ?? null,
203
- latencyMs: e.latencyMs ?? null,
204
- scheduledAt: B(e.scheduledAt),
205
- nextAttemptAt: B(e.nextAttemptAt),
206
- createdAt: B(e.createdAt)
207
- }), Pe = (e, t) => e === t ? "current" : e < t ? "behind" : "ahead", H = (e, t) => {
208
- if (e === null) return !0;
209
- let n = { payload: t };
210
- for (let [t, r] of Object.entries(e)) if (!Ie(Fe(n, t), r)) return !1;
211
- return !0;
212
- }, Fe = (e, t) => {
213
- let n = t.split("."), r = e;
214
- for (let e of n) {
215
- if (typeof r != "object" || !r) return;
216
- r = r[e];
217
- }
218
- return r;
219
- }, U = (e, t) => {
220
- if (typeof e == "number" && typeof t == "number") return e - t;
221
- if (typeof e == "string" && typeof t == "string") return e < t ? -1 : +(e > t);
222
- }, Ie = (e, t) => {
223
- if (typeof t == "object" && t && !Array.isArray(t)) {
224
- let n = t;
225
- return "eq" in n && e !== n.eq || "in" in n && (!Array.isArray(n.in) || !n.in.includes(e)) || "gt" in n && !((U(e, n.gt) ?? 0) > 0) || "gte" in n && !((U(e, n.gte) ?? -1) >= 0) || "lt" in n && !((U(e, n.lt) ?? 0) < 0) || "lte" in n && !((U(e, n.lte) ?? 1) <= 0) ? !1 : [
226
- "eq",
227
- "in",
228
- "gt",
229
- "gte",
230
- "lt",
231
- "lte"
232
- ].some((e) => e in n);
233
- }
234
- return e === t;
235
- }, W = (e, t) => e !== null && Object.entries(t).every(([t, n]) => e[t] === n), G = 500, Le = 200, K = async (e, t) => {
236
- let n = [];
237
- for (let r = 0; r < Le; r += 1) {
238
- let i = await e.store.query({
239
- table: "_voltro_webhook_targets",
240
- predicate: t,
241
- order: [{
242
- column: "id",
243
- direction: "asc"
244
- }],
245
- take: G,
246
- skip: r === 0 ? void 0 : r * G,
247
- projection: void 0
248
- });
249
- if (n.push(...i), i.length < G) return n;
250
- }
251
- throw Error(`webhooks: more than ${Le * G} 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.`);
252
- }, Re = async (e, t) => {
253
- let n = (await K(e)).filter((e) => W(e.scope, t));
254
- if (n.length === 0) return;
255
- let r = new Set(n.map((e) => e.secret));
256
- 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.`);
257
- return [...r][0];
258
- }, ze = async (e, t) => {
259
- try {
260
- let n = await e.store.query({
261
- table: "_voltro_webhook_event_stats",
262
- predicate: O("event", t),
263
- order: [],
264
- take: 1,
265
- skip: void 0,
266
- projection: void 0
267
- }), r = /* @__PURE__ */ new Date(), i = n[0];
268
- if (i === void 0) {
269
- await e.store.insert("_voltro_webhook_event_stats", {
270
- event: t,
271
- emitCount: 1,
272
- lastEmitAt: r,
273
- createdAt: r,
274
- updatedAt: r
275
- });
276
- return;
277
- }
278
- await e.store.update("_voltro_webhook_event_stats", i.id, {
279
- emitCount: (i.emitCount ?? 0) + 1,
280
- lastEmitAt: r,
281
- updatedAt: r
282
- });
283
- } catch {}
284
- }, Be = (e, t, n = {}) => {
285
- let r = new Map((n.events ?? []).map((e) => [e.id, e])), a = {
286
- subscribe: async (t) => {
287
- R(t);
288
- let n = I(t), a = t.secret === void 0 && t.scope !== void 0 ? await Re(e, t.scope) : void 0, o = t.secret ?? a ?? i(), s = n.map((e) => z(t, r.get(e), e, o));
289
- for (let t of s) await e.store.insert("_voltro_webhook_targets", {
290
- ...t,
291
- createdAt: /* @__PURE__ */ new Date(),
292
- updatedAt: /* @__PURE__ */ new Date()
293
- });
294
- let c = s[0];
295
- return {
296
- id: c.id,
297
- event: c.event,
298
- url: c.url,
299
- secret: c.secret,
300
- signing: c.signing,
301
- retry: c.retry,
302
- ...s.length > 1 ? { targets: s.map((e) => ({
303
- id: e.id,
304
- event: e.event
305
- })) } : {}
306
- };
307
- },
308
- emit: async (n, i, a) => {
309
- let o = typeof n == "object" && n.kind === "event" ? N(n) : void 0, s = typeof n == "string" ? r.get(n) : o ?? n, c = typeof n == "string" ? n : n.id ?? n.name;
310
- if (s !== void 0) {
311
- let e = T.decodeUnknownResult(s.payload)(i, { errors: "all" });
312
- if (me.isFailure(e)) throw new se({
313
- event: c,
314
- issues: e.failure.message
315
- });
316
- }
317
- let l = a?.eventId ?? E(), u = a?.tenantId, d = await K(e, u == null ? O("event", c) : D(O("event", c), O("tenantId", u)));
318
- await ze(e, c);
319
- let f = d.filter((e) => H(e.filter, i)), p = [], m = JSON.stringify(i), h = async (n, r) => {
320
- let i = E();
321
- n.active ? (await t({
322
- deliveryId: i,
323
- targetId: n.id,
324
- event: c,
325
- eventId: l,
326
- payloadJson: m
327
- }), p[r] = {
328
- targetId: n.id,
329
- deliveryId: i,
330
- status: "dispatched"
331
- }) : (await e.store.insert("_voltro_webhook_deliveries", {
332
- id: `${i}:1`,
333
- deliveryId: i,
334
- targetId: n.id,
335
- tenantId: n.tenantId ?? null,
336
- event: c,
337
- eventId: l,
338
- attempt: 1,
339
- status: "pending",
340
- payload: m,
341
- scheduledAt: /* @__PURE__ */ new Date(),
342
- createdAt: /* @__PURE__ */ new Date(),
343
- updatedAt: /* @__PURE__ */ new Date()
344
- }), p[r] = {
345
- targetId: n.id,
346
- deliveryId: i,
347
- status: "queued"
348
- });
349
- }, g = 0, _ = !1, v;
350
- if (await Promise.all(Array.from({ length: Math.min(4, f.length) }, async () => {
351
- for (; !_ && g < f.length;) {
352
- let e = g++;
353
- try {
354
- await h(f[e], e);
355
- } catch (e) {
356
- _ || (_ = !0, v = e);
357
- }
358
- }
359
- })), _) throw v;
360
- return {
361
- eventId: l,
362
- deliveries: p
363
- };
364
- },
365
- replay: async (n) => {
366
- let r = (await e.store.query({
367
- table: "_voltro_webhook_deliveries",
368
- predicate: O("deliveryId", n),
369
- order: [{
370
- column: "attempt",
371
- direction: "asc"
372
- }],
373
- take: 1,
374
- skip: void 0,
375
- projection: void 0
376
- }))[0];
377
- if (r === void 0) throw new oe({ deliveryId: n });
378
- await t({
379
- deliveryId: r.deliveryId,
380
- targetId: r.targetId,
381
- event: r.event,
382
- eventId: r.eventId ?? E(),
383
- payloadJson: typeof r.payload == "string" ? r.payload : JSON.stringify(r.payload),
384
- attemptEpoch: Date.now()
385
- });
386
- },
387
- resolveTargets: async (t) => {
388
- if (typeof t == "string") {
389
- if ((await e.store.query({
390
- table: "_voltro_webhook_targets",
391
- predicate: O("id", t),
392
- order: [],
393
- take: 1,
394
- skip: void 0,
395
- projection: ["id"]
396
- })).length === 0) throw new C({ targetId: t });
397
- return [t];
398
- }
399
- let n = (await K(e)).filter((e) => W(e.scope, t.scope)).map((e) => e.id);
400
- if (n.length === 0) throw Error(`webhooks: no target matches scope ${JSON.stringify(t.scope)} — the operation would have silently done nothing`);
401
- return n;
402
- },
403
- pauseTarget: async (t) => {
404
- for (let n of await a.resolveTargets(t)) await e.store.update("_voltro_webhook_targets", n, {
405
- active: !1,
406
- updatedAt: /* @__PURE__ */ new Date()
407
- });
408
- },
409
- resumeTarget: async (n) => {
410
- for (let r of await a.resolveTargets(n)) {
411
- await e.store.update("_voltro_webhook_targets", r, {
412
- active: !0,
413
- consecutiveFailures: 0,
414
- autoDisabledAt: null,
415
- autoDisableReason: null,
416
- updatedAt: /* @__PURE__ */ new Date()
417
- });
418
- let n = await e.store.query({
419
- table: "_voltro_webhook_deliveries",
420
- predicate: D(O("targetId", r), O("status", "pending")),
421
- order: [{
422
- column: "createdAt",
423
- direction: "asc"
424
- }],
425
- take: 1e4,
426
- skip: void 0,
427
- projection: void 0
428
- }), i = Date.now();
429
- for (let e of n) await t({
430
- deliveryId: e.deliveryId,
431
- targetId: r,
432
- event: e.event,
433
- eventId: e.eventId ?? E(),
434
- payloadJson: typeof e.payload == "string" ? e.payload : JSON.stringify(e.payload),
435
- attemptEpoch: i
436
- });
437
- }
438
- },
439
- deleteTarget: async (t) => {
440
- for (let n of await a.resolveTargets(t)) await e.store.delete("_voltro_webhook_targets", n), await e.store.deleteMany("_voltro_webhook_deliveries", { where: D(O("targetId", n), O("status", "pending")) });
441
- },
442
- rotateSecret: async (t) => {
443
- let n = i();
444
- for (let r of await a.resolveTargets(t)) await e.store.update("_voltro_webhook_targets", r, {
445
- secret: n,
446
- updatedAt: /* @__PURE__ */ new Date()
447
- });
448
- return { secret: n };
449
- },
450
- updateTarget: async (t, n) => {
451
- if (n.url !== void 0 && R({
452
- event: "x",
453
- url: n.url
454
- }), 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");
455
- let r = { updatedAt: /* @__PURE__ */ new Date() };
456
- for (let e of [
457
- "url",
458
- "description",
459
- "filter",
460
- "scope",
461
- "headers",
462
- "rateLimitPerMinute",
463
- "autoDisableAfter",
464
- "format"
465
- ]) n[e] !== void 0 && (r[e] = n[e]);
466
- for (let n of await a.resolveTargets(t)) await e.store.update("_voltro_webhook_targets", n, r);
467
- },
468
- testTarget: async (n, r) => {
469
- let i = (await e.store.query({
470
- table: "_voltro_webhook_targets",
471
- predicate: O("id", n),
472
- order: [],
473
- take: 1,
474
- skip: void 0,
475
- projection: void 0
476
- }))[0];
477
- if (i === void 0) throw new C({ targetId: n });
478
- let a = E(), o = JSON.stringify(r ?? {
479
- test: !0,
480
- targetId: n,
481
- at: (/* @__PURE__ */ new Date()).toISOString()
482
- });
483
- return i.active ? await t({
484
- deliveryId: a,
485
- targetId: i.id,
486
- event: i.event,
487
- eventId: E(),
488
- payloadJson: o
489
- }) : await e.store.insert("_voltro_webhook_deliveries", {
490
- id: `${a}:1`,
491
- deliveryId: a,
492
- targetId: i.id,
493
- tenantId: i.tenantId ?? null,
494
- event: i.event,
495
- eventId: null,
496
- attempt: 1,
497
- status: "pending",
498
- payload: o,
499
- scheduledAt: /* @__PURE__ */ new Date(),
500
- createdAt: /* @__PURE__ */ new Date(),
501
- updatedAt: /* @__PURE__ */ new Date()
502
- }), { deliveryId: a };
503
- },
504
- listDeliveries: async (t) => {
505
- let n = t?.scope === void 0 ? void 0 : new Set(await a.resolveTargets({ scope: t.scope })), r = await e.store.query({
506
- table: "_voltro_webhook_deliveries",
507
- predicate: t?.targetId === void 0 ? void 0 : O("targetId", t.targetId),
508
- order: [{
509
- column: "createdAt",
510
- direction: "desc"
511
- }],
512
- take: Math.min((t?.limit ?? 200) * (t?.status !== void 0 || t?.since !== void 0 ? 10 : 1), 5e3),
513
- skip: void 0,
514
- projection: void 0
515
- }), i = t?.since?.getTime();
516
- return r.filter((e) => n === void 0 || n.has(String(e.targetId))).filter((e) => t?.status === void 0 || e.status === t.status).filter((e) => i === void 0 || Me(e.createdAt) >= i).slice(0, t?.limit ?? 200).map(V);
517
- },
518
- getDelivery: async (t) => {
519
- let n = (await e.store.query({
520
- table: "_voltro_webhook_deliveries",
521
- predicate: O("id", t),
522
- order: [],
523
- take: 1,
524
- skip: void 0,
525
- projection: void 0
526
- }))[0];
527
- return n === void 0 ? null : {
528
- ...V(n),
529
- payload: Ne(n.payload),
530
- responseBody: n.responseBody ?? null
531
- };
532
- },
533
- updateTargetPayloadVersion: async (t, n) => {
534
- if (n < 1 || !Number.isInteger(n)) throw new x({ version: n });
535
- await a.resolveTargets(t), await e.store.update("_voltro_webhook_targets", t, {
536
- payloadVersion: n,
537
- updatedAt: /* @__PURE__ */ new Date()
538
- });
539
- },
540
- listTargets: async (t, n) => {
541
- let r = await e.store.query({
542
- table: "_voltro_webhook_targets",
543
- predicate: t === void 0 ? void 0 : O("event", t),
544
- order: [{
545
- column: "createdAt",
546
- direction: "desc"
547
- }],
548
- take: 500,
549
- skip: void 0,
550
- projection: void 0
551
- });
552
- return (n === void 0 ? r : r.filter((e) => e.scope !== null && Object.entries(n).every(([t, n]) => e.scope?.[t] === n))).map((e) => ({
553
- id: e.id,
554
- event: e.event,
555
- url: e.url,
556
- active: e.active,
557
- rateLimitPerMinute: e.rateLimitPerMinute,
558
- description: e.description,
559
- payloadVersion: e.payloadVersion,
560
- autoDisableAfter: e.autoDisableAfter,
561
- consecutiveFailures: e.consecutiveFailures ?? 0,
562
- autoDisabledAt: e.autoDisabledAt === null || e.autoDisabledAt === void 0 ? null : e.autoDisabledAt instanceof Date ? e.autoDisabledAt.toISOString() : String(e.autoDisabledAt),
563
- autoDisableReason: e.autoDisableReason,
564
- scope: e.scope,
565
- filter: e.filter ?? null
566
- }));
567
- }
568
- };
569
- return a;
570
- }, Ve = w.gen(function* () {
571
- return yield* Oe;
572
- }), He = (e) => {
573
- if (e.webhooks === void 0) throw Error("useWebhooks: `ctx.webhooks` is not set. Declare an outgoing event with a `webhook:` block in a *.event.ts file, or an incoming endpoint in a *.webhook.tsx file — the service is wired the moment the app has either.");
574
- return e.webhooks;
575
- }, Ue = 1e4, We = class {
576
- capacity;
577
- cache = /* @__PURE__ */ new Map();
578
- constructor(e = Ue) {
579
- this.capacity = e;
580
- }
581
- evictExpired() {
582
- let e = Date.now();
583
- for (let [t, n] of this.cache) n.expiresAt < e && this.cache.delete(t);
584
- }
585
- claim(e, t) {
586
- let n = this.cache.get(e);
587
- if (n !== void 0 && n.expiresAt > Date.now()) return this.cache.delete(e), this.cache.set(e, n), n.value === "inflight" ? "inflight" : "duplicate";
588
- if (this.cache.set(e, {
589
- value: "inflight",
590
- expiresAt: Date.now() + t
591
- }), this.cache.size > this.capacity) {
592
- let e = this.cache.keys().next().value;
593
- e !== void 0 && this.cache.delete(e);
594
- }
595
- return "fresh";
596
- }
597
- commit(e) {
598
- let t = this.cache.get(e);
599
- t !== void 0 && this.cache.set(e, {
600
- value: "processed",
601
- expiresAt: t.expiresAt
602
- });
603
- }
604
- release(e) {
605
- this.cache.delete(e);
606
- }
607
- size() {
608
- return this.evictExpired(), this.cache.size;
609
- }
610
- clear() {
611
- this.cache.clear();
612
- }
613
- }, q = null, Ge = () => (q === null && (q = new We()), q), J = {
614
- "5m": 3e5,
615
- "1h": 36e5,
616
- "6h": 216e5,
617
- "1d": 864e5,
618
- "7d": 6048e5,
619
- "30d": 2592e6
620
- }, Ke = (e) => e === void 0 ? J["7d"] : J[e] ?? J["7d"], Y = (e, t) => ({
621
- status: e,
622
- contentType: "application/json; charset=utf-8",
623
- body: JSON.stringify(t)
624
- }), qe = "voltroWebhookVerification", Je = class extends Error {
625
- webhookId;
626
- name = "UnverifiedIncomingWebhook";
627
- constructor(e) {
628
- super(`incoming webhook '${e}' declares no verification. It would be mounted as a public, unauthenticated POST that runs your application code, so the framework refuses to mount it. Pick one on the descriptor:\n · provider: stripeWebhookProvider() — a preset (HMAC scheme + idempotency + body type)\n · signature: { _tag: 'hmac', algorithm: 'hmacSha256', header: 'X-Signature', encoding: 'hex', includeTimestamp: true }\n · verification: 'provider' — the handler verifies with the provider's own SDK\n · verification: 'none' — deliberately public; a gateway / IP allow-list owns it\nA signature-verified webhook reads its shared secret from VOLTRO_WEBHOOK_SECRET_${e.toUpperCase().replace(/[^A-Z0-9]/g, "_")}. The framework never invents that value — the sender holds the other half of it.`), this.webhookId = e;
629
- }
630
- }, Ye = (e) => `VOLTRO_WEBHOOK_SECRET_${e.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`, Xe = (e) => {
631
- let t = e.signature ?? e.provider?.signature ?? null;
632
- return (e.verification === void 0 || e.verification === "signature") && t === null ? null : e.verification ?? "signature";
633
- }, Ze = (e, t) => {
634
- let n = e.provider, r = e.signature ?? n?.signature ?? null, i = e.idempotency ?? n?.idempotency ?? null, a = e.bodyType ?? n?.bodyType ?? "json", o = Xe(e);
635
- if (o === null) throw new Je(e.id);
636
- let c = o;
637
- c === "none" && A({ scope: `webhook:${e.id}` }).warn("incoming webhook mounted WITHOUT verification — anyone who can reach this URL can run its handler", {
638
- webhookId: e.id,
639
- verification: "none"
640
- });
641
- let l = t.idempotencyCache ?? Ge(), u = Ke(i?.ttl), d = t.log ?? ((e) => {
642
- A({ scope: `webhook:${e.webhookId}` }).debug("incoming webhook", {
643
- status: e.status,
644
- signatureOk: e.signatureOk,
645
- idempotency: e.idempotency,
646
- durationMs: e.durationMs,
647
- ...e.errorMessage === void 0 ? {} : { errorMessage: e.errorMessage }
648
- });
649
- });
650
- return Object.assign(async (n) => {
651
- let o = Date.now();
652
- if (n.method !== "POST") {
653
- let t = Y(405, {
654
- error: "method not allowed",
655
- expected: "POST"
656
- });
657
- return d({
658
- webhookId: e.id,
659
- status: 405,
660
- signatureOk: "skipped",
661
- idempotency: "skipped",
662
- durationMs: Date.now() - o
663
- }), t;
664
- }
665
- let f = "skipped";
666
- if (r !== null && c === "signature") {
667
- let i = await t.resolveSecret(e.id);
668
- if (i === null) {
669
- let t = `no signing secret configured — set ${Ye(e.id)}`, n = Y(503, {
670
- error: "webhook not configured",
671
- reason: t
672
- });
673
- return d({
674
- webhookId: e.id,
675
- status: 503,
676
- signatureOk: !1,
677
- idempotency: "skipped",
678
- durationMs: Date.now() - o,
679
- errorMessage: t
680
- }), n;
681
- }
682
- {
683
- let t = s(r, {
684
- rawBody: n.rawBody,
685
- secret: i,
686
- headers: n.headers
687
- });
688
- if (f = t.ok, !t.ok) {
689
- let n = Y(401, {
690
- error: "invalid signature",
691
- reason: t.reason
692
- });
693
- return d({
694
- webhookId: e.id,
695
- status: 401,
696
- signatureOk: !1,
697
- idempotency: "skipped",
698
- durationMs: Date.now() - o,
699
- errorMessage: t.reason
700
- }), n;
701
- }
702
- }
703
- }
704
- let p;
705
- try {
706
- if (a === "json") {
707
- let e = new TextDecoder().decode(n.rawBody);
708
- p = e.length === 0 ? null : JSON.parse(e);
709
- } else if (a === "form") {
710
- let e = new TextDecoder().decode(n.rawBody), t = new URLSearchParams(e), r = {};
711
- for (let [e, n] of t.entries()) r[e] = n;
712
- p = r;
713
- } else p = n.rawBody;
714
- } catch (t) {
715
- let n = Y(400, {
716
- error: "malformed body",
717
- reason: t.message
718
- });
719
- return d({
720
- webhookId: e.id,
721
- status: 400,
722
- signatureOk: f,
723
- idempotency: "skipped",
724
- durationMs: Date.now() - o,
725
- errorMessage: t.message
726
- }), n;
727
- }
728
- let m;
729
- try {
730
- m = T.decodeUnknownSync(e.payload)(p);
731
- } catch (t) {
732
- let n = Y(422, {
733
- error: "schema validation failed",
734
- reason: t.message
735
- });
736
- return d({
737
- webhookId: e.id,
738
- status: 422,
739
- signatureOk: f,
740
- idempotency: "skipped",
741
- durationMs: Date.now() - o,
742
- errorMessage: t.message
743
- }), n;
744
- }
745
- let h, g = "skipped";
746
- if (i !== null) {
747
- let t = i.from;
748
- if (h = typeof t == "string" ? n.headers[t.toLowerCase()] : t(n.headers, n.rawBody), h !== void 0 && h.length > 0) {
749
- let t = l.claim(h, u);
750
- if (g = t, t === "duplicate") {
751
- let t = Y(200, {
752
- received: !0,
753
- duplicate: !0
754
- });
755
- return d({
756
- webhookId: e.id,
757
- status: 200,
758
- signatureOk: f,
759
- idempotency: "duplicate",
760
- durationMs: Date.now() - o
761
- }), t;
762
- }
763
- if (t === "inflight") {
764
- let t = Y(409, {
765
- error: "request already in flight",
766
- idempotencyKey: h
767
- });
768
- return d({
769
- webhookId: e.id,
770
- status: 409,
771
- signatureOk: f,
772
- idempotency: "inflight",
773
- durationMs: Date.now() - o
774
- }), t;
775
- }
776
- }
777
- }
778
- try {
779
- let r = t.resolveWorkflows?.();
780
- await e.handler({
781
- body: m,
782
- rawBody: n.rawBody,
783
- headers: n.headers,
784
- idempotencyKey: h ?? "",
785
- ...r ? { workflows: r } : {}
786
- }), h !== void 0 && l.commit(h);
787
- let i = Y(200, { received: !0 });
788
- return d({
789
- webhookId: e.id,
790
- status: 200,
791
- signatureOk: f,
792
- idempotency: g,
793
- durationMs: Date.now() - o
794
- }), i;
795
- } catch (t) {
796
- h !== void 0 && l.release(h);
797
- let n = Y(500, {
798
- error: "handler threw",
799
- reason: t.message
800
- });
801
- return d({
802
- webhookId: e.id,
803
- status: 500,
804
- signatureOk: f,
805
- idempotency: g,
806
- durationMs: Date.now() - o,
807
- errorMessage: t.message
808
- }), ge({
809
- error: t,
810
- source: "webhook",
811
- name: e.id,
812
- fields: { status: 500 }
813
- }), n;
814
- }
815
- }, { [qe]: c });
816
- }, Qe = (e) => `/webhooks/${e}`, $e = "_voltro_webhook_rate_windows", et = 6e4, tt = (e, t) => `${e}@${t}`, nt = async (e, t, n) => (await e.query({
817
- table: "_voltro_webhook_rate_windows",
818
- predicate: D(O("scope", t), O("bucket", n)),
819
- order: [],
820
- take: 1,
821
- skip: void 0,
822
- projection: void 0
823
- }))[0] ?? null, X = 32, rt = async (e, t, n) => {
824
- for (let r = 0; r < X; r++) {
825
- r > 0 && await new Promise((e) => setTimeout(e, r));
826
- let i = await nt(e, t.key, n);
827
- if (i === null) {
828
- await e.insertIgnore($e, {
829
- id: tt(t.key, n),
830
- scope: t.key,
831
- bucket: n,
832
- count: 0,
833
- createdAt: /* @__PURE__ */ new Date(),
834
- updatedAt: /* @__PURE__ */ new Date()
835
- }, { conflictColumns: ["id"] });
836
- continue;
837
- }
838
- if (i.count >= t.limit) return !1;
839
- if (await e.updateMany("_voltro_webhook_rate_windows", {
840
- count: i.count + 1,
841
- updatedAt: /* @__PURE__ */ new Date()
842
- }, { where: D(O("id", i.id), O("count", i.count)) }) === 1) return !0;
843
- }
844
- throw Error(`webhook rate window: contention on (${t.key}, ${n}) exceeded ${X} attempts`);
845
- }, it = async (e, t, n) => {
846
- for (let r = 0; r < X; r++) {
847
- r > 0 && await new Promise((e) => setTimeout(e, r));
848
- let i = await nt(e, t, n);
849
- if (i === null || i.count <= 0 || await e.updateMany("_voltro_webhook_rate_windows", {
850
- count: i.count - 1,
851
- updatedAt: /* @__PURE__ */ new Date()
852
- }, { where: D(O("id", i.id), O("count", i.count)) }) === 1) return;
853
- }
854
- throw Error(`webhook rate window: release contention on (${t}) exceeded ${X} attempts`);
855
- }, at = async (e, t, n, r = et) => {
856
- if (t.length === 0) return {
857
- acquired: !0,
858
- retryInMs: 0
859
- };
860
- let i = Math.floor(n / r), a = [];
861
- for (let o of t) {
862
- if (await rt(e, o, i)) {
863
- a.push(o);
864
- continue;
865
- }
866
- for (let t of a) await it(e, t.key, i);
867
- return {
868
- acquired: !1,
869
- retryInMs: (i + 1) * r - n
870
- };
871
- }
872
- return {
873
- acquired: !0,
874
- retryInMs: 0
875
- };
876
- }, ot = (e, t) => {
877
- if (e === "json") return {
878
- contentType: "application/json",
879
- bytes: Buffer.from(t, "utf8")
880
- };
881
- let n;
882
- try {
883
- n = JSON.parse(t);
884
- } catch (t) {
885
- throw new b({
886
- format: e,
887
- reason: `payload is not valid JSON: ${t.message}`
888
- });
889
- }
890
- return e === "form" ? {
891
- contentType: "application/x-www-form-urlencoded",
892
- bytes: Buffer.from(st(n), "utf8")
893
- } : {
894
- contentType: "application/xml",
895
- bytes: Buffer.from(lt(n), "utf8")
896
- };
897
- }, st = (e) => {
898
- if (typeof e != "object" || !e || Array.isArray(e)) throw new b({
899
- format: "form",
900
- reason: `form encoding requires a JSON object at the top level, got ${ft(e)}`
901
- });
902
- let t = new URLSearchParams();
903
- for (let [n, r] of Object.entries(e)) Z(t, n, r);
904
- return t.toString();
905
- }, Z = (e, t, n) => {
906
- if (n === null) {
907
- e.append(t, "");
908
- return;
909
- }
910
- if (Array.isArray(n)) {
911
- n.forEach((n, r) => Z(e, `${t}[${r}]`, n));
912
- return;
913
- }
914
- if (typeof n == "object") {
915
- for (let [r, i] of Object.entries(n)) Z(e, `${t}[${r}]`, i);
916
- return;
917
- }
918
- e.append(t, String(n));
919
- }, ct = /^[A-Za-z_][A-Za-z0-9_.-]*$/, lt = (e) => `<?xml version="1.0" encoding="UTF-8"?>${Q("webhook", e)}`, Q = (e, t) => {
920
- if (!ct.test(e)) throw new b({
921
- format: "xml",
922
- reason: `object key ${JSON.stringify(e)} is not a valid XML element name`
923
- });
924
- return `<${e}>${ut(e, t)}</${e}>`;
925
- }, ut = (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("") : dt(String(t)), dt = (e) => e.replace(/[&<>"']/g, (e) => {
926
- switch (e) {
927
- case "&": return "&amp;";
928
- case "<": return "&lt;";
929
- case ">": return "&gt;";
930
- case "\"": return "&quot;";
931
- default: return "&apos;";
932
- }
933
- }), ft = (e) => e === null ? "null" : Array.isArray(e) ? "array" : typeof e, pt = "_voltro_webhook_targets", mt = 32, ht = async (e, t) => (await e.query({
934
- table: "_voltro_webhook_targets",
935
- predicate: O("id", t),
936
- order: [],
937
- take: 1,
938
- skip: void 0,
939
- projection: void 0
940
- }))[0] ?? null, gt = async (e, t, n, r, i = /* @__PURE__ */ new Date(), a = !1) => {
941
- for (let o = 0; o < mt; o++) {
942
- o > 0 && await new Promise((e) => setTimeout(e, o));
943
- let s = await ht(e, t);
944
- if (s === null) return {
945
- consecutiveFailures: 0,
946
- autoDisabled: !1
947
- };
948
- let c = s.consecutiveFailures ?? 0;
949
- if (n) {
950
- if (c === 0 || await e.updateMany("_voltro_webhook_targets", {
951
- consecutiveFailures: 0,
952
- updatedAt: i
953
- }, { where: D(O("id", t), O("consecutiveFailures", c)) }) === 1) return {
954
- consecutiveFailures: 0,
955
- autoDisabled: !1
956
- };
957
- continue;
958
- }
959
- let l = c + 1, u = s.autoDisableAfter, d = s.active && (a || typeof u == "number" && u > 0 && l >= u), f = {
960
- consecutiveFailures: l,
961
- updatedAt: i
962
- };
963
- if (d && (f.active = !1, f.autoDisabledAt = i, f.autoDisableReason = r.slice(0, 500)), await e.updateMany("_voltro_webhook_targets", f, { where: D(O("id", t), O("consecutiveFailures", c)) }) === 1) return {
964
- consecutiveFailures: l,
965
- autoDisabled: d
966
- };
967
- }
968
- throw Error(`webhook auto-disable: streak CAS contention on target ${t} exceeded ${mt} attempts`);
969
- }, _t = T.Struct({
970
- status: T.Number,
971
- body: T.String,
972
- latencyMs: T.Number,
973
- retryAfterSec: T.Union([T.Number, T.Undefined]),
974
- transportError: T.Union([T.String, T.Undefined]),
975
- encodeError: T.Union([T.String, T.Undefined])
976
- }), vt = T.Struct({
977
- outcome: T.Literals([
978
- "succeeded",
979
- "failed",
980
- "retry"
981
- ]),
982
- delayMs: T.Union([T.Number, T.Undefined]),
983
- reason: T.Union([T.String, T.Undefined]),
984
- autoDisabled: T.Union([T.Boolean, T.Undefined]),
985
- consecutiveFailures: T.Union([T.Number, T.Undefined])
986
- }), yt = T.Struct({
987
- acquired: T.Boolean,
988
- retryInMs: T.Number
989
- }), bt = {
990
- deliveryId: T.String,
991
- targetId: T.String,
992
- event: T.String,
993
- eventId: T.String,
994
- payloadJson: T.String,
995
- attemptEpoch: T.optional(T.Number)
996
- }, xt = ye.make("voltro.deliverWebhook", {
997
- payload: bt,
998
- success: T.Struct({
999
- finalStatus: T.Literals([
1000
- "succeeded",
1001
- "failed",
1002
- "deferred"
1003
- ]),
1004
- attempts: T.Number
1005
- }),
1006
- idempotencyKey: ({ deliveryId: e, attemptEpoch: t }) => `voltro.deliverWebhook:${e}:${t ?? 0}`
1007
- }), St = 3e4, Ct = (e, t) => {
1008
- if (e !== void 0) return e;
1009
- let n = Number(t.VOLTRO_WEBHOOK_TIMEOUT_MS);
1010
- return Number.isFinite(n) && n > 0 ? n : St;
1011
- }, wt = (e, t = {}) => (n, r) => {
1012
- let { deliveryId: i, targetId: a, event: o, eventId: s, payloadJson: c } = n, l = A({ scope: `webhook:${i}` });
1013
- return w.gen(function* () {
1014
- let n = yield* j.make({
1015
- name: "fetch-target",
1016
- success: T.Union([T.Null, T.Struct({
1017
- id: T.String,
1018
- event: T.String,
1019
- url: T.String,
1020
- secret: T.String,
1021
- signing: T.Any,
1022
- retry: T.Any,
1023
- headers: T.Union([T.Record(T.String, T.String), T.Null]),
1024
- rateLimitPerMinute: T.Union([T.Number, T.Null]),
1025
- active: T.Boolean,
1026
- format: T.Literals([
1027
- "json",
1028
- "form",
1029
- "xml"
1030
- ]),
1031
- tenantId: T.Union([T.String, T.Null])
1032
- })]),
1033
- execute: w.tryPromise({
1034
- try: async () => (await e.store.query({
1035
- table: "_voltro_webhook_targets",
1036
- predicate: O("id", a),
1037
- order: [{
1038
- column: "createdAt",
1039
- direction: "asc"
1040
- }],
1041
- take: 1,
1042
- skip: void 0,
1043
- projection: void 0
1044
- }))[0] ?? null,
1045
- catch: (e) => (l.debug("fetch-target raw error", {
1046
- targetId: a,
1047
- event: o,
1048
- cause: String(e)
1049
- }), /* @__PURE__ */ Error(`fetch-target failed: ${e.message}`))
1050
- }).pipe(w.orDie)
1051
- });
1052
- if (n === null) return {
1053
- finalStatus: "failed",
1054
- attempts: 0
1055
- };
1056
- let r = async (t, r, l) => {
1057
- await e.store.update("_voltro_webhook_deliveries", t, {
1058
- ...l,
1059
- updatedAt: /* @__PURE__ */ new Date()
1060
- }) === null && await e.store.insert("_voltro_webhook_deliveries", {
1061
- id: t,
1062
- deliveryId: i,
1063
- targetId: a,
1064
- tenantId: n.tenantId ?? null,
1065
- event: o,
1066
- eventId: s,
1067
- attempt: r,
1068
- payload: c,
1069
- scheduledAt: /* @__PURE__ */ new Date(),
1070
- createdAt: /* @__PURE__ */ new Date(),
1071
- updatedAt: /* @__PURE__ */ new Date(),
1072
- ...l
1073
- });
1074
- };
1075
- if (n.active !== !0) return yield* j.make({
1076
- name: "record-paused-pending",
1077
- success: T.Void,
1078
- execute: w.tryPromise({
1079
- try: () => r(`${i}:1`, 1, { status: "pending" }),
1080
- catch: (e) => /* @__PURE__ */ Error(`record-paused-pending failed: ${e.message}`)
1081
- }).pipe(w.orDie)
1082
- }), {
1083
- finalStatus: "deferred",
1084
- attempts: 0
1085
- };
1086
- let u = (e) => typeof e == "string" ? JSON.parse(e) : e, d = u(n.retry), p = u(n.signing), m = n.headers === null ? null : u(n.headers), h = t.rateWindowMs ?? 6e4, g = Ct(t.timeoutMs, process.env), _ = t.events?.find((e) => e.id === o)?.globalRateLimit, v = [...typeof n.rateLimitPerMinute == "number" && n.rateLimitPerMinute > 0 ? [{
1087
- key: `target:${n.id}`,
1088
- limit: n.rateLimitPerMinute
1089
- }] : [], ..._ !== void 0 && _.perMinute > 0 ? [{
1090
- key: `event:${o}`,
1091
- limit: _.perMinute
1092
- }] : []];
1093
- for (let t = 1; t <= d.maxAttempts; t++) {
1094
- let u = `${i}:${t}`;
1095
- if (v.length > 0) for (let n = 0;; n++) {
1096
- let i = yield* j.make({
1097
- name: `rate-acquire-${t}-${n}`,
1098
- success: yt,
1099
- execute: w.tryPromise({
1100
- try: () => at(e.store, v, Date.now(), h),
1101
- catch: (e) => (l.debug("rate-acquire raw error", {
1102
- targetId: a,
1103
- event: o,
1104
- attempt: t,
1105
- cause: String(e)
1106
- }), /* @__PURE__ */ Error(`rate-acquire failed: ${e.message}`))
1107
- }).pipe(w.orDie)
1108
- });
1109
- if (i.acquired) break;
1110
- yield* j.make({
1111
- name: `record-rate-deferral-${t}-${n}`,
1112
- success: T.Void,
1113
- execute: w.tryPromise({
1114
- try: () => r(u, t, {
1115
- status: "pending",
1116
- nextAttemptAt: new Date(Date.now() + i.retryInMs)
1117
- }),
1118
- catch: (e) => /* @__PURE__ */ Error(`record-rate-deferral failed: ${e.message}`)
1119
- }).pipe(w.orDie)
1120
- }), yield* ve.sleep({
1121
- name: `rate-window-sleep-${t}-${n}`,
1122
- duration: `${i.retryInMs} millis`
1123
- });
1124
- }
1125
- yield* j.make({
1126
- name: `record-attempt-start-${t}`,
1127
- success: T.Void,
1128
- execute: w.tryPromise({
1129
- try: () => r(u, t, {
1130
- status: "inFlight",
1131
- nextAttemptAt: null,
1132
- scheduledAt: /* @__PURE__ */ new Date()
1133
- }),
1134
- catch: (e) => /* @__PURE__ */ Error(`record-attempt-start failed: ${e.message}`)
1135
- }).pipe(w.orDie)
1136
- });
1137
- let _ = yield* j.make({
1138
- name: `sign-and-post-${t}`,
1139
- success: _t,
1140
- execute: w.promise(async () => {
1141
- let e = Date.now(), r;
1142
- try {
1143
- r = ot(n.format, c);
1144
- } catch (t) {
1145
- if (t instanceof b) return {
1146
- status: 0,
1147
- body: "",
1148
- latencyMs: Date.now() - e,
1149
- retryAfterSec: void 0,
1150
- transportError: void 0,
1151
- encodeError: t.reason
1152
- };
1153
- throw t;
1154
- }
1155
- let a = Buffer.from(r.bytes), { headers: l } = f(p, {
1156
- rawBody: a,
1157
- secret: n.secret,
1158
- messageId: i
1159
- });
1160
- process.env.NODE_ENV === "production" && k(n.url);
1161
- try {
1162
- let c = await fetch(n.url, {
1163
- method: "POST",
1164
- headers: {
1165
- "content-type": r.contentType,
1166
- ...l,
1167
- "x-voltro-event": o,
1168
- "x-voltro-event-id": s,
1169
- "x-voltro-delivery-id": i,
1170
- "x-voltro-attempt": String(t),
1171
- "idempotency-key": i,
1172
- ...m ?? {}
1173
- },
1174
- body: a,
1175
- redirect: "manual",
1176
- signal: AbortSignal.timeout(g)
1177
- }), u = (await c.text()).slice(0, 8192), d = c.headers.get("retry-after");
1178
- return {
1179
- status: c.status,
1180
- body: u,
1181
- latencyMs: Date.now() - e,
1182
- retryAfterSec: d !== null && /^\d+$/.test(d) ? Number(d) : void 0,
1183
- transportError: void 0,
1184
- encodeError: void 0
1185
- };
1186
- } catch (t) {
1187
- return {
1188
- status: 0,
1189
- body: "",
1190
- latencyMs: Date.now() - e,
1191
- retryAfterSec: void 0,
1192
- transportError: t.message,
1193
- encodeError: void 0
1194
- };
1195
- }
1196
- })
1197
- }), y = yield* j.make({
1198
- name: `classify-and-record-${t}`,
1199
- success: vt,
1200
- execute: w.tryPromise({
1201
- try: async () => {
1202
- let r = _.status >= 200 && _.status < 300, i = _.transportError !== void 0, o = _.encodeError !== void 0, s = _.status === 410, c = (t, n) => gt(e.store, a, t, n, /* @__PURE__ */ new Date(), !t && s);
1203
- if (r) {
1204
- await e.store.update("_voltro_webhook_deliveries", u, {
1205
- status: "succeeded",
1206
- responseStatus: _.status,
1207
- responseBody: _.body,
1208
- latencyMs: _.latencyMs,
1209
- updatedAt: /* @__PURE__ */ new Date()
1210
- });
1211
- let t = await c(!0, "");
1212
- return {
1213
- outcome: "succeeded",
1214
- delayMs: void 0,
1215
- reason: void 0,
1216
- autoDisabled: t.autoDisabled,
1217
- consecutiveFailures: t.consecutiveFailures
1218
- };
1219
- }
1220
- let l = o ? null : Te(d, t, {
1221
- ...i ? {} : { status: _.status },
1222
- ..._.retryAfterSec === void 0 ? {} : { retryAfterSeconds: _.retryAfterSec }
1223
- });
1224
- if (l === null) {
1225
- let t = o ? `encode (${n.format}): ${_.encodeError}` : i ? `transport: ${_.transportError}` : `http ${_.status}: ${_.body.slice(0, 200)}`;
1226
- await e.store.update("_voltro_webhook_deliveries", u, {
1227
- status: "failed",
1228
- responseStatus: i || o ? null : _.status,
1229
- responseBody: i || o ? null : _.body,
1230
- errorMessage: o ? t : _.transportError ?? null,
1231
- latencyMs: _.latencyMs,
1232
- updatedAt: /* @__PURE__ */ new Date()
1233
- });
1234
- let r = await c(!1, t);
1235
- return {
1236
- outcome: "failed",
1237
- delayMs: void 0,
1238
- reason: t,
1239
- autoDisabled: r.autoDisabled,
1240
- consecutiveFailures: r.consecutiveFailures
1241
- };
1242
- }
1243
- return await e.store.update("_voltro_webhook_deliveries", u, {
1244
- status: "retryScheduled",
1245
- responseStatus: i ? null : _.status,
1246
- responseBody: i ? null : _.body,
1247
- errorMessage: _.transportError ?? null,
1248
- latencyMs: _.latencyMs,
1249
- nextAttemptAt: new Date(Date.now() + l.delayMs),
1250
- updatedAt: /* @__PURE__ */ new Date()
1251
- }), {
1252
- outcome: "retry",
1253
- delayMs: l.delayMs,
1254
- reason: void 0,
1255
- autoDisabled: void 0,
1256
- consecutiveFailures: void 0
1257
- };
1258
- },
1259
- catch: (e) => (l.debug("classify-and-record raw error", {
1260
- targetId: a,
1261
- event: o,
1262
- attempt: t,
1263
- cause: String(e)
1264
- }), /* @__PURE__ */ Error(`classify-and-record failed: ${e.message}`))
1265
- }).pipe(w.orDie)
1266
- });
1267
- if (y.autoDisabled === !0 && l.warn("target auto-disabled after consecutive failures", {
1268
- targetId: a,
1269
- event: o,
1270
- consecutiveFailures: y.consecutiveFailures ?? null,
1271
- reason: y.reason ?? null
1272
- }), y.outcome === "succeeded") return {
1273
- finalStatus: "succeeded",
1274
- attempts: t
1275
- };
1276
- if (y.outcome === "failed") return {
1277
- finalStatus: "failed",
1278
- attempts: t
1279
- };
1280
- y.delayMs !== void 0 && (yield* ve.sleep({
1281
- name: `retry-sleep-${t}`,
1282
- duration: `${y.delayMs} millis`
1283
- }));
1284
- }
1285
- return {
1286
- finalStatus: "failed",
1287
- attempts: d.maxAttempts
1288
- };
1289
- });
1290
- }, $ = "voltro.webhook.emit", Tt = (e) => typeof e.event == "string", Et = (e) => be({
1291
- effect: $,
1292
- maxAttempts: 1,
1293
- handler: async ({ payload: t, outboxId: n }) => {
1294
- if (!Tt(t)) throw Error(`${$}: row payload has no \`event\` name — cannot resolve a descriptor. Got keys: ${Object.keys(t).join(", ") || "(none)"}`);
1295
- let r = e();
1296
- if (r === 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.`);
1297
- await r.emit(t.event, t.payload, {
1298
- tenantId: t.tenantId,
1299
- eventId: n
1300
- });
1301
- }
1302
- });
1303
- //#endregion
1304
- export { We as IdempotencyCache, et as RATE_WINDOW_MS, $e as RATE_WINDOW_TABLE, p as STANDARD_WEBHOOKS_DEFAULT_TOLERANCE_SECONDS, te as STANDARD_WEBHOOKS_ID_HEADER, c as STANDARD_WEBHOOKS_MAX_KEY_BYTES, l as STANDARD_WEBHOOKS_MIN_KEY_BYTES, _ as STANDARD_WEBHOOKS_SECRET_PREFIX, m as STANDARD_WEBHOOKS_SIGNATURE_HEADER, d as STANDARD_WEBHOOKS_TIMESTAMP_HEADER, u as STANDARD_WEBHOOKS_VERSION, pt as TARGETS_TABLE, Je as UnverifiedIncomingWebhook, M as WEBHOOKS_EGRESS, xe as WEBHOOKS_PLUGIN_PERMISSIONS, $ as WEBHOOK_EMIT_EFFECT, qe as WEBHOOK_VERIFICATION_PROPERTY, oe as WebhookDeliveryNotFound, se as WebhookPayloadInvalid, b as WebhookPayloadUnrepresentable, x as WebhookPayloadVersionInvalid, S as WebhookSubscribeInvalid, C as WebhookTargetNotFound, Oe as WebhooksService, ce as _voltroWebhookDeliveriesTable, le as _voltroWebhookEventStatsTable, ue as _voltroWebhookRateWindowsTable, de as _voltroWebhookTargetsTable, at as acquireRateSlots, N as asOutgoingEvent, wt as buildDeliverWebhookExecute, Be as buildWebhooksService, Pe as compareVersions, rt as consumeRateSlot, Qe as defaultIncomingPath, ee as defaultOutgoingSignature, Ee as defaultRetryPolicy, re as defineIncomingWebhook, r as defineWebhookProvider, xt as deliverWebhookWorkflow, ot as encodePayload, I as eventsOf, De as fastRetryPolicy, je as generateSecret, i as generateStandardWebhooksSecret, h as genericHmacSignature, Ge as getIdempotencyCache, v as githubSignature, Ce as hasWebhookAudience, Xe as incomingWebhookVerification, ne as isStandardWebhooksSecret, t as isWebhookDescriptor, Et as makeDeferredEmitHandler, H as matchesFilter, Ze as mountIncomingWebhook, Te as nextRetry, P as parseDuration, Ke as parseTtl, gt as recordDeliveryOutcome, it as releaseRateSlot, z as resolveSubscribe, f as signRequest, a as slackSignature, ae as standardWebhooksHeaders, o as standardWebhooksKey, g as standardWebhooksSignature, ie as standardWebhooksSignatureToken, n as standardWebhooksSignedContent, y as stripeSignature, He as useWebhooks, Ve as useWebhooksEffect, R as validateSubscribe, s as verifyRequest, e as verifyStandardWebhooks, fe as webhookTables, Se as webhooksPlugin };
1
+ import { WEBHOOKS_EGRESS as e, WEBHOOKS_PLUGIN_PERMISSIONS as t, webhooksPlugin as n } from "./plugin.js";
2
+ import { defineIncomingWebhook as r, defineWebhookProvider as i, isWebhookDescriptor as a } from "./dsl.js";
3
+ import { asOutgoingEvent as o, hasWebhookAudience as s } from "./eventAudience.js";
4
+ import { STANDARD_WEBHOOKS_DEFAULT_TOLERANCE_SECONDS as c, STANDARD_WEBHOOKS_ID_HEADER as l, STANDARD_WEBHOOKS_MAX_KEY_BYTES as u, STANDARD_WEBHOOKS_MIN_KEY_BYTES as d, STANDARD_WEBHOOKS_SECRET_PREFIX as f, STANDARD_WEBHOOKS_SIGNATURE_HEADER as p, STANDARD_WEBHOOKS_TIMESTAMP_HEADER as m, STANDARD_WEBHOOKS_VERSION as h, generateStandardWebhooksSecret as g, isStandardWebhooksSecret as _, standardWebhooksHeaders as v, standardWebhooksKey as y, standardWebhooksSignatureToken as b, standardWebhooksSignedContent as x, verifyStandardWebhooks as S } from "./standardWebhooks.js";
5
+ import { defaultOutgoingSignature as C, genericHmacSignature as w, githubSignature as T, signRequest as E, slackSignature as D, standardWebhooksSignature as O, stripeSignature as k, verifyRequest as A } from "./signing.js";
6
+ import { defaultRetryPolicy as j, fastRetryPolicy as M, nextRetry as N, parseDuration as P } from "./retry.js";
7
+ import { WebhookDeliveryNotFound as F, WebhookPayloadInvalid as I, WebhookPayloadUnrepresentable as L, WebhookPayloadVersionInvalid as R, WebhookSubscribeInvalid as z, WebhookTargetNotFound as B } from "./errors.js";
8
+ import { MAX_SECRET_OVERLAP_SECONDS as V, WebhooksService as H, buildWebhooksService as U, compareVersions as W, eventsOf as G, generateSecret as K, matchesFilter as q, resolveSubscribe as J, useWebhooks as Y, useWebhooksEffect as X, validateSubscribe as Z } from "./service.js";
9
+ import { IdempotencyCache as Q, getIdempotencyCache as $, parseTtl as ee } from "./idempotency.js";
10
+ import { UnverifiedIncomingWebhook as te, WEBHOOK_VERIFICATION_PROPERTY as ne, defaultIncomingPath as re, incomingWebhookVerification as ie, mountIncomingWebhook as ae } from "./incoming.js";
11
+ import { RATE_WINDOW_MS as oe, RATE_WINDOW_TABLE as se, acquireRateSlots as ce, consumeRateSlot as le, releaseRateSlot as ue } from "./rateLimit.js";
12
+ import { encodePayload as de } from "./encode.js";
13
+ import { TARGETS_TABLE as fe, recordDeliveryOutcome as pe } from "./autoDisable.js";
14
+ import { buildDeliverWebhookExecute as me, deliverWebhookWorkflow as he } from "./deliverWorkflow.js";
15
+ import { _voltroWebhookDeliveriesTable as ge, _voltroWebhookEventStatsTable as _e, _voltroWebhookRateWindowsTable as ve, _voltroWebhookTargetsTable as ye, webhookTables as be } from "./mixin.js";
16
+ import { WEBHOOK_EMIT_EFFECT as xe, makeDeferredEmitHandler as Se } from "./deferredEmit.js";
17
+ export { Q as IdempotencyCache, V as MAX_SECRET_OVERLAP_SECONDS, oe as RATE_WINDOW_MS, se as RATE_WINDOW_TABLE, c as STANDARD_WEBHOOKS_DEFAULT_TOLERANCE_SECONDS, l as STANDARD_WEBHOOKS_ID_HEADER, u as STANDARD_WEBHOOKS_MAX_KEY_BYTES, d as STANDARD_WEBHOOKS_MIN_KEY_BYTES, f as STANDARD_WEBHOOKS_SECRET_PREFIX, p as STANDARD_WEBHOOKS_SIGNATURE_HEADER, m as STANDARD_WEBHOOKS_TIMESTAMP_HEADER, h as STANDARD_WEBHOOKS_VERSION, fe as TARGETS_TABLE, te as UnverifiedIncomingWebhook, e as WEBHOOKS_EGRESS, t as WEBHOOKS_PLUGIN_PERMISSIONS, xe as WEBHOOK_EMIT_EFFECT, ne as WEBHOOK_VERIFICATION_PROPERTY, F as WebhookDeliveryNotFound, I as WebhookPayloadInvalid, L as WebhookPayloadUnrepresentable, R as WebhookPayloadVersionInvalid, z as WebhookSubscribeInvalid, B as WebhookTargetNotFound, H as WebhooksService, ge as _voltroWebhookDeliveriesTable, _e as _voltroWebhookEventStatsTable, ve as _voltroWebhookRateWindowsTable, ye as _voltroWebhookTargetsTable, ce as acquireRateSlots, o as asOutgoingEvent, me as buildDeliverWebhookExecute, U as buildWebhooksService, W as compareVersions, le as consumeRateSlot, re as defaultIncomingPath, C as defaultOutgoingSignature, j as defaultRetryPolicy, r as defineIncomingWebhook, i as defineWebhookProvider, he as deliverWebhookWorkflow, de as encodePayload, G as eventsOf, M as fastRetryPolicy, K as generateSecret, g as generateStandardWebhooksSecret, w as genericHmacSignature, $ as getIdempotencyCache, T as githubSignature, s as hasWebhookAudience, ie as incomingWebhookVerification, _ as isStandardWebhooksSecret, a as isWebhookDescriptor, Se as makeDeferredEmitHandler, q as matchesFilter, ae as mountIncomingWebhook, N as nextRetry, P as parseDuration, ee as parseTtl, pe as recordDeliveryOutcome, ue as releaseRateSlot, J as resolveSubscribe, E as signRequest, D as slackSignature, v as standardWebhooksHeaders, y as standardWebhooksKey, O as standardWebhooksSignature, b as standardWebhooksSignatureToken, x as standardWebhooksSignedContent, k as stripeSignature, Y as useWebhooks, X as useWebhooksEffect, Z as validateSubscribe, A as verifyRequest, S as verifyStandardWebhooks, be as webhookTables, n as webhooksPlugin };