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