@voltro/plugin-notifications 0.25.0 → 0.27.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,18 +1,18 @@
1
- import { clearQuietHoursDescriptor as e, inboxDescriptor as t, markReadDescriptor as n, notificationsRpcClientImports as r, preferencesDescriptor as i, setPreferenceDescriptor as a, setQuietHoursDescriptor as o, subscribeDescriptor as s, unreadCountDescriptor as c, unsubscribeDescriptor as l } from "./rpc.js";
2
- import { Context as u, Effect as d, Layer as f, Schema as p } from "effect";
3
- import { and as m, boolean as h, eq as g, generateId as _, id as v, integer as y, isNotNull as ee, isNull as b, json as x, lte as S, registerRetention as C, retentionTtlMsFromEnv as w, table as T, text as E, timestamp as D } from "@voltro/database";
4
- import { definePlugin as O } from "@voltro/protocol";
5
- import { randomUUID as k } from "node:crypto";
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 ee, unreadCountDescriptor as te, unsubscribeDescriptor as ne } from "./rpc.js";
2
+ import { Context as d, Effect as f, Layer as p, Schema as m } from "effect";
3
+ import { and as h, boolean as g, eq as _, generateId as v, id as y, integer as b, isNotNull as re, isNull as x, json as S, lte as C, registerRetention as w, retentionTtlMsFromEnv as T, table as E, text as D, timestamp as O } from "@voltro/database";
4
+ import { definePlugin as k } from "@voltro/protocol";
5
+ import { randomUUID as A } from "node:crypto";
6
6
  //#region src/errors.ts
7
- var A = class extends p.TaggedError()("PushTokenRejected", {
8
- token: p.String,
9
- reason: p.String
10
- }) {}, j = () => ({
7
+ var j = class extends m.TaggedError()("PushTokenRejected", {
8
+ token: m.String,
9
+ reason: m.String
10
+ }) {}, M = () => ({
11
11
  id: "console",
12
12
  deliver: async (e) => {
13
13
  process.stdout.write(`[notify:${e.category}] → ${e.to}: ${e.title} — ${e.body}\n`);
14
14
  }
15
- }), M = (e) => ({
15
+ }), ie = (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 A = class extends p.TaggedError()("PushTokenRejected", {
25
25
  });
26
26
  if (!r.ok) throw Error(`webhook ${e.id ?? "slack"} POST ${r.status}`);
27
27
  }
28
- }), N = (e) => ({
28
+ }), ae = (e) => ({
29
29
  id: "email",
30
30
  deliver: (t) => e({
31
31
  to: t.to,
@@ -33,13 +33,13 @@ var A = class extends p.TaggedError()("PushTokenRejected", {
33
33
  html: `<p>${t.body}</p>`,
34
34
  text: t.body
35
35
  })
36
- }), P = (e) => ({
36
+ }), N = (e) => ({
37
37
  id: "sms",
38
38
  deliver: (t) => e(t.to, `${t.title}: ${t.body}`)
39
- }), F = (e, t) => ({
39
+ }), P = (e, t) => ({
40
40
  id: e,
41
41
  deliver: t
42
- }), I = (e) => ({
42
+ }), F = (e) => ({
43
43
  id: e.id ?? "push",
44
44
  deliver: async (t) => {
45
45
  let n = await e.tokensFor(t.to), r = t.data?.badge, i = typeof r == "number" ? r : void 0;
@@ -54,23 +54,24 @@ var A = class extends p.TaggedError()("PushTokenRejected", {
54
54
  try {
55
55
  await e.transport(n);
56
56
  } catch (e) {
57
- throw e instanceof A ? e : new A({
57
+ throw e instanceof j ? e : new j({
58
58
  token: r,
59
59
  reason: e instanceof Error ? e.message : String(e)
60
60
  });
61
61
  }
62
62
  }
63
63
  }
64
- }), L = (e, t, n, r, i) => {
64
+ }), I = (e, t, n, r, i) => {
65
65
  let a = n ? e.filter((e) => n.includes(e)) : [...e], o = new Set(r.filter((e) => e.subjectId === i && e.category === t && e.enabled === !1).map((e) => e.channel));
66
66
  return a.filter((e) => !o.has(e));
67
- }, R = () => {
67
+ }, L = () => {
68
68
  let e = [], t = /* @__PURE__ */ new Map(), n = [], r = /* @__PURE__ */ new Map(), i = /* @__PURE__ */ new Map(), a = [], o = (e) => `${e.subjectId}|${e.category}|${e.channel}`, s = (e, t) => `${e}|${t}`;
69
69
  return {
70
70
  addInbox: async (t) => {
71
71
  e.unshift({
72
72
  ...t,
73
73
  readAt: null,
74
+ archivedAt: null,
74
75
  data: t.data ?? {},
75
76
  tenantId: t.tenantId ?? null
76
77
  });
@@ -86,6 +87,31 @@ var A = class extends p.TaggedError()("PushTokenRejected", {
86
87
  readAt: (/* @__PURE__ */ new Date(0)).toISOString()
87
88
  }, !0);
88
89
  },
90
+ markUnread: async (t, n) => {
91
+ let r = e.findIndex((e) => e.id === t && e.subjectId === n);
92
+ return r === -1 ? !1 : (e[r] = {
93
+ ...e[r],
94
+ readAt: null
95
+ }, !0);
96
+ },
97
+ markAllRead: async (t) => {
98
+ let n = 0;
99
+ for (let r = 0; r < e.length; r++) {
100
+ let i = e[r];
101
+ i.subjectId !== t || i.readAt !== null || (e[r] = {
102
+ ...i,
103
+ readAt: (/* @__PURE__ */ new Date(0)).toISOString()
104
+ }, n++);
105
+ }
106
+ return n;
107
+ },
108
+ setArchived: async (t, n, r) => {
109
+ let i = e.findIndex((e) => e.id === t && e.subjectId === n);
110
+ return i === -1 ? !1 : (e[i] = {
111
+ ...e[i],
112
+ archivedAt: r ? (/* @__PURE__ */ new Date(0)).toISOString() : null
113
+ }, !0);
114
+ },
89
115
  unreadCount: async (t) => e.filter((e) => e.subjectId === t && e.readAt === null).length,
90
116
  getPreferences: async (e) => [...t.values()].filter((t) => t.subjectId === e),
91
117
  setPreference: async (e) => {
@@ -119,7 +145,7 @@ var A = class extends p.TaggedError()("PushTokenRejected", {
119
145
  for (let e = a.length - 1; e >= 0; e--) t.has(a[e].id) && a.splice(e, 1);
120
146
  }
121
147
  };
122
- }, z = (e) => e instanceof Date ? e.toISOString() : typeof e == "string" ? e : "", B = (e) => {
148
+ }, R = (e) => e instanceof Date ? e.toISOString() : typeof e == "string" ? e : "", z = (e) => {
123
149
  let t = async (t, n, r, i) => await e.query({
124
150
  table: t,
125
151
  predicate: n,
@@ -134,8 +160,9 @@ var A = class extends p.TaggedError()("PushTokenRejected", {
134
160
  title: String(e.title),
135
161
  body: String(e.body),
136
162
  data: e.data ?? {},
137
- readAt: e.readAt == null ? null : z(e.readAt),
138
- createdAt: z(e.createdAt),
163
+ readAt: e.readAt == null ? null : R(e.readAt),
164
+ archivedAt: e.archivedAt == null ? null : R(e.archivedAt),
165
+ createdAt: R(e.createdAt),
139
166
  tenantId: e.tenantId ?? null
140
167
  }), r = (e) => ({
141
168
  subjectId: String(e.subjectId),
@@ -149,7 +176,7 @@ var A = class extends p.TaggedError()("PushTokenRejected", {
149
176
  category: String(e.category),
150
177
  channel: String(e.channel),
151
178
  status: e.status,
152
- at: z(e.at)
179
+ at: R(e.at)
153
180
  };
154
181
  return e.error == null ? t : {
155
182
  ...t,
@@ -170,7 +197,7 @@ var A = class extends p.TaggedError()("PushTokenRejected", {
170
197
  kind: e.kind === "quiet" ? "quiet" : "digest",
171
198
  subjectId: String(e.subjectId),
172
199
  send: e.send ?? {},
173
- flushAt: z(e.flushAt)
200
+ flushAt: R(e.flushAt)
174
201
  });
175
202
  return {
176
203
  addInbox: async (t) => {
@@ -182,22 +209,30 @@ var A = class extends p.TaggedError()("PushTokenRejected", {
182
209
  body: t.body,
183
210
  data: t.data ?? {},
184
211
  readAt: null,
212
+ archivedAt: null,
185
213
  createdAt: t.createdAt,
186
214
  tenantId: t.tenantId ?? null
187
215
  });
188
216
  },
189
217
  listInbox: async (e, r) => {
190
- let i = r?.unreadOnly ? m(g("subjectId", e), b("readAt")) : g("subjectId", e);
218
+ let i = r?.unreadOnly ? h(_("subjectId", e), x("readAt")) : _("subjectId", e);
191
219
  return (await t("_voltro_notification_inbox", i, [{
192
220
  column: "createdAt",
193
221
  direction: "desc"
194
222
  }], r?.limit)).map(n);
195
223
  },
196
- markRead: async (n, r) => (await t("_voltro_notification_inbox", m(g("id", n), g("subjectId", r)), [], 1))[0] ? (await e.update("_voltro_notification_inbox", n, { readAt: (/* @__PURE__ */ new Date()).toISOString() }), !0) : !1,
197
- unreadCount: async (e) => (await t("_voltro_notification_inbox", m(g("subjectId", e), b("readAt")), [], void 0)).length,
198
- getPreferences: async (e) => (await t("_voltro_notification_preferences", g("subjectId", e), [], void 0)).map(r),
224
+ markRead: async (n, r) => (await t("_voltro_notification_inbox", h(_("id", n), _("subjectId", r)), [], 1))[0] ? (await e.update("_voltro_notification_inbox", n, { readAt: (/* @__PURE__ */ new Date()).toISOString() }), !0) : !1,
225
+ markUnread: async (n, r) => (await t("_voltro_notification_inbox", h(_("id", n), _("subjectId", r)), [], 1))[0] ? (await e.update("_voltro_notification_inbox", n, { readAt: null }), !0) : !1,
226
+ markAllRead: async (n) => {
227
+ let r = await t("_voltro_notification_inbox", h(_("subjectId", n), x("readAt")), [], void 0), i = (/* @__PURE__ */ new Date()).toISOString();
228
+ for (let t of r) await e.update("_voltro_notification_inbox", String(t.id), { readAt: i });
229
+ return r.length;
230
+ },
231
+ setArchived: async (n, r, i) => (await t("_voltro_notification_inbox", h(_("id", n), _("subjectId", r)), [], 1))[0] ? (await e.update("_voltro_notification_inbox", n, { archivedAt: i ? (/* @__PURE__ */ new Date()).toISOString() : null }), !0) : !1,
232
+ unreadCount: async (e) => (await t("_voltro_notification_inbox", h(_("subjectId", e), x("readAt")), [], void 0)).length,
233
+ getPreferences: async (e) => (await t("_voltro_notification_preferences", _("subjectId", e), [], void 0)).map(r),
199
234
  setPreference: async (t) => {
200
- let n = _({
235
+ let n = v({
201
236
  kind: "typeid",
202
237
  prefix: "notifpref"
203
238
  }, "_voltro_notification_preferences");
@@ -232,7 +267,7 @@ var A = class extends p.TaggedError()("PushTokenRejected", {
232
267
  direction: "desc"
233
268
  }], e?.limit)).map(i),
234
269
  subscribeTopic: async (t) => {
235
- let n = _({
270
+ let n = v({
236
271
  kind: "typeid",
237
272
  prefix: "notiftsub"
238
273
  }, "_voltro_notification_topic_subscriptions");
@@ -247,16 +282,16 @@ var A = class extends p.TaggedError()("PushTokenRejected", {
247
282
  });
248
283
  },
249
284
  unsubscribeTopic: async (n, r) => {
250
- let i = await t("_voltro_notification_topic_subscriptions", m(g("topic", n), g("subjectId", r)), [], 1);
285
+ let i = await t("_voltro_notification_topic_subscriptions", h(_("topic", n), _("subjectId", r)), [], 1);
251
286
  i[0] && await e.delete("_voltro_notification_topic_subscriptions", String(i[0].id));
252
287
  },
253
- listTopicSubscribers: async (e) => (await t("_voltro_notification_topic_subscriptions", g("topic", e), [], void 0)).map(a),
288
+ listTopicSubscribers: async (e) => (await t("_voltro_notification_topic_subscriptions", _("topic", e), [], void 0)).map(a),
254
289
  getQuietHours: async (e) => {
255
- let n = await t("_voltro_notification_quiet_hours", g("subjectId", e), [], 1);
290
+ let n = await t("_voltro_notification_quiet_hours", _("subjectId", e), [], 1);
256
291
  return n[0] ? o(n[0]) : null;
257
292
  },
258
293
  setQuietHours: async (t) => {
259
- let n = _({
294
+ let n = v({
260
295
  kind: "typeid",
261
296
  prefix: "notifqh"
262
297
  }, "_voltro_notification_quiet_hours");
@@ -278,7 +313,7 @@ var A = class extends p.TaggedError()("PushTokenRejected", {
278
313
  });
279
314
  },
280
315
  clearQuietHours: async (n) => {
281
- let r = await t("_voltro_notification_quiet_hours", g("subjectId", n), [], 1);
316
+ let r = await t("_voltro_notification_quiet_hours", _("subjectId", n), [], 1);
282
317
  r[0] && await e.delete("_voltro_notification_quiet_hours", String(r[0].id));
283
318
  },
284
319
  enqueueHeld: async (t) => {
@@ -290,11 +325,11 @@ var A = class extends p.TaggedError()("PushTokenRejected", {
290
325
  flushAt: t.flushAt
291
326
  });
292
327
  },
293
- pendingDigest: async (e) => (await t("_voltro_notification_held", m(g("kind", "digest"), g("subjectId", e)), [{
328
+ pendingDigest: async (e) => (await t("_voltro_notification_held", h(_("kind", "digest"), _("subjectId", e)), [{
294
329
  column: "flushAt",
295
330
  direction: "asc"
296
331
  }], void 0)).map(s),
297
- dueHeld: async (e) => (await t("_voltro_notification_held", S("flushAt", e), [{
332
+ dueHeld: async (e) => (await t("_voltro_notification_held", C("flushAt", e), [{
298
333
  column: "flushAt",
299
334
  direction: "asc"
300
335
  }], void 0)).map(s),
@@ -302,11 +337,11 @@ var A = class extends p.TaggedError()("PushTokenRejected", {
302
337
  for (let n of t) await e.delete("_voltro_notification_held", n);
303
338
  }
304
339
  };
305
- }, V = (e) => ({
340
+ }, B = (e) => ({
306
341
  id: "inApp",
307
342
  deliver: async (t) => {
308
343
  await e.addInbox({
309
- id: k(),
344
+ id: A(),
310
345
  subjectId: t.to,
311
346
  category: t.category,
312
347
  title: t.title,
@@ -316,7 +351,7 @@ var A = class extends p.TaggedError()("PushTokenRejected", {
316
351
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
317
352
  });
318
353
  }
319
- }), H = (e, t) => {
354
+ }), V = (e, t) => {
320
355
  try {
321
356
  let n = new Intl.DateTimeFormat("en-US", {
322
357
  timeZone: t,
@@ -328,16 +363,16 @@ var A = class extends p.TaggedError()("PushTokenRejected", {
328
363
  } catch {
329
364
  return e.getUTCHours() * 60 + e.getUTCMinutes();
330
365
  }
331
- }, U = (e, t) => {
366
+ }, H = (e, t) => {
332
367
  if (e.startMinute === e.endMinute) return !1;
333
- let n = H(t, e.tz ?? "UTC");
368
+ let n = V(t, e.tz ?? "UTC");
334
369
  return e.startMinute < e.endMinute ? n >= e.startMinute && n < e.endMinute : n >= e.startMinute || n < e.endMinute;
335
- }, W = (e, t) => {
336
- let n = H(t, e.tz ?? "UTC"), r = e.endMinute - n;
370
+ }, U = (e, t) => {
371
+ let n = V(t, e.tz ?? "UTC"), r = e.endMinute - n;
337
372
  return r <= 0 && (r += 1440), new Date(t.getTime() + r * 6e4);
338
- }, G = (e) => {
373
+ }, W = (e) => {
339
374
  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) => {
340
- 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 = {
375
+ let a = await e.store.getPreferences(i.to), o = I(n, i.category, i.channels, a, i.to), s = n.filter((e) => !o.includes(e)), c = [], l = [], u = {
341
376
  to: i.to,
342
377
  category: i.category,
343
378
  title: i.title,
@@ -349,7 +384,7 @@ var A = class extends p.TaggedError()("PushTokenRejected", {
349
384
  let a = t.get(n);
350
385
  if (!a) continue;
351
386
  let o = {
352
- id: k(),
387
+ id: A(),
353
388
  to: i.to,
354
389
  category: i.category,
355
390
  channel: n,
@@ -379,17 +414,17 @@ var A = class extends p.TaggedError()("PushTokenRejected", {
379
414
  };
380
415
  }, o = async (t) => {
381
416
  let n = await e.store.getQuietHours(t.to), o = new Date(r());
382
- if (n && U(n, o)) return (n.policy ?? "hold") === "drop" ? {
417
+ if (n && H(n, o)) return (n.policy ?? "hold") === "drop" ? {
383
418
  delivered: [],
384
419
  failed: [],
385
420
  skipped: [],
386
421
  held: "quiet-drop"
387
422
  } : (await e.store.enqueueHeld({
388
- id: k(),
423
+ id: A(),
389
424
  kind: "quiet",
390
425
  subjectId: t.to,
391
426
  send: t,
392
- flushAt: W(n, o).toISOString()
427
+ flushAt: U(n, o).toISOString()
393
428
  }), {
394
429
  delivered: [],
395
430
  failed: [],
@@ -399,7 +434,7 @@ var A = class extends p.TaggedError()("PushTokenRejected", {
399
434
  if (i > 0 && !t.channels) {
400
435
  let n = (await e.store.pendingDigest(t.to))[0]?.flushAt ?? new Date(o.getTime() + i).toISOString();
401
436
  return await e.store.enqueueHeld({
402
- id: k(),
437
+ id: A(),
403
438
  kind: "digest",
404
439
  subjectId: t.to,
405
440
  send: t,
@@ -418,6 +453,9 @@ var A = class extends p.TaggedError()("PushTokenRejected", {
418
453
  inbox: (t, n) => e.store.listInbox(t, n),
419
454
  unreadCount: (t) => e.store.unreadCount(t),
420
455
  markRead: (t, n) => e.store.markRead(t, n),
456
+ markUnread: (t, n) => e.store.markUnread(t, n),
457
+ markAllRead: (t) => e.store.markAllRead(t),
458
+ setArchived: (t, n, r) => e.store.setArchived(t, n, r),
421
459
  getPreferences: (t) => e.store.getPreferences(t),
422
460
  setPreference: (t) => e.store.setPreference(t),
423
461
  subscribe: (t, n, r) => e.store.subscribeTopic({
@@ -450,11 +488,11 @@ var A = class extends p.TaggedError()("PushTokenRejected", {
450
488
  let t = i.get(e.subjectId) ?? [];
451
489
  t.push(e), i.set(e.subjectId, t);
452
490
  }
453
- for (let [e, t] of i) await a(te(e, t));
491
+ for (let [e, t] of i) await a(G(e, t));
454
492
  return await e.store.clearHeld(n.map((e) => e.id)), n.length;
455
493
  }
456
494
  };
457
- }, te = (e, t) => {
495
+ }, G = (e, t) => {
458
496
  if (t.length === 1) return t[0].send;
459
497
  let n = t.map((e) => ({
460
498
  category: e.send.category,
@@ -469,119 +507,149 @@ var A = class extends p.TaggedError()("PushTokenRejected", {
469
507
  data: { items: n },
470
508
  tenantId: t[0].send.tenantId ?? null
471
509
  };
472
- }, K = T("_voltro_notification_inbox", {
473
- id: v({ prefix: "notif" }),
474
- subjectId: E(),
475
- category: E(),
476
- title: E(),
477
- body: E(),
478
- data: x(),
479
- readAt: D().nullable(),
480
- createdAt: D().default("now"),
481
- tenantId: E().nullable()
482
- }).renamedFrom("notification_inbox").index("byInboxSubject", ["subjectId"]), q = T("_voltro_notification_preferences", {
483
- id: v({ prefix: "notifpref" }),
484
- subjectId: E(),
485
- category: E(),
486
- channel: E(),
487
- enabled: h().default(!0)
510
+ }, K = E("_voltro_notification_inbox", {
511
+ id: y({ prefix: "notif" }),
512
+ subjectId: D(),
513
+ category: D(),
514
+ title: D(),
515
+ body: D(),
516
+ data: S(),
517
+ readAt: O().nullable(),
518
+ archivedAt: O().nullable(),
519
+ createdAt: O().default("now"),
520
+ tenantId: D().nullable()
521
+ }).renamedFrom("notification_inbox").index("byInboxSubject", ["subjectId"]), q = E("_voltro_notification_preferences", {
522
+ id: y({ prefix: "notifpref" }),
523
+ subjectId: D(),
524
+ category: D(),
525
+ channel: D(),
526
+ enabled: g().default(!0)
488
527
  }).renamedFrom("notification_preferences").unique("byPreferencesSubjectCategoryChannel", [
489
528
  "subjectId",
490
529
  "category",
491
530
  "channel"
492
- ]), J = T("_voltro_notification_deliveries", {
493
- id: v({ prefix: "notifdlv" }),
494
- recipient: E(),
495
- category: E(),
496
- channel: E(),
497
- status: E(),
498
- error: E().nullable(),
499
- at: D().default("now")
500
- }).renamedFrom("notification_deliveries").index("byDeliveriesAt", ["at"]), Y = T("_voltro_notification_topic_subscriptions", {
501
- id: v({ prefix: "notiftsub" }),
502
- topic: E(),
503
- subjectId: E(),
504
- tenantId: E().nullable()
505
- }).renamedFrom("notification_topic_subscriptions").unique("byTopicSubject", ["topic", "subjectId"]).index("byTopic", ["topic"]), X = T("_voltro_notification_quiet_hours", {
506
- id: v({ prefix: "notifqh" }),
507
- subjectId: E(),
508
- startMinute: y(),
509
- endMinute: y(),
510
- tz: E().default("UTC"),
511
- policy: E().default("hold")
512
- }).renamedFrom("notification_quiet_hours").unique("byQuietHoursSubject", ["subjectId"]), Z = T("_voltro_notification_held", {
513
- id: v({ prefix: "notifheld" }),
514
- kind: E(),
515
- subjectId: E(),
516
- send: x(),
517
- flushAt: D()
518
- }).renamedFrom("notification_held").index("byHeldFlushAt", ["flushAt"]).index("byHeldSubjectKind", ["subjectId", "kind"]), Q = class extends u.Tag("@voltro/plugin-notifications/NotificationService")() {}, $ = (e) => e.request.subject.id ?? "anonymous", ne = (e) => e.request.subject.tenantId ?? null, re = (u = {}) => {
519
- let p = u.name ? `@voltro/plugin-notifications#${u.name}` : "@voltro/plugin-notifications";
520
- C({
531
+ ]), J = E("_voltro_notification_deliveries", {
532
+ id: y({ prefix: "notifdlv" }),
533
+ recipient: D(),
534
+ category: D(),
535
+ channel: D(),
536
+ status: D(),
537
+ error: D().nullable(),
538
+ at: O().default("now")
539
+ }).renamedFrom("notification_deliveries").index("byDeliveriesAt", ["at"]), Y = E("_voltro_notification_topic_subscriptions", {
540
+ id: y({ prefix: "notiftsub" }),
541
+ topic: D(),
542
+ subjectId: D(),
543
+ tenantId: D().nullable()
544
+ }).renamedFrom("notification_topic_subscriptions").unique("byTopicSubject", ["topic", "subjectId"]).index("byTopic", ["topic"]), X = E("_voltro_notification_quiet_hours", {
545
+ id: y({ prefix: "notifqh" }),
546
+ subjectId: D(),
547
+ startMinute: b(),
548
+ endMinute: b(),
549
+ tz: D().default("UTC"),
550
+ policy: D().default("hold")
551
+ }).renamedFrom("notification_quiet_hours").unique("byQuietHoursSubject", ["subjectId"]), Z = E("_voltro_notification_held", {
552
+ id: y({ prefix: "notifheld" }),
553
+ kind: D(),
554
+ subjectId: D(),
555
+ send: S(),
556
+ flushAt: O()
557
+ }).renamedFrom("notification_held").index("byHeldFlushAt", ["flushAt"]).index("byHeldSubjectKind", ["subjectId", "kind"]), Q = class extends d.Tag("@voltro/plugin-notifications/NotificationService")() {}, $ = (e) => (t) => {
558
+ if (e !== void 0) try {
559
+ let n = e(t);
560
+ if (typeof n == "string" && n !== "") return n;
561
+ } catch {}
562
+ return t.request.subject.id ?? "anonymous";
563
+ }, oe = (e) => e.request.subject.tenantId ?? null, se = (d = {}) => {
564
+ let m = $(d.resolveSubjectId), h = d.name ? `@voltro/plugin-notifications#${d.name}` : "@voltro/plugin-notifications";
565
+ w({
521
566
  table: "_voltro_notification_deliveries",
522
567
  timeColumn: "at",
523
- ttlMs: w(process.env.VOLTRO_NOTIFICATION_DELIVERIES_TTL_HOURS, 2160)
524
- }), C({
568
+ ttlMs: T(process.env.VOLTRO_NOTIFICATION_DELIVERIES_TTL_HOURS, 2160)
569
+ }), w({
525
570
  table: "_voltro_notification_inbox",
526
571
  timeColumn: "createdAt",
527
- ttlMs: w(process.env.VOLTRO_NOTIFICATION_INBOX_READ_TTL_HOURS, 4320),
528
- where: ee("readAt")
529
- }), C({
572
+ ttlMs: T(process.env.VOLTRO_NOTIFICATION_INBOX_READ_TTL_HOURS, 4320),
573
+ where: re("readAt")
574
+ }), w({
530
575
  table: "_voltro_notification_held",
531
576
  timeColumn: "flushAt",
532
- ttlMs: w(process.env.VOLTRO_NOTIFICATION_HELD_TTL_HOURS, 168)
577
+ ttlMs: T(process.env.VOLTRO_NOTIFICATION_HELD_TTL_HOURS, 168)
533
578
  });
534
- let m = u.store === void 0, h = { current: u.store ?? R() }, g = {
535
- addInbox: (e) => h.current.addInbox(e),
536
- listInbox: (e, t) => h.current.listInbox(e, t),
537
- markRead: (e, t) => h.current.markRead(e, t),
538
- unreadCount: (e) => h.current.unreadCount(e),
539
- getPreferences: (e) => h.current.getPreferences(e),
540
- setPreference: (e) => h.current.setPreference(e),
541
- recordDelivery: (e) => h.current.recordDelivery(e),
542
- listDeliveries: (e) => h.current.listDeliveries(e),
543
- subscribeTopic: (e) => h.current.subscribeTopic(e),
544
- unsubscribeTopic: (e, t) => h.current.unsubscribeTopic(e, t),
545
- listTopicSubscribers: (e) => h.current.listTopicSubscribers(e),
546
- getQuietHours: (e) => h.current.getQuietHours(e),
547
- setQuietHours: (e) => h.current.setQuietHours(e),
548
- clearQuietHours: (e) => h.current.clearQuietHours(e),
549
- enqueueHeld: (e) => h.current.enqueueHeld(e),
550
- pendingDigest: (e) => h.current.pendingDigest(e),
551
- dueHeld: (e) => h.current.dueHeld(e),
552
- clearHeld: (e) => h.current.clearHeld(e)
553
- }, _ = u.channels ?? [j()], v = _.some((e) => e.id === "inApp") ? _ : [..._, V(g)], y = u.digestWindowMs ?? 0, b = G({
554
- channels: v,
555
- store: g,
556
- digestWindowMs: y
557
- }), x, S = [
579
+ let g = d.store === void 0, _ = { current: d.store ?? L() }, v = {
580
+ addInbox: (e) => _.current.addInbox(e),
581
+ listInbox: (e, t) => _.current.listInbox(e, t),
582
+ markRead: (e, t) => _.current.markRead(e, t),
583
+ markUnread: (e, t) => _.current.markUnread(e, t),
584
+ markAllRead: (e) => _.current.markAllRead(e),
585
+ setArchived: (e, t, n) => _.current.setArchived(e, t, n),
586
+ unreadCount: (e) => _.current.unreadCount(e),
587
+ getPreferences: (e) => _.current.getPreferences(e),
588
+ setPreference: (e) => _.current.setPreference(e),
589
+ recordDelivery: (e) => _.current.recordDelivery(e),
590
+ listDeliveries: (e) => _.current.listDeliveries(e),
591
+ subscribeTopic: (e) => _.current.subscribeTopic(e),
592
+ unsubscribeTopic: (e, t) => _.current.unsubscribeTopic(e, t),
593
+ listTopicSubscribers: (e) => _.current.listTopicSubscribers(e),
594
+ getQuietHours: (e) => _.current.getQuietHours(e),
595
+ setQuietHours: (e) => _.current.setQuietHours(e),
596
+ clearQuietHours: (e) => _.current.clearQuietHours(e),
597
+ enqueueHeld: (e) => _.current.enqueueHeld(e),
598
+ pendingDigest: (e) => _.current.pendingDigest(e),
599
+ dueHeld: (e) => _.current.dueHeld(e),
600
+ clearHeld: (e) => _.current.clearHeld(e)
601
+ }, y = d.channels ?? [M()], b = y.some((e) => e.id === "inApp") ? y : [...y, B(v)], x = d.digestWindowMs ?? 0, S = W({
602
+ channels: b,
603
+ store: v,
604
+ digestWindowMs: x
605
+ }), C, E = [
558
606
  {
559
- ...t,
607
+ ...n,
560
608
  description: "The caller's in-app inbox items.",
561
- execute: (e, t) => d.promise(() => b.inbox($(t), e))
609
+ execute: (e, t) => f.promise(() => S.inbox(m(t), e))
562
610
  },
563
611
  {
564
- ...c,
612
+ ...te,
565
613
  description: "The caller's unread count.",
566
- execute: (e, t) => d.promise(async () => ({ count: await b.unreadCount($(t)) }))
614
+ execute: (e, t) => f.promise(async () => ({ count: await S.unreadCount(m(t)) }))
567
615
  },
568
616
  {
569
- ...n,
617
+ ...i,
570
618
  description: "Mark an inbox item read.",
571
- execute: (e, t) => d.promise(async () => ({ ok: await b.markRead(e.id, $(t)) }))
619
+ execute: (e, t) => f.promise(async () => ({ ok: await S.markRead(e.id, m(t)) }))
572
620
  },
573
621
  {
574
- ...i,
622
+ ...a,
623
+ description: "Mark an inbox item unread again.",
624
+ execute: (e, t) => f.promise(async () => ({ ok: await S.markUnread(e.id, m(t)) }))
625
+ },
626
+ {
627
+ ...r,
628
+ description: "Mark every unread item in the caller's inbox read.",
629
+ execute: (e, t) => f.promise(async () => ({ count: await S.markAllRead(m(t)) }))
630
+ },
631
+ {
632
+ ...e,
633
+ description: "Archive an inbox item — a different state from read; this is what empties the inbox.",
634
+ execute: (e, t) => f.promise(async () => ({ ok: await S.setArchived(e.id, m(t), !0) }))
635
+ },
636
+ {
637
+ ...ee,
638
+ description: "Return an archived item to the inbox.",
639
+ execute: (e, t) => f.promise(async () => ({ ok: await S.setArchived(e.id, m(t), !1) }))
640
+ },
641
+ {
642
+ ...s,
575
643
  description: "The caller's channel preferences.",
576
- execute: (e, t) => d.promise(() => b.getPreferences($(t)))
644
+ execute: (e, t) => f.promise(() => S.getPreferences(m(t)))
577
645
  },
578
646
  {
579
- ...a,
647
+ ...c,
580
648
  description: "Toggle a channel on/off for a category.",
581
- execute: (e, t) => d.promise(async () => {
649
+ execute: (e, t) => f.promise(async () => {
582
650
  let { category: n, channel: r, enabled: i } = e;
583
- return await b.setPreference({
584
- subjectId: $(t),
651
+ return await S.setPreference({
652
+ subjectId: m(t),
585
653
  category: n,
586
654
  channel: r,
587
655
  enabled: i
@@ -589,22 +657,22 @@ var A = class extends p.TaggedError()("PushTokenRejected", {
589
657
  })
590
658
  },
591
659
  {
592
- ...s,
660
+ ...u,
593
661
  description: "Subscribe the caller to a broadcast topic.",
594
- execute: (e, t) => d.promise(async () => (await b.subscribe(e.topic, $(t), ne(t)), { ok: !0 }))
662
+ execute: (e, t) => f.promise(async () => (await S.subscribe(e.topic, m(t), oe(t)), { ok: !0 }))
595
663
  },
596
664
  {
597
- ...l,
665
+ ...ne,
598
666
  description: "Unsubscribe the caller from a broadcast topic.",
599
- execute: (e, t) => d.promise(async () => (await b.unsubscribe(e.topic, $(t)), { ok: !0 }))
667
+ execute: (e, t) => f.promise(async () => (await S.unsubscribe(e.topic, m(t)), { ok: !0 }))
600
668
  },
601
669
  {
602
- ...o,
670
+ ...l,
603
671
  description: "Set the caller's quiet-hours (DND) window.",
604
- execute: (e, t) => d.promise(async () => {
672
+ execute: (e, t) => f.promise(async () => {
605
673
  let n = e;
606
- return await b.setQuietHours({
607
- subjectId: $(t),
674
+ return await S.setQuietHours({
675
+ subjectId: m(t),
608
676
  startMinute: n.startMinute,
609
677
  endMinute: n.endMinute,
610
678
  ...n.tz ? { tz: n.tz } : {},
@@ -613,20 +681,20 @@ var A = class extends p.TaggedError()("PushTokenRejected", {
613
681
  })
614
682
  },
615
683
  {
616
- ...e,
684
+ ...t,
617
685
  description: "Clear the caller's quiet-hours window.",
618
- execute: (e, t) => d.promise(async () => (await b.clearQuietHours($(t)), { ok: !0 }))
686
+ execute: (e, t) => f.promise(async () => (await S.clearQuietHours(m(t)), { ok: !0 }))
619
687
  }
620
- ], T = (e) => ({
688
+ ], D = (e) => ({
621
689
  kind: "json",
622
690
  data: e
623
- }), E = (e, t) => ({
691
+ }), O = (e, t) => ({
624
692
  kind: "json",
625
693
  status: e,
626
694
  data: { error: t }
627
- }), D = (e, t) => new URLSearchParams(new URL(e, "http://x").search).get(t);
628
- return O({
629
- name: p,
695
+ }), A = (e, t) => new URLSearchParams(new URL(e, "http://x").search).get(t);
696
+ return k({
697
+ name: h,
630
698
  description: "Unified notifications — email/slack/sms/push/in-app with preference routing, inbox + delivery log.",
631
699
  permissions: ["inspect:read", "store:write"],
632
700
  extendSchema: { tables: [
@@ -637,19 +705,19 @@ var A = class extends p.TaggedError()("PushTokenRejected", {
637
705
  X,
638
706
  Z
639
707
  ] },
640
- services: f.succeed(Q, b),
641
- routes: S,
642
- rpcClientDescriptors: r,
708
+ services: p.succeed(Q, S),
709
+ routes: E,
710
+ rpcClientDescriptors: o,
643
711
  inspectEndpoints: [{
644
712
  method: "GET",
645
713
  path: "/deliveries",
646
714
  description: "Recent delivery records (newest first) + per-channel + per-status counts.",
647
- handler: () => d.promise(async () => {
648
- let e = await g.listDeliveries({ limit: 200 }), t = {}, n = 0, r = 0;
715
+ handler: () => f.promise(async () => {
716
+ let e = await v.listDeliveries({ limit: 200 }), t = {}, n = 0, r = 0;
649
717
  for (let i of e) t[i.channel] = (t[i.channel] ?? 0) + 1, i.status === "sent" ? n++ : r++;
650
- return T({
718
+ return D({
651
719
  deliveries: e.slice(0, 100),
652
- channels: v.map((e) => e.id),
720
+ channels: b.map((e) => e.id),
653
721
  stats: {
654
722
  sent: n,
655
723
  failed: r,
@@ -662,28 +730,28 @@ var A = class extends p.TaggedError()("PushTokenRejected", {
662
730
  path: "/inbox",
663
731
  description: "In-app inbox for a subject (?subjectId=…).",
664
732
  handler: (e) => {
665
- let t = D(e.url, "subjectId");
666
- return t ? d.promise(async () => T({
667
- items: await g.listInbox(t, { limit: 100 }),
668
- unread: await g.unreadCount(t)
669
- })) : d.succeed(E(400, "subjectId query param required"));
733
+ let t = A(e.url, "subjectId");
734
+ return t ? f.promise(async () => D({
735
+ items: await v.listInbox(t, { limit: 100 }),
736
+ unread: await v.unreadCount(t)
737
+ })) : f.succeed(O(400, "subjectId query param required"));
670
738
  }
671
739
  }],
672
740
  bindDataStore: (e) => {
673
- m && (h.current = B(e));
674
- let t = u.flushIntervalMs ?? 3e4;
675
- x = setInterval(() => {
676
- b.flushDue().catch(() => {});
677
- }, t), typeof x.unref == "function" && x.unref();
678
- },
679
- onActivate: (e) => d.sync(() => e.logger.info("notifications active", {
680
- channels: v.map((e) => e.id),
681
- digestWindowMs: y
741
+ g && (_.current = z(e));
742
+ let t = d.flushIntervalMs ?? 3e4;
743
+ C = setInterval(() => {
744
+ S.flushDue().catch(() => {});
745
+ }, t), typeof C.unref == "function" && C.unref();
746
+ },
747
+ onActivate: (e) => f.sync(() => e.logger.info("notifications active", {
748
+ channels: b.map((e) => e.id),
749
+ digestWindowMs: x
682
750
  })),
683
- onDeactivate: () => d.sync(() => {
684
- x && clearInterval(x), x = void 0;
751
+ onDeactivate: () => f.sync(() => {
752
+ C && clearInterval(C), C = void 0;
685
753
  })
686
754
  });
687
755
  };
688
756
  //#endregion
689
- export { Q as NotificationService, A as PushTokenRejected, G as buildNotificationService, j as consoleChannel, F as customChannel, B as dataStoreNotificationStore, J as deliveriesTable, N as emailChannel, Z as heldTable, V as inAppChannel, U as inQuietHours, K as inboxTable, R as memoryNotificationStore, H as minuteOfDayInZone, re as notificationsPlugin, q as preferencesTable, I as pushChannel, W as quietHoursEnd, X as quietHoursTable, L as resolveChannels, P as smsChannel, Y as topicSubscriptionsTable, M as webhookChannel };
757
+ export { Q as NotificationService, j as PushTokenRejected, W as buildNotificationService, M as consoleChannel, P as customChannel, z as dataStoreNotificationStore, J as deliveriesTable, ae as emailChannel, Z as heldTable, B as inAppChannel, H as inQuietHours, K as inboxTable, $ as makeSubjectId, L as memoryNotificationStore, V as minuteOfDayInZone, se as notificationsPlugin, q as preferencesTable, F as pushChannel, U as quietHoursEnd, X as quietHoursTable, I as resolveChannels, N as smsChannel, Y as topicSubscriptionsTable, ie as webhookChannel };