Array of AniList media entries to sync.
The user's authentication token.
OptionalonProgress: (progress: SyncProgress) => voidOptional callback for progress updates.
OptionalabortSignal: AbortSignalOptional abort signal to cancel the sync.
OptionaldisplayOrderMediaIds: number[]Optional array of media IDs to control sync order.
OptionalonBatchComplete: (Optional callback fired after each media ID completes (batch boundary). Called with current progress state and last batch result to enable checkpoint persistence.
A promise resolving to a SyncReport object.
export async function syncMangaBatch(
mediaEntries: AniListMediaEntry[],
token: string,
onProgress?: (progress: SyncProgress) => void,
abortSignal?: AbortSignal,
displayOrderMediaIds?: number[],
onBatchComplete?: (
progress: SyncProgress,
batchResult: { mediaId: number; success: boolean; error?: string },
) => void,
): Promise<SyncReport> {
return withGroupAsync(
`[AniListSync] Batch Sync (${mediaEntries.length} entries)`,
async () => {
const errors: { mediaId: number; error: string }[] = [];
// Initialize progress
const initialProgress: SyncProgress = {
total: mediaEntries.length,
completed: 0,
successful: 0,
failed: 0,
skipped: 0,
currentEntry: null,
currentStep: null,
totalSteps: null,
rateLimited: false,
retryAfter: null,
};
if (onProgress) onProgress({ ...initialProgress });
// Use worker pool for pre-processing
const pool = getBatchSyncWorkerPool();
await pool.initialize();
try {
await pool.executeBatchSyncPreprocessing(
mediaEntries,
(
phase: string,
processed: number,
total: number,
currentMediaId?: number,
) => {
// Update progress during pre-processing phase
const progress: SyncProgress = {
...initialProgress,
completed: processed,
total: total,
};
if (currentMediaId) {
const mediaEntry = mediaEntries.find(
(e) => e.mediaId === currentMediaId,
);
if (mediaEntry) {
progress.currentEntry = {
mediaId: currentMediaId,
title: mediaEntry.title || "Unknown",
coverImage: mediaEntry.coverImage || "",
};
}
}
if (onProgress) onProgress(progress);
},
);
} catch (error) {
console.error(
"[AniListSync] ⚠️ Pre-processing failed, continuing with direct sync:",
error,
);
// Pre-processing failure is not fatal - continue with direct sync
}
// Organize entries by media ID for handling incremental sync properly
const entriesGroupedByMediaId = organizeEntriesByMediaId(mediaEntries);
// Determine processing order and unique entry count
const userOrderMediaIds = determineProcessingOrder(
displayOrderMediaIds,
entriesGroupedByMediaId,
);
const uniqueEntryCount = userOrderMediaIds.length;
// Track progress against unique media IDs rather than incremental steps
const progress: SyncProgress = {
total: uniqueEntryCount,
completed: 0,
successful: 0,
failed: 0,
skipped: 0,
currentEntry: null,
currentStep: null,
totalSteps: null,
rateLimited: false,
retryAfter: null,
};
if (onProgress) onProgress({ ...progress });
const apiCallsCompleted = { count: 0 };
// Process each media ID in order
for (const mediaId of userOrderMediaIds) {
await processMediaIdInBatch(mediaId, {
entriesGroupedByMediaId,
token,
apiCallsCompleted,
progress,
onProgress,
abortSignal,
onBatchComplete,
errors,
});
// If the caller aborted during processing, exit early
if (abortSignal?.aborted) break;
}
return generateSyncReport(mediaEntries, progress, errors);
},
);
}
Process a batch of manga updates with rate limiting and progress tracking.