@voltro/plugin-notifications 0.33.0 → 0.34.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.d.ts CHANGED
@@ -305,6 +305,48 @@ export declare interface NotificationsPluginOptions {
305
305
  /** How often the scheduled flush drains due digest windows + quiet-hours
306
306
  * deferrals. Default 30s. Only runs once the store is bound at boot. */
307
307
  readonly flushIntervalMs?: number;
308
+ /**
309
+ * Namespace for this plugin's rpc tags + inspect endpoints. Default `notifications`.
310
+ *
311
+ * Set it when your app already publishes under that name — an exact tag
312
+ * collision is fatal at codegen, and this is the way out. Orthogonal to
313
+ * `name` below: `alias` REPLACES the namespace, `name` distinguishes two
314
+ * installations within it.
315
+ *
316
+ * The cost, stated because nothing else states it: the local and cloud
317
+ * dashboards fetch this plugin's panel at the DEFAULT slug, so an aliased
318
+ * install keeps working while its dashboard panel 404s. Alias to escape a
319
+ * collision, not for taste.
320
+ */
321
+ readonly alias?: string;
322
+ /**
323
+ * Contribute this plugin's tables via `extendSchema.tables`. Default `true`.
324
+ *
325
+ * Set `false` when your app ALREADY declares equivalent tables and you want to
326
+ * keep them — the seam this exists for. The plugin then contributes no DDL and
327
+ * the declarative differ never proposes its tables; everything else (routes,
328
+ * inspect, interceptors) is unchanged.
329
+ *
330
+ * **What you take over, exactly:** all six tables this plugin writes BY NAME —
331
+ * `_voltro_notification_inbox`, `_voltro_notification_preferences`,
332
+ * `_voltro_notification_deliveries`, `_voltro_notification_topic_subscriptions`,
333
+ * `_voltro_notification_quiet_hours`, `_voltro_notification_held`. This is the
334
+ * 14 670-row overlap the seam was reported for; note it does NOT solve an rpc
335
+ * name collision — `alias` is the field for that.
336
+ *
337
+ * It is not offered on every plugin, and the omissions are deliberate rather
338
+ * than unfinished: a `tables: false` that quietly disables a table carrying an
339
+ * AUTHORIZATION or SAFETY decision — the SAML replay cache, SCIM provisioning
340
+ * state, billing's usage counters, cdc-out's outbox — is a security regression
341
+ * shipped as an ergonomics feature. Those plugins need a named store seam
342
+ * first, not a boolean.
343
+ */
344
+ readonly tables?: boolean;
345
+ /**
346
+ * Discriminator for a SECOND installation of this plugin, when one app runs
347
+ * two (`@voltro/plugin-notifications#analytics`). Not a rename — for that use
348
+ * `alias`.
349
+ */
308
350
  readonly name?: string;
309
351
  }
310
352
 
package/dist/index.js CHANGED
@@ -1,18 +1,18 @@
1
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
2
  import { Context as d, Effect as f, Layer as p, Schema as m } from "effect";
3
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";
4
+ import { definePlugin as ie, pluginInstanceName as ae } from "@voltro/protocol";
5
+ import { randomUUID as k } from "node:crypto";
6
6
  //#region src/errors.ts
7
- var j = class extends m.TaggedError()("PushTokenRejected", {
7
+ var A = class extends m.TaggedError()("PushTokenRejected", {
8
8
  token: m.String,
9
9
  reason: m.String
10
- }) {}, M = () => ({
10
+ }) {}, j = () => ({
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
- }), ie = (e) => ({
15
+ }), oe = (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 j = class extends m.TaggedError()("PushTokenRejected", {
25
25
  });
26
26
  if (!r.ok) throw Error(`webhook ${e.id ?? "slack"} POST ${r.status}`);
27
27
  }
28
- }), ae = (e) => ({
28
+ }), M = (e) => ({
29
29
  id: "email",
30
30
  deliver: (t) => e({
31
31
  to: t.to,
@@ -39,7 +39,7 @@ var j = class extends m.TaggedError()("PushTokenRejected", {
39
39
  }), P = (e, t) => ({
40
40
  id: e,
41
41
  deliver: t
42
- }), F = (e) => ({
42
+ }), se = (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,17 +54,17 @@ var j = class extends m.TaggedError()("PushTokenRejected", {
54
54
  try {
55
55
  await e.transport(n);
56
56
  } catch (e) {
57
- throw e instanceof j ? e : new j({
57
+ throw e instanceof A ? e : new A({
58
58
  token: r,
59
59
  reason: e instanceof Error ? e.message : String(e)
60
60
  });
61
61
  }
62
62
  }
63
63
  }
64
- }), I = (e, t, n, r, i) => {
64
+ }), F = (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
- }, L = () => {
67
+ }, I = () => {
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) => {
@@ -145,7 +145,7 @@ var j = class extends m.TaggedError()("PushTokenRejected", {
145
145
  for (let e = a.length - 1; e >= 0; e--) t.has(a[e].id) && a.splice(e, 1);
146
146
  }
147
147
  };
148
- }, R = (e) => e instanceof Date ? e.toISOString() : typeof e == "string" ? e : "", z = (e) => {
148
+ }, L = (e) => e instanceof Date ? e.toISOString() : typeof e == "string" ? e : "", R = (e) => {
149
149
  let t = async (t, n, r, i) => await e.query({
150
150
  table: t,
151
151
  predicate: n,
@@ -160,9 +160,9 @@ var j = class extends m.TaggedError()("PushTokenRejected", {
160
160
  title: String(e.title),
161
161
  body: String(e.body),
162
162
  data: e.data ?? {},
163
- readAt: e.readAt == null ? null : R(e.readAt),
164
- archivedAt: e.archivedAt == null ? null : R(e.archivedAt),
165
- createdAt: R(e.createdAt),
163
+ readAt: e.readAt == null ? null : L(e.readAt),
164
+ archivedAt: e.archivedAt == null ? null : L(e.archivedAt),
165
+ createdAt: L(e.createdAt),
166
166
  tenantId: e.tenantId ?? null
167
167
  }), r = (e) => ({
168
168
  subjectId: String(e.subjectId),
@@ -176,7 +176,7 @@ var j = class extends m.TaggedError()("PushTokenRejected", {
176
176
  category: String(e.category),
177
177
  channel: String(e.channel),
178
178
  status: e.status,
179
- at: R(e.at)
179
+ at: L(e.at)
180
180
  };
181
181
  return e.error == null ? t : {
182
182
  ...t,
@@ -197,7 +197,7 @@ var j = class extends m.TaggedError()("PushTokenRejected", {
197
197
  kind: e.kind === "quiet" ? "quiet" : "digest",
198
198
  subjectId: String(e.subjectId),
199
199
  send: e.send ?? {},
200
- flushAt: R(e.flushAt)
200
+ flushAt: L(e.flushAt)
201
201
  });
202
202
  return {
203
203
  addInbox: async (t) => {
@@ -337,11 +337,11 @@ var j = class extends m.TaggedError()("PushTokenRejected", {
337
337
  for (let n of t) await e.delete("_voltro_notification_held", n);
338
338
  }
339
339
  };
340
- }, B = (e) => ({
340
+ }, z = (e) => ({
341
341
  id: "inApp",
342
342
  deliver: async (t) => {
343
343
  await e.addInbox({
344
- id: A(),
344
+ id: k(),
345
345
  subjectId: t.to,
346
346
  category: t.category,
347
347
  title: t.title,
@@ -351,7 +351,7 @@ var j = class extends m.TaggedError()("PushTokenRejected", {
351
351
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
352
352
  });
353
353
  }
354
- }), V = (e, t) => {
354
+ }), B = (e, t) => {
355
355
  try {
356
356
  let n = new Intl.DateTimeFormat("en-US", {
357
357
  timeZone: t,
@@ -363,16 +363,16 @@ var j = class extends m.TaggedError()("PushTokenRejected", {
363
363
  } catch {
364
364
  return e.getUTCHours() * 60 + e.getUTCMinutes();
365
365
  }
366
- }, H = (e, t) => {
366
+ }, V = (e, t) => {
367
367
  if (e.startMinute === e.endMinute) return !1;
368
- let n = V(t, e.tz ?? "UTC");
368
+ let n = B(t, e.tz ?? "UTC");
369
369
  return e.startMinute < e.endMinute ? n >= e.startMinute && n < e.endMinute : n >= e.startMinute || n < e.endMinute;
370
- }, U = (e, t) => {
371
- let n = V(t, e.tz ?? "UTC"), r = e.endMinute - n;
370
+ }, H = (e, t) => {
371
+ let n = B(t, e.tz ?? "UTC"), r = e.endMinute - n;
372
372
  return r <= 0 && (r += 1440), new Date(t.getTime() + r * 6e4);
373
- }, W = (e) => {
373
+ }, U = (e) => {
374
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) => {
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 = {
375
+ let a = await e.store.getPreferences(i.to), o = F(n, i.category, i.channels, a, i.to), s = n.filter((e) => !o.includes(e)), c = [], l = [], u = {
376
376
  to: i.to,
377
377
  category: i.category,
378
378
  title: i.title,
@@ -384,7 +384,7 @@ var j = class extends m.TaggedError()("PushTokenRejected", {
384
384
  let a = t.get(n);
385
385
  if (!a) continue;
386
386
  let o = {
387
- id: A(),
387
+ id: k(),
388
388
  to: i.to,
389
389
  category: i.category,
390
390
  channel: n,
@@ -414,17 +414,17 @@ var j = class extends m.TaggedError()("PushTokenRejected", {
414
414
  };
415
415
  }, o = async (t) => {
416
416
  let n = await e.store.getQuietHours(t.to), o = new Date(r());
417
- if (n && H(n, o)) return (n.policy ?? "hold") === "drop" ? {
417
+ if (n && V(n, o)) return (n.policy ?? "hold") === "drop" ? {
418
418
  delivered: [],
419
419
  failed: [],
420
420
  skipped: [],
421
421
  held: "quiet-drop"
422
422
  } : (await e.store.enqueueHeld({
423
- id: A(),
423
+ id: k(),
424
424
  kind: "quiet",
425
425
  subjectId: t.to,
426
426
  send: t,
427
- flushAt: U(n, o).toISOString()
427
+ flushAt: H(n, o).toISOString()
428
428
  }), {
429
429
  delivered: [],
430
430
  failed: [],
@@ -434,7 +434,7 @@ var j = class extends m.TaggedError()("PushTokenRejected", {
434
434
  if (i > 0 && !t.channels) {
435
435
  let n = (await e.store.pendingDigest(t.to))[0]?.flushAt ?? new Date(o.getTime() + i).toISOString();
436
436
  return await e.store.enqueueHeld({
437
- id: A(),
437
+ id: k(),
438
438
  kind: "digest",
439
439
  subjectId: t.to,
440
440
  send: t,
@@ -488,11 +488,11 @@ var j = class extends m.TaggedError()("PushTokenRejected", {
488
488
  let t = i.get(e.subjectId) ?? [];
489
489
  t.push(e), i.set(e.subjectId, t);
490
490
  }
491
- for (let [e, t] of i) await a(G(e, t));
491
+ for (let [e, t] of i) await a(W(e, t));
492
492
  return await e.store.clearHeld(n.map((e) => e.id)), n.length;
493
493
  }
494
494
  };
495
- }, G = (e, t) => {
495
+ }, W = (e, t) => {
496
496
  if (t.length === 1) return t[0].send;
497
497
  let n = t.map((e) => ({
498
498
  category: e.send.category,
@@ -507,7 +507,7 @@ var j = class extends m.TaggedError()("PushTokenRejected", {
507
507
  data: { items: n },
508
508
  tenantId: t[0].send.tenantId ?? null
509
509
  };
510
- }, K = E("_voltro_notification_inbox", {
510
+ }, G = E("_voltro_notification_inbox", {
511
511
  id: y({ prefix: "notif" }),
512
512
  subjectId: D(),
513
513
  category: D(),
@@ -518,7 +518,7 @@ var j = class extends m.TaggedError()("PushTokenRejected", {
518
518
  archivedAt: O().nullable(),
519
519
  createdAt: O().default("now"),
520
520
  tenantId: D().nullable()
521
- }).renamedFrom("notification_inbox").index("byInboxSubject", ["subjectId"]).index("byInboxSubjectCreatedAt", ["subjectId", "createdAt"]).index("byInboxSubjectUnread", ["subjectId"], { where: "\"readAt\" IS NULL" }), q = E("_voltro_notification_preferences", {
521
+ }).renamedFrom("notification_inbox").index("byInboxSubject", ["subjectId"]).index("byInboxSubjectCreatedAt", ["subjectId", "createdAt"]).index("byInboxSubjectUnread", ["subjectId"], { where: "\"readAt\" IS NULL" }), K = E("_voltro_notification_preferences", {
522
522
  id: y({ prefix: "notifpref" }),
523
523
  subjectId: D(),
524
524
  category: D(),
@@ -528,7 +528,7 @@ var j = class extends m.TaggedError()("PushTokenRejected", {
528
528
  "subjectId",
529
529
  "category",
530
530
  "channel"
531
- ]), J = E("_voltro_notification_deliveries", {
531
+ ]), q = E("_voltro_notification_deliveries", {
532
532
  id: y({ prefix: "notifdlv" }),
533
533
  recipient: D(),
534
534
  category: D(),
@@ -536,50 +536,57 @@ var j = class extends m.TaggedError()("PushTokenRejected", {
536
536
  status: D(),
537
537
  error: D().nullable(),
538
538
  at: O().default("now")
539
- }).renamedFrom("notification_deliveries").index("byDeliveriesAt", ["at"]), Y = E("_voltro_notification_topic_subscriptions", {
539
+ }).renamedFrom("notification_deliveries").index("byDeliveriesAt", ["at"]), J = E("_voltro_notification_topic_subscriptions", {
540
540
  id: y({ prefix: "notiftsub" }),
541
541
  topic: D(),
542
542
  subjectId: D(),
543
543
  tenantId: D().nullable()
544
- }).renamedFrom("notification_topic_subscriptions").unique("byTopicSubject", ["topic", "subjectId"]).index("byTopic", ["topic"]), X = E("_voltro_notification_quiet_hours", {
544
+ }).renamedFrom("notification_topic_subscriptions").unique("byTopicSubject", ["topic", "subjectId"]).index("byTopic", ["topic"]), Y = E("_voltro_notification_quiet_hours", {
545
545
  id: y({ prefix: "notifqh" }),
546
546
  subjectId: D(),
547
547
  startMinute: b(),
548
548
  endMinute: b(),
549
549
  tz: D().default("UTC"),
550
550
  policy: D().default("hold")
551
- }).renamedFrom("notification_quiet_hours").unique("byQuietHoursSubject", ["subjectId"]), Z = E("_voltro_notification_held", {
551
+ }).renamedFrom("notification_quiet_hours").unique("byQuietHoursSubject", ["subjectId"]), X = E("_voltro_notification_held", {
552
552
  id: y({ prefix: "notifheld" }),
553
553
  kind: D(),
554
554
  subjectId: D(),
555
555
  send: S(),
556
556
  flushAt: O()
557
- }).renamedFrom("notification_held").index("byHeldFlushAt", ["flushAt"]).index("byHeldSubjectKind", ["subjectId", "kind"]), Q = class extends d.Tag("@voltro/plugin-notifications/NotificationService")() {}, $ = (e) => async (t) => {
557
+ }).renamedFrom("notification_held").index("byHeldFlushAt", ["flushAt"]).index("byHeldSubjectKind", ["subjectId", "kind"]), Z = class extends d.Tag("@voltro/plugin-notifications/NotificationService")() {}, Q = (e) => async (t) => {
558
558
  if (e !== void 0) try {
559
559
  let n = await e(t);
560
560
  if (typeof n == "string" && n !== "") return n;
561
561
  } catch {}
562
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";
563
+ }, ce = (e) => e.request.subject.tenantId ?? null, $ = "@voltro/plugin-notifications", le = (d = {}) => {
564
+ let m = Q(d.resolveSubjectId), h = ae({
565
+ base: $,
566
+ alias: d.alias,
567
+ instance: d.name
568
+ });
565
569
  w({
570
+ source: "plugin",
566
571
  table: "_voltro_notification_deliveries",
567
572
  timeColumn: "at",
568
573
  ttlMs: T(process.env.VOLTRO_NOTIFICATION_DELIVERIES_TTL_HOURS, 2160),
569
574
  envVar: "VOLTRO_NOTIFICATION_DELIVERIES_TTL_HOURS"
570
575
  }), w({
576
+ source: "plugin",
571
577
  table: "_voltro_notification_inbox",
572
578
  timeColumn: "createdAt",
573
579
  ttlMs: T(process.env.VOLTRO_NOTIFICATION_INBOX_READ_TTL_HOURS, 4320),
574
580
  envVar: "VOLTRO_NOTIFICATION_INBOX_READ_TTL_HOURS",
575
581
  where: re("readAt")
576
582
  }), w({
583
+ source: "plugin",
577
584
  table: "_voltro_notification_held",
578
585
  timeColumn: "flushAt",
579
586
  ttlMs: T(process.env.VOLTRO_NOTIFICATION_HELD_TTL_HOURS, 168),
580
587
  envVar: "VOLTRO_NOTIFICATION_HELD_TTL_HOURS"
581
588
  });
582
- let g = d.store === void 0, _ = { current: d.store ?? L() }, v = {
589
+ let g = d.store === void 0, _ = { current: d.store ?? I() }, v = {
583
590
  addInbox: (e) => _.current.addInbox(e),
584
591
  listInbox: (e, t) => _.current.listInbox(e, t),
585
592
  markRead: (e, t) => _.current.markRead(e, t),
@@ -601,7 +608,7 @@ var j = class extends m.TaggedError()("PushTokenRejected", {
601
608
  pendingDigest: (e) => _.current.pendingDigest(e),
602
609
  dueHeld: (e) => _.current.dueHeld(e),
603
610
  clearHeld: (e) => _.current.clearHeld(e)
604
- }, y = d.channels ?? [M()], b = y.some((e) => e.id === "inApp") ? y : [...y, B(v)], x = d.digestWindowMs ?? 0, S = W({
611
+ }, y = d.channels ?? [j()], b = y.some((e) => e.id === "inApp") ? y : [...y, z(v)], x = d.digestWindowMs ?? 0, S = U({
605
612
  channels: b,
606
613
  store: v,
607
614
  digestWindowMs: x
@@ -662,7 +669,7 @@ var j = class extends m.TaggedError()("PushTokenRejected", {
662
669
  {
663
670
  ...u,
664
671
  description: "Subscribe the caller to a broadcast topic.",
665
- execute: (e, t) => f.promise(async () => (await S.subscribe(e.topic, await m(t), oe(t)), { ok: !0 }))
672
+ execute: (e, t) => f.promise(async () => (await S.subscribe(e.topic, await m(t), ce(t)), { ok: !0 }))
666
673
  },
667
674
  {
668
675
  ...ne,
@@ -695,53 +702,54 @@ var j = class extends m.TaggedError()("PushTokenRejected", {
695
702
  kind: "json",
696
703
  status: e,
697
704
  data: { error: t }
698
- }), A = (e, t) => new URLSearchParams(new URL(e, "http://x").search).get(t);
699
- return k({
705
+ }), k = (e, t) => new URLSearchParams(new URL(e, "http://x").search).get(t), A = [{
706
+ method: "GET",
707
+ path: "/deliveries",
708
+ description: "Recent delivery records (newest first) + per-channel + per-status counts.",
709
+ handler: () => f.promise(async () => {
710
+ let e = await v.listDeliveries({ limit: 200 }), t = {}, n = 0, r = 0;
711
+ for (let i of e) t[i.channel] = (t[i.channel] ?? 0) + 1, i.status === "sent" ? n++ : r++;
712
+ return D({
713
+ deliveries: e.slice(0, 100),
714
+ channels: b.map((e) => e.id),
715
+ stats: {
716
+ sent: n,
717
+ failed: r,
718
+ byChannel: t
719
+ }
720
+ });
721
+ })
722
+ }, {
723
+ method: "GET",
724
+ path: "/inbox",
725
+ description: "In-app inbox for a subject (?subjectId=…).",
726
+ handler: (e) => {
727
+ let t = k(e.url, "subjectId");
728
+ return t ? f.promise(async () => D({
729
+ items: await v.listInbox(t, { limit: 100 }),
730
+ unread: await v.unreadCount(t)
731
+ })) : f.succeed(O(400, "subjectId query param required"));
732
+ }
733
+ }];
734
+ return ie({
700
735
  name: h,
736
+ baseName: $,
701
737
  description: "Unified notifications — email/slack/sms/push/in-app with preference routing, inbox + delivery log.",
702
738
  permissions: ["inspect:read", "store:write"],
703
- extendSchema: { tables: [
739
+ ...d.tables === !1 ? {} : { extendSchema: { tables: [
740
+ G,
704
741
  K,
705
742
  q,
706
743
  J,
707
744
  Y,
708
- X,
709
- Z
710
- ] },
711
- services: p.succeed(Q, S),
745
+ X
746
+ ] } },
747
+ services: p.succeed(Z, S),
712
748
  routes: E,
713
749
  rpcClientDescriptors: o,
714
- inspectEndpoints: [{
715
- method: "GET",
716
- path: "/deliveries",
717
- description: "Recent delivery records (newest first) + per-channel + per-status counts.",
718
- handler: () => f.promise(async () => {
719
- let e = await v.listDeliveries({ limit: 200 }), t = {}, n = 0, r = 0;
720
- for (let i of e) t[i.channel] = (t[i.channel] ?? 0) + 1, i.status === "sent" ? n++ : r++;
721
- return D({
722
- deliveries: e.slice(0, 100),
723
- channels: b.map((e) => e.id),
724
- stats: {
725
- sent: n,
726
- failed: r,
727
- byChannel: t
728
- }
729
- });
730
- })
731
- }, {
732
- method: "GET",
733
- path: "/inbox",
734
- description: "In-app inbox for a subject (?subjectId=…).",
735
- handler: (e) => {
736
- let t = A(e.url, "subjectId");
737
- return t ? f.promise(async () => D({
738
- items: await v.listInbox(t, { limit: 100 }),
739
- unread: await v.unreadCount(t)
740
- })) : f.succeed(O(400, "subjectId query param required"));
741
- }
742
- }],
750
+ inspectEndpoints: A,
743
751
  bindDataStore: (e) => {
744
- g && (_.current = z(e));
752
+ g && (_.current = R(e));
745
753
  let t = d.flushIntervalMs ?? 3e4;
746
754
  C = setInterval(() => {
747
755
  S.flushDue().catch(() => {});
@@ -757,4 +765,4 @@ var j = class extends m.TaggedError()("PushTokenRejected", {
757
765
  });
758
766
  };
759
767
  //#endregion
760
- 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 };
768
+ export { Z as NotificationService, A as PushTokenRejected, U as buildNotificationService, j as consoleChannel, P as customChannel, R as dataStoreNotificationStore, q as deliveriesTable, M as emailChannel, X as heldTable, z as inAppChannel, V as inQuietHours, G as inboxTable, Q as makeSubjectId, I as memoryNotificationStore, B as minuteOfDayInZone, le as notificationsPlugin, K as preferencesTable, se as pushChannel, H as quietHoursEnd, Y as quietHoursTable, F as resolveChannels, N as smsChannel, J as topicSubscriptionsTable, oe as webhookChannel };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/plugin-notifications",
3
- "version": "0.33.0",
3
+ "version": "0.34.0",
4
4
  "description": "One unified notification surface — email, Slack/webhook, SMS, push, in-app — with per-subject preference routing, an in-app inbox, and a delivery log. NotificationService + typed routes + useInbox/useUnreadCount hooks.",
5
5
  "keywords": [
6
6
  "voltro",
@@ -43,9 +43,9 @@
43
43
  "node": ">=24.0.0"
44
44
  },
45
45
  "dependencies": {
46
- "@voltro/client": "0.33.0",
47
- "@voltro/database": "0.33.0",
48
- "@voltro/protocol": "0.33.0"
46
+ "@voltro/client": "0.34.0",
47
+ "@voltro/database": "0.34.0",
48
+ "@voltro/protocol": "0.34.0"
49
49
  },
50
50
  "peerDependencies": {
51
51
  "effect": "^3.22.0"