PageSourceSearch

https://mtg.studio/js/data/scryfall.js

js mtg.studio collected 2026-10-09 22:17:22 UTC 1,923 bytes, 46 lines download raw bytes

1// @ts-check
2/**
3 * The one third-party data source the app talks to directly: Scryfall, used only to turn
4 * MTGO catalog ids into card names during an MTGO `.dek` import (our own card table has no
5 * MTGO ids). Lives in js/data so the UI layer never holds a URL or a `fetch` (NFR-A-4/A-7),
6 * and so the call is bounded like every other network call (NFR-C-7).
7 */
8import { withTimeout } from './api.js';
9
10const COLLECTION_URL = 'https://api.scryfall.com/cards/collection';
11/** Scryfall's documented per-request identifier cap. */
12const CHUNK = 75;
13const TIMEOUT_MS = 15_000;
14
15/**
16 * Resolve MTGO catalog ids → card names via Scryfall's batch collection endpoint, matched on
17 * `mtgo_id`. Best-effort: a failed or timed-out chunk is skipped, the rest still resolve.
18 * @param {number[]} ids
19 * @param {{ timeoutMs?: number }} [opts] the deadline is overridable so tests need not wait 15s.
20 * @returns {Promise<Map<number, string>>}
21 */
22export async function resolveMtgoIds(ids, { timeoutMs = TIMEOUT_MS } = {}) {
23    const map = new Map();
24    for (let i = 0; i < ids.length; i += CHUNK) {
25        const chunk = ids.slice(i, i + CHUNK);
26        let data;
27        try {
28            data = await withTimeout(timeoutMs, async (signal) => {
29                const r = await fetch(COLLECTION_URL, {
30                    method: 'POST',
31                    headers: { 'Content-Type': 'application/json' },
32                    body: JSON.stringify({ identifiers: chunk.map((mtgo_i
32d) => ({ mtgo_id })) }),
33                    signal,
34                });
35                return r.ok ? r.json() : null;
36            });
37        } catch {
38            data = null;
39        }
40        for (const card of data?.data || []) {
41            if (typeof card.mtgo_id === 'number') map.set(card.mtgo_id, card.name);
42            if (typeof card.mtgo_foil_id === 'number') map.set(card.mtgo_foil_id, card.name);
43        }
44    }
45    return map;
46}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.