@molecule/api-notification-center
Core interface · notification-center · API (Node) · v1.0.3 · Apache-2.0
Notification center core interface for molecule.dev
npm install @molecule/api-notification-centerHow it works
@molecule/api-notification-center is the notification-center core interface on the API (Node) side: the API your app calls, with no vendor inside.
Choose the implementation by bonding one of its 1 provider: @molecule/api-notification-center-database.
import { setProvider, send, getAll, markRead } from '@molecule/api-notification-center'
import { createProvider } from '@molecule/api-notification-center-database'
// Startup — after the @molecule/api-database DataStore is bonded
setProvider(createProvider())
// Send an in-app notification
const notification = await send('user-123', {
type: 'system',
title: 'Welcome!',
body: 'Your account is ready.',
})
// List unread notifications
const { items, total } = await getAll('user-123', { read: false })
// Mark as read (scoped to the owner — only affects this user's row)
await markRead('user-123', notification.id)Providers (1): @molecule/api-notification-center-database
Works with: @molecule/api-bond, @molecule/api-i18n
Reference
Notification center core interface for molecule.dev.
Per-user, persistent IN-APP notifications (the bell-icon inbox): send,
paginated listing, unread counts, mark-read, owner-scoped delete, and
per-user notification preferences. For fire-and-forget ops/broadcast
channels (Slack/webhook alerts), use @molecule/api-notifications instead.
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
Quick Start
import { setProvider, send, getAll, markRead } from '@molecule/api-notification-center'
import { createProvider } from '@molecule/api-notification-center-database'
// Startup — after the @molecule/api-database DataStore is bonded
setProvider(createProvider())
// Send an in-app notification
const notification = await send('user-123', {
type: 'system',
title: 'Welcome!',
body: 'Your account is ready.',
})
// List unread notifications
const { items, total } = await getAll('user-123', { read: false })
// Mark as read (scoped to the owner — only affects this user's row)
await markRead('user-123', notification.id)
Type
core
Installation
npm install @molecule/api-notification-center @molecule/api-bond @molecule/api-i18n
API
Interfaces
Notification
An in-app notification.
interface Notification {
/** Provider-assigned notification identifier. */
id: string
/** The user this notification belongs to. */
userId: string
/** Notification category (e.g. 'system', 'message', 'alert'). */
type: string
/** Notification headline. */
title: string
/** Notification body text. */
body: string
/** Whether the notification has been read. */
read: boolean
/** Arbitrary structured data attached to the notification. */
data?: Record<string, unknown>
/** When the notification was created. */
createdAt: Date
}
CreateNotification
Data required to create a new notification.
interface CreateNotification {
/** Notification category. */
type: string
/** Notification headline. */
title: string
/** Notification body text. */
body: string
/** Arbitrary structured data to attach. */
data?: Record<string, unknown>
/** Delivery channels for this notification. Defaults to `['inApp']`. */
channels?: ('inApp' | 'email' | 'push' | 'sms')[]
}
BulkNotification
A bulk notification targeting a specific user.
interface BulkNotification {
/** The target user. */
userId: string
/** The notification to send. */
notification: CreateNotification
}
NotificationPreferences
User notification preferences.
interface NotificationPreferences {
/** Whether email notifications are enabled. */
email: boolean
/** Whether push notifications are enabled. */
push: boolean
/** Whether SMS notifications are enabled. */
sms: boolean
/** Per-channel or per-type overrides. */
channels: Record<string, boolean>
}
NotificationQuery
Query options for listing notifications.
interface NotificationQuery {
/** Maximum number of results to return. */
limit?: number
/** Number of results to skip. */
offset?: number
/** Filter by read status. */
read?: boolean
/** Filter by notification type. */
type?: string
}
PaginatedResult
Paginated result set.
interface PaginatedResult<T> {
/** The result items for this page. */
items: T[]
/** Total number of matching items. */
total: number
/** Number of items skipped. */
offset: number
/** Maximum items per page. */
limit: number
}
NotificationCenterProvider
Notification center provider interface.
All notification center providers must implement this interface to provide in-app notification CRUD, read status management, and user preferences.
interface NotificationCenterProvider {
/**
* Sends a notification to a specific user.
*
* @param userId - The target user identifier.
* @param notification - The notification to create.
* @returns The created notification.
*/
send(userId: string, notification: CreateNotification): Promise<Notification>
/**
* Sends notifications to multiple users in a single batch.
*
* @param notifications - Array of user-targeted notifications.
* @returns The created notifications.
*/
sendBulk(notifications: BulkNotification[]): Promise<Notification[]>
/**
* Retrieves all notifications for a user with optional filtering.
*
* @param userId - The user to retrieve notifications for.
* @param options - Optional query filters and pagination.
* @returns Paginated notification results.
*/
getAll(userId: string, options?: NotificationQuery): Promise<PaginatedResult<Notification>>
/**
* Returns the count of unread notifications for a user.
*
* @param userId - The user to count unread notifications for.
* @returns The unread notification count.
*/
getUnreadCount(userId: string): Promise<number>
/**
* Marks a single notification as read, scoped to its owner.
*
* Implementations MUST only affect rows where `user_id = userId` so a user
* can never mark another user's notification read by id (IDOR).
*
* @param userId - The owner whose notification should be marked read.
* @param notificationId - The notification to mark as read.
* @returns `true` if a row owned by `userId` was updated, `false` otherwise.
*/
markRead(userId: string, notificationId: string): Promise<boolean>
/**
* Marks all notifications for a user as read.
*
* @param userId - The user whose notifications should be marked read.
*/
markAllRead(userId: string): Promise<void>
/**
* Deletes a notification, scoped to its owner.
*
* Implementations MUST only affect rows where `user_id = userId` so a user
* can never delete another user's notification by id (IDOR).
*
* @param userId - The owner whose notification should be deleted.
* @param notificationId - The notification to delete.
* @returns `true` if a row owned by `userId` was deleted, `false` otherwise.
*/
delete(userId: string, notificationId: string): Promise<boolean>
/**
* Retrieves notification preferences for a user.
*
* @param userId - The user to retrieve preferences for.
* @returns The user's notification preferences.
*/
getPreferences(userId: string): Promise<NotificationPreferences>
/**
* Updates notification preferences for a user.
*
* @param userId - The user to update preferences for.
* @param preferences - The preferences to merge.
*/
setPreferences(userId: string, preferences: Partial<NotificationPreferences>): Promise<void>
}
Functions
send(userId, notification)
Sends a notification to a specific user.
function send(userId: string, notification: CreateNotification): Promise<Notification>
userId— The target user identifier.notification— The notification to create.
Returns: The created notification.
sendBulk(notifications)
Sends notifications to multiple users in a single batch.
function sendBulk(notifications: BulkNotification[]): Promise<Notification[]>
notifications— Array of user-targeted notifications.
Returns: The created notifications.
getAll(userId, options)
Retrieves all notifications for a user with optional filtering.
function getAll(userId: string, options?: NotificationQuery): Promise<PaginatedResult<Notification>>
userId— The user to retrieve notifications for.options— Optional query filters and pagination.
Returns: Paginated notification results.
getUnreadCount(userId)
Returns the count of unread notifications for a user.
function getUnreadCount(userId: string): Promise<number>
userId— The user to count unread notifications for.
Returns: The unread notification count.
markRead(userId, notificationId)
Marks a single notification as read, scoped to its owner.
Only affects the notification when it belongs to userId, preventing a user
from marking another user's notification read by id (IDOR).
function markRead(userId: string, notificationId: string): Promise<boolean>
userId— The owner whose notification should be marked read.notificationId— The notification to mark as read.
Returns: true if a row owned by userId was updated, false otherwise.
markAllRead(userId)
Marks all notifications for a user as read.
function markAllRead(userId: string): Promise<void>
userId— The user whose notifications should be marked read.
Returns: Resolves when all notifications have been marked read.
deleteNotification(userId, notificationId)
Deletes a notification, scoped to its owner.
Only deletes the notification when it belongs to userId, preventing a user
from deleting another user's notification by id (IDOR).
function deleteNotification(userId: string, notificationId: string): Promise<boolean>
userId— The owner whose notification should be deleted.notificationId— The notification to delete.
Returns: true if a row owned by userId was deleted, false otherwise.
getPreferences(userId)
Retrieves notification preferences for a user.
function getPreferences(userId: string): Promise<NotificationPreferences>
userId— The user to retrieve preferences for.
Returns: The user's notification preferences.
setPreferences(userId, preferences)
Updates notification preferences for a user.
function setPreferences(
userId: string,
preferences: Partial<NotificationPreferences>,
): Promise<void>
userId— The user to update preferences for.preferences— The preferences to merge.
Returns: Resolves when preferences have been updated.
setProvider(provider)
Registers a notification center provider as the active singleton. Called by bond packages during application startup.
function setProvider(provider: NotificationCenterProvider): void
provider— The notification center provider implementation to bond.
getProvider()
Retrieves the bonded notification center provider, throwing if none is configured.
function getProvider(): NotificationCenterProvider
Returns: The bonded notification center provider.
hasProvider()
Checks whether a notification center provider is currently bonded.
function hasProvider(): boolean
Returns: true if a notification center provider is bonded.
Available Providers
| Provider | Package |
|---|---|
| Notifications | @molecule/api-notification-center-database |
Injection Notes
Requirements
Peer dependencies:
@molecule/api-bond^1.0.1@molecule/api-i18n^1.0.1
Runtime Dependencies
-
@molecule/api-bond -
@molecule/api-i18n -
Wire the database first — and migrate the tables. The database-backed bond (
@molecule/api-notification-center-database) persists through the bonded@molecule/api-databaseDataStore: bond the DataStore beforesetProvider(createProvider()), and create thenotificationsandnotification_preferencestables in your app's migrations (see the bond's docs for the expected columns) — nothing auto-creates them. -
Every operation is owner-scoped by
userId—markRead/deleteNotificationaffect only that user's row (returningfalsewhen nothing matched) andgetAllreturns only that user's items. In handlers, ALWAYS pass the AUTHENTICATED user's id — forwarding a client-supplieduserIdrecreates the cross-user access the scoping exists to prevent. -
getAllreturns a paginated{ items, total, offset, limit }result — drive the UI fromitems+total, notitems.length. -
Sending here does NOT deliver email/push — it writes the in-app record. Fan out to
@molecule/api-emails/ push packages separately (honoringgetPreferences(userId)) when a notification should also leave the app.
E2E Tests
Integration checklist — drive the real UI (live preview, no mocks), adapt each item to this app's actual bell/feed and notification triggers, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip:
- An event that should notify a user (a mention, invite, comment, a
finished job — whatever this app defines) creates an in-app notification
that appears in THAT user's bell/feed with the correct type, title, body,
and any link/target carried in
data. Sending here writes only the in-app record — do not expect it to also arrive by email/push. - The unread badge (from
getUnreadCount) increments when a new notification arrives and equals the number of unread items shown in the feed. - Marking one read (
markRead) flips that item to read and drops the badge by one; mark-all-read (markAllRead) shows every item as read and the badge as zero — and BOTH changes persist across a full reload (they are stored, not client-only state). - Real-time: if the app wires a live channel (SSE/websocket), a notification sent while the feed is open appears WITHOUT a manual reload and the badge updates live; with no live channel, confirm the new notification shows on the next feed load / poll.
- Clicking a notification navigates to its target (the link/id in
data) and, where that is the intended behavior, marks it read. - Clearing/deleting one (
deleteNotification) removes it from the feed and it does NOT reappear on reload (it is deleted from the store, not just hidden client-side). - Scoping — a user sees ONLY their own feed:
getAllreturns just the authenticated user's items, a notification created for user A never shows for user B, and no route returns or mutates another user's notification by id (markRead/deleteNotificationon someone else's id must no-op and return false, never touch that row). Handlers pass the AUTHENTICATED user's id — never a client-supplieduserId.