Initial Commit
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
# `delivery-tracker`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`delivery-tracker` is the bounded LRU cache of
|
||||
`SmsDeliveryReport` records that `SmsService` consults for
|
||||
delivery status. Receipts may arrive via webhook ingest
|
||||
(`tracker.ingest(report)`) or be lazily fetched from the
|
||||
configured provider via `tracker.lookupOrFetch(messageId,
|
||||
provider)`. The cache is bounded so a long-running service does
|
||||
not retain every receipt indefinitely.
|
||||
|
||||
When no provider is supplied and the cache misses,
|
||||
`lookupOrFetch` throws `SmsReceiptNotFoundError` (code
|
||||
`SMS.RECEIPT_NOT_FOUND`) so callers can distinguish a miss from
|
||||
a remote provider error.
|
||||
|
||||
## Features
|
||||
|
||||
- LRU cache keyed by `messageId`, bounded by `maxEntries`
|
||||
(default 1024).
|
||||
- `ingest(report)` — record / refresh a receipt; oldest entry
|
||||
evicts on overflow.
|
||||
- `getStatus(messageId)` — synchronous cache read; refreshes
|
||||
LRU order on hit.
|
||||
- `lookupOrFetch(messageId, provider?)` — cache-first, falls
|
||||
back to `provider.getStatus`; result is cached on success.
|
||||
- `size()` and `clear()` for observation / reset.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { DeliveryTracker } from "@suaveplan/sms/delivery-tracker";
|
||||
|
||||
const tracker = new DeliveryTracker();
|
||||
tracker.ingest({
|
||||
messageId: "m1",
|
||||
to: "+15551234567",
|
||||
status: "delivered",
|
||||
timestamp: new Date(),
|
||||
});
|
||||
console.log(tracker.getStatus("m1"));
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { DeliveryTracker } from "@suaveplan/sms/delivery-tracker";
|
||||
import { InMemorySmsProvider } from "@suaveplan/sms/in-memory-provider";
|
||||
|
||||
const tracker = new DeliveryTracker({ maxEntries: 4 });
|
||||
const provider = new InMemorySmsProvider();
|
||||
|
||||
// Lazy fetch — first call hits the provider, second hits cache.
|
||||
const fromProvider = await tracker.lookupOrFetch("m1", provider);
|
||||
const fromCache = await tracker.lookupOrFetch("m1", provider);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `class DeliveryTracker`
|
||||
- `constructor(options?: DeliveryTrackerOptions)`
|
||||
- `ingest(report: SmsDeliveryReport): void`
|
||||
- `getStatus(messageId: string): SmsDeliveryReport | undefined`
|
||||
- `lookupOrFetch(messageId: string, provider?: SmsProvider):
|
||||
Promise<SmsDeliveryReport>`
|
||||
- `size(): number`
|
||||
- `clear(): void`
|
||||
- `interface DeliveryTrackerOptions`
|
||||
- `maxEntries?: number` (default 1024, must be ≥ 1)
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The implementation uses a plain `Map` and exploits JavaScript's
|
||||
insertion-ordered iteration to maintain LRU order: on each hit
|
||||
we `delete` then `set` the entry, pushing it to the back. On
|
||||
`ingest`, an existing entry is deleted before re-insertion to
|
||||
keep order canonical. Eviction reads the first key in the
|
||||
iterator (the oldest entry) and deletes it. `lookupOrFetch`
|
||||
catches no provider errors — they propagate to the caller so a
|
||||
network failure on receipt fetch isn't silently swallowed.
|
||||
Reference in New Issue
Block a user