3.7 KiB
3.7 KiB
segment-refresher
(no submodules)
Purpose
segment-refresher.ts orchestrates re-evaluation of one or many segments
and persists the resulting contactIds back to the SegmentStore cache. It
exposes a typed EventBus<SegmentRefresherEventMap> so downstream consumers
(broadcast schedulers, real-time dashboards, OTel exporters) can subscribe to
segment:refreshed without coupling to the refresher's internals. The
refresher is intentionally driver-agnostic — it does not own a timer; the
consumer drives refresh / refreshAll from whatever cadence policy
(cron, push, manual button) it prefers.
Features
refresh(segmentId)— single-segment re-evaluation; returnsundefinedfor an unknown ID without emitting.refreshAll(tenantId)— multi-segment fan-out; emits once per refreshed segment.- Typed
EventBus<SegmentRefresherEventMap>—events.on("segment:refreshed", handler)receives{ segmentId, result }. - Cache write through
store.updateCache(segmentId, contactIds)on every refresh, updatingcachedContactIds,cachedCount,lastRefreshedAt,updatedAt. - Pure composition of
SegmentEvaluator+SegmentStore— both are constructor-injected so test doubles wire in cleanly.
Usage: Basic Example
import {
InMemorySegmentStore,
SegmentEvaluator,
SegmentRefresher,
} from "@suaveplan/audience-rules";
const store = new InMemorySegmentStore();
const evaluator = new SegmentEvaluator(async () => [
{ id: "c1", tags: ["vip"] },
]);
const refresher = new SegmentRefresher(store, evaluator);
const segment = await store.create({
tenantId: "t1",
name: "VIPs",
rules: [{ field: "tags", operator: "contains", value: "vip" }],
refreshMode: "manual",
});
const result = await refresher.refresh(segment.id);
Usage: Advanced Example
import {
InMemorySegmentStore,
SegmentEvaluator,
SegmentRefresher,
} from "@suaveplan/audience-rules";
const refresher = new SegmentRefresher(
new InMemorySegmentStore(),
new SegmentEvaluator(async () => [])
);
refresher.events.on("segment:refreshed", ({ segmentId, result }) => {
// Forward to OTel, dashboard, downstream pub/sub, etc.
console.log(
`refreshed ${segmentId}: ${result.total} (${(result.matchRate * 100).toFixed(1)}%)`
);
});
await refresher.refreshAll("tenant-1");
API Reference
interface SegmentRefresherEventMap—"segment:refreshed": { segmentId, result }.class SegmentRefresher— constructor(store: SegmentStore, evaluator: SegmentEvaluator).refresher.events: EventBus<SegmentRefresherEventMap>— typed event bus from@suaveplan/events;on/off/emit/use/clear.refresher.refresh(segmentId: string): Promise<SegmentEvaluationResult | undefined>refresher.refreshAll(tenantId: string): Promise<Map<string, SegmentEvaluationResult>>
Implementation Notes
- The migration swaps upstream's untyped
EventEmitterfor the suaveplanEventBus<TMap>. The consumer API (on("segment:refreshed", handler)) is preserved verbatim; only the type signature ofhandlerbecomes precise. refreshreturnsundefinedfor a missing segment because the absence is a normal (not exceptional) outcome — the consumer typically retries with a corrected ID or surfaces a404at the API layer.refreshAlliterates sequentially; concurrent fan-out is intentionally left to the caller (Promise.all over selected segment IDs) so the refresher does not need to own a concurrency policy.- The
eventsfield is publicly readable but theEventBusis mutable — consumers are expected to subscribe, not replace.