@voltro/plugin-notifications 0.54.0 → 0.55.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,8 +1,8 @@
1
- import { archiveDescriptor as e, clearQuietHoursDescriptor as t, inboxDescriptor as n, markAllReadDescriptor as r, markReadDescriptor as i, markUnreadDescriptor as a, notificationsRpcClientImports as o, preferencesDescriptor as s, setPreferenceDescriptor as c, setQuietHoursDescriptor as l, subscribeDescriptor as u, unarchiveDescriptor as d, unreadCountDescriptor as f, unsubscribeDescriptor as p, webPushPublicKeyDescriptor as m, webPushRpcClientImports as ee, webPushStatusDescriptor as te, webPushSubscribeDescriptor as ne, webPushUnsubscribeDescriptor as re } from "./rpc.js";
2
- import { Context as h, Effect as g, Layer as ie, Schema as _ } from "effect";
3
- import { and as v, boolean as y, eq as b, generateId as x, id as S, integer as C, isNotNull as ae, isNull as w, json as T, lte as E, registerRetention as D, retentionTtlMsFromEnv as O, table as k, text as A, timestamp as j } from "@voltro/database";
4
- import { definePlugin as oe, pluginInstanceName as se } from "@voltro/protocol";
5
- import { createCipheriv as M, createDecipheriv as ce, createECDH as N, createHash as le, createHmac as ue, createPrivateKey as de, randomBytes as fe, randomUUID as P, sign as pe, timingSafeEqual as me } from "node:crypto";
1
+ import { BASE_NAME as e, archiveDescriptor as t, clearQuietHoursDescriptor as n, inboxDescriptor as r, markAllReadDescriptor as i, markReadDescriptor as a, markUnreadDescriptor as o, notificationsRpcClientImports as s, preferencesDescriptor as c, setPreferenceDescriptor as l, setQuietHoursDescriptor as u, subscribeDescriptor as d, unarchiveDescriptor as f, unreadCountDescriptor as p, unsubscribeDescriptor as m, webPushPublicKeyDescriptor as ee, webPushRpcClientImports as te, webPushStatusDescriptor as ne, webPushSubscribeDescriptor as re, webPushUnsubscribeDescriptor as ie } from "./rpc.js";
2
+ import { Context as h, Effect as g, Layer as ae, Schema as _ } from "effect";
3
+ import { and as v, boolean as y, eq as b, generateId as x, id as S, integer as C, isNotNull as oe, isNull as w, json as T, lte as E, registerRetention as D, retentionTtlMsFromEnv as O, table as k, text as A, timestamp as j } from "@voltro/database";
4
+ import { definePlugin as se, pluginInstanceName as ce } from "@voltro/protocol";
5
+ import { createCipheriv as le, createDecipheriv as M, createECDH as N, createHash as ue, createHmac as de, createPrivateKey as fe, randomBytes as pe, randomUUID as P, sign as me, timingSafeEqual as he } from "node:crypto";
6
6
  //#region src/errors.ts
7
7
  var F = class extends _.TaggedError()("PushTokenRejected", {
8
8
  token: _.String,
@@ -12,7 +12,7 @@ var F = class extends _.TaggedError()("PushTokenRejected", {
12
12
  deliver: async (e) => {
13
13
  process.stdout.write(`[notify:${e.category}] → ${e.to}: ${e.title} — ${e.body}\n`);
14
14
  }
15
- }), he = (e) => ({
15
+ }), ge = (e) => ({
16
16
  id: e.id ?? "slack",
17
17
  deliver: async (t) => {
18
18
  let n = e.format ? e.format(t) : { text: `*${t.title}*\n${t.body}` }, r = await fetch(e.url, {
@@ -25,7 +25,7 @@ var F = class extends _.TaggedError()("PushTokenRejected", {
25
25
  });
26
26
  if (!r.ok) throw Error(`webhook ${e.id ?? "slack"} POST ${r.status}`);
27
27
  }
28
- }), ge = (e) => ({
28
+ }), _e = (e) => ({
29
29
  id: "email",
30
30
  deliver: (t) => e({
31
31
  to: t.to,
@@ -33,13 +33,13 @@ var F = class extends _.TaggedError()("PushTokenRejected", {
33
33
  html: `<p>${t.body}</p>`,
34
34
  text: t.body
35
35
  })
36
- }), _e = (e) => ({
36
+ }), ve = (e) => ({
37
37
  id: "sms",
38
38
  deliver: (t) => e(t.to, `${t.title}: ${t.body}`)
39
- }), ve = (e, t) => ({
39
+ }), ye = (e, t) => ({
40
40
  id: e,
41
41
  deliver: t
42
- }), ye = (e) => {
42
+ }), be = (e) => {
43
43
  let t = async (t, n, r) => {
44
44
  let i = {
45
45
  token: n,
@@ -172,7 +172,7 @@ var F = class extends _.TaggedError()("PushTokenRejected", {
172
172
  for (let e = a.length - 1; e >= 0; e--) t.has(a[e].id) && a.splice(e, 1);
173
173
  }
174
174
  };
175
- }, z = (e) => e instanceof Date ? e.toISOString() : typeof e == "string" ? e : "", be = (e) => {
175
+ }, z = (e) => e instanceof Date ? e.toISOString() : typeof e == "string" ? e : "", xe = (e) => {
176
176
  let t = async (t, n, r, i) => await e.query({
177
177
  table: t,
178
178
  predicate: n,
@@ -369,7 +369,7 @@ var F = class extends _.TaggedError()("PushTokenRejected", {
369
369
  for (let n of t) await e.delete("_voltro_notification_held", n);
370
370
  }
371
371
  };
372
- }, xe = (e) => ({
372
+ }, Se = (e) => ({
373
373
  id: "inApp",
374
374
  deliver: async (t) => {
375
375
  await e.addInbox({
@@ -395,14 +395,14 @@ var F = class extends _.TaggedError()("PushTokenRejected", {
395
395
  } catch {
396
396
  return e.getUTCHours() * 60 + e.getUTCMinutes();
397
397
  }
398
- }, Se = (e, t) => {
398
+ }, Ce = (e, t) => {
399
399
  if (e.startMinute === e.endMinute) return !1;
400
400
  let n = B(t, e.tz ?? "UTC");
401
401
  return e.startMinute < e.endMinute ? n >= e.startMinute && n < e.endMinute : n >= e.startMinute || n < e.endMinute;
402
- }, Ce = (e, t) => {
402
+ }, we = (e, t) => {
403
403
  let n = B(t, e.tz ?? "UTC"), r = e.endMinute - n;
404
404
  return r <= 0 && (r += 1440), new Date(t.getTime() + r * 6e4);
405
- }, we = (e) => {
405
+ }, Te = (e) => {
406
406
  let t = new Map(e.channels.map((e) => [e.id, e])), n = e.channels.map((e) => e.id), r = e.now ?? (() => (/* @__PURE__ */ new Date()).toISOString()), i = e.digestWindowMs ?? 0, a = async (i) => {
407
407
  let a = await e.store.getPreferences(i.to), o = L(n, i.category, i.channels, a, i.to), s = n.filter((e) => !o.includes(e)), c = [], l = [], u = {
408
408
  to: i.to,
@@ -482,7 +482,7 @@ var F = class extends _.TaggedError()("PushTokenRejected", {
482
482
  };
483
483
  }, o = async (t) => {
484
484
  let n = await e.store.getQuietHours(t.to), o = new Date(r());
485
- if (n && Se(n, o)) return (n.policy ?? "hold") === "drop" ? {
485
+ if (n && Ce(n, o)) return (n.policy ?? "hold") === "drop" ? {
486
486
  delivered: [],
487
487
  failed: [],
488
488
  skipped: [],
@@ -492,7 +492,7 @@ var F = class extends _.TaggedError()("PushTokenRejected", {
492
492
  kind: "quiet",
493
493
  subjectId: t.to,
494
494
  send: t,
495
- flushAt: Ce(n, o).toISOString()
495
+ flushAt: we(n, o).toISOString()
496
496
  }), {
497
497
  delivered: [],
498
498
  failed: [],
@@ -556,11 +556,11 @@ var F = class extends _.TaggedError()("PushTokenRejected", {
556
556
  let t = i.get(e.subjectId) ?? [];
557
557
  t.push(e), i.set(e.subjectId, t);
558
558
  }
559
- for (let [e, t] of i) await a(Te(e, t));
559
+ for (let [e, t] of i) await a(Ee(e, t));
560
560
  return await e.store.clearHeld(n.map((e) => e.id)), n.length;
561
561
  }
562
562
  };
563
- }, Te = (e, t) => {
563
+ }, Ee = (e, t) => {
564
564
  if (t.length === 1) return t[0].send;
565
565
  let n = t.map((e) => ({
566
566
  category: e.send.category,
@@ -575,7 +575,7 @@ var F = class extends _.TaggedError()("PushTokenRejected", {
575
575
  data: { items: n },
576
576
  tenantId: t[0].send.tenantId ?? null
577
577
  };
578
- }, V = (e) => e.toString("base64url"), H = (e) => Buffer.from(e, "base64url"), U = (e, t) => ue("sha256", e).update(t).digest(), W = (e, t, n) => U(e, Buffer.concat([t, Buffer.from([1])])).subarray(0, n), Ee = () => {
578
+ }, V = (e) => e.toString("base64url"), H = (e) => Buffer.from(e, "base64url"), U = (e, t) => de("sha256", e).update(t).digest(), W = (e, t, n) => U(e, Buffer.concat([t, Buffer.from([1])])).subarray(0, n), De = () => {
579
579
  let e = N("prime256v1");
580
580
  return e.generateKeys(), V(e.getPrivateKey());
581
581
  }, G = (e) => {
@@ -583,11 +583,11 @@ var F = class extends _.TaggedError()("PushTokenRejected", {
583
583
  if (t.length !== 32) throw Error(`VOLTRO_VAPID_PRIVATE_KEY must be a base64url 32-byte P-256 scalar (got ${t.length} bytes)`);
584
584
  let n = N("prime256v1");
585
585
  return n.setPrivateKey(t), V(n.getPublicKey());
586
- }, De = (e) => {
586
+ }, Oe = (e) => {
587
587
  let t = H(e), n = N("prime256v1");
588
588
  n.setPrivateKey(t);
589
589
  let r = n.getPublicKey();
590
- return de({
590
+ return fe({
591
591
  key: {
592
592
  kty: "EC",
593
593
  crv: "P-256",
@@ -597,7 +597,7 @@ var F = class extends _.TaggedError()("PushTokenRejected", {
597
597
  },
598
598
  format: "jwk"
599
599
  });
600
- }, K = (e, t, n, r = Date.now()) => {
600
+ }, ke = (e, t, n, r = Date.now()) => {
601
601
  let i = new URL(e), a = {
602
602
  aud: `${i.protocol}//${i.host}`,
603
603
  exp: Math.floor(r / 1e3) + 43200,
@@ -606,45 +606,45 @@ var F = class extends _.TaggedError()("PushTokenRejected", {
606
606
  typ: "JWT",
607
607
  alg: "ES256"
608
608
  })))}.${V(Buffer.from(JSON.stringify(a)))}`;
609
- return `vapid t=${o}.${V(pe("sha256", Buffer.from(o), {
610
- key: De(t),
609
+ return `vapid t=${o}.${V(me("sha256", Buffer.from(o), {
610
+ key: Oe(t),
611
611
  dsaEncoding: "ieee-p1363"
612
612
  }))}, k=${G(t)}`;
613
- }, Oe = Buffer.from("WebPush: info\0"), ke = Buffer.from("Content-Encoding: aes128gcm\0"), Ae = Buffer.from("Content-Encoding: nonce\0"), je = 4096, Me = (e, t, n = {}) => {
613
+ }, K = Buffer.from("WebPush: info\0"), Ae = Buffer.from("Content-Encoding: aes128gcm\0"), je = Buffer.from("Content-Encoding: nonce\0"), Me = 4096, Ne = (e, t, n = {}) => {
614
614
  let r = H(e.p256dh), i = H(e.auth);
615
615
  if (r.length !== 65 || r[0] !== 4) throw Error("push subscription p256dh must be a 65-byte uncompressed P-256 point");
616
616
  if (i.length !== 16) throw Error("push subscription auth must be 16 bytes");
617
617
  let a = N("prime256v1");
618
618
  n.asPrivate ? a.setPrivateKey(n.asPrivate) : a.generateKeys();
619
619
  let o = a.getPublicKey(), s = W(U(i, a.computeSecret(r)), Buffer.concat([
620
- Oe,
620
+ K,
621
621
  r,
622
622
  o
623
- ]), 32), c = n.salt ?? fe(16), l = U(c, s), u = W(l, ke, 16), d = W(l, Ae, 12), f = M("aes-128-gcm", u, d), p = Buffer.concat([
623
+ ]), 32), c = n.salt ?? pe(16), l = U(c, s), u = W(l, Ae, 16), d = W(l, je, 12), f = le("aes-128-gcm", u, d), p = Buffer.concat([
624
624
  f.update(Buffer.concat([t, Buffer.from([2])])),
625
625
  f.final(),
626
626
  f.getAuthTag()
627
627
  ]), m = Buffer.alloc(21);
628
- return c.copy(m, 0), m.writeUInt32BE(je, 16), m.writeUInt8(o.length, 20), Buffer.concat([
628
+ return c.copy(m, 0), m.writeUInt32BE(Me, 16), m.writeUInt8(o.length, 20), Buffer.concat([
629
629
  m,
630
630
  o,
631
631
  p
632
632
  ]);
633
- }, Ne = (e, t) => {
633
+ }, Pe = (e, t) => {
634
634
  let n = e.subarray(0, 16), r = e.readUInt8(20), i = e.subarray(21, 21 + r), a = e.subarray(21 + r), o = N("prime256v1");
635
635
  o.setPrivateKey(t.uaPrivate);
636
636
  let s = o.getPublicKey(), c = o.computeSecret(i), l = U(n, W(U(t.authSecret, c), Buffer.concat([
637
- Oe,
637
+ K,
638
638
  s,
639
639
  i
640
- ]), 32)), u = W(l, ke, 16), d = W(l, Ae, 12), f = ce("aes-128-gcm", u, d);
640
+ ]), 32)), u = W(l, Ae, 16), d = W(l, je, 12), f = M("aes-128-gcm", u, d);
641
641
  f.setAuthTag(a.subarray(a.length - 16));
642
642
  let p = Buffer.concat([f.update(a.subarray(0, a.length - 16)), f.final()]), m = p.length - 1;
643
643
  for (; m >= 0 && p[m] === 0;) m--;
644
644
  if (p[m] !== 2) throw Error("web-push record missing the last-record delimiter");
645
645
  return p.subarray(0, m);
646
- }, Pe = async (e, t, n) => {
647
- let r = Me(e.keys, t), i = n.fetchImpl ?? fetch, a;
646
+ }, Fe = async (e, t, n) => {
647
+ let r = Ne(e.keys, t), i = n.fetchImpl ?? fetch, a;
648
648
  try {
649
649
  a = await i(e.endpoint, {
650
650
  method: "POST",
@@ -653,7 +653,7 @@ var F = class extends _.TaggedError()("PushTokenRejected", {
653
653
  "content-type": "application/octet-stream",
654
654
  ttl: String(n.ttl ?? 86400),
655
655
  urgency: "normal",
656
- authorization: K(e.endpoint, n.privateKey, n.contact)
656
+ authorization: ke(e.endpoint, n.privateKey, n.contact)
657
657
  },
658
658
  body: new Uint8Array(r)
659
659
  });
@@ -678,10 +678,10 @@ var F = class extends _.TaggedError()("PushTokenRejected", {
678
678
  status: a.status,
679
679
  detail: o.slice(0, 200)
680
680
  };
681
- }, Fe = (e, t) => {
681
+ }, Ie = (e, t) => {
682
682
  let n = Buffer.from(e), r = Buffer.from(t);
683
- return n.length === r.length && me(n, r);
684
- }, Ie = k("_voltro_notification_push_subscriptions", {
683
+ return n.length === r.length && he(n, r);
684
+ }, Le = k("_voltro_notification_push_subscriptions", {
685
685
  id: S({ prefix: "notifpush" }),
686
686
  subjectId: A(),
687
687
  endpointHash: A(),
@@ -691,7 +691,7 @@ var F = class extends _.TaggedError()("PushTokenRejected", {
691
691
  ua: A().nullable(),
692
692
  createdAt: j().default("now"),
693
693
  tenantId: A().nullable()
694
- }).unique("byPushEndpointHash", ["endpointHash"]).index("byPushSubject", ["subjectId"]), q = (e) => le("sha256").update(e).digest("hex"), J = () => {
694
+ }).unique("byPushEndpointHash", ["endpointHash"]).index("byPushSubject", ["subjectId"]), q = (e) => ue("sha256").update(e).digest("hex"), J = () => {
695
695
  let e = /* @__PURE__ */ new Map();
696
696
  return {
697
697
  upsert: async (t) => {
@@ -707,7 +707,7 @@ var F = class extends _.TaggedError()("PushTokenRejected", {
707
707
  },
708
708
  countForSubject: async (t) => [...e.values()].filter((e) => e.subjectId === t).length
709
709
  };
710
- }, Y = "_voltro_notification_push_subscriptions", Le = (e) => {
710
+ }, Y = "_voltro_notification_push_subscriptions", Re = (e) => {
711
711
  let t = async (t, n) => e.query({
712
712
  table: Y,
713
713
  predicate: t,
@@ -761,23 +761,23 @@ var F = class extends _.TaggedError()("PushTokenRejected", {
761
761
  },
762
762
  countForSubject: async (e) => (await t(b("subjectId", e))).length
763
763
  };
764
- }, Re = Symbol.for("@voltro/plugin-notifications:web-push"), X = () => {
764
+ }, ze = Symbol.for("@voltro/plugin-notifications:web-push"), X = () => {
765
765
  let e = globalThis;
766
- return e[Re] ??= {
766
+ return e[ze] ??= {
767
767
  store: J(),
768
768
  bound: !1
769
769
  };
770
- }, ze = (e) => {
770
+ }, Be = (e) => {
771
771
  let t = X();
772
- t.store = Le(e), t.bound = !0;
773
- }, Be = () => {
772
+ t.store = Re(e), t.bound = !0;
773
+ }, Ve = () => {
774
774
  let e = X();
775
775
  e.store = J(), e.bound = !1;
776
776
  }, Z = (e) => {
777
777
  let t = e ?? process.env.VOLTRO_VAPID_PRIVATE_KEY;
778
778
  if (t === void 0 || t.trim() === "") throw Error("web push needs VOLTRO_VAPID_PRIVATE_KEY (a base64url P-256 scalar). `voltro dev` mints one into .env.local; for production generate one and set it in the deployment environment. There is deliberately no default.");
779
779
  return t;
780
- }, Q = 3800, Ve = (e, t) => {
780
+ }, Q = 3800, He = (e, t) => {
781
781
  let n = typeof e.data?.url == "string" ? e.data.url : void 0, r = {
782
782
  title: e.title,
783
783
  body: e.body,
@@ -794,31 +794,31 @@ var F = class extends _.TaggedError()("PushTokenRejected", {
794
794
  ...o,
795
795
  body: `${l}…`
796
796
  }));
797
- }, He = (e = {}) => (X().channelOptions = e, We(e)), Ue = () => {
797
+ }, Ue = (e = {}) => (X().channelOptions = e, Ge(e)), We = () => {
798
798
  let e = X().channelOptions;
799
799
  G(Z(e?.privateKey));
800
- }, We = (e) => ({
800
+ }, Ge = (e) => ({
801
801
  id: e.id ?? "webPush",
802
802
  deliver: async (t) => {
803
- let n = await Ge(t, e), r = n.filter((e) => e.status === "failed");
803
+ let n = await Ke(t, e), r = n.filter((e) => e.status === "failed");
804
804
  if (n.length > 0 && r.length === n.length) throw Error(`web push failed for all ${r.length} endpoint(s): ${r[0].error ?? "unknown"}`);
805
805
  },
806
- deliverDetailed: (t) => Ge(t, e)
807
- }), Ge = async (e, t) => {
806
+ deliverDetailed: (t) => Ke(t, e)
807
+ }), Ke = async (e, t) => {
808
808
  let n = Z(t.privateKey), r = await X().store.listForSubject(e.to), i = [];
809
809
  for (let a of r) {
810
- let r = fe(16).toString("base64url"), o = {
810
+ let r = pe(16).toString("base64url"), o = {
811
811
  privateKey: n,
812
812
  ...t.contact === void 0 ? {} : { contact: t.contact },
813
813
  ...t.ttl === void 0 ? {} : { ttl: t.ttl },
814
814
  ...t.fetchImpl === void 0 ? {} : { fetchImpl: t.fetchImpl }
815
- }, s = await Pe({
815
+ }, s = await Fe({
816
816
  endpoint: a.endpoint,
817
817
  keys: {
818
818
  p256dh: a.p256dh,
819
819
  auth: a.auth
820
820
  }
821
- }, Ve(e, r), o);
821
+ }, He(e, r), o);
822
822
  s.kind === "delivered" ? i.push({
823
823
  endpoint: a.endpoint,
824
824
  status: "sent",
@@ -835,7 +835,7 @@ var F = class extends _.TaggedError()("PushTokenRejected", {
835
835
  });
836
836
  }
837
837
  return i;
838
- }, Ke = (e) => G(Z(e)), qe = k("_voltro_notification_inbox", {
838
+ }, qe = (e) => G(Z(e)), Je = k("_voltro_notification_inbox", {
839
839
  id: S({ prefix: "notif" }),
840
840
  subjectId: A(),
841
841
  category: A(),
@@ -846,7 +846,7 @@ var F = class extends _.TaggedError()("PushTokenRejected", {
846
846
  archivedAt: j().nullable(),
847
847
  createdAt: j().default("now"),
848
848
  tenantId: A().nullable()
849
- }).renamedFrom("notification_inbox").index("byInboxSubject", ["subjectId"]).index("byInboxSubjectCreatedAt", ["subjectId", "createdAt"]).index("byInboxSubjectUnread", ["subjectId"], { where: "\"readAt\" IS NULL" }), Je = k("_voltro_notification_preferences", {
849
+ }).renamedFrom("notification_inbox").index("byInboxSubject", ["subjectId"]).index("byInboxSubjectCreatedAt", ["subjectId", "createdAt"]).index("byInboxSubjectUnread", ["subjectId"], { where: "\"readAt\" IS NULL" }), Ye = k("_voltro_notification_preferences", {
850
850
  id: S({ prefix: "notifpref" }),
851
851
  subjectId: A(),
852
852
  category: A(),
@@ -856,7 +856,7 @@ var F = class extends _.TaggedError()("PushTokenRejected", {
856
856
  "subjectId",
857
857
  "category",
858
858
  "channel"
859
- ]), Ye = k("_voltro_notification_deliveries", {
859
+ ]), Xe = k("_voltro_notification_deliveries", {
860
860
  id: S({ prefix: "notifdlv" }),
861
861
  recipient: A(),
862
862
  category: A(),
@@ -867,35 +867,35 @@ var F = class extends _.TaggedError()("PushTokenRejected", {
867
867
  endpoint: A().nullable(),
868
868
  clickToken: A().nullable(),
869
869
  clickedAt: j().nullable()
870
- }).renamedFrom("notification_deliveries").index("byDeliveriesAt", ["at"]).index("byDeliveriesClickToken", ["clickToken"]), Xe = k("_voltro_notification_topic_subscriptions", {
870
+ }).renamedFrom("notification_deliveries").index("byDeliveriesAt", ["at"]).index("byDeliveriesClickToken", ["clickToken"]), Ze = k("_voltro_notification_topic_subscriptions", {
871
871
  id: S({ prefix: "notiftsub" }),
872
872
  topic: A(),
873
873
  subjectId: A(),
874
874
  tenantId: A().nullable()
875
- }).renamedFrom("notification_topic_subscriptions").unique("byTopicSubject", ["topic", "subjectId"]).index("byTopic", ["topic"]), $ = k("_voltro_notification_quiet_hours", {
875
+ }).renamedFrom("notification_topic_subscriptions").unique("byTopicSubject", ["topic", "subjectId"]).index("byTopic", ["topic"]), Qe = k("_voltro_notification_quiet_hours", {
876
876
  id: S({ prefix: "notifqh" }),
877
877
  subjectId: A(),
878
878
  startMinute: C(),
879
879
  endMinute: C(),
880
880
  tz: A().default("UTC"),
881
881
  policy: A().default("hold")
882
- }).renamedFrom("notification_quiet_hours").unique("byQuietHoursSubject", ["subjectId"]), Ze = k("_voltro_notification_held", {
882
+ }).renamedFrom("notification_quiet_hours").unique("byQuietHoursSubject", ["subjectId"]), $e = k("_voltro_notification_held", {
883
883
  id: S({ prefix: "notifheld" }),
884
884
  kind: A(),
885
885
  subjectId: A(),
886
886
  send: T(),
887
887
  flushAt: j()
888
- }).renamedFrom("notification_held").index("byHeldFlushAt", ["flushAt"]).index("byHeldSubjectKind", ["subjectId", "kind"]), Qe = class extends h.Tag("@voltro/plugin-notifications/NotificationService")() {}, $e = (e) => async (t) => {
888
+ }).renamedFrom("notification_held").index("byHeldFlushAt", ["flushAt"]).index("byHeldSubjectKind", ["subjectId", "kind"]), et = class extends h.Tag("@voltro/plugin-notifications/NotificationService")() {}, tt = (e) => async (t) => {
889
889
  if (e !== void 0) try {
890
890
  let n = await e(t);
891
891
  if (typeof n == "string" && n !== "") return n;
892
892
  } catch {}
893
893
  return t.request.subject.id ?? "anonymous";
894
- }, et = (e) => e.request.subject.tenantId ?? null, tt = "@voltro/plugin-notifications", nt = (h = {}) => {
895
- let _ = $e(h.resolveSubjectId), v = se({
896
- base: tt,
897
- alias: h.alias,
898
- instance: h.name
894
+ }, nt = (e) => e.request.subject.tenantId ?? null, $ = e, rt = (e = {}) => {
895
+ let h = tt(e.resolveSubjectId), _ = ce({
896
+ base: $,
897
+ alias: e.alias,
898
+ instance: e.name
899
899
  });
900
900
  D({
901
901
  source: "plugin",
@@ -909,7 +909,7 @@ var F = class extends _.TaggedError()("PushTokenRejected", {
909
909
  timeColumn: "createdAt",
910
910
  ttlMs: O(process.env.VOLTRO_NOTIFICATION_INBOX_READ_TTL_HOURS, 4320),
911
911
  envVar: "VOLTRO_NOTIFICATION_INBOX_READ_TTL_HOURS",
912
- where: ae("readAt")
912
+ where: oe("readAt")
913
913
  }), D({
914
914
  source: "plugin",
915
915
  table: "_voltro_notification_held",
@@ -917,81 +917,81 @@ var F = class extends _.TaggedError()("PushTokenRejected", {
917
917
  ttlMs: O(process.env.VOLTRO_NOTIFICATION_HELD_TTL_HOURS, 168),
918
918
  envVar: "VOLTRO_NOTIFICATION_HELD_TTL_HOURS"
919
919
  });
920
- let y = h.store === void 0, b = { current: h.store ?? R() }, x = {
921
- addInbox: (e) => b.current.addInbox(e),
922
- listInbox: (e, t) => b.current.listInbox(e, t),
923
- markRead: (e, t) => b.current.markRead(e, t),
924
- markUnread: (e, t) => b.current.markUnread(e, t),
925
- markAllRead: (e) => b.current.markAllRead(e),
926
- setArchived: (e, t, n) => b.current.setArchived(e, t, n),
927
- unreadCount: (e) => b.current.unreadCount(e),
928
- getPreferences: (e) => b.current.getPreferences(e),
929
- setPreference: (e) => b.current.setPreference(e),
930
- recordDelivery: (e) => b.current.recordDelivery(e),
931
- listDeliveries: (e) => b.current.listDeliveries(e),
932
- markClicked: (e) => b.current.markClicked(e),
933
- subscribeTopic: (e) => b.current.subscribeTopic(e),
934
- unsubscribeTopic: (e, t) => b.current.unsubscribeTopic(e, t),
935
- listTopicSubscribers: (e) => b.current.listTopicSubscribers(e),
936
- getQuietHours: (e) => b.current.getQuietHours(e),
937
- setQuietHours: (e) => b.current.setQuietHours(e),
938
- clearQuietHours: (e) => b.current.clearQuietHours(e),
939
- enqueueHeld: (e) => b.current.enqueueHeld(e),
940
- pendingDigest: (e) => b.current.pendingDigest(e),
941
- dueHeld: (e) => b.current.dueHeld(e),
942
- clearHeld: (e) => b.current.clearHeld(e)
943
- }, S = h.channels ?? [I()], C = S.some((e) => e.id === "inApp") ? S : [...S, xe(x)], w = h.digestWindowMs ?? 0, T = C.some((e) => e.id === "webPush"), E = we({
944
- channels: C,
945
- store: x,
946
- digestWindowMs: w
947
- }), k, A = [
920
+ let v = e.store === void 0, y = { current: e.store ?? R() }, b = {
921
+ addInbox: (e) => y.current.addInbox(e),
922
+ listInbox: (e, t) => y.current.listInbox(e, t),
923
+ markRead: (e, t) => y.current.markRead(e, t),
924
+ markUnread: (e, t) => y.current.markUnread(e, t),
925
+ markAllRead: (e) => y.current.markAllRead(e),
926
+ setArchived: (e, t, n) => y.current.setArchived(e, t, n),
927
+ unreadCount: (e) => y.current.unreadCount(e),
928
+ getPreferences: (e) => y.current.getPreferences(e),
929
+ setPreference: (e) => y.current.setPreference(e),
930
+ recordDelivery: (e) => y.current.recordDelivery(e),
931
+ listDeliveries: (e) => y.current.listDeliveries(e),
932
+ markClicked: (e) => y.current.markClicked(e),
933
+ subscribeTopic: (e) => y.current.subscribeTopic(e),
934
+ unsubscribeTopic: (e, t) => y.current.unsubscribeTopic(e, t),
935
+ listTopicSubscribers: (e) => y.current.listTopicSubscribers(e),
936
+ getQuietHours: (e) => y.current.getQuietHours(e),
937
+ setQuietHours: (e) => y.current.setQuietHours(e),
938
+ clearQuietHours: (e) => y.current.clearQuietHours(e),
939
+ enqueueHeld: (e) => y.current.enqueueHeld(e),
940
+ pendingDigest: (e) => y.current.pendingDigest(e),
941
+ dueHeld: (e) => y.current.dueHeld(e),
942
+ clearHeld: (e) => y.current.clearHeld(e)
943
+ }, x = e.channels ?? [I()], S = x.some((e) => e.id === "inApp") ? x : [...x, Se(b)], C = e.digestWindowMs ?? 0, w = S.some((e) => e.id === "webPush"), T = Te({
944
+ channels: S,
945
+ store: b,
946
+ digestWindowMs: C
947
+ }), E, k = [
948
948
  {
949
- ...n,
949
+ ...r,
950
950
  description: "The caller's in-app inbox items.",
951
- execute: (e, t) => g.promise(async () => E.inbox(await _(t), e))
951
+ execute: (e, t) => g.promise(async () => T.inbox(await h(t), e))
952
952
  },
953
953
  {
954
- ...f,
954
+ ...p,
955
955
  description: "The caller's unread count.",
956
- execute: (e, t) => g.promise(async () => ({ count: await E.unreadCount(await _(t)) }))
956
+ execute: (e, t) => g.promise(async () => ({ count: await T.unreadCount(await h(t)) }))
957
957
  },
958
958
  {
959
- ...i,
959
+ ...a,
960
960
  description: "Mark an inbox item read.",
961
- execute: (e, t) => g.promise(async () => ({ ok: await E.markRead(e.id, await _(t)) }))
961
+ execute: (e, t) => g.promise(async () => ({ ok: await T.markRead(e.id, await h(t)) }))
962
962
  },
963
963
  {
964
- ...a,
964
+ ...o,
965
965
  description: "Mark an inbox item unread again.",
966
- execute: (e, t) => g.promise(async () => ({ ok: await E.markUnread(e.id, await _(t)) }))
966
+ execute: (e, t) => g.promise(async () => ({ ok: await T.markUnread(e.id, await h(t)) }))
967
967
  },
968
968
  {
969
- ...r,
969
+ ...i,
970
970
  description: "Mark every unread item in the caller's inbox read.",
971
- execute: (e, t) => g.promise(async () => ({ count: await E.markAllRead(await _(t)) }))
971
+ execute: (e, t) => g.promise(async () => ({ count: await T.markAllRead(await h(t)) }))
972
972
  },
973
973
  {
974
- ...e,
974
+ ...t,
975
975
  description: "Archive an inbox item — a different state from read; this is what empties the inbox.",
976
- execute: (e, t) => g.promise(async () => ({ ok: await E.setArchived(e.id, await _(t), !0) }))
976
+ execute: (e, t) => g.promise(async () => ({ ok: await T.setArchived(e.id, await h(t), !0) }))
977
977
  },
978
978
  {
979
- ...d,
979
+ ...f,
980
980
  description: "Return an archived item to the inbox.",
981
- execute: (e, t) => g.promise(async () => ({ ok: await E.setArchived(e.id, await _(t), !1) }))
981
+ execute: (e, t) => g.promise(async () => ({ ok: await T.setArchived(e.id, await h(t), !1) }))
982
982
  },
983
983
  {
984
- ...s,
984
+ ...c,
985
985
  description: "The caller's channel preferences.",
986
- execute: (e, t) => g.promise(async () => E.getPreferences(await _(t)))
986
+ execute: (e, t) => g.promise(async () => T.getPreferences(await h(t)))
987
987
  },
988
988
  {
989
- ...c,
989
+ ...l,
990
990
  description: "Toggle a channel on/off for a category.",
991
991
  execute: (e, t) => g.promise(async () => {
992
992
  let { category: n, channel: r, enabled: i } = e;
993
- return await E.setPreference({
994
- subjectId: await _(t),
993
+ return await T.setPreference({
994
+ subjectId: await h(t),
995
995
  category: n,
996
996
  channel: r,
997
997
  enabled: i
@@ -999,22 +999,22 @@ var F = class extends _.TaggedError()("PushTokenRejected", {
999
999
  })
1000
1000
  },
1001
1001
  {
1002
- ...u,
1002
+ ...d,
1003
1003
  description: "Subscribe the caller to a broadcast topic.",
1004
- execute: (e, t) => g.promise(async () => (await E.subscribe(e.topic, await _(t), et(t)), { ok: !0 }))
1004
+ execute: (e, t) => g.promise(async () => (await T.subscribe(e.topic, await h(t), nt(t)), { ok: !0 }))
1005
1005
  },
1006
1006
  {
1007
- ...p,
1007
+ ...m,
1008
1008
  description: "Unsubscribe the caller from a broadcast topic.",
1009
- execute: (e, t) => g.promise(async () => (await E.unsubscribe(e.topic, await _(t)), { ok: !0 }))
1009
+ execute: (e, t) => g.promise(async () => (await T.unsubscribe(e.topic, await h(t)), { ok: !0 }))
1010
1010
  },
1011
1011
  {
1012
- ...l,
1012
+ ...u,
1013
1013
  description: "Set the caller's quiet-hours (DND) window.",
1014
1014
  execute: (e, t) => g.promise(async () => {
1015
1015
  let n = e;
1016
- return await E.setQuietHours({
1017
- subjectId: await _(t),
1016
+ return await T.setQuietHours({
1017
+ subjectId: await h(t),
1018
1018
  startMinute: n.startMinute,
1019
1019
  endMinute: n.endMinute,
1020
1020
  ...n.tz ? { tz: n.tz } : {},
@@ -1023,60 +1023,60 @@ var F = class extends _.TaggedError()("PushTokenRejected", {
1023
1023
  })
1024
1024
  },
1025
1025
  {
1026
- ...t,
1026
+ ...n,
1027
1027
  description: "Clear the caller's quiet-hours window.",
1028
- execute: (e, t) => g.promise(async () => (await E.clearQuietHours(await _(t)), { ok: !0 }))
1028
+ execute: (e, t) => g.promise(async () => (await T.clearQuietHours(await h(t)), { ok: !0 }))
1029
1029
  }
1030
- ], j = T ? [
1030
+ ], A = w ? [
1031
1031
  {
1032
- ...m,
1032
+ ...ee,
1033
1033
  description: "The VAPID application-server key a subscribing browser needs.",
1034
- execute: () => g.sync(() => ({ key: Ke(X().channelOptions?.privateKey) }))
1034
+ execute: () => g.sync(() => ({ key: qe(X().channelOptions?.privateKey) }))
1035
1035
  },
1036
1036
  {
1037
- ...ne,
1037
+ ...re,
1038
1038
  description: "Register the calling subject's browser push subscription (one row per endpoint).",
1039
1039
  execute: (e, t) => g.promise(async () => {
1040
1040
  let n = e;
1041
1041
  return await X().store.upsert({
1042
- subjectId: await _(t),
1042
+ subjectId: await h(t),
1043
1043
  endpoint: n.endpoint,
1044
1044
  endpointHash: q(n.endpoint),
1045
1045
  p256dh: n.p256dh,
1046
1046
  auth: n.auth,
1047
1047
  ua: n.ua ?? null,
1048
- tenantId: et(t)
1048
+ tenantId: nt(t)
1049
1049
  }), { ok: !0 };
1050
1050
  })
1051
1051
  },
1052
1052
  {
1053
- ...re,
1053
+ ...ie,
1054
1054
  description: "Remove one of the calling subject's own subscriptions.",
1055
- execute: (e, t) => g.promise(async () => ({ ok: await X().store.removeForSubject(q(e.endpoint), await _(t)) }))
1055
+ execute: (e, t) => g.promise(async () => ({ ok: await X().store.removeForSubject(q(e.endpoint), await h(t)) }))
1056
1056
  },
1057
1057
  {
1058
- ...te,
1058
+ ...ne,
1059
1059
  description: "How many push endpoints the calling subject has registered.",
1060
- execute: (e, t) => g.promise(async () => ({ endpoints: await X().store.countForSubject(await _(t)) }))
1060
+ execute: (e, t) => g.promise(async () => ({ endpoints: await X().store.countForSubject(await h(t)) }))
1061
1061
  }
1062
- ] : [], M = (e) => ({
1062
+ ] : [], j = (e) => ({
1063
1063
  kind: "json",
1064
1064
  data: e
1065
- }), ce = (e, t) => ({
1065
+ }), le = (e, t) => ({
1066
1066
  kind: "json",
1067
1067
  status: e,
1068
1068
  data: { error: t }
1069
- }), N = (e, t) => new URLSearchParams(new URL(e, "http://x").search).get(t), le = [{
1069
+ }), M = (e, t) => new URLSearchParams(new URL(e, "http://x").search).get(t), N = [{
1070
1070
  method: "GET",
1071
1071
  path: "/deliveries",
1072
1072
  description: "Recent delivery records (newest first) + per-channel + per-status counts.",
1073
1073
  handler: () => g.promise(async () => {
1074
- let e = await x.listDeliveries({ limit: 200 }), t = {}, n = 0, r = 0;
1074
+ let e = await b.listDeliveries({ limit: 200 }), t = {}, n = 0, r = 0;
1075
1075
  for (let i of e) t[i.channel] = (t[i.channel] ?? 0) + 1, i.status === "sent" ? n++ : r++;
1076
1076
  let i = e.slice(0, 100).map(({ clickToken: e, ...t }) => t);
1077
- return M({
1077
+ return j({
1078
1078
  deliveries: i,
1079
- channels: C.map((e) => e.id),
1079
+ channels: S.map((e) => e.id),
1080
1080
  stats: {
1081
1081
  sent: n,
1082
1082
  failed: r,
@@ -1089,28 +1089,28 @@ var F = class extends _.TaggedError()("PushTokenRejected", {
1089
1089
  path: "/inbox",
1090
1090
  description: "In-app inbox for a subject (?subjectId=…).",
1091
1091
  handler: (e) => {
1092
- let t = N(e.url, "subjectId");
1093
- return t ? g.promise(async () => M({
1094
- items: await x.listInbox(t, { limit: 100 }),
1095
- unread: await x.unreadCount(t)
1096
- })) : g.succeed(ce(400, "subjectId query param required"));
1092
+ let t = M(e.url, "subjectId");
1093
+ return t ? g.promise(async () => j({
1094
+ items: await b.listInbox(t, { limit: 100 }),
1095
+ unread: await b.unreadCount(t)
1096
+ })) : g.succeed(le(400, "subjectId query param required"));
1097
1097
  }
1098
1098
  }];
1099
- return oe({
1100
- name: v,
1101
- baseName: tt,
1099
+ return se({
1100
+ name: _,
1101
+ baseName: $,
1102
1102
  description: "Unified notifications — email/slack/sms/push/in-app with preference routing, inbox + delivery log.",
1103
1103
  permissions: ["inspect:read", "store:write"],
1104
- ...h.tables === !1 ? {} : { extendSchema: { tables: [
1105
- qe,
1104
+ ...e.tables === !1 ? {} : { extendSchema: { tables: [
1106
1105
  Je,
1107
1106
  Ye,
1108
1107
  Xe,
1109
- $,
1110
1108
  Ze,
1111
- Ie
1109
+ Qe,
1110
+ $e,
1111
+ Le
1112
1112
  ] } },
1113
- ...T ? {
1113
+ ...w ? {
1114
1114
  declaredEnv: [{
1115
1115
  name: "VOLTRO_VAPID_PRIVATE_KEY",
1116
1116
  required: !0,
@@ -1126,12 +1126,12 @@ var F = class extends _.TaggedError()("PushTokenRejected", {
1126
1126
  try {
1127
1127
  t = JSON.parse(Buffer.from(e.rawBody).toString("utf8")).token;
1128
1128
  } catch {}
1129
- if (typeof t != "string" || t === "" || !Fe(t, t)) return {
1129
+ if (typeof t != "string" || t === "" || !Ie(t, t)) return {
1130
1130
  status: 400,
1131
1131
  body: JSON.stringify({ error: "token required" }),
1132
1132
  contentType: "application/json"
1133
1133
  };
1134
- let n = await x.markClicked(t);
1134
+ let n = await b.markClicked(t);
1135
1135
  return {
1136
1136
  status: n ? 200 : 404,
1137
1137
  body: JSON.stringify({ ok: n }),
@@ -1141,27 +1141,27 @@ var F = class extends _.TaggedError()("PushTokenRejected", {
1141
1141
  maxBodyBytes: 4096
1142
1142
  }]
1143
1143
  } : {},
1144
- services: ie.succeed(Qe, E),
1145
- routes: [...A, ...j],
1146
- rpcClientDescriptors: T ? [...o, ...ee] : o,
1147
- inspectEndpoints: le,
1148
- bindDataStore: (e) => {
1149
- y && (b.current = be(e)), ze(e);
1150
- let t = h.flushIntervalMs ?? 3e4;
1151
- k = setInterval(() => {
1152
- E.flushDue().catch(() => {});
1153
- }, t), typeof k.unref == "function" && k.unref();
1144
+ services: ae.succeed(et, T),
1145
+ routes: [...k, ...A],
1146
+ rpcClientDescriptors: w ? [...s, ...te] : s,
1147
+ inspectEndpoints: N,
1148
+ bindDataStore: (t) => {
1149
+ v && (y.current = xe(t)), Be(t);
1150
+ let n = e.flushIntervalMs ?? 3e4;
1151
+ E = setInterval(() => {
1152
+ T.flushDue().catch(() => {});
1153
+ }, n), typeof E.unref == "function" && E.unref();
1154
1154
  },
1155
1155
  onActivate: (e) => g.sync(() => {
1156
- T && Ue(), e.logger.info("notifications active", {
1157
- channels: C.map((e) => e.id),
1158
- digestWindowMs: w
1156
+ w && We(), e.logger.info("notifications active", {
1157
+ channels: S.map((e) => e.id),
1158
+ digestWindowMs: C
1159
1159
  });
1160
1160
  }),
1161
1161
  onDeactivate: () => g.sync(() => {
1162
- k && clearInterval(k), k = void 0;
1162
+ E && clearInterval(E), E = void 0;
1163
1163
  })
1164
1164
  });
1165
1165
  };
1166
1166
  //#endregion
1167
- export { Qe as NotificationService, F as PushTokenRejected, we as buildNotificationService, I as consoleChannel, ve as customChannel, be as dataStoreNotificationStore, Le as dataStorePushSubscriptionStore, Ne as decryptWebPushPayload, Ye as deliveriesTable, ge as emailChannel, Me as encryptWebPushPayload, q as endpointHashOf, Ee as generateVapidPrivateKey, Ze as heldTable, xe as inAppChannel, Se as inQuietHours, qe as inboxTable, $e as makeSubjectId, R as memoryNotificationStore, J as memoryPushSubscriptionStore, B as minuteOfDayInZone, nt as notificationsPlugin, Je as preferencesTable, ye as pushChannel, Ie as pushSubscriptionsTable, Ce as quietHoursEnd, $ as quietHoursTable, Be as resetWebPushStoreForTest, L as resolveChannels, Pe as sendWebPush, _e as smsChannel, Xe as topicSubscriptionsTable, K as vapidAuthorization, G as vapidPublicKeyFor, He as webPushChannel, Ke as webPushPublicKey, he as webhookChannel };
1167
+ export { et as NotificationService, F as PushTokenRejected, Te as buildNotificationService, I as consoleChannel, ye as customChannel, xe as dataStoreNotificationStore, Re as dataStorePushSubscriptionStore, Pe as decryptWebPushPayload, Xe as deliveriesTable, _e as emailChannel, Ne as encryptWebPushPayload, q as endpointHashOf, De as generateVapidPrivateKey, $e as heldTable, Se as inAppChannel, Ce as inQuietHours, Je as inboxTable, tt as makeSubjectId, R as memoryNotificationStore, J as memoryPushSubscriptionStore, B as minuteOfDayInZone, rt as notificationsPlugin, Ye as preferencesTable, be as pushChannel, Le as pushSubscriptionsTable, we as quietHoursEnd, Qe as quietHoursTable, Ve as resetWebPushStoreForTest, L as resolveChannels, Fe as sendWebPush, ve as smsChannel, Ze as topicSubscriptionsTable, ke as vapidAuthorization, G as vapidPublicKeyFor, Ue as webPushChannel, qe as webPushPublicKey, ge as webhookChannel };