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