@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/CHANGELOG.md +541 -0
- package/dist/index.d.ts +78 -1
- package/dist/index.js +245 -177
- package/dist/rpc.d.ts +22 -0
- package/dist/rpc.js +24 -8
- package/dist/web.d.ts +2 -0
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import {
|
|
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 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
|
-
}),
|
|
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
|
-
}),
|
|
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,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
|
|
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) => {
|
|
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
|
-
},
|
|
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 :
|
|
138
|
-
|
|
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:
|
|
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:
|
|
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 ?
|
|
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",
|
|
197
|
-
|
|
198
|
-
|
|
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",
|
|
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",
|
|
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",
|
|
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",
|
|
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",
|
|
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",
|
|
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
|
-
},
|
|
340
|
+
}, B = (e) => ({
|
|
306
341
|
id: "inApp",
|
|
307
342
|
deliver: async (t) => {
|
|
308
343
|
await e.addInbox({
|
|
309
|
-
id:
|
|
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
|
-
}),
|
|
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
|
-
},
|
|
366
|
+
}, H = (e, t) => {
|
|
332
367
|
if (e.startMinute === e.endMinute) return !1;
|
|
333
|
-
let n =
|
|
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
|
-
},
|
|
336
|
-
let 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
|
-
},
|
|
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 =
|
|
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:
|
|
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 &&
|
|
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:
|
|
423
|
+
id: A(),
|
|
389
424
|
kind: "quiet",
|
|
390
425
|
subjectId: t.to,
|
|
391
426
|
send: t,
|
|
392
|
-
flushAt:
|
|
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:
|
|
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(
|
|
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
|
-
},
|
|
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 =
|
|
473
|
-
id:
|
|
474
|
-
subjectId:
|
|
475
|
-
category:
|
|
476
|
-
title:
|
|
477
|
-
body:
|
|
478
|
-
data:
|
|
479
|
-
readAt:
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
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 =
|
|
493
|
-
id:
|
|
494
|
-
recipient:
|
|
495
|
-
category:
|
|
496
|
-
channel:
|
|
497
|
-
status:
|
|
498
|
-
error:
|
|
499
|
-
at:
|
|
500
|
-
}).renamedFrom("notification_deliveries").index("byDeliveriesAt", ["at"]), Y =
|
|
501
|
-
id:
|
|
502
|
-
topic:
|
|
503
|
-
subjectId:
|
|
504
|
-
tenantId:
|
|
505
|
-
}).renamedFrom("notification_topic_subscriptions").unique("byTopicSubject", ["topic", "subjectId"]).index("byTopic", ["topic"]), X =
|
|
506
|
-
id:
|
|
507
|
-
subjectId:
|
|
508
|
-
startMinute:
|
|
509
|
-
endMinute:
|
|
510
|
-
tz:
|
|
511
|
-
policy:
|
|
512
|
-
}).renamedFrom("notification_quiet_hours").unique("byQuietHoursSubject", ["subjectId"]), Z =
|
|
513
|
-
id:
|
|
514
|
-
kind:
|
|
515
|
-
subjectId:
|
|
516
|
-
send:
|
|
517
|
-
flushAt:
|
|
518
|
-
}).renamedFrom("notification_held").index("byHeldFlushAt", ["flushAt"]).index("byHeldSubjectKind", ["subjectId", "kind"]), Q = class extends
|
|
519
|
-
|
|
520
|
-
|
|
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:
|
|
524
|
-
}),
|
|
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:
|
|
528
|
-
where:
|
|
529
|
-
}),
|
|
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:
|
|
577
|
+
ttlMs: T(process.env.VOLTRO_NOTIFICATION_HELD_TTL_HOURS, 168)
|
|
533
578
|
});
|
|
534
|
-
let
|
|
535
|
-
addInbox: (e) =>
|
|
536
|
-
listInbox: (e, t) =>
|
|
537
|
-
markRead: (e, t) =>
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
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
|
-
...
|
|
607
|
+
...n,
|
|
560
608
|
description: "The caller's in-app inbox items.",
|
|
561
|
-
execute: (e, t) =>
|
|
609
|
+
execute: (e, t) => f.promise(() => S.inbox(m(t), e))
|
|
562
610
|
},
|
|
563
611
|
{
|
|
564
|
-
...
|
|
612
|
+
...te,
|
|
565
613
|
description: "The caller's unread count.",
|
|
566
|
-
execute: (e, t) =>
|
|
614
|
+
execute: (e, t) => f.promise(async () => ({ count: await S.unreadCount(m(t)) }))
|
|
567
615
|
},
|
|
568
616
|
{
|
|
569
|
-
...
|
|
617
|
+
...i,
|
|
570
618
|
description: "Mark an inbox item read.",
|
|
571
|
-
execute: (e, t) =>
|
|
619
|
+
execute: (e, t) => f.promise(async () => ({ ok: await S.markRead(e.id, m(t)) }))
|
|
572
620
|
},
|
|
573
621
|
{
|
|
574
|
-
...
|
|
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) =>
|
|
644
|
+
execute: (e, t) => f.promise(() => S.getPreferences(m(t)))
|
|
577
645
|
},
|
|
578
646
|
{
|
|
579
|
-
...
|
|
647
|
+
...c,
|
|
580
648
|
description: "Toggle a channel on/off for a category.",
|
|
581
|
-
execute: (e, t) =>
|
|
649
|
+
execute: (e, t) => f.promise(async () => {
|
|
582
650
|
let { category: n, channel: r, enabled: i } = e;
|
|
583
|
-
return await
|
|
584
|
-
subjectId:
|
|
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
|
-
...
|
|
660
|
+
...u,
|
|
593
661
|
description: "Subscribe the caller to a broadcast topic.",
|
|
594
|
-
execute: (e, t) =>
|
|
662
|
+
execute: (e, t) => f.promise(async () => (await S.subscribe(e.topic, m(t), oe(t)), { ok: !0 }))
|
|
595
663
|
},
|
|
596
664
|
{
|
|
597
|
-
...
|
|
665
|
+
...ne,
|
|
598
666
|
description: "Unsubscribe the caller from a broadcast topic.",
|
|
599
|
-
execute: (e, t) =>
|
|
667
|
+
execute: (e, t) => f.promise(async () => (await S.unsubscribe(e.topic, m(t)), { ok: !0 }))
|
|
600
668
|
},
|
|
601
669
|
{
|
|
602
|
-
...
|
|
670
|
+
...l,
|
|
603
671
|
description: "Set the caller's quiet-hours (DND) window.",
|
|
604
|
-
execute: (e, t) =>
|
|
672
|
+
execute: (e, t) => f.promise(async () => {
|
|
605
673
|
let n = e;
|
|
606
|
-
return await
|
|
607
|
-
subjectId:
|
|
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
|
-
...
|
|
684
|
+
...t,
|
|
617
685
|
description: "Clear the caller's quiet-hours window.",
|
|
618
|
-
execute: (e, t) =>
|
|
686
|
+
execute: (e, t) => f.promise(async () => (await S.clearQuietHours(m(t)), { ok: !0 }))
|
|
619
687
|
}
|
|
620
|
-
],
|
|
688
|
+
], D = (e) => ({
|
|
621
689
|
kind: "json",
|
|
622
690
|
data: e
|
|
623
|
-
}),
|
|
691
|
+
}), O = (e, t) => ({
|
|
624
692
|
kind: "json",
|
|
625
693
|
status: e,
|
|
626
694
|
data: { error: t }
|
|
627
|
-
}),
|
|
628
|
-
return
|
|
629
|
-
name:
|
|
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:
|
|
641
|
-
routes:
|
|
642
|
-
rpcClientDescriptors:
|
|
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: () =>
|
|
648
|
-
let e = await
|
|
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
|
|
718
|
+
return D({
|
|
651
719
|
deliveries: e.slice(0, 100),
|
|
652
|
-
channels:
|
|
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 =
|
|
666
|
-
return t ?
|
|
667
|
-
items: await
|
|
668
|
-
unread: await
|
|
669
|
-
})) :
|
|
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
|
-
|
|
674
|
-
let t =
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
}, t), typeof
|
|
678
|
-
},
|
|
679
|
-
onActivate: (e) =>
|
|
680
|
-
channels:
|
|
681
|
-
digestWindowMs:
|
|
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: () =>
|
|
684
|
-
|
|
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,
|
|
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 };
|