first commit
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
# `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
|
||||
|
||||
```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<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.
|
||||
Reference in New Issue
Block a user