Skip to content
adscapi

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.
  • deliverySink swallows both thrown and rejected record() calls.
  • InMemoryDeliveryLog evicts the oldest row once at capacity. Pass a different max to the constructor if you need a smaller buffer.

Related