tcgpriser 0.10.0 → 1.0.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 +214 -19
- package/dist/index.cjs +335 -84
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +576 -79
- package/dist/index.d.ts +576 -79
- package/dist/index.js +335 -85
- 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,32 @@ 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);
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// src/resources/brands.ts
|
|
225
|
+
var BrandsResource = class {
|
|
226
|
+
constructor(http) {
|
|
227
|
+
this.http = http;
|
|
228
|
+
}
|
|
229
|
+
http;
|
|
230
|
+
/** `GET /brands`: every brand. Unwrapped to a plain array, nothing to paginate here — same
|
|
231
|
+
* shape as `expansions.list()`/`shops.list()`. */
|
|
232
|
+
async list(options = {}) {
|
|
233
|
+
const res = await this.http.get("/brands", options);
|
|
234
|
+
return res.data;
|
|
235
|
+
}
|
|
236
|
+
/** `GET /brands/{id}`: fetch one brand by its id or technicalName. */
|
|
237
|
+
get(idOrTechnicalName, options = {}) {
|
|
238
|
+
return this.http.get(`/brands/${encodeURIComponent(idOrTechnicalName)}`, options);
|
|
129
239
|
}
|
|
130
240
|
};
|
|
131
241
|
|
|
@@ -135,34 +245,45 @@ var CardsResource = class {
|
|
|
135
245
|
this.http = http;
|
|
136
246
|
}
|
|
137
247
|
http;
|
|
138
|
-
/** `GET /cards`: search
|
|
248
|
+
/** `GET /cards`: list cards, newest first. No free-text search — use `search()` for that. */
|
|
139
249
|
list(params = {}) {
|
|
140
|
-
|
|
250
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
251
|
+
return this.http.get(`/cards${toQueryString(query)}`, requestOptions);
|
|
252
|
+
}
|
|
253
|
+
/** `GET /cards/search`: like `list()`, but with free-text search on card and set names. Premium. */
|
|
254
|
+
search(params = {}) {
|
|
255
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
256
|
+
return this.http.get(`/cards/search${toQueryString(query)}`, requestOptions);
|
|
141
257
|
}
|
|
142
|
-
/** `GET /cards/{id}`: fetch one card by its id or technicalName.
|
|
143
|
-
|
|
144
|
-
|
|
258
|
+
/** `GET /cards/{id}`: fetch one card by its id or technicalName. Pass `brand` if two brands
|
|
259
|
+
* might share the same technicalName — see `GetCardParams`. */
|
|
260
|
+
get(idOrTechnicalName, params = {}) {
|
|
261
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
262
|
+
return this.http.get(
|
|
263
|
+
`/cards/${encodeURIComponent(idOrTechnicalName)}${toQueryString(query)}`,
|
|
264
|
+
requestOptions
|
|
265
|
+
);
|
|
145
266
|
}
|
|
146
267
|
/** `GET /cards/{id}/matches`: current shop listings matched to this card (latest per shop). */
|
|
147
268
|
matches(idOrTechnicalName, params = {}) {
|
|
269
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
148
270
|
return this.http.get(
|
|
149
|
-
`/cards/${encodeURIComponent(idOrTechnicalName)}/matches${toQueryString(
|
|
271
|
+
`/cards/${encodeURIComponent(idOrTechnicalName)}/matches${toQueryString(query)}`,
|
|
272
|
+
requestOptions
|
|
150
273
|
);
|
|
151
274
|
}
|
|
152
275
|
/** `GET /cards/{id}/reference-prices`: Cardmarket/TCGplayer/eBay/Tradera price history. Premium. */
|
|
153
276
|
referencePrices(idOrTechnicalName, params = {}) {
|
|
154
|
-
const [query,
|
|
277
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
155
278
|
return this.http.get(
|
|
156
279
|
`/cards/${encodeURIComponent(idOrTechnicalName)}/reference-prices${toQueryString(query)}`,
|
|
157
|
-
|
|
280
|
+
requestOptions
|
|
158
281
|
);
|
|
159
282
|
}
|
|
160
283
|
/** `GET /cards/{id}/prices`: individual marketplace sale records. Premium. */
|
|
161
284
|
prices(idOrTechnicalName, params = {}) {
|
|
162
|
-
const [query,
|
|
163
|
-
return this.http.get(`/cards/${encodeURIComponent(idOrTechnicalName)}/prices${toQueryString(query)}`,
|
|
164
|
-
authToken
|
|
165
|
-
});
|
|
285
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
286
|
+
return this.http.get(`/cards/${encodeURIComponent(idOrTechnicalName)}/prices${toQueryString(query)}`, requestOptions);
|
|
166
287
|
}
|
|
167
288
|
/** `GET /cards/{id}/pricing/live`: computed fresh for this request, not read from the last
|
|
168
289
|
* stats job. Premium. */
|
|
@@ -173,16 +294,39 @@ var CardsResource = class {
|
|
|
173
294
|
* `estimatedValue`, `lowestShopOffer`, `referencePriceSnapshotsByProvider` — refreshed once a day
|
|
174
295
|
* by the nightly pricing/scraper jobs. `get()` returns content only; this is the separate,
|
|
175
296
|
* 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
|
|
297
|
+
pricing(idOrTechnicalName, options = {}) {
|
|
298
|
+
return this.http.get(`/cards/${encodeURIComponent(idOrTechnicalName)}/pricing`, options);
|
|
178
299
|
}
|
|
179
300
|
/** `GET /cards/pricing`: pricing for up to 200 cards in one request, keyed by `id` — the batch
|
|
180
301
|
* counterpart to `pricing()`, for a page of results (a search page, an expansion's contents) that
|
|
181
302
|
* needs pricing for many items at once. Unlike `get()`/`pricing()`, this only accepts `id`s, not
|
|
182
303
|
* technicalNames — pass the `id`s already on the cards you fetched. Ids with no match are
|
|
183
304
|
* 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(",")}
|
|
305
|
+
pricingBatch(ids, options = {}) {
|
|
306
|
+
return this.http.get(`/cards/pricing?ids=${ids.map(encodeURIComponent).join(",")}`, options);
|
|
307
|
+
}
|
|
308
|
+
/** `GET /cards/technical-names`: every card's `technicalName` and `updatedAt`, unpaginated and
|
|
309
|
+
* with no pricing joins. Built for enumerating the whole catalog cheaply — a sitemap, or working
|
|
310
|
+
* out which items changed since your last sync — where `list()` would make you page through full
|
|
311
|
+
* card documents to learn the same two fields. */
|
|
312
|
+
technicalNames(options = {}) {
|
|
313
|
+
return this.http.get("/cards/technical-names", options);
|
|
314
|
+
}
|
|
315
|
+
/** `GET /cards/price-stats/daily`: daily average price history, cards only. The same data as
|
|
316
|
+
* `client.priceStats.daily()`, scoped to the card catalog so a filter like `expansion` can't pull
|
|
317
|
+
* in that expansion's sealed products too. */
|
|
318
|
+
dailyStats(params = {}) {
|
|
319
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
320
|
+
return this.http.get(`/cards/price-stats/daily${toQueryString(query)}`, requestOptions);
|
|
321
|
+
}
|
|
322
|
+
/** `GET /cards/price-stats/estimated-values`: current estimated market value, cards only. The
|
|
323
|
+
* card-scoped counterpart to `client.priceStats.estimatedValues()`. */
|
|
324
|
+
estimatedValues(params = {}) {
|
|
325
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
326
|
+
return this.http.get(
|
|
327
|
+
`/cards/price-stats/estimated-values${toQueryString(query)}`,
|
|
328
|
+
requestOptions
|
|
329
|
+
);
|
|
186
330
|
}
|
|
187
331
|
};
|
|
188
332
|
|
|
@@ -193,30 +337,34 @@ var ExpansionsResource = class {
|
|
|
193
337
|
}
|
|
194
338
|
http;
|
|
195
339
|
/** `GET /expansions`: every expansion. Unwrapped to a plain array, nothing to paginate here. */
|
|
196
|
-
async list() {
|
|
197
|
-
const
|
|
340
|
+
async list(params = {}) {
|
|
341
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
342
|
+
const res = await this.http.get(
|
|
343
|
+
`/expansions${toQueryString(query)}`,
|
|
344
|
+
requestOptions
|
|
345
|
+
);
|
|
198
346
|
return res.data;
|
|
199
347
|
}
|
|
200
348
|
/** `GET /expansions/{technicalName}`: metadata only — no cards or sealed products. Returns the
|
|
201
349
|
* smaller `ExpansionRef`, not the full `Expansion`: this is a plain lookup by technicalName, not
|
|
202
350
|
* the aggregation `list()` runs, so `sealedCount`/`cardCount`/`productCount` aren't available
|
|
203
351
|
* here. See `cards()` and `sealedProducts()` for this expansion's contents. */
|
|
204
|
-
get(technicalName) {
|
|
205
|
-
return this.http.get(`/expansions/${encodeURIComponent(technicalName)}
|
|
352
|
+
get(technicalName, options = {}) {
|
|
353
|
+
return this.http.get(`/expansions/${encodeURIComponent(technicalName)}`, options);
|
|
206
354
|
}
|
|
207
355
|
/** `GET /expansions/{technicalName}/cards`: every card in this expansion. Content only, no
|
|
208
356
|
* pricing fields — pass the `id`s from the result to `client.cards.pricingBatch()` if you need
|
|
209
357
|
* pricing too. Sealed products are a separate call — see `sealedProducts()` — never merged into
|
|
210
358
|
* this one. */
|
|
211
|
-
cards(technicalName) {
|
|
212
|
-
return this.http.get(`/expansions/${encodeURIComponent(technicalName)}/cards
|
|
359
|
+
cards(technicalName, options = {}) {
|
|
360
|
+
return this.http.get(`/expansions/${encodeURIComponent(technicalName)}/cards`, options);
|
|
213
361
|
}
|
|
214
362
|
/** `GET /expansions/{technicalName}/products`: every sealed product in this expansion. Content
|
|
215
363
|
* only, no pricing fields — pass the `id`s from the result to `client.products.pricingBatch()`
|
|
216
364
|
* if you need pricing too. Cards are a separate call — see `cards()` — never merged into this
|
|
217
365
|
* one. */
|
|
218
|
-
sealedProducts(technicalName) {
|
|
219
|
-
return this.http.get(`/expansions/${encodeURIComponent(technicalName)}/products
|
|
366
|
+
sealedProducts(technicalName, options = {}) {
|
|
367
|
+
return this.http.get(`/expansions/${encodeURIComponent(technicalName)}/products`, options);
|
|
220
368
|
}
|
|
221
369
|
/** `GET /expansions/{technicalName}/cards/live-pricing`: computed fresh for every card in this
|
|
222
370
|
* expansion, not read from the last stats job. Premium. */
|
|
@@ -244,13 +392,13 @@ var PackRatesResource = class {
|
|
|
244
392
|
http;
|
|
245
393
|
/** `GET /pack-rates`: pull-rate odds for every expansion that has them. Unwrapped to a plain
|
|
246
394
|
* array, nothing to paginate here. */
|
|
247
|
-
async list() {
|
|
248
|
-
const res = await this.http.get("/pack-rates");
|
|
395
|
+
async list(options = {}) {
|
|
396
|
+
const res = await this.http.get("/pack-rates", options);
|
|
249
397
|
return res.data;
|
|
250
398
|
}
|
|
251
399
|
/** `GET /pack-rates/{expansionId}`: pull-rate odds for one expansion. */
|
|
252
|
-
get(expansionId) {
|
|
253
|
-
return this.http.get(`/pack-rates/${encodeURIComponent(expansionId)}
|
|
400
|
+
get(expansionId, options = {}) {
|
|
401
|
+
return this.http.get(`/pack-rates/${encodeURIComponent(expansionId)}`, options);
|
|
254
402
|
}
|
|
255
403
|
};
|
|
256
404
|
|
|
@@ -262,17 +410,20 @@ var PriceStatsResource = class {
|
|
|
262
410
|
http;
|
|
263
411
|
/** `GET /price-stats/daily`: daily average price history, filtered to matching product(s). */
|
|
264
412
|
daily(params = {}) {
|
|
265
|
-
|
|
413
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
414
|
+
return this.http.get(`/price-stats/daily${toQueryString(query)}`, requestOptions);
|
|
266
415
|
}
|
|
267
416
|
/** `GET /price-stats/estimated-values`: current estimated market value, filtered to matching
|
|
268
417
|
* product(s). */
|
|
269
418
|
estimatedValues(params = {}) {
|
|
270
|
-
|
|
419
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
420
|
+
return this.http.get(`/price-stats/estimated-values${toQueryString(query)}`, requestOptions);
|
|
271
421
|
}
|
|
272
422
|
/** `GET /price-stats/top-products`: items ranked by shop availability (how many shops carry
|
|
273
423
|
* them), not by price. */
|
|
274
424
|
topProducts(params = {}) {
|
|
275
|
-
|
|
425
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
426
|
+
return this.http.get(`/price-stats/top-products${toQueryString(query)}`, requestOptions);
|
|
276
427
|
}
|
|
277
428
|
/** `GET /price-stats/product/{id}`: daily price history, current estimate, and a variant-count
|
|
278
429
|
* summary for one product. Premium. */
|
|
@@ -287,10 +438,10 @@ var PriceStatsResource = class {
|
|
|
287
438
|
/** `GET /price-stats/product/{id}/daily`: daily price history for one product, with a
|
|
288
439
|
* caller-chosen window. Premium. */
|
|
289
440
|
productDaily(idOrTechnicalName, params = {}) {
|
|
290
|
-
const [query,
|
|
441
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
291
442
|
return this.http.get(
|
|
292
443
|
`/price-stats/product/${encodeURIComponent(idOrTechnicalName)}/daily${toQueryString(query)}`,
|
|
293
|
-
|
|
444
|
+
requestOptions
|
|
294
445
|
);
|
|
295
446
|
}
|
|
296
447
|
/** `GET /price-stats/product/{id}/daily-last-30`: daily price history for the last 30 days
|
|
@@ -311,20 +462,20 @@ var PriceStatsResource = class {
|
|
|
311
462
|
/** `GET /price-stats/product/{id}/by-variant`: price stats broken out per card condition/grade.
|
|
312
463
|
* Premium. */
|
|
313
464
|
productByVariant(idOrTechnicalName, params = {}) {
|
|
314
|
-
const [query,
|
|
465
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
315
466
|
return this.http.get(
|
|
316
467
|
`/price-stats/product/${encodeURIComponent(idOrTechnicalName)}/by-variant${toQueryString(query)}`,
|
|
317
|
-
|
|
468
|
+
requestOptions
|
|
318
469
|
);
|
|
319
470
|
}
|
|
320
471
|
/** `GET /price-stats/product/{id}/daily-by-variant`: daily price history for one specific
|
|
321
472
|
* condition/grade. `condition` is required for `cardType: 'loose'`; `gradingCompany` and `grade`
|
|
322
473
|
* are required for `cardType: 'graded'`. Premium. */
|
|
323
474
|
productDailyByVariant(idOrTechnicalName, params) {
|
|
324
|
-
const [query,
|
|
475
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
325
476
|
return this.http.get(
|
|
326
477
|
`/price-stats/product/${encodeURIComponent(idOrTechnicalName)}/daily-by-variant${toQueryString(query)}`,
|
|
327
|
-
|
|
478
|
+
requestOptions
|
|
328
479
|
);
|
|
329
480
|
}
|
|
330
481
|
};
|
|
@@ -335,34 +486,48 @@ var ProductsResource = class {
|
|
|
335
486
|
this.http = http;
|
|
336
487
|
}
|
|
337
488
|
http;
|
|
338
|
-
/** `GET /product`:
|
|
489
|
+
/** `GET /product`: list sealed products, newest first. No free-text search — use `search()` for
|
|
490
|
+
* that. */
|
|
339
491
|
list(params = {}) {
|
|
340
|
-
|
|
492
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
493
|
+
return this.http.get(`/product${toQueryString(query)}`, requestOptions);
|
|
341
494
|
}
|
|
342
|
-
/** `GET /product/
|
|
343
|
-
|
|
344
|
-
|
|
495
|
+
/** `GET /product/search`: like `list()`, but with free-text search on the product name. Premium. */
|
|
496
|
+
search(params = {}) {
|
|
497
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
498
|
+
return this.http.get(`/product/search${toQueryString(query)}`, requestOptions);
|
|
499
|
+
}
|
|
500
|
+
/** `GET /product/{id}`: fetch one sealed product by its id or technicalName. Pass `brand` if two
|
|
501
|
+
* brands might share the same technicalName — see `GetProductParams`. */
|
|
502
|
+
get(idOrTechnicalName, params = {}) {
|
|
503
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
504
|
+
return this.http.get(
|
|
505
|
+
`/product/${encodeURIComponent(idOrTechnicalName)}${toQueryString(query)}`,
|
|
506
|
+
requestOptions
|
|
507
|
+
);
|
|
345
508
|
}
|
|
346
509
|
/** `GET /product/{id}/matches`: current shop listings matched to this product (latest per shop). */
|
|
347
510
|
matches(idOrTechnicalName, params = {}) {
|
|
511
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
348
512
|
return this.http.get(
|
|
349
|
-
`/product/${encodeURIComponent(idOrTechnicalName)}/matches${toQueryString(
|
|
513
|
+
`/product/${encodeURIComponent(idOrTechnicalName)}/matches${toQueryString(query)}`,
|
|
514
|
+
requestOptions
|
|
350
515
|
);
|
|
351
516
|
}
|
|
352
517
|
/** `GET /product/{id}/reference-prices`: Cardmarket/TCGplayer/Tradera price history. Premium. */
|
|
353
518
|
referencePrices(idOrTechnicalName, params = {}) {
|
|
354
|
-
const [query,
|
|
519
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
355
520
|
return this.http.get(
|
|
356
521
|
`/product/${encodeURIComponent(idOrTechnicalName)}/reference-prices${toQueryString(query)}`,
|
|
357
|
-
|
|
522
|
+
requestOptions
|
|
358
523
|
);
|
|
359
524
|
}
|
|
360
525
|
/** `GET /product/{id}/prices`: individual marketplace sale records. Premium. */
|
|
361
526
|
prices(idOrTechnicalName, params = {}) {
|
|
362
|
-
const [query,
|
|
527
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
363
528
|
return this.http.get(
|
|
364
529
|
`/product/${encodeURIComponent(idOrTechnicalName)}/prices${toQueryString(query)}`,
|
|
365
|
-
|
|
530
|
+
requestOptions
|
|
366
531
|
);
|
|
367
532
|
}
|
|
368
533
|
/** `GET /product/{id}/pricing/live`: computed fresh for this request, not read from the last
|
|
@@ -374,16 +539,38 @@ var ProductsResource = class {
|
|
|
374
539
|
* `estimatedValue`, `lowestShopOffer`, `referencePriceSnapshotsByProvider` — refreshed once a day
|
|
375
540
|
* by the nightly pricing/scraper jobs. `get()` returns content only; this is the separate,
|
|
376
541
|
* 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
|
|
542
|
+
pricing(idOrTechnicalName, options = {}) {
|
|
543
|
+
return this.http.get(`/product/${encodeURIComponent(idOrTechnicalName)}/pricing`, options);
|
|
379
544
|
}
|
|
380
545
|
/** `GET /product/pricing`: pricing for up to 200 sealed products in one request, keyed by `id` —
|
|
381
546
|
* the batch counterpart to `pricing()`, for a page of results (a search page, an expansion's
|
|
382
547
|
* contents) that needs pricing for many items at once. Unlike `get()`/`pricing()`, this only
|
|
383
548
|
* accepts `id`s, not technicalNames — pass the `id`s already on the products you fetched. Ids with
|
|
384
549
|
* 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(",")}
|
|
550
|
+
pricingBatch(ids, options = {}) {
|
|
551
|
+
return this.http.get(`/product/pricing?ids=${ids.map(encodeURIComponent).join(",")}`, options);
|
|
552
|
+
}
|
|
553
|
+
/** `GET /product/technical-names`: every sealed product's `technicalName` and `updatedAt`,
|
|
554
|
+
* unpaginated and with no pricing joins. The sealed counterpart to
|
|
555
|
+
* `client.cards.technicalNames()` — for sitemaps and incremental syncs. */
|
|
556
|
+
technicalNames(options = {}) {
|
|
557
|
+
return this.http.get("/product/technical-names", options);
|
|
558
|
+
}
|
|
559
|
+
/** `GET /product/price-stats/daily`: daily average price history, sealed products only. The same
|
|
560
|
+
* data as `client.priceStats.daily()`, scoped to the sealed catalog so a filter like `expansion`
|
|
561
|
+
* can't pull in that expansion's single cards too. */
|
|
562
|
+
dailyStats(params = {}) {
|
|
563
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
564
|
+
return this.http.get(`/product/price-stats/daily${toQueryString(query)}`, requestOptions);
|
|
565
|
+
}
|
|
566
|
+
/** `GET /product/price-stats/estimated-values`: current estimated market value, sealed products
|
|
567
|
+
* only. The sealed-scoped counterpart to `client.priceStats.estimatedValues()`. */
|
|
568
|
+
estimatedValues(params = {}) {
|
|
569
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
570
|
+
return this.http.get(
|
|
571
|
+
`/product/price-stats/estimated-values${toQueryString(query)}`,
|
|
572
|
+
requestOptions
|
|
573
|
+
);
|
|
387
574
|
}
|
|
388
575
|
};
|
|
389
576
|
|
|
@@ -395,24 +582,22 @@ var ShopMatchStatsResource = class {
|
|
|
395
582
|
http;
|
|
396
583
|
/** `GET /shop-match-stats/product/{productId}`: one product's price history, broken out per shop. */
|
|
397
584
|
forProduct(productId, params = {}) {
|
|
398
|
-
const [query,
|
|
585
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
399
586
|
return this.http.get(
|
|
400
587
|
`/shop-match-stats/product/${encodeURIComponent(productId)}${toQueryString(query)}`,
|
|
401
|
-
|
|
588
|
+
requestOptions
|
|
402
589
|
);
|
|
403
590
|
}
|
|
404
591
|
/** `GET /shop-match-stats/shop/{shop}`: one shop's price history, broken out per product. */
|
|
405
592
|
forShop(shop, params = {}) {
|
|
406
|
-
const [query,
|
|
407
|
-
return this.http.get(`/shop-match-stats/shop/${encodeURIComponent(shop)}${toQueryString(query)}`,
|
|
408
|
-
authToken
|
|
409
|
-
});
|
|
593
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
594
|
+
return this.http.get(`/shop-match-stats/shop/${encodeURIComponent(shop)}${toQueryString(query)}`, requestOptions);
|
|
410
595
|
}
|
|
411
596
|
/** `GET /shop-match-stats/compare`: one product's price at every shop that carries it, as of
|
|
412
597
|
* one date (defaults to the latest). */
|
|
413
598
|
compare(params) {
|
|
414
|
-
const [query,
|
|
415
|
-
return this.http.get(`/shop-match-stats/compare${toQueryString(query)}`,
|
|
599
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
600
|
+
return this.http.get(`/shop-match-stats/compare${toQueryString(query)}`, requestOptions);
|
|
416
601
|
}
|
|
417
602
|
};
|
|
418
603
|
|
|
@@ -424,15 +609,21 @@ var ShopMatchesResource = class {
|
|
|
424
609
|
http;
|
|
425
610
|
/** `GET /shop-matches`: every current match across every shop (latest record per url+shop). */
|
|
426
611
|
list(params = {}) {
|
|
427
|
-
|
|
612
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
613
|
+
return this.http.get(`/shop-matches${toQueryString(query)}`, requestOptions);
|
|
428
614
|
}
|
|
429
615
|
/** `GET /shop-matches/{shop}`: every current match at one shop (latest record per url). */
|
|
430
616
|
forShop(technicalName, params = {}) {
|
|
431
|
-
|
|
617
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
618
|
+
return this.http.get(
|
|
619
|
+
`/shop-matches/${encodeURIComponent(technicalName)}${toQueryString(query)}`,
|
|
620
|
+
requestOptions
|
|
621
|
+
);
|
|
432
622
|
}
|
|
433
623
|
/** `GET /shop-matches/shops`: match counts per shop (based on latest records only). */
|
|
434
624
|
shopStats(params = {}) {
|
|
435
|
-
|
|
625
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
626
|
+
return this.http.get(`/shop-matches/shops${toQueryString(query)}`, requestOptions);
|
|
436
627
|
}
|
|
437
628
|
};
|
|
438
629
|
|
|
@@ -444,16 +635,16 @@ var ShopUrlsResource = class {
|
|
|
444
635
|
http;
|
|
445
636
|
/** `POST /shop-urls/submit`: submit a shop URL for scraping. */
|
|
446
637
|
submit(params) {
|
|
447
|
-
const {
|
|
448
|
-
return this.http.post("/shop-urls/submit", { url, shop },
|
|
638
|
+
const { url, shop, ...requestOptions } = params;
|
|
639
|
+
return this.http.post("/shop-urls/submit", { url, shop }, requestOptions);
|
|
449
640
|
}
|
|
450
641
|
/** `PATCH /shop-urls/{id}/product`: manually assign (or clear) the product a shop URL resolves to. */
|
|
451
642
|
assignProduct(shopUrlId, params) {
|
|
452
|
-
const {
|
|
643
|
+
const { productId, ...requestOptions } = params;
|
|
453
644
|
return this.http.patch(
|
|
454
645
|
`/shop-urls/${encodeURIComponent(shopUrlId)}/product`,
|
|
455
646
|
{ productId },
|
|
456
|
-
|
|
647
|
+
requestOptions
|
|
457
648
|
);
|
|
458
649
|
}
|
|
459
650
|
};
|
|
@@ -466,12 +657,13 @@ var ShopsResource = class {
|
|
|
466
657
|
http;
|
|
467
658
|
/** `GET /shops`: every tracked shop. Unwrapped to a plain array, nothing to paginate here. */
|
|
468
659
|
async list(params = {}) {
|
|
469
|
-
const
|
|
660
|
+
const [query, requestOptions] = splitRequestOptions(params);
|
|
661
|
+
const res = await this.http.get(`/shops${toQueryString(query)}`, requestOptions);
|
|
470
662
|
return res.data;
|
|
471
663
|
}
|
|
472
664
|
/** `GET /shops/{id}`: fetch one shop by its id or technicalName. */
|
|
473
|
-
get(idOrTechnicalName) {
|
|
474
|
-
return this.http.get(`/shops/${encodeURIComponent(idOrTechnicalName)}
|
|
665
|
+
get(idOrTechnicalName, options = {}) {
|
|
666
|
+
return this.http.get(`/shops/${encodeURIComponent(idOrTechnicalName)}`, options);
|
|
475
667
|
}
|
|
476
668
|
};
|
|
477
669
|
|
|
@@ -482,8 +674,40 @@ var StatsResource = class {
|
|
|
482
674
|
}
|
|
483
675
|
http;
|
|
484
676
|
/** `GET /stats`: platform-wide overview counts (shops, expansions, products, prices tracked). */
|
|
485
|
-
platform() {
|
|
486
|
-
return this.http.get("/stats");
|
|
677
|
+
platform(options = {}) {
|
|
678
|
+
return this.http.get("/stats", options);
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
// src/resources/webhooks.ts
|
|
683
|
+
var WebhooksResource = class {
|
|
684
|
+
constructor(http) {
|
|
685
|
+
this.http = http;
|
|
686
|
+
}
|
|
687
|
+
http;
|
|
688
|
+
/**
|
|
689
|
+
* `POST /webhooks`: register a new webhook.
|
|
690
|
+
*
|
|
691
|
+
* The returned `secret` is the only copy you will ever get — sign-verification depends on it and
|
|
692
|
+
* no endpoint reads it back. Persist it here, at creation, or delete the webhook and make a new
|
|
693
|
+
* one.
|
|
694
|
+
*/
|
|
695
|
+
create(params) {
|
|
696
|
+
const { url, events, ...requestOptions } = params;
|
|
697
|
+
return this.http.post("/webhooks", { url, events }, requestOptions);
|
|
698
|
+
}
|
|
699
|
+
/** `GET /webhooks`: every webhook registered on this account. Secrets are never included. */
|
|
700
|
+
list(options = {}) {
|
|
701
|
+
return this.http.get("/webhooks", options);
|
|
702
|
+
}
|
|
703
|
+
/** `DELETE /webhooks/{id}`: revoke a webhook. Deliveries stop immediately; its secret is void. */
|
|
704
|
+
delete(webhookId, options = {}) {
|
|
705
|
+
return this.http.delete(`/webhooks/${encodeURIComponent(webhookId)}`, options);
|
|
706
|
+
}
|
|
707
|
+
/** `POST /webhooks/{id}/test`: send a sample delivery to the registered URL, so you can verify
|
|
708
|
+
* your endpoint and your signature check before waiting on a real event. */
|
|
709
|
+
test(webhookId, options = {}) {
|
|
710
|
+
return this.http.post(`/webhooks/${encodeURIComponent(webhookId)}/test`, void 0, options);
|
|
487
711
|
}
|
|
488
712
|
};
|
|
489
713
|
|
|
@@ -493,6 +717,7 @@ var TcgPriser = class {
|
|
|
493
717
|
cards;
|
|
494
718
|
products;
|
|
495
719
|
expansions;
|
|
720
|
+
brands;
|
|
496
721
|
shops;
|
|
497
722
|
shopMatches;
|
|
498
723
|
shopMatchStats;
|
|
@@ -501,6 +726,9 @@ var TcgPriser = class {
|
|
|
501
726
|
bargains;
|
|
502
727
|
packRates;
|
|
503
728
|
stats;
|
|
729
|
+
webhooks;
|
|
730
|
+
/** Holds the `HttpClient` so `creditsRemaining` can read the running value off it. */
|
|
731
|
+
http;
|
|
504
732
|
/**
|
|
505
733
|
* @param optionsOrAuthToken A subscriber's API token (`new TcgPriser(myApiToken)`), a full
|
|
506
734
|
* `TcgPriserOptions` object, or omit it entirely for an anonymous, public-only client.
|
|
@@ -514,6 +742,7 @@ var TcgPriser = class {
|
|
|
514
742
|
);
|
|
515
743
|
}
|
|
516
744
|
const http = new HttpClient({
|
|
745
|
+
timeoutMs: advanced.timeoutMs,
|
|
517
746
|
baseUrl: advanced.baseUrl ?? DEFAULT_BASE_URL,
|
|
518
747
|
// Bound to globalThis: both browsers and Node's undici implement fetch as a method that
|
|
519
748
|
// checks its receiver, so an unbound reference throws "Illegal invocation" the moment it's
|
|
@@ -526,6 +755,7 @@ var TcgPriser = class {
|
|
|
526
755
|
this.cards = new CardsResource(http);
|
|
527
756
|
this.products = new ProductsResource(http);
|
|
528
757
|
this.expansions = new ExpansionsResource(http);
|
|
758
|
+
this.brands = new BrandsResource(http);
|
|
529
759
|
this.shops = new ShopsResource(http);
|
|
530
760
|
this.shopMatches = new ShopMatchesResource(http);
|
|
531
761
|
this.shopMatchStats = new ShopMatchStatsResource(http);
|
|
@@ -534,9 +764,29 @@ var TcgPriser = class {
|
|
|
534
764
|
this.bargains = new BargainsResource(http);
|
|
535
765
|
this.packRates = new PackRatesResource(http);
|
|
536
766
|
this.stats = new StatsResource(http);
|
|
767
|
+
this.webhooks = new WebhooksResource(http);
|
|
768
|
+
this.http = http;
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Credits left in this week's allowance, as of the last charged call this client made.
|
|
772
|
+
*
|
|
773
|
+
* The API returns `X-Credits-Remaining` on every response it charges for, so this needs no extra
|
|
774
|
+
* request — but it is only as current as your last premium call, and it is `undefined` until you
|
|
775
|
+
* make one. Uncharged calls (every public method, and any call authenticated with something other
|
|
776
|
+
* than an API token) don't update it, because the API doesn't meter them.
|
|
777
|
+
*
|
|
778
|
+
* ```ts
|
|
779
|
+
* await tcgpriser.cards.livePricing('fezandipiti-ex');
|
|
780
|
+
* if ((tcgpriser.creditsRemaining ?? Infinity) < 100) scheduleFewerRefreshes();
|
|
781
|
+
* ```
|
|
782
|
+
*
|
|
783
|
+
* Reading it in a browser additionally needs the API to expose the header via CORS, which it does.
|
|
784
|
+
*/
|
|
785
|
+
get creditsRemaining() {
|
|
786
|
+
return this.http.creditsRemaining;
|
|
537
787
|
}
|
|
538
788
|
};
|
|
539
789
|
|
|
540
|
-
export { DEFAULT_BASE_URL, TcgPriser, TcgPriserError };
|
|
790
|
+
export { DEFAULT_BASE_URL, DEFAULT_TIMEOUT_MS, TcgPriser, TcgPriserError };
|
|
541
791
|
//# sourceMappingURL=index.js.map
|
|
542
792
|
//# sourceMappingURL=index.js.map
|