@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.
@@ -0,0 +1,41 @@
1
+ import { and as e, eq as t } from "@voltro/database";
2
+ //#region src/autoDisable.ts
3
+ var n = "_voltro_webhook_targets", r = 32, i = async (e, n) => (await e.query({
4
+ table: "_voltro_webhook_targets",
5
+ predicate: t("id", n),
6
+ order: [],
7
+ take: 1,
8
+ skip: void 0,
9
+ projection: void 0
10
+ }))[0] ?? null, a = async (n, a, o, s, c = /* @__PURE__ */ new Date(), l = !1) => {
11
+ for (let u = 0; u < r; u++) {
12
+ u > 0 && await new Promise((e) => setTimeout(e, u));
13
+ let r = await i(n, a);
14
+ if (r === null) return {
15
+ consecutiveFailures: 0,
16
+ autoDisabled: !1
17
+ };
18
+ let d = r.consecutiveFailures ?? 0;
19
+ if (o) {
20
+ if (d === 0 || await n.updateMany("_voltro_webhook_targets", {
21
+ consecutiveFailures: 0,
22
+ updatedAt: c
23
+ }, { where: e(t("id", a), t("consecutiveFailures", d)) }) === 1) return {
24
+ consecutiveFailures: 0,
25
+ autoDisabled: !1
26
+ };
27
+ continue;
28
+ }
29
+ let f = d + 1, p = r.autoDisableAfter, m = r.active && (l || typeof p == "number" && p > 0 && f >= p), h = {
30
+ consecutiveFailures: f,
31
+ updatedAt: c
32
+ };
33
+ if (m && (h.active = !1, h.autoDisabledAt = c, h.autoDisableReason = s.slice(0, 500)), await n.updateMany("_voltro_webhook_targets", h, { where: e(t("id", a), t("consecutiveFailures", d)) }) === 1) return {
34
+ consecutiveFailures: f,
35
+ autoDisabled: m
36
+ };
37
+ }
38
+ throw Error(`webhook auto-disable: streak CAS contention on target ${a} exceeded ${r} attempts`);
39
+ };
40
+ //#endregion
41
+ export { n as TARGETS_TABLE, a as recordDeliveryOutcome };
@@ -0,0 +1,17 @@
1
+ import { defineOutboxHandler as e } from "@voltro/runtime";
2
+ //#region src/deferredEmit.ts
3
+ var t = "voltro.webhook.emit", n = (e) => typeof e.event == "string", r = (r) => e({
4
+ effect: t,
5
+ maxAttempts: 1,
6
+ handler: async ({ payload: e, outboxId: i }) => {
7
+ if (!n(e)) throw Error(`${t}: row payload has no \`event\` name — cannot resolve a descriptor. Got keys: ${Object.keys(e).join(", ") || "(none)"}`);
8
+ let a = r();
9
+ if (a === void 0) throw Error(`${t}: 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.`);
10
+ await a.emit(e.event, e.payload, {
11
+ tenantId: e.tenantId,
12
+ eventId: i
13
+ });
14
+ }
15
+ });
16
+ //#endregion
17
+ export { t as WEBHOOK_EMIT_EFFECT, r as makeDeferredEmitHandler };
@@ -0,0 +1,336 @@
1
+ import { signRequest as e } from "./signing.js";
2
+ import { nextRetry as t } from "./retry.js";
3
+ import { WebhookPayloadUnrepresentable as n } from "./errors.js";
4
+ import { acquireRateSlots as r } from "./rateLimit.js";
5
+ import { encodePayload as i } from "./encode.js";
6
+ import { recordDeliveryOutcome as a } from "./autoDisable.js";
7
+ import { Effect as o, Schema as s } from "effect";
8
+ import { eq as c } from "@voltro/database";
9
+ import { assertPublicUrl as l } from "@voltro/integration-http";
10
+ import { createLogger as u } from "@voltro/logger";
11
+ import { Activity as d, DurableClock as f, Workflow as p } from "effect/workflow";
12
+ //#region src/deliverWorkflow.ts
13
+ var m = s.Struct({
14
+ status: s.Number,
15
+ body: s.String,
16
+ latencyMs: s.Number,
17
+ retryAfterSec: s.Union([s.Number, s.Undefined]),
18
+ transportError: s.Union([s.String, s.Undefined]),
19
+ encodeError: s.Union([s.String, s.Undefined])
20
+ }), h = s.Struct({
21
+ outcome: s.Literals([
22
+ "succeeded",
23
+ "failed",
24
+ "retry"
25
+ ]),
26
+ delayMs: s.Union([s.Number, s.Undefined]),
27
+ reason: s.Union([s.String, s.Undefined]),
28
+ autoDisabled: s.Union([s.Boolean, s.Undefined]),
29
+ consecutiveFailures: s.Union([s.Number, s.Undefined])
30
+ }), g = s.Struct({
31
+ acquired: s.Boolean,
32
+ retryInMs: s.Number
33
+ }), _ = {
34
+ deliveryId: s.String,
35
+ targetId: s.String,
36
+ event: s.String,
37
+ eventId: s.String,
38
+ payloadJson: s.String,
39
+ attemptEpoch: s.optional(s.Number)
40
+ }, v = p.make("voltro.deliverWebhook", {
41
+ payload: _,
42
+ success: s.Struct({
43
+ finalStatus: s.Literals([
44
+ "succeeded",
45
+ "failed",
46
+ "deferred"
47
+ ]),
48
+ attempts: s.Number
49
+ }),
50
+ idempotencyKey: ({ deliveryId: e, attemptEpoch: t }) => `voltro.deliverWebhook:${e}:${t ?? 0}`
51
+ }), y = 3e4, b = (e, t) => {
52
+ if (e !== void 0) return e;
53
+ let n = Number(t.VOLTRO_WEBHOOK_TIMEOUT_MS);
54
+ return Number.isFinite(n) && n > 0 ? n : y;
55
+ }, x = (p, _ = {}) => (v, y) => {
56
+ let { deliveryId: x, targetId: S, event: C, eventId: w, payloadJson: T } = v, E = u({ scope: `webhook:${x}` });
57
+ return o.gen(function* () {
58
+ let u = yield* d.make({
59
+ name: "fetch-target",
60
+ success: s.Union([s.Null, s.Struct({
61
+ id: s.String,
62
+ event: s.String,
63
+ url: s.String,
64
+ secret: s.String,
65
+ signing: s.Any,
66
+ retry: s.Any,
67
+ headers: s.Union([s.Record(s.String, s.String), s.Null]),
68
+ rateLimitPerMinute: s.Union([s.Number, s.Null]),
69
+ active: s.Boolean,
70
+ format: s.Literals([
71
+ "json",
72
+ "form",
73
+ "xml"
74
+ ]),
75
+ tenantId: s.Union([s.String, s.Null])
76
+ })]),
77
+ execute: o.tryPromise({
78
+ try: async () => (await p.store.query({
79
+ table: "_voltro_webhook_targets",
80
+ predicate: c("id", S),
81
+ order: [{
82
+ column: "createdAt",
83
+ direction: "asc"
84
+ }],
85
+ take: 1,
86
+ skip: void 0,
87
+ projection: void 0
88
+ }))[0] ?? null,
89
+ catch: (e) => (E.debug("fetch-target raw error", {
90
+ targetId: S,
91
+ event: C,
92
+ cause: String(e)
93
+ }), /* @__PURE__ */ Error(`fetch-target failed: ${e.message}`))
94
+ }).pipe(o.orDie)
95
+ });
96
+ if (u === null) return {
97
+ finalStatus: "failed",
98
+ attempts: 0
99
+ };
100
+ let v = async (e, t, n) => {
101
+ await p.store.update("_voltro_webhook_deliveries", e, {
102
+ ...n,
103
+ updatedAt: /* @__PURE__ */ new Date()
104
+ }) === null && await p.store.insert("_voltro_webhook_deliveries", {
105
+ id: e,
106
+ deliveryId: x,
107
+ targetId: S,
108
+ tenantId: u.tenantId ?? null,
109
+ event: C,
110
+ eventId: w,
111
+ attempt: t,
112
+ payload: T,
113
+ scheduledAt: /* @__PURE__ */ new Date(),
114
+ createdAt: /* @__PURE__ */ new Date(),
115
+ updatedAt: /* @__PURE__ */ new Date(),
116
+ ...n
117
+ });
118
+ };
119
+ if (u.active !== !0) return yield* d.make({
120
+ name: "record-paused-pending",
121
+ success: s.Void,
122
+ execute: o.tryPromise({
123
+ try: () => v(`${x}:1`, 1, { status: "pending" }),
124
+ catch: (e) => /* @__PURE__ */ Error(`record-paused-pending failed: ${e.message}`)
125
+ }).pipe(o.orDie)
126
+ }), {
127
+ finalStatus: "deferred",
128
+ attempts: 0
129
+ };
130
+ let y = (e) => typeof e == "string" ? JSON.parse(e) : e, D = y(u.retry), O = y(u.signing), k = u.headers === null ? null : y(u.headers), A = _.rateWindowMs ?? 6e4, j = b(_.timeoutMs, process.env), M = _.events?.find((e) => e.id === C)?.globalRateLimit, N = [...typeof u.rateLimitPerMinute == "number" && u.rateLimitPerMinute > 0 ? [{
131
+ key: `target:${u.id}`,
132
+ limit: u.rateLimitPerMinute
133
+ }] : [], ...M !== void 0 && M.perMinute > 0 ? [{
134
+ key: `event:${C}`,
135
+ limit: M.perMinute
136
+ }] : []];
137
+ for (let c = 1; c <= D.maxAttempts; c++) {
138
+ let _ = `${x}:${c}`;
139
+ if (N.length > 0) for (let e = 0;; e++) {
140
+ let t = yield* d.make({
141
+ name: `rate-acquire-${c}-${e}`,
142
+ success: g,
143
+ execute: o.tryPromise({
144
+ try: () => r(p.store, N, Date.now(), A),
145
+ catch: (e) => (E.debug("rate-acquire raw error", {
146
+ targetId: S,
147
+ event: C,
148
+ attempt: c,
149
+ cause: String(e)
150
+ }), /* @__PURE__ */ Error(`rate-acquire failed: ${e.message}`))
151
+ }).pipe(o.orDie)
152
+ });
153
+ if (t.acquired) break;
154
+ yield* d.make({
155
+ name: `record-rate-deferral-${c}-${e}`,
156
+ success: s.Void,
157
+ execute: o.tryPromise({
158
+ try: () => v(_, c, {
159
+ status: "pending",
160
+ nextAttemptAt: new Date(Date.now() + t.retryInMs)
161
+ }),
162
+ catch: (e) => /* @__PURE__ */ Error(`record-rate-deferral failed: ${e.message}`)
163
+ }).pipe(o.orDie)
164
+ }), yield* f.sleep({
165
+ name: `rate-window-sleep-${c}-${e}`,
166
+ duration: `${t.retryInMs} millis`
167
+ });
168
+ }
169
+ yield* d.make({
170
+ name: `record-attempt-start-${c}`,
171
+ success: s.Void,
172
+ execute: o.tryPromise({
173
+ try: () => v(_, c, {
174
+ status: "inFlight",
175
+ nextAttemptAt: null,
176
+ scheduledAt: /* @__PURE__ */ new Date()
177
+ }),
178
+ catch: (e) => /* @__PURE__ */ Error(`record-attempt-start failed: ${e.message}`)
179
+ }).pipe(o.orDie)
180
+ });
181
+ let y = yield* d.make({
182
+ name: `sign-and-post-${c}`,
183
+ success: m,
184
+ execute: o.promise(async () => {
185
+ let t = Date.now(), r;
186
+ try {
187
+ r = i(u.format, T);
188
+ } catch (e) {
189
+ if (e instanceof n) return {
190
+ status: 0,
191
+ body: "",
192
+ latencyMs: Date.now() - t,
193
+ retryAfterSec: void 0,
194
+ transportError: void 0,
195
+ encodeError: e.reason
196
+ };
197
+ throw e;
198
+ }
199
+ let a = Buffer.from(r.bytes), { headers: o } = e(O, {
200
+ rawBody: a,
201
+ secret: u.secret,
202
+ messageId: x
203
+ });
204
+ process.env.NODE_ENV === "production" && l(u.url);
205
+ try {
206
+ let e = await fetch(u.url, {
207
+ method: "POST",
208
+ headers: {
209
+ "content-type": r.contentType,
210
+ ...o,
211
+ "x-voltro-event": C,
212
+ "x-voltro-event-id": w,
213
+ "x-voltro-delivery-id": x,
214
+ "x-voltro-attempt": String(c),
215
+ "idempotency-key": x,
216
+ ...k ?? {}
217
+ },
218
+ body: a,
219
+ redirect: "manual",
220
+ signal: AbortSignal.timeout(j)
221
+ }), n = (await e.text()).slice(0, 8192), i = e.headers.get("retry-after");
222
+ return {
223
+ status: e.status,
224
+ body: n,
225
+ latencyMs: Date.now() - t,
226
+ retryAfterSec: i !== null && /^\d+$/.test(i) ? Number(i) : void 0,
227
+ transportError: void 0,
228
+ encodeError: void 0
229
+ };
230
+ } catch (e) {
231
+ return {
232
+ status: 0,
233
+ body: "",
234
+ latencyMs: Date.now() - t,
235
+ retryAfterSec: void 0,
236
+ transportError: e.message,
237
+ encodeError: void 0
238
+ };
239
+ }
240
+ })
241
+ }), b = yield* d.make({
242
+ name: `classify-and-record-${c}`,
243
+ success: h,
244
+ execute: o.tryPromise({
245
+ try: async () => {
246
+ let e = y.status >= 200 && y.status < 300, n = y.transportError !== void 0, r = y.encodeError !== void 0, i = y.status === 410, o = (e, t) => a(p.store, S, e, t, /* @__PURE__ */ new Date(), !e && i);
247
+ if (e) {
248
+ await p.store.update("_voltro_webhook_deliveries", _, {
249
+ status: "succeeded",
250
+ responseStatus: y.status,
251
+ responseBody: y.body,
252
+ latencyMs: y.latencyMs,
253
+ updatedAt: /* @__PURE__ */ new Date()
254
+ });
255
+ let e = await o(!0, "");
256
+ return {
257
+ outcome: "succeeded",
258
+ delayMs: void 0,
259
+ reason: void 0,
260
+ autoDisabled: e.autoDisabled,
261
+ consecutiveFailures: e.consecutiveFailures
262
+ };
263
+ }
264
+ let s = r ? null : t(D, c, {
265
+ ...n ? {} : { status: y.status },
266
+ ...y.retryAfterSec === void 0 ? {} : { retryAfterSeconds: y.retryAfterSec }
267
+ });
268
+ if (s === null) {
269
+ let e = r ? `encode (${u.format}): ${y.encodeError}` : n ? `transport: ${y.transportError}` : `http ${y.status}: ${y.body.slice(0, 200)}`;
270
+ await p.store.update("_voltro_webhook_deliveries", _, {
271
+ status: "failed",
272
+ responseStatus: n || r ? null : y.status,
273
+ responseBody: n || r ? null : y.body,
274
+ errorMessage: r ? e : y.transportError ?? null,
275
+ latencyMs: y.latencyMs,
276
+ updatedAt: /* @__PURE__ */ new Date()
277
+ });
278
+ let t = await o(!1, e);
279
+ return {
280
+ outcome: "failed",
281
+ delayMs: void 0,
282
+ reason: e,
283
+ autoDisabled: t.autoDisabled,
284
+ consecutiveFailures: t.consecutiveFailures
285
+ };
286
+ }
287
+ return await p.store.update("_voltro_webhook_deliveries", _, {
288
+ status: "retryScheduled",
289
+ responseStatus: n ? null : y.status,
290
+ responseBody: n ? null : y.body,
291
+ errorMessage: y.transportError ?? null,
292
+ latencyMs: y.latencyMs,
293
+ nextAttemptAt: new Date(Date.now() + s.delayMs),
294
+ updatedAt: /* @__PURE__ */ new Date()
295
+ }), {
296
+ outcome: "retry",
297
+ delayMs: s.delayMs,
298
+ reason: void 0,
299
+ autoDisabled: void 0,
300
+ consecutiveFailures: void 0
301
+ };
302
+ },
303
+ catch: (e) => (E.debug("classify-and-record raw error", {
304
+ targetId: S,
305
+ event: C,
306
+ attempt: c,
307
+ cause: String(e)
308
+ }), /* @__PURE__ */ Error(`classify-and-record failed: ${e.message}`))
309
+ }).pipe(o.orDie)
310
+ });
311
+ if (b.autoDisabled === !0 && E.warn("target auto-disabled after consecutive failures", {
312
+ targetId: S,
313
+ event: C,
314
+ consecutiveFailures: b.consecutiveFailures ?? null,
315
+ reason: b.reason ?? null
316
+ }), b.outcome === "succeeded") return {
317
+ finalStatus: "succeeded",
318
+ attempts: c
319
+ };
320
+ if (b.outcome === "failed") return {
321
+ finalStatus: "failed",
322
+ attempts: c
323
+ };
324
+ b.delayMs !== void 0 && (yield* f.sleep({
325
+ name: `retry-sleep-${c}`,
326
+ duration: `${b.delayMs} millis`
327
+ }));
328
+ }
329
+ return {
330
+ finalStatus: "failed",
331
+ attempts: D.maxAttempts
332
+ };
333
+ });
334
+ };
335
+ //#endregion
336
+ export { y as DEFAULT_WEBHOOK_TIMEOUT_MS, x as buildDeliverWebhookExecute, v as deliverWebhookWorkflow };
package/dist/dsl.js ADDED
@@ -0,0 +1,14 @@
1
+ //#region src/dsl.ts
2
+ var e = (e) => ({
3
+ _tag: "incomingWebhook",
4
+ ...e
5
+ }), t = (e) => ({
6
+ _tag: "webhookProvider",
7
+ ...e
8
+ }), n = (e) => {
9
+ if (typeof e != "object" || !e) return !1;
10
+ let t = e._tag;
11
+ return t === "outgoingEvent" || t === "incomingWebhook" || t === "webhookProvider";
12
+ };
13
+ //#endregion
14
+ export { e as defineIncomingWebhook, t as defineWebhookProvider, n as isWebhookDescriptor };
package/dist/encode.js ADDED
@@ -0,0 +1,53 @@
1
+ import { WebhookPayloadUnrepresentable as e } from "./errors.js";
2
+ //#region src/encode.ts
3
+ var t = (t, r) => {
4
+ if (t === "json") return {
5
+ contentType: "application/json",
6
+ bytes: Buffer.from(r, "utf8")
7
+ };
8
+ let i;
9
+ try {
10
+ i = JSON.parse(r);
11
+ } catch (n) {
12
+ throw new e({
13
+ format: t,
14
+ reason: `payload is not valid JSON: ${n.message}`
15
+ });
16
+ }
17
+ return t === "form" ? {
18
+ contentType: "application/x-www-form-urlencoded",
19
+ bytes: Buffer.from(n(i), "utf8")
20
+ } : {
21
+ contentType: "application/xml",
22
+ bytes: Buffer.from(a(i), "utf8")
23
+ };
24
+ }, n = (t) => {
25
+ if (typeof t != "object" || !t || Array.isArray(t)) throw new e({
26
+ format: "form",
27
+ reason: `form encoding requires a JSON object at the top level, got ${l(t)}`
28
+ });
29
+ let n = new URLSearchParams();
30
+ for (let [e, i] of Object.entries(t)) r(n, e, i);
31
+ return n.toString();
32
+ }, r = (e, t, n) => {
33
+ if (n === null) e.append(t, "");
34
+ else if (Array.isArray(n)) n.forEach((n, i) => r(e, `${t}[${i}]`, n));
35
+ else if (typeof n == "object") for (let [i, a] of Object.entries(n)) r(e, `${t}[${i}]`, a);
36
+ else e.append(t, String(n));
37
+ }, i = /^[A-Za-z_][A-Za-z0-9_.-]*$/, a = (e) => `<?xml version="1.0" encoding="UTF-8"?>${o("webhook", e)}`, o = (t, n) => {
38
+ if (!i.test(t)) throw new e({
39
+ format: "xml",
40
+ reason: `object key ${JSON.stringify(t)} is not a valid XML element name`
41
+ });
42
+ return `<${t}>${s(t, n)}</${t}>`;
43
+ }, s = (e, t) => t == null ? "" : Array.isArray(t) ? t.map((e) => o("item", e)).join("") : typeof t == "object" ? Object.entries(t).map(([e, t]) => o(e, t)).join("") : c(String(t)), c = (e) => e.replace(/[&<>"']/g, (e) => {
44
+ switch (e) {
45
+ case "&": return "&amp;";
46
+ case "<": return "&lt;";
47
+ case ">": return "&gt;";
48
+ case "\"": return "&quot;";
49
+ default: return "&apos;";
50
+ }
51
+ }), l = (e) => e === null ? "null" : Array.isArray(e) ? "array" : typeof e;
52
+ //#endregion
53
+ export { t as encodePayload };
package/dist/errors.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Schema } from 'effect';
2
+ import { YieldableError } from 'effect/Cause';
2
3
 
3
4
  /**
4
5
  * Thrown by `WebhooksService.replay` when no delivery row exists for the
@@ -8,12 +9,10 @@ import { Schema } from 'effect';
8
9
  export declare class WebhookDeliveryNotFound extends WebhookDeliveryNotFound_base {
9
10
  }
10
11
 
11
- declare const WebhookDeliveryNotFound_base: Schema.TaggedErrorClass<WebhookDeliveryNotFound, "WebhookDeliveryNotFound", {
12
- readonly _tag: Schema.tag<"WebhookDeliveryNotFound">;
13
- } & {
12
+ declare const WebhookDeliveryNotFound_base: Schema.Class<WebhookDeliveryNotFound, Schema.TaggedStruct<"WebhookDeliveryNotFound", {
14
13
  /** The delivery id that did not resolve to a row. */
15
- deliveryId: typeof Schema.String;
16
- }>;
14
+ readonly deliveryId: Schema.String;
15
+ }>, YieldableError>;
17
16
 
18
17
  /**
19
18
  * Thrown by `WebhooksService.emit` when the payload does not decode
@@ -25,14 +24,12 @@ declare const WebhookDeliveryNotFound_base: Schema.TaggedErrorClass<WebhookDeliv
25
24
  export declare class WebhookPayloadInvalid extends WebhookPayloadInvalid_base {
26
25
  }
27
26
 
28
- declare const WebhookPayloadInvalid_base: Schema.TaggedErrorClass<WebhookPayloadInvalid, "WebhookPayloadInvalid", {
29
- readonly _tag: Schema.tag<"WebhookPayloadInvalid">;
30
- } & {
27
+ declare const WebhookPayloadInvalid_base: Schema.Class<WebhookPayloadInvalid, Schema.TaggedStruct<"WebhookPayloadInvalid", {
31
28
  /** The outgoing event id the payload was emitted for. */
32
- event: typeof Schema.String;
29
+ readonly event: Schema.String;
33
30
  /** Tree-formatted schema decode issues (one line per violation). */
34
- issues: typeof Schema.String;
35
- }>;
31
+ readonly issues: Schema.String;
32
+ }>, YieldableError>;
36
33
 
37
34
  /**
38
35
  * Raised by the delivery workflow's wire-encoder when the target's
@@ -47,14 +44,12 @@ declare const WebhookPayloadInvalid_base: Schema.TaggedErrorClass<WebhookPayload
47
44
  export declare class WebhookPayloadUnrepresentable extends WebhookPayloadUnrepresentable_base {
48
45
  }
49
46
 
50
- declare const WebhookPayloadUnrepresentable_base: Schema.TaggedErrorClass<WebhookPayloadUnrepresentable, "WebhookPayloadUnrepresentable", {
51
- readonly _tag: Schema.tag<"WebhookPayloadUnrepresentable">;
52
- } & {
47
+ declare const WebhookPayloadUnrepresentable_base: Schema.Class<WebhookPayloadUnrepresentable, Schema.TaggedStruct<"WebhookPayloadUnrepresentable", {
53
48
  /** The wire format that could not represent the payload. */
54
- format: Schema.Literal<["json", "form", "xml"]>;
49
+ readonly format: Schema.Literals<readonly ["json", "form", "xml"]>;
55
50
  /** Human-readable detail — which shape/key defeated the encoder. */
56
- reason: typeof Schema.String;
57
- }>;
51
+ readonly reason: Schema.String;
52
+ }>, YieldableError>;
58
53
 
59
54
  /**
60
55
  * Thrown by `WebhooksService.updateTargetPayloadVersion` when the
@@ -63,12 +58,10 @@ declare const WebhookPayloadUnrepresentable_base: Schema.TaggedErrorClass<Webhoo
63
58
  export declare class WebhookPayloadVersionInvalid extends WebhookPayloadVersionInvalid_base {
64
59
  }
65
60
 
66
- declare const WebhookPayloadVersionInvalid_base: Schema.TaggedErrorClass<WebhookPayloadVersionInvalid, "WebhookPayloadVersionInvalid", {
67
- readonly _tag: Schema.tag<"WebhookPayloadVersionInvalid">;
68
- } & {
61
+ declare const WebhookPayloadVersionInvalid_base: Schema.Class<WebhookPayloadVersionInvalid, Schema.TaggedStruct<"WebhookPayloadVersionInvalid", {
69
62
  /** The rejected version value (as supplied by the caller). */
70
- version: typeof Schema.Number;
71
- }>;
63
+ readonly version: Schema.Number;
64
+ }>, YieldableError>;
72
65
 
73
66
  /**
74
67
  * Thrown by `WebhooksService.subscribe` (via `validateSubscribe`) when
@@ -78,26 +71,23 @@ declare const WebhookPayloadVersionInvalid_base: Schema.TaggedErrorClass<Webhook
78
71
  * `reason` is the human-readable detail (carries the offending value).
79
72
  */
80
73
  export declare class WebhookSubscribeInvalid extends WebhookSubscribeInvalid_base {
74
+ get message(): string;
81
75
  }
82
76
 
83
- declare const WebhookSubscribeInvalid_base: Schema.TaggedErrorClass<WebhookSubscribeInvalid, "WebhookSubscribeInvalid", {
84
- readonly _tag: Schema.tag<"WebhookSubscribeInvalid">;
85
- } & {
77
+ declare const WebhookSubscribeInvalid_base: Schema.Class<WebhookSubscribeInvalid, Schema.TaggedStruct<"WebhookSubscribeInvalid", {
86
78
  /** The offending subscribe-input field: `'url'` | `'event'` |
87
79
  * `'secret'` | `'retry'` | `'rateLimitPerMinute'`. */
88
- field: typeof Schema.String;
80
+ readonly field: Schema.String;
89
81
  /** Human-readable detail, including the offending value. */
90
- reason: typeof Schema.String;
91
- }>;
82
+ readonly reason: Schema.String;
83
+ }>, YieldableError>;
92
84
 
93
85
  /** A target selected for management no longer exists. */
94
86
  export declare class WebhookTargetNotFound extends WebhookTargetNotFound_base {
95
87
  }
96
88
 
97
- declare const WebhookTargetNotFound_base: Schema.TaggedErrorClass<WebhookTargetNotFound, "WebhookTargetNotFound", {
98
- readonly _tag: Schema.tag<"WebhookTargetNotFound">;
99
- } & {
100
- targetId: typeof Schema.String;
101
- }>;
89
+ declare const WebhookTargetNotFound_base: Schema.Class<WebhookTargetNotFound, Schema.TaggedStruct<"WebhookTargetNotFound", {
90
+ readonly targetId: Schema.String;
91
+ }>, YieldableError>;
102
92
 
103
93
  export { }
package/dist/errors.js CHANGED
@@ -3,11 +3,19 @@ import { Schema as e } from "effect";
3
3
  var t = class extends e.TaggedError()("WebhookSubscribeInvalid", {
4
4
  field: e.String,
5
5
  reason: e.String
6
- }) {}, n = class extends e.TaggedError()("WebhookDeliveryNotFound", { deliveryId: e.String }) {}, r = class extends e.TaggedError()("WebhookTargetNotFound", { targetId: e.String }) {}, i = class extends e.TaggedError()("WebhookPayloadVersionInvalid", { version: e.Number }) {}, a = class extends e.TaggedError()("WebhookPayloadInvalid", {
6
+ }) {
7
+ get message() {
8
+ return `${this.field}: ${this.reason}`;
9
+ }
10
+ }, n = class extends e.TaggedError()("WebhookDeliveryNotFound", { deliveryId: e.String }) {}, r = class extends e.TaggedError()("WebhookTargetNotFound", { targetId: e.String }) {}, i = class extends e.TaggedError()("WebhookPayloadVersionInvalid", { version: e.Number }) {}, a = class extends e.TaggedError()("WebhookPayloadInvalid", {
7
11
  event: e.String,
8
12
  issues: e.String
9
13
  }) {}, o = class extends e.TaggedError()("WebhookPayloadUnrepresentable", {
10
- format: e.Literal("json", "form", "xml"),
14
+ format: e.Literals([
15
+ "json",
16
+ "form",
17
+ "xml"
18
+ ]),
11
19
  reason: e.String
12
20
  }) {};
13
21
  //#endregion
@@ -0,0 +1,14 @@
1
+ //#region src/eventAudience.ts
2
+ var e = (e) => e.webhook !== void 0, t = (e) => {
3
+ let t = e.webhook;
4
+ if (t !== void 0) return {
5
+ _tag: "outgoingEvent",
6
+ id: e.name,
7
+ payload: e.payload,
8
+ ...t.description === void 0 ? {} : { description: t.description },
9
+ ...t.version === void 0 ? {} : { version: t.version },
10
+ ...t.rateLimit === void 0 ? {} : { globalRateLimit: t.rateLimit }
11
+ };
12
+ };
13
+ //#endregion
14
+ export { t as asOutgoingEvent, e as hasWebhookAudience };
@@ -0,0 +1,49 @@
1
+ //#region src/idempotency.ts
2
+ var e = class {
3
+ capacity;
4
+ cache = /* @__PURE__ */ new Map();
5
+ constructor(e = 1e4) {
6
+ this.capacity = e;
7
+ }
8
+ evictExpired() {
9
+ let e = Date.now();
10
+ for (let [t, n] of this.cache) n.expiresAt < e && this.cache.delete(t);
11
+ }
12
+ claim(e, t) {
13
+ let n = this.cache.get(e);
14
+ if (n !== void 0 && n.expiresAt > Date.now()) return this.cache.delete(e), this.cache.set(e, n), n.value === "inflight" ? "inflight" : "duplicate";
15
+ if (this.cache.set(e, {
16
+ value: "inflight",
17
+ expiresAt: Date.now() + t
18
+ }), this.cache.size > this.capacity) {
19
+ let e = this.cache.keys().next().value;
20
+ e !== void 0 && this.cache.delete(e);
21
+ }
22
+ return "fresh";
23
+ }
24
+ commit(e) {
25
+ let t = this.cache.get(e);
26
+ t !== void 0 && this.cache.set(e, {
27
+ value: "processed",
28
+ expiresAt: t.expiresAt
29
+ });
30
+ }
31
+ release(e) {
32
+ this.cache.delete(e);
33
+ }
34
+ size() {
35
+ return this.evictExpired(), this.cache.size;
36
+ }
37
+ clear() {
38
+ this.cache.clear();
39
+ }
40
+ }, t = null, n = () => (t === null && (t = new e()), t), r = {
41
+ "5m": 3e5,
42
+ "1h": 36e5,
43
+ "6h": 216e5,
44
+ "1d": 864e5,
45
+ "7d": 6048e5,
46
+ "30d": 2592e6
47
+ }, i = (e) => e === void 0 ? r["7d"] : r[e] ?? r["7d"];
48
+ //#endregion
49
+ export { e as IdempotencyCache, n as getIdempotencyCache, i as parseTtl };