Files
skills/docs/communication/messaging/notification-channel/src/channels/in-app-channel.md
2026-05-29 08:20:56 +01:00

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 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

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.