Skip to content

Errors

@media-sdk/core uses typed errors for HTTP failures, unsupported capabilities, and invalid filters. Branch on error.code and isAbortError — never parse error.message for control flow.

For copy-paste handlers and a decision tree, see Error handling recipes. For the full failure-mode matrix (HTTP statuses, timeout, network), see Production reliability.

Installation

bash
pnpm add @media-sdk/core@^0.3.0

Error taxonomy

FailureError typecodestatus
HTTP 4xx / 5xxMediaErroroptionalset
Unsupported operation (e.g. curated on Pixabay)MediaErrorUNSUPPORTED_CAPABILITY
Unsupported search filter keyMediaErrorUNSUPPORTED_FILTER
Invalid filter value for providerMediaErrorINVALID_FILTER_VALUE
Request cancelled via AbortSignalAbortError / DOMException
Network / fetch rejectionnative Error
Invalid JSON on 2xx responsenative Error
Missing API key in ApiKeyProvidernative Error

MediaError

ts
class MediaError extends Error {
  readonly status?: number;
  readonly code?: string;
}

Import from the package entry:

ts
import { MediaError } from "@media-sdk/core";

UNSUPPORTED_CAPABILITY

Thrown when the client does not support an operation:

ts
import { ApiKeyProvider, PixabayMediaClient, MediaError } from "@media-sdk/core";

const pixabay = new PixabayMediaClient(
  new ApiKeyProvider(process.env.PIXABAY_API_KEY!),
);

try {
  await pixabay.getCuratedPhotos({ page: 1, perPage: 20 });
} catch (error) {
  if (error instanceof MediaError && error.code === "UNSUPPORTED_CAPABILITY") {
    console.error("Curated photos not available for this client");
  }
}

Guard with getCapabilities to avoid throwing in normal UI flows.

UNSUPPORTED_FILTER

Thrown when a filter key is not supported. Filters are never silently ignored — see Filters.

ts
try {
  await pixabay.searchPhotos({
    query: "nature",
    photoFilters: { size: "large" },
  });
} catch (error) {
  if (error instanceof MediaError && error.code === "UNSUPPORTED_FILTER") {
    console.error(error.message);
    // Filter "size" is not supported by this provider
  }
}

INVALID_FILTER_VALUE

Thrown when a filter key is supported but the value is invalid for that provider:

ts
try {
  await pexels.searchPhotos({
    query: "nature",
    photoFilters: { color: "grayscale" },
  });
} catch (error) {
  if (error instanceof MediaError && error.code === "INVALID_FILTER_VALUE") {
    console.error(error.message);
    // Invalid value "grayscale" for filter "color"
  }
}

HTTP errors

Provider API failures surface as MediaError with a status field:

ts
try {
  await client.getPhoto(999999999);
} catch (error) {
  if (error instanceof MediaError && error.status === 404) {
    console.error("Media not found");
  }
}

isAbortError

Cancelled search requests reject with an abort error, not MediaError:

ts
import { isAbortError } from "@media-sdk/core";

try {
  await client.searchPhotos({
    query: "nature",
    signal: controller.signal,
  });
} catch (error) {
  if (isAbortError(error)) {
    return; // expected cancellation
  }
  throw error;
}

See Cancellation for signal usage and React hook behavior.

ts
import { isAbortError, MediaError } from "@media-sdk/core";

import type { MediaClient } from "@media-sdk/core";

async function safeSearch(
  client: MediaClient,
  query: string,
) {
  try {
    return await client.searchPhotos({
      query,
      page: 1,
      perPage: 20,
    });
  } catch (error) {
    if (isAbortError(error)) {
      return null;
    }

    if (error instanceof MediaError) {
      switch (error.code) {
        case "UNSUPPORTED_FILTER":
          throw new Error("Selected filters are not supported");
        case "UNSUPPORTED_CAPABILITY":
          throw new Error("This action is not supported");
        case "INVALID_FILTER_VALUE":
          throw new Error("Invalid filter selection");
        default:
          throw new Error(
            `API error${error.status ? ` (${error.status})` : ""}: ${error.message}`,
          );
      }
    }

    throw error;
  }
}
tsx
import { isAbortError, MediaError } from "@media-sdk/core";
import { useMediaSearch } from "@media-sdk/react";

function SearchResults({ query }: { query: string }) {
  const { data, error, loading } = useMediaSearch({
    query,
    page: 1,
    perPage: 20,
    enabled: query.length > 0,
  });

  if (loading) return <p>Loading…</p>;

  if (error) {
    if (error instanceof MediaError) {
      if (error.code === "UNSUPPORTED_FILTER") {
        return <p>Filters not supported by the active provider.</p>;
      }
      return <p>API error: {error.message}</p>;
    }
    return <p>Something went wrong.</p>;
  }

  // Aborts are not surfaced as error — hooks swallow them
  return <p>{data?.items.length ?? 0} results</p>;
}

Anti-patterns

ts
// ❌ Do not branch on message text
if (error.message.includes("not supported")) { ... }

// ✅ Branch on code
if (error instanceof MediaError && error.code === "UNSUPPORTED_FILTER") { ... }
ts
// ❌ Do not assume all failures are MediaError
catch (error) {
  console.log(error.status); // may be undefined
}

// ✅ Narrow the type first
catch (error) {
  if (error instanceof MediaError) {
    console.log(error.status, error.code);
  }
}