# `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`, 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 `InAppStore` adapter for persistence. - `getByRecipient(recipientId)` and `markRead(id)` convenience methods. - Records unread by default — flips to read via `markRead`. - Returns `DeliveryResult { success: false }` when the store throws, never propagates. - Injectable `Clock` for deterministic `createdAt` and `timestamp` fields. ## Usage: Basic ```ts 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 ```ts 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( "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.