export const CANVAS_IMAGE_TASK_QUEUE_CONFIG_KEY = "canvas_image_task_queue_config"; export const MIN_CANVAS_IMAGE_TASK_CONCURRENCY = 1; export const DEFAULT_CANVAS_IMAGE_TASK_CONCURRENCY = 3; export const MAX_CANVAS_IMAGE_TASK_CONCURRENCY = 100; export const MIN_CANVAS_IMAGE_BATCH_DISPATCH_INTERVAL_MS = 0; export const DEFAULT_CANVAS_IMAGE_BATCH_DISPATCH_INTERVAL_MS = 0; export const MAX_CANVAS_IMAGE_BATCH_DISPATCH_INTERVAL_MS = 60 * 1000; export interface CanvasImageTaskQueueConfig { concurrency: number; batchDispatchIntervalMs: number; } function clampConcurrency(value: number) { return Math.min( MAX_CANVAS_IMAGE_TASK_CONCURRENCY, Math.max(MIN_CANVAS_IMAGE_TASK_CONCURRENCY, Math.floor(value)) ); } function clampBatchDispatchIntervalMs(value: number) { return Math.min( MAX_CANVAS_IMAGE_BATCH_DISPATCH_INTERVAL_MS, Math.max(MIN_CANVAS_IMAGE_BATCH_DISPATCH_INTERVAL_MS, Math.floor(value)) ); } function parseConcurrencyCandidate(value: unknown): number | null { if (typeof value === "number" && Number.isFinite(value)) { return clampConcurrency(value); } if (typeof value === "string" && value.trim()) { const parsed = Number.parseInt(value.trim(), 10); if (Number.isFinite(parsed)) { return clampConcurrency(parsed); } } return null; } function parseBatchDispatchIntervalCandidate( value: unknown, options?: { treatAsSeconds?: boolean } ): number | null { const normalize = (candidate: number) => clampBatchDispatchIntervalMs( options?.treatAsSeconds ? candidate * 1000 : candidate ); if (typeof value === "number" && Number.isFinite(value)) { return normalize(value); } if (typeof value === "string" && value.trim()) { const parsed = Number.parseFloat(value.trim()); if (Number.isFinite(parsed)) { return normalize(parsed); } } return null; } export function normalizeCanvasImageTaskQueueConfig( value: unknown, options?: { envConcurrency?: unknown; envBatchDispatchIntervalMs?: unknown } ): CanvasImageTaskQueueConfig { const raw = value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : {}; const concurrencyCandidates = [ raw.concurrency, raw.maxConcurrency, raw.maxConcurrent, typeof value === "number" || typeof value === "string" ? value : undefined, options?.envConcurrency, ]; let concurrency = DEFAULT_CANVAS_IMAGE_TASK_CONCURRENCY; for (const candidate of concurrencyCandidates) { const parsed = parseConcurrencyCandidate(candidate); if (parsed !== null) { concurrency = parsed; break; } } const intervalCandidates = [ raw.batchDispatchIntervalMs, raw.dispatchIntervalMs, raw.batchDispatchDelayMs, raw.batchDispatchInterval, raw.dispatchInterval, options?.envBatchDispatchIntervalMs, ]; const intervalSecondsCandidates = [ raw.batchDispatchIntervalSeconds, raw.dispatchIntervalSeconds, ]; let batchDispatchIntervalMs = DEFAULT_CANVAS_IMAGE_BATCH_DISPATCH_INTERVAL_MS; for (const candidate of intervalCandidates) { const parsed = parseBatchDispatchIntervalCandidate(candidate); if (parsed !== null) { batchDispatchIntervalMs = parsed; break; } } if (batchDispatchIntervalMs === DEFAULT_CANVAS_IMAGE_BATCH_DISPATCH_INTERVAL_MS) { for (const candidate of intervalSecondsCandidates) { const parsed = parseBatchDispatchIntervalCandidate(candidate, { treatAsSeconds: true, }); if (parsed !== null) { batchDispatchIntervalMs = parsed; break; } } } return { concurrency, batchDispatchIntervalMs, }; }