@murumets-ee/notifications 0.12.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/LICENSE +94 -0
- package/README.md +102 -0
- package/dist/admin.d.mts +29 -0
- package/dist/admin.d.mts.map +1 -0
- package/dist/admin.mjs +2 -0
- package/dist/admin.mjs.map +1 -0
- package/dist/client-CtklhnNF.mjs +2 -0
- package/dist/client-CtklhnNF.mjs.map +1 -0
- package/dist/define.d.mts +39 -0
- package/dist/define.d.mts.map +1 -0
- package/dist/define.mjs +2 -0
- package/dist/define.mjs.map +1 -0
- package/dist/email-DgfO6gZR.mjs +2 -0
- package/dist/email-DgfO6gZR.mjs.map +1 -0
- package/dist/index.d.mts +500 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -0
- package/dist/plugin.d.mts +69 -0
- package/dist/plugin.d.mts.map +1 -0
- package/dist/plugin.mjs +2 -0
- package/dist/plugin.mjs.map +1 -0
- package/dist/preferences-BCkY2j9L.d.mts +38 -0
- package/dist/preferences-BCkY2j9L.d.mts.map +1 -0
- package/dist/recipients-DDN8AJzX.mjs +2 -0
- package/dist/recipients-DDN8AJzX.mjs.map +1 -0
- package/dist/types-B8qKgKMj.d.mts +194 -0
- package/dist/types-B8qKgKMj.d.mts.map +1 -0
- package/dist/types-Dy_AGX6X.mjs +2 -0
- package/dist/types-Dy_AGX6X.mjs.map +1 -0
- package/package.json +53 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
import { _ as RenderedEmail, a as NOTIFICATION_TOPICS, c as NotificationRealtimePayload, d as NotifyOptions, f as NotifyResult, g as RenderedContent, h as RenderArgs, i as DEFAULT_RECIPIENT_CAP, l as NotificationTopic, m as Recipients, n as ChannelDeliveryRecord, o as NotificationChannelId, p as RecipientContext, r as ChannelDeliveryState, s as NotificationPreferences, t as ChannelDefinition, u as NotificationType, v as RenderedInApp } from "./types-B8qKgKMj.mjs";
|
|
2
|
+
import { clearNotificationTypeRegistry, defineNotificationType, getAllNotificationTypes, getNotificationType, registerNotificationType } from "./define.mjs";
|
|
3
|
+
import { n as notificationPreferencesSettings, r as resolveEnabledChannels, t as notificationPreferencesSchema } from "./preferences-BCkY2j9L.mjs";
|
|
4
|
+
import { JobDefinition } from "@murumets-ee/queue/client";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import * as _$_murumets_ee_db0 from "@murumets-ee/db";
|
|
7
|
+
import * as _$drizzle_orm0 from "drizzle-orm";
|
|
8
|
+
import { Logger } from "@murumets-ee/core";
|
|
9
|
+
import * as _$drizzle_orm_postgres_js0 from "drizzle-orm/postgres-js";
|
|
10
|
+
import { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
11
|
+
import { MailMessage, MailProvider, SendResult } from "@murumets-ee/mail";
|
|
12
|
+
import * as _$drizzle_orm_pg_core0 from "drizzle-orm/pg-core";
|
|
13
|
+
|
|
14
|
+
//#region src/channels/types.d.ts
|
|
15
|
+
interface ChannelSendContext {
|
|
16
|
+
/** The notification row id this delivery is for. */
|
|
17
|
+
notificationId: string;
|
|
18
|
+
/** Whether this is a fresh row (`created`) or a group-collapse update. */
|
|
19
|
+
kind: 'created' | 'updated';
|
|
20
|
+
/** Type id (e.g. `ticketing.message.created`). */
|
|
21
|
+
typeId: string;
|
|
22
|
+
/** Recipient context (id + locale + verified email if any). */
|
|
23
|
+
recipient: RecipientContext;
|
|
24
|
+
/** Rendered content for ALL channels — channel picks its own slice. */
|
|
25
|
+
rendered: RenderedContent;
|
|
26
|
+
/**
|
|
27
|
+
* Total unread count for the recipient AFTER this notification — used
|
|
28
|
+
* in realtime payloads so connected tabs can update the badge in one
|
|
29
|
+
* round-trip without an extra fetch.
|
|
30
|
+
*/
|
|
31
|
+
unreadCount: number;
|
|
32
|
+
/**
|
|
33
|
+
* Optional Drizzle transaction handle — present when the publisher
|
|
34
|
+
* called `notify(..., { tx })`. Channels that perform side-effects
|
|
35
|
+
* which must roll back with the publisher's tx (canonically, the
|
|
36
|
+
* email channel's `queue.enqueue`) MUST forward it. Channels with
|
|
37
|
+
* purely ephemeral side-effects (in-app realtime publish) ignore it.
|
|
38
|
+
*
|
|
39
|
+
* Documented in PLAN-OUTBOX Phase 5 / PR E (a.k.a. PLAN-NOTIFICATIONS PR F).
|
|
40
|
+
*
|
|
41
|
+
* Spelled `T | undefined` (not bare optional) because the publisher's
|
|
42
|
+
* tx is passed in as a destructured variable that may be undefined,
|
|
43
|
+
* and the repo enables `exactOptionalPropertyTypes` — bare optional
|
|
44
|
+
* would refuse the assignment.
|
|
45
|
+
*/
|
|
46
|
+
tx?: PostgresJsDatabase | undefined;
|
|
47
|
+
}
|
|
48
|
+
interface NotificationChannel {
|
|
49
|
+
id: NotificationChannelId;
|
|
50
|
+
/**
|
|
51
|
+
* Perform the channel's delivery. Returning the delivery record lets
|
|
52
|
+
* the notify pipeline merge per-channel state into the row's `channels`
|
|
53
|
+
* array under a single TX-free UPDATE per notify call.
|
|
54
|
+
*
|
|
55
|
+
* Implementations MUST NOT throw — return a `failed` record with the
|
|
56
|
+
* reason in `detail` instead. A throw would leak into the publisher's
|
|
57
|
+
* call stack and break user-facing routes that fan out notifications
|
|
58
|
+
* non-critically (e.g. ticket reply succeeded, but notify() crashed).
|
|
59
|
+
*/
|
|
60
|
+
send(ctx: ChannelSendContext): Promise<ChannelDeliveryRecord>;
|
|
61
|
+
}
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/channels/email.d.ts
|
|
64
|
+
/**
|
|
65
|
+
* Payload schema for the `notifications:send-email` job.
|
|
66
|
+
*
|
|
67
|
+
* Deliberately minimal — only the row id + the recipient. Render content
|
|
68
|
+
* is re-derived from the row's `payload` + the type registry at handler
|
|
69
|
+
* run time, so a group-collapse update between enqueue and run is reflected
|
|
70
|
+
* in the sent email.
|
|
71
|
+
*
|
|
72
|
+
* `recipientUserId` is repeated alongside `notificationId` so the handler's
|
|
73
|
+
* row read can scope to `(id, recipientUserId)` without needing a second
|
|
74
|
+
* lookup — defense-in-depth against a misbehaving plugin queueing a job
|
|
75
|
+
* for a row owned by a different user.
|
|
76
|
+
*/
|
|
77
|
+
declare const sendEmailJobPayloadSchema: z.ZodObject<{
|
|
78
|
+
notificationId: z.ZodString;
|
|
79
|
+
recipientUserId: z.ZodString;
|
|
80
|
+
}, "strip", z.ZodTypeAny, {
|
|
81
|
+
notificationId: string;
|
|
82
|
+
recipientUserId: string;
|
|
83
|
+
}, {
|
|
84
|
+
notificationId: string;
|
|
85
|
+
recipientUserId: string;
|
|
86
|
+
}>;
|
|
87
|
+
type SendEmailJobPayload = z.infer<typeof sendEmailJobPayloadSchema>;
|
|
88
|
+
/**
|
|
89
|
+
* Queue job definition. Plugin wiring registers a handler against this
|
|
90
|
+
* definition at init time when the mail provider is available.
|
|
91
|
+
*/
|
|
92
|
+
declare const notificationsSendEmailJob: JobDefinition<SendEmailJobPayload>;
|
|
93
|
+
/**
|
|
94
|
+
* Minimal subset of `QueueClient.enqueue` the email channel relies on.
|
|
95
|
+
* Defining it as a shape (instead of importing the whole class) lets tests
|
|
96
|
+
* inject a stub without spinning up the queue table.
|
|
97
|
+
*
|
|
98
|
+
* The third `options` argument is structurally-typed against the bits of
|
|
99
|
+
* `EnqueueOptions` this channel actually forwards — currently just `tx`
|
|
100
|
+
* for outbox-style atomic enqueue (PLAN-OUTBOX Phase 5 / PR E). The real
|
|
101
|
+
* `QueueClient.enqueue` accepts a wider `EnqueueOptions`, which is
|
|
102
|
+
* structurally assignable to this narrower shape.
|
|
103
|
+
*/
|
|
104
|
+
interface QueueEnqueueShape {
|
|
105
|
+
enqueue(job: JobDefinition<SendEmailJobPayload>, payload: SendEmailJobPayload, options?: {
|
|
106
|
+
tx?: PostgresJsDatabase | undefined;
|
|
107
|
+
}): Promise<string>;
|
|
108
|
+
}
|
|
109
|
+
interface EmailChannelConfig {
|
|
110
|
+
/** Queue client used to enqueue the send job. */
|
|
111
|
+
queue: QueueEnqueueShape;
|
|
112
|
+
/** Optional logger; child-logger from the plugin is preferred. */
|
|
113
|
+
logger?: Logger | undefined;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Build the `email` channel. Returns a `NotificationChannel` whose `send`
|
|
117
|
+
* enqueues a queue job — never blocks on mail I/O.
|
|
118
|
+
*/
|
|
119
|
+
declare function createEmailChannel(config: EmailChannelConfig): NotificationChannel;
|
|
120
|
+
interface SendEmailHandlerConfig {
|
|
121
|
+
db: PostgresJsDatabase;
|
|
122
|
+
/** Mail provider — usually `getMailConfig().provider`. */
|
|
123
|
+
mail: MailProvider;
|
|
124
|
+
/** From-address. Plugin resolves this from `mail().defaultFrom` or its own opt. */
|
|
125
|
+
from: string;
|
|
126
|
+
/** Default locale fallback when the recipient has no per-user locale. */
|
|
127
|
+
defaultLocale?: string;
|
|
128
|
+
logger?: Logger | undefined;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Subset of `JobContext` the send-email handler reads. Declared structurally
|
|
132
|
+
* so we don't depend on a `JobHandler` import that the queue package doesn't
|
|
133
|
+
* re-export from any subpath today. Function-param contravariance lets a
|
|
134
|
+
* `(SendEmailJobContext) => Promise<void>` be assigned to the broader
|
|
135
|
+
* `JobHandler<SendEmailJobPayload>` shape `registerJob` expects.
|
|
136
|
+
*/
|
|
137
|
+
interface SendEmailJobContext {
|
|
138
|
+
payload: SendEmailJobPayload;
|
|
139
|
+
/** Number of attempts so far (incl. the current one). Surfaced in failure logs. */
|
|
140
|
+
attempts: number;
|
|
141
|
+
}
|
|
142
|
+
type SendEmailHandler = (job: SendEmailJobContext) => Promise<void>;
|
|
143
|
+
/**
|
|
144
|
+
* Build the queue-handler closure. Plugin wiring calls
|
|
145
|
+
* `registerJob(notificationsSendEmailJob, createSendEmailHandler({...}))`.
|
|
146
|
+
*
|
|
147
|
+
* The closure is a thin adapter: it builds DB-backed deps and forwards to
|
|
148
|
+
* `runSendEmailJob(deps, ...)`, which is the testable orchestration core.
|
|
149
|
+
*/
|
|
150
|
+
declare function createSendEmailHandler(config: SendEmailHandlerConfig): SendEmailHandler;
|
|
151
|
+
/**
|
|
152
|
+
* Subset of `notificationsTable.makeClient(...).findOne` return shape that
|
|
153
|
+
* `runSendEmailJob` reads. Re-declared structurally so tests don't need a
|
|
154
|
+
* real DB to construct one.
|
|
155
|
+
*/
|
|
156
|
+
interface NotificationRowSnapshot {
|
|
157
|
+
id: string;
|
|
158
|
+
type: string;
|
|
159
|
+
payload: Record<string, unknown>;
|
|
160
|
+
channels: ChannelDeliveryRecord[];
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Recipient subset returned by `fetchRecipient`. Mirrors `RecipientContext`
|
|
164
|
+
* but spelled out so tests don't need to import the full type for stubs.
|
|
165
|
+
*/
|
|
166
|
+
interface SendEmailRecipient {
|
|
167
|
+
id: string;
|
|
168
|
+
name?: string | undefined;
|
|
169
|
+
email?: string | undefined;
|
|
170
|
+
locale: string;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Lower-level deps used by `runSendEmailJob`. The shipping
|
|
174
|
+
* `createSendEmailHandler` builds these from a `SendEmailHandlerConfig`;
|
|
175
|
+
* unit tests inject their own stubs to drive each branch without touching
|
|
176
|
+
* the DB or mail provider.
|
|
177
|
+
*/
|
|
178
|
+
interface SendEmailJobDeps {
|
|
179
|
+
fetchRow(id: string, recipientUserId: string): Promise<NotificationRowSnapshot | null>;
|
|
180
|
+
fetchRecipient(userId: string): Promise<SendEmailRecipient | null>;
|
|
181
|
+
lookupType(typeId: string): {
|
|
182
|
+
render: (args: {
|
|
183
|
+
payload: Record<string, unknown>;
|
|
184
|
+
recipient: SendEmailRecipient;
|
|
185
|
+
locale: string;
|
|
186
|
+
}) => {
|
|
187
|
+
email?: RenderedEmail | undefined;
|
|
188
|
+
};
|
|
189
|
+
} | undefined;
|
|
190
|
+
sendMail(message: MailMessage): Promise<SendResult>;
|
|
191
|
+
flipChannelState(id: string, recipientUserId: string, entry: ChannelDeliveryRecord): Promise<void>;
|
|
192
|
+
from: string;
|
|
193
|
+
logger?: Logger | undefined;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Run the send-email job logic against pluggable deps.
|
|
197
|
+
*
|
|
198
|
+
* Behaviour summary:
|
|
199
|
+
* - Row missing → log + return (archived/deleted between enqueue and run).
|
|
200
|
+
* - Type missing → log + return (deregistered between enqueue and run).
|
|
201
|
+
* - Recipient missing or email unverified at run time → flip channel
|
|
202
|
+
* state to `skipped_unverified`, return cleanly.
|
|
203
|
+
* - Render produced no `email` slice → flip to `failed`, log, return
|
|
204
|
+
* cleanly (no retry — render is deterministic).
|
|
205
|
+
* - mail.send threw → flip to `failed` with detail, RE-THROW so the
|
|
206
|
+
* queue retries until maxRetries.
|
|
207
|
+
* - mail.send returned → flip to `delivered`.
|
|
208
|
+
*/
|
|
209
|
+
declare function runSendEmailJob(deps: SendEmailJobDeps, job: SendEmailJobContext): Promise<void>;
|
|
210
|
+
//#endregion
|
|
211
|
+
//#region src/channels/in-app.d.ts
|
|
212
|
+
declare const inAppChannel: NotificationChannel;
|
|
213
|
+
//#endregion
|
|
214
|
+
//#region src/throttle.d.ts
|
|
215
|
+
/**
|
|
216
|
+
* Per-channel in-memory throttle - sliding window per (userId, typeId, channel).
|
|
217
|
+
*
|
|
218
|
+
* v1 lives in process memory; multi-process slip is acceptable for the v1
|
|
219
|
+
* targets ("5 emails per 15 min per user"). PLAN-NOTIFICATIONS section 6 Q2
|
|
220
|
+
* calls out the trade-off and the upgrade path (Postgres-backed counter) if
|
|
221
|
+
* the slip ever shows up in production.
|
|
222
|
+
*
|
|
223
|
+
* Format `<count>/<window>` - window is `<n><s|m|h>` (e.g. `5/15m`).
|
|
224
|
+
*
|
|
225
|
+
* Over-cap returns `false` from `tryConsume`. The caller (notify pipeline)
|
|
226
|
+
* records a `throttled` ChannelDeliveryRecord on the row instead of
|
|
227
|
+
* enqueueing the channel work.
|
|
228
|
+
*/
|
|
229
|
+
interface ParsedThrottle {
|
|
230
|
+
/** Max events allowed inside the window. */
|
|
231
|
+
count: number;
|
|
232
|
+
/** Window length in milliseconds. */
|
|
233
|
+
windowMs: number;
|
|
234
|
+
}
|
|
235
|
+
declare function parseThrottle(spec: string): ParsedThrottle;
|
|
236
|
+
/**
|
|
237
|
+
* Tracks per-(userId, typeId, channel) event timestamps in memory.
|
|
238
|
+
*
|
|
239
|
+
* Memory bound: each key keeps at most `count` timestamps; entries with
|
|
240
|
+
* no recent activity are evicted on the next consume call against that
|
|
241
|
+
* key. The unbounded growth concern is therefore the *number of distinct
|
|
242
|
+
* keys*, not per-key length. v2 may add a periodic sweeper if the key
|
|
243
|
+
* count ever shows up in heap profiles.
|
|
244
|
+
*/
|
|
245
|
+
declare class ThrottleStore {
|
|
246
|
+
private readonly events;
|
|
247
|
+
private readonly nowFn;
|
|
248
|
+
constructor(nowFn?: () => number);
|
|
249
|
+
/**
|
|
250
|
+
* @returns `true` if the event fits in the window (and is recorded);
|
|
251
|
+
* `false` if it would exceed the cap (no record made - caller should
|
|
252
|
+
* mark the delivery as `throttled` and skip the channel work).
|
|
253
|
+
*/
|
|
254
|
+
tryConsume(key: string, spec: string): boolean;
|
|
255
|
+
/**
|
|
256
|
+
* Build a stable throttle key from the per-recipient identity tuple.
|
|
257
|
+
* Uses `:` as a separator - none of the components (better-auth
|
|
258
|
+
* user ids, dot-namespaced type ids, camelCase channel ids) ever
|
|
259
|
+
* contain `:`, so collisions are impossible.
|
|
260
|
+
*/
|
|
261
|
+
static buildKey(userId: string, typeId: string, channel: string): string;
|
|
262
|
+
/** @internal - for tests. */
|
|
263
|
+
reset(): void;
|
|
264
|
+
}
|
|
265
|
+
//#endregion
|
|
266
|
+
//#region src/client.d.ts
|
|
267
|
+
/**
|
|
268
|
+
* Resolver for per-user preferences. Allows the plugin to wire up a
|
|
269
|
+
* read-through cache without forcing the client to depend on a specific
|
|
270
|
+
* settings client implementation.
|
|
271
|
+
*/
|
|
272
|
+
type PreferencesResolver = (userId: string) => Promise<NotificationPreferences>;
|
|
273
|
+
interface NotificationClientConfig {
|
|
274
|
+
db: PostgresJsDatabase;
|
|
275
|
+
logger?: Logger;
|
|
276
|
+
/** Default locale fallback when a recipient has no per-user locale. */
|
|
277
|
+
defaultLocale?: string;
|
|
278
|
+
/** Recipient cap for `resolveUsers()`. Defaults to 1000. */
|
|
279
|
+
recipientCap?: number;
|
|
280
|
+
/** Channel implementations. Keyed by channel id. Defaults to `inApp` only. */
|
|
281
|
+
channels?: Record<string, NotificationChannel>;
|
|
282
|
+
/** Resolver for per-user preferences. Defaults to "always empty" → type defaults apply. */
|
|
283
|
+
preferencesResolver?: PreferencesResolver;
|
|
284
|
+
/** In-memory throttle store. Tests inject a deterministic clock. */
|
|
285
|
+
throttleStore?: ThrottleStore;
|
|
286
|
+
}
|
|
287
|
+
declare class NotificationClient {
|
|
288
|
+
private readonly db;
|
|
289
|
+
private readonly logger?;
|
|
290
|
+
private readonly defaultLocale;
|
|
291
|
+
private readonly recipientCap;
|
|
292
|
+
private readonly channels;
|
|
293
|
+
private readonly preferencesResolver;
|
|
294
|
+
private readonly throttleStore;
|
|
295
|
+
private readonly notificationsClient;
|
|
296
|
+
constructor(config: NotificationClientConfig);
|
|
297
|
+
/**
|
|
298
|
+
* Resolve the table client to use for notification row writes. Mirrors
|
|
299
|
+
* `QueueClient.resolveJobsClient` (PR A of PLAN-OUTBOX). When `tx` is
|
|
300
|
+
* provided, every INSERT/UPDATE on `toolkit_notifications` participates
|
|
301
|
+
* in the caller's transaction; otherwise the package's owned client is
|
|
302
|
+
* used. Recipient lookup against the auth `user` table is a SELECT and
|
|
303
|
+
* does not need tx-binding.
|
|
304
|
+
*/
|
|
305
|
+
private resolveNotificationsClient;
|
|
306
|
+
/**
|
|
307
|
+
* Publish a notification to one or more recipients. See PLAN-NOTIFICATIONS §3.3.
|
|
308
|
+
*
|
|
309
|
+
* When `options.tx` is set, every row write and every email-channel
|
|
310
|
+
* queue enqueue participates in the caller's transaction — see the
|
|
311
|
+
* file-level comment for the full contract.
|
|
312
|
+
*/
|
|
313
|
+
notify<TPayload extends Record<string, unknown>>(options: NotifyOptions<TPayload>): Promise<NotifyResult>;
|
|
314
|
+
/**
|
|
315
|
+
* Per-recipient: collapse-or-insert the row, then dispatch each surviving
|
|
316
|
+
* channel under the throttle gate, then persist channel delivery state.
|
|
317
|
+
*
|
|
318
|
+
* `notificationsClient` is the tx-resolved table client from `notify()` —
|
|
319
|
+
* passed in instead of read from `this.notificationsClient` so every
|
|
320
|
+
* row write inside this method participates in the caller's tx when set.
|
|
321
|
+
* `tx` is forwarded to channels (the email channel needs it to attach
|
|
322
|
+
* `queue.enqueue` to the same tx).
|
|
323
|
+
*/
|
|
324
|
+
private persistAndDispatch;
|
|
325
|
+
/**
|
|
326
|
+
* Count of unread (readAt IS NULL) notifications for a user. Drives the
|
|
327
|
+
* realtime payload's `unreadCount` and the bell badge.
|
|
328
|
+
*/
|
|
329
|
+
unreadCount(userId: string): Promise<number>;
|
|
330
|
+
/**
|
|
331
|
+
* Result of `markRead` — `mutated` distinguishes "we updated readAt now"
|
|
332
|
+
* from "row was already read, no-op" so the route can decide whether to
|
|
333
|
+
* fire a realtime event.
|
|
334
|
+
*/
|
|
335
|
+
markRead(userId: string, notificationId: string): Promise<{
|
|
336
|
+
id: string;
|
|
337
|
+
type: string;
|
|
338
|
+
readAt: Date;
|
|
339
|
+
mutated: boolean;
|
|
340
|
+
} | null>;
|
|
341
|
+
/** Mark all unread notifications for a user as read. Returns the count affected. */
|
|
342
|
+
markAllRead(userId: string): Promise<number>;
|
|
343
|
+
/**
|
|
344
|
+
* Soft-archive a notification. Returns the archived row or null if not
|
|
345
|
+
* addressable. `mutated` is false on a second call (already archived).
|
|
346
|
+
*/
|
|
347
|
+
archive(userId: string, notificationId: string): Promise<{
|
|
348
|
+
id: string;
|
|
349
|
+
type: string;
|
|
350
|
+
mutated: boolean;
|
|
351
|
+
} | null>;
|
|
352
|
+
}
|
|
353
|
+
declare function setActiveNotificationClient(client: NotificationClient | null): void;
|
|
354
|
+
declare function getActiveNotificationClient(): NotificationClient | null;
|
|
355
|
+
/**
|
|
356
|
+
* Free-function publisher — the canonical caller-facing API. Resolves the
|
|
357
|
+
* active client wired up by the `notifications()` plugin's init hook.
|
|
358
|
+
*
|
|
359
|
+
* @example
|
|
360
|
+
* ```ts
|
|
361
|
+
* import { notify } from '@murumets-ee/notifications'
|
|
362
|
+
*
|
|
363
|
+
* await notify({
|
|
364
|
+
* type: TicketingMessageCreated,
|
|
365
|
+
* recipients: { userIds: [agentId] },
|
|
366
|
+
* payload: { ticketId, messageId, ticketSubject, authorName },
|
|
367
|
+
* })
|
|
368
|
+
* ```
|
|
369
|
+
*/
|
|
370
|
+
declare function notify<TPayload extends Record<string, unknown>>(options: NotifyOptions<TPayload>): Promise<NotifyResult>;
|
|
371
|
+
//#endregion
|
|
372
|
+
//#region src/errors.d.ts
|
|
373
|
+
/**
|
|
374
|
+
* Errors thrown by `@murumets-ee/notifications`.
|
|
375
|
+
*
|
|
376
|
+
* All extend `Error` so callers can `instanceof` discriminate. Names match
|
|
377
|
+
* the class so logger output reads cleanly.
|
|
378
|
+
*/
|
|
379
|
+
declare class RecipientRequiredError extends Error {
|
|
380
|
+
constructor();
|
|
381
|
+
}
|
|
382
|
+
declare class NotificationFanoutTooLargeError extends Error {
|
|
383
|
+
readonly attemptedSize: number;
|
|
384
|
+
readonly cap: number;
|
|
385
|
+
constructor(attemptedSize: number, cap: number);
|
|
386
|
+
}
|
|
387
|
+
declare class UnknownNotificationTypeError extends Error {
|
|
388
|
+
readonly typeId: string;
|
|
389
|
+
constructor(typeId: string);
|
|
390
|
+
}
|
|
391
|
+
declare class UnsupportedChannelError extends Error {
|
|
392
|
+
readonly typeId: string;
|
|
393
|
+
readonly channelId: string;
|
|
394
|
+
constructor(typeId: string, channelId: string);
|
|
395
|
+
}
|
|
396
|
+
//#endregion
|
|
397
|
+
//#region src/notifications-table.d.ts
|
|
398
|
+
declare const notificationsTable: {
|
|
399
|
+
table: _$drizzle_orm_pg_core0.PgTableWithColumns<{
|
|
400
|
+
name: string;
|
|
401
|
+
schema: undefined;
|
|
402
|
+
columns: {
|
|
403
|
+
[x: string]: _$drizzle_orm_pg_core0.PgColumn<{
|
|
404
|
+
name: string;
|
|
405
|
+
tableName: string;
|
|
406
|
+
dataType: _$drizzle_orm0.ColumnDataType;
|
|
407
|
+
columnType: string;
|
|
408
|
+
data: unknown;
|
|
409
|
+
driverParam: unknown;
|
|
410
|
+
notNull: false;
|
|
411
|
+
hasDefault: false;
|
|
412
|
+
isPrimaryKey: false;
|
|
413
|
+
isAutoincrement: false;
|
|
414
|
+
hasRuntimeDefault: false;
|
|
415
|
+
enumValues: string[] | undefined;
|
|
416
|
+
baseColumn: never;
|
|
417
|
+
identity: undefined;
|
|
418
|
+
generated: undefined;
|
|
419
|
+
}, {}, {}>;
|
|
420
|
+
};
|
|
421
|
+
dialect: "pg";
|
|
422
|
+
}>;
|
|
423
|
+
schema: _$_murumets_ee_db0.TableDefinition<{
|
|
424
|
+
readonly id: _$_murumets_ee_db0.ColumnFactory<string, "uuid", true, true>;
|
|
425
|
+
readonly recipientUserId: _$_murumets_ee_db0.ColumnFactory<string, "varchar", true, false>;
|
|
426
|
+
readonly type: _$_murumets_ee_db0.ColumnFactory<string, "varchar", true, false>;
|
|
427
|
+
readonly payload: _$_murumets_ee_db0.ColumnFactory<Record<string, unknown>, "jsonb", true, true>;
|
|
428
|
+
readonly channels: _$_murumets_ee_db0.ColumnFactory<ChannelDeliveryRecord[], "jsonb", true, true>;
|
|
429
|
+
readonly groupKey: _$_murumets_ee_db0.ColumnFactory<string, "varchar", false, false>;
|
|
430
|
+
readonly readAt: _$_murumets_ee_db0.ColumnFactory<Date, "timestamp", false, false>;
|
|
431
|
+
readonly archivedAt: _$_murumets_ee_db0.ColumnFactory<Date, "timestamp", false, false>;
|
|
432
|
+
readonly createdAt: _$_murumets_ee_db0.ColumnFactory<Date, "timestamp", true, true>;
|
|
433
|
+
readonly updatedAt: _$_murumets_ee_db0.ColumnFactory<Date, "timestamp", true, true>;
|
|
434
|
+
}>;
|
|
435
|
+
columnKinds: Readonly<Record<string, _$_murumets_ee_db0.ColumnKind>>;
|
|
436
|
+
primaryKeyColumns: readonly string[];
|
|
437
|
+
makeClient: (db: _$drizzle_orm_postgres_js0.PostgresJsDatabase) => _$_murumets_ee_db0.TableClient<{
|
|
438
|
+
readonly id: _$_murumets_ee_db0.ColumnFactory<string, "uuid", true, true>;
|
|
439
|
+
readonly recipientUserId: _$_murumets_ee_db0.ColumnFactory<string, "varchar", true, false>;
|
|
440
|
+
readonly type: _$_murumets_ee_db0.ColumnFactory<string, "varchar", true, false>;
|
|
441
|
+
readonly payload: _$_murumets_ee_db0.ColumnFactory<Record<string, unknown>, "jsonb", true, true>;
|
|
442
|
+
readonly channels: _$_murumets_ee_db0.ColumnFactory<ChannelDeliveryRecord[], "jsonb", true, true>;
|
|
443
|
+
readonly groupKey: _$_murumets_ee_db0.ColumnFactory<string, "varchar", false, false>;
|
|
444
|
+
readonly readAt: _$_murumets_ee_db0.ColumnFactory<Date, "timestamp", false, false>;
|
|
445
|
+
readonly archivedAt: _$_murumets_ee_db0.ColumnFactory<Date, "timestamp", false, false>;
|
|
446
|
+
readonly createdAt: _$_murumets_ee_db0.ColumnFactory<Date, "timestamp", true, true>;
|
|
447
|
+
readonly updatedAt: _$_murumets_ee_db0.ColumnFactory<Date, "timestamp", true, true>;
|
|
448
|
+
}, _$drizzle_orm_pg_core0.PgTableWithColumns<{
|
|
449
|
+
name: string;
|
|
450
|
+
schema: undefined;
|
|
451
|
+
columns: {
|
|
452
|
+
[x: string]: _$drizzle_orm_pg_core0.PgColumn<{
|
|
453
|
+
name: string;
|
|
454
|
+
tableName: string;
|
|
455
|
+
dataType: _$drizzle_orm0.ColumnDataType;
|
|
456
|
+
columnType: string;
|
|
457
|
+
data: unknown;
|
|
458
|
+
driverParam: unknown;
|
|
459
|
+
notNull: false;
|
|
460
|
+
hasDefault: false;
|
|
461
|
+
isPrimaryKey: false;
|
|
462
|
+
isAutoincrement: false;
|
|
463
|
+
hasRuntimeDefault: false;
|
|
464
|
+
enumValues: string[] | undefined;
|
|
465
|
+
baseColumn: never;
|
|
466
|
+
identity: undefined;
|
|
467
|
+
generated: undefined;
|
|
468
|
+
}, {}, {}>;
|
|
469
|
+
};
|
|
470
|
+
dialect: "pg";
|
|
471
|
+
}>>;
|
|
472
|
+
};
|
|
473
|
+
/** Backward-compatible re-export following the queue/jobs convention. */
|
|
474
|
+
declare const toolkitNotifications: _$drizzle_orm_pg_core0.PgTableWithColumns<{
|
|
475
|
+
name: string;
|
|
476
|
+
schema: undefined;
|
|
477
|
+
columns: {
|
|
478
|
+
[x: string]: _$drizzle_orm_pg_core0.PgColumn<{
|
|
479
|
+
name: string;
|
|
480
|
+
tableName: string;
|
|
481
|
+
dataType: _$drizzle_orm0.ColumnDataType;
|
|
482
|
+
columnType: string;
|
|
483
|
+
data: unknown;
|
|
484
|
+
driverParam: unknown;
|
|
485
|
+
notNull: false;
|
|
486
|
+
hasDefault: false;
|
|
487
|
+
isPrimaryKey: false;
|
|
488
|
+
isAutoincrement: false;
|
|
489
|
+
hasRuntimeDefault: false;
|
|
490
|
+
enumValues: string[] | undefined;
|
|
491
|
+
baseColumn: never;
|
|
492
|
+
identity: undefined;
|
|
493
|
+
generated: undefined;
|
|
494
|
+
}, {}, {}>;
|
|
495
|
+
};
|
|
496
|
+
dialect: "pg";
|
|
497
|
+
}>;
|
|
498
|
+
//#endregion
|
|
499
|
+
export { type ChannelDefinition, type ChannelDeliveryRecord, type ChannelDeliveryState, type ChannelSendContext, DEFAULT_RECIPIENT_CAP, type EmailChannelConfig, NOTIFICATION_TOPICS, type NotificationChannel, type NotificationChannelId, NotificationClient, type NotificationClientConfig, NotificationFanoutTooLargeError, type NotificationPreferences, type NotificationRealtimePayload, type NotificationRowSnapshot, type NotificationTopic, type NotificationType, type NotifyOptions, type NotifyResult, type PreferencesResolver, type QueueEnqueueShape, type RecipientContext, RecipientRequiredError, type Recipients, type RenderArgs, type RenderedContent, type RenderedEmail, type RenderedInApp, type SendEmailHandler, type SendEmailHandlerConfig, type SendEmailJobContext, type SendEmailJobDeps, type SendEmailJobPayload, type SendEmailRecipient, ThrottleStore, UnknownNotificationTypeError, UnsupportedChannelError, clearNotificationTypeRegistry, createEmailChannel, createSendEmailHandler, defineNotificationType, getActiveNotificationClient, getAllNotificationTypes, getNotificationType, inAppChannel, notificationPreferencesSchema, notificationPreferencesSettings, notificationsSendEmailJob, notificationsTable, notify, parseThrottle, registerNotificationType, resolveEnabledChannels, runSendEmailJob, sendEmailJobPayloadSchema, setActiveNotificationClient, toolkitNotifications };
|
|
500
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/channels/types.ts","../src/channels/email.ts","../src/channels/in-app.ts","../src/throttle.ts","../src/client.ts","../src/errors.ts","../src/notifications-table.ts"],"mappings":"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"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{a as e,i as t,n,o as r,t as i}from"./email-DgfO6gZR.mjs";import{n as a,t as o}from"./types-Dy_AGX6X.mjs";import{clearNotificationTypeRegistry as s,defineNotificationType as c,getAllNotificationTypes as l,getNotificationType as u,registerNotificationType as d}from"./define.mjs";import{a as f,c as p,i as m,o as h,r as g,s as _}from"./recipients-DDN8AJzX.mjs";import{a as v,c as y,i as b,l as x,n as S,o as C,r as w,s as T,t as E,u as D}from"./client-CtklhnNF.mjs";throw Error(`This module cannot be imported from a Client Component module. It should only be used from a Server Component.`);export{o as DEFAULT_RECIPIENT_CAP,a as NOTIFICATION_TOPICS,E as NotificationClient,g as NotificationFanoutTooLargeError,m as RecipientRequiredError,v as ThrottleStore,f as UnknownNotificationTypeError,h as UnsupportedChannelError,s as clearNotificationTypeRegistry,i as createEmailChannel,n as createSendEmailHandler,c as defineNotificationType,S as getActiveNotificationClient,l as getAllNotificationTypes,u as getNotificationType,D as inAppChannel,T as notificationPreferencesSchema,y as notificationPreferencesSettings,t as notificationsSendEmailJob,_ as notificationsTable,w as notify,C as parseThrottle,d as registerNotificationType,x as resolveEnabledChannels,e as runSendEmailJob,r as sendEmailJobPayloadSchema,b as setActiveNotificationClient,p as toolkitNotifications};
|
|
2
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../node_modules/.pnpm/server-only@0.0.1/node_modules/server-only/index.js"],"sourcesContent":["throw new Error(\n \"This module cannot be imported from a Client Component module. \" +\n \"It should only be used from a Server Component.\"\n);\n"],"x_google_ignoreList":[0],"mappings":"udAAA,MAAU,MACR,iHAED"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { o as NotificationChannelId, u as NotificationType } from "./types-B8qKgKMj.mjs";
|
|
2
|
+
import { r as resolveEnabledChannels } from "./preferences-BCkY2j9L.mjs";
|
|
3
|
+
import { Plugin } from "@murumets-ee/core";
|
|
4
|
+
|
|
5
|
+
//#region src/plugin.d.ts
|
|
6
|
+
interface NotificationsPluginOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Notification types contributed at the consumer level (i.e. defined in
|
|
9
|
+
* the app, not in a plugin). Plugin-contributed types are also collected
|
|
10
|
+
* via `Plugin.shared.notifications` and merged in at init time.
|
|
11
|
+
*/
|
|
12
|
+
types?: NotificationType[];
|
|
13
|
+
/**
|
|
14
|
+
* Default locale for renders when the recipient has no per-user locale.
|
|
15
|
+
* Defaults to `'en'`.
|
|
16
|
+
*/
|
|
17
|
+
defaultLocale?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Maximum number of recipients a single `resolveUsers()` call may return.
|
|
20
|
+
* Defaults to 1000 — bounds the blast radius of a misbehaving permission
|
|
21
|
+
* resolver. See PLAN-NOTIFICATIONS §6 Q6.
|
|
22
|
+
*/
|
|
23
|
+
recipientCap?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Per-channel options. Currently only `email` is configurable.
|
|
26
|
+
*
|
|
27
|
+
* The email channel auto-enables when both `@murumets-ee/mail` (with a
|
|
28
|
+
* provider) and `@murumets-ee/queue` are initialised in the same app.
|
|
29
|
+
* Set `email.enabled: false` to opt out even when both are available
|
|
30
|
+
* (e.g. an environment that should only deliver in-app).
|
|
31
|
+
*/
|
|
32
|
+
channels?: {
|
|
33
|
+
email?: {
|
|
34
|
+
/**
|
|
35
|
+
* Override the default opt-in. `true` forces email enabled (throws on
|
|
36
|
+
* init if mail is unavailable), `false` disables it explicitly,
|
|
37
|
+
* `undefined` (default) auto-enables when mail+queue are present.
|
|
38
|
+
*/
|
|
39
|
+
enabled?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* From-address. Falls back to `mail().defaultFrom`. One of the two
|
|
42
|
+
* MUST be configured or the email channel skips registration with
|
|
43
|
+
* a warning.
|
|
44
|
+
*/
|
|
45
|
+
from?: string;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
declare function notifications(options?: NotificationsPluginOptions): Plugin;
|
|
50
|
+
/**
|
|
51
|
+
* Cascade-loop guard for the queue terminal notifier.
|
|
52
|
+
*
|
|
53
|
+
* The notifications package owns `notifications:send-email` (and any
|
|
54
|
+
* future `notifications:*` jobs). If the mail provider is down, every
|
|
55
|
+
* send-email job dies after 3 retries. Without this guard each death
|
|
56
|
+
* would call `notify(QueueJobDead)` → enqueue one more send-email job
|
|
57
|
+
* per admin → each dies → geometric blow-up.
|
|
58
|
+
*
|
|
59
|
+
* The leading-edge alerter in `@murumets-ee/queue/alerter` still emails
|
|
60
|
+
* operators about send-email failures via its dedupe-window path, so
|
|
61
|
+
* the failure is still surfaced — just not via the bell drawer's
|
|
62
|
+
* per-admin path that would self-amplify.
|
|
63
|
+
*
|
|
64
|
+
* Exported for unit testing.
|
|
65
|
+
*/
|
|
66
|
+
declare function isQueueDeadEventNotifiable(jobType: string): boolean;
|
|
67
|
+
//#endregion
|
|
68
|
+
export { type NotificationChannelId, NotificationsPluginOptions, isQueueDeadEventNotifiable, notifications, resolveEnabledChannels };
|
|
69
|
+
//# sourceMappingURL=plugin.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.mts","names":[],"sources":["../src/plugin.ts"],"mappings":";;;;;UAoCiB,0BAAA;EA4C8D;;AAuQ/E;;;EA7SE,KAAA,GAAQ,gBAAA;EA6SgD;;;;EAxSxD,aAAA;;;;;;EAMA,YAAA;;;;;;;;;EASA,QAAA;IACE,KAAA;;;;;;MAME,OAAA;;;;;;MAMA,IAAA;IAAA;EAAA;AAAA;AAAA,iBAKU,aAAA,CAAc,OAAA,GAAS,0BAAA,GAAkC,MAAA;;;;;;;;;;;;;;;;;iBAuQzD,0BAAA,CAA2B,OAAA"}
|
package/dist/plugin.mjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import"./types-Dy_AGX6X.mjs";import{registerNotificationType as e}from"./define.mjs";import{c as t}from"./recipients-DDN8AJzX.mjs";import{c as n,i as r,l as i,r as a,t as o,u as s}from"./client-CtklhnNF.mjs";import{notificationsRoutes as c}from"./admin.mjs";import{schemaRegistry as l}from"@murumets-ee/db";import{user as u}from"@murumets-ee/auth/schema";import{eq as d}from"drizzle-orm";async function f(e){return(await e.select({id:u.id}).from(u).where(d(u.role,`admin`))).map(e=>e.id)}function p(i={}){let a=i.types??[];return{name:`@murumets-ee/notifications`,server:{tables:{toolkitNotifications:t},routes:[c()],init:async c=>{l.has(`toolkit_notifications`)||l.register(`toolkit_notifications`,t);for(let t of a)e(t);for(let t of c.plugins.all()){if(t.name===`@murumets-ee/notifications`)continue;let n=t.shared?.notifications;if(n)for(let t of n)e(t)}let u=async e=>{let{createSettingsClient:t}=await import(`@murumets-ee/settings`);return await t(n,{app:c,scopeId:e}).get(`prefs`)??{}},d={inApp:s},f=i.channels?.email;f?.enabled!==!1&&await h({app:c,channels:d,forced:f?.enabled===!0,from:f?.from,defaultLocale:i.defaultLocale??`en`}),r(new o({db:c.db.readWrite,logger:c.logger.child({pkg:`notifications`}),defaultLocale:i.defaultLocale??`en`,recipientCap:i.recipientCap??1e3,preferencesResolver:u,channels:d})),await _({app:c}),c.logger.info({types:Array.from(new Set(a.map(e=>e.id))),channels:Object.keys(d)},`Notifications plugin initialized`)}},shared:{settings:[n],notifications:a}}}const m=/^[^\s@]+@[^\s@]+\.[^\s@]+$|<[^\s@]+@[^\s@]+\.[^\s@]+>/;async function h(e){let{app:t,channels:n,forced:r,from:i,defaultLocale:a}=e,o;try{o=await import(`@murumets-ee/mail/plugin`)}catch(e){let n=`notifications: @murumets-ee/mail not available — email channel disabled (in-app still works)`;if(r)throw Error(`${n} (cause: ${e.message})`);t.logger.warn({err:e},n);return}let s;try{s=o.getMailConfig()}catch(e){let n=`notifications: mail() plugin not in plugins array — email channel disabled (in-app still works)`;if(r)throw Error(`${n} (cause: ${e.message})`);t.logger.warn({err:e},n);return}if(!s.provider){let e=`notifications: mail provider not configured — email channel disabled (in-app still works)`;if(r)throw Error(e);t.logger.warn(e);return}let c=i||s.defaultFrom;if(!c){let e=`notifications: no from-address — set channels.email.from or mail().defaultFrom (email channel disabled)`;if(r)throw Error(e);t.logger.warn(e);return}if(!m.test(c)){let e=`notifications: from-address "${c}" is not a valid email — email channel disabled`;if(r)throw Error(e);t.logger.warn(e);return}let l;try{l=await import(`@murumets-ee/queue/client`)}catch(e){let n=`notifications: @murumets-ee/queue not available — email channel disabled (in-app still works)`;if(r)throw Error(`${n} (cause: ${e.message})`);t.logger.warn({err:e},n);return}if(!t.plugins.all().some(e=>e.name===`@murumets-ee/queue`)){let e=`notifications: queue() plugin not in plugins array — email channel disabled (in-app still works)`;if(r)throw Error(e);t.logger.warn(e);return}let{createEmailChannel:u,createSendEmailHandler:d,notificationsSendEmailJob:f}=await import(`./email-DgfO6gZR.mjs`).then(e=>e.r),p=t.logger.child({pkg:`notifications`,channel:`email`});n.email=u({queue:new l.QueueClient({db:t.db.readWrite,logger:p}),logger:p}),l.registerJob(f,d({db:t.db.readWrite,mail:s.provider,from:c,defaultLocale:a,logger:p})),t.logger.info({from:c},`Notifications email channel enabled`)}function g(e){return!e.startsWith(`notifications:`)}async function _(e){let{app:t}=e;if(!t.plugins.all().some(e=>e.name===`@murumets-ee/queue`))return;let n;try{n=await import(`@murumets-ee/queue/client`)}catch(e){t.logger.warn({err:e},`notifications: @murumets-ee/queue not available — queue terminal notifier disabled`);return}let r;try{r=await import(`@murumets-ee/queue/notifications`)}catch(e){t.logger.warn({err:e},`notifications: @murumets-ee/queue/notifications subpath not available — queue terminal notifier disabled`);return}let{QueueJobDead:i,buildQueueJobDeadPayload:o}=r,s=t.db.readWrite,c=t.logger.child({pkg:`notifications`,wiring:`queue-notifier`});n.setActiveQueueNotifier({onJobDead:async e=>{if(g(e.jobType))try{await a({type:i,recipients:{resolveUsers:()=>f(s)},payload:o({jobId:e.jobId,jobType:e.jobType,attempts:e.attempts,error:e.error,failedAt:e.failedAt})})}catch(t){c.warn({err:t,jobId:e.jobId,jobType:e.jobType},`queue.job.dead notify() failed`)}}}),t.logger.info(`Notifications queue terminal notifier wired (queue.job.dead → admins)`)}export{g as isQueueDeadEventNotifiable,p as notifications,i as resolveEnabledChannels};
|
|
2
|
+
//# sourceMappingURL=plugin.mjs.map
|