@pokemontcgapi/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 pokemontcgapi.com
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,185 @@
1
+ # @pokemontcgapi/sdk
2
+
3
+ TypeScript client for the Pokémon TCG API at [pokemontcgapi.com](https://pokemontcgapi.com) —
4
+ **615 sets and 52,337 cards** across three print regions (379 Japanese, 176 international, 60
5
+ Simplified Chinese), card names in six languages, 399 illustrators, images, and prices that state
6
+ their source, basis, grade and sample size.
7
+
8
+ **Zero runtime dependencies.** Uses the global `fetch`, so it runs unchanged on Node ≥ 20, Bun, Deno,
9
+ Cloudflare Workers and in the browser.
10
+
11
+ Unofficial. Not produced, endorsed, supported by or affiliated with Nintendo, Creatures Inc.,
12
+ GAME FREAK inc. or The Pokémon Company International. Pokémon and all related marks are trademarks of
13
+ their respective owners.
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ npm install @pokemontcgapi/sdk
19
+ ```
20
+
21
+ ## Use
22
+
23
+ ```ts
24
+ import { PokemonTcgApi } from '@pokemontcgapi/sdk';
25
+
26
+ const client = new PokemonTcgApi({ apiKey: process.env.PTCG_API_KEY });
27
+
28
+ const card = await client.cards.get('base1-4', { include: ['prices'] });
29
+ console.log(card.id, card.name, card.index_eur);
30
+ // bs-4 Charizard 561.84
31
+ ```
32
+
33
+ `base1-4` and `bs-4` both resolve: the id is the printed coordinate — set code, dash, collector
34
+ number — and the alternate legacy id resolves on the same route, so a catalogue you already have does
35
+ not start with a matching problem.
36
+
37
+ ### Pagination that you never have to think about
38
+
39
+ Every list method returns a `Page`, which is also an `AsyncIterable`. Iterating it follows
40
+ `links.next` for you:
41
+
42
+ ```ts
43
+ for await (const set of await client.sets.list({ region: 'JP' })) {
44
+ console.log(set.code, set.name, set.release_date);
45
+ }
46
+ ```
47
+
48
+ The cursor carries a signature of the sort order, so it must never be reconstructed by hand — the SDK
49
+ follows the URL the API returned, which is the failure mode this avoids. `.toArray({ max })` requires
50
+ an explicit ceiling, because the catalogue is large enough that an unbounded materialisation is a
51
+ mistake rather than a choice.
52
+
53
+ ### One call for a hundred cards
54
+
55
+ ```ts
56
+ const { data, requested, found } = await client.cards.batch(['bs-4', 'sv3-001'], {
57
+ select: ['id', 'name', 'index_eur'],
58
+ });
59
+ ```
60
+
61
+ Ids that do not exist are omitted rather than reported one by one — compare `requested` with `found`.
62
+
63
+ ### Japanese, and the other five locales
64
+
65
+ ```ts
66
+ const page = await client.sets.cards('sv8', { lang: 'ja', limit: 1 });
67
+ console.log(page.data[0]?.name); // タマタマ
68
+ ```
69
+
70
+ `lang` replaces the `name` field itself and falls back to English where a translation is missing.
71
+ Locales: `en`, `ja`, `fr`, `de`, `es`, `it`.
72
+
73
+ ### Conditional requests are free
74
+
75
+ ```ts
76
+ const client = new PokemonTcgApi({ cache: 'etag' });
77
+ ```
78
+
79
+ Every collection carries a strong ETag. With the cache on, the client stores it and replays a `304`
80
+ without a body — no quota consumed. A mirror that re-syncs often pays only for what changed.
81
+
82
+ ### A photo instead of an id
83
+
84
+ ```ts
85
+ const { data } = await client.vision.identify(file, { set: 'sv3' });
86
+
87
+ // Read `decision` before `id`. Always.
88
+ switch (data.decision) {
89
+ case 'match':
90
+ // One candidate, close, and clear of the next.
91
+ add(data.id!);
92
+ break;
93
+ case 'ambiguous':
94
+ // Two printings share this illustration. `data.id` is null on purpose.
95
+ showPicker(data.candidates);
96
+ break;
97
+ case 'no_match':
98
+ askForABetterPhoto();
99
+ }
100
+ ```
101
+
102
+ Reprints and regional twins share their artwork, so artwork alone cannot name a printing — not here
103
+ and not anywhere. The endpoint returns candidates with a `distance` (0–512, lower is closer; real
104
+ matches land well under 150) and refuses to pick when two are within a few bits of each other.
105
+ Passing `set` or `region` when your workflow knows them is what resolves the tie.
106
+
107
+ It costs 25 credits a call against 1 for a lookup: it is the whole image index answering, not a row
108
+ being read. Do not put it in a loop.
109
+
110
+ ### Errors you can branch on
111
+
112
+ ```ts
113
+ import { NotFoundError, RateLimitedError, QuotaExceededError } from '@pokemontcgapi/sdk';
114
+
115
+ try {
116
+ await client.cards.get('nope-1');
117
+ } catch (error) {
118
+ if (error instanceof NotFoundError) { /* ... */ }
119
+ if (error instanceof RateLimitedError) { /* error.retryAfter */ }
120
+ if (error instanceof QuotaExceededError) { /* retrying will never help */ }
121
+ }
122
+ ```
123
+
124
+ Every error carries `code`, `status`, `details` and `requestId` — quote the request id in a support
125
+ message, it is the only thing that can be looked up. Retries use exponential backoff with full
126
+ jitter on 429, 5xx and network failures, honour `Retry-After`, and never retry a quota exhaustion.
127
+
128
+ ## What this API does not have
129
+
130
+ Stated up front so you find out here rather than three days into an integration:
131
+
132
+ - **No Korean cards.** Zero `KR` sets, zero `ko` translations. Both are modelled in the schema and
133
+ carry no data.
134
+ - **No card game text.** `attacks`, `abilities`, `weaknesses`, `resistances`, `subtypes`,
135
+ `retreat_cost`, `rules`, `flavor_text` and `legalities` are empty for every card; `types` and
136
+ `national_pokedex_numbers` are populated only on part of the Scarlet & Violet era. The types in
137
+ this package say so on each field. If you are building a deck checker or a rules engine, this is
138
+ not the data source you need.
139
+
140
+ What it does have: the printing itself — set, number, rarity, region, release date, illustrator,
141
+ image, marketplace ids, six-language names — and prices.
142
+
143
+ ## Prices
144
+
145
+ ```ts
146
+ const card = await client.cards.get('base1-4', { include: ['prices'] });
147
+ for (const price of card.prices ?? []) {
148
+ console.log(price.source, price.basis, price.price, price.currency, price.as_of, price.sample_n);
149
+ }
150
+ ```
151
+
152
+ There is no printing filter: first edition, holofoil and graded rows come back together, so read
153
+ `printing`, `condition` and `grading` per row. `basis` separates `GUIDE` (published upstream) from
154
+ `DERIVED` (computed by us). `PTCG_INDEX` is a composite index in EUR carrying `sample_n`, and it is
155
+ also on every card row as `index_eur`, so a list already has a comparable number without a second
156
+ request per card.
157
+
158
+ ## Also available
159
+
160
+ - **MCP server** for agents: [`@pokemontcgapi/mcp`](https://www.npmjs.com/package/@pokemontcgapi/mcp) — [source](https://github.com/pokemontcgapi/mcp-server)
161
+ - **Docs**: <https://pokemontcgapi.com/docs>
162
+ - **Coverage, measured live**: <https://pokemontcgapi.com/coverage>
163
+
164
+ ## Build from source
165
+
166
+ ```bash
167
+ npm ci
168
+ npm run typecheck
169
+ npm run build
170
+ ```
171
+
172
+ Node >= 20. No test suite lives here yet: what CI enforces is that the package
173
+ typechecks and builds on both Node 20 and Node 22, and that `npm pack` produces
174
+ the file list the registry is meant to receive.
175
+
176
+ This package is developed inside the private monorepo that runs
177
+ [pokemontcgapi.com](https://pokemontcgapi.com) and mirrored here on each release,
178
+ so a merged pull request travels back by hand rather than by merge button. That
179
+ is not a reason to send patches elsewhere — open the issue or the PR here, it is
180
+ the address that gets read.
181
+
182
+ ## Licence
183
+
184
+ MIT. Data served by the API carries per-source redistribution terms — see
185
+ <https://pokemontcgapi.com/legal/attribution>.
@@ -0,0 +1,99 @@
1
+ import type { Collection } from './types.js';
2
+ /**
3
+ * Il trasporto.
4
+ *
5
+ * Nessuna dipendenza a runtime: `fetch` globale, che c'e' su Node 20+, Bun,
6
+ * Deno, i Workers e i browser. Un client HTTP portato dentro il pacchetto
7
+ * costerebbe piu' della funzione che sostituisce, e ogni sua CVE diventerebbe
8
+ * nostra.
9
+ */
10
+ export interface ClientOptions {
11
+ /** Se assente si legge `PTCG_API_KEY` dall'ambiente, dove esiste. */
12
+ readonly apiKey?: string;
13
+ readonly baseUrl?: string;
14
+ /** Millisecondi per singolo tentativo, non per l'operazione intera. */
15
+ readonly timeout?: number;
16
+ /** Tentativi RIPETUTI, oltre al primo. 0 disattiva. */
17
+ readonly maxRetries?: number;
18
+ /** Iniettabile per i test e per gli ambienti che avvolgono fetch. */
19
+ readonly fetch?: typeof globalThis.fetch;
20
+ /**
21
+ * Ricorda gli ETag e rispedisce `If-None-Match`.
22
+ *
23
+ * Vale la pena accenderlo: un 304 non ha corpo e non consuma quota, quindi
24
+ * un mirror che risincronizza spesso paga solo le pagine cambiate. La cache
25
+ * e' in memoria e per-istanza: non sopravvive al processo, di proposito —
26
+ * una cache su disco dentro un SDK e' una sorgente di bug che il chiamante
27
+ * non puo' ispezionare.
28
+ */
29
+ readonly cache?: 'none' | 'etag';
30
+ readonly userAgent?: string;
31
+ }
32
+ export declare function serializeParams(params: Record<string, unknown> | undefined): URLSearchParams;
33
+ export declare class HttpClient {
34
+ readonly baseUrl: string;
35
+ private readonly apiKey;
36
+ private readonly timeout;
37
+ private readonly maxRetries;
38
+ private readonly doFetch;
39
+ private readonly userAgent;
40
+ private readonly etags;
41
+ constructor(options?: ClientOptions);
42
+ /** URL assoluto da un path applicativo piu' i parametri. */
43
+ url(path: string, params?: Record<string, unknown>): string;
44
+ get<T>(path: string, params?: Record<string, unknown>): Promise<T>;
45
+ /**
46
+ * POST con un corpo.
47
+ *
48
+ * Non passa dalla cache ETag e non viene mai ritentato su un errore di rete:
49
+ * il ritentativo automatico e' sicuro solo su richieste idempotenti, e una
50
+ * POST che potrebbe essere arrivata a destinazione non lo e'. Un 429 con
51
+ * `Retry-After` resta ritentabile perche' li' sappiamo che non e' stata
52
+ * eseguita.
53
+ */
54
+ post<T>(path: string, body: BodyInit, contentType?: string, params?: Record<string, unknown>): Promise<T>;
55
+ /**
56
+ * Segue un URL gia' costruito dall'API (`links.next`).
57
+ *
58
+ * Esiste come metodo pubblico perche' il cursore porta la firma
59
+ * dell'ordinamento: ricostruire l'URL a mano e rimetterci dentro il cursore
60
+ * e' esattamente cio' che l'API rifiuta con `INVALID_CURSOR`.
61
+ */
62
+ follow<T>(absoluteUrl: string): Promise<T>;
63
+ private request;
64
+ private attempt;
65
+ /**
66
+ * Un 5xx puo' arrivare da un proxy davanti all'API, quindi in HTML: se il
67
+ * corpo non e' il nostro envelope si costruisce un errore comunque, invece di
68
+ * far esplodere il parser e nascondere lo status vero.
69
+ */
70
+ private readErrorBody;
71
+ }
72
+ /**
73
+ * Una pagina che e' anche un iteratore.
74
+ *
75
+ * `for await (const card of client.cards.search(...))` cammina l'intera
76
+ * collezione seguendo `links.next`, senza che il chiamante veda mai un cursore.
77
+ * E' il motivo per cui vale la pena usare l'SDK invece di `fetch`: la paginazione
78
+ * a cursore e' corretta ma noiosa, ed e' il punto in cui le integrazioni scritte
79
+ * a mano perdono righe.
80
+ */
81
+ export declare class Page<T> implements AsyncIterable<T> {
82
+ readonly data: readonly T[];
83
+ readonly meta: Collection<T>['meta'];
84
+ private readonly nextUrl;
85
+ private readonly http;
86
+ constructor(http: HttpClient, body: Collection<T>);
87
+ get hasMore(): boolean;
88
+ nextPage(): Promise<Page<T> | null>;
89
+ [Symbol.asyncIterator](): AsyncIterator<T>;
90
+ /**
91
+ * Materializza in un array. `max` e' OBBLIGATORIO: il catalogo ha oltre
92
+ * 52.000 carte, e un `.toArray()` senza tetto e' il modo piu' rapido di
93
+ * riempire la memoria di un processo per sbaglio.
94
+ */
95
+ toArray({ max }: {
96
+ max: number;
97
+ }): Promise<T[]>;
98
+ }
99
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;;;;;;GAOG;AAEH,MAAM,WAAW,aAAa;IAC5B,qEAAqE;IACrE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,uEAAuE;IACvE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,uDAAuD;IACvD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,qEAAqE;IACrE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IACzC;;;;;;;;OAQG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACjC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAuCD,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAAG,eAAe,CAgB5F;AAED,qBAAa,UAAU;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAC5C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;IAClD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsD;gBAEhE,OAAO,GAAE,aAAkB;IAUvC,4DAA4D;IAC5D,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM;IAKrD,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAIxE;;;;;;;;OAQG;IACG,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAI/G;;;;;;OAMG;IACG,MAAM,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;YAIlC,OAAO;YAiBP,OAAO;IA0DrB;;;;OAIG;YACW,aAAa;CAY5B;AAED;;;;;;;;GAQG;AACH,qBAAa,IAAI,CAAC,CAAC,CAAE,YAAW,aAAa,CAAC,CAAC,CAAC;IAC9C,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAa;gBAEtB,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC;IAOjD,IAAI,OAAO,IAAI,OAAO,CAErB;IAEK,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAMlC,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC;IAQjD;;;;OAIG;IACG,OAAO,CAAC,EAAE,GAAG,EAAE,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;CAQtD"}
package/dist/client.js ADDED
@@ -0,0 +1,247 @@
1
+ import { ApiConnectionError, ApiTimeoutError, PokemonTcgApiError, QuotaExceededError, RateLimitedError, ServerError, toApiError, } from './errors.js';
2
+ const DEFAULT_BASE_URL = 'https://api.pokemontcgapi.com';
3
+ const DEFAULT_TIMEOUT = 30_000;
4
+ const DEFAULT_MAX_RETRIES = 2;
5
+ /** Errore di rete o 5xx/429: e' l'unico insieme su cui riprovare ha senso. */
6
+ function isRetryable(error) {
7
+ if (error instanceof QuotaExceededError)
8
+ return false;
9
+ if (error instanceof RateLimitedError)
10
+ return true;
11
+ if (error instanceof ServerError)
12
+ return true;
13
+ if (error instanceof ApiConnectionError)
14
+ return true;
15
+ if (error instanceof PokemonTcgApiError)
16
+ return error.status === 408;
17
+ return false;
18
+ }
19
+ /**
20
+ * Backoff esponenziale con jitter pieno.
21
+ *
22
+ * Il jitter non e' un dettaglio: senza, mille client che prendono lo stesso 429
23
+ * riprovano tutti nello stesso millisecondo e ricostruiscono la coda che
24
+ * stavano cercando di far smaltire.
25
+ */
26
+ function backoffMs(attempt, retryAfter) {
27
+ if (retryAfter !== undefined)
28
+ return Math.min(retryAfter * 1000, 60_000);
29
+ const ceiling = Math.min(500 * 2 ** attempt, 8_000);
30
+ return Math.random() * ceiling;
31
+ }
32
+ function sleep(ms) {
33
+ return new Promise((resolve) => setTimeout(resolve, ms));
34
+ }
35
+ function readEnv(name) {
36
+ // `process` non esiste nei browser ne' nei Workers: si guarda senza assumerlo.
37
+ const proc = globalThis.process;
38
+ return proc?.env?.[name];
39
+ }
40
+ export function serializeParams(params) {
41
+ const search = new URLSearchParams();
42
+ if (!params)
43
+ return search;
44
+ for (const [key, value] of Object.entries(params)) {
45
+ if (value === undefined || value === null)
46
+ continue;
47
+ // Gli array (select, include, ids) viaggiano come lista separata da virgole,
48
+ // che e' la forma che l'API accetta — non come chiavi ripetute.
49
+ if (Array.isArray(value)) {
50
+ if (value.length === 0)
51
+ continue;
52
+ search.set(key, value.join(','));
53
+ continue;
54
+ }
55
+ search.set(key, String(value));
56
+ }
57
+ return search;
58
+ }
59
+ export class HttpClient {
60
+ baseUrl;
61
+ apiKey;
62
+ timeout;
63
+ maxRetries;
64
+ doFetch;
65
+ userAgent;
66
+ etags;
67
+ constructor(options = {}) {
68
+ this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, '');
69
+ this.apiKey = options.apiKey ?? readEnv('PTCG_API_KEY');
70
+ this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
71
+ this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
72
+ this.doFetch = options.fetch ?? globalThis.fetch.bind(globalThis);
73
+ this.userAgent = options.userAgent ?? '@pokemontcgapi/sdk';
74
+ this.etags = options.cache === 'etag' ? new Map() : null;
75
+ }
76
+ /** URL assoluto da un path applicativo piu' i parametri. */
77
+ url(path, params) {
78
+ const search = serializeParams(params).toString();
79
+ return `${this.baseUrl}${path}${search === '' ? '' : `?${search}`}`;
80
+ }
81
+ async get(path, params) {
82
+ return this.request(this.url(path, params));
83
+ }
84
+ /**
85
+ * POST con un corpo.
86
+ *
87
+ * Non passa dalla cache ETag e non viene mai ritentato su un errore di rete:
88
+ * il ritentativo automatico e' sicuro solo su richieste idempotenti, e una
89
+ * POST che potrebbe essere arrivata a destinazione non lo e'. Un 429 con
90
+ * `Retry-After` resta ritentabile perche' li' sappiamo che non e' stata
91
+ * eseguita.
92
+ */
93
+ async post(path, body, contentType, params) {
94
+ return this.attempt(this.url(path, params), { method: 'POST', body, contentType });
95
+ }
96
+ /**
97
+ * Segue un URL gia' costruito dall'API (`links.next`).
98
+ *
99
+ * Esiste come metodo pubblico perche' il cursore porta la firma
100
+ * dell'ordinamento: ricostruire l'URL a mano e rimetterci dentro il cursore
101
+ * e' esattamente cio' che l'API rifiuta con `INVALID_CURSOR`.
102
+ */
103
+ async follow(absoluteUrl) {
104
+ return this.request(absoluteUrl);
105
+ }
106
+ async request(url) {
107
+ let lastError;
108
+ for (let attempt = 0; attempt <= this.maxRetries; attempt += 1) {
109
+ try {
110
+ return await this.attempt(url);
111
+ }
112
+ catch (error) {
113
+ lastError = error;
114
+ if (attempt === this.maxRetries || !isRetryable(error))
115
+ throw error;
116
+ const retryAfter = error instanceof RateLimitedError ? error.retryAfter : undefined;
117
+ await sleep(backoffMs(attempt, retryAfter));
118
+ }
119
+ }
120
+ throw lastError;
121
+ }
122
+ async attempt(url, write) {
123
+ const headers = {
124
+ accept: 'application/json',
125
+ 'user-agent': this.userAgent,
126
+ };
127
+ if (this.apiKey !== undefined)
128
+ headers['x-api-key'] = this.apiKey;
129
+ // `contentType` si imposta solo se lo si conosce: su un FormData va lasciato
130
+ // scrivere a fetch, che ci mette dentro il `boundary`. Scriverlo a mano
131
+ // produce un multipart che il server non riesce a separare, e l'errore che
132
+ // ne esce parla di un campo mancante invece che di un'intestazione sbagliata.
133
+ if (write?.contentType !== undefined)
134
+ headers['content-type'] = write.contentType;
135
+ // Nessuna cache condizionale sulle scritture: l'ETag e' l'impronta di un
136
+ // corpo, e su una risposta che dipende da cio' che hai appena caricato non
137
+ // significherebbe niente.
138
+ const cached = write === undefined ? this.etags?.get(url) : undefined;
139
+ if (cached !== undefined)
140
+ headers['if-none-match'] = cached.etag;
141
+ const controller = new AbortController();
142
+ const timer = setTimeout(() => controller.abort(), this.timeout);
143
+ let response;
144
+ try {
145
+ response = await this.doFetch(url, {
146
+ method: write?.method ?? 'GET',
147
+ headers,
148
+ ...(write === undefined ? {} : { body: write.body }),
149
+ signal: controller.signal,
150
+ });
151
+ }
152
+ catch (error) {
153
+ if (controller.signal.aborted)
154
+ throw new ApiTimeoutError(this.timeout, error);
155
+ throw new ApiConnectionError(`Request to ${url} failed`, error);
156
+ }
157
+ finally {
158
+ clearTimeout(timer);
159
+ }
160
+ // 304: il corpo e' vuoto per definizione, la risposta e' quella in cache.
161
+ if (response.status === 304 && cached !== undefined)
162
+ return cached.body;
163
+ if (!response.ok) {
164
+ const retryAfterHeader = response.headers.get('retry-after');
165
+ const retryAfter = retryAfterHeader === null ? undefined : Number.parseInt(retryAfterHeader, 10);
166
+ const body = await this.readErrorBody(response);
167
+ throw toApiError(response.status, body, Number.isFinite(retryAfter) ? retryAfter : undefined);
168
+ }
169
+ const body = (await response.json());
170
+ const etag = response.headers.get('etag');
171
+ if (write === undefined && this.etags !== null && etag !== null)
172
+ this.etags.set(url, { etag, body });
173
+ return body;
174
+ }
175
+ /**
176
+ * Un 5xx puo' arrivare da un proxy davanti all'API, quindi in HTML: se il
177
+ * corpo non e' il nostro envelope si costruisce un errore comunque, invece di
178
+ * far esplodere il parser e nascondere lo status vero.
179
+ */
180
+ async readErrorBody(response) {
181
+ try {
182
+ const parsed = (await response.json());
183
+ if (parsed.error !== undefined && typeof parsed.error.code === 'string')
184
+ return parsed.error;
185
+ }
186
+ catch {
187
+ /* cade sotto */
188
+ }
189
+ return {
190
+ code: `HTTP_${response.status}`,
191
+ message: response.statusText === '' ? `HTTP ${response.status}` : response.statusText,
192
+ };
193
+ }
194
+ }
195
+ /**
196
+ * Una pagina che e' anche un iteratore.
197
+ *
198
+ * `for await (const card of client.cards.search(...))` cammina l'intera
199
+ * collezione seguendo `links.next`, senza che il chiamante veda mai un cursore.
200
+ * E' il motivo per cui vale la pena usare l'SDK invece di `fetch`: la paginazione
201
+ * a cursore e' corretta ma noiosa, ed e' il punto in cui le integrazioni scritte
202
+ * a mano perdono righe.
203
+ */
204
+ export class Page {
205
+ data;
206
+ meta;
207
+ nextUrl;
208
+ http;
209
+ constructor(http, body) {
210
+ this.http = http;
211
+ this.data = body.data;
212
+ this.meta = body.meta;
213
+ this.nextUrl = body.links?.next;
214
+ }
215
+ get hasMore() {
216
+ return this.nextUrl !== undefined;
217
+ }
218
+ async nextPage() {
219
+ if (this.nextUrl === undefined)
220
+ return null;
221
+ const body = await this.http.follow(this.nextUrl);
222
+ return new Page(this.http, body);
223
+ }
224
+ async *[Symbol.asyncIterator]() {
225
+ let page = this;
226
+ while (page !== null) {
227
+ for (const item of page.data)
228
+ yield item;
229
+ page = await page.nextPage();
230
+ }
231
+ }
232
+ /**
233
+ * Materializza in un array. `max` e' OBBLIGATORIO: il catalogo ha oltre
234
+ * 52.000 carte, e un `.toArray()` senza tetto e' il modo piu' rapido di
235
+ * riempire la memoria di un processo per sbaglio.
236
+ */
237
+ async toArray({ max }) {
238
+ const out = [];
239
+ for await (const item of this) {
240
+ out.push(item);
241
+ if (out.length >= max)
242
+ break;
243
+ }
244
+ return out;
245
+ }
246
+ }
247
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Gerarchia degli errori.
3
+ *
4
+ * Sottoclassi e non un solo tipo con un campo `code`, per una ragione pratica:
5
+ * chi integra scrive `catch (e) { if (e instanceof RateLimited) ... }`, e con un
6
+ * tipo solo dovrebbe confrontare stringhe — cioe' riscrivere a mano la
7
+ * tassonomia che noi gia' conosciamo, sbagliando i nomi.
8
+ *
9
+ * `QuotaExceeded` e' separato da `RateLimited` di proposito: sembrano lo stesso
10
+ * errore (entrambi 429) ma si trattano in modo opposto. Un rate limit passa
11
+ * aspettando; una quota mensile finita non passa mai, e riprovare e' solo un
12
+ * modo di consumare tempo. Il retry automatico di questo client riprova il
13
+ * primo e non riprova mai il secondo.
14
+ */
15
+ export interface ApiErrorBody {
16
+ readonly code: string;
17
+ readonly message: string;
18
+ readonly details?: Record<string, unknown>;
19
+ readonly request_id?: string;
20
+ }
21
+ export declare class PokemonTcgApiError extends Error {
22
+ /** Codice stabile della tassonomia, es. `CARD_NOT_FOUND`. */
23
+ readonly code: string;
24
+ readonly status: number;
25
+ /**
26
+ * Sempre valorizzato quando la risposta e' passata dall'API: e' l'unica cosa
27
+ * che il supporto puo' cercare nei log. Va incluso in ogni bug report.
28
+ */
29
+ readonly requestId: string | undefined;
30
+ readonly details: Record<string, unknown> | undefined;
31
+ constructor(status: number, body: ApiErrorBody);
32
+ }
33
+ /** 401 — chiave assente, malformata o revocata. */
34
+ export declare class AuthenticationError extends PokemonTcgApiError {
35
+ }
36
+ /** 403 — la chiave e' valida ma non puo' fare questa cosa. */
37
+ export declare class PermissionDeniedError extends PokemonTcgApiError {
38
+ }
39
+ /** 402 / UPGRADE_REQUIRED — serve un piano superiore. */
40
+ export declare class UpgradeRequiredError extends PokemonTcgApiError {
41
+ /** Finestra concessa dal piano corrente, quando l'API la dichiara. */
42
+ get permittedWindow(): unknown;
43
+ }
44
+ /** 404 — la risorsa non esiste. Non e' un errore di rete: non si riprova. */
45
+ export declare class NotFoundError extends PokemonTcgApiError {
46
+ }
47
+ /** 400 / 422 — la richiesta e' sbagliata. `field` dice quale parametro. */
48
+ export declare class InvalidRequestError extends PokemonTcgApiError {
49
+ get field(): string | undefined;
50
+ }
51
+ /** 429 con Retry-After: passa aspettando. */
52
+ export declare class RateLimitedError extends PokemonTcgApiError {
53
+ /** Secondi da aspettare, dall'header `Retry-After`, se c'era. */
54
+ readonly retryAfter: number | undefined;
55
+ constructor(status: number, body: ApiErrorBody, retryAfter?: number);
56
+ }
57
+ /** 429 per quota di periodo esaurita: NON passa aspettando, e non si riprova. */
58
+ export declare class QuotaExceededError extends PokemonTcgApiError {
59
+ }
60
+ /** 5xx. */
61
+ export declare class ServerError extends PokemonTcgApiError {
62
+ }
63
+ /** La richiesta non e' mai arrivata: DNS, TLS, socket. */
64
+ export declare class ApiConnectionError extends Error {
65
+ /** `override` perche' Error dichiara gia' `cause` da ES2022. */
66
+ readonly cause: unknown;
67
+ constructor(message: string, cause: unknown);
68
+ }
69
+ /** La richiesta e' stata abbandonata da noi dopo `timeout`. */
70
+ export declare class ApiTimeoutError extends ApiConnectionError {
71
+ constructor(timeoutMs: number, cause: unknown);
72
+ }
73
+ /**
74
+ * Dal corpo dell'errore alla classe giusta.
75
+ *
76
+ * Si guarda PRIMA il `code` e poi lo status: lo status dice la famiglia, il
77
+ * code dice il caso, e i due casi che contano davvero (limite contro quota)
78
+ * condividono lo stesso status.
79
+ */
80
+ export declare function toApiError(status: number, body: ApiErrorBody, retryAfter?: number): PokemonTcgApiError;
81
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC3C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,6DAA6D;IAC7D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;gBAE1C,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY;CAQ/C;AAED,mDAAmD;AACnD,qBAAa,mBAAoB,SAAQ,kBAAkB;CAAG;AAE9D,8DAA8D;AAC9D,qBAAa,qBAAsB,SAAQ,kBAAkB;CAAG;AAEhE,yDAAyD;AACzD,qBAAa,oBAAqB,SAAQ,kBAAkB;IAC1D,sEAAsE;IACtE,IAAI,eAAe,IAAI,OAAO,CAE7B;CACF;AAED,6EAA6E;AAC7E,qBAAa,aAAc,SAAQ,kBAAkB;CAAG;AAExD,2EAA2E;AAC3E,qBAAa,mBAAoB,SAAQ,kBAAkB;IACzD,IAAI,KAAK,IAAI,MAAM,GAAG,SAAS,CAG9B;CACF;AAED,6CAA6C;AAC7C,qBAAa,gBAAiB,SAAQ,kBAAkB;IACtD,iEAAiE;IACjE,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;gBAE5B,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,UAAU,CAAC,EAAE,MAAM;CAIpE;AAED,iFAAiF;AACjF,qBAAa,kBAAmB,SAAQ,kBAAkB;CAAG;AAE7D,WAAW;AACX,qBAAa,WAAY,SAAQ,kBAAkB;CAAG;AAEtD,0DAA0D;AAC1D,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,gEAAgE;IAChE,SAAkB,KAAK,EAAE,OAAO,CAAC;gBACrB,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;CAK5C;AAED,+DAA+D;AAC/D,qBAAa,eAAgB,SAAQ,kBAAkB;gBACzC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;CAI9C;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,kBAAkB,CAatG"}