A zero-dependency JavaScript client for the Metron comic book database API, with automatic handling of Metron's rate-limit scheme and full TypeScript types.
Covers the read-only (list/retrieve) surface of the API: arc, character, creator, imprint, issue, publisher, role, series, seriesType, team, universe, plus their nested sub-lists (issue_list, series_list). credit and variant are not included — the API only exposes create/update for those, no list or retrieve endpoints.
fetch)npm install shaligo
The runtime is plain JavaScript (no build step for consumers); TypeScript types are published alongside it in dist/*.d.ts and picked up automatically.
Full API reference (every resource, method, and type) is generated from source with TypeDoc and published at each push to main: see the "Deploy docs" GitHub Actions workflow. To build it locally:
npm run docs # writes static HTML to docs/
import { MetronClient } from 'shaligo';
const client = new MetronClient({ token: process.env.METRON_TOKEN });
const page = await client.series.list({ name: 'Batman' });
console.log(page.count, page.results);
const series = await client.series.get(8477);
console.log(series);
Every list() method returns a single page ({ count, next, previous, results }). Each resource also has a listAll() async generator that walks every page for you, sleeping/retrying between requests as needed:
for await (const issue of client.issue.listAll({ series_name: 'Batman' })) {
console.log(issue.id, issue.issue);
}
A few resources expose a related list of issues or series:
await client.arc.issueList(arcId, { page: 2 });
await client.character.issueList(characterId);
await client.publisher.seriesList(publisherId);
await client.series.issueList(seriesId);
await client.team.issueList(teamId);
Each has an *All() variant (arc.issueListAll(arcId), etc.) that pages through the sub-list the same way listAll() does.
role and seriesType are list-only — they have no get().
Metron enforces two independent counters per token: a short burst window and a longer sustained window. The actual limits vary by account (e.g. supporters get elevated sustained limits) and may change, so they're never hardcoded — MetronClient reads the live values from the X-RateLimit-* response headers on every request and, by default, pauses proactively before a request would exceed whichever counter is closer to its reset — sustained is checked first since it's slower to recover.
If a request still comes back 429, the client honors the Retry-After header exactly and retries (up to maxRetries, default 3) before giving up.
const client = new MetronClient({
token: process.env.METRON_TOKEN,
autoThrottle: true, // set false to disable proactive waiting
maxRetries: 3,
});
// Inspect the last known counter state without making a request:
client.getRateLimitStatus();
// => { burst: { limit, remaining, resetAt }, sustained: { limit, remaining, resetAt } }
MetronApiError — thrown for any non-2xx response; has status, body, and url.MetronRateLimitError (extends MetronApiError) — thrown when maxRetries is exhausted on repeated 429s; also has retryAfter and limitType ('burst' or 'sustained').import { MetronApiError, MetronRateLimitError } from 'shaligo';
try {
await client.series.get(999999999);
} catch (err) {
if (err instanceof MetronApiError) {
console.error(err.status, err.body);
}
}
Full API reference can be found at GitHub Pages, generated with TypeDoc and published on each push to main.
npm test # runs the suite (Node's built-in test runner + a local mock HTTP server, no real token needed)
npm run build:types # type-checks src/*.js against the JSDoc annotations and emits dist/*.d.ts
npm run docs # builds the TypeDoc reference site into docs/
Types are authored as JSDoc comments in src/*.js (see src/types.js for the response/param shapes) — there's no separate TypeScript source to keep in sync.
LGPL-2.1-only, see LICENSE.