tcgpriser 0.10.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/README.md +175 -17
- package/dist/index.cjs +299 -82
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +427 -62
- package/dist/index.d.ts +427 -62
- package/dist/index.js +299 -83
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
|
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",
|
|
@@ -46,6 +61,12 @@ var KNOWN_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
|
46
61
|
"creditsExhausted",
|
|
47
62
|
"internalError"
|
|
48
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
|
+
}
|
|
49
70
|
async function toApiError(res, url) {
|
|
50
71
|
const body = await res.text();
|
|
51
72
|
let code = "unknown";
|
|
@@ -60,18 +81,63 @@ async function toApiError(res, url) {
|
|
|
60
81
|
details = parsed.error?.details;
|
|
61
82
|
} catch {
|
|
62
83
|
}
|
|
63
|
-
return new TcgPriserError({
|
|
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
|
+
};
|
|
64
123
|
}
|
|
65
124
|
var HttpClient = class {
|
|
66
125
|
baseUrl;
|
|
67
126
|
fetchImpl;
|
|
68
127
|
defaultHeaders;
|
|
69
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;
|
|
70
135
|
constructor(options) {
|
|
71
136
|
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
72
137
|
this.fetchImpl = options.fetch;
|
|
73
138
|
this.defaultHeaders = options.headers ?? {};
|
|
74
139
|
this.defaultAuthToken = options.authToken;
|
|
140
|
+
this.defaultTimeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
75
141
|
}
|
|
76
142
|
get(path, requestOptions) {
|
|
77
143
|
return this.request("GET", path, void 0, requestOptions);
|
|
@@ -82,17 +148,42 @@ var HttpClient = class {
|
|
|
82
148
|
patch(path, body, requestOptions) {
|
|
83
149
|
return this.request("PATCH", path, body, requestOptions);
|
|
84
150
|
}
|
|
151
|
+
delete(path, requestOptions) {
|
|
152
|
+
return this.request("DELETE", path, void 0, requestOptions);
|
|
153
|
+
}
|
|
85
154
|
async request(method, path, body, requestOptions) {
|
|
86
155
|
const url = `${this.baseUrl}${path}`;
|
|
87
156
|
const authToken = requestOptions && "authToken" in requestOptions ? requestOptions.authToken : this.defaultAuthToken;
|
|
88
157
|
const headers = { Accept: "application/json", ...this.defaultHeaders };
|
|
89
158
|
if (authToken) headers.Authorization = `Bearer ${authToken}`;
|
|
90
159
|
if (body !== void 0) headers["Content-Type"] = "application/json";
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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;
|
|
96
187
|
if (!res.ok) throw await toApiError(res, url);
|
|
97
188
|
if (res.status === 204) return void 0;
|
|
98
189
|
return nullsToUndefined(await res.json());
|
|
@@ -119,13 +210,14 @@ var BargainsResource = class {
|
|
|
119
210
|
* count is fixed by the API (no `limit`/`skip` on the public tier); `pagination.hasMore` tells
|
|
120
211
|
* you if more exist. */
|
|
121
212
|
list(params = {}) {
|
|
122
|
-
|
|
213
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
214
|
+
return this.http.get(`/bargains${toQueryString(query)}`, requestOptions);
|
|
123
215
|
}
|
|
124
216
|
/** `GET /bargains/search`: like `list()`, but with real pagination and filters (shop, discount
|
|
125
217
|
* threshold, card condition/grade, free-text search). Premium. */
|
|
126
218
|
search(params = {}) {
|
|
127
|
-
const [query,
|
|
128
|
-
return this.http.get(`/bargains/search${toQueryString(query)}`,
|
|
219
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
220
|
+
return this.http.get(`/bargains/search${toQueryString(query)}`, requestOptions);
|
|
129
221
|
}
|
|
130
222
|
};
|
|
131
223
|
|
|
@@ -135,34 +227,40 @@ var CardsResource = class {
|
|
|
135
227
|
this.http = http;
|
|
136
228
|
}
|
|
137
229
|
http;
|
|
138
|
-
/** `GET /cards`: search
|
|
230
|
+
/** `GET /cards`: list cards, newest first. No free-text search — use `search()` for that. */
|
|
139
231
|
list(params = {}) {
|
|
140
|
-
|
|
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);
|
|
141
239
|
}
|
|
142
240
|
/** `GET /cards/{id}`: fetch one card by its id or technicalName. */
|
|
143
|
-
get(idOrTechnicalName) {
|
|
144
|
-
return this.http.get(`/cards/${encodeURIComponent(idOrTechnicalName)}
|
|
241
|
+
get(idOrTechnicalName, options = {}) {
|
|
242
|
+
return this.http.get(`/cards/${encodeURIComponent(idOrTechnicalName)}`, options);
|
|
145
243
|
}
|
|
146
244
|
/** `GET /cards/{id}/matches`: current shop listings matched to this card (latest per shop). */
|
|
147
245
|
matches(idOrTechnicalName, params = {}) {
|
|
246
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
148
247
|
return this.http.get(
|
|
149
|
-
`/cards/${encodeURIComponent(idOrTechnicalName)}/matches${toQueryString(
|
|
248
|
+
`/cards/${encodeURIComponent(idOrTechnicalName)}/matches${toQueryString(query)}`,
|
|
249
|
+
requestOptions
|
|
150
250
|
);
|
|
151
251
|
}
|
|
152
252
|
/** `GET /cards/{id}/reference-prices`: Cardmarket/TCGplayer/eBay/Tradera price history. Premium. */
|
|
153
253
|
referencePrices(idOrTechnicalName, params = {}) {
|
|
154
|
-
const [query,
|
|
254
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
155
255
|
return this.http.get(
|
|
156
256
|
`/cards/${encodeURIComponent(idOrTechnicalName)}/reference-prices${toQueryString(query)}`,
|
|
157
|
-
|
|
257
|
+
requestOptions
|
|
158
258
|
);
|
|
159
259
|
}
|
|
160
260
|
/** `GET /cards/{id}/prices`: individual marketplace sale records. Premium. */
|
|
161
261
|
prices(idOrTechnicalName, params = {}) {
|
|
162
|
-
const [query,
|
|
163
|
-
return this.http.get(`/cards/${encodeURIComponent(idOrTechnicalName)}/prices${toQueryString(query)}`,
|
|
164
|
-
authToken
|
|
165
|
-
});
|
|
262
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
263
|
+
return this.http.get(`/cards/${encodeURIComponent(idOrTechnicalName)}/prices${toQueryString(query)}`, requestOptions);
|
|
166
264
|
}
|
|
167
265
|
/** `GET /cards/{id}/pricing/live`: computed fresh for this request, not read from the last
|
|
168
266
|
* stats job. Premium. */
|
|
@@ -173,16 +271,39 @@ var CardsResource = class {
|
|
|
173
271
|
* `estimatedValue`, `lowestShopOffer`, `referencePriceSnapshotsByProvider` — refreshed once a day
|
|
174
272
|
* by the nightly pricing/scraper jobs. `get()` returns content only; this is the separate,
|
|
175
273
|
* shorter-cached call for the part of a card that actually changes day to day. */
|
|
176
|
-
pricing(idOrTechnicalName) {
|
|
177
|
-
return this.http.get(`/cards/${encodeURIComponent(idOrTechnicalName)}/pricing
|
|
274
|
+
pricing(idOrTechnicalName, options = {}) {
|
|
275
|
+
return this.http.get(`/cards/${encodeURIComponent(idOrTechnicalName)}/pricing`, options);
|
|
178
276
|
}
|
|
179
277
|
/** `GET /cards/pricing`: pricing for up to 200 cards in one request, keyed by `id` — the batch
|
|
180
278
|
* counterpart to `pricing()`, for a page of results (a search page, an expansion's contents) that
|
|
181
279
|
* needs pricing for many items at once. Unlike `get()`/`pricing()`, this only accepts `id`s, not
|
|
182
280
|
* technicalNames — pass the `id`s already on the cards you fetched. Ids with no match are
|
|
183
281
|
* silently omitted from the result rather than causing an error. */
|
|
184
|
-
pricingBatch(ids) {
|
|
185
|
-
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
|
+
);
|
|
186
307
|
}
|
|
187
308
|
};
|
|
188
309
|
|
|
@@ -193,30 +314,30 @@ var ExpansionsResource = class {
|
|
|
193
314
|
}
|
|
194
315
|
http;
|
|
195
316
|
/** `GET /expansions`: every expansion. Unwrapped to a plain array, nothing to paginate here. */
|
|
196
|
-
async list() {
|
|
197
|
-
const res = await this.http.get("/expansions");
|
|
317
|
+
async list(options = {}) {
|
|
318
|
+
const res = await this.http.get("/expansions", options);
|
|
198
319
|
return res.data;
|
|
199
320
|
}
|
|
200
321
|
/** `GET /expansions/{technicalName}`: metadata only — no cards or sealed products. Returns the
|
|
201
322
|
* smaller `ExpansionRef`, not the full `Expansion`: this is a plain lookup by technicalName, not
|
|
202
323
|
* the aggregation `list()` runs, so `sealedCount`/`cardCount`/`productCount` aren't available
|
|
203
324
|
* here. See `cards()` and `sealedProducts()` for this expansion's contents. */
|
|
204
|
-
get(technicalName) {
|
|
205
|
-
return this.http.get(`/expansions/${encodeURIComponent(technicalName)}
|
|
325
|
+
get(technicalName, options = {}) {
|
|
326
|
+
return this.http.get(`/expansions/${encodeURIComponent(technicalName)}`, options);
|
|
206
327
|
}
|
|
207
328
|
/** `GET /expansions/{technicalName}/cards`: every card in this expansion. Content only, no
|
|
208
329
|
* pricing fields — pass the `id`s from the result to `client.cards.pricingBatch()` if you need
|
|
209
330
|
* pricing too. Sealed products are a separate call — see `sealedProducts()` — never merged into
|
|
210
331
|
* this one. */
|
|
211
|
-
cards(technicalName) {
|
|
212
|
-
return this.http.get(`/expansions/${encodeURIComponent(technicalName)}/cards
|
|
332
|
+
cards(technicalName, options = {}) {
|
|
333
|
+
return this.http.get(`/expansions/${encodeURIComponent(technicalName)}/cards`, options);
|
|
213
334
|
}
|
|
214
335
|
/** `GET /expansions/{technicalName}/products`: every sealed product in this expansion. Content
|
|
215
336
|
* only, no pricing fields — pass the `id`s from the result to `client.products.pricingBatch()`
|
|
216
337
|
* if you need pricing too. Cards are a separate call — see `cards()` — never merged into this
|
|
217
338
|
* one. */
|
|
218
|
-
sealedProducts(technicalName) {
|
|
219
|
-
return this.http.get(`/expansions/${encodeURIComponent(technicalName)}/products
|
|
339
|
+
sealedProducts(technicalName, options = {}) {
|
|
340
|
+
return this.http.get(`/expansions/${encodeURIComponent(technicalName)}/products`, options);
|
|
220
341
|
}
|
|
221
342
|
/** `GET /expansions/{technicalName}/cards/live-pricing`: computed fresh for every card in this
|
|
222
343
|
* expansion, not read from the last stats job. Premium. */
|
|
@@ -244,13 +365,13 @@ var PackRatesResource = class {
|
|
|
244
365
|
http;
|
|
245
366
|
/** `GET /pack-rates`: pull-rate odds for every expansion that has them. Unwrapped to a plain
|
|
246
367
|
* array, nothing to paginate here. */
|
|
247
|
-
async list() {
|
|
248
|
-
const res = await this.http.get("/pack-rates");
|
|
368
|
+
async list(options = {}) {
|
|
369
|
+
const res = await this.http.get("/pack-rates", options);
|
|
249
370
|
return res.data;
|
|
250
371
|
}
|
|
251
372
|
/** `GET /pack-rates/{expansionId}`: pull-rate odds for one expansion. */
|
|
252
|
-
get(expansionId) {
|
|
253
|
-
return this.http.get(`/pack-rates/${encodeURIComponent(expansionId)}
|
|
373
|
+
get(expansionId, options = {}) {
|
|
374
|
+
return this.http.get(`/pack-rates/${encodeURIComponent(expansionId)}`, options);
|
|
254
375
|
}
|
|
255
376
|
};
|
|
256
377
|
|
|
@@ -262,17 +383,20 @@ var PriceStatsResource = class {
|
|
|
262
383
|
http;
|
|
263
384
|
/** `GET /price-stats/daily`: daily average price history, filtered to matching product(s). */
|
|
264
385
|
daily(params = {}) {
|
|
265
|
-
|
|
386
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
387
|
+
return this.http.get(`/price-stats/daily${toQueryString(query)}`, requestOptions);
|
|
266
388
|
}
|
|
267
389
|
/** `GET /price-stats/estimated-values`: current estimated market value, filtered to matching
|
|
268
390
|
* product(s). */
|
|
269
391
|
estimatedValues(params = {}) {
|
|
270
|
-
|
|
392
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
393
|
+
return this.http.get(`/price-stats/estimated-values${toQueryString(query)}`, requestOptions);
|
|
271
394
|
}
|
|
272
395
|
/** `GET /price-stats/top-products`: items ranked by shop availability (how many shops carry
|
|
273
396
|
* them), not by price. */
|
|
274
397
|
topProducts(params = {}) {
|
|
275
|
-
|
|
398
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
399
|
+
return this.http.get(`/price-stats/top-products${toQueryString(query)}`, requestOptions);
|
|
276
400
|
}
|
|
277
401
|
/** `GET /price-stats/product/{id}`: daily price history, current estimate, and a variant-count
|
|
278
402
|
* summary for one product. Premium. */
|
|
@@ -287,10 +411,10 @@ var PriceStatsResource = class {
|
|
|
287
411
|
/** `GET /price-stats/product/{id}/daily`: daily price history for one product, with a
|
|
288
412
|
* caller-chosen window. Premium. */
|
|
289
413
|
productDaily(idOrTechnicalName, params = {}) {
|
|
290
|
-
const [query,
|
|
414
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
291
415
|
return this.http.get(
|
|
292
416
|
`/price-stats/product/${encodeURIComponent(idOrTechnicalName)}/daily${toQueryString(query)}`,
|
|
293
|
-
|
|
417
|
+
requestOptions
|
|
294
418
|
);
|
|
295
419
|
}
|
|
296
420
|
/** `GET /price-stats/product/{id}/daily-last-30`: daily price history for the last 30 days
|
|
@@ -311,20 +435,20 @@ var PriceStatsResource = class {
|
|
|
311
435
|
/** `GET /price-stats/product/{id}/by-variant`: price stats broken out per card condition/grade.
|
|
312
436
|
* Premium. */
|
|
313
437
|
productByVariant(idOrTechnicalName, params = {}) {
|
|
314
|
-
const [query,
|
|
438
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
315
439
|
return this.http.get(
|
|
316
440
|
`/price-stats/product/${encodeURIComponent(idOrTechnicalName)}/by-variant${toQueryString(query)}`,
|
|
317
|
-
|
|
441
|
+
requestOptions
|
|
318
442
|
);
|
|
319
443
|
}
|
|
320
444
|
/** `GET /price-stats/product/{id}/daily-by-variant`: daily price history for one specific
|
|
321
445
|
* condition/grade. `condition` is required for `cardType: 'loose'`; `gradingCompany` and `grade`
|
|
322
446
|
* are required for `cardType: 'graded'`. Premium. */
|
|
323
447
|
productDailyByVariant(idOrTechnicalName, params) {
|
|
324
|
-
const [query,
|
|
448
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
325
449
|
return this.http.get(
|
|
326
450
|
`/price-stats/product/${encodeURIComponent(idOrTechnicalName)}/daily-by-variant${toQueryString(query)}`,
|
|
327
|
-
|
|
451
|
+
requestOptions
|
|
328
452
|
);
|
|
329
453
|
}
|
|
330
454
|
};
|
|
@@ -335,34 +459,43 @@ var ProductsResource = class {
|
|
|
335
459
|
this.http = http;
|
|
336
460
|
}
|
|
337
461
|
http;
|
|
338
|
-
/** `GET /product`:
|
|
462
|
+
/** `GET /product`: list sealed products, newest first. No free-text search — use `search()` for
|
|
463
|
+
* that. */
|
|
339
464
|
list(params = {}) {
|
|
340
|
-
|
|
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);
|
|
341
472
|
}
|
|
342
473
|
/** `GET /product/{id}`: fetch one sealed product by its id or technicalName. */
|
|
343
|
-
get(idOrTechnicalName) {
|
|
344
|
-
return this.http.get(`/product/${encodeURIComponent(idOrTechnicalName)}
|
|
474
|
+
get(idOrTechnicalName, options = {}) {
|
|
475
|
+
return this.http.get(`/product/${encodeURIComponent(idOrTechnicalName)}`, options);
|
|
345
476
|
}
|
|
346
477
|
/** `GET /product/{id}/matches`: current shop listings matched to this product (latest per shop). */
|
|
347
478
|
matches(idOrTechnicalName, params = {}) {
|
|
479
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
348
480
|
return this.http.get(
|
|
349
|
-
`/product/${encodeURIComponent(idOrTechnicalName)}/matches${toQueryString(
|
|
481
|
+
`/product/${encodeURIComponent(idOrTechnicalName)}/matches${toQueryString(query)}`,
|
|
482
|
+
requestOptions
|
|
350
483
|
);
|
|
351
484
|
}
|
|
352
485
|
/** `GET /product/{id}/reference-prices`: Cardmarket/TCGplayer/Tradera price history. Premium. */
|
|
353
486
|
referencePrices(idOrTechnicalName, params = {}) {
|
|
354
|
-
const [query,
|
|
487
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
355
488
|
return this.http.get(
|
|
356
489
|
`/product/${encodeURIComponent(idOrTechnicalName)}/reference-prices${toQueryString(query)}`,
|
|
357
|
-
|
|
490
|
+
requestOptions
|
|
358
491
|
);
|
|
359
492
|
}
|
|
360
493
|
/** `GET /product/{id}/prices`: individual marketplace sale records. Premium. */
|
|
361
494
|
prices(idOrTechnicalName, params = {}) {
|
|
362
|
-
const [query,
|
|
495
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
363
496
|
return this.http.get(
|
|
364
497
|
`/product/${encodeURIComponent(idOrTechnicalName)}/prices${toQueryString(query)}`,
|
|
365
|
-
|
|
498
|
+
requestOptions
|
|
366
499
|
);
|
|
367
500
|
}
|
|
368
501
|
/** `GET /product/{id}/pricing/live`: computed fresh for this request, not read from the last
|
|
@@ -374,16 +507,38 @@ var ProductsResource = class {
|
|
|
374
507
|
* `estimatedValue`, `lowestShopOffer`, `referencePriceSnapshotsByProvider` — refreshed once a day
|
|
375
508
|
* by the nightly pricing/scraper jobs. `get()` returns content only; this is the separate,
|
|
376
509
|
* shorter-cached call for the part of a product that actually changes day to day. */
|
|
377
|
-
pricing(idOrTechnicalName) {
|
|
378
|
-
return this.http.get(`/product/${encodeURIComponent(idOrTechnicalName)}/pricing
|
|
510
|
+
pricing(idOrTechnicalName, options = {}) {
|
|
511
|
+
return this.http.get(`/product/${encodeURIComponent(idOrTechnicalName)}/pricing`, options);
|
|
379
512
|
}
|
|
380
513
|
/** `GET /product/pricing`: pricing for up to 200 sealed products in one request, keyed by `id` —
|
|
381
514
|
* the batch counterpart to `pricing()`, for a page of results (a search page, an expansion's
|
|
382
515
|
* contents) that needs pricing for many items at once. Unlike `get()`/`pricing()`, this only
|
|
383
516
|
* accepts `id`s, not technicalNames — pass the `id`s already on the products you fetched. Ids with
|
|
384
517
|
* no match are silently omitted from the result rather than causing an error. */
|
|
385
|
-
pricingBatch(ids) {
|
|
386
|
-
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
|
+
);
|
|
387
542
|
}
|
|
388
543
|
};
|
|
389
544
|
|
|
@@ -395,24 +550,22 @@ var ShopMatchStatsResource = class {
|
|
|
395
550
|
http;
|
|
396
551
|
/** `GET /shop-match-stats/product/{productId}`: one product's price history, broken out per shop. */
|
|
397
552
|
forProduct(productId, params = {}) {
|
|
398
|
-
const [query,
|
|
553
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
399
554
|
return this.http.get(
|
|
400
555
|
`/shop-match-stats/product/${encodeURIComponent(productId)}${toQueryString(query)}`,
|
|
401
|
-
|
|
556
|
+
requestOptions
|
|
402
557
|
);
|
|
403
558
|
}
|
|
404
559
|
/** `GET /shop-match-stats/shop/{shop}`: one shop's price history, broken out per product. */
|
|
405
560
|
forShop(shop, params = {}) {
|
|
406
|
-
const [query,
|
|
407
|
-
return this.http.get(`/shop-match-stats/shop/${encodeURIComponent(shop)}${toQueryString(query)}`,
|
|
408
|
-
authToken
|
|
409
|
-
});
|
|
561
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
562
|
+
return this.http.get(`/shop-match-stats/shop/${encodeURIComponent(shop)}${toQueryString(query)}`, requestOptions);
|
|
410
563
|
}
|
|
411
564
|
/** `GET /shop-match-stats/compare`: one product's price at every shop that carries it, as of
|
|
412
565
|
* one date (defaults to the latest). */
|
|
413
566
|
compare(params) {
|
|
414
|
-
const [query,
|
|
415
|
-
return this.http.get(`/shop-match-stats/compare${toQueryString(query)}`,
|
|
567
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
568
|
+
return this.http.get(`/shop-match-stats/compare${toQueryString(query)}`, requestOptions);
|
|
416
569
|
}
|
|
417
570
|
};
|
|
418
571
|
|
|
@@ -424,15 +577,21 @@ var ShopMatchesResource = class {
|
|
|
424
577
|
http;
|
|
425
578
|
/** `GET /shop-matches`: every current match across every shop (latest record per url+shop). */
|
|
426
579
|
list(params = {}) {
|
|
427
|
-
|
|
580
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
581
|
+
return this.http.get(`/shop-matches${toQueryString(query)}`, requestOptions);
|
|
428
582
|
}
|
|
429
583
|
/** `GET /shop-matches/{shop}`: every current match at one shop (latest record per url). */
|
|
430
584
|
forShop(technicalName, params = {}) {
|
|
431
|
-
|
|
585
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
586
|
+
return this.http.get(
|
|
587
|
+
`/shop-matches/${encodeURIComponent(technicalName)}${toQueryString(query)}`,
|
|
588
|
+
requestOptions
|
|
589
|
+
);
|
|
432
590
|
}
|
|
433
591
|
/** `GET /shop-matches/shops`: match counts per shop (based on latest records only). */
|
|
434
592
|
shopStats(params = {}) {
|
|
435
|
-
|
|
593
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
594
|
+
return this.http.get(`/shop-matches/shops${toQueryString(query)}`, requestOptions);
|
|
436
595
|
}
|
|
437
596
|
};
|
|
438
597
|
|
|
@@ -444,16 +603,16 @@ var ShopUrlsResource = class {
|
|
|
444
603
|
http;
|
|
445
604
|
/** `POST /shop-urls/submit`: submit a shop URL for scraping. */
|
|
446
605
|
submit(params) {
|
|
447
|
-
const {
|
|
448
|
-
return this.http.post("/shop-urls/submit", { url, shop },
|
|
606
|
+
const { url, shop, ...requestOptions } = params;
|
|
607
|
+
return this.http.post("/shop-urls/submit", { url, shop }, requestOptions);
|
|
449
608
|
}
|
|
450
609
|
/** `PATCH /shop-urls/{id}/product`: manually assign (or clear) the product a shop URL resolves to. */
|
|
451
610
|
assignProduct(shopUrlId, params) {
|
|
452
|
-
const {
|
|
611
|
+
const { productId, ...requestOptions } = params;
|
|
453
612
|
return this.http.patch(
|
|
454
613
|
`/shop-urls/${encodeURIComponent(shopUrlId)}/product`,
|
|
455
614
|
{ productId },
|
|
456
|
-
|
|
615
|
+
requestOptions
|
|
457
616
|
);
|
|
458
617
|
}
|
|
459
618
|
};
|
|
@@ -466,12 +625,13 @@ var ShopsResource = class {
|
|
|
466
625
|
http;
|
|
467
626
|
/** `GET /shops`: every tracked shop. Unwrapped to a plain array, nothing to paginate here. */
|
|
468
627
|
async list(params = {}) {
|
|
469
|
-
const
|
|
628
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
629
|
+
const res = await this.http.get(`/shops${toQueryString(query)}`, requestOptions);
|
|
470
630
|
return res.data;
|
|
471
631
|
}
|
|
472
632
|
/** `GET /shops/{id}`: fetch one shop by its id or technicalName. */
|
|
473
|
-
get(idOrTechnicalName) {
|
|
474
|
-
return this.http.get(`/shops/${encodeURIComponent(idOrTechnicalName)}
|
|
633
|
+
get(idOrTechnicalName, options = {}) {
|
|
634
|
+
return this.http.get(`/shops/${encodeURIComponent(idOrTechnicalName)}`, options);
|
|
475
635
|
}
|
|
476
636
|
};
|
|
477
637
|
|
|
@@ -482,8 +642,40 @@ var StatsResource = class {
|
|
|
482
642
|
}
|
|
483
643
|
http;
|
|
484
644
|
/** `GET /stats`: platform-wide overview counts (shops, expansions, products, prices tracked). */
|
|
485
|
-
platform() {
|
|
486
|
-
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);
|
|
487
679
|
}
|
|
488
680
|
};
|
|
489
681
|
|
|
@@ -501,6 +693,9 @@ var TcgPriser = class {
|
|
|
501
693
|
bargains;
|
|
502
694
|
packRates;
|
|
503
695
|
stats;
|
|
696
|
+
webhooks;
|
|
697
|
+
/** Holds the `HttpClient` so `creditsRemaining` can read the running value off it. */
|
|
698
|
+
http;
|
|
504
699
|
/**
|
|
505
700
|
* @param optionsOrAuthToken A subscriber's API token (`new TcgPriser(myApiToken)`), a full
|
|
506
701
|
* `TcgPriserOptions` object, or omit it entirely for an anonymous, public-only client.
|
|
@@ -514,6 +709,7 @@ var TcgPriser = class {
|
|
|
514
709
|
);
|
|
515
710
|
}
|
|
516
711
|
const http = new HttpClient({
|
|
712
|
+
timeoutMs: advanced.timeoutMs,
|
|
517
713
|
baseUrl: advanced.baseUrl ?? DEFAULT_BASE_URL,
|
|
518
714
|
// Bound to globalThis: both browsers and Node's undici implement fetch as a method that
|
|
519
715
|
// checks its receiver, so an unbound reference throws "Illegal invocation" the moment it's
|
|
@@ -534,9 +730,29 @@ var TcgPriser = class {
|
|
|
534
730
|
this.bargains = new BargainsResource(http);
|
|
535
731
|
this.packRates = new PackRatesResource(http);
|
|
536
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;
|
|
537
753
|
}
|
|
538
754
|
};
|
|
539
755
|
|
|
540
|
-
export { DEFAULT_BASE_URL, TcgPriser, TcgPriserError };
|
|
756
|
+
export { DEFAULT_BASE_URL, DEFAULT_TIMEOUT_MS, TcgPriser, TcgPriserError };
|
|
541
757
|
//# sourceMappingURL=index.js.map
|
|
542
758
|
//# sourceMappingURL=index.js.map
|