Skip to content
adscapi

Errors

Three typed error classes. track() captures per-destination failures into DestinationResult.error and never aborts the fan-out. Use instanceof and .code / .platform when you catch.

Hierarchy

ts
import {
  AdscapiError,
  PlatformDispatchError,
  PlatformHttpError,
} from 'adscapi';

// Base
class AdscapiError extends Error {
  readonly code: string;
  readonly platform?: string;
  // name = 'AdscapiError'
}

// A single platform's dispatch failed. Captured per-destination by track() —
// never aborts the fan-out to the other platforms.
class PlatformDispatchError extends AdscapiError {
  readonly cause: unknown;
  // code = 'platform_dispatch_failed'
  // message = `[${platform}] ${String(cause)}`
  // name = 'PlatformDispatchError'
}

// Non-2xx HTTP response from a platform endpoint. Carries status so retry
// logic can decide retryable (5xx/429) vs terminal (4xx).
class PlatformHttpError extends AdscapiError {
  readonly status: number;
  readonly body?: string;
  // code = 'platform_http_error'
  // message = `[${platform}] HTTP ${status}`
  // name = 'PlatformHttpError'
}

Fields

ClasscodeExtra fields
AdscapiErrorstring (caller-supplied)platform?: string
PlatformDispatchErrorplatform_dispatch_failedplatform: string, cause: unknown
PlatformHttpErrorplatform_http_errorplatform: string, status: number, body?: string

How track() uses them

Inside conversions.track(), each destination is wrapped:

ts
try {
  await withRetry(() => p.dispatch(event, secrets));
  result = { platform: p.key, ok: true };
} catch (error) {
  const wrapped =
    error instanceof PlatformDispatchError
      ? error
      : new PlatformDispatchError(p.key, error);
  result = { platform: p.key, ok: false, error: wrapped.message };
}
// → Promise settles with DestinationResult[]; the other platforms keep running.

Example

ts
import {
  createAdscapi,
  AdscapiError,
  PlatformDispatchError,
  PlatformHttpError,
  uploadOffline,
  sendWebhook,
} from 'adscapi';

const ads = createAdscapi();

// 1. track() — per-destination errors are strings on the result, not throws
const results = await ads.conversions.track(event);
for (const r of results) {
  if (!r.ok) {
    // r.error is wrapped.message from PlatformDispatchError
    console.error(r.platform, r.error);
  }
}

// 2. When you catch a thrown AdscapiError (offline, webhooks, direct dispatchers)
try {
  await uploadOffline({ platform: 'meta', csv });
} catch (err) {
  if (err instanceof PlatformHttpError) {
    console.error(err.platform, err.status, err.body);
    // retry.ts treats 5xx / 429 as retryable, 4xx as terminal
  } else if (err instanceof PlatformDispatchError) {
    console.error(err.platform, err.cause);
  } else if (err instanceof AdscapiError) {
    console.error(err.code, err.message);
  } else {
    throw err;
  }
}

// 3. sendWebhook throws a plain Error on non-2xx (not AdscapiError)
try {
  await sendWebhook({ url, secret, payload });
} catch (err) {
  // Error: Webhook delivery failed: HTTP 502
}

// 4. Narrow on .code when instanceof crosses bundle boundaries
function handle(err: unknown) {
  if (err instanceof AdscapiError) {
    switch (err.code) {
      case 'platform_dispatch_failed':
      case 'platform_http_error':
        return { retry: err instanceof PlatformHttpError && err.status >= 500 };
      default:
        return { retry: false };
    }
  }
}

Related