tcgpriser 0.7.0 → 0.13.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/dist/index.js CHANGED
@@ -7,8 +7,21 @@ var TcgPriserError = class extends Error {
7
7
  details;
8
8
  /** The raw response body, for debugging when `code`/`details` don't cover what you need. */
9
9
  body;
10
+ /**
11
+ * Seconds to wait before retrying, from the `Retry-After` header. Present on `rateLimited`, and
12
+ * on anything else a proxy in front of the API decides to send it with. Absent otherwise — an
13
+ * error without it is not one that says retrying will help.
14
+ */
15
+ retryAfter;
16
+ /**
17
+ * Credits left in this week's allowance, from `X-Credits-Remaining`. Present on errors from
18
+ * charged routes — notably `creditsExhausted`, where it is `0`. Absent on uncharged routes and on
19
+ * anything a proxy answered instead of the API.
20
+ */
21
+ creditsRemaining;
10
22
  constructor(params) {
11
- super(`tcgpriser: ${params.statusCode} ${params.code} - ${params.message} (${params.url})`);
23
+ const status = params.statusCode === 0 ? "" : `${params.statusCode} `;
24
+ super(`tcgpriser: ${status}${params.code} - ${params.message} (${params.url})`);
12
25
  this.name = "TcgPriserError";
13
26
  this.statusCode = params.statusCode;
14
27
  this.statusText = params.statusText;
@@ -16,6 +29,8 @@ var TcgPriserError = class extends Error {
16
29
  this.code = params.code;
17
30
  this.details = params.details;
18
31
  this.body = params.body;
32
+ this.retryAfter = params.retryAfter;
33
+ this.creditsRemaining = params.creditsRemaining;
19
34
  }
20
35
  };
21
36
 
@@ -29,9 +44,9 @@ function toQueryString(params) {
29
44
  const query = search.toString();
30
45
  return query ? `?${query}` : "";
31
46
  }
32
- function splitAuthToken(params) {
33
- const { authToken, ...rest } = params;
34
- return [rest, authToken];
47
+ function splitRequestOptions(params) {
48
+ const { authToken, signal, timeoutMs, ...rest } = params;
49
+ return [rest, { authToken, signal, timeoutMs }];
35
50
  }
36
51
  var KNOWN_ERROR_CODES = /* @__PURE__ */ new Set([
37
52
  "validationFailed",
@@ -42,8 +57,16 @@ var KNOWN_ERROR_CODES = /* @__PURE__ */ new Set([
42
57
  "readOnlyField",
43
58
  "rateLimited",
44
59
  "premiumRequired",
60
+ "businessRequired",
61
+ "creditsExhausted",
45
62
  "internalError"
46
63
  ]);
64
+ function readIntHeader(res, name) {
65
+ const raw = res.headers.get(name);
66
+ if (raw === null) return void 0;
67
+ const parsed = Number(raw);
68
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
69
+ }
47
70
  async function toApiError(res, url) {
48
71
  const body = await res.text();
49
72
  let code = "unknown";
@@ -58,18 +81,63 @@ async function toApiError(res, url) {
58
81
  details = parsed.error?.details;
59
82
  } catch {
60
83
  }
61
- return new TcgPriserError({ statusCode: res.status, statusText: res.statusText, url, code, message, details, body });
84
+ return new TcgPriserError({
85
+ statusCode: res.status,
86
+ statusText: res.statusText,
87
+ url,
88
+ code,
89
+ message,
90
+ details,
91
+ body,
92
+ // Both are most useful on exactly the errors that carry them: `retryAfter` on `rateLimited`,
93
+ // `creditsRemaining` on `creditsExhausted` (where it is 0) and on any error from a charged
94
+ // route. Read unconditionally rather than branching on the code, since a proxy can return a
95
+ // 429 with `Retry-After` and no envelope at all.
96
+ retryAfter: readIntHeader(res, "Retry-After"),
97
+ creditsRemaining: readIntHeader(res, "X-Credits-Remaining")
98
+ });
99
+ }
100
+ var DEFAULT_TIMEOUT_MS = 6e4;
101
+ function withTimeout(timeoutMs, callerSignal) {
102
+ if (timeoutMs <= 0) return { signal: callerSignal, clear: () => {
103
+ }, timedOut: () => false };
104
+ const controller = new AbortController();
105
+ let expired = false;
106
+ const timer = setTimeout(() => {
107
+ expired = true;
108
+ controller.abort();
109
+ }, timeoutMs);
110
+ const onCallerAbort = () => controller.abort();
111
+ if (callerSignal) {
112
+ if (callerSignal.aborted) controller.abort();
113
+ else callerSignal.addEventListener("abort", onCallerAbort, { once: true });
114
+ }
115
+ return {
116
+ signal: controller.signal,
117
+ clear: () => {
118
+ clearTimeout(timer);
119
+ callerSignal?.removeEventListener("abort", onCallerAbort);
120
+ },
121
+ timedOut: () => expired
122
+ };
62
123
  }
63
124
  var HttpClient = class {
64
125
  baseUrl;
65
126
  fetchImpl;
66
127
  defaultHeaders;
67
128
  defaultAuthToken;
129
+ defaultTimeoutMs;
130
+ /**
131
+ * The `X-Credits-Remaining` value from the most recent charged response, or `undefined` if no
132
+ * charged call has been made yet. See `TcgPriser.creditsRemaining`.
133
+ */
134
+ creditsRemaining;
68
135
  constructor(options) {
69
136
  this.baseUrl = options.baseUrl.replace(/\/+$/, "");
70
137
  this.fetchImpl = options.fetch;
71
138
  this.defaultHeaders = options.headers ?? {};
72
139
  this.defaultAuthToken = options.authToken;
140
+ this.defaultTimeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
73
141
  }
74
142
  get(path, requestOptions) {
75
143
  return this.request("GET", path, void 0, requestOptions);
@@ -80,17 +148,42 @@ var HttpClient = class {
80
148
  patch(path, body, requestOptions) {
81
149
  return this.request("PATCH", path, body, requestOptions);
82
150
  }
151
+ delete(path, requestOptions) {
152
+ return this.request("DELETE", path, void 0, requestOptions);
153
+ }
83
154
  async request(method, path, body, requestOptions) {
84
155
  const url = `${this.baseUrl}${path}`;
85
156
  const authToken = requestOptions && "authToken" in requestOptions ? requestOptions.authToken : this.defaultAuthToken;
86
157
  const headers = { Accept: "application/json", ...this.defaultHeaders };
87
158
  if (authToken) headers.Authorization = `Bearer ${authToken}`;
88
159
  if (body !== void 0) headers["Content-Type"] = "application/json";
89
- const res = await this.fetchImpl(url, {
90
- method,
91
- headers,
92
- body: body === void 0 ? void 0 : JSON.stringify(body)
93
- });
160
+ const timeoutMs = requestOptions?.timeoutMs ?? this.defaultTimeoutMs;
161
+ const timeout = withTimeout(timeoutMs, requestOptions?.signal);
162
+ let res;
163
+ try {
164
+ res = await this.fetchImpl(url, {
165
+ method,
166
+ headers,
167
+ body: body === void 0 ? void 0 : JSON.stringify(body),
168
+ signal: timeout.signal
169
+ });
170
+ } catch (error) {
171
+ if (timeout.timedOut()) {
172
+ throw new TcgPriserError({
173
+ statusCode: 0,
174
+ statusText: "Timeout",
175
+ url,
176
+ code: "timeout",
177
+ message: `Request timed out after ${timeoutMs}ms`,
178
+ body: ""
179
+ });
180
+ }
181
+ throw error;
182
+ } finally {
183
+ timeout.clear();
184
+ }
185
+ const credits = readIntHeader(res, "X-Credits-Remaining");
186
+ if (credits !== void 0) this.creditsRemaining = credits;
94
187
  if (!res.ok) throw await toApiError(res, url);
95
188
  if (res.status === 204) return void 0;
96
189
  return nullsToUndefined(await res.json());
@@ -117,13 +210,14 @@ var BargainsResource = class {
117
210
  * count is fixed by the API (no `limit`/`skip` on the public tier); `pagination.hasMore` tells
118
211
  * you if more exist. */
119
212
  list(params = {}) {
120
- return this.http.get(`/bargains${toQueryString(params)}`);
213
+ const [query, requestOptions] = splitRequestOptions(params);
214
+ return this.http.get(`/bargains${toQueryString(query)}`, requestOptions);
121
215
  }
122
216
  /** `GET /bargains/search`: like `list()`, but with real pagination and filters (shop, discount
123
217
  * threshold, card condition/grade, free-text search). Premium. */
124
218
  search(params = {}) {
125
- const [query, authToken] = splitAuthToken(params);
126
- return this.http.get(`/bargains/search${toQueryString(query)}`, { authToken });
219
+ const [query, requestOptions] = splitRequestOptions(params);
220
+ return this.http.get(`/bargains/search${toQueryString(query)}`, requestOptions);
127
221
  }
128
222
  };
129
223
 
@@ -133,34 +227,40 @@ var CardsResource = class {
133
227
  this.http = http;
134
228
  }
135
229
  http;
136
- /** `GET /cards`: search or list cards. */
230
+ /** `GET /cards`: list cards, newest first. No free-text search — use `search()` for that. */
137
231
  list(params = {}) {
138
- return this.http.get(`/cards${toQueryString(params)}`);
232
+ const [query, requestOptions] = splitRequestOptions(params);
233
+ return this.http.get(`/cards${toQueryString(query)}`, requestOptions);
234
+ }
235
+ /** `GET /cards/search`: like `list()`, but with free-text search on card and set names. Premium. */
236
+ search(params = {}) {
237
+ const [query, requestOptions] = splitRequestOptions(params);
238
+ return this.http.get(`/cards/search${toQueryString(query)}`, requestOptions);
139
239
  }
140
240
  /** `GET /cards/{id}`: fetch one card by its id or technicalName. */
141
- get(idOrTechnicalName) {
142
- return this.http.get(`/cards/${encodeURIComponent(idOrTechnicalName)}`);
241
+ get(idOrTechnicalName, options = {}) {
242
+ return this.http.get(`/cards/${encodeURIComponent(idOrTechnicalName)}`, options);
143
243
  }
144
244
  /** `GET /cards/{id}/matches`: current shop listings matched to this card (latest per shop). */
145
245
  matches(idOrTechnicalName, params = {}) {
246
+ const [query, requestOptions] = splitRequestOptions(params);
146
247
  return this.http.get(
147
- `/cards/${encodeURIComponent(idOrTechnicalName)}/matches${toQueryString(params)}`
248
+ `/cards/${encodeURIComponent(idOrTechnicalName)}/matches${toQueryString(query)}`,
249
+ requestOptions
148
250
  );
149
251
  }
150
252
  /** `GET /cards/{id}/reference-prices`: Cardmarket/TCGplayer/eBay/Tradera price history. Premium. */
151
253
  referencePrices(idOrTechnicalName, params = {}) {
152
- const [query, authToken] = splitAuthToken(params);
254
+ const [query, requestOptions] = splitRequestOptions(params);
153
255
  return this.http.get(
154
256
  `/cards/${encodeURIComponent(idOrTechnicalName)}/reference-prices${toQueryString(query)}`,
155
- { authToken }
257
+ requestOptions
156
258
  );
157
259
  }
158
260
  /** `GET /cards/{id}/prices`: individual marketplace sale records. Premium. */
159
261
  prices(idOrTechnicalName, params = {}) {
160
- const [query, authToken] = splitAuthToken(params);
161
- return this.http.get(`/cards/${encodeURIComponent(idOrTechnicalName)}/prices${toQueryString(query)}`, {
162
- authToken
163
- });
262
+ const [query, requestOptions] = splitRequestOptions(params);
263
+ return this.http.get(`/cards/${encodeURIComponent(idOrTechnicalName)}/prices${toQueryString(query)}`, requestOptions);
164
264
  }
165
265
  /** `GET /cards/{id}/pricing/live`: computed fresh for this request, not read from the last
166
266
  * stats job. Premium. */
@@ -171,16 +271,39 @@ var CardsResource = class {
171
271
  * `estimatedValue`, `lowestShopOffer`, `referencePriceSnapshotsByProvider` — refreshed once a day
172
272
  * by the nightly pricing/scraper jobs. `get()` returns content only; this is the separate,
173
273
  * shorter-cached call for the part of a card that actually changes day to day. */
174
- pricing(idOrTechnicalName) {
175
- return this.http.get(`/cards/${encodeURIComponent(idOrTechnicalName)}/pricing`);
274
+ pricing(idOrTechnicalName, options = {}) {
275
+ return this.http.get(`/cards/${encodeURIComponent(idOrTechnicalName)}/pricing`, options);
176
276
  }
177
277
  /** `GET /cards/pricing`: pricing for up to 200 cards in one request, keyed by `id` — the batch
178
278
  * counterpart to `pricing()`, for a page of results (a search page, an expansion's contents) that
179
279
  * needs pricing for many items at once. Unlike `get()`/`pricing()`, this only accepts `id`s, not
180
280
  * technicalNames — pass the `id`s already on the cards you fetched. Ids with no match are
181
281
  * silently omitted from the result rather than causing an error. */
182
- pricingBatch(ids) {
183
- return this.http.get(`/cards/pricing?ids=${ids.map(encodeURIComponent).join(",")}`);
282
+ pricingBatch(ids, options = {}) {
283
+ return this.http.get(`/cards/pricing?ids=${ids.map(encodeURIComponent).join(",")}`, options);
284
+ }
285
+ /** `GET /cards/technical-names`: every card's `technicalName` and `updatedAt`, unpaginated and
286
+ * with no pricing joins. Built for enumerating the whole catalog cheaply — a sitemap, or working
287
+ * out which items changed since your last sync — where `list()` would make you page through full
288
+ * card documents to learn the same two fields. */
289
+ technicalNames(options = {}) {
290
+ return this.http.get("/cards/technical-names", options);
291
+ }
292
+ /** `GET /cards/price-stats/daily`: daily average price history, cards only. The same data as
293
+ * `client.priceStats.daily()`, scoped to the card catalog so a filter like `expansion` can't pull
294
+ * in that expansion's sealed products too. */
295
+ dailyStats(params = {}) {
296
+ const [query, requestOptions] = splitRequestOptions(params);
297
+ return this.http.get(`/cards/price-stats/daily${toQueryString(query)}`, requestOptions);
298
+ }
299
+ /** `GET /cards/price-stats/estimated-values`: current estimated market value, cards only. The
300
+ * card-scoped counterpart to `client.priceStats.estimatedValues()`. */
301
+ estimatedValues(params = {}) {
302
+ const [query, requestOptions] = splitRequestOptions(params);
303
+ return this.http.get(
304
+ `/cards/price-stats/estimated-values${toQueryString(query)}`,
305
+ requestOptions
306
+ );
184
307
  }
185
308
  };
186
309
 
@@ -191,21 +314,42 @@ var ExpansionsResource = class {
191
314
  }
192
315
  http;
193
316
  /** `GET /expansions`: every expansion. Unwrapped to a plain array, nothing to paginate here. */
194
- async list() {
195
- const res = await this.http.get("/expansions");
317
+ async list(options = {}) {
318
+ const res = await this.http.get("/expansions", options);
196
319
  return res.data;
197
320
  }
198
- /** `GET /expansions/{technicalName}/products`: every card and sealed product in one
199
- * expansion, kept as separate `cards`/`sealed` groups. Content only, no pricing fields — pass the
200
- * `id`s from the result to `client.cards.pricingBatch()` / `client.products.pricingBatch()` if you
201
- * need pricing too. This mirrors the API 1:1 rather than fetching pricing for you, since pricing
202
- * for every item in an expansion is a second, separately-cached call the caller may not want. */
203
- products(technicalName) {
204
- return this.http.get(`/expansions/${encodeURIComponent(technicalName)}/products?grouped=true`);
205
- }
206
- /** `GET /expansions/{technicalName}/products/live-pricing`: computed fresh for every item in
207
- * this expansion, not read from the last stats job. Premium. */
208
- livePricing(technicalName, options = {}) {
321
+ /** `GET /expansions/{technicalName}`: metadata only — no cards or sealed products. Returns the
322
+ * smaller `ExpansionRef`, not the full `Expansion`: this is a plain lookup by technicalName, not
323
+ * the aggregation `list()` runs, so `sealedCount`/`cardCount`/`productCount` aren't available
324
+ * here. See `cards()` and `sealedProducts()` for this expansion's contents. */
325
+ get(technicalName, options = {}) {
326
+ return this.http.get(`/expansions/${encodeURIComponent(technicalName)}`, options);
327
+ }
328
+ /** `GET /expansions/{technicalName}/cards`: every card in this expansion. Content only, no
329
+ * pricing fields — pass the `id`s from the result to `client.cards.pricingBatch()` if you need
330
+ * pricing too. Sealed products are a separate call — see `sealedProducts()` — never merged into
331
+ * this one. */
332
+ cards(technicalName, options = {}) {
333
+ return this.http.get(`/expansions/${encodeURIComponent(technicalName)}/cards`, options);
334
+ }
335
+ /** `GET /expansions/{technicalName}/products`: every sealed product in this expansion. Content
336
+ * only, no pricing fields — pass the `id`s from the result to `client.products.pricingBatch()`
337
+ * if you need pricing too. Cards are a separate call — see `cards()` — never merged into this
338
+ * one. */
339
+ sealedProducts(technicalName, options = {}) {
340
+ return this.http.get(`/expansions/${encodeURIComponent(technicalName)}/products`, options);
341
+ }
342
+ /** `GET /expansions/{technicalName}/cards/live-pricing`: computed fresh for every card in this
343
+ * expansion, not read from the last stats job. Premium. */
344
+ cardsLivePricing(technicalName, options = {}) {
345
+ return this.http.get(
346
+ `/expansions/${encodeURIComponent(technicalName)}/cards/live-pricing`,
347
+ options
348
+ );
349
+ }
350
+ /** `GET /expansions/{technicalName}/products/live-pricing`: computed fresh for every sealed
351
+ * product in this expansion, not read from the last stats job. Premium. */
352
+ productsLivePricing(technicalName, options = {}) {
209
353
  return this.http.get(
210
354
  `/expansions/${encodeURIComponent(technicalName)}/products/live-pricing`,
211
355
  options
@@ -221,13 +365,13 @@ var PackRatesResource = class {
221
365
  http;
222
366
  /** `GET /pack-rates`: pull-rate odds for every expansion that has them. Unwrapped to a plain
223
367
  * array, nothing to paginate here. */
224
- async list() {
225
- const res = await this.http.get("/pack-rates");
368
+ async list(options = {}) {
369
+ const res = await this.http.get("/pack-rates", options);
226
370
  return res.data;
227
371
  }
228
372
  /** `GET /pack-rates/{expansionId}`: pull-rate odds for one expansion. */
229
- get(expansionId) {
230
- return this.http.get(`/pack-rates/${encodeURIComponent(expansionId)}`);
373
+ get(expansionId, options = {}) {
374
+ return this.http.get(`/pack-rates/${encodeURIComponent(expansionId)}`, options);
231
375
  }
232
376
  };
233
377
 
@@ -239,17 +383,20 @@ var PriceStatsResource = class {
239
383
  http;
240
384
  /** `GET /price-stats/daily`: daily average price history, filtered to matching product(s). */
241
385
  daily(params = {}) {
242
- return this.http.get(`/price-stats/daily${toQueryString(params)}`);
386
+ const [query, requestOptions] = splitRequestOptions(params);
387
+ return this.http.get(`/price-stats/daily${toQueryString(query)}`, requestOptions);
243
388
  }
244
389
  /** `GET /price-stats/estimated-values`: current estimated market value, filtered to matching
245
390
  * product(s). */
246
391
  estimatedValues(params = {}) {
247
- return this.http.get(`/price-stats/estimated-values${toQueryString(params)}`);
392
+ const [query, requestOptions] = splitRequestOptions(params);
393
+ return this.http.get(`/price-stats/estimated-values${toQueryString(query)}`, requestOptions);
248
394
  }
249
395
  /** `GET /price-stats/top-products`: items ranked by shop availability (how many shops carry
250
396
  * them), not by price. */
251
397
  topProducts(params = {}) {
252
- return this.http.get(`/price-stats/top-products${toQueryString(params)}`);
398
+ const [query, requestOptions] = splitRequestOptions(params);
399
+ return this.http.get(`/price-stats/top-products${toQueryString(query)}`, requestOptions);
253
400
  }
254
401
  /** `GET /price-stats/product/{id}`: daily price history, current estimate, and a variant-count
255
402
  * summary for one product. Premium. */
@@ -264,10 +411,10 @@ var PriceStatsResource = class {
264
411
  /** `GET /price-stats/product/{id}/daily`: daily price history for one product, with a
265
412
  * caller-chosen window. Premium. */
266
413
  productDaily(idOrTechnicalName, params = {}) {
267
- const [query, authToken] = splitAuthToken(params);
414
+ const [query, requestOptions] = splitRequestOptions(params);
268
415
  return this.http.get(
269
416
  `/price-stats/product/${encodeURIComponent(idOrTechnicalName)}/daily${toQueryString(query)}`,
270
- { authToken }
417
+ requestOptions
271
418
  );
272
419
  }
273
420
  /** `GET /price-stats/product/{id}/daily-last-30`: daily price history for the last 30 days
@@ -288,20 +435,20 @@ var PriceStatsResource = class {
288
435
  /** `GET /price-stats/product/{id}/by-variant`: price stats broken out per card condition/grade.
289
436
  * Premium. */
290
437
  productByVariant(idOrTechnicalName, params = {}) {
291
- const [query, authToken] = splitAuthToken(params);
438
+ const [query, requestOptions] = splitRequestOptions(params);
292
439
  return this.http.get(
293
440
  `/price-stats/product/${encodeURIComponent(idOrTechnicalName)}/by-variant${toQueryString(query)}`,
294
- { authToken }
441
+ requestOptions
295
442
  );
296
443
  }
297
444
  /** `GET /price-stats/product/{id}/daily-by-variant`: daily price history for one specific
298
445
  * condition/grade. `condition` is required for `cardType: 'loose'`; `gradingCompany` and `grade`
299
446
  * are required for `cardType: 'graded'`. Premium. */
300
447
  productDailyByVariant(idOrTechnicalName, params) {
301
- const [query, authToken] = splitAuthToken(params);
448
+ const [query, requestOptions] = splitRequestOptions(params);
302
449
  return this.http.get(
303
450
  `/price-stats/product/${encodeURIComponent(idOrTechnicalName)}/daily-by-variant${toQueryString(query)}`,
304
- { authToken }
451
+ requestOptions
305
452
  );
306
453
  }
307
454
  };
@@ -312,34 +459,43 @@ var ProductsResource = class {
312
459
  this.http = http;
313
460
  }
314
461
  http;
315
- /** `GET /product`: search or list sealed products. */
462
+ /** `GET /product`: list sealed products, newest first. No free-text search — use `search()` for
463
+ * that. */
316
464
  list(params = {}) {
317
- return this.http.get(`/product${toQueryString(params)}`);
465
+ const [query, requestOptions] = splitRequestOptions(params);
466
+ return this.http.get(`/product${toQueryString(query)}`, requestOptions);
467
+ }
468
+ /** `GET /product/search`: like `list()`, but with free-text search on the product name. Premium. */
469
+ search(params = {}) {
470
+ const [query, requestOptions] = splitRequestOptions(params);
471
+ return this.http.get(`/product/search${toQueryString(query)}`, requestOptions);
318
472
  }
319
473
  /** `GET /product/{id}`: fetch one sealed product by its id or technicalName. */
320
- get(idOrTechnicalName) {
321
- return this.http.get(`/product/${encodeURIComponent(idOrTechnicalName)}`);
474
+ get(idOrTechnicalName, options = {}) {
475
+ return this.http.get(`/product/${encodeURIComponent(idOrTechnicalName)}`, options);
322
476
  }
323
477
  /** `GET /product/{id}/matches`: current shop listings matched to this product (latest per shop). */
324
478
  matches(idOrTechnicalName, params = {}) {
479
+ const [query, requestOptions] = splitRequestOptions(params);
325
480
  return this.http.get(
326
- `/product/${encodeURIComponent(idOrTechnicalName)}/matches${toQueryString(params)}`
481
+ `/product/${encodeURIComponent(idOrTechnicalName)}/matches${toQueryString(query)}`,
482
+ requestOptions
327
483
  );
328
484
  }
329
485
  /** `GET /product/{id}/reference-prices`: Cardmarket/TCGplayer/Tradera price history. Premium. */
330
486
  referencePrices(idOrTechnicalName, params = {}) {
331
- const [query, authToken] = splitAuthToken(params);
487
+ const [query, requestOptions] = splitRequestOptions(params);
332
488
  return this.http.get(
333
489
  `/product/${encodeURIComponent(idOrTechnicalName)}/reference-prices${toQueryString(query)}`,
334
- { authToken }
490
+ requestOptions
335
491
  );
336
492
  }
337
493
  /** `GET /product/{id}/prices`: individual marketplace sale records. Premium. */
338
494
  prices(idOrTechnicalName, params = {}) {
339
- const [query, authToken] = splitAuthToken(params);
495
+ const [query, requestOptions] = splitRequestOptions(params);
340
496
  return this.http.get(
341
497
  `/product/${encodeURIComponent(idOrTechnicalName)}/prices${toQueryString(query)}`,
342
- { authToken }
498
+ requestOptions
343
499
  );
344
500
  }
345
501
  /** `GET /product/{id}/pricing/live`: computed fresh for this request, not read from the last
@@ -351,16 +507,38 @@ var ProductsResource = class {
351
507
  * `estimatedValue`, `lowestShopOffer`, `referencePriceSnapshotsByProvider` — refreshed once a day
352
508
  * by the nightly pricing/scraper jobs. `get()` returns content only; this is the separate,
353
509
  * shorter-cached call for the part of a product that actually changes day to day. */
354
- pricing(idOrTechnicalName) {
355
- return this.http.get(`/product/${encodeURIComponent(idOrTechnicalName)}/pricing`);
510
+ pricing(idOrTechnicalName, options = {}) {
511
+ return this.http.get(`/product/${encodeURIComponent(idOrTechnicalName)}/pricing`, options);
356
512
  }
357
513
  /** `GET /product/pricing`: pricing for up to 200 sealed products in one request, keyed by `id` —
358
514
  * the batch counterpart to `pricing()`, for a page of results (a search page, an expansion's
359
515
  * contents) that needs pricing for many items at once. Unlike `get()`/`pricing()`, this only
360
516
  * accepts `id`s, not technicalNames — pass the `id`s already on the products you fetched. Ids with
361
517
  * no match are silently omitted from the result rather than causing an error. */
362
- pricingBatch(ids) {
363
- return this.http.get(`/product/pricing?ids=${ids.map(encodeURIComponent).join(",")}`);
518
+ pricingBatch(ids, options = {}) {
519
+ return this.http.get(`/product/pricing?ids=${ids.map(encodeURIComponent).join(",")}`, options);
520
+ }
521
+ /** `GET /product/technical-names`: every sealed product's `technicalName` and `updatedAt`,
522
+ * unpaginated and with no pricing joins. The sealed counterpart to
523
+ * `client.cards.technicalNames()` — for sitemaps and incremental syncs. */
524
+ technicalNames(options = {}) {
525
+ return this.http.get("/product/technical-names", options);
526
+ }
527
+ /** `GET /product/price-stats/daily`: daily average price history, sealed products only. The same
528
+ * data as `client.priceStats.daily()`, scoped to the sealed catalog so a filter like `expansion`
529
+ * can't pull in that expansion's single cards too. */
530
+ dailyStats(params = {}) {
531
+ const [query, requestOptions] = splitRequestOptions(params);
532
+ return this.http.get(`/product/price-stats/daily${toQueryString(query)}`, requestOptions);
533
+ }
534
+ /** `GET /product/price-stats/estimated-values`: current estimated market value, sealed products
535
+ * only. The sealed-scoped counterpart to `client.priceStats.estimatedValues()`. */
536
+ estimatedValues(params = {}) {
537
+ const [query, requestOptions] = splitRequestOptions(params);
538
+ return this.http.get(
539
+ `/product/price-stats/estimated-values${toQueryString(query)}`,
540
+ requestOptions
541
+ );
364
542
  }
365
543
  };
366
544
 
@@ -372,24 +550,22 @@ var ShopMatchStatsResource = class {
372
550
  http;
373
551
  /** `GET /shop-match-stats/product/{productId}`: one product's price history, broken out per shop. */
374
552
  forProduct(productId, params = {}) {
375
- const [query, authToken] = splitAuthToken(params);
553
+ const [query, requestOptions] = splitRequestOptions(params);
376
554
  return this.http.get(
377
555
  `/shop-match-stats/product/${encodeURIComponent(productId)}${toQueryString(query)}`,
378
- { authToken }
556
+ requestOptions
379
557
  );
380
558
  }
381
559
  /** `GET /shop-match-stats/shop/{shop}`: one shop's price history, broken out per product. */
382
560
  forShop(shop, params = {}) {
383
- const [query, authToken] = splitAuthToken(params);
384
- return this.http.get(`/shop-match-stats/shop/${encodeURIComponent(shop)}${toQueryString(query)}`, {
385
- authToken
386
- });
561
+ const [query, requestOptions] = splitRequestOptions(params);
562
+ return this.http.get(`/shop-match-stats/shop/${encodeURIComponent(shop)}${toQueryString(query)}`, requestOptions);
387
563
  }
388
564
  /** `GET /shop-match-stats/compare`: one product's price at every shop that carries it, as of
389
565
  * one date (defaults to the latest). */
390
566
  compare(params) {
391
- const [query, authToken] = splitAuthToken(params);
392
- return this.http.get(`/shop-match-stats/compare${toQueryString(query)}`, { authToken });
567
+ const [query, requestOptions] = splitRequestOptions(params);
568
+ return this.http.get(`/shop-match-stats/compare${toQueryString(query)}`, requestOptions);
393
569
  }
394
570
  };
395
571
 
@@ -401,15 +577,21 @@ var ShopMatchesResource = class {
401
577
  http;
402
578
  /** `GET /shop-matches`: every current match across every shop (latest record per url+shop). */
403
579
  list(params = {}) {
404
- return this.http.get(`/shop-matches${toQueryString(params)}`);
580
+ const [query, requestOptions] = splitRequestOptions(params);
581
+ return this.http.get(`/shop-matches${toQueryString(query)}`, requestOptions);
405
582
  }
406
583
  /** `GET /shop-matches/{shop}`: every current match at one shop (latest record per url). */
407
584
  forShop(technicalName, params = {}) {
408
- return this.http.get(`/shop-matches/${encodeURIComponent(technicalName)}${toQueryString(params)}`);
585
+ const [query, requestOptions] = splitRequestOptions(params);
586
+ return this.http.get(
587
+ `/shop-matches/${encodeURIComponent(technicalName)}${toQueryString(query)}`,
588
+ requestOptions
589
+ );
409
590
  }
410
591
  /** `GET /shop-matches/shops`: match counts per shop (based on latest records only). */
411
592
  shopStats(params = {}) {
412
- return this.http.get(`/shop-matches/shops${toQueryString(params)}`);
593
+ const [query, requestOptions] = splitRequestOptions(params);
594
+ return this.http.get(`/shop-matches/shops${toQueryString(query)}`, requestOptions);
413
595
  }
414
596
  };
415
597
 
@@ -421,16 +603,16 @@ var ShopUrlsResource = class {
421
603
  http;
422
604
  /** `POST /shop-urls/submit`: submit a shop URL for scraping. */
423
605
  submit(params) {
424
- const { authToken, url, shop } = params;
425
- return this.http.post("/shop-urls/submit", { url, shop }, { authToken });
606
+ const { url, shop, ...requestOptions } = params;
607
+ return this.http.post("/shop-urls/submit", { url, shop }, requestOptions);
426
608
  }
427
609
  /** `PATCH /shop-urls/{id}/product`: manually assign (or clear) the product a shop URL resolves to. */
428
610
  assignProduct(shopUrlId, params) {
429
- const { authToken, productId } = params;
611
+ const { productId, ...requestOptions } = params;
430
612
  return this.http.patch(
431
613
  `/shop-urls/${encodeURIComponent(shopUrlId)}/product`,
432
614
  { productId },
433
- { authToken }
615
+ requestOptions
434
616
  );
435
617
  }
436
618
  };
@@ -443,12 +625,13 @@ var ShopsResource = class {
443
625
  http;
444
626
  /** `GET /shops`: every tracked shop. Unwrapped to a plain array, nothing to paginate here. */
445
627
  async list(params = {}) {
446
- const res = await this.http.get(`/shops${toQueryString(params)}`);
628
+ const [query, requestOptions] = splitRequestOptions(params);
629
+ const res = await this.http.get(`/shops${toQueryString(query)}`, requestOptions);
447
630
  return res.data;
448
631
  }
449
632
  /** `GET /shops/{id}`: fetch one shop by its id or technicalName. */
450
- get(idOrTechnicalName) {
451
- return this.http.get(`/shops/${encodeURIComponent(idOrTechnicalName)}`);
633
+ get(idOrTechnicalName, options = {}) {
634
+ return this.http.get(`/shops/${encodeURIComponent(idOrTechnicalName)}`, options);
452
635
  }
453
636
  };
454
637
 
@@ -459,8 +642,40 @@ var StatsResource = class {
459
642
  }
460
643
  http;
461
644
  /** `GET /stats`: platform-wide overview counts (shops, expansions, products, prices tracked). */
462
- platform() {
463
- return this.http.get("/stats");
645
+ platform(options = {}) {
646
+ return this.http.get("/stats", options);
647
+ }
648
+ };
649
+
650
+ // src/resources/webhooks.ts
651
+ var WebhooksResource = class {
652
+ constructor(http) {
653
+ this.http = http;
654
+ }
655
+ http;
656
+ /**
657
+ * `POST /webhooks`: register a new webhook.
658
+ *
659
+ * The returned `secret` is the only copy you will ever get — sign-verification depends on it and
660
+ * no endpoint reads it back. Persist it here, at creation, or delete the webhook and make a new
661
+ * one.
662
+ */
663
+ create(params) {
664
+ const { url, events, ...requestOptions } = params;
665
+ return this.http.post("/webhooks", { url, events }, requestOptions);
666
+ }
667
+ /** `GET /webhooks`: every webhook registered on this account. Secrets are never included. */
668
+ list(options = {}) {
669
+ return this.http.get("/webhooks", options);
670
+ }
671
+ /** `DELETE /webhooks/{id}`: revoke a webhook. Deliveries stop immediately; its secret is void. */
672
+ delete(webhookId, options = {}) {
673
+ return this.http.delete(`/webhooks/${encodeURIComponent(webhookId)}`, options);
674
+ }
675
+ /** `POST /webhooks/{id}/test`: send a sample delivery to the registered URL, so you can verify
676
+ * your endpoint and your signature check before waiting on a real event. */
677
+ test(webhookId, options = {}) {
678
+ return this.http.post(`/webhooks/${encodeURIComponent(webhookId)}/test`, void 0, options);
464
679
  }
465
680
  };
466
681
 
@@ -478,6 +693,9 @@ var TcgPriser = class {
478
693
  bargains;
479
694
  packRates;
480
695
  stats;
696
+ webhooks;
697
+ /** Holds the `HttpClient` so `creditsRemaining` can read the running value off it. */
698
+ http;
481
699
  /**
482
700
  * @param optionsOrAuthToken A subscriber's API token (`new TcgPriser(myApiToken)`), a full
483
701
  * `TcgPriserOptions` object, or omit it entirely for an anonymous, public-only client.
@@ -491,6 +709,7 @@ var TcgPriser = class {
491
709
  );
492
710
  }
493
711
  const http = new HttpClient({
712
+ timeoutMs: advanced.timeoutMs,
494
713
  baseUrl: advanced.baseUrl ?? DEFAULT_BASE_URL,
495
714
  // Bound to globalThis: both browsers and Node's undici implement fetch as a method that
496
715
  // checks its receiver, so an unbound reference throws "Illegal invocation" the moment it's
@@ -511,9 +730,29 @@ var TcgPriser = class {
511
730
  this.bargains = new BargainsResource(http);
512
731
  this.packRates = new PackRatesResource(http);
513
732
  this.stats = new StatsResource(http);
733
+ this.webhooks = new WebhooksResource(http);
734
+ this.http = http;
735
+ }
736
+ /**
737
+ * Credits left in this week's allowance, as of the last charged call this client made.
738
+ *
739
+ * The API returns `X-Credits-Remaining` on every response it charges for, so this needs no extra
740
+ * request — but it is only as current as your last premium call, and it is `undefined` until you
741
+ * make one. Uncharged calls (every public method, and any call authenticated with something other
742
+ * than an API token) don't update it, because the API doesn't meter them.
743
+ *
744
+ * ```ts
745
+ * await tcgpriser.cards.livePricing('fezandipiti-ex');
746
+ * if ((tcgpriser.creditsRemaining ?? Infinity) < 100) scheduleFewerRefreshes();
747
+ * ```
748
+ *
749
+ * Reading it in a browser additionally needs the API to expose the header via CORS, which it does.
750
+ */
751
+ get creditsRemaining() {
752
+ return this.http.creditsRemaining;
514
753
  }
515
754
  };
516
755
 
517
- export { DEFAULT_BASE_URL, TcgPriser, TcgPriserError };
756
+ export { DEFAULT_BASE_URL, DEFAULT_TIMEOUT_MS, TcgPriser, TcgPriserError };
518
757
  //# sourceMappingURL=index.js.map
519
758
  //# sourceMappingURL=index.js.map