• Batch search for multiple manga titles in a single GraphQL request. More efficient than individual searches when matching multiple titles.

    Parameters

    • searches: { alias: string; title: string; index: number }[]

      Array of search queries with alias, title, and index.

    • options: {
          token?: string;
          perPage?: number;
          abortSignal?: AbortSignal;
          noRetry?: boolean;
      } = {}

      Configuration including auth token, page size, abort signal.

    Returns Promise<Map<string, { media: AniListManga[]; index: number; title: string }>>

    Promise resolving to map of search results keyed by alias.

    export async function batchSearchManga(
    searches: Array<{ alias: string; title: string; index: number }>,
    options: {
    token?: string;
    perPage?: number;
    abortSignal?: AbortSignal;
    noRetry?: boolean;
    } = {},
    ): Promise<
    Map<
    string,
    {
    media: AniListManga[];
    index: number;
    title: string;
    }
    >
    > {
    return withGroupAsync(
    `[AniListClient] Batch Search (${searches.length} queries)`,
    async () => {
    if (searches.length === 0) {
    return new Map();
    }

    const { token, perPage = 10, abortSignal, noRetry } = options;

    console.info(
    `[AniListClient] 🚀 Batch searching ${searches.length} manga titles`,
    );

    // Build the batched query dynamically
    const queryParts: string[] = [];

    for (const { alias, title } of searches) {
    // Sanitize the title for use in GraphQL (escape quotes)
    const sanitizedTitle = JSON.stringify(title).slice(1, -1);

    queryParts.push(`
    ${alias}: Page(page: 1, perPage: ${perPage}) {
    pageInfo {
    total
    currentPage
    lastPage
    hasNextPage
    perPage
    }
    media(type: MANGA, search: "${sanitizedTitle}", format_not_in: [NOVEL]) {
    id
    title {
    romaji
    english
    native
    }
    synonyms
    format
    status
    chapters
    volumes
    description
    genres
    tags {
    id
    name
    category
    }
    countryOfOrigin
    source
    staff {
    edges {
    role
    node {
    name {
    full
    }
    }
    }
    }
    coverImage {
    large
    medium
    }
    mediaListEntry {
    id
    status
    progress
    score
    private
    }
    isAdult
    }
    }`);
    }

    const batchedQuery = `
    query BatchSearchManga {
    ${queryParts.join("\n")}
    }
    `;

    type BatchQueryResponse = Record<
    string,
    {
    media?: AniListManga[];
    pageInfo?: unknown;
    }
    >;

    try {
    // Execute the batched request
    const response = await request<BatchQueryResponse>(
    batchedQuery,
    {}, // No variables needed - all values are in the query
    token,
    abortSignal,
    true, // Bypass cache for batch requests
    noRetry,
    );

    console.debug(`[AniListClient] 🔍 Batch search response:`, response);

    // Validate response structure
    if (!response?.data) {
    console.error(
    `[AniListClient] ❌ Invalid API response for batch search:`,
    response,
    );
    throw new Error(`Invalid API response: missing data property`);
    }

    // Handle nested data structure and type assertion
    const responseData = (response.data.data ??
    response.data) as unknown as BatchQueryResponse;

    // Process results into a map
    const results = new Map<
    string,
    {
    media: AniListManga[];
    index: number;
    title: string;
    }
    >();

    let totalResults = 0;
    for (const { alias, index, title } of searches) {
    const aliasData = responseData[alias];
    if (aliasData?.media?.length) {
    results.set(alias, {
    media: aliasData.media,
    index,
    title,
    });
    totalResults += aliasData.media.length;
    } else {
    // Return empty array if no results
    results.set(alias, {
    media: [],
    index,
    title,
    });
    }
    }

    console.info(
    `[AniListClient] ✅ Batch search complete: ${totalResults} total results for ${searches.length} queries`,
    );

    return results;
    } catch (error) {
    captureError(
    ErrorType.NETWORK,
    `Error in batch search for ${searches.length} queries`,
    error instanceof Error ? error : new Error(String(error)),
    { queryCount: searches.length },
    );

    // Return empty results for all searches on error
    const emptyResults = new Map<
    string,
    {
    media: AniListManga[];
    index: number;
    title: string;
    }
    >();

    for (const { alias, index, title } of searches) {
    emptyResults.set(alias, {
    media: [],
    index,
    title,
    });
    }

    return emptyResults;
    }
    },
    );
    }