@carddex/sdk 0.1.0 → 0.3.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 Michał Furgalski
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,170 @@
1
+ # @carddex/sdk
2
+
3
+ Official TypeScript/JavaScript client for the [CardDex](https://carddex.dev) Pokémon TCG API — cards, sets, print variants, sealed products, and price history from Cardmarket (EUR) and TCGplayer (USD).
4
+
5
+ - Zero runtime dependencies — uses global `fetch`, so it runs unmodified in Node 18+, browsers, Cloudflare Workers, Deno and Bun.
6
+ - Fully typed, ESM + CJS builds, with a shipped `.d.ts`.
7
+ - No codegen step for consumers: types are generated at build time from the CardDex OpenAPI spec and committed.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install @carddex/sdk
13
+ # or
14
+ pnpm add @carddex/sdk
15
+ # or
16
+ yarn add @carddex/sdk
17
+ ```
18
+
19
+ ## Quickstart
20
+
21
+ ```ts
22
+ import { CardDex } from '@carddex/sdk';
23
+
24
+ const client = new CardDex({ apiKey: process.env.CARDDEX_API_KEY });
25
+
26
+ const { data: cards } = await client.cards.list({ name: 'Charizard', sort: 'release_date', order: 'desc' });
27
+
28
+ const card = await client.cards.get('sv06-001', { include: ['prices', 'images'] });
29
+
30
+ const prices = await client.cards.prices('sv06-001');
31
+
32
+ const history = await client.cards.priceHistory('sv06-001', { days: 180, source: ['cardmarket', 'tcgplayer'] });
33
+ ```
34
+
35
+ An API key is optional. Omit it to call the API anonymously. The SDK sends the key in the `X-API-Key` header.
36
+
37
+ You can also sign in with an email link at [carddex.dev/account](https://carddex.dev/account) to see your keys, create up to 2 active keys, and rotate or revoke them.
38
+
39
+ ### Card names in German, French, Italian, Spanish and Portuguese
40
+
41
+ The catalogue is the English one, and `name` is always the English name. Pass `lang` (`de`, `fr`, `it`, `es` or `pt`) and every card and set also carries `localized_name`: its name in that language edition, or `null` where none is known. With `lang`, `name` and `q` also match names in that language. `include: ['translations']` lists every localized name a card has.
42
+
43
+ ```ts
44
+ const { data } = await client.cards.list({ name: 'Glurak', lang: 'de' });
45
+ // data[0].name === 'Charizard', data[0].localized_name === 'Glurak'
46
+
47
+ const sets = await client.sets.list({ lang: 'fr' });
48
+ const card = await client.cards.get('base1-4', { include: ['translations'] });
49
+ ```
50
+
51
+ Polish card names are not available from any source, so `lang: 'pl'` is rejected (the API answers 400). Japanese cards are not in the catalogue yet.
52
+
53
+ ### Get a free API key
54
+
55
+ ```ts
56
+ import { CardDex } from '@carddex/sdk';
57
+
58
+ const client = new CardDex();
59
+ const { api_key } = await client.auth.register('you@example.com', 'My App');
60
+ // Save api_key now — it is shown only once. If you lose it, sign in at https://carddex.dev/account to create a new one or rotate/revoke keys.
61
+ ```
62
+
63
+ ## Calling from a browser
64
+
65
+ Public read endpoints accept requests from any origin. **Never ship a real API key in public frontend code** — anyone who opens dev tools can read it, and it would be shared by every visitor to your site. Instead:
66
+
67
+ - Call anonymously from the browser. CardDex allows this at a lower but real rate limit (30 req/min, 5,000 req/day per visitor IP) specifically so a frontend can call it directly, with no key:
68
+
69
+ ```ts
70
+ const client = new CardDex(); // no apiKey — anonymous browser mode
71
+ ```
72
+
73
+ - Or proxy through your own backend and attach the key there, server-side.
74
+
75
+ If you construct `new CardDex({ apiKey })` in what looks like a browser environment, the SDK logs a one-time `console.warn` as a safety net — it does not block the request.
76
+
77
+ ## Pagination
78
+
79
+ Every list endpoint returns `{ data, meta: { total, page, pageSize, totalPages } }`. Each resource that lists something also exposes an `*All` method that returns an async iterator, fetching pages lazily as you consume them:
80
+
81
+ ```ts
82
+ for await (const card of client.cards.listAll({ set: 'sv06' })) {
83
+ console.log(card.name);
84
+ }
85
+
86
+ for await (const product of client.sealed.listAll({ set_id: 'sv06' })) { ... }
87
+
88
+ for await (const card of client.sets.cardsAll('sv06')) { ... }
89
+ ```
90
+
91
+ You can also use the lower-level `paginate()` helper directly against any page-shaped fetcher:
92
+
93
+ ```ts
94
+ import { paginate } from '@carddex/sdk';
95
+
96
+ for await (const card of paginate((page) => client.cards.list({ set: 'sv06', page }))) { ... }
97
+ ```
98
+
99
+ ## Errors
100
+
101
+ Every non-2xx response throws a `CardDexError`:
102
+
103
+ ```ts
104
+ import { CardDex, CardDexError } from '@carddex/sdk';
105
+
106
+ try {
107
+ await client.cards.get('does-not-exist');
108
+ } catch (err) {
109
+ if (err instanceof CardDexError) {
110
+ err.status; // HTTP status, e.g. 404
111
+ err.code; // the API's error.code (a number for a real API response; 'NETWORK_ERROR' for a transport failure)
112
+ err.message; // the API's error.message
113
+ err.retryAfter; // seconds to wait, parsed from Retry-After on a 429 — null otherwise
114
+ err.rateLimit; // { limit, dailyLimit, dailyRemaining } parsed from response headers, or null
115
+ err.isRateLimited; // true for a 429 (either the per-minute or the daily cap)
116
+ }
117
+ }
118
+ ```
119
+
120
+ ## Rate limits & retries
121
+
122
+ Every response carries `X-RateLimit-Limit` (per minute), `X-RateLimit-Daily-Limit` and `X-RateLimit-Daily-Remaining` (per UTC day, approximate). A 429 carries `Retry-After` in seconds: `60` for the per-minute limit, or the seconds until 00:00 UTC for the daily one.
123
+
124
+ Automatic retry on 429 is **off by default** — the SDK never silently blocks your code. Turn it on when you want it:
125
+
126
+ ```ts
127
+ const client = new CardDex({ retry: true }); // up to 2 retries, never waiting more than 60s
128
+
129
+ const client2 = new CardDex({ retry: { maxRetries: 3, maxWaitSeconds: 30 } });
130
+ ```
131
+
132
+ A `Retry-After` longer than `maxWaitSeconds` is never honoured — the call fails immediately instead of sleeping. This is deliberate: the daily-quota 429's `Retry-After` can be most of a day, and a small SDK default should never block a request for hours. The per-minute 429's `Retry-After: 60` fits comfortably under the default 60s cap, so that case retries; a daily-cap 429 does not, by design.
133
+
134
+ ## Migrating from pokemontcg.io
135
+
136
+ The pokemontcg.io v2 compatibility layer is live: an app built on pokemontcg.io v2 can keep its code and stored ids and change its base URL to `https://api.carddex.dev/compat/pokemontcg/v2`. The guide at [carddex.dev/migrate/pokemontcg](https://carddex.dev/migrate/pokemontcg) covers what the layer supports, which official pokemontcg.io SDKs work with it, and the fields that differ.
137
+
138
+ This SDK talks to the native `/v1` API, which uses CardDex ids (`sv02-062`) but accepts pokemontcg.io ids as well: `client.cards.get('sv2-62')` returns the same card. The API also returns a `ptcgio_id` field on every card and accepts a `ptcgio_id` filter on the card list; this SDK's types don't include either yet.
139
+
140
+ ## API surface
141
+
142
+ | Resource | Methods |
143
+ | --- | --- |
144
+ | `client.cards` | `list`, `listAll`, `get`, `random`, `prices`, `priceHistory` |
145
+ | `client.sets` | `list`, `get`, `cards`, `cardsAll`, `neighbors`, `sealed` |
146
+ | `client.sealed` | `list`, `listAll`, `get`, `prices` |
147
+ | `client.prices` | `bulk`, `top`, `trends`, `bargains`, `history` |
148
+ | `client.auth` | `register`, `usage` |
149
+ | `client` | `usage()` (shorthand for `client.auth.usage()`), `health()` |
150
+
151
+ Full parameter and response types are exported from the package root — see `src/params.ts` and `src/types.ts`, or your editor's autocomplete.
152
+
153
+ ## Development (this monorepo)
154
+
155
+ ```bash
156
+ pnpm install
157
+ pnpm --filter @carddex/sdk generate # refresh src/generated/openapi.d.ts from the live OpenAPI spec
158
+ pnpm --filter @carddex/sdk build
159
+ pnpm --filter @carddex/sdk test
160
+ pnpm --filter @carddex/sdk typecheck
161
+ pnpm --filter @carddex/sdk lint
162
+ ```
163
+
164
+ `openapi.json` in this package's root is a committed snapshot of `https://api.carddex.dev/openapi.json`. `pnpm generate` refreshes it and regenerates `src/generated/openapi.d.ts` from it; if the fetch fails (offline, no network egress in CI), it regenerates from whatever snapshot is already committed instead of failing outright.
165
+
166
+ The OpenAPI spec now documents response body schemas too (`components.schemas`, validated against live API responses by `carddex-api`'s `tests/integration/openapi-contract.test.ts`), so `src/generated/openapi.d.ts` types response bodies precisely rather than as `unknown`. The hand-written interfaces in `src/types.ts` predate that and can stay — they're what this SDK's public API is built on — but new code can also reach into `components["schemas"]` from the generated file directly.
167
+
168
+ ## License
169
+
170
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,445 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ CardDex: () => CardDex,
24
+ CardDexError: () => CardDexError,
25
+ paginate: () => paginate
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+
29
+ // src/errors.ts
30
+ function parseRateLimitHeaders(headers) {
31
+ const num = (name) => {
32
+ const raw = headers.get(name);
33
+ if (raw == null) return null;
34
+ const n = Number(raw);
35
+ return Number.isFinite(n) ? n : null;
36
+ };
37
+ return {
38
+ limit: num("X-RateLimit-Limit"),
39
+ dailyLimit: num("X-RateLimit-Daily-Limit"),
40
+ dailyRemaining: num("X-RateLimit-Daily-Remaining")
41
+ };
42
+ }
43
+ var CardDexError = class _CardDexError extends Error {
44
+ /** HTTP status code. `0` for a request that never got a response (network failure, abort). */
45
+ status;
46
+ /**
47
+ * The API's own `error.code`. For a response the API actually produced,
48
+ * this is the same number as `status` (see the note on `ApiErrorBody` in
49
+ * `types.ts`) — kept as its own field because a transport-level failure
50
+ * (no response at all) sets it to a string like `NETWORK_ERROR` instead.
51
+ */
52
+ code;
53
+ /** Seconds to wait before retrying, parsed from `Retry-After` on a 429. `null` otherwise. */
54
+ retryAfter;
55
+ /** Rate-limit headers on the response, when there was one. */
56
+ rateLimit;
57
+ constructor(message, options) {
58
+ super(message, options.cause !== void 0 ? { cause: options.cause } : void 0);
59
+ this.name = "CardDexError";
60
+ this.status = options.status;
61
+ this.code = options.code;
62
+ this.retryAfter = options.retryAfter ?? null;
63
+ this.rateLimit = options.rateLimit ?? null;
64
+ Object.setPrototypeOf(this, _CardDexError.prototype);
65
+ }
66
+ /** Whether this is a 429 (either the per-minute or the daily cap). */
67
+ get isRateLimited() {
68
+ return this.status === 429;
69
+ }
70
+ static async fromResponse(response) {
71
+ const rateLimit = parseRateLimitHeaders(response.headers);
72
+ const retryAfterHeader = response.headers.get("Retry-After");
73
+ const retryAfter = retryAfterHeader != null ? Number(retryAfterHeader) : null;
74
+ let message = `CardDex API error (${response.status})`;
75
+ let code = response.status;
76
+ try {
77
+ const body = await response.clone().json();
78
+ if (body?.error) {
79
+ if (typeof body.error.message === "string") message = body.error.message;
80
+ if (typeof body.error.code === "number" || typeof body.error.code === "string") {
81
+ code = body.error.code;
82
+ }
83
+ }
84
+ } catch {
85
+ }
86
+ return new _CardDexError(message, {
87
+ status: response.status,
88
+ code,
89
+ retryAfter: Number.isFinite(retryAfter) ? retryAfter : null,
90
+ rateLimit
91
+ });
92
+ }
93
+ static network(cause) {
94
+ const message = cause instanceof Error ? cause.message : "Network request failed";
95
+ return new _CardDexError(message, { status: 0, code: "NETWORK_ERROR", cause });
96
+ }
97
+ };
98
+
99
+ // src/http.ts
100
+ var DEFAULT_MAX_RETRIES = 2;
101
+ var DEFAULT_MAX_WAIT_SECONDS = 60;
102
+ function buildQueryString(params) {
103
+ if (!params) return "";
104
+ const search = new URLSearchParams();
105
+ for (const [key, value] of Object.entries(params)) {
106
+ if (value === void 0 || value === null) continue;
107
+ if (Array.isArray(value)) {
108
+ if (value.length === 0) continue;
109
+ search.set(key, value.join(","));
110
+ } else {
111
+ search.set(key, String(value));
112
+ }
113
+ }
114
+ const qs = search.toString();
115
+ return qs ? `?${qs}` : "";
116
+ }
117
+ function sleep(ms) {
118
+ return new Promise((resolve) => setTimeout(resolve, ms));
119
+ }
120
+ var HttpClient = class {
121
+ baseUrl;
122
+ apiKey;
123
+ fetchImpl;
124
+ retryConfig;
125
+ constructor(options) {
126
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
127
+ this.apiKey = options.apiKey;
128
+ this.fetchImpl = options.fetch;
129
+ if (options.retry === true) {
130
+ this.retryConfig = {
131
+ maxRetries: DEFAULT_MAX_RETRIES,
132
+ maxWaitSeconds: DEFAULT_MAX_WAIT_SECONDS
133
+ };
134
+ } else if (options.retry && typeof options.retry === "object") {
135
+ this.retryConfig = {
136
+ maxRetries: options.retry.maxRetries ?? DEFAULT_MAX_RETRIES,
137
+ maxWaitSeconds: options.retry.maxWaitSeconds ?? DEFAULT_MAX_WAIT_SECONDS
138
+ };
139
+ } else {
140
+ this.retryConfig = null;
141
+ }
142
+ }
143
+ async get(path, query, signal) {
144
+ return this.request("GET", path, { query, signal });
145
+ }
146
+ async post(path, body, signal) {
147
+ return this.request("POST", path, { body, signal });
148
+ }
149
+ async request(method, path, opts) {
150
+ const url = `${this.baseUrl}${path}${buildQueryString(opts.query)}`;
151
+ const headers = { Accept: "application/json" };
152
+ if (this.apiKey) headers["X-API-Key"] = this.apiKey;
153
+ if (opts.body !== void 0) headers["Content-Type"] = "application/json";
154
+ let attempt = 0;
155
+ for (; ; ) {
156
+ let response;
157
+ try {
158
+ response = await this.fetchImpl(url, {
159
+ method,
160
+ headers,
161
+ body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0,
162
+ signal: opts.signal
163
+ });
164
+ } catch (cause) {
165
+ throw CardDexError.network(cause);
166
+ }
167
+ if (response.status === 429 && this.retryConfig && attempt < this.retryConfig.maxRetries) {
168
+ const retryAfterHeader = response.headers.get("Retry-After");
169
+ const retryAfterSeconds = retryAfterHeader != null ? Number(retryAfterHeader) : Number.NaN;
170
+ if (Number.isFinite(retryAfterSeconds) && retryAfterSeconds <= this.retryConfig.maxWaitSeconds) {
171
+ attempt++;
172
+ await sleep(retryAfterSeconds * 1e3);
173
+ continue;
174
+ }
175
+ }
176
+ if (!response.ok) {
177
+ throw await CardDexError.fromResponse(response);
178
+ }
179
+ if (response.status === 204) return void 0;
180
+ return await response.json();
181
+ }
182
+ }
183
+ };
184
+
185
+ // src/resources/auth.ts
186
+ var AuthResource = class {
187
+ constructor(http) {
188
+ this.http = http;
189
+ }
190
+ http;
191
+ /**
192
+ * `POST /v1/auth/register` — claim a free API key. The key is returned
193
+ * exactly once, so store it immediately. A lost key can be replaced by signing
194
+ * in at https://carddex.dev/account (create, rotate or revoke keys there).
195
+ */
196
+ async register(email, name) {
197
+ const res = await this.http.post("/v1/auth/register", {
198
+ email,
199
+ name
200
+ });
201
+ return res.data;
202
+ }
203
+ /** `GET /v1/auth/usage` — usage stats for the API key this client was constructed with. Requires an `apiKey`. */
204
+ async usage() {
205
+ const res = await this.http.get("/v1/auth/usage");
206
+ return res.data;
207
+ }
208
+ };
209
+
210
+ // src/pagination.ts
211
+ async function* paginate(fetchPage, startPage = 1) {
212
+ let page = startPage;
213
+ for (; ; ) {
214
+ const response = await fetchPage(page);
215
+ if (response.data.length === 0) return;
216
+ yield* response.data;
217
+ if (page >= response.meta.totalPages) return;
218
+ page++;
219
+ }
220
+ }
221
+
222
+ // src/resources/cards.ts
223
+ var CardsResource = class {
224
+ constructor(http) {
225
+ this.http = http;
226
+ }
227
+ http;
228
+ /** `GET /v1/cards` — list, search, filter and sort cards. */
229
+ list(params) {
230
+ return this.http.get("/v1/cards", params);
231
+ }
232
+ /**
233
+ * Iterates every card matching `params` across all pages, fetching pages
234
+ * lazily as you consume them.
235
+ *
236
+ * @example
237
+ * ```ts
238
+ * for await (const card of client.cards.listAll({ set: 'sv06' })) { ... }
239
+ * ```
240
+ */
241
+ listAll(params) {
242
+ return paginate((page) => this.list({ ...params, page }));
243
+ }
244
+ /** `GET /v1/cards/random` */
245
+ async random() {
246
+ const res = await this.http.get("/v1/cards/random");
247
+ return res.data;
248
+ }
249
+ /** `GET /v1/cards/{id}` */
250
+ async get(id, params) {
251
+ const res = await this.http.get(
252
+ `/v1/cards/${encodeURIComponent(id)}`,
253
+ params
254
+ );
255
+ return res.data;
256
+ }
257
+ /** `GET /v1/cards/{id}/prices` — the latest price row(s) for a card. */
258
+ async prices(id, params) {
259
+ const res = await this.http.get(
260
+ `/v1/cards/${encodeURIComponent(id)}/prices`,
261
+ params
262
+ );
263
+ return res.data;
264
+ }
265
+ /** `GET /v1/cards/{id}/prices/history` — one row per day per price guide, oldest first. */
266
+ async priceHistory(id, params) {
267
+ const res = await this.http.get(
268
+ `/v1/cards/${encodeURIComponent(id)}/prices/history`,
269
+ params
270
+ );
271
+ return res.data;
272
+ }
273
+ };
274
+
275
+ // src/resources/prices.ts
276
+ var PricesResource = class {
277
+ constructor(http) {
278
+ this.http = http;
279
+ }
280
+ http;
281
+ /**
282
+ * `GET /v1/prices` — latest price rows for up to 100 card ids (and/or up
283
+ * to 100 print/variant ids) in one request. Keyed by the id you asked for;
284
+ * an id with no price data maps to `[]`.
285
+ */
286
+ async bulk(params) {
287
+ const res = await this.http.get(
288
+ "/v1/prices",
289
+ params
290
+ );
291
+ return res.data;
292
+ }
293
+ /** `GET /v1/prices/top` — highest-valued cards. */
294
+ async top(params) {
295
+ const res = await this.http.get("/v1/prices/top", params);
296
+ return res.data;
297
+ }
298
+ /** `GET /v1/prices/trends` — biggest movers against the card's own rolling average. */
299
+ async trends(params) {
300
+ const res = await this.http.get("/v1/prices/trends", params);
301
+ return res.data;
302
+ }
303
+ /** `GET /v1/prices/bargains` — cards trading below a reference price, best discount first. */
304
+ async bargains(params) {
305
+ const res = await this.http.get(
306
+ "/v1/prices/bargains",
307
+ params
308
+ );
309
+ return res.data;
310
+ }
311
+ /** `GET /v1/prices/{cardId}/history` — one row per day per guide, oldest first. */
312
+ async history(cardId, params) {
313
+ const res = await this.http.get(
314
+ `/v1/prices/${encodeURIComponent(cardId)}/history`,
315
+ params
316
+ );
317
+ return res.data;
318
+ }
319
+ };
320
+
321
+ // src/resources/sealed.ts
322
+ var SealedResource = class {
323
+ constructor(http) {
324
+ this.http = http;
325
+ }
326
+ http;
327
+ /** `GET /v1/sealed` — list sealed products with their latest price. */
328
+ list(params) {
329
+ return this.http.get("/v1/sealed", params);
330
+ }
331
+ /** Iterates every sealed product matching `params` across all pages. */
332
+ listAll(params) {
333
+ return paginate((page) => this.list({ ...params, page }));
334
+ }
335
+ /** `GET /v1/sealed/{id}` */
336
+ async get(id, params) {
337
+ const res = await this.http.get(
338
+ `/v1/sealed/${encodeURIComponent(id)}`,
339
+ params
340
+ );
341
+ return res.data;
342
+ }
343
+ /** `GET /v1/sealed/{id}/prices` — one row per synced day, oldest first. */
344
+ async prices(id, params) {
345
+ const res = await this.http.get(
346
+ `/v1/sealed/${encodeURIComponent(id)}/prices`,
347
+ params
348
+ );
349
+ return res.data;
350
+ }
351
+ };
352
+
353
+ // src/resources/sets.ts
354
+ var SetsResource = class {
355
+ constructor(http) {
356
+ this.http = http;
357
+ }
358
+ http;
359
+ /** `GET /v1/sets` — every set. `lang` adds `localized_name`. */
360
+ async list(params) {
361
+ const res = await this.http.get("/v1/sets", params);
362
+ return res.data;
363
+ }
364
+ /** `GET /v1/sets/{id}` — `lang` adds `localized_name`. */
365
+ async get(id, params) {
366
+ const res = await this.http.get(
367
+ `/v1/sets/${encodeURIComponent(id)}`,
368
+ params
369
+ );
370
+ return res.data;
371
+ }
372
+ /** `GET /v1/sets/{id}/cards` */
373
+ cards(id, params) {
374
+ return this.http.get(
375
+ `/v1/sets/${encodeURIComponent(id)}/cards`,
376
+ params
377
+ );
378
+ }
379
+ /** Iterates every card in a set across all pages. */
380
+ cardsAll(id, params) {
381
+ return paginate((page) => this.cards(id, { ...params, page }));
382
+ }
383
+ /** `GET /v1/sets/{id}/cards/{cardId}/neighbors` — the 2 previous / 2 next card ids. */
384
+ async neighbors(setId, cardId) {
385
+ const res = await this.http.get(
386
+ `/v1/sets/${encodeURIComponent(setId)}/cards/${encodeURIComponent(cardId)}/neighbors`
387
+ );
388
+ return res.data;
389
+ }
390
+ /** `GET /v1/sets/{id}/sealed` — sealed products attached to a set, each with its latest price. */
391
+ async sealed(id, params) {
392
+ const res = await this.http.get(
393
+ `/v1/sets/${encodeURIComponent(id)}/sealed`,
394
+ params
395
+ );
396
+ return res.data;
397
+ }
398
+ };
399
+
400
+ // src/client.ts
401
+ var DEFAULT_BASE_URL = "https://api.carddex.dev";
402
+ function isBrowserLike() {
403
+ return typeof window !== "undefined" && typeof window.document !== "undefined";
404
+ }
405
+ var CardDex = class {
406
+ cards;
407
+ sets;
408
+ sealed;
409
+ prices;
410
+ auth;
411
+ http;
412
+ constructor(options = {}) {
413
+ if (options.apiKey && isBrowserLike()) {
414
+ console.warn(
415
+ "[@carddex/sdk] An apiKey was passed in what looks like a browser environment. API keys shipped in frontend code are visible to every visitor. Omit apiKey for CardDex's anonymous browser mode, or proxy authenticated requests through your own backend."
416
+ );
417
+ }
418
+ const fetchImpl = options.fetch ?? globalThis.fetch;
419
+ if (!fetchImpl) {
420
+ throw new Error(
421
+ "@carddex/sdk requires a global fetch implementation. On Node < 18, pass one via `fetch` in the options."
422
+ );
423
+ }
424
+ this.http = new HttpClient({
425
+ baseUrl: options.baseUrl ?? DEFAULT_BASE_URL,
426
+ apiKey: options.apiKey,
427
+ fetch: fetchImpl.bind(globalThis),
428
+ retry: options.retry
429
+ });
430
+ this.cards = new CardsResource(this.http);
431
+ this.sets = new SetsResource(this.http);
432
+ this.sealed = new SealedResource(this.http);
433
+ this.prices = new PricesResource(this.http);
434
+ this.auth = new AuthResource(this.http);
435
+ }
436
+ /** `GET /v1/auth/usage` — shorthand for `client.auth.usage()`. Requires an `apiKey`. */
437
+ usage() {
438
+ return this.auth.usage();
439
+ }
440
+ /** `GET /v1/health` — API health, price freshness, and cron status. */
441
+ async health() {
442
+ return this.http.get("/v1/health");
443
+ }
444
+ };
445
+ //# sourceMappingURL=index.cjs.map