2.7 KiB
in-app-channel — In-app notification channel
Purpose
in-app-channel.ts ships the InAppChannel implementation that stores notifications for an in-app inbox / unread badge. The default storage is an in-memory Map<recipientId, InAppNotification[]>, but the constructor accepts any InAppStore implementation so consumers can persist to SurrealDB, Postgres, Redis, or a local IndexedDB browser store. The class is universal — no Node-only or browser-only APIs.
Features
- Default in-memory store — useful for tests and prototypes.
- Pluggable
InAppStoreadapter for persistence. getByRecipient(recipientId)andmarkRead(id)convenience methods.- Records unread by default — flips to read via
markRead. - Returns
DeliveryResult { success: false }when the store throws, never propagates. - Injectable
Clockfor deterministiccreatedAtandtimestampfields.
Usage: Basic
import { InAppChannel } from "@suaveplan/notification-channel";
const channel = new InAppChannel();
await channel.send(notification);
const unread = (await channel.getByRecipient(notification.recipient)).filter(
(n) => !n.read
);
Usage: Advanced
import {
InAppChannel,
type InAppNotification,
type InAppStore,
} from "@suaveplan/notification-channel";
const dbStore: InAppStore = {
async save(n) {
await db.execute("INSERT INTO notifications ...", [n]);
},
async getByRecipient(recipientId) {
return db.query<InAppNotification>(
"SELECT * FROM notifications WHERE recipientId = ?",
[recipientId]
);
},
async markRead(id) {
await db.execute(
"UPDATE notifications SET read = true WHERE id = ?",
[id]
);
},
};
const channel = new InAppChannel(dbStore);
API Reference
InAppChannel(store?: InAppStore, clock?: Clock)— constructor.InAppNotification—{ id, recipientId, subject?, body, read, createdAt, metadata? }.InAppStore—save,getByRecipient,markRead.channel.canSend(n),channel.send(n),channel.getByRecipient(id),channel.markRead(id).
Implementation Notes
The default in-memory store backs onto a private Map so multiple InAppChannel instances do not share state. The store factory closes over the per-instance map to avoid leaking persistence between independent channel objects (useful in tests that construct multiple channels). markRead is implemented by scanning every recipient's list — for production-scale deployments the consumer must inject a real store with an indexed lookup; the in-memory variant is a development convenience, not a recommended persistence layer.