Skip to main content
Glama
csb-index.ts3.32 kB
import path from "node:path"; import { mkdir, readFile, writeFile, stat } from "node:fs/promises"; import { CSB } from "./csb.js"; export type CsbCardIndex = { builtAtMs: number; total: number; oracleToId: Record<string, number>; }; const DEFAULT_CACHE_PATH = process.env.CSB_CARD_INDEX_PATH || path.join(process.cwd(), "cache", "csb-card-index.json"); const DEFAULT_TTL_MS = Number(process.env.CSB_CARD_INDEX_TTL_MS || 24 * 60 * 60 * 1000); // 24h let memCache: { at: number; data: CsbCardIndex } | undefined; async function ensureDirFor(filePath: string): Promise<void> { await mkdir(path.dirname(filePath), { recursive: true }); } export async function readCsbIndex(cachePath = DEFAULT_CACHE_PATH): Promise<{ data: CsbCardIndex; at: number } | null> { try { const [buf, st] = await Promise.all([readFile(cachePath, "utf8"), stat(cachePath)]); const data = JSON.parse(buf) as CsbCardIndex; return { data, at: st.mtimeMs }; } catch { return null; } } export async function writeCsbIndex(data: CsbCardIndex, cachePath = DEFAULT_CACHE_PATH): Promise<string> { await ensureDirFor(cachePath); await writeFile(cachePath, JSON.stringify(data, null, 2), "utf8"); return cachePath; } export async function buildCsbIndex(): Promise<CsbCardIndex> { const oracleToId: Record<string, number> = Object.create(null); const limit = 100; let offset = 0; let total = 0; while (true) { const page: any = await CSB.cards({ limit, offset }); const results: any[] = Array.isArray(page?.results) ? page.results : []; total = Number(page?.count ?? total); for (const c of results) { const oid = c?.oracleId; const id = c?.id; if (typeof oid === "string" && typeof id === "number") { if (!(oid in oracleToId)) oracleToId[oid] = id; } } if (!page?.next || results.length === 0) break; offset += limit; } return { builtAtMs: Date.now(), total, oracleToId }; } export async function loadCsbIndex(options?: { ttlMs?: number; force?: boolean; cachePath?: string }): Promise<CsbCardIndex> { const ttl = options?.ttlMs ?? DEFAULT_TTL_MS; const cachePath = options?.cachePath ?? DEFAULT_CACHE_PATH; const now = Date.now(); if (!options?.force && memCache && now - memCache.at < ttl) return memCache.data; if (!options?.force) { const disk = await readCsbIndex(cachePath); if (disk && now - disk.at < ttl) { memCache = { at: disk.at, data: disk.data }; return disk.data; } } const data = await buildCsbIndex(); memCache = { at: now, data }; await writeCsbIndex(data, cachePath).catch(() => {}); return data; } export async function lookupCsbIdsByOracle(oracleIds: string[], options?: { ttlMs?: number; force?: boolean; cachePath?: string }): Promise<{ found: Record<string, number>; missing: string[] }> { const idx = await loadCsbIndex(options); const found: Record<string, number> = {}; const missing: string[] = []; for (const oid of oracleIds) { const id = idx.oracleToId[oid]; if (typeof id === "number") found[oid] = id; else missing.push(oid); } return { found, missing }; }

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/latte-chan/scryfall-connector'

If you have feedback or need assistance with the MCP directory API, please join our Discord server