npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

pokemon-tcg-deck-parser

v1.1.0

Published

Parse and export Pokémon TCG Live and Limitless decklists, and resolve cards via the TCGdex API.

Readme

pokemon-tcg-deck-parser

Parse and export Pokémon TCG Live (ptcgl) and Limitless (limitless) decklists, then look up the cards through the TCGdex TypeScript SDK.

The library is built to be reused across projects: parsing is synchronous and local, lookups are cached, and the API is queried per unique set rather than once per card.

Install

npm i pokemon-tcg-deck-parser

Usage

import { DeckParser, parseDecklist, exportDecklist, detectFormat } from "pokemon-tcg-deck-parser";

const parser = new DeckParser({
  lang: "en",
  cacheTTL: 60 * 60 * 24, // 24h SDK cache
  concurrency: 4,         // max parallel TCGdex requests
  hydrate: "resume",      // or "full" for complete card objects
});

const deck = await parser.parseAndResolve(`
Pokémon: 2
3 Chien-Pao ex PAL 61
1 Nest Ball SVI 181
Energy: 1
2 Basic {W} Energy SVE 11
Total Cards: 6
`);

deck.cards.forEach((entry) => {
  console.log(entry.quantity, entry.name, entry.tcgdexId, entry.card?.image);
});

// Write the same deck back out. Defaults to the parsed format.
const limitless = exportDecklist(deck, { format: "limitless" });
const ptcgl = parser.export(deck, { format: "ptcgl" });

Share one parser instance across a process so set lists, abbreviation maps, and SDK responses stay cached.

parser.clearCache(); // drop set/name/category caches and the SDK response cache

Debug web app

npm run dev

Serves a small tester at http://localhost:5174. Paste a decklist (or load a sample), parse locally, or resolve through TCGdex and inspect cards, warnings, and debug JSON.

Parse without hitting the API

const parsed = parseDecklist(text); // or parser.parse(text)
const format = detectFormat(text);  // "ptcgl" | "limitless" | "unknown"

Custom TCGdex endpoint

Point the parser at a self-hosted TCGdex instance, a mirror, or a caching proxy. The SDK appends the language segment (/en, /fr, …), so pass only the base URL.

const parser = new DeckParser({
  endpoint: "https://tcgdex.example.com/v2", // defaults to https://api.tcgdex.net/v2
});

endpoint also applies when you pass your own tcgdex client, overriding whatever endpoint it was configured with.

Reuse an existing TCGdex client

import TCGdex from "@tcgdex/sdk";
import { DeckParser } from "pokemon-tcg-deck-parser";

const tcgdex = new TCGdex("en");
tcgdex.setCacheTTL(60 * 60 * 24);

const parser = new DeckParser({ tcgdex, hydrate: "full" });

Formats

Both formats are quantity + name, optionally followed by set code and collector number.

PTCGL export

Pokémon: 10
3 Chien-Pao ex PAL 61
Trainer: 18
1 Switch SVI 194 PH
Energy: 3
11 Basic {W} Energy Energy 29
2 Basic {W} Energy SVE 11 PH
Total Cards: 60

Handles PH foil markers, {W} energy shorthand, and PTCGL's pseudo Energy set.

Limitless export

Copy as text from the Limitless deck builder:

Pokémon: 13
3 Charcadet SSP 32
Trainer: 27
4 Nest Ball SVI 181
Energy: 20
10 Fighting Energy SVE 22

Pass { format: "ptcgl" } or { format: "limitless" } to skip auto-detection.

Export

exportDecklist (and parser.export) write a parsed deck in either format. Section headers are the number of cards in that section. PTCGL also adds a Total Cards: line and spells basic Energy as Basic {W} Energy. Limitless spells the same card Water Energy and omits the total line. Special Energy (Jet Energy) and foil markers (PH) are kept in both.

const parsed = parseDecklist(text);
exportDecklist(parsed);                        // same format as parsed.format
exportDecklist(parsed, { format: "limitless" });
parser.export(parsed, { format: "ptcgl" });

How lookups stay cheap

  1. Parse the list locally.
  2. Collect unique set codes.
  3. Resolve those codes in batch through TCGdex (abbreviation.official and tcgOnline), with a small alias table for promo codes (PR-SW → swshp, SVE → sve, …).
  4. Fetch each unique set once. Set payloads already include every card brief (id, localId, name, image), so a 60-card deck that uses 10 sets costs about 10 cached set requests — not 60 card requests.
  5. Match collector numbers locally (padding, TG/GG gallery sets, promo IDs). RR is treated as both Rising Rivals and Team Rocket Returns and disambiguated by card name; Limitless TRR maps to Team Rocket Returns.
  6. Name search is only used for leftovers (basic Energy-set lines, ALT prints, name-only rows).
  7. hydrate: "full" then fetches each unique matched card id, still through the SDK cache and a concurrency cap.
  8. Confirm each matched card's category. Set briefs do not include one, and a missing or wrong section divider can file trainers under Pokémon. Resolved ids are checked with filtered card-list queries — one query per category (Pokemon, Trainer, Energy) per chunk of ids, not one request per card. When the API disagrees, the card's category is corrected and a warning is recorded. hydrate: "full" already returns category, so those decks skip the extra queries.

The SDK cache TTL defaults to 24 hours. Repeat parses of overlapping decks should mostly be cache hits. Call parser.clearCache() when you need a forced refresh.

Result shape

{
  format: "ptcgl",
  totalCards: 60,
  cards: [
    {
      quantity: 3,
      name: "Chien-Pao ex",
      setCode: "PAL",
      number: "61",
      category: "pokemon",
      tcgdexId: "sv02-061",
      card: { id: "sv02-061", localId: "061", name: "Chien-Pao ex", image: "..." }
    }
  ],
  unresolved: [],
  warnings: []
}

Unresolved rows stay in cards with unresolvedReason, and are also listed on unresolved.

Publishing

Releases are published to npm automatically via GitHub Actions when a GitHub Release is created. The workflow uses npm trusted publishing (OIDC)—no long-lived npm token is stored in the repo.

One-time setup on npmjs.com → package Settings → Trusted Publisher:

| Field | Value | | --- | --- | | Organization or user | osamc | | Repository | pokemon-tcg-deck-parser | | Workflow filename | publish.yml | | Environment name | (leave blank) |

Then create a GitHub Release whose tag is a semver version (1.1.0 or v1.1.0). The workflow sets package.json to that version and runs npm publish.

License

MIT