@voltro/plugin-webhooks 0.29.0 → 0.30.1

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