@voltro/plugin-webhooks 0.24.0 → 0.26.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/mixin.d.ts CHANGED
@@ -118,6 +118,30 @@ export declare const _voltroWebhookTargetsTable: Table<"_voltro_webhook_targets"
118
118
  readonly signing: ColumnBuilder<unknown, "json", boolean>;
119
119
  /** Serialised `RetryPolicy` — see `retry.ts`. */
120
120
  readonly retry: ColumnBuilder<unknown, "json", boolean>;
121
+ /**
122
+ * The APP's own scoping dimension. Opaque to the framework.
123
+ *
124
+ * Stored and returned verbatim, never interpreted — the framework does not
125
+ * know what a team, a project or a workspace is, and does not need to. Reads
126
+ * filter on equality against the json you wrote:
127
+ *
128
+ * subscribe({ event, url, scope: { teamId: 'q970…' } })
129
+ * listTargets({ scope: { teamId } })
130
+ *
131
+ * It exists because `.with(tenant())` is one level too coarse for real
132
+ * deployments. A consumer's endpoints are scoped to a TEAM and a tenant has
133
+ * many teams; every read filters by it and every write guards on it, so
134
+ * without this column the plugin cannot hold their rows at all and they keep
135
+ * a parallel table.
136
+ *
137
+ * The precedent is `_voltro_presence.meta`, and it is worth stating because
138
+ * it decided a migration: that column is json the framework stores and never
139
+ * interprets, and it is the ONLY reason the same consumer's presence
140
+ * migration was lossless — their three denormalised columns went straight in.
141
+ * The general form: a plugin that stores rows in an app's database on the
142
+ * app's behalf needs one place for the app's own dimension.
143
+ */
144
+ readonly scope: ColumnBuilder<unknown, "json", boolean>;
121
145
  /** Optional predicate filter (subset of `Predicate`) — the engine
122
146
  * evaluates this against each emit's payload to decide whether
123
147
  * this target receives the delivery. */
@@ -206,6 +230,30 @@ export declare const webhookTables: () => readonly [ Table<"_voltro_webhook_targ
206
230
  readonly signing: ColumnBuilder<unknown, "json", boolean>;
207
231
  /** Serialised `RetryPolicy` — see `retry.ts`. */
208
232
  readonly retry: ColumnBuilder<unknown, "json", boolean>;
233
+ /**
234
+ * The APP's own scoping dimension. Opaque to the framework.
235
+ *
236
+ * Stored and returned verbatim, never interpreted — the framework does not
237
+ * know what a team, a project or a workspace is, and does not need to. Reads
238
+ * filter on equality against the json you wrote:
239
+ *
240
+ * subscribe({ event, url, scope: { teamId: 'q970…' } })
241
+ * listTargets({ scope: { teamId } })
242
+ *
243
+ * It exists because `.with(tenant())` is one level too coarse for real
244
+ * deployments. A consumer's endpoints are scoped to a TEAM and a tenant has
245
+ * many teams; every read filters by it and every write guards on it, so
246
+ * without this column the plugin cannot hold their rows at all and they keep
247
+ * a parallel table.
248
+ *
249
+ * The precedent is `_voltro_presence.meta`, and it is worth stating because
250
+ * it decided a migration: that column is json the framework stores and never
251
+ * interprets, and it is the ONLY reason the same consumer's presence
252
+ * migration was lossless — their three denormalised columns went straight in.
253
+ * The general form: a plugin that stores rows in an app's database on the
254
+ * app's behalf needs one place for the app's own dimension.
255
+ */
256
+ readonly scope: ColumnBuilder<unknown, "json", boolean>;
209
257
  /** Optional predicate filter (subset of `Predicate`) — the engine
210
258
  * evaluates this against each emit's payload to decide whether
211
259
  * this target receives the delivery. */
package/dist/mixin.js CHANGED
@@ -8,6 +8,7 @@ var u = o("_voltro_webhook_targets", {
8
8
  secret: s(),
9
9
  signing: r(),
10
10
  retry: r(),
11
+ scope: r().nullable(),
11
12
  filter: r().nullable(),
12
13
  headers: r().nullable(),
13
14
  rateLimitPerMinute: n().nullable(),
@@ -1,4 +1,4 @@
1
- import { a as e, l as t, n, t as r } from "../signing-BOUxHdAo.js";
1
+ import { a as e, c as t, n, t as r } from "../signing-DUG4JWwk.js";
2
2
  import { createHmac as i, timingSafeEqual as a } from "node:crypto";
3
3
  //#region src/providers/stripe.ts
4
4
  var o = (n) => t({
@@ -1,41 +1,38 @@
1
1
  import { createHmac as e, timingSafeEqual as t } from "node:crypto";
2
2
  //#region src/dsl.ts
3
3
  var n = (e) => ({
4
- _tag: "outgoingEvent",
5
- ...e
6
- }), r = (e) => ({
7
4
  _tag: "incomingWebhook",
8
5
  ...e
9
- }), i = (e) => ({
6
+ }), r = (e) => ({
10
7
  _tag: "webhookProvider",
11
8
  ...e
12
- }), a = (e) => {
9
+ }), i = (e) => {
13
10
  if (typeof e != "object" || !e) return !1;
14
11
  let t = e._tag;
15
12
  return t === "outgoingEvent" || t === "incomingWebhook" || t === "webhookProvider";
16
- }, o = (e) => e === "hmacSha256" ? "sha256" : "sha1", s = (t, n, r, i = "hex") => {
17
- let a = e(o(t), n);
18
- return a.update(r), a.digest(i);
19
- }, c = (e, t, n) => {
13
+ }, a = (e) => e === "hmacSha256" ? "sha256" : "sha1", o = (t, n, r, i = "hex") => {
14
+ let o = e(a(t), n);
15
+ return o.update(r), o.digest(i);
16
+ }, s = (e, t, n) => {
20
17
  if (e._tag === "custom") return {
21
18
  headerName: e.header,
22
19
  headerValue: e.sign(t, n)
23
20
  };
24
21
  let r = e.encoding ?? "hex", i = e.versionPrefix ?? "";
25
22
  if (e.includeTimestamp) {
26
- let a = Math.floor(Date.now() / 1e3), o = Buffer.concat([Buffer.from(`${a}.`, "utf8"), Buffer.from(t)]), c = s(e.algorithm, n, o, r);
23
+ let a = Math.floor(Date.now() / 1e3), s = Buffer.concat([Buffer.from(`${a}.`, "utf8"), Buffer.from(t)]), c = o(e.algorithm, n, s, r);
27
24
  return {
28
25
  headerName: e.header,
29
26
  headerValue: `t=${a},${i}${c}`,
30
27
  timestamp: a
31
28
  };
32
29
  }
33
- let a = s(e.algorithm, n, t, r);
30
+ let a = o(e.algorithm, n, t, r);
34
31
  return {
35
32
  headerName: e.header,
36
33
  headerValue: `${i}${a}`
37
34
  };
38
- }, l = (e, t, n, r) => {
35
+ }, c = (e, t, n, r) => {
39
36
  if (!r) return {
40
37
  ok: !1,
41
38
  reason: "missing signature header"
@@ -44,50 +41,50 @@ var n = (e) => ({
44
41
  ok: !1,
45
42
  reason: "custom verifier rejected"
46
43
  };
47
- let i = e.encoding ?? "hex", a = e.versionPrefix ?? "", o = (n) => {
44
+ let i = e.encoding ?? "hex", a = e.versionPrefix ?? "", s = (n) => {
48
45
  if (e.includeTimestamp) {
49
- let o = r.split(",").map((e) => e.trim()), c = o.find((e) => e.startsWith("t="));
46
+ let s = r.split(",").map((e) => e.trim()), c = s.find((e) => e.startsWith("t="));
50
47
  if (!c) return {
51
48
  ok: !1,
52
49
  reason: "no timestamp in signature header"
53
50
  };
54
- let l = Number(c.slice(2));
55
- if (!Number.isFinite(l)) return {
51
+ let u = Number(c.slice(2));
52
+ if (!Number.isFinite(u)) return {
56
53
  ok: !1,
57
54
  reason: "invalid timestamp"
58
55
  };
59
56
  let d = e.replayWindowSeconds ?? 300, f = Math.floor(Date.now() / 1e3);
60
- if (Math.abs(f - l) > d) return {
57
+ if (Math.abs(f - u) > d) return {
61
58
  ok: !1,
62
- reason: `replay window exceeded (${Math.abs(f - l)}s > ${d}s)`
59
+ reason: `replay window exceeded (${Math.abs(f - u)}s > ${d}s)`
63
60
  };
64
- let p = a.length > 0 ? o.find((e) => e.startsWith(a))?.slice(a.length) : o.find((e) => !e.startsWith("t="));
61
+ let p = a.length > 0 ? s.find((e) => e.startsWith(a))?.slice(a.length) : s.find((e) => !e.startsWith("t="));
65
62
  if (!p) return {
66
63
  ok: !1,
67
64
  reason: "no signature in header"
68
65
  };
69
- let m = Buffer.concat([Buffer.from(`${l}.`, "utf8"), Buffer.from(t)]);
70
- return u(s(e.algorithm, n, m, i), p) ? { ok: !0 } : {
66
+ let m = Buffer.concat([Buffer.from(`${u}.`, "utf8"), Buffer.from(t)]);
67
+ return l(o(e.algorithm, n, m, i), p) ? { ok: !0 } : {
71
68
  ok: !1,
72
69
  reason: "signature mismatch"
73
70
  };
74
71
  }
75
- let o = a.length > 0 && r.startsWith(a) ? r.slice(a.length) : r;
76
- return u(s(e.algorithm, n, t, i), o) ? { ok: !0 } : {
72
+ let s = a.length > 0 && r.startsWith(a) ? r.slice(a.length) : r;
73
+ return l(o(e.algorithm, n, t, i), s) ? { ok: !0 } : {
77
74
  ok: !1,
78
75
  reason: "signature mismatch"
79
76
  };
80
- }, c = o(n);
77
+ }, c = s(n);
81
78
  if (c.ok) return c;
82
79
  if (e.previousSecret !== void 0) {
83
- let t = o(e.previousSecret);
80
+ let t = s(e.previousSecret);
84
81
  if (t.ok) return t;
85
82
  }
86
83
  return c;
87
- }, u = (e, n) => {
84
+ }, l = (e, n) => {
88
85
  let r = Buffer.from(e, "utf8"), i = Buffer.from(n, "utf8");
89
86
  return r.length === i.length ? t(r, i) : (t(r, Buffer.alloc(r.length)), !1);
90
- }, d = (e = "Stripe-Signature") => ({
87
+ }, u = (e = "Stripe-Signature") => ({
91
88
  _tag: "hmac",
92
89
  algorithm: "hmacSha256",
93
90
  header: e,
@@ -95,19 +92,19 @@ var n = (e) => ({
95
92
  replayWindowSeconds: 300,
96
93
  encoding: "hex",
97
94
  versionPrefix: "v1="
98
- }), f = () => ({
95
+ }), d = () => ({
99
96
  _tag: "hmac",
100
97
  algorithm: "hmacSha256",
101
98
  header: "X-Hub-Signature-256",
102
99
  includeTimestamp: !1,
103
100
  encoding: "hex",
104
101
  versionPrefix: "sha256="
105
- }), p = () => ({
102
+ }), f = () => ({
106
103
  _tag: "custom",
107
104
  header: "X-Slack-Signature",
108
- sign: (e, t) => `v0=${s("hmacSha256", t, `v0:${Math.floor(Date.now() / 1e3)}:${Buffer.from(e).toString("utf8")}`)}`,
105
+ sign: (e, t) => `v0=${o("hmacSha256", t, `v0:${Math.floor(Date.now() / 1e3)}:${Buffer.from(e).toString("utf8")}`)}`,
109
106
  verify: (e, t, n) => !1
110
- }), m = () => ({
107
+ }), p = () => ({
111
108
  _tag: "hmac",
112
109
  algorithm: "hmacSha256",
113
110
  header: "X-Webhook-Signature",
@@ -117,4 +114,4 @@ var n = (e) => ({
117
114
  versionPrefix: "v1="
118
115
  });
119
116
  //#endregion
120
- export { d as a, n as c, p as i, i as l, f as n, l as o, c as r, r as s, m as t, a as u };
117
+ export { u as a, r as c, f as i, i as l, d as n, c as o, s as r, n as s, p as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/plugin-webhooks",
3
- "version": "0.24.0",
3
+ "version": "0.26.0",
4
4
  "description": "Webhooks plugin — first-class outgoing (multi-target, durable-workflow delivery, HMAC signing, retry policies, rate limits) + incoming (signature verification, idempotency, provider templates for Stripe / GitHub / Slack / generic).",
5
5
  "keywords": [
6
6
  "voltro",
@@ -48,12 +48,12 @@
48
48
  },
49
49
  "dependencies": {
50
50
  "@effect/workflow": "^0.19.0",
51
- "@voltro/database": "0.24.0",
52
- "@voltro/integration-http": "0.24.0",
53
- "@voltro/logger": "0.24.0",
54
- "@voltro/plugin-multitenancy": "0.24.0",
55
- "@voltro/protocol": "0.24.0",
56
- "@voltro/runtime": "0.24.0"
51
+ "@voltro/database": "0.26.0",
52
+ "@voltro/integration-http": "0.26.0",
53
+ "@voltro/logger": "0.26.0",
54
+ "@voltro/plugin-multitenancy": "0.26.0",
55
+ "@voltro/protocol": "0.26.0",
56
+ "@voltro/runtime": "0.26.0"
57
57
  },
58
58
  "peerDependencies": {
59
59
  "effect": "^3.22.0"