@samveys/notifications
From packages/notifications/README.md in the framework repository.
One notification, several channels: mail and a database inbox are built in, and extend adds more.
Define and send
import { database, defineDatabaseConfig, sqlite } from '@samveys/database'
import { mail, smtp } from '@samveys/mail'
import { defineNotification, notifications } from '@samveys/notifications'
import type { Notifiable } from '@samveys/notifications'
interface User extends Notifiable {
readonly name: string
}
export const invoicePaid = defineNotification<User>({
type: 'invoice.paid',
via: (user) => (user.email === undefined ? ['database'] : ['mail', 'database']),
toMail: (user) => ({ subject: `Thanks ${user.name}`, text: 'Your invoice was paid.' }),
toDatabase: (user) => ({ message: `Invoice paid by ${user.name}` }),
})
export const plugins = [
database(defineDatabaseConfig({ default: 'main', connections: { main: sqlite({ file: 'app.sqlite' }) } })),
mail({ from: 'noreply@sadata.id', transport: smtp({ host: 'smtp.example.com', port: 587, requireTLS: true }) }),
notifications({ notifications: [invoicePaid as never] }),
]await notifications.notify(user, invoicePaid) mails user.email (the draft's to defaults to it; a user without an email and a mail channel is SVS_E103) and stores an inbox row in samveys_notifications. An unknown channel name is refused before anything is sent.
Inbox
import type { Notifications } from '@samveys/notifications'
export async function readInbox(service: Notifications, user: { id: number }): Promise<number> {
const unread = await service.unread(user)
for (const entry of unread) await service.markAsRead(entry.id)
return unread.length
}all(user), unread(user), markAsRead(id) and markAllAsRead(user) only touch the rows of that user.
Queue a notification
Set queue: true and install queue() from @samveys/queue: notify then dispatches one job and returns; a worker (svs queue:work) delivers it on every channel. List the notification in notifications({ notifications: [...] }) so a worker can resolve it by type.
More channels
notifications.extend('broadcast', {
async send(user, notification) {
publish(`user.${user.id}`, notification.payloadFor('broadcast', user))
},
})A notification supplies the payload for a custom channel with toChannel: { broadcast: (user) => ({ … }) }.