@pokewallet/sdk 0.0.0-stage → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +28 -2
- package/dist/index.cjs +956 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2859 -0
- package/dist/index.d.ts +2859 -0
- package/dist/index.js +912 -0
- package/dist/index.js.map +1 -0
- package/package.json +70 -4
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,956 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AmbiguousSetError: () => AmbiguousSetError,
|
|
24
|
+
AuthError: () => AuthError,
|
|
25
|
+
BadRequestError: () => BadRequestError,
|
|
26
|
+
FeatureNotEnabledError: () => FeatureNotEnabledError,
|
|
27
|
+
GAME_CODES: () => GAME_CODES,
|
|
28
|
+
GameUnavailableError: () => GameUnavailableError,
|
|
29
|
+
NetworkError: () => NetworkError,
|
|
30
|
+
NotFoundError: () => NotFoundError,
|
|
31
|
+
PlanRequiredError: () => PlanRequiredError,
|
|
32
|
+
PokeWallet: () => PokeWallet,
|
|
33
|
+
PokeWalletError: () => PokeWalletError,
|
|
34
|
+
RateLimitError: () => RateLimitError,
|
|
35
|
+
ServerError: () => ServerError,
|
|
36
|
+
UnavailableError: () => UnavailableError,
|
|
37
|
+
VERSION: () => VERSION,
|
|
38
|
+
ValidationError: () => ValidationError,
|
|
39
|
+
imageUrl: () => imageUrl,
|
|
40
|
+
paginate: () => paginate
|
|
41
|
+
});
|
|
42
|
+
module.exports = __toCommonJS(index_exports);
|
|
43
|
+
|
|
44
|
+
// src/errors.ts
|
|
45
|
+
var PokeWalletError = class extends Error {
|
|
46
|
+
status;
|
|
47
|
+
/** Parsed JSON body of the answer, when there was one. */
|
|
48
|
+
body;
|
|
49
|
+
/** The `error` field of the body, when present. */
|
|
50
|
+
code;
|
|
51
|
+
constructor(init) {
|
|
52
|
+
const code = bodyString(init.body, "error");
|
|
53
|
+
super(init.message ?? bodyString(init.body, "message") ?? code ?? `HTTP ${init.status}`);
|
|
54
|
+
this.name = new.target.name;
|
|
55
|
+
this.status = init.status;
|
|
56
|
+
this.body = init.body;
|
|
57
|
+
this.code = code;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
var ValidationError = class extends RangeError {
|
|
61
|
+
constructor(message) {
|
|
62
|
+
super(message);
|
|
63
|
+
this.name = "ValidationError";
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
var NetworkError = class extends Error {
|
|
67
|
+
constructor(message, options) {
|
|
68
|
+
super(message, options);
|
|
69
|
+
this.name = "NetworkError";
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
var BadRequestError = class extends PokeWalletError {
|
|
73
|
+
};
|
|
74
|
+
var AuthError = class extends PokeWalletError {
|
|
75
|
+
};
|
|
76
|
+
var PlanRequiredError = class extends PokeWalletError {
|
|
77
|
+
redeemUrl;
|
|
78
|
+
upgradeUrl;
|
|
79
|
+
constructor(init) {
|
|
80
|
+
super(init);
|
|
81
|
+
this.redeemUrl = bodyString(init.body, "redeem_url");
|
|
82
|
+
this.upgradeUrl = bodyString(init.body, "upgrade_url");
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
var FeatureNotEnabledError = class extends PokeWalletError {
|
|
86
|
+
contactUrl;
|
|
87
|
+
constructor(init) {
|
|
88
|
+
super(init);
|
|
89
|
+
this.contactUrl = bodyString(init.body, "contact_url");
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
var NotFoundError = class extends PokeWalletError {
|
|
93
|
+
didYouMean;
|
|
94
|
+
suggestions;
|
|
95
|
+
constructor(init) {
|
|
96
|
+
super(init);
|
|
97
|
+
this.didYouMean = bodyString(init.body, "did_you_mean");
|
|
98
|
+
const s = init.body?.suggestions;
|
|
99
|
+
this.suggestions = Array.isArray(s) ? s : [];
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
var GameUnavailableError = class extends NotFoundError {
|
|
103
|
+
};
|
|
104
|
+
var AmbiguousSetError = class extends PokeWalletError {
|
|
105
|
+
matches;
|
|
106
|
+
constructor(init) {
|
|
107
|
+
super({ ...init, message: init.message ?? "Several sets match this code: pick one by setId" });
|
|
108
|
+
this.matches = init.matches;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
var RateLimitError = class extends PokeWalletError {
|
|
112
|
+
limits;
|
|
113
|
+
/** Seconds, from the `Retry-After` header, when present. */
|
|
114
|
+
retryAfter;
|
|
115
|
+
constructor(init) {
|
|
116
|
+
super(init);
|
|
117
|
+
this.retryAfter = init.retryAfter;
|
|
118
|
+
const l = init.body?.limits;
|
|
119
|
+
this.limits = { hourly: toWindow(l?.hourly), daily: toWindow(l?.daily) };
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
var UnavailableError = class extends PokeWalletError {
|
|
123
|
+
retryAfter;
|
|
124
|
+
constructor(init) {
|
|
125
|
+
super(init);
|
|
126
|
+
this.retryAfter = init.retryAfter;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
var ServerError = class extends PokeWalletError {
|
|
130
|
+
};
|
|
131
|
+
function bodyString(body, key) {
|
|
132
|
+
const v = body?.[key];
|
|
133
|
+
return typeof v === "string" ? v : void 0;
|
|
134
|
+
}
|
|
135
|
+
function toWindow(v) {
|
|
136
|
+
if (!v || typeof v !== "object") return void 0;
|
|
137
|
+
const w = v;
|
|
138
|
+
if (typeof w.limit !== "number" || typeof w.used !== "number" || typeof w.remaining !== "number") return void 0;
|
|
139
|
+
return { limit: w.limit, used: w.used, remaining: w.remaining };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// src/error-map.ts
|
|
143
|
+
var str = (v) => typeof v === "string" ? v : typeof v === "number" ? String(v) : null;
|
|
144
|
+
function toSetMatches(body) {
|
|
145
|
+
const raw = body?.matches;
|
|
146
|
+
if (!Array.isArray(raw)) return [];
|
|
147
|
+
return raw.map((m) => ({
|
|
148
|
+
setId: str(m.set_id) ?? "",
|
|
149
|
+
setCode: str(m.set_code),
|
|
150
|
+
name: str(m.name),
|
|
151
|
+
language: str(m.language),
|
|
152
|
+
releaseDate: str(m.release_date)
|
|
153
|
+
}));
|
|
154
|
+
}
|
|
155
|
+
function isDisambiguation(body) {
|
|
156
|
+
return !!body && typeof body === "object" && body.disambiguation === true;
|
|
157
|
+
}
|
|
158
|
+
function ambiguousSetError(status, body) {
|
|
159
|
+
return new AmbiguousSetError({ status, body, matches: toSetMatches(body) });
|
|
160
|
+
}
|
|
161
|
+
function errorFromResponse(ctx) {
|
|
162
|
+
const { status, body, retryAfter, gameRoute } = ctx;
|
|
163
|
+
const init = { status, body };
|
|
164
|
+
const code = body?.error;
|
|
165
|
+
if (status === 300 || status === 409 && isDisambiguation(body) || status === 409 && code === "Ambiguous set code") {
|
|
166
|
+
return ambiguousSetError(status, body);
|
|
167
|
+
}
|
|
168
|
+
switch (status) {
|
|
169
|
+
case 400:
|
|
170
|
+
return new BadRequestError(init);
|
|
171
|
+
case 401:
|
|
172
|
+
return new AuthError(init);
|
|
173
|
+
case 403:
|
|
174
|
+
return code === "Feature not enabled" ? new FeatureNotEnabledError(init) : new PlanRequiredError(init);
|
|
175
|
+
case 404:
|
|
176
|
+
return gameRoute && code === "Endpoint not found" ? new GameUnavailableError(init) : new NotFoundError(init);
|
|
177
|
+
case 429:
|
|
178
|
+
return new RateLimitError({ ...init, retryAfter });
|
|
179
|
+
case 503:
|
|
180
|
+
return new UnavailableError({ ...init, retryAfter });
|
|
181
|
+
default:
|
|
182
|
+
return status >= 500 ? new ServerError(init) : new PokeWalletError(init);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// src/version.ts
|
|
187
|
+
var VERSION = "0.1.3";
|
|
188
|
+
var DEFAULT_BASE_URL = "https://api.pokewallet.io";
|
|
189
|
+
var DEFAULT_USER_AGENT = `pokewallet-sdk-ts/${VERSION}`;
|
|
190
|
+
|
|
191
|
+
// src/http.ts
|
|
192
|
+
var GAME_PREFIX = /^\/(mtg|ygo|cfv|dbs|fab|dgm|swu|lor|rft|cpk)\//;
|
|
193
|
+
var KEY_REQUIRED = "An API key is required: pass apiKey or set POKEWALLET_API_KEY";
|
|
194
|
+
function readKey(options) {
|
|
195
|
+
const env = globalThis.process?.env;
|
|
196
|
+
const key = (options.apiKey ?? env?.POKEWALLET_API_KEY ?? "").trim();
|
|
197
|
+
if (!key) return void 0;
|
|
198
|
+
if (!key.startsWith("pk_")) throw new ValidationError('API keys start with "pk_"');
|
|
199
|
+
return key;
|
|
200
|
+
}
|
|
201
|
+
function parseRetryAfter(value, now = Date.now()) {
|
|
202
|
+
if (value == null || value.trim() === "") return void 0;
|
|
203
|
+
const n = Number(value);
|
|
204
|
+
if (Number.isFinite(n) && n >= 0) return n;
|
|
205
|
+
const t = Date.parse(value);
|
|
206
|
+
return Number.isNaN(t) ? void 0 : Math.max(0, Math.ceil((t - now) / 1e3));
|
|
207
|
+
}
|
|
208
|
+
function num(h, name) {
|
|
209
|
+
const v = h.get(name);
|
|
210
|
+
if (v == null) return null;
|
|
211
|
+
const n = Number(v);
|
|
212
|
+
return Number.isFinite(n) ? n : null;
|
|
213
|
+
}
|
|
214
|
+
function exhausted(err) {
|
|
215
|
+
return [err.limits.hourly, err.limits.daily].some((w) => w !== void 0 && w.remaining <= 0);
|
|
216
|
+
}
|
|
217
|
+
var HttpClient = class {
|
|
218
|
+
/** Quota counters of the most recent answer that carried them. */
|
|
219
|
+
lastRateLimit = null;
|
|
220
|
+
#key;
|
|
221
|
+
baseUrl;
|
|
222
|
+
userAgent;
|
|
223
|
+
fetchImpl;
|
|
224
|
+
maxRetries;
|
|
225
|
+
capSeconds;
|
|
226
|
+
timeoutMs;
|
|
227
|
+
sleep;
|
|
228
|
+
constructor(options = {}) {
|
|
229
|
+
this.#key = readKey(options);
|
|
230
|
+
this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
231
|
+
this.userAgent = options.userAgent ?? DEFAULT_USER_AGENT;
|
|
232
|
+
const g = globalThis;
|
|
233
|
+
const f = options.fetch ?? (g.fetch ? g.fetch.bind(globalThis) : void 0);
|
|
234
|
+
if (!f) throw new ValidationError("No fetch available: pass options.fetch (Node 18+ has one built in)");
|
|
235
|
+
this.fetchImpl = f;
|
|
236
|
+
this.maxRetries = options.maxRetries ?? 2;
|
|
237
|
+
this.capSeconds = options.retryAfterCapSeconds ?? 30;
|
|
238
|
+
this.timeoutMs = options.timeoutMs ?? 3e4;
|
|
239
|
+
this.sleep = options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
240
|
+
}
|
|
241
|
+
url(path, query) {
|
|
242
|
+
const qs = new URLSearchParams();
|
|
243
|
+
for (const [k, v] of Object.entries(query ?? {})) if (v !== void 0 && v !== null) qs.set(k, String(v));
|
|
244
|
+
const s = qs.toString();
|
|
245
|
+
return `${this.baseUrl}${path}${s ? `?${s}` : ""}`;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* GET `path` and return the parsed JSON body; throws a typed error otherwise. `isPublic`
|
|
249
|
+
* marks the two endpoints the server answers without a key; everything else needs one.
|
|
250
|
+
* `accept` lets a caller treat a non-2xx body as data (see `health()`).
|
|
251
|
+
*/
|
|
252
|
+
async get(path, query, options = {}) {
|
|
253
|
+
if (this.#key === void 0 && !options.isPublic) throw new ValidationError(KEY_REQUIRED);
|
|
254
|
+
const url = this.url(path, query);
|
|
255
|
+
for (let attempt = 0; ; attempt++) {
|
|
256
|
+
const res = await this.send(url);
|
|
257
|
+
const body = parseJson(this.#redact(await res.text()));
|
|
258
|
+
this.trackQuota(res.headers);
|
|
259
|
+
if (res.status >= 200 && res.status < 300) return body;
|
|
260
|
+
if (options.accept?.(body)) return body;
|
|
261
|
+
const retryAfter = parseRetryAfter(res.headers.get("retry-after"));
|
|
262
|
+
const err = errorFromResponse({ status: res.status, body, retryAfter, gameRoute: GAME_PREFIX.test(path) });
|
|
263
|
+
if (attempt < this.maxRetries && this.shouldRetry(err, retryAfter)) {
|
|
264
|
+
await this.sleep((retryAfter ?? 1) * 1e3);
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
throw err;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
shouldRetry(err, retryAfter) {
|
|
271
|
+
if (err.status === 429) {
|
|
272
|
+
return retryAfter !== void 0 && retryAfter <= this.capSeconds && !exhausted(err);
|
|
273
|
+
}
|
|
274
|
+
if (err.status === 503) return (retryAfter ?? 1) <= this.capSeconds;
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
async send(url) {
|
|
278
|
+
try {
|
|
279
|
+
return await this.fetchImpl(url, {
|
|
280
|
+
method: "GET",
|
|
281
|
+
headers: {
|
|
282
|
+
...this.#key === void 0 ? {} : { "X-API-Key": this.#key },
|
|
283
|
+
"User-Agent": this.userAgent,
|
|
284
|
+
Accept: "application/json"
|
|
285
|
+
},
|
|
286
|
+
signal: AbortSignal.timeout(this.timeoutMs)
|
|
287
|
+
});
|
|
288
|
+
} catch (cause) {
|
|
289
|
+
throw new NetworkError("Request failed before an answer arrived", { cause: this.#scrubError(cause) });
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
/** Masked form: the only representation of this object that logging or serializing can reach. */
|
|
293
|
+
toJSON() {
|
|
294
|
+
return { baseUrl: this.baseUrl, userAgent: this.userAgent, apiKey: this.#key === void 0 ? null : "pk_***" };
|
|
295
|
+
}
|
|
296
|
+
[/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
|
|
297
|
+
return `HttpClient ${JSON.stringify(this.toJSON())}`;
|
|
298
|
+
}
|
|
299
|
+
#redact(text) {
|
|
300
|
+
return this.#key === void 0 ? text : text.split(this.#key).join("pk_***");
|
|
301
|
+
}
|
|
302
|
+
/** A copy of a thrown value with the key removed from its message, stack and string fields. */
|
|
303
|
+
#scrubError(cause) {
|
|
304
|
+
if (!(cause instanceof Error)) return typeof cause === "string" ? this.#redact(cause) : void 0;
|
|
305
|
+
const copy = new Error(this.#redact(cause.message));
|
|
306
|
+
copy.name = cause.name;
|
|
307
|
+
if (cause.stack) copy.stack = this.#redact(cause.stack);
|
|
308
|
+
const code = cause.code;
|
|
309
|
+
if (typeof code === "string") copy.code = code;
|
|
310
|
+
return copy;
|
|
311
|
+
}
|
|
312
|
+
trackQuota(h) {
|
|
313
|
+
const info = {
|
|
314
|
+
limitHour: num(h, "x-ratelimit-limit-hour"),
|
|
315
|
+
remainingHour: num(h, "x-ratelimit-remaining-hour"),
|
|
316
|
+
limitDay: num(h, "x-ratelimit-limit-day"),
|
|
317
|
+
remainingDay: num(h, "x-ratelimit-remaining-day")
|
|
318
|
+
};
|
|
319
|
+
if (Object.values(info).some((v) => v !== null)) this.lastRateLimit = info;
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
function parseJson(text) {
|
|
323
|
+
if (!text) return void 0;
|
|
324
|
+
try {
|
|
325
|
+
return JSON.parse(text);
|
|
326
|
+
} catch {
|
|
327
|
+
return { error: text.slice(0, 200) };
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// src/types.ts
|
|
332
|
+
var GAME_CODES = ["mtg", "ygo", "cfv", "dbs", "fab", "dgm", "swu", "lor", "rft", "cpk"];
|
|
333
|
+
|
|
334
|
+
// src/images.ts
|
|
335
|
+
function imageUrl(id, options = {}) {
|
|
336
|
+
const clean = id.trim();
|
|
337
|
+
if (!clean) throw new ValidationError("id is required");
|
|
338
|
+
const base = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
339
|
+
const game = GAME_CODES.find((g) => clean.toLowerCase().startsWith(`${g}_`));
|
|
340
|
+
const path = game ? `/${game}/images/${encodeURIComponent(clean)}` : `/images/${encodeURIComponent(clean)}`;
|
|
341
|
+
const qs = new URLSearchParams();
|
|
342
|
+
if (!game) {
|
|
343
|
+
if (options.size) qs.set("size", options.size);
|
|
344
|
+
if (options.lang) qs.set("lang", options.lang);
|
|
345
|
+
if (options.set) qs.set("set", options.set);
|
|
346
|
+
}
|
|
347
|
+
const s = qs.toString();
|
|
348
|
+
return `${base}${path}${s ? `?${s}` : ""}`;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// src/normalize/common.ts
|
|
352
|
+
var rec = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
353
|
+
var arr = (v) => Array.isArray(v) ? v : [];
|
|
354
|
+
function str2(v) {
|
|
355
|
+
if (typeof v === "string") return v;
|
|
356
|
+
if (typeof v === "number" && Number.isFinite(v)) return String(v);
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
function num2(v) {
|
|
360
|
+
if (typeof v === "number") return Number.isFinite(v) ? v : null;
|
|
361
|
+
if (typeof v === "string" && v.trim() !== "") {
|
|
362
|
+
const n = Number(v);
|
|
363
|
+
return Number.isFinite(n) ? n : null;
|
|
364
|
+
}
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
var NUMERIC_ID = /^-?\d+$/;
|
|
368
|
+
function setPair(code, id) {
|
|
369
|
+
const c = str2(code);
|
|
370
|
+
const i = str2(id);
|
|
371
|
+
if (i === null && c !== null && NUMERIC_ID.test(c)) return { id: c, code: null };
|
|
372
|
+
return { id: i, code: c };
|
|
373
|
+
}
|
|
374
|
+
function setRef(name, code, id) {
|
|
375
|
+
return { ...setPair(code, id), name: str2(name) };
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// src/normalize/misc.ts
|
|
379
|
+
function strings(v) {
|
|
380
|
+
const out = {};
|
|
381
|
+
for (const [k, val] of Object.entries(rec(v))) if (typeof val === "string") out[k] = val;
|
|
382
|
+
return out;
|
|
383
|
+
}
|
|
384
|
+
function normalizeGames(raw) {
|
|
385
|
+
return arr(rec(raw).data).map((item) => {
|
|
386
|
+
const g = rec(item);
|
|
387
|
+
const code = str2(g.code);
|
|
388
|
+
if (code === null || !GAME_CODES.includes(code)) return null;
|
|
389
|
+
return { code, name: str2(g.name) ?? code, endpoints: strings(g.endpoints), proEndpoints: strings(g.pro_endpoints), raw: item };
|
|
390
|
+
}).filter((g) => g !== null);
|
|
391
|
+
}
|
|
392
|
+
function isHealthReport(body) {
|
|
393
|
+
return typeof rec(body).status === "string";
|
|
394
|
+
}
|
|
395
|
+
function normalizeHealth(raw) {
|
|
396
|
+
const r = rec(raw);
|
|
397
|
+
const checks = {};
|
|
398
|
+
for (const [name, value] of Object.entries(rec(r.checks))) {
|
|
399
|
+
const c = rec(value);
|
|
400
|
+
checks[name] = { status: str2(c.status) ?? "unknown", message: str2(c.message), responseTime: num2(c.responseTime) };
|
|
401
|
+
}
|
|
402
|
+
return {
|
|
403
|
+
status: str2(r.status) ?? "unknown",
|
|
404
|
+
timestamp: str2(r.timestamp),
|
|
405
|
+
version: str2(r.version),
|
|
406
|
+
checks,
|
|
407
|
+
responseTime: num2(r.responseTime),
|
|
408
|
+
region: str2(r.region),
|
|
409
|
+
message: str2(r.message),
|
|
410
|
+
raw
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// src/normalize/prices.ts
|
|
415
|
+
function tcgPrice(v, variant) {
|
|
416
|
+
const r = rec(v);
|
|
417
|
+
return {
|
|
418
|
+
variant: str2(variant ?? r.sub_type_name ?? r.variant),
|
|
419
|
+
low: num2(r.low_price),
|
|
420
|
+
mid: num2(r.mid_price),
|
|
421
|
+
high: num2(r.high_price),
|
|
422
|
+
market: num2(r.market_price),
|
|
423
|
+
directLow: num2(r.direct_low_price),
|
|
424
|
+
updatedAt: str2(r.updated_at),
|
|
425
|
+
raw: v
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
function cardmarketPrice(v, variant) {
|
|
429
|
+
const r = rec(v);
|
|
430
|
+
return {
|
|
431
|
+
variant: str2(variant ?? r.variant_type ?? r.variant),
|
|
432
|
+
avg: num2(r.avg),
|
|
433
|
+
low: num2(r.low),
|
|
434
|
+
trend: num2(r.trend),
|
|
435
|
+
avg1: num2(r.avg1),
|
|
436
|
+
avg7: num2(r.avg7),
|
|
437
|
+
avg30: num2(r.avg30),
|
|
438
|
+
updatedAt: str2(r.updated_at),
|
|
439
|
+
raw: v
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// src/normalize/card.ts
|
|
444
|
+
var KNOWN = /* @__PURE__ */ new Set([
|
|
445
|
+
"name",
|
|
446
|
+
"clean_name",
|
|
447
|
+
"set_name",
|
|
448
|
+
"set_code",
|
|
449
|
+
"set_id",
|
|
450
|
+
"card_number",
|
|
451
|
+
"rarity",
|
|
452
|
+
"card_type",
|
|
453
|
+
"product_type",
|
|
454
|
+
"variants",
|
|
455
|
+
"card_text"
|
|
456
|
+
]);
|
|
457
|
+
function normalizeCard(raw, ctx) {
|
|
458
|
+
const r = rec(raw);
|
|
459
|
+
const info = rec(r.card_info);
|
|
460
|
+
const tcg = r.tcgplayer ? rec(r.tcgplayer) : null;
|
|
461
|
+
const cm = r.cardmarket ? rec(r.cardmarket) : null;
|
|
462
|
+
const pair = ctx ?? { setId: setPair(info.set_code, info.set_id).id, setCode: setPair(info.set_code, info.set_id).code };
|
|
463
|
+
return {
|
|
464
|
+
id: str2(r.id) ?? "",
|
|
465
|
+
name: str2(info.name) ?? "",
|
|
466
|
+
cleanName: str2(info.clean_name),
|
|
467
|
+
number: str2(info.card_number),
|
|
468
|
+
rarity: str2(info.rarity),
|
|
469
|
+
cardType: str2(info.card_type),
|
|
470
|
+
productType: str2(info.product_type),
|
|
471
|
+
text: str2(info.card_text),
|
|
472
|
+
variants: arr(info.variants).filter((v) => typeof v === "string"),
|
|
473
|
+
setId: pair.setId,
|
|
474
|
+
setCode: pair.setCode,
|
|
475
|
+
setName: str2(info.set_name),
|
|
476
|
+
tcgplayer: tcg ? { url: str2(tcg.url), prices: arr(tcg.prices).map((p) => tcgPrice(p)) } : null,
|
|
477
|
+
cardmarket: cm ? { productName: str2(cm.product_name), url: str2(cm.product_url), prices: arr(cm.prices).map((p) => cardmarketPrice(p)) } : null,
|
|
478
|
+
info: extras(info),
|
|
479
|
+
raw
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
function extras(info) {
|
|
483
|
+
const out = {};
|
|
484
|
+
for (const [k, v] of Object.entries(info)) if (!KNOWN.has(k)) out[k] = v;
|
|
485
|
+
return out;
|
|
486
|
+
}
|
|
487
|
+
function normalizeOpCard(raw, ctx) {
|
|
488
|
+
const r = rec(raw);
|
|
489
|
+
const tcg = r.tcgplayer ? rec(r.tcgplayer) : null;
|
|
490
|
+
const cm = r.cardmarket ? rec(r.cardmarket) : null;
|
|
491
|
+
const finish = r.sub_type_name;
|
|
492
|
+
const info = {};
|
|
493
|
+
for (const [k, v] of Object.entries(r)) if (k.startsWith("ext_")) info[k] = v;
|
|
494
|
+
return {
|
|
495
|
+
id: str2(r.id) ?? "",
|
|
496
|
+
name: str2(r.name) ?? "",
|
|
497
|
+
cleanName: str2(r.clean_name),
|
|
498
|
+
number: str2(r.card_number),
|
|
499
|
+
rarity: str2(r.rarity),
|
|
500
|
+
cardType: str2(r.card_type),
|
|
501
|
+
productType: null,
|
|
502
|
+
text: str2(r.ext_description),
|
|
503
|
+
variants: typeof finish === "string" ? [finish] : [],
|
|
504
|
+
setId: ctx?.setId ?? null,
|
|
505
|
+
setCode: ctx?.setCode ?? null,
|
|
506
|
+
setName: null,
|
|
507
|
+
tcgplayer: tcg ? { url: str2(tcg.url), prices: tcg.prices ? [tcgPrice(tcg.prices, finish)] : [] } : null,
|
|
508
|
+
cardmarket: cm ? { productName: str2(cm.product_name), url: str2(cm.product_url), prices: cm.prices ? [cardmarketPrice(cm.prices, finish)] : [] } : null,
|
|
509
|
+
info,
|
|
510
|
+
raw
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// src/normalize/pro.ts
|
|
515
|
+
var DAYS = [7, 14, 30, 60, 120];
|
|
516
|
+
function normalizeSetPrices(raw) {
|
|
517
|
+
const r = rec(raw);
|
|
518
|
+
const pair = setPair(r.set_code, r.set_id ?? r.group_id);
|
|
519
|
+
const rows = arr(r.data).map((d) => {
|
|
520
|
+
const row = rec(d);
|
|
521
|
+
return {
|
|
522
|
+
id: str2(row.id) ?? "",
|
|
523
|
+
number: str2(row.card_number),
|
|
524
|
+
name: str2(row.name) ?? "",
|
|
525
|
+
variant: str2(row.variant),
|
|
526
|
+
productType: str2(row.product_type),
|
|
527
|
+
tcgplayer: row.tcgplayer ? tcgPrice(row.tcgplayer, row.variant) : null,
|
|
528
|
+
cardmarket: row.cardmarket ? cardmarketPrice(row.cardmarket, row.variant) : null
|
|
529
|
+
};
|
|
530
|
+
});
|
|
531
|
+
return { source: str2(r.source) ?? "both", setId: pair.id, setCode: pair.code, rows, total: num2(r.total) ?? rows.length, raw };
|
|
532
|
+
}
|
|
533
|
+
function priceStats(v) {
|
|
534
|
+
if (!v || typeof v !== "object") return null;
|
|
535
|
+
const s = rec(v);
|
|
536
|
+
const top = s.most_expensive ? rec(s.most_expensive) : null;
|
|
537
|
+
return {
|
|
538
|
+
avg: num2(s.avg_market_price ?? s.avg_price),
|
|
539
|
+
min: num2(s.min_price),
|
|
540
|
+
max: num2(s.max_price),
|
|
541
|
+
mostExpensive: top ? { id: str2(top.id), name: str2(top.name), price: num2(top.price) } : null,
|
|
542
|
+
trendDirection: str2(s.trend_direction)
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
function normalizeStatistics(raw) {
|
|
546
|
+
const r = rec(raw);
|
|
547
|
+
const info = rec(r.set_info);
|
|
548
|
+
const stats = rec(r.price_statistics);
|
|
549
|
+
const rarity = {};
|
|
550
|
+
for (const [k, v] of Object.entries(rec(r.rarity_breakdown))) rarity[k] = num2(v) ?? 0;
|
|
551
|
+
return {
|
|
552
|
+
set: { ...setRef(info.name, info.set_code, info.set_id), totalCards: num2(info.total_cards) },
|
|
553
|
+
tcgplayer: priceStats(stats.tcgplayer),
|
|
554
|
+
cardmarket: priceStats(stats.cardmarket),
|
|
555
|
+
rarityBreakdown: rarity,
|
|
556
|
+
raw
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
function normalizeCompletionValue(raw) {
|
|
560
|
+
const r = rec(raw);
|
|
561
|
+
const info = rec(r.set_info);
|
|
562
|
+
const cost = rec(r.completion_cost);
|
|
563
|
+
const t = cost.tcgplayer ? rec(cost.tcgplayer) : null;
|
|
564
|
+
const c = cost.cardmarket ? rec(cost.cardmarket) : null;
|
|
565
|
+
const byRarity = {};
|
|
566
|
+
for (const [k, v] of Object.entries(rec(r.breakdown_by_rarity))) {
|
|
567
|
+
byRarity[k] = { count: num2(rec(v).count) ?? 0, totalCost: num2(rec(v).total_cost) };
|
|
568
|
+
}
|
|
569
|
+
return {
|
|
570
|
+
set: { ...setRef(info.name, info.set_code, info.set_id), totalCards: num2(info.total_cards) },
|
|
571
|
+
tcgplayer: t ? { currency: str2(t.currency), low: num2(t.low_estimate), market: num2(t.market_estimate), high: num2(t.high_estimate) } : null,
|
|
572
|
+
cardmarket: c ? { currency: str2(c.currency), low: num2(c.low_estimate), avg: num2(c.avg_estimate), trend: num2(c.trend_estimate) } : null,
|
|
573
|
+
byRarity,
|
|
574
|
+
note: str2(cost.note),
|
|
575
|
+
raw
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
function pct(v) {
|
|
579
|
+
const s = str2(v);
|
|
580
|
+
return s === null ? null : num2(s.replace("%", "").replace("+", ""));
|
|
581
|
+
}
|
|
582
|
+
function normalizeTrending(raw) {
|
|
583
|
+
return arr(rec(raw).trending_sets).map((item) => {
|
|
584
|
+
const s = rec(item);
|
|
585
|
+
const cm = rec(s.cardmarket);
|
|
586
|
+
return {
|
|
587
|
+
set: setRef(s.set_name, s.set_code, s.set_id),
|
|
588
|
+
cardCount: num2(s.card_count),
|
|
589
|
+
change: str2(cm.price_change),
|
|
590
|
+
changePct: pct(cm.price_change),
|
|
591
|
+
avgPriceCurrent: num2(cm.avg_price_current),
|
|
592
|
+
avgPricePrevious: num2(cm.avg_price_previous),
|
|
593
|
+
topMovers: arr(cm.top_movers).map((m) => ({ name: str2(rec(m).name), change: str2(rec(m).change) })),
|
|
594
|
+
raw: item
|
|
595
|
+
};
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
function normalizeTopCards(raw) {
|
|
599
|
+
return arr(rec(raw).top_cards).map((item) => {
|
|
600
|
+
const c = rec(item);
|
|
601
|
+
const info = rec(c.card_info);
|
|
602
|
+
const pair = setPair(info.set_code, info.set_id);
|
|
603
|
+
return {
|
|
604
|
+
id: str2(c.id) ?? "",
|
|
605
|
+
name: str2(info.name) ?? "",
|
|
606
|
+
number: str2(info.card_number),
|
|
607
|
+
rarity: str2(info.rarity),
|
|
608
|
+
setName: str2(info.set_name),
|
|
609
|
+
setId: pair.id,
|
|
610
|
+
setCode: pair.code,
|
|
611
|
+
price: num2(c.current_price),
|
|
612
|
+
change7d: str2(c.change_7d),
|
|
613
|
+
raw: item
|
|
614
|
+
};
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
function historySide(v) {
|
|
618
|
+
const variants = v && typeof v === "object" ? rec(rec(v).variants) : null;
|
|
619
|
+
if (!variants) return null;
|
|
620
|
+
const out = {};
|
|
621
|
+
for (const [name, data] of Object.entries(variants)) {
|
|
622
|
+
const d = rec(data);
|
|
623
|
+
const current = {};
|
|
624
|
+
for (const [k, val] of Object.entries(rec(d.current))) current[k] = num2(val);
|
|
625
|
+
const price = {};
|
|
626
|
+
const change = {};
|
|
627
|
+
for (const day of DAYS) {
|
|
628
|
+
price[day] = num2(d[`price_${day}d`]);
|
|
629
|
+
change[day] = str2(d[`change_${day}d`]);
|
|
630
|
+
}
|
|
631
|
+
out[name] = { price, change, current, lastUpdated: str2(d.last_updated) };
|
|
632
|
+
}
|
|
633
|
+
return out;
|
|
634
|
+
}
|
|
635
|
+
function normalizePriceHistory(raw) {
|
|
636
|
+
const r = rec(raw);
|
|
637
|
+
return { id: str2(r.id) ?? "", name: str2(r.card_name), tcgplayer: historySide(r.tcgplayer), cardmarket: historySide(r.cardmarket), raw };
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// src/normalize/sets.ts
|
|
641
|
+
function normalizeSet(raw) {
|
|
642
|
+
const r = rec(raw);
|
|
643
|
+
return {
|
|
644
|
+
id: str2(r.set_id) ?? str2(r.group_id) ?? "",
|
|
645
|
+
code: str2(r.set_code),
|
|
646
|
+
name: str2(r.name) ?? str2(r.set_name) ?? "",
|
|
647
|
+
language: str2(r.language),
|
|
648
|
+
releaseDate: str2(r.release_date),
|
|
649
|
+
cardCount: num2(r.card_count ?? r.total_cards),
|
|
650
|
+
sealedCount: num2(r.sealed_count),
|
|
651
|
+
raw
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// src/pagination.ts
|
|
656
|
+
function makePage(items, meta) {
|
|
657
|
+
const totalPages = meta.limit > 0 ? Math.ceil(meta.total / meta.limit) : 0;
|
|
658
|
+
return { items, page: meta.page, limit: meta.limit, total: meta.total, totalPages };
|
|
659
|
+
}
|
|
660
|
+
async function* paginate(fetchPage, options = {}) {
|
|
661
|
+
let page = options.startPage ?? 1;
|
|
662
|
+
let yielded = 0;
|
|
663
|
+
for (; ; ) {
|
|
664
|
+
const res = await fetchPage(page);
|
|
665
|
+
for (const item of res.items) {
|
|
666
|
+
if (options.maxItems !== void 0 && yielded >= options.maxItems) return;
|
|
667
|
+
yield item;
|
|
668
|
+
yielded++;
|
|
669
|
+
}
|
|
670
|
+
if (res.items.length === 0 || page >= res.totalPages) return;
|
|
671
|
+
page++;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// src/validation.ts
|
|
676
|
+
function checkPage(page) {
|
|
677
|
+
if (page === void 0) return void 0;
|
|
678
|
+
if (!Number.isInteger(page) || page < 1) throw new ValidationError(`page must be an integer >= 1 (got ${page})`);
|
|
679
|
+
return page;
|
|
680
|
+
}
|
|
681
|
+
function checkLimit(limit, max) {
|
|
682
|
+
if (limit === void 0) return void 0;
|
|
683
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > max) {
|
|
684
|
+
throw new ValidationError(`limit must be an integer between 1 and ${max} (got ${limit})`);
|
|
685
|
+
}
|
|
686
|
+
return limit;
|
|
687
|
+
}
|
|
688
|
+
function checkPeriod(period) {
|
|
689
|
+
if (period !== void 0 && period !== "7d") throw new ValidationError(`period ${period} is not available (only 7d)`);
|
|
690
|
+
return "7d";
|
|
691
|
+
}
|
|
692
|
+
function checkVariant(variant) {
|
|
693
|
+
if (variant === void 0) return void 0;
|
|
694
|
+
if (variant !== "normal" && variant !== "foil") throw new ValidationError(`variant must be normal or foil (got ${variant})`);
|
|
695
|
+
return variant;
|
|
696
|
+
}
|
|
697
|
+
function checkText(name, value, min = 1) {
|
|
698
|
+
if (typeof value !== "string" || value.trim().length < min) {
|
|
699
|
+
throw new ValidationError(`${name} must be at least ${min} character${min === 1 ? "" : "s"} long`);
|
|
700
|
+
}
|
|
701
|
+
return value.trim();
|
|
702
|
+
}
|
|
703
|
+
function seg(value) {
|
|
704
|
+
return encodeURIComponent(checkText("id", value));
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// src/worlds/world.ts
|
|
708
|
+
var LIMITS = { search: 100, set: 200, trending: 50, topCards: 100 };
|
|
709
|
+
|
|
710
|
+
// src/worlds/card-world.ts
|
|
711
|
+
var CardWorld = class {
|
|
712
|
+
constructor(http, config) {
|
|
713
|
+
this.http = http;
|
|
714
|
+
this.config = config;
|
|
715
|
+
}
|
|
716
|
+
http;
|
|
717
|
+
config;
|
|
718
|
+
path(rest) {
|
|
719
|
+
return `${this.config.prefix}${rest}`;
|
|
720
|
+
}
|
|
721
|
+
async search(q, options = {}) {
|
|
722
|
+
const body = rec(
|
|
723
|
+
await this.http.get(this.path("/search"), {
|
|
724
|
+
q: checkText("q", q, this.config.minQuery),
|
|
725
|
+
page: checkPage(options.page),
|
|
726
|
+
limit: checkLimit(options.limit, LIMITS.search)
|
|
727
|
+
})
|
|
728
|
+
);
|
|
729
|
+
const p = rec(body.pagination);
|
|
730
|
+
const items = arr(body.results).map((c) => normalizeCard(c));
|
|
731
|
+
const page = makePage(items, { page: num2(p.page) ?? 1, limit: num2(p.limit) ?? items.length, total: num2(p.total) ?? items.length });
|
|
732
|
+
return { ...page, truncated: rec(body.metadata).truncated === true };
|
|
733
|
+
}
|
|
734
|
+
searchAll(q, options = {}) {
|
|
735
|
+
return paginate((page) => this.search(q, { page, limit: options.limit }), { maxItems: options.maxItems });
|
|
736
|
+
}
|
|
737
|
+
async card(id) {
|
|
738
|
+
return normalizeCard(await this.http.get(this.path(`/cards/${seg(id)}`)));
|
|
739
|
+
}
|
|
740
|
+
async sets(options = {}) {
|
|
741
|
+
const body = rec(await this.http.get(this.path("/sets"), { language: options.language }));
|
|
742
|
+
return arr(body.data).map(normalizeSet);
|
|
743
|
+
}
|
|
744
|
+
async set(code, options = {}) {
|
|
745
|
+
const body = rec(
|
|
746
|
+
await this.http.get(this.path(`/sets/${seg(code)}`), {
|
|
747
|
+
page: checkPage(options.page),
|
|
748
|
+
limit: checkLimit(options.limit, LIMITS.set),
|
|
749
|
+
language: options.language
|
|
750
|
+
})
|
|
751
|
+
);
|
|
752
|
+
if (isDisambiguation(body)) throw ambiguousSetError(200, body);
|
|
753
|
+
const set = normalizeSet(body.set);
|
|
754
|
+
const ctx = { setId: set.id || null, setCode: set.code };
|
|
755
|
+
const items = arr(body.cards).map((c) => normalizeCard(c, ctx));
|
|
756
|
+
const p = rec(body.pagination);
|
|
757
|
+
const page = makePage(items, { page: num2(p.page) ?? 1, limit: num2(p.limit) ?? items.length, total: num2(p.total) ?? items.length });
|
|
758
|
+
return { ...page, set };
|
|
759
|
+
}
|
|
760
|
+
setCards(code, options = {}) {
|
|
761
|
+
return paginate((page) => this.set(code, { page, limit: options.limit, language: options.language }), {
|
|
762
|
+
maxItems: options.maxItems
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
async prices(code, options = {}) {
|
|
766
|
+
return normalizeSetPrices(await this.http.get(this.path(`/prices/${seg(code)}`), { source: options.source }));
|
|
767
|
+
}
|
|
768
|
+
async statistics(code, options = {}) {
|
|
769
|
+
return normalizeStatistics(await this.http.get(this.path(`/sets/${seg(code)}/statistics`), { variant: checkVariant(options.variant) }));
|
|
770
|
+
}
|
|
771
|
+
async completionValue(code) {
|
|
772
|
+
return normalizeCompletionValue(await this.http.get(this.path(`/sets/${seg(code)}/completion-value`)));
|
|
773
|
+
}
|
|
774
|
+
async trending(options = {}) {
|
|
775
|
+
const period = checkPeriod(options.period);
|
|
776
|
+
return normalizeTrending(await this.http.get(this.path("/sets/trending"), { period, limit: checkLimit(options.limit, LIMITS.trending) }));
|
|
777
|
+
}
|
|
778
|
+
async topCards(options = {}) {
|
|
779
|
+
return normalizeTopCards(
|
|
780
|
+
await this.http.get(this.path("/analytics/top-cards"), {
|
|
781
|
+
metric: options.metric,
|
|
782
|
+
source: options.source,
|
|
783
|
+
limit: checkLimit(options.limit, LIMITS.topCards)
|
|
784
|
+
})
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
};
|
|
788
|
+
|
|
789
|
+
// src/worlds/onepiece-world.ts
|
|
790
|
+
var NUMERIC_ID2 = /^-?\d+$/;
|
|
791
|
+
var OnePieceWorld = class {
|
|
792
|
+
constructor(http) {
|
|
793
|
+
this.http = http;
|
|
794
|
+
}
|
|
795
|
+
http;
|
|
796
|
+
async search(q, options = {}) {
|
|
797
|
+
const body = rec(
|
|
798
|
+
await this.http.get("/op/search", {
|
|
799
|
+
q: checkText("q", q),
|
|
800
|
+
page: checkPage(options.page),
|
|
801
|
+
limit: checkLimit(options.limit, LIMITS.search)
|
|
802
|
+
})
|
|
803
|
+
);
|
|
804
|
+
const items = arr(body.data).map((c) => normalizeOpCard(c));
|
|
805
|
+
const page = makePage(items, { page: num2(body.page) ?? 1, limit: num2(body.limit) ?? items.length, total: num2(body.total) ?? items.length });
|
|
806
|
+
return { ...page, truncated: false };
|
|
807
|
+
}
|
|
808
|
+
searchAll(q, options = {}) {
|
|
809
|
+
return paginate((page) => this.search(q, { page, limit: options.limit }), { maxItems: options.maxItems });
|
|
810
|
+
}
|
|
811
|
+
async card(id) {
|
|
812
|
+
return normalizeOpCard(await this.http.get(`/op/cards/${seg(id)}`));
|
|
813
|
+
}
|
|
814
|
+
async sets(options = {}) {
|
|
815
|
+
const body = rec(await this.http.get("/op/sets", { language: options.language }));
|
|
816
|
+
return arr(body.data).map(normalizeSet);
|
|
817
|
+
}
|
|
818
|
+
/** `options.language` is ignored: One Piece maps a code to its first matching set. */
|
|
819
|
+
async set(code, options = {}) {
|
|
820
|
+
const body = rec(
|
|
821
|
+
await this.http.get(`/op/sets/${seg(code)}`, {
|
|
822
|
+
page: checkPage(options.page),
|
|
823
|
+
limit: checkLimit(options.limit, LIMITS.set)
|
|
824
|
+
})
|
|
825
|
+
);
|
|
826
|
+
const set = normalizeSet(body.set);
|
|
827
|
+
const ctx = { setId: set.id || null, setCode: set.code };
|
|
828
|
+
const items = arr(body.data).map((c) => normalizeOpCard(c, ctx));
|
|
829
|
+
const page = makePage(items, { page: num2(body.page) ?? 1, limit: num2(body.limit) ?? items.length, total: num2(body.total) ?? items.length });
|
|
830
|
+
return { ...page, set };
|
|
831
|
+
}
|
|
832
|
+
setCards(code, options = {}) {
|
|
833
|
+
return paginate((page) => this.set(code, { page, limit: options.limit }), { maxItems: options.maxItems });
|
|
834
|
+
}
|
|
835
|
+
/** The set goes in the query here (`group_id` for numeric ids, `set_code` otherwise). */
|
|
836
|
+
async prices(code, options = {}) {
|
|
837
|
+
if (options.source !== void 0) throw new ValidationError("source is not supported for One Piece prices");
|
|
838
|
+
const c = checkText("code", code);
|
|
839
|
+
const query = NUMERIC_ID2.test(c) ? { group_id: c } : { set_code: c };
|
|
840
|
+
return normalizeSetPrices(await this.http.get("/op/prices", query));
|
|
841
|
+
}
|
|
842
|
+
async statistics(code, options = {}) {
|
|
843
|
+
return normalizeStatistics(await this.http.get(`/op/sets/${seg(code)}/statistics`, { variant: checkVariant(options.variant) }));
|
|
844
|
+
}
|
|
845
|
+
async completionValue(code) {
|
|
846
|
+
return normalizeCompletionValue(await this.http.get(`/op/sets/${seg(code)}/completion-value`));
|
|
847
|
+
}
|
|
848
|
+
async trending(options = {}) {
|
|
849
|
+
const period = checkPeriod(options.period);
|
|
850
|
+
return normalizeTrending(await this.http.get("/op/sets/trending", { period, limit: checkLimit(options.limit, LIMITS.trending) }));
|
|
851
|
+
}
|
|
852
|
+
async topCards(options = {}) {
|
|
853
|
+
return normalizeTopCards(
|
|
854
|
+
await this.http.get("/op/analytics/top-cards", {
|
|
855
|
+
metric: options.metric,
|
|
856
|
+
source: options.source,
|
|
857
|
+
limit: checkLimit(options.limit, LIMITS.topCards)
|
|
858
|
+
})
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
};
|
|
862
|
+
|
|
863
|
+
// src/worlds/pokemon-world.ts
|
|
864
|
+
var PokemonWorld = class extends CardWorld {
|
|
865
|
+
constructor(http) {
|
|
866
|
+
super(http, { prefix: "", minQuery: 1 });
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
869
|
+
* Card by id, or by plain card number together with `setCode` (`card('086/078', { setCode: 'SV8a' })`).
|
|
870
|
+
* Ids from `search` and `set` are the safest input.
|
|
871
|
+
*/
|
|
872
|
+
async card(id, options = {}) {
|
|
873
|
+
const body = await this.http.get(this.path(`/cards/${seg(id)}`), { set_code: options.setCode });
|
|
874
|
+
if (isDisambiguation(body)) throw ambiguousSetError(200, body);
|
|
875
|
+
return normalizeCard(body);
|
|
876
|
+
}
|
|
877
|
+
/** Price snapshots at 7/14/30/60/120 days, per finish (not a time series). Pro or trial. */
|
|
878
|
+
async priceHistory(id) {
|
|
879
|
+
return normalizePriceHistory(await this.http.get(`/cards/${seg(id)}/price-history`));
|
|
880
|
+
}
|
|
881
|
+
};
|
|
882
|
+
|
|
883
|
+
// src/client.ts
|
|
884
|
+
var PokeWallet = class {
|
|
885
|
+
pokemon;
|
|
886
|
+
onepiece;
|
|
887
|
+
games;
|
|
888
|
+
#http;
|
|
889
|
+
#baseUrl;
|
|
890
|
+
constructor(options = {}) {
|
|
891
|
+
this.#http = new HttpClient(options);
|
|
892
|
+
this.#baseUrl = options.baseUrl;
|
|
893
|
+
this.pokemon = new PokemonWorld(this.#http);
|
|
894
|
+
this.onepiece = new OnePieceWorld(this.#http);
|
|
895
|
+
const games = {};
|
|
896
|
+
for (const code of GAME_CODES) {
|
|
897
|
+
games[code] = new CardWorld(this.#http, { prefix: `/${code}`, minQuery: 2 });
|
|
898
|
+
}
|
|
899
|
+
this.games = games;
|
|
900
|
+
}
|
|
901
|
+
/** Quota counters from the most recent answer, or null before the first call. */
|
|
902
|
+
get lastRateLimit() {
|
|
903
|
+
return this.#http.lastRateLimit;
|
|
904
|
+
}
|
|
905
|
+
/** The world for a game code known only at runtime. */
|
|
906
|
+
game(code) {
|
|
907
|
+
return this.games[code];
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Service status. Works without a key; the header is sent only when a key is set.
|
|
911
|
+
*
|
|
912
|
+
* The API answers 503 with a full report when a critical service is down. That report is
|
|
913
|
+
* information, not a failure: it is returned like a 200, with `status: 'unhealthy'` and the
|
|
914
|
+
* failing `checks`, and is never retried. A 503 without a report (for example an error page
|
|
915
|
+
* from the edge) still throws `UnavailableError`.
|
|
916
|
+
*/
|
|
917
|
+
async health() {
|
|
918
|
+
return normalizeHealth(await this.#http.get("/health", void 0, { isPublic: true, accept: isHealthReport }));
|
|
919
|
+
}
|
|
920
|
+
/** The ten games with their route maps. Works without a key. */
|
|
921
|
+
async listGames() {
|
|
922
|
+
return normalizeGames(await this.#http.get("/games", void 0, { isPublic: true }));
|
|
923
|
+
}
|
|
924
|
+
toJSON() {
|
|
925
|
+
return { client: this.#http.toJSON() };
|
|
926
|
+
}
|
|
927
|
+
[/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
|
|
928
|
+
return `PokeWallet ${JSON.stringify(this.toJSON())}`;
|
|
929
|
+
}
|
|
930
|
+
/** See {@link imageUrl}: the URL still needs the API key header. */
|
|
931
|
+
imageUrl(id, options = {}) {
|
|
932
|
+
return imageUrl(id, { ...options, baseUrl: this.#baseUrl });
|
|
933
|
+
}
|
|
934
|
+
};
|
|
935
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
936
|
+
0 && (module.exports = {
|
|
937
|
+
AmbiguousSetError,
|
|
938
|
+
AuthError,
|
|
939
|
+
BadRequestError,
|
|
940
|
+
FeatureNotEnabledError,
|
|
941
|
+
GAME_CODES,
|
|
942
|
+
GameUnavailableError,
|
|
943
|
+
NetworkError,
|
|
944
|
+
NotFoundError,
|
|
945
|
+
PlanRequiredError,
|
|
946
|
+
PokeWallet,
|
|
947
|
+
PokeWalletError,
|
|
948
|
+
RateLimitError,
|
|
949
|
+
ServerError,
|
|
950
|
+
UnavailableError,
|
|
951
|
+
VERSION,
|
|
952
|
+
ValidationError,
|
|
953
|
+
imageUrl,
|
|
954
|
+
paginate
|
|
955
|
+
});
|
|
956
|
+
//# sourceMappingURL=index.cjs.map
|