Observability
InMemoryDeliveryLog plus deliverySink record every destination result from track so you can query what actually shipped.
Why
track returns a result per destination, then that array is gone. A delivery log lets you ask “which Meta sends failed since T?” without wrapping every call yourself.
adscapi stays stateless. You own the store.
ts
import { createAdscapi, InMemoryDeliveryLog, deliverySink } from 'adscapi';
const log = new InMemoryDeliveryLog();
const ads = createAdscapi(secrets, { onResult: deliverySink(log) });
await ads.conversions.track(event);
const failures = log.query({ platform: 'meta', ok: false, since: Date.now() - 86_400_000, limit: 50 });onResult fires once per destination after each attempt — success, error, dryRun, or deduped. A throwing sink never breaks the fan-out.
Query
query is newest-first. Filters AND together. eventId is '' when the conversion had none.
ts
type DeliveryQuery = {
platform?: string;
ok?: boolean;
eventId?: string;
since?: number; // unix ms, inclusive
limit?: number;
};
// Each row:
{
eventId: string;
eventName: string;
platform: string;
ok: boolean;
error?: string;
deduped?: boolean;
dryRun?: boolean;
at: number;
}Your own store
InMemoryDeliveryLog is a bounded ring buffer (default 10,000). Fine in a long-lived Node process. It does not survive a restart.
For production, implement DeliveryLogStore and persist — Workers KV or a Durable Object on Cloudflare, a table anywhere else:
ts
const store: DeliveryLogStore = {
async record(r) {
await env.DELIVERIES.put(`${r.at}:${r.platform}:${r.eventId}`, JSON.stringify(r));
},
async query(filter) {
// your index / list implementation
return [];
},
};
const ads = createAdscapi(secrets, { onResult: deliverySink(store) });Notes
- The log is caller-owned. adscapi does not write to disk, KV, or a database unless you do.
deliverySinkswallows both thrown and rejectedrecord()calls.InMemoryDeliveryLogevicts the oldest row once at capacity. Pass a differentmaxto the constructor if you need a smaller buffer.
Related
- conversions.track() — the
onResulthook lives on the client - Governance — audit trail for the gate, delivery log for the send
- Webhooks — signed delivery to your own systems