@voltro/plugin-notifications 0.26.0 → 0.28.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/CHANGELOG.md +532 -0
- package/dist/index.d.ts +71 -0
- package/dist/index.js +146 -140
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
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
|
|
2
|
-
import { Context as
|
|
3
|
-
import { and as
|
|
4
|
-
import { definePlugin as
|
|
5
|
-
import { randomUUID as
|
|
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
|
|
8
|
-
token:
|
|
9
|
-
reason:
|
|
10
|
-
}) {},
|
|
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
|
-
}),
|
|
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 M = class extends h.TaggedError()("PushTokenRejected", {
|
|
|
25
25
|
});
|
|
26
26
|
if (!r.ok) throw Error(`webhook ${e.id ?? "slack"} POST ${r.status}`);
|
|
27
27
|
}
|
|
28
|
-
}),
|
|
28
|
+
}), ae = (e) => ({
|
|
29
29
|
id: "email",
|
|
30
30
|
deliver: (t) => e({
|
|
31
31
|
to: t.to,
|
|
@@ -33,13 +33,13 @@ var M = class extends h.TaggedError()("PushTokenRejected", {
|
|
|
33
33
|
html: `<p>${t.body}</p>`,
|
|
34
34
|
text: t.body
|
|
35
35
|
})
|
|
36
|
-
}),
|
|
36
|
+
}), N = (e) => ({
|
|
37
37
|
id: "sms",
|
|
38
38
|
deliver: (t) => e(t.to, `${t.title}: ${t.body}`)
|
|
39
|
-
}),
|
|
39
|
+
}), P = (e, t) => ({
|
|
40
40
|
id: e,
|
|
41
41
|
deliver: t
|
|
42
|
-
}),
|
|
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,17 +54,17 @@ var M = class extends h.TaggedError()("PushTokenRejected", {
|
|
|
54
54
|
try {
|
|
55
55
|
await e.transport(n);
|
|
56
56
|
} catch (e) {
|
|
57
|
-
throw e instanceof
|
|
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
|
-
}),
|
|
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
|
-
},
|
|
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) => {
|
|
@@ -145,7 +145,7 @@ var M = class extends h.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
|
-
},
|
|
148
|
+
}, R = (e) => e instanceof Date ? e.toISOString() : typeof e == "string" ? e : "", z = (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 M = class extends h.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 :
|
|
164
|
-
archivedAt: e.archivedAt == null ? null :
|
|
165
|
-
createdAt:
|
|
163
|
+
readAt: e.readAt == null ? null : R(e.readAt),
|
|
164
|
+
archivedAt: e.archivedAt == null ? null : R(e.archivedAt),
|
|
165
|
+
createdAt: R(e.createdAt),
|
|
166
166
|
tenantId: e.tenantId ?? null
|
|
167
167
|
}), r = (e) => ({
|
|
168
168
|
subjectId: String(e.subjectId),
|
|
@@ -176,7 +176,7 @@ var M = class extends h.TaggedError()("PushTokenRejected", {
|
|
|
176
176
|
category: String(e.category),
|
|
177
177
|
channel: String(e.channel),
|
|
178
178
|
status: e.status,
|
|
179
|
-
at:
|
|
179
|
+
at: R(e.at)
|
|
180
180
|
};
|
|
181
181
|
return e.error == null ? t : {
|
|
182
182
|
...t,
|
|
@@ -197,7 +197,7 @@ var M = class extends h.TaggedError()("PushTokenRejected", {
|
|
|
197
197
|
kind: e.kind === "quiet" ? "quiet" : "digest",
|
|
198
198
|
subjectId: String(e.subjectId),
|
|
199
199
|
send: e.send ?? {},
|
|
200
|
-
flushAt:
|
|
200
|
+
flushAt: R(e.flushAt)
|
|
201
201
|
});
|
|
202
202
|
return {
|
|
203
203
|
addInbox: async (t) => {
|
|
@@ -215,24 +215,24 @@ var M = class extends h.TaggedError()("PushTokenRejected", {
|
|
|
215
215
|
});
|
|
216
216
|
},
|
|
217
217
|
listInbox: async (e, r) => {
|
|
218
|
-
let i = r?.unreadOnly ?
|
|
218
|
+
let i = r?.unreadOnly ? h(_("subjectId", e), x("readAt")) : _("subjectId", e);
|
|
219
219
|
return (await t("_voltro_notification_inbox", i, [{
|
|
220
220
|
column: "createdAt",
|
|
221
221
|
direction: "desc"
|
|
222
222
|
}], r?.limit)).map(n);
|
|
223
223
|
},
|
|
224
|
-
markRead: async (n, r) => (await t("_voltro_notification_inbox",
|
|
225
|
-
markUnread: async (n, r) => (await t("_voltro_notification_inbox",
|
|
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
226
|
markAllRead: async (n) => {
|
|
227
|
-
let r = await t("_voltro_notification_inbox",
|
|
227
|
+
let r = await t("_voltro_notification_inbox", h(_("subjectId", n), x("readAt")), [], void 0), i = (/* @__PURE__ */ new Date()).toISOString();
|
|
228
228
|
for (let t of r) await e.update("_voltro_notification_inbox", String(t.id), { readAt: i });
|
|
229
229
|
return r.length;
|
|
230
230
|
},
|
|
231
|
-
setArchived: async (n, r, i) => (await t("_voltro_notification_inbox",
|
|
232
|
-
unreadCount: async (e) => (await t("_voltro_notification_inbox",
|
|
233
|
-
getPreferences: async (e) => (await t("_voltro_notification_preferences",
|
|
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),
|
|
234
234
|
setPreference: async (t) => {
|
|
235
|
-
let n =
|
|
235
|
+
let n = v({
|
|
236
236
|
kind: "typeid",
|
|
237
237
|
prefix: "notifpref"
|
|
238
238
|
}, "_voltro_notification_preferences");
|
|
@@ -267,7 +267,7 @@ var M = class extends h.TaggedError()("PushTokenRejected", {
|
|
|
267
267
|
direction: "desc"
|
|
268
268
|
}], e?.limit)).map(i),
|
|
269
269
|
subscribeTopic: async (t) => {
|
|
270
|
-
let n =
|
|
270
|
+
let n = v({
|
|
271
271
|
kind: "typeid",
|
|
272
272
|
prefix: "notiftsub"
|
|
273
273
|
}, "_voltro_notification_topic_subscriptions");
|
|
@@ -282,16 +282,16 @@ var M = class extends h.TaggedError()("PushTokenRejected", {
|
|
|
282
282
|
});
|
|
283
283
|
},
|
|
284
284
|
unsubscribeTopic: async (n, r) => {
|
|
285
|
-
let i = await t("_voltro_notification_topic_subscriptions",
|
|
285
|
+
let i = await t("_voltro_notification_topic_subscriptions", h(_("topic", n), _("subjectId", r)), [], 1);
|
|
286
286
|
i[0] && await e.delete("_voltro_notification_topic_subscriptions", String(i[0].id));
|
|
287
287
|
},
|
|
288
|
-
listTopicSubscribers: async (e) => (await t("_voltro_notification_topic_subscriptions",
|
|
288
|
+
listTopicSubscribers: async (e) => (await t("_voltro_notification_topic_subscriptions", _("topic", e), [], void 0)).map(a),
|
|
289
289
|
getQuietHours: async (e) => {
|
|
290
|
-
let n = await t("_voltro_notification_quiet_hours",
|
|
290
|
+
let n = await t("_voltro_notification_quiet_hours", _("subjectId", e), [], 1);
|
|
291
291
|
return n[0] ? o(n[0]) : null;
|
|
292
292
|
},
|
|
293
293
|
setQuietHours: async (t) => {
|
|
294
|
-
let n =
|
|
294
|
+
let n = v({
|
|
295
295
|
kind: "typeid",
|
|
296
296
|
prefix: "notifqh"
|
|
297
297
|
}, "_voltro_notification_quiet_hours");
|
|
@@ -313,7 +313,7 @@ var M = class extends h.TaggedError()("PushTokenRejected", {
|
|
|
313
313
|
});
|
|
314
314
|
},
|
|
315
315
|
clearQuietHours: async (n) => {
|
|
316
|
-
let r = await t("_voltro_notification_quiet_hours",
|
|
316
|
+
let r = await t("_voltro_notification_quiet_hours", _("subjectId", n), [], 1);
|
|
317
317
|
r[0] && await e.delete("_voltro_notification_quiet_hours", String(r[0].id));
|
|
318
318
|
},
|
|
319
319
|
enqueueHeld: async (t) => {
|
|
@@ -325,11 +325,11 @@ var M = class extends h.TaggedError()("PushTokenRejected", {
|
|
|
325
325
|
flushAt: t.flushAt
|
|
326
326
|
});
|
|
327
327
|
},
|
|
328
|
-
pendingDigest: async (e) => (await t("_voltro_notification_held",
|
|
328
|
+
pendingDigest: async (e) => (await t("_voltro_notification_held", h(_("kind", "digest"), _("subjectId", e)), [{
|
|
329
329
|
column: "flushAt",
|
|
330
330
|
direction: "asc"
|
|
331
331
|
}], void 0)).map(s),
|
|
332
|
-
dueHeld: async (e) => (await t("_voltro_notification_held",
|
|
332
|
+
dueHeld: async (e) => (await t("_voltro_notification_held", C("flushAt", e), [{
|
|
333
333
|
column: "flushAt",
|
|
334
334
|
direction: "asc"
|
|
335
335
|
}], void 0)).map(s),
|
|
@@ -337,11 +337,11 @@ var M = class extends h.TaggedError()("PushTokenRejected", {
|
|
|
337
337
|
for (let n of t) await e.delete("_voltro_notification_held", n);
|
|
338
338
|
}
|
|
339
339
|
};
|
|
340
|
-
},
|
|
340
|
+
}, B = (e) => ({
|
|
341
341
|
id: "inApp",
|
|
342
342
|
deliver: async (t) => {
|
|
343
343
|
await e.addInbox({
|
|
344
|
-
id:
|
|
344
|
+
id: A(),
|
|
345
345
|
subjectId: t.to,
|
|
346
346
|
category: t.category,
|
|
347
347
|
title: t.title,
|
|
@@ -351,7 +351,7 @@ var M = class extends h.TaggedError()("PushTokenRejected", {
|
|
|
351
351
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
352
352
|
});
|
|
353
353
|
}
|
|
354
|
-
}),
|
|
354
|
+
}), V = (e, t) => {
|
|
355
355
|
try {
|
|
356
356
|
let n = new Intl.DateTimeFormat("en-US", {
|
|
357
357
|
timeZone: t,
|
|
@@ -363,16 +363,16 @@ var M = class extends h.TaggedError()("PushTokenRejected", {
|
|
|
363
363
|
} catch {
|
|
364
364
|
return e.getUTCHours() * 60 + e.getUTCMinutes();
|
|
365
365
|
}
|
|
366
|
-
},
|
|
366
|
+
}, H = (e, t) => {
|
|
367
367
|
if (e.startMinute === e.endMinute) return !1;
|
|
368
|
-
let n =
|
|
368
|
+
let n = V(t, e.tz ?? "UTC");
|
|
369
369
|
return e.startMinute < e.endMinute ? n >= e.startMinute && n < e.endMinute : n >= e.startMinute || n < e.endMinute;
|
|
370
|
-
},
|
|
371
|
-
let n =
|
|
370
|
+
}, U = (e, t) => {
|
|
371
|
+
let n = V(t, e.tz ?? "UTC"), r = e.endMinute - n;
|
|
372
372
|
return r <= 0 && (r += 1440), new Date(t.getTime() + r * 6e4);
|
|
373
|
-
},
|
|
373
|
+
}, W = (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 =
|
|
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 = {
|
|
376
376
|
to: i.to,
|
|
377
377
|
category: i.category,
|
|
378
378
|
title: i.title,
|
|
@@ -384,7 +384,7 @@ var M = class extends h.TaggedError()("PushTokenRejected", {
|
|
|
384
384
|
let a = t.get(n);
|
|
385
385
|
if (!a) continue;
|
|
386
386
|
let o = {
|
|
387
|
-
id:
|
|
387
|
+
id: A(),
|
|
388
388
|
to: i.to,
|
|
389
389
|
category: i.category,
|
|
390
390
|
channel: n,
|
|
@@ -414,17 +414,17 @@ var M = class extends h.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 &&
|
|
417
|
+
if (n && H(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:
|
|
423
|
+
id: A(),
|
|
424
424
|
kind: "quiet",
|
|
425
425
|
subjectId: t.to,
|
|
426
426
|
send: t,
|
|
427
|
-
flushAt:
|
|
427
|
+
flushAt: U(n, o).toISOString()
|
|
428
428
|
}), {
|
|
429
429
|
delivered: [],
|
|
430
430
|
failed: [],
|
|
@@ -434,7 +434,7 @@ var M = class extends h.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:
|
|
437
|
+
id: A(),
|
|
438
438
|
kind: "digest",
|
|
439
439
|
subjectId: t.to,
|
|
440
440
|
send: t,
|
|
@@ -488,11 +488,11 @@ var M = class extends h.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(
|
|
491
|
+
for (let [e, t] of i) await a(G(e, t));
|
|
492
492
|
return await e.store.clearHeld(n.map((e) => e.id)), n.length;
|
|
493
493
|
}
|
|
494
494
|
};
|
|
495
|
-
},
|
|
495
|
+
}, G = (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,70 +507,76 @@ var M = class extends h.TaggedError()("PushTokenRejected", {
|
|
|
507
507
|
data: { items: n },
|
|
508
508
|
tenantId: t[0].send.tenantId ?? null
|
|
509
509
|
};
|
|
510
|
-
}, K =
|
|
511
|
-
id:
|
|
512
|
-
subjectId:
|
|
513
|
-
category:
|
|
514
|
-
title:
|
|
515
|
-
body:
|
|
516
|
-
data:
|
|
517
|
-
readAt:
|
|
518
|
-
archivedAt:
|
|
519
|
-
createdAt:
|
|
520
|
-
tenantId:
|
|
521
|
-
}).renamedFrom("notification_inbox").index("byInboxSubject", ["subjectId"]), q =
|
|
522
|
-
id:
|
|
523
|
-
subjectId:
|
|
524
|
-
category:
|
|
525
|
-
channel:
|
|
526
|
-
enabled:
|
|
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)
|
|
527
527
|
}).renamedFrom("notification_preferences").unique("byPreferencesSubjectCategoryChannel", [
|
|
528
528
|
"subjectId",
|
|
529
529
|
"category",
|
|
530
530
|
"channel"
|
|
531
|
-
]), J =
|
|
532
|
-
id:
|
|
533
|
-
recipient:
|
|
534
|
-
category:
|
|
535
|
-
channel:
|
|
536
|
-
status:
|
|
537
|
-
error:
|
|
538
|
-
at:
|
|
539
|
-
}).renamedFrom("notification_deliveries").index("byDeliveriesAt", ["at"]), Y =
|
|
540
|
-
id:
|
|
541
|
-
topic:
|
|
542
|
-
subjectId:
|
|
543
|
-
tenantId:
|
|
544
|
-
}).renamedFrom("notification_topic_subscriptions").unique("byTopicSubject", ["topic", "subjectId"]).index("byTopic", ["topic"]), X =
|
|
545
|
-
id:
|
|
546
|
-
subjectId:
|
|
547
|
-
startMinute:
|
|
548
|
-
endMinute:
|
|
549
|
-
tz:
|
|
550
|
-
policy:
|
|
551
|
-
}).renamedFrom("notification_quiet_hours").unique("byQuietHoursSubject", ["subjectId"]), Z =
|
|
552
|
-
id:
|
|
553
|
-
kind:
|
|
554
|
-
subjectId:
|
|
555
|
-
send:
|
|
556
|
-
flushAt:
|
|
557
|
-
}).renamedFrom("notification_held").index("byHeldFlushAt", ["flushAt"]).index("byHeldSubjectKind", ["subjectId", "kind"]), Q = class extends
|
|
558
|
-
|
|
559
|
-
|
|
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) => async (t) => {
|
|
558
|
+
if (e !== void 0) try {
|
|
559
|
+
let n = await 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({
|
|
560
566
|
table: "_voltro_notification_deliveries",
|
|
561
567
|
timeColumn: "at",
|
|
562
|
-
ttlMs:
|
|
563
|
-
}),
|
|
568
|
+
ttlMs: T(process.env.VOLTRO_NOTIFICATION_DELIVERIES_TTL_HOURS, 2160)
|
|
569
|
+
}), w({
|
|
564
570
|
table: "_voltro_notification_inbox",
|
|
565
571
|
timeColumn: "createdAt",
|
|
566
|
-
ttlMs:
|
|
567
|
-
where:
|
|
568
|
-
}),
|
|
572
|
+
ttlMs: T(process.env.VOLTRO_NOTIFICATION_INBOX_READ_TTL_HOURS, 4320),
|
|
573
|
+
where: re("readAt")
|
|
574
|
+
}), w({
|
|
569
575
|
table: "_voltro_notification_held",
|
|
570
576
|
timeColumn: "flushAt",
|
|
571
|
-
ttlMs:
|
|
577
|
+
ttlMs: T(process.env.VOLTRO_NOTIFICATION_HELD_TTL_HOURS, 168)
|
|
572
578
|
});
|
|
573
|
-
let g =
|
|
579
|
+
let g = d.store === void 0, _ = { current: d.store ?? L() }, v = {
|
|
574
580
|
addInbox: (e) => _.current.addInbox(e),
|
|
575
581
|
listInbox: (e, t) => _.current.listInbox(e, t),
|
|
576
582
|
markRead: (e, t) => _.current.markRead(e, t),
|
|
@@ -592,58 +598,58 @@ var M = class extends h.TaggedError()("PushTokenRejected", {
|
|
|
592
598
|
pendingDigest: (e) => _.current.pendingDigest(e),
|
|
593
599
|
dueHeld: (e) => _.current.dueHeld(e),
|
|
594
600
|
clearHeld: (e) => _.current.clearHeld(e)
|
|
595
|
-
}, y =
|
|
601
|
+
}, y = d.channels ?? [M()], b = y.some((e) => e.id === "inApp") ? y : [...y, B(v)], x = d.digestWindowMs ?? 0, S = W({
|
|
596
602
|
channels: b,
|
|
597
603
|
store: v,
|
|
598
604
|
digestWindowMs: x
|
|
599
|
-
}), C,
|
|
605
|
+
}), C, E = [
|
|
600
606
|
{
|
|
601
607
|
...n,
|
|
602
608
|
description: "The caller's in-app inbox items.",
|
|
603
|
-
execute: (e, t) =>
|
|
609
|
+
execute: (e, t) => f.promise(async () => S.inbox(await m(t), e))
|
|
604
610
|
},
|
|
605
611
|
{
|
|
606
|
-
...
|
|
612
|
+
...te,
|
|
607
613
|
description: "The caller's unread count.",
|
|
608
|
-
execute: (e, t) =>
|
|
614
|
+
execute: (e, t) => f.promise(async () => ({ count: await S.unreadCount(await m(t)) }))
|
|
609
615
|
},
|
|
610
616
|
{
|
|
611
617
|
...i,
|
|
612
618
|
description: "Mark an inbox item read.",
|
|
613
|
-
execute: (e, t) =>
|
|
619
|
+
execute: (e, t) => f.promise(async () => ({ ok: await S.markRead(e.id, await m(t)) }))
|
|
614
620
|
},
|
|
615
621
|
{
|
|
616
622
|
...a,
|
|
617
623
|
description: "Mark an inbox item unread again.",
|
|
618
|
-
execute: (e, t) =>
|
|
624
|
+
execute: (e, t) => f.promise(async () => ({ ok: await S.markUnread(e.id, await m(t)) }))
|
|
619
625
|
},
|
|
620
626
|
{
|
|
621
627
|
...r,
|
|
622
628
|
description: "Mark every unread item in the caller's inbox read.",
|
|
623
|
-
execute: (e, t) =>
|
|
629
|
+
execute: (e, t) => f.promise(async () => ({ count: await S.markAllRead(await m(t)) }))
|
|
624
630
|
},
|
|
625
631
|
{
|
|
626
632
|
...e,
|
|
627
633
|
description: "Archive an inbox item — a different state from read; this is what empties the inbox.",
|
|
628
|
-
execute: (e, t) =>
|
|
634
|
+
execute: (e, t) => f.promise(async () => ({ ok: await S.setArchived(e.id, await m(t), !0) }))
|
|
629
635
|
},
|
|
630
636
|
{
|
|
631
637
|
...ee,
|
|
632
638
|
description: "Return an archived item to the inbox.",
|
|
633
|
-
execute: (e, t) =>
|
|
639
|
+
execute: (e, t) => f.promise(async () => ({ ok: await S.setArchived(e.id, await m(t), !1) }))
|
|
634
640
|
},
|
|
635
641
|
{
|
|
636
642
|
...s,
|
|
637
643
|
description: "The caller's channel preferences.",
|
|
638
|
-
execute: (e, t) =>
|
|
644
|
+
execute: (e, t) => f.promise(async () => S.getPreferences(await m(t)))
|
|
639
645
|
},
|
|
640
646
|
{
|
|
641
647
|
...c,
|
|
642
648
|
description: "Toggle a channel on/off for a category.",
|
|
643
|
-
execute: (e, t) =>
|
|
649
|
+
execute: (e, t) => f.promise(async () => {
|
|
644
650
|
let { category: n, channel: r, enabled: i } = e;
|
|
645
651
|
return await S.setPreference({
|
|
646
|
-
subjectId:
|
|
652
|
+
subjectId: await m(t),
|
|
647
653
|
category: n,
|
|
648
654
|
channel: r,
|
|
649
655
|
enabled: i
|
|
@@ -653,20 +659,20 @@ var M = class extends h.TaggedError()("PushTokenRejected", {
|
|
|
653
659
|
{
|
|
654
660
|
...u,
|
|
655
661
|
description: "Subscribe the caller to a broadcast topic.",
|
|
656
|
-
execute: (e, t) =>
|
|
662
|
+
execute: (e, t) => f.promise(async () => (await S.subscribe(e.topic, await m(t), oe(t)), { ok: !0 }))
|
|
657
663
|
},
|
|
658
664
|
{
|
|
659
|
-
...
|
|
665
|
+
...ne,
|
|
660
666
|
description: "Unsubscribe the caller from a broadcast topic.",
|
|
661
|
-
execute: (e, t) =>
|
|
667
|
+
execute: (e, t) => f.promise(async () => (await S.unsubscribe(e.topic, await m(t)), { ok: !0 }))
|
|
662
668
|
},
|
|
663
669
|
{
|
|
664
670
|
...l,
|
|
665
671
|
description: "Set the caller's quiet-hours (DND) window.",
|
|
666
|
-
execute: (e, t) =>
|
|
672
|
+
execute: (e, t) => f.promise(async () => {
|
|
667
673
|
let n = e;
|
|
668
674
|
return await S.setQuietHours({
|
|
669
|
-
subjectId:
|
|
675
|
+
subjectId: await m(t),
|
|
670
676
|
startMinute: n.startMinute,
|
|
671
677
|
endMinute: n.endMinute,
|
|
672
678
|
...n.tz ? { tz: n.tz } : {},
|
|
@@ -677,7 +683,7 @@ var M = class extends h.TaggedError()("PushTokenRejected", {
|
|
|
677
683
|
{
|
|
678
684
|
...t,
|
|
679
685
|
description: "Clear the caller's quiet-hours window.",
|
|
680
|
-
execute: (e, t) =>
|
|
686
|
+
execute: (e, t) => f.promise(async () => (await S.clearQuietHours(await m(t)), { ok: !0 }))
|
|
681
687
|
}
|
|
682
688
|
], D = (e) => ({
|
|
683
689
|
kind: "json",
|
|
@@ -686,8 +692,8 @@ var M = class extends h.TaggedError()("PushTokenRejected", {
|
|
|
686
692
|
kind: "json",
|
|
687
693
|
status: e,
|
|
688
694
|
data: { error: t }
|
|
689
|
-
}),
|
|
690
|
-
return
|
|
695
|
+
}), A = (e, t) => new URLSearchParams(new URL(e, "http://x").search).get(t);
|
|
696
|
+
return k({
|
|
691
697
|
name: h,
|
|
692
698
|
description: "Unified notifications — email/slack/sms/push/in-app with preference routing, inbox + delivery log.",
|
|
693
699
|
permissions: ["inspect:read", "store:write"],
|
|
@@ -699,14 +705,14 @@ var M = class extends h.TaggedError()("PushTokenRejected", {
|
|
|
699
705
|
X,
|
|
700
706
|
Z
|
|
701
707
|
] },
|
|
702
|
-
services:
|
|
703
|
-
routes:
|
|
708
|
+
services: p.succeed(Q, S),
|
|
709
|
+
routes: E,
|
|
704
710
|
rpcClientDescriptors: o,
|
|
705
711
|
inspectEndpoints: [{
|
|
706
712
|
method: "GET",
|
|
707
713
|
path: "/deliveries",
|
|
708
714
|
description: "Recent delivery records (newest first) + per-channel + per-status counts.",
|
|
709
|
-
handler: () =>
|
|
715
|
+
handler: () => f.promise(async () => {
|
|
710
716
|
let e = await v.listDeliveries({ limit: 200 }), t = {}, n = 0, r = 0;
|
|
711
717
|
for (let i of e) t[i.channel] = (t[i.channel] ?? 0) + 1, i.status === "sent" ? n++ : r++;
|
|
712
718
|
return D({
|
|
@@ -724,28 +730,28 @@ var M = class extends h.TaggedError()("PushTokenRejected", {
|
|
|
724
730
|
path: "/inbox",
|
|
725
731
|
description: "In-app inbox for a subject (?subjectId=…).",
|
|
726
732
|
handler: (e) => {
|
|
727
|
-
let t =
|
|
728
|
-
return t ?
|
|
733
|
+
let t = A(e.url, "subjectId");
|
|
734
|
+
return t ? f.promise(async () => D({
|
|
729
735
|
items: await v.listInbox(t, { limit: 100 }),
|
|
730
736
|
unread: await v.unreadCount(t)
|
|
731
|
-
})) :
|
|
737
|
+
})) : f.succeed(O(400, "subjectId query param required"));
|
|
732
738
|
}
|
|
733
739
|
}],
|
|
734
740
|
bindDataStore: (e) => {
|
|
735
|
-
g && (_.current =
|
|
736
|
-
let t =
|
|
741
|
+
g && (_.current = z(e));
|
|
742
|
+
let t = d.flushIntervalMs ?? 3e4;
|
|
737
743
|
C = setInterval(() => {
|
|
738
744
|
S.flushDue().catch(() => {});
|
|
739
745
|
}, t), typeof C.unref == "function" && C.unref();
|
|
740
746
|
},
|
|
741
|
-
onActivate: (e) =>
|
|
747
|
+
onActivate: (e) => f.sync(() => e.logger.info("notifications active", {
|
|
742
748
|
channels: b.map((e) => e.id),
|
|
743
749
|
digestWindowMs: x
|
|
744
750
|
})),
|
|
745
|
-
onDeactivate: () =>
|
|
751
|
+
onDeactivate: () => f.sync(() => {
|
|
746
752
|
C && clearInterval(C), C = void 0;
|
|
747
753
|
})
|
|
748
754
|
});
|
|
749
755
|
};
|
|
750
756
|
//#endregion
|
|
751
|
-
export { Q as NotificationService,
|
|
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 };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/plugin-notifications",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.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",
|
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
"node": ">=24.0.0"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@voltro/client": "0.
|
|
46
|
-
"@voltro/database": "0.
|
|
47
|
-
"@voltro/protocol": "0.
|
|
45
|
+
"@voltro/client": "0.28.0",
|
|
46
|
+
"@voltro/database": "0.28.0",
|
|
47
|
+
"@voltro/protocol": "0.28.0"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"effect": "^3.22.0"
|