← All @molecule/* packages · App templates
@molecule/app-notification-centerCore interface · notification-center · App (browser) · v1.0.1 · Apache-2.0
Notification center core interface for molecule.dev — in-app notification bell with pagination, read tracking, realtime updates, and polling
npm install @molecule/app-notification-center@molecule/app-notification-center is the notification-center core interface on the app (browser) side: the API your app calls, with no vendor inside.
Choose the implementation by bonding one of its 1 provider: @molecule/app-notification-center-default.
import { setProvider, createNotificationCenter } from '@molecule/app-notification-center'
import { provider } from '@molecule/app-notification-center-default'
setProvider(provider)
const center = createNotificationCenter({
fetchNotifications: (opts) => api.get('/notifications', opts),
fetchUnreadCount: () => api.get('/notifications/unread-count'),
markAsRead: (id) => api.post(`/notifications/${id}/read`),
markAllAsRead: () => api.post('/notifications/read-all'),
pollInterval: 30_000,
})
center.onUpdate((state) => {
console.log('Unread:', state.unreadCount)
})Providers (1): @molecule/app-notification-center-default
Works with: @molecule/app-bond
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.
Notification center core interface for molecule.dev.
Provides a framework-agnostic contract for in-app notification center widgets
with paginated fetching, read/unread tracking, realtime push updates, polling,
and subscription-based state notifications. Bond a provider (e.g.
@molecule/app-notification-center-default) at startup, then use
{@link createNotificationCenter} anywhere.
import { setProvider, createNotificationCenter } from '@molecule/app-notification-center'
import { provider } from '@molecule/app-notification-center-default'
setProvider(provider)
const center = createNotificationCenter({
fetchNotifications: (opts) => api.get('/notifications', opts),
fetchUnreadCount: () => api.get('/notifications/unread-count'),
markAsRead: (id) => api.post(`/notifications/${id}/read`),
markAllAsRead: () => api.post('/notifications/read-all'),
pollInterval: 30_000,
})
center.onUpdate((state) => {
console.log('Unread:', state.unreadCount)
})
core
npm install @molecule/app-notification-center @molecule/app-bond
AppNotificationA single in-app notification.
interface AppNotification {
/** Unique identifier. */
id: string
/** Notification type key (e.g. `'mention'`, `'comment'`, `'invite'`). */
type: string
/** Short title for the notification (pass through i18n before setting). */
title: string
/** Body / description text (pass through i18n before setting). */
body: string
/** Whether the notification has been read. */
read: boolean
/** Optional URL the notification links to. */
actionUrl?: string
/** Optional avatar URL for the notification sender / actor. */
avatar?: string
/** Timestamp when the notification was created. */
createdAt: Date
/** Arbitrary metadata attached to the notification. */
metadata?: Record<string, unknown>
}
FetchOptionsOptions for fetching a page of notifications.
interface FetchOptions {
/** Opaque cursor for cursor-based pagination. `undefined` fetches the first page. */
cursor?: string
/** Maximum number of items to return per page. */
limit?: number
/** Optional filter criteria. */
filter?: NotificationFilter
}
NotificationCenterInstanceA live notification center instance exposing query, mutation, and subscription methods.
interface NotificationCenterInstance {
// -- Query ---------------------------------------------------------------
/**
* Returns the currently loaded notifications.
*
* @returns Array of notifications loaded so far.
*/
getNotifications(): AppNotification[]
/**
* Returns the current unread notification count.
*
* @returns The unread count.
*/
getUnreadCount(): number
/**
* Returns whether more notifications can be loaded.
*
* @returns `true` if additional pages are available.
*/
hasMore(): boolean
/**
* Returns whether a fetch operation is currently in progress.
*
* @returns `true` if loading.
*/
isLoading(): boolean
// -- Actions -------------------------------------------------------------
/**
* Loads the next page of notifications (appends to the current list).
*/
loadMore(): Promise<void>
/**
* Refreshes the notification list and unread count from scratch.
*/
refresh(): Promise<void>
/**
* Marks a single notification as read.
*
* @param notificationId - The id of the notification to mark as read.
*/
markAsRead(notificationId: string): Promise<void>
/** Marks all notifications as read. */
markAllAsRead(): Promise<void>
// -- Subscriptions -------------------------------------------------------
/**
* Registers a handler that fires when the notification center state changes.
*
* @param handler - The update handler.
*/
onUpdate(handler: NotificationUpdateHandler): void
/**
* Removes a previously registered update handler.
*
* @param handler - The handler to remove.
*/
offUpdate(handler: NotificationUpdateHandler): void
// -- Lifecycle -----------------------------------------------------------
/**
* Returns the current state snapshot.
*
* @returns The current {@link NotificationCenterState}.
*/
getState(): NotificationCenterState
/**
* Releases resources held by the notification center instance
* (stops polling, removes realtime listeners, clears subscriptions).
*/
destroy(): void
}
NotificationCenterOptionsConfiguration for creating a notification center instance.
interface NotificationCenterOptions {
/**
* Fetches a page of notifications.
*
* @param options - Pagination and filter options.
* @returns A promise resolving to a paginated result of notifications.
*/
fetchNotifications: (options: FetchOptions) => Promise<PaginatedResult<AppNotification>>
/**
* Fetches the current unread notification count.
*
* @returns A promise resolving to the unread count.
*/
fetchUnreadCount: () => Promise<number>
/**
* Marks a single notification as read.
*
* @param notificationId - The id of the notification to mark as read.
*/
markAsRead: (notificationId: string) => Promise<void>
/** Marks all notifications as read. */
markAllAsRead: () => Promise<void>
/**
* Called when a new notification is received (via polling or realtime).
*
* @param notification - The new notification.
*/
onNotification?: (notification: AppNotification) => void
/**
* Polling interval in milliseconds for fetching new notifications.
* Set to `0` or `undefined` to disable polling. Defaults to `0` (disabled).
*/
pollInterval?: number
/**
* Optional realtime transport for receiving push notifications.
* Any object satisfying {@link NotificationRealtimeAdapter} (e.g. a
* `RealtimeConnection` from `@molecule/app-realtime`) can be used.
*/
realtime?: NotificationRealtimeAdapter
/**
* The event name to listen for on the realtime adapter.
* Defaults to `'notification'`.
*/
realtimeEvent?: string
}
NotificationCenterProviderContract that bond packages must implement to provide notification center functionality.
interface NotificationCenterProvider {
/**
* Creates a new notification center instance from the given options.
*
* @param options - Notification center configuration.
* @returns A notification center instance.
*/
createNotificationCenter(options: NotificationCenterOptions): NotificationCenterInstance
}
NotificationCenterStateSnapshot of the notification center state, emitted to subscribers on change.
interface NotificationCenterState {
/** Current list of loaded notifications. */
notifications: AppNotification[]
/** Current unread notification count. */
unreadCount: number
/** Whether a fetch operation is in progress. */
loading: boolean
/** Whether more notifications are available to load. */
hasMore: boolean
/**
* The error from the most recent failed `refresh()` / `loadMore()` /
* `poll()` attempt, or `undefined` if the last attempt (or the most
* recent one per-operation) succeeded. Providers MUST clear this (set it
* back to `undefined`) on the next successful fetch — it is not a sticky
* banner.
*
* Without this field a provider that swallows fetch failures (documented
* noop) renders identically whether the user genuinely has zero
* notifications OR the very first fetch failed (network/server error):
* both look like an empty inbox with `loading: false`. Consumers should
* check this field to show a retry banner instead of a bare empty state.
*/
lastError?: Error
}
NotificationFilterFilter criteria for narrowing notification queries.
interface NotificationFilter {
/** Filter by notification type (e.g. `'mention'`, `'comment'`). */
type?: string
/** Filter by read status. */
read?: boolean
}
NotificationRealtimeAdapterMinimal realtime transport interface for receiving push notifications.
Any object with on / off methods satisfies this contract — including
a realtime connection from the @molecule/app-realtime package.
interface NotificationRealtimeAdapter {
/**
* Registers a handler for an incoming event.
*
* @param event - The event name to listen for.
* @param handler - The handler callback.
*/
on(event: string, handler: (data: unknown) => void): void
/**
* Removes a handler for an event.
*
* @param event - The event name.
* @param handler - The specific handler to remove (optional).
*/
off(event: string, handler?: (data: unknown) => void): void
}
PaginatedResultA generic paginated result set.
interface PaginatedResult<T> {
/** The items for the current page. */
items: T[]
/** Cursor for fetching the next page. `undefined` when there are no more pages. */
nextCursor?: string
/** Whether more items are available beyond this page. */
hasMore: boolean
/** Optional total count of items across all pages. */
total?: number
}
NotificationUpdateHandlerHandler invoked when the notification center state changes.
type NotificationUpdateHandler = (state: NotificationCenterState) => void
createNotificationCenter(options)Creates a notification center instance using the bonded provider.
function createNotificationCenter(options: NotificationCenterOptions): NotificationCenterInstance
options — Notification center configuration.Returns: A notification center instance.
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.
setProvider(provider)Registers a notification center provider as the active singleton. Called by
bond packages (e.g. @molecule/app-notification-center-default) during app
startup.
function setProvider(provider: NotificationCenterProvider): void
provider — The notification center provider implementation to bond.| Provider | Package |
|---|---|
| Notification Center | @molecule/app-notification-center-default |
Peer dependencies:
@molecule/app-bond ^1.0.1@molecule/app-bondNotificationCenterState.lastError is the error surface for a failed
refresh() / loadMore() / poll() attempt. Without it, a provider that
documents fetch failures as a silent noop renders a FIRST-load failure
identically to "you have no notifications" — both are an empty list with
loading: false, and a consumer UI cannot tell "network/server error"
from "genuinely empty" or show a retry banner.
Provider implementations (e.g. @molecule/app-notification-center-default)
MUST populate lastError in their fetch catch blocks and clear it
(lastError: undefined) on the next successful fetch — it is not a
sticky banner that survives a later success. Framework bindings (e.g.
@molecule/app-notification-center-react) should read this field to
render a retry affordance instead of a bare empty state.
Integration checklist — drive the real UI (live preview, no mocks), adapt each item to this app's actual screens/flows, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip:
unreadCount / getUnreadCount())
exactly equals the number of loaded AppNotifications with read: false.actionUrl (its target,
not a shared/hardcoded route) AND marks it read (markAsRead(id)): its
read flag flips, it loses the unread treatment, and the badge decrements
by one — and that stays after a full reload (persisted server-side, not
just local state).markAllAsRead() drops the unread badge to 0 and NO remaining item
still shows the unread treatment; the cleared state survives a reload.NotificationFilter.read / by-type
via NotificationFilter.type, passed as FetchOptions.filter) renders
ONLY matching notifications and the visible count reflects the filter
honestly — no read items leak into an unread-only view.loadMore() following PaginatedResult.hasMore
nextCursor) appends OLDER notifications to the list with zero
duplicates — every rendered id is unique — and the control stops once
hasMore is false.NotificationRealtimeAdapter wired via realtime / realtimeEvent,
surfaced through onNotification) appears at the TOP of the list and bumps
the unread badge WITHOUT any manual reload or refetch.lastError renders a retry affordance, never a silent "empty".id belonging to another
user is never listed, and cannot be reached via markAsRead(id) or the
click-through actionUrl (no cross-user read/navigation).Translation strings are provided by @molecule/app-locales-notification-center.