mtgjson-sdk 0.1.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/LICENSE +21 -0
- package/README.md +463 -0
- package/dist/index.cjs +1874 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1043 -0
- package/dist/index.d.ts +1043 -0
- package/dist/index.js +1840 -0
- package/dist/index.js.map +1 -0
- package/package.json +47 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1840 @@
|
|
|
1
|
+
// src/booster/simulator.ts
|
|
2
|
+
var BoosterSimulator = class {
|
|
3
|
+
_conn;
|
|
4
|
+
constructor(conn) {
|
|
5
|
+
this._conn = conn;
|
|
6
|
+
}
|
|
7
|
+
async _ensure() {
|
|
8
|
+
await this._conn.ensureViews("sets", "cards");
|
|
9
|
+
}
|
|
10
|
+
async _getBoosterConfig(setCode) {
|
|
11
|
+
await this._ensure();
|
|
12
|
+
try {
|
|
13
|
+
const rows = await this._conn.execute(
|
|
14
|
+
"SELECT booster FROM sets WHERE code = $1",
|
|
15
|
+
[setCode.toUpperCase()]
|
|
16
|
+
);
|
|
17
|
+
if (!rows.length || !rows[0].booster) return null;
|
|
18
|
+
return rows[0].booster;
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async availableTypes(setCode) {
|
|
24
|
+
const config = await this._getBoosterConfig(setCode);
|
|
25
|
+
if (!config) return [];
|
|
26
|
+
return Object.keys(config);
|
|
27
|
+
}
|
|
28
|
+
async openPack(setCode, boosterType = "draft") {
|
|
29
|
+
const configs = await this._getBoosterConfig(setCode);
|
|
30
|
+
if (!configs || !(boosterType in configs)) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`No booster config for set '${setCode}' type '${boosterType}'. Available: ${configs ? Object.keys(configs) : []}`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
const config = configs[boosterType];
|
|
36
|
+
const packTemplate = pickPack(config.boosters);
|
|
37
|
+
const sheets = config.sheets;
|
|
38
|
+
const cardUuids = [];
|
|
39
|
+
for (const [sheetName, count] of Object.entries(packTemplate.contents)) {
|
|
40
|
+
if (!(sheetName in sheets)) continue;
|
|
41
|
+
const sheet = sheets[sheetName];
|
|
42
|
+
const picked = pickFromSheet(sheet, count);
|
|
43
|
+
cardUuids.push(...picked);
|
|
44
|
+
}
|
|
45
|
+
if (cardUuids.length === 0) return [];
|
|
46
|
+
await this._conn.ensureViews("cards");
|
|
47
|
+
const placeholders = cardUuids.map((_, i) => `$${i + 1}`).join(", ");
|
|
48
|
+
const sql = `SELECT * FROM cards WHERE uuid IN (${placeholders})`;
|
|
49
|
+
const rows = await this._conn.execute(sql, cardUuids);
|
|
50
|
+
const uuidToRow = /* @__PURE__ */ new Map();
|
|
51
|
+
for (const r of rows) {
|
|
52
|
+
uuidToRow.set(r.uuid, r);
|
|
53
|
+
}
|
|
54
|
+
const ordered = [];
|
|
55
|
+
for (const u of cardUuids) {
|
|
56
|
+
const row = uuidToRow.get(u);
|
|
57
|
+
if (row) ordered.push(row);
|
|
58
|
+
}
|
|
59
|
+
return ordered;
|
|
60
|
+
}
|
|
61
|
+
async openBox(setCode, boosterType = "draft", packs = 36) {
|
|
62
|
+
const results = [];
|
|
63
|
+
for (let i = 0; i < packs; i++) {
|
|
64
|
+
results.push(await this.openPack(setCode, boosterType));
|
|
65
|
+
}
|
|
66
|
+
return results;
|
|
67
|
+
}
|
|
68
|
+
async sheetContents(setCode, boosterType, sheetName) {
|
|
69
|
+
const configs = await this._getBoosterConfig(setCode);
|
|
70
|
+
if (!configs || !(boosterType in configs)) return null;
|
|
71
|
+
const sheets = configs[boosterType].sheets ?? {};
|
|
72
|
+
const sheet = sheets[sheetName];
|
|
73
|
+
if (!sheet) return null;
|
|
74
|
+
return sheet.cards;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
function pickPack(boosters) {
|
|
78
|
+
const weights = boosters.map((b) => b.weight);
|
|
79
|
+
return weightedChoice(boosters, weights);
|
|
80
|
+
}
|
|
81
|
+
function pickFromSheet(sheet, count) {
|
|
82
|
+
const cards = sheet.cards;
|
|
83
|
+
const uuids = Object.keys(cards);
|
|
84
|
+
const weights = Object.values(cards);
|
|
85
|
+
const allowDuplicates = sheet.allowDuplicates ?? false;
|
|
86
|
+
if (allowDuplicates) {
|
|
87
|
+
const picked2 = [];
|
|
88
|
+
for (let i = 0; i < count; i++) {
|
|
89
|
+
picked2.push(weightedChoice(uuids, weights));
|
|
90
|
+
}
|
|
91
|
+
return picked2;
|
|
92
|
+
}
|
|
93
|
+
if (count >= uuids.length) {
|
|
94
|
+
const result = [...uuids];
|
|
95
|
+
shuffle(result);
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
const picked = [];
|
|
99
|
+
const remainingUuids = [...uuids];
|
|
100
|
+
const remainingWeights = [...weights];
|
|
101
|
+
for (let i = 0; i < Math.min(count, remainingUuids.length); i++) {
|
|
102
|
+
const choice = weightedChoice(remainingUuids, remainingWeights);
|
|
103
|
+
picked.push(choice);
|
|
104
|
+
const idx = remainingUuids.indexOf(choice);
|
|
105
|
+
remainingUuids.splice(idx, 1);
|
|
106
|
+
remainingWeights.splice(idx, 1);
|
|
107
|
+
}
|
|
108
|
+
return picked;
|
|
109
|
+
}
|
|
110
|
+
function weightedChoice(items, weights) {
|
|
111
|
+
const totalWeight = weights.reduce((a, b) => a + b, 0);
|
|
112
|
+
let random = Math.random() * totalWeight;
|
|
113
|
+
for (let i = 0; i < items.length; i++) {
|
|
114
|
+
random -= weights[i];
|
|
115
|
+
if (random <= 0) return items[i];
|
|
116
|
+
}
|
|
117
|
+
return items[items.length - 1];
|
|
118
|
+
}
|
|
119
|
+
function shuffle(array) {
|
|
120
|
+
for (let i = array.length - 1; i > 0; i--) {
|
|
121
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
122
|
+
[array[i], array[j]] = [array[j], array[i]];
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/cache.ts
|
|
127
|
+
import { createWriteStream } from "fs";
|
|
128
|
+
import { existsSync } from "fs";
|
|
129
|
+
import {
|
|
130
|
+
mkdir,
|
|
131
|
+
readFile,
|
|
132
|
+
rename,
|
|
133
|
+
rm,
|
|
134
|
+
unlink,
|
|
135
|
+
writeFile
|
|
136
|
+
} from "fs/promises";
|
|
137
|
+
import { get as httpsGet } from "https";
|
|
138
|
+
import { join as join2 } from "path";
|
|
139
|
+
import { createGunzip } from "zlib";
|
|
140
|
+
|
|
141
|
+
// src/config.ts
|
|
142
|
+
import { homedir, platform } from "os";
|
|
143
|
+
import { join } from "path";
|
|
144
|
+
var CDN_BASE = "https://mtgjson.com/api/v5";
|
|
145
|
+
var PARQUET_FILES = {
|
|
146
|
+
// Flat normalized tables
|
|
147
|
+
cards: "parquet/cards.parquet",
|
|
148
|
+
tokens: "parquet/tokens.parquet",
|
|
149
|
+
sets: "parquet/sets.parquet",
|
|
150
|
+
card_identifiers: "parquet/cardIdentifiers.parquet",
|
|
151
|
+
card_legalities: "parquet/cardLegalities.parquet",
|
|
152
|
+
card_foreign_data: "parquet/cardForeignData.parquet",
|
|
153
|
+
card_rulings: "parquet/cardRulings.parquet",
|
|
154
|
+
card_purchase_urls: "parquet/cardPurchaseUrls.parquet",
|
|
155
|
+
set_translations: "parquet/setTranslations.parquet",
|
|
156
|
+
token_identifiers: "parquet/tokenIdentifiers.parquet",
|
|
157
|
+
// Booster tables
|
|
158
|
+
set_booster_content_weights: "parquet/setBoosterContentWeights.parquet",
|
|
159
|
+
set_booster_contents: "parquet/setBoosterContents.parquet",
|
|
160
|
+
set_booster_sheet_cards: "parquet/setBoosterSheetCards.parquet",
|
|
161
|
+
set_booster_sheets: "parquet/setBoosterSheets.parquet",
|
|
162
|
+
// Full nested
|
|
163
|
+
all_printings: "parquet/AllPrintings.parquet",
|
|
164
|
+
// Prices and SKUs
|
|
165
|
+
all_prices_today: "parquet/AllPricesToday.parquet",
|
|
166
|
+
all_prices: "parquet/AllPrices.parquet",
|
|
167
|
+
tcgplayer_skus: "parquet/TcgplayerSkus.parquet"
|
|
168
|
+
};
|
|
169
|
+
var JSON_FILES = {
|
|
170
|
+
keywords: "Keywords.json",
|
|
171
|
+
card_types: "CardTypes.json",
|
|
172
|
+
deck_list: "DeckList.json",
|
|
173
|
+
enum_values: "EnumValues.json",
|
|
174
|
+
meta: "Meta.json"
|
|
175
|
+
};
|
|
176
|
+
var META_URL = `${CDN_BASE}/Meta.json`;
|
|
177
|
+
function defaultCacheDir() {
|
|
178
|
+
const sys = platform();
|
|
179
|
+
let base;
|
|
180
|
+
if (sys === "win32") {
|
|
181
|
+
base = join(homedir(), "AppData", "Local");
|
|
182
|
+
} else if (sys === "darwin") {
|
|
183
|
+
base = join(homedir(), "Library", "Caches");
|
|
184
|
+
} else {
|
|
185
|
+
base = join(homedir(), ".cache");
|
|
186
|
+
}
|
|
187
|
+
return join(base, "mtgjson-sdk");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/cache.ts
|
|
191
|
+
var CacheManager = class {
|
|
192
|
+
cacheDir;
|
|
193
|
+
offline;
|
|
194
|
+
timeout;
|
|
195
|
+
_remoteVersion = null;
|
|
196
|
+
_onProgress;
|
|
197
|
+
constructor(options) {
|
|
198
|
+
this.cacheDir = options?.cacheDir ?? defaultCacheDir();
|
|
199
|
+
this.offline = options?.offline ?? false;
|
|
200
|
+
this.timeout = options?.timeout ?? 12e4;
|
|
201
|
+
this._onProgress = options?.onProgress ?? null;
|
|
202
|
+
}
|
|
203
|
+
async init() {
|
|
204
|
+
await mkdir(this.cacheDir, { recursive: true });
|
|
205
|
+
}
|
|
206
|
+
close() {
|
|
207
|
+
}
|
|
208
|
+
async _localVersion() {
|
|
209
|
+
const versionFile = join2(this.cacheDir, "version.txt");
|
|
210
|
+
try {
|
|
211
|
+
return (await readFile(versionFile, "utf-8")).trim();
|
|
212
|
+
} catch {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
async _saveVersion(version) {
|
|
217
|
+
await writeFile(join2(this.cacheDir, "version.txt"), version, "utf-8");
|
|
218
|
+
}
|
|
219
|
+
async remoteVersion() {
|
|
220
|
+
if (this._remoteVersion) return this._remoteVersion;
|
|
221
|
+
if (this.offline) return null;
|
|
222
|
+
try {
|
|
223
|
+
const resp = await fetch(META_URL, {
|
|
224
|
+
signal: AbortSignal.timeout(this.timeout)
|
|
225
|
+
});
|
|
226
|
+
if (!resp.ok) return null;
|
|
227
|
+
const data = await resp.json();
|
|
228
|
+
this._remoteVersion = data?.data?.version ?? data?.meta?.version ?? null;
|
|
229
|
+
return this._remoteVersion;
|
|
230
|
+
} catch {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async isStale() {
|
|
235
|
+
const local = await this._localVersion();
|
|
236
|
+
const remote = await this.remoteVersion();
|
|
237
|
+
if (remote === null) return false;
|
|
238
|
+
if (local === null) return true;
|
|
239
|
+
return local !== remote;
|
|
240
|
+
}
|
|
241
|
+
async _downloadFile(filename, dest) {
|
|
242
|
+
const url = `${CDN_BASE}/${filename}`;
|
|
243
|
+
const dir = join2(dest, "..");
|
|
244
|
+
await mkdir(dir, { recursive: true });
|
|
245
|
+
const tmpDest = `${dest}.tmp`;
|
|
246
|
+
try {
|
|
247
|
+
await new Promise((resolve, reject) => {
|
|
248
|
+
httpsGet(url, { timeout: this.timeout }, (res) => {
|
|
249
|
+
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
250
|
+
httpsGet(
|
|
251
|
+
res.headers.location,
|
|
252
|
+
{ timeout: this.timeout },
|
|
253
|
+
(res2) => {
|
|
254
|
+
this._handleDownloadResponse(
|
|
255
|
+
res2,
|
|
256
|
+
tmpDest,
|
|
257
|
+
filename,
|
|
258
|
+
resolve,
|
|
259
|
+
reject
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
).on("error", reject);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
this._handleDownloadResponse(res, tmpDest, filename, resolve, reject);
|
|
266
|
+
}).on("error", reject);
|
|
267
|
+
});
|
|
268
|
+
await rename(tmpDest, dest);
|
|
269
|
+
} catch (err) {
|
|
270
|
+
try {
|
|
271
|
+
await unlink(tmpDest);
|
|
272
|
+
} catch {
|
|
273
|
+
}
|
|
274
|
+
throw err;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
_handleDownloadResponse(res, tmpDest, filename, resolve, reject) {
|
|
278
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
279
|
+
reject(new Error(`HTTP ${res.statusCode} downloading ${filename}`));
|
|
280
|
+
res.resume();
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const total = res.headers["content-length"] ? Number.parseInt(res.headers["content-length"], 10) : null;
|
|
284
|
+
let downloaded = 0;
|
|
285
|
+
const ws = createWriteStream(tmpDest);
|
|
286
|
+
res.on("data", (chunk) => {
|
|
287
|
+
downloaded += chunk.length;
|
|
288
|
+
if (this._onProgress) {
|
|
289
|
+
this._onProgress(filename, downloaded, total);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
res.pipe(ws);
|
|
293
|
+
ws.on("finish", resolve);
|
|
294
|
+
ws.on("error", reject);
|
|
295
|
+
res.on("error", reject);
|
|
296
|
+
}
|
|
297
|
+
async ensureParquet(viewName) {
|
|
298
|
+
const filename = PARQUET_FILES[viewName];
|
|
299
|
+
if (!filename) throw new Error(`Unknown parquet view: ${viewName}`);
|
|
300
|
+
const localPath = join2(this.cacheDir, filename);
|
|
301
|
+
if (!existsSync(localPath) || await this.isStale()) {
|
|
302
|
+
if (this.offline) {
|
|
303
|
+
if (existsSync(localPath)) return localPath;
|
|
304
|
+
throw new Error(
|
|
305
|
+
`Parquet file ${filename} not cached and offline mode is enabled`
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
await this._downloadFile(filename, localPath);
|
|
309
|
+
const version = await this.remoteVersion();
|
|
310
|
+
if (version) await this._saveVersion(version);
|
|
311
|
+
}
|
|
312
|
+
return localPath;
|
|
313
|
+
}
|
|
314
|
+
async ensureJson(name) {
|
|
315
|
+
const filename = JSON_FILES[name];
|
|
316
|
+
if (!filename) throw new Error(`Unknown JSON file: ${name}`);
|
|
317
|
+
const localPath = join2(this.cacheDir, filename);
|
|
318
|
+
if (!existsSync(localPath) || await this.isStale()) {
|
|
319
|
+
if (this.offline) {
|
|
320
|
+
if (existsSync(localPath)) return localPath;
|
|
321
|
+
throw new Error(
|
|
322
|
+
`JSON file ${filename} not cached and offline mode is enabled`
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
await this._downloadFile(filename, localPath);
|
|
326
|
+
const version = await this.remoteVersion();
|
|
327
|
+
if (version) await this._saveVersion(version);
|
|
328
|
+
}
|
|
329
|
+
return localPath;
|
|
330
|
+
}
|
|
331
|
+
async loadJson(name) {
|
|
332
|
+
const path = await this.ensureJson(name);
|
|
333
|
+
try {
|
|
334
|
+
if (path.endsWith(".gz")) {
|
|
335
|
+
const compressed = await readFile(path);
|
|
336
|
+
const decompressed = await new Promise((resolve, reject) => {
|
|
337
|
+
const gunzip = createGunzip();
|
|
338
|
+
const chunks = [];
|
|
339
|
+
gunzip.on("data", (chunk) => chunks.push(chunk));
|
|
340
|
+
gunzip.on("end", () => resolve(Buffer.concat(chunks)));
|
|
341
|
+
gunzip.on("error", reject);
|
|
342
|
+
gunzip.end(compressed);
|
|
343
|
+
});
|
|
344
|
+
return JSON.parse(decompressed.toString("utf-8"));
|
|
345
|
+
}
|
|
346
|
+
const text = await readFile(path, "utf-8");
|
|
347
|
+
return JSON.parse(text);
|
|
348
|
+
} catch (err) {
|
|
349
|
+
try {
|
|
350
|
+
await unlink(path);
|
|
351
|
+
} catch {
|
|
352
|
+
}
|
|
353
|
+
throw new Error(
|
|
354
|
+
`Cache file '${path}' was corrupt and has been removed. Retry to re-download. Original error: ${err}`
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
async clear() {
|
|
359
|
+
try {
|
|
360
|
+
await rm(this.cacheDir, { recursive: true, force: true });
|
|
361
|
+
} catch {
|
|
362
|
+
}
|
|
363
|
+
await mkdir(this.cacheDir, { recursive: true });
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
// src/connection.ts
|
|
368
|
+
import { unlinkSync, writeFileSync } from "fs";
|
|
369
|
+
import { tmpdir } from "os";
|
|
370
|
+
import { join as join3 } from "path";
|
|
371
|
+
import { DuckDBInstance } from "@duckdb/node-api";
|
|
372
|
+
var STATIC_LIST_COLUMNS = {
|
|
373
|
+
cards: /* @__PURE__ */ new Set([
|
|
374
|
+
"artistIds",
|
|
375
|
+
"attractionLights",
|
|
376
|
+
"availability",
|
|
377
|
+
"boosterTypes",
|
|
378
|
+
"cardParts",
|
|
379
|
+
"colorIdentity",
|
|
380
|
+
"colorIndicator",
|
|
381
|
+
"colors",
|
|
382
|
+
"finishes",
|
|
383
|
+
"frameEffects",
|
|
384
|
+
"keywords",
|
|
385
|
+
"originalPrintings",
|
|
386
|
+
"otherFaceIds",
|
|
387
|
+
"printings",
|
|
388
|
+
"producedMana",
|
|
389
|
+
"promoTypes",
|
|
390
|
+
"rebalancedPrintings",
|
|
391
|
+
"subsets",
|
|
392
|
+
"subtypes",
|
|
393
|
+
"supertypes",
|
|
394
|
+
"types",
|
|
395
|
+
"variations"
|
|
396
|
+
]),
|
|
397
|
+
tokens: /* @__PURE__ */ new Set([
|
|
398
|
+
"artistIds",
|
|
399
|
+
"availability",
|
|
400
|
+
"boosterTypes",
|
|
401
|
+
"colorIdentity",
|
|
402
|
+
"colorIndicator",
|
|
403
|
+
"colors",
|
|
404
|
+
"finishes",
|
|
405
|
+
"frameEffects",
|
|
406
|
+
"keywords",
|
|
407
|
+
"otherFaceIds",
|
|
408
|
+
"producedMana",
|
|
409
|
+
"promoTypes",
|
|
410
|
+
"reverseRelated",
|
|
411
|
+
"subtypes",
|
|
412
|
+
"supertypes",
|
|
413
|
+
"types"
|
|
414
|
+
])
|
|
415
|
+
};
|
|
416
|
+
var IGNORED_COLUMNS = /* @__PURE__ */ new Set([
|
|
417
|
+
"text",
|
|
418
|
+
"originalText",
|
|
419
|
+
"flavorText",
|
|
420
|
+
"printedText",
|
|
421
|
+
"identifiers",
|
|
422
|
+
"legalities",
|
|
423
|
+
"leadershipSkills",
|
|
424
|
+
"purchaseUrls",
|
|
425
|
+
"relatedCards",
|
|
426
|
+
"rulings",
|
|
427
|
+
"sourceProducts",
|
|
428
|
+
"foreignData",
|
|
429
|
+
"translations",
|
|
430
|
+
"toughness",
|
|
431
|
+
"status",
|
|
432
|
+
"format",
|
|
433
|
+
"uris",
|
|
434
|
+
"scryfallUri"
|
|
435
|
+
]);
|
|
436
|
+
var JSON_CAST_COLUMNS = /* @__PURE__ */ new Set([
|
|
437
|
+
"identifiers",
|
|
438
|
+
"legalities",
|
|
439
|
+
"leadershipSkills",
|
|
440
|
+
"purchaseUrls",
|
|
441
|
+
"relatedCards",
|
|
442
|
+
"rulings",
|
|
443
|
+
"sourceProducts",
|
|
444
|
+
"foreignData",
|
|
445
|
+
"translations"
|
|
446
|
+
]);
|
|
447
|
+
var Connection = class _Connection {
|
|
448
|
+
_conn;
|
|
449
|
+
_instance;
|
|
450
|
+
_registeredViews = /* @__PURE__ */ new Set();
|
|
451
|
+
cache;
|
|
452
|
+
constructor(cache) {
|
|
453
|
+
this.cache = cache;
|
|
454
|
+
}
|
|
455
|
+
static async create(cache) {
|
|
456
|
+
const conn = new _Connection(cache);
|
|
457
|
+
conn._instance = await DuckDBInstance.create(":memory:");
|
|
458
|
+
conn._conn = await conn._instance.connect();
|
|
459
|
+
return conn;
|
|
460
|
+
}
|
|
461
|
+
async close() {
|
|
462
|
+
if (this._conn) {
|
|
463
|
+
this._conn.closeSync();
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
async _ensureView(viewName) {
|
|
467
|
+
if (this._registeredViews.has(viewName)) return;
|
|
468
|
+
const path = await this.cache.ensureParquet(viewName);
|
|
469
|
+
const pathStr = path.replace(/\\/g, "/");
|
|
470
|
+
if (viewName === "card_legalities") {
|
|
471
|
+
await this._registerLegalitiesView(pathStr);
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
const replaceClause = await this._buildCsvReplace(pathStr, viewName);
|
|
475
|
+
await this._run(
|
|
476
|
+
`CREATE OR REPLACE VIEW ${viewName} AS SELECT *${replaceClause} FROM read_parquet('${pathStr}')`
|
|
477
|
+
);
|
|
478
|
+
this._registeredViews.add(viewName);
|
|
479
|
+
}
|
|
480
|
+
async _buildCsvReplace(pathStr, viewName) {
|
|
481
|
+
const reader = await this._conn.runAndReadAll(
|
|
482
|
+
`SELECT column_name, column_type FROM (DESCRIBE SELECT * FROM read_parquet('${pathStr}'))`
|
|
483
|
+
);
|
|
484
|
+
const rows = reader.getRowObjects();
|
|
485
|
+
const schema = /* @__PURE__ */ new Map();
|
|
486
|
+
for (const row of rows) {
|
|
487
|
+
schema.set(row.column_name, row.column_type);
|
|
488
|
+
}
|
|
489
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
490
|
+
if (STATIC_LIST_COLUMNS[viewName]) {
|
|
491
|
+
for (const col of STATIC_LIST_COLUMNS[viewName]) {
|
|
492
|
+
candidates.add(col);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
for (const [col, dtype] of schema) {
|
|
496
|
+
if (dtype !== "VARCHAR") continue;
|
|
497
|
+
if (IGNORED_COLUMNS.has(col)) continue;
|
|
498
|
+
if (col.endsWith("s")) candidates.add(col);
|
|
499
|
+
}
|
|
500
|
+
const finalCols = [...candidates].filter((col) => schema.get(col) === "VARCHAR").sort();
|
|
501
|
+
const exprs = [];
|
|
502
|
+
for (const col of finalCols) {
|
|
503
|
+
exprs.push(
|
|
504
|
+
`CASE WHEN "${col}" IS NULL OR TRIM("${col}") = '' THEN []::VARCHAR[] ELSE string_split("${col}", ', ') END AS "${col}"`
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
const jsonCols = [...JSON_CAST_COLUMNS].sort();
|
|
508
|
+
for (const col of jsonCols) {
|
|
509
|
+
if (schema.get(col) === "VARCHAR") {
|
|
510
|
+
exprs.push(`TRY_CAST("${col}" AS JSON) AS "${col}"`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
if (exprs.length === 0) return "";
|
|
514
|
+
return ` REPLACE (${exprs.join(", ")})`;
|
|
515
|
+
}
|
|
516
|
+
async _registerLegalitiesView(pathStr) {
|
|
517
|
+
const reader = await this._conn.runAndReadAll(
|
|
518
|
+
`SELECT column_name FROM (DESCRIBE SELECT * FROM read_parquet('${pathStr}'))`
|
|
519
|
+
);
|
|
520
|
+
const allCols = reader.getRowObjects().map((r) => r.column_name);
|
|
521
|
+
const staticCols = /* @__PURE__ */ new Set(["uuid"]);
|
|
522
|
+
const formatCols = allCols.filter((c) => !staticCols.has(c));
|
|
523
|
+
if (formatCols.length === 0) {
|
|
524
|
+
await this._run(
|
|
525
|
+
`CREATE OR REPLACE VIEW card_legalities AS SELECT * FROM read_parquet('${pathStr}')`
|
|
526
|
+
);
|
|
527
|
+
} else {
|
|
528
|
+
const colsSql = formatCols.map((c) => `"${c}"`).join(", ");
|
|
529
|
+
await this._run(
|
|
530
|
+
`CREATE OR REPLACE VIEW card_legalities AS SELECT uuid, format, status FROM ( UNPIVOT (SELECT * FROM read_parquet('${pathStr}')) ON ${colsSql} INTO NAME format VALUE status) WHERE status IS NOT NULL`
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
this._registeredViews.add("card_legalities");
|
|
534
|
+
}
|
|
535
|
+
async registerTableFromData(tableName, data) {
|
|
536
|
+
if (data.length === 0) return;
|
|
537
|
+
await this._run(`DROP TABLE IF EXISTS ${tableName}`);
|
|
538
|
+
const tmpPath = join3(tmpdir(), `mtgjson_${tableName}_${Date.now()}.json`);
|
|
539
|
+
try {
|
|
540
|
+
writeFileSync(tmpPath, JSON.stringify(data), "utf-8");
|
|
541
|
+
const fwd = tmpPath.replace(/\\/g, "/");
|
|
542
|
+
await this._run(
|
|
543
|
+
`CREATE TABLE ${tableName} AS SELECT * FROM read_json_auto('${fwd}')`
|
|
544
|
+
);
|
|
545
|
+
} finally {
|
|
546
|
+
try {
|
|
547
|
+
unlinkSync(tmpPath);
|
|
548
|
+
} catch {
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
this._registeredViews.add(tableName);
|
|
552
|
+
}
|
|
553
|
+
async registerTableFromNdjson(tableName, ndjsonPath) {
|
|
554
|
+
await this._run(`DROP TABLE IF EXISTS ${tableName}`);
|
|
555
|
+
const fwd = ndjsonPath.replace(/\\/g, "/");
|
|
556
|
+
await this._run(
|
|
557
|
+
`CREATE TABLE ${tableName} AS SELECT * FROM read_json_auto('${fwd}', format='newline_delimited')`
|
|
558
|
+
);
|
|
559
|
+
this._registeredViews.add(tableName);
|
|
560
|
+
}
|
|
561
|
+
async ensureViews(...viewNames) {
|
|
562
|
+
for (const name of viewNames) {
|
|
563
|
+
await this._ensureView(name);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
async execute(sql, params) {
|
|
567
|
+
const reader = await this._runWithParams(sql, params);
|
|
568
|
+
const rows = reader.getRowObjects();
|
|
569
|
+
return rows.map((row) => coerceValues(row));
|
|
570
|
+
}
|
|
571
|
+
async executeJson(sql, params) {
|
|
572
|
+
const wrapped = `SELECT to_json(list(sub)) FROM (${sql}) sub`;
|
|
573
|
+
const reader = await this._runWithParams(wrapped, params);
|
|
574
|
+
const rows = reader.getRows();
|
|
575
|
+
if (!rows || rows.length === 0 || rows[0][0] == null) return "[]";
|
|
576
|
+
return String(rows[0][0]);
|
|
577
|
+
}
|
|
578
|
+
async executeScalar(sql, params) {
|
|
579
|
+
const reader = await this._runWithParams(sql, params);
|
|
580
|
+
const rows = reader.getRows();
|
|
581
|
+
if (!rows || rows.length === 0) return null;
|
|
582
|
+
const val = rows[0][0] ?? null;
|
|
583
|
+
if (typeof val === "bigint") return Number(val);
|
|
584
|
+
return val;
|
|
585
|
+
}
|
|
586
|
+
get raw() {
|
|
587
|
+
return this._conn;
|
|
588
|
+
}
|
|
589
|
+
async _run(sql) {
|
|
590
|
+
await this._conn.run(sql);
|
|
591
|
+
}
|
|
592
|
+
async _runWithParams(sql, params) {
|
|
593
|
+
if (params && params.length > 0) {
|
|
594
|
+
const stmt = await this._conn.prepare(sql);
|
|
595
|
+
for (let i = 0; i < params.length; i++) {
|
|
596
|
+
const val = params[i];
|
|
597
|
+
if (typeof val === "string") {
|
|
598
|
+
stmt.bindVarchar(i + 1, val);
|
|
599
|
+
} else if (typeof val === "number") {
|
|
600
|
+
if (Number.isInteger(val)) {
|
|
601
|
+
stmt.bindInteger(i + 1, val);
|
|
602
|
+
} else {
|
|
603
|
+
stmt.bindDouble(i + 1, val);
|
|
604
|
+
}
|
|
605
|
+
} else if (typeof val === "boolean") {
|
|
606
|
+
stmt.bindBoolean(i + 1, val);
|
|
607
|
+
} else if (val === null || val === void 0) {
|
|
608
|
+
stmt.bindNull(i + 1);
|
|
609
|
+
} else {
|
|
610
|
+
stmt.bindVarchar(i + 1, String(val));
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
return await stmt.runAndReadAll();
|
|
614
|
+
}
|
|
615
|
+
return await this._conn.runAndReadAll(sql);
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
function coerceValues(val) {
|
|
619
|
+
if (val instanceof Date) {
|
|
620
|
+
return val.toISOString();
|
|
621
|
+
}
|
|
622
|
+
if (typeof val === "bigint") {
|
|
623
|
+
return Number(val);
|
|
624
|
+
}
|
|
625
|
+
if (Array.isArray(val)) {
|
|
626
|
+
return val.map(coerceValues);
|
|
627
|
+
}
|
|
628
|
+
if (val !== null && typeof val === "object") {
|
|
629
|
+
const obj = val;
|
|
630
|
+
if ("items" in obj && Array.isArray(obj.items)) {
|
|
631
|
+
return obj.items.map(coerceValues);
|
|
632
|
+
}
|
|
633
|
+
if ("days" in obj && typeof obj.days === "number" && Object.keys(obj).length === 1) {
|
|
634
|
+
const ms = obj.days * 864e5;
|
|
635
|
+
return new Date(ms).toISOString().slice(0, 10);
|
|
636
|
+
}
|
|
637
|
+
if ("micros" in obj && typeof obj.micros === "bigint" && Object.keys(obj).length === 1) {
|
|
638
|
+
const ms = Number(obj.micros / 1000n);
|
|
639
|
+
return new Date(ms).toISOString();
|
|
640
|
+
}
|
|
641
|
+
const out = {};
|
|
642
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
643
|
+
out[k] = coerceValues(v);
|
|
644
|
+
}
|
|
645
|
+
return out;
|
|
646
|
+
}
|
|
647
|
+
return val;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// src/sql-builder.ts
|
|
651
|
+
var SQLBuilder = class {
|
|
652
|
+
_select = ["*"];
|
|
653
|
+
_distinct = false;
|
|
654
|
+
_from;
|
|
655
|
+
_joins = [];
|
|
656
|
+
/** WHERE clauses (public for direct manipulation by query modules). */
|
|
657
|
+
_where = [];
|
|
658
|
+
/** Bound parameter values (public for direct manipulation by query modules). */
|
|
659
|
+
_params = [];
|
|
660
|
+
_groupBy = [];
|
|
661
|
+
_having = [];
|
|
662
|
+
_orderBy = [];
|
|
663
|
+
_limit = null;
|
|
664
|
+
_offset = null;
|
|
665
|
+
constructor(baseTable) {
|
|
666
|
+
this._from = baseTable;
|
|
667
|
+
}
|
|
668
|
+
select(...columns) {
|
|
669
|
+
this._select = columns;
|
|
670
|
+
return this;
|
|
671
|
+
}
|
|
672
|
+
distinct() {
|
|
673
|
+
this._distinct = true;
|
|
674
|
+
return this;
|
|
675
|
+
}
|
|
676
|
+
join(clause) {
|
|
677
|
+
this._joins.push(clause);
|
|
678
|
+
return this;
|
|
679
|
+
}
|
|
680
|
+
where(condition, ...params) {
|
|
681
|
+
const offset = this._params.length;
|
|
682
|
+
let remapped = condition;
|
|
683
|
+
for (let i = params.length; i >= 1; i--) {
|
|
684
|
+
remapped = remapped.replaceAll(`$${i}`, `$${offset + i}`);
|
|
685
|
+
}
|
|
686
|
+
this._where.push(remapped);
|
|
687
|
+
this._params.push(...params);
|
|
688
|
+
return this;
|
|
689
|
+
}
|
|
690
|
+
whereLike(column, value) {
|
|
691
|
+
const idx = this._params.length + 1;
|
|
692
|
+
this._where.push(`LOWER(${column}) LIKE LOWER($${idx})`);
|
|
693
|
+
this._params.push(value);
|
|
694
|
+
return this;
|
|
695
|
+
}
|
|
696
|
+
whereIn(column, values) {
|
|
697
|
+
if (values.length === 0) {
|
|
698
|
+
this._where.push("FALSE");
|
|
699
|
+
return this;
|
|
700
|
+
}
|
|
701
|
+
const placeholders = [];
|
|
702
|
+
for (const v of values) {
|
|
703
|
+
const idx = this._params.length + 1;
|
|
704
|
+
placeholders.push(`$${idx}`);
|
|
705
|
+
this._params.push(v);
|
|
706
|
+
}
|
|
707
|
+
this._where.push(`${column} IN (${placeholders.join(", ")})`);
|
|
708
|
+
return this;
|
|
709
|
+
}
|
|
710
|
+
whereEq(column, value) {
|
|
711
|
+
const idx = this._params.length + 1;
|
|
712
|
+
this._where.push(`${column} = $${idx}`);
|
|
713
|
+
this._params.push(value);
|
|
714
|
+
return this;
|
|
715
|
+
}
|
|
716
|
+
whereGte(column, value) {
|
|
717
|
+
const idx = this._params.length + 1;
|
|
718
|
+
this._where.push(`${column} >= $${idx}`);
|
|
719
|
+
this._params.push(value);
|
|
720
|
+
return this;
|
|
721
|
+
}
|
|
722
|
+
whereLte(column, value) {
|
|
723
|
+
const idx = this._params.length + 1;
|
|
724
|
+
this._where.push(`${column} <= $${idx}`);
|
|
725
|
+
this._params.push(value);
|
|
726
|
+
return this;
|
|
727
|
+
}
|
|
728
|
+
whereRegex(column, pattern) {
|
|
729
|
+
const idx = this._params.length + 1;
|
|
730
|
+
this._where.push(`regexp_matches(${column}, $${idx})`);
|
|
731
|
+
this._params.push(pattern);
|
|
732
|
+
return this;
|
|
733
|
+
}
|
|
734
|
+
whereFuzzy(column, value, threshold = 0.8) {
|
|
735
|
+
if (typeof threshold !== "number" || threshold < 0 || threshold > 1) {
|
|
736
|
+
throw new Error(
|
|
737
|
+
`threshold must be a number between 0 and 1, got ${threshold}`
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
const idx = this._params.length + 1;
|
|
741
|
+
this._where.push(
|
|
742
|
+
`jaro_winkler_similarity(${column}, $${idx}) > ${threshold}`
|
|
743
|
+
);
|
|
744
|
+
this._params.push(value);
|
|
745
|
+
return this;
|
|
746
|
+
}
|
|
747
|
+
whereOr(...conditions) {
|
|
748
|
+
if (conditions.length === 0) return this;
|
|
749
|
+
const orParts = [];
|
|
750
|
+
for (const [cond, param] of conditions) {
|
|
751
|
+
const idx = this._params.length + 1;
|
|
752
|
+
const remapped = cond.replaceAll("$1", `$${idx}`);
|
|
753
|
+
orParts.push(remapped);
|
|
754
|
+
this._params.push(param);
|
|
755
|
+
}
|
|
756
|
+
this._where.push(`(${orParts.join(" OR ")})`);
|
|
757
|
+
return this;
|
|
758
|
+
}
|
|
759
|
+
groupBy(...columns) {
|
|
760
|
+
this._groupBy.push(...columns);
|
|
761
|
+
return this;
|
|
762
|
+
}
|
|
763
|
+
having(condition, ...params) {
|
|
764
|
+
const offset = this._params.length;
|
|
765
|
+
let remapped = condition;
|
|
766
|
+
for (let i = params.length; i >= 1; i--) {
|
|
767
|
+
remapped = remapped.replaceAll(`$${i}`, `$${offset + i}`);
|
|
768
|
+
}
|
|
769
|
+
this._having.push(remapped);
|
|
770
|
+
this._params.push(...params);
|
|
771
|
+
return this;
|
|
772
|
+
}
|
|
773
|
+
orderBy(...clauses) {
|
|
774
|
+
this._orderBy.push(...clauses);
|
|
775
|
+
return this;
|
|
776
|
+
}
|
|
777
|
+
limit(n) {
|
|
778
|
+
if (!Number.isInteger(n) || n < 0) {
|
|
779
|
+
throw new TypeError(`limit must be a non-negative integer, got ${n}`);
|
|
780
|
+
}
|
|
781
|
+
this._limit = n;
|
|
782
|
+
return this;
|
|
783
|
+
}
|
|
784
|
+
offset(n) {
|
|
785
|
+
if (!Number.isInteger(n) || n < 0) {
|
|
786
|
+
throw new TypeError(`offset must be a non-negative integer, got ${n}`);
|
|
787
|
+
}
|
|
788
|
+
this._offset = n;
|
|
789
|
+
return this;
|
|
790
|
+
}
|
|
791
|
+
build() {
|
|
792
|
+
const distinctStr = this._distinct ? "DISTINCT " : "";
|
|
793
|
+
const parts = [
|
|
794
|
+
`SELECT ${distinctStr}${this._select.join(", ")}`,
|
|
795
|
+
`FROM ${this._from}`
|
|
796
|
+
];
|
|
797
|
+
for (const j of this._joins) {
|
|
798
|
+
parts.push(j);
|
|
799
|
+
}
|
|
800
|
+
if (this._where.length > 0) {
|
|
801
|
+
parts.push(`WHERE ${this._where.join(" AND ")}`);
|
|
802
|
+
}
|
|
803
|
+
if (this._groupBy.length > 0) {
|
|
804
|
+
parts.push(`GROUP BY ${this._groupBy.join(", ")}`);
|
|
805
|
+
}
|
|
806
|
+
if (this._having.length > 0) {
|
|
807
|
+
parts.push(`HAVING ${this._having.join(" AND ")}`);
|
|
808
|
+
}
|
|
809
|
+
if (this._orderBy.length > 0) {
|
|
810
|
+
parts.push(`ORDER BY ${this._orderBy.join(", ")}`);
|
|
811
|
+
}
|
|
812
|
+
if (this._limit !== null) {
|
|
813
|
+
parts.push(`LIMIT ${this._limit}`);
|
|
814
|
+
}
|
|
815
|
+
if (this._offset !== null) {
|
|
816
|
+
parts.push(`OFFSET ${this._offset}`);
|
|
817
|
+
}
|
|
818
|
+
return [parts.join("\n"), this._params];
|
|
819
|
+
}
|
|
820
|
+
};
|
|
821
|
+
|
|
822
|
+
// src/queries/cards.ts
|
|
823
|
+
var CardQuery = class {
|
|
824
|
+
_conn;
|
|
825
|
+
constructor(conn) {
|
|
826
|
+
this._conn = conn;
|
|
827
|
+
}
|
|
828
|
+
async _ensure() {
|
|
829
|
+
await this._conn.ensureViews("cards");
|
|
830
|
+
}
|
|
831
|
+
async getByUuid(uuid) {
|
|
832
|
+
await this._ensure();
|
|
833
|
+
const rows = await this._conn.execute(
|
|
834
|
+
"SELECT * FROM cards WHERE uuid = $1",
|
|
835
|
+
[uuid]
|
|
836
|
+
);
|
|
837
|
+
return rows[0] ?? null;
|
|
838
|
+
}
|
|
839
|
+
async getByUuids(uuids) {
|
|
840
|
+
if (uuids.length === 0) return [];
|
|
841
|
+
await this._ensure();
|
|
842
|
+
const q = new SQLBuilder("cards").whereIn("uuid", uuids);
|
|
843
|
+
const [sql, params] = q.build();
|
|
844
|
+
return await this._conn.execute(sql, params);
|
|
845
|
+
}
|
|
846
|
+
async getByName(name, options) {
|
|
847
|
+
await this._ensure();
|
|
848
|
+
const q = new SQLBuilder("cards").whereEq("name", name);
|
|
849
|
+
if (options?.setCode) q.whereEq("setCode", options.setCode);
|
|
850
|
+
q.orderBy("setCode DESC", "number ASC");
|
|
851
|
+
const [sql, params] = q.build();
|
|
852
|
+
return await this._conn.execute(sql, params);
|
|
853
|
+
}
|
|
854
|
+
async search(options) {
|
|
855
|
+
await this._ensure();
|
|
856
|
+
const q = new SQLBuilder("cards");
|
|
857
|
+
const opts = options ?? {};
|
|
858
|
+
const limit = opts.limit ?? 100;
|
|
859
|
+
const offset = opts.offset ?? 0;
|
|
860
|
+
if (opts.name) {
|
|
861
|
+
if (opts.name.includes("%")) {
|
|
862
|
+
q.whereLike("name", opts.name);
|
|
863
|
+
} else {
|
|
864
|
+
q.whereEq("name", opts.name);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
if (opts.fuzzyName) {
|
|
868
|
+
q.whereFuzzy("cards.name", opts.fuzzyName, 0.8);
|
|
869
|
+
}
|
|
870
|
+
if (opts.setCode) q.whereEq("setCode", opts.setCode);
|
|
871
|
+
if (opts.rarity) q.whereEq("rarity", opts.rarity);
|
|
872
|
+
if (opts.manaValue !== void 0) q.whereEq("manaValue", opts.manaValue);
|
|
873
|
+
if (opts.manaValueLte !== void 0)
|
|
874
|
+
q.whereLte("manaValue", opts.manaValueLte);
|
|
875
|
+
if (opts.manaValueGte !== void 0)
|
|
876
|
+
q.whereGte("manaValue", opts.manaValueGte);
|
|
877
|
+
if (opts.text) q.whereLike("text", `%${opts.text}%`);
|
|
878
|
+
if (opts.textRegex) q.whereRegex("text", opts.textRegex);
|
|
879
|
+
if (opts.types) q.whereLike("type", `%${opts.types}%`);
|
|
880
|
+
if (opts.power) q.whereEq("power", opts.power);
|
|
881
|
+
if (opts.toughness) q.whereEq("toughness", opts.toughness);
|
|
882
|
+
if (opts.artist) q.whereLike("artist", `%${opts.artist}%`);
|
|
883
|
+
if (opts.language) q.whereEq("language", opts.language);
|
|
884
|
+
if (opts.layout) q.whereEq("layout", opts.layout);
|
|
885
|
+
if (opts.isPromo !== void 0) q.whereEq("isPromo", opts.isPromo);
|
|
886
|
+
if (opts.colors) {
|
|
887
|
+
for (const color of opts.colors) {
|
|
888
|
+
const idx = q._params.length + 1;
|
|
889
|
+
q._where.push(`list_contains(colors, $${idx})`);
|
|
890
|
+
q._params.push(color);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
if (opts.colorIdentity) {
|
|
894
|
+
for (const color of opts.colorIdentity) {
|
|
895
|
+
const idx = q._params.length + 1;
|
|
896
|
+
q._where.push(`list_contains(colorIdentity, $${idx})`);
|
|
897
|
+
q._params.push(color);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
if (opts.keyword) {
|
|
901
|
+
const idx = q._params.length + 1;
|
|
902
|
+
q._where.push(`list_contains(keywords, $${idx})`);
|
|
903
|
+
q._params.push(opts.keyword);
|
|
904
|
+
}
|
|
905
|
+
if (opts.availability) {
|
|
906
|
+
const idx = q._params.length + 1;
|
|
907
|
+
q._where.push(`list_contains(availability, $${idx})`);
|
|
908
|
+
q._params.push(opts.availability);
|
|
909
|
+
}
|
|
910
|
+
if (opts.localizedName) {
|
|
911
|
+
await this._conn.ensureViews("card_foreign_data");
|
|
912
|
+
q.select("cards.*");
|
|
913
|
+
q.join("JOIN card_foreign_data cfd ON cards.uuid = cfd.uuid");
|
|
914
|
+
if (opts.localizedName.includes("%")) {
|
|
915
|
+
q.whereLike("cfd.name", opts.localizedName);
|
|
916
|
+
} else {
|
|
917
|
+
q.whereEq("cfd.name", opts.localizedName);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
if (opts.legalIn) {
|
|
921
|
+
await this._conn.ensureViews("card_legalities");
|
|
922
|
+
q.join("JOIN card_legalities cl ON cards.uuid = cl.uuid");
|
|
923
|
+
q.whereEq("cl.format", opts.legalIn);
|
|
924
|
+
q.whereEq("cl.status", "Legal");
|
|
925
|
+
}
|
|
926
|
+
if (opts.setType) {
|
|
927
|
+
await this._conn.ensureViews("sets");
|
|
928
|
+
q.select("cards.*");
|
|
929
|
+
q.join("JOIN sets s ON cards.setCode = s.code");
|
|
930
|
+
q.whereEq("s.type", opts.setType);
|
|
931
|
+
}
|
|
932
|
+
if (opts.fuzzyName) {
|
|
933
|
+
const simIdx = q._params.length + 1;
|
|
934
|
+
q._params.push(opts.fuzzyName);
|
|
935
|
+
q.orderBy(
|
|
936
|
+
`jaro_winkler_similarity(cards.name, $${simIdx}) DESC`,
|
|
937
|
+
"cards.number ASC"
|
|
938
|
+
);
|
|
939
|
+
} else {
|
|
940
|
+
q.orderBy("cards.name ASC", "cards.number ASC");
|
|
941
|
+
}
|
|
942
|
+
q.limit(limit).offset(offset);
|
|
943
|
+
const [sql, params] = q.build();
|
|
944
|
+
return await this._conn.execute(sql, params);
|
|
945
|
+
}
|
|
946
|
+
async getPrintings(name) {
|
|
947
|
+
return this.getByName(name);
|
|
948
|
+
}
|
|
949
|
+
async getAtomic(name) {
|
|
950
|
+
await this._ensure();
|
|
951
|
+
const atomicCols = [
|
|
952
|
+
"name",
|
|
953
|
+
"asciiName",
|
|
954
|
+
"faceName",
|
|
955
|
+
"type",
|
|
956
|
+
"types",
|
|
957
|
+
"subtypes",
|
|
958
|
+
"supertypes",
|
|
959
|
+
"colors",
|
|
960
|
+
"colorIdentity",
|
|
961
|
+
"colorIndicator",
|
|
962
|
+
"producedMana",
|
|
963
|
+
"manaCost",
|
|
964
|
+
"text",
|
|
965
|
+
"layout",
|
|
966
|
+
"side",
|
|
967
|
+
"power",
|
|
968
|
+
"toughness",
|
|
969
|
+
"loyalty",
|
|
970
|
+
"keywords",
|
|
971
|
+
"isFunny",
|
|
972
|
+
"edhrecSaltiness",
|
|
973
|
+
"subsets",
|
|
974
|
+
"manaValue",
|
|
975
|
+
"faceConvertedManaCost",
|
|
976
|
+
"faceManaValue",
|
|
977
|
+
"defense",
|
|
978
|
+
"hand",
|
|
979
|
+
"life",
|
|
980
|
+
"edhrecRank",
|
|
981
|
+
"hasAlternativeDeckLimit",
|
|
982
|
+
"isReserved",
|
|
983
|
+
"isGameChanger",
|
|
984
|
+
"printings",
|
|
985
|
+
"leadershipSkills",
|
|
986
|
+
"relatedCards"
|
|
987
|
+
];
|
|
988
|
+
const q = new SQLBuilder("cards");
|
|
989
|
+
q.select(...atomicCols);
|
|
990
|
+
q.whereEq("name", name);
|
|
991
|
+
q.orderBy(
|
|
992
|
+
"isFunny ASC NULLS FIRST",
|
|
993
|
+
"isOnlineOnly ASC NULLS FIRST",
|
|
994
|
+
"side ASC NULLS FIRST"
|
|
995
|
+
);
|
|
996
|
+
const [sql, params] = q.build();
|
|
997
|
+
let rows = await this._conn.execute(sql, params);
|
|
998
|
+
if (rows.length === 0) {
|
|
999
|
+
const q2 = new SQLBuilder("cards");
|
|
1000
|
+
q2.select(...atomicCols);
|
|
1001
|
+
q2.where("CAST(faceName AS VARCHAR) = $1", name);
|
|
1002
|
+
q2.orderBy(
|
|
1003
|
+
"isFunny ASC NULLS FIRST",
|
|
1004
|
+
"isOnlineOnly ASC NULLS FIRST",
|
|
1005
|
+
"side ASC NULLS FIRST"
|
|
1006
|
+
);
|
|
1007
|
+
const [sql2, params2] = q2.build();
|
|
1008
|
+
rows = await this._conn.execute(sql2, params2);
|
|
1009
|
+
}
|
|
1010
|
+
if (rows.length === 0) return [];
|
|
1011
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1012
|
+
const unique = [];
|
|
1013
|
+
for (const r of rows) {
|
|
1014
|
+
const key = `${r.name ?? ""}|${r.faceName ?? ""}`;
|
|
1015
|
+
if (!seen.has(key)) {
|
|
1016
|
+
seen.add(key);
|
|
1017
|
+
unique.push(r);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
return unique;
|
|
1021
|
+
}
|
|
1022
|
+
async findByScryfallId(scryfallId) {
|
|
1023
|
+
await this._conn.ensureViews("cards", "card_identifiers");
|
|
1024
|
+
const sql = "SELECT c.* FROM cards c JOIN card_identifiers ci ON c.uuid = ci.uuid WHERE ci.scryfallId = $1";
|
|
1025
|
+
return await this._conn.execute(sql, [scryfallId]);
|
|
1026
|
+
}
|
|
1027
|
+
async random(count = 1) {
|
|
1028
|
+
await this._ensure();
|
|
1029
|
+
const sql = `SELECT * FROM cards USING SAMPLE ${count}`;
|
|
1030
|
+
return await this._conn.execute(sql);
|
|
1031
|
+
}
|
|
1032
|
+
async count(filters) {
|
|
1033
|
+
await this._ensure();
|
|
1034
|
+
if (!filters || Object.keys(filters).length === 0) {
|
|
1035
|
+
return await this._conn.executeScalar(
|
|
1036
|
+
"SELECT COUNT(*) FROM cards"
|
|
1037
|
+
) ?? 0;
|
|
1038
|
+
}
|
|
1039
|
+
const q = new SQLBuilder("cards").select("COUNT(*)");
|
|
1040
|
+
for (const [col, val] of Object.entries(filters)) {
|
|
1041
|
+
q.whereEq(col, val);
|
|
1042
|
+
}
|
|
1043
|
+
const [sql, params] = q.build();
|
|
1044
|
+
return await this._conn.executeScalar(sql, params) ?? 0;
|
|
1045
|
+
}
|
|
1046
|
+
};
|
|
1047
|
+
|
|
1048
|
+
// src/queries/sets.ts
|
|
1049
|
+
var SetQuery = class {
|
|
1050
|
+
_conn;
|
|
1051
|
+
constructor(conn) {
|
|
1052
|
+
this._conn = conn;
|
|
1053
|
+
}
|
|
1054
|
+
async _ensure() {
|
|
1055
|
+
await this._conn.ensureViews("sets");
|
|
1056
|
+
}
|
|
1057
|
+
async get(code) {
|
|
1058
|
+
await this._ensure();
|
|
1059
|
+
const rows = await this._conn.execute(
|
|
1060
|
+
"SELECT * FROM sets WHERE code = $1",
|
|
1061
|
+
[code.toUpperCase()]
|
|
1062
|
+
);
|
|
1063
|
+
return rows[0] ?? null;
|
|
1064
|
+
}
|
|
1065
|
+
async list(options) {
|
|
1066
|
+
await this._ensure();
|
|
1067
|
+
const q = new SQLBuilder("sets");
|
|
1068
|
+
if (options?.setType) q.whereEq("type", options.setType);
|
|
1069
|
+
if (options?.name) {
|
|
1070
|
+
if (options.name.includes("%")) {
|
|
1071
|
+
q.whereLike("name", options.name);
|
|
1072
|
+
} else {
|
|
1073
|
+
q.whereEq("name", options.name);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
q.orderBy("releaseDate DESC");
|
|
1077
|
+
q.limit(options?.limit ?? 1e3).offset(options?.offset ?? 0);
|
|
1078
|
+
const [sql, params] = q.build();
|
|
1079
|
+
return await this._conn.execute(sql, params);
|
|
1080
|
+
}
|
|
1081
|
+
async search(options) {
|
|
1082
|
+
await this._ensure();
|
|
1083
|
+
const q = new SQLBuilder("sets");
|
|
1084
|
+
if (options?.name) q.whereLike("name", `%${options.name}%`);
|
|
1085
|
+
if (options?.setType) q.whereEq("type", options.setType);
|
|
1086
|
+
if (options?.block) q.whereLike("block", `%${options.block}%`);
|
|
1087
|
+
if (options?.releaseYear) {
|
|
1088
|
+
const idx = q._params.length + 1;
|
|
1089
|
+
q._where.push(`EXTRACT(YEAR FROM CAST(releaseDate AS DATE)) = $${idx}`);
|
|
1090
|
+
q._params.push(options.releaseYear);
|
|
1091
|
+
}
|
|
1092
|
+
q.orderBy("releaseDate DESC");
|
|
1093
|
+
q.limit(options?.limit ?? 100);
|
|
1094
|
+
const [sql, params] = q.build();
|
|
1095
|
+
return await this._conn.execute(sql, params);
|
|
1096
|
+
}
|
|
1097
|
+
async getFinancialSummary(setCode, options) {
|
|
1098
|
+
await this._conn.ensureViews("cards", "all_prices_today");
|
|
1099
|
+
const provider = options?.provider ?? "tcgplayer";
|
|
1100
|
+
const currency = options?.currency ?? "USD";
|
|
1101
|
+
const finish = options?.finish ?? "normal";
|
|
1102
|
+
const priceType = options?.priceType ?? "retail";
|
|
1103
|
+
const sql = `
|
|
1104
|
+
SELECT
|
|
1105
|
+
COUNT(DISTINCT c.uuid) AS card_count,
|
|
1106
|
+
ROUND(SUM(p.price), 2) AS total_value,
|
|
1107
|
+
ROUND(AVG(p.price), 2) AS avg_value,
|
|
1108
|
+
MIN(p.price) AS min_value,
|
|
1109
|
+
MAX(p.price) AS max_price,
|
|
1110
|
+
MAX(p.date) AS date
|
|
1111
|
+
FROM cards c
|
|
1112
|
+
JOIN all_prices_today p ON c.uuid = p.uuid
|
|
1113
|
+
WHERE c.setCode = $1
|
|
1114
|
+
AND p.provider = $2
|
|
1115
|
+
AND p.currency = $3
|
|
1116
|
+
AND p.finish = $4
|
|
1117
|
+
AND p.price_type = $5
|
|
1118
|
+
AND p.date = (SELECT MAX(p2.date) FROM all_prices_today p2)
|
|
1119
|
+
`;
|
|
1120
|
+
const rows = await this._conn.execute(sql, [
|
|
1121
|
+
setCode.toUpperCase(),
|
|
1122
|
+
provider,
|
|
1123
|
+
currency,
|
|
1124
|
+
finish,
|
|
1125
|
+
priceType
|
|
1126
|
+
]);
|
|
1127
|
+
if (!rows.length || rows[0].card_count === 0) return null;
|
|
1128
|
+
return rows[0];
|
|
1129
|
+
}
|
|
1130
|
+
async count() {
|
|
1131
|
+
await this._ensure();
|
|
1132
|
+
return await this._conn.executeScalar(
|
|
1133
|
+
"SELECT COUNT(*) FROM sets"
|
|
1134
|
+
) ?? 0;
|
|
1135
|
+
}
|
|
1136
|
+
};
|
|
1137
|
+
|
|
1138
|
+
// src/queries/tokens.ts
|
|
1139
|
+
var TokenQuery = class {
|
|
1140
|
+
_conn;
|
|
1141
|
+
constructor(conn) {
|
|
1142
|
+
this._conn = conn;
|
|
1143
|
+
}
|
|
1144
|
+
async _ensure() {
|
|
1145
|
+
await this._conn.ensureViews("tokens");
|
|
1146
|
+
}
|
|
1147
|
+
async getByUuid(uuid) {
|
|
1148
|
+
await this._ensure();
|
|
1149
|
+
const rows = await this._conn.execute(
|
|
1150
|
+
"SELECT * FROM tokens WHERE uuid = $1",
|
|
1151
|
+
[uuid]
|
|
1152
|
+
);
|
|
1153
|
+
return rows[0] ?? null;
|
|
1154
|
+
}
|
|
1155
|
+
async getByUuids(uuids) {
|
|
1156
|
+
if (uuids.length === 0) return [];
|
|
1157
|
+
await this._ensure();
|
|
1158
|
+
const q = new SQLBuilder("tokens").whereIn("uuid", uuids);
|
|
1159
|
+
const [sql, params] = q.build();
|
|
1160
|
+
return await this._conn.execute(sql, params);
|
|
1161
|
+
}
|
|
1162
|
+
async getByName(name, options) {
|
|
1163
|
+
await this._ensure();
|
|
1164
|
+
const q = new SQLBuilder("tokens").whereEq("name", name);
|
|
1165
|
+
if (options?.setCode) q.whereEq("setCode", options.setCode);
|
|
1166
|
+
q.orderBy("setCode DESC", "number ASC");
|
|
1167
|
+
const [sql, params] = q.build();
|
|
1168
|
+
return await this._conn.execute(sql, params);
|
|
1169
|
+
}
|
|
1170
|
+
async search(options) {
|
|
1171
|
+
await this._ensure();
|
|
1172
|
+
const q = new SQLBuilder("tokens");
|
|
1173
|
+
if (options?.name) {
|
|
1174
|
+
if (options.name.includes("%")) {
|
|
1175
|
+
q.whereLike("name", options.name);
|
|
1176
|
+
} else {
|
|
1177
|
+
q.whereEq("name", options.name);
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
if (options?.setCode) q.whereEq("setCode", options.setCode);
|
|
1181
|
+
if (options?.types) q.whereLike("type", `%${options.types}%`);
|
|
1182
|
+
if (options?.artist) q.whereLike("artist", `%${options.artist}%`);
|
|
1183
|
+
if (options?.colors) {
|
|
1184
|
+
for (const color of options.colors) {
|
|
1185
|
+
const idx = q._params.length + 1;
|
|
1186
|
+
q._where.push(`list_contains(colors, $${idx})`);
|
|
1187
|
+
q._params.push(color);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
q.orderBy("name ASC", "number ASC");
|
|
1191
|
+
q.limit(options?.limit ?? 100).offset(options?.offset ?? 0);
|
|
1192
|
+
const [sql, params] = q.build();
|
|
1193
|
+
return await this._conn.execute(sql, params);
|
|
1194
|
+
}
|
|
1195
|
+
async forSet(setCode) {
|
|
1196
|
+
return this.search({ setCode, limit: 1e3 });
|
|
1197
|
+
}
|
|
1198
|
+
async count(filters) {
|
|
1199
|
+
await this._ensure();
|
|
1200
|
+
if (!filters || Object.keys(filters).length === 0) {
|
|
1201
|
+
return await this._conn.executeScalar(
|
|
1202
|
+
"SELECT COUNT(*) FROM tokens"
|
|
1203
|
+
) ?? 0;
|
|
1204
|
+
}
|
|
1205
|
+
const q = new SQLBuilder("tokens").select("COUNT(*)");
|
|
1206
|
+
for (const [col, val] of Object.entries(filters)) {
|
|
1207
|
+
q.whereEq(col, val);
|
|
1208
|
+
}
|
|
1209
|
+
const [sql, params] = q.build();
|
|
1210
|
+
return await this._conn.executeScalar(sql, params) ?? 0;
|
|
1211
|
+
}
|
|
1212
|
+
};
|
|
1213
|
+
|
|
1214
|
+
// src/queries/prices.ts
|
|
1215
|
+
var PriceQuery = class {
|
|
1216
|
+
_conn;
|
|
1217
|
+
constructor(conn) {
|
|
1218
|
+
this._conn = conn;
|
|
1219
|
+
}
|
|
1220
|
+
async _ensure() {
|
|
1221
|
+
await this._conn.ensureViews("all_prices_today");
|
|
1222
|
+
}
|
|
1223
|
+
async get(uuid) {
|
|
1224
|
+
await this._ensure();
|
|
1225
|
+
const rows = await this._conn.execute(
|
|
1226
|
+
"SELECT * FROM all_prices_today WHERE uuid = $1 ORDER BY source, provider, price_type, finish, date",
|
|
1227
|
+
[uuid]
|
|
1228
|
+
);
|
|
1229
|
+
if (rows.length === 0) return null;
|
|
1230
|
+
const result = {};
|
|
1231
|
+
for (const r of rows) {
|
|
1232
|
+
const srcKey = r.source;
|
|
1233
|
+
if (!result[srcKey]) result[srcKey] = {};
|
|
1234
|
+
const src = result[srcKey];
|
|
1235
|
+
const provKey = r.provider;
|
|
1236
|
+
if (!src[provKey]) src[provKey] = { currency: r.currency ?? "USD" };
|
|
1237
|
+
const prov = src[provKey];
|
|
1238
|
+
const catKey = r.price_type;
|
|
1239
|
+
if (!prov[catKey]) prov[catKey] = {};
|
|
1240
|
+
const cat = prov[catKey];
|
|
1241
|
+
const finKey = r.finish;
|
|
1242
|
+
if (!cat[finKey]) cat[finKey] = {};
|
|
1243
|
+
const fin = cat[finKey];
|
|
1244
|
+
fin[r.date] = r.price;
|
|
1245
|
+
}
|
|
1246
|
+
return result;
|
|
1247
|
+
}
|
|
1248
|
+
async today(uuid, options) {
|
|
1249
|
+
await this._ensure();
|
|
1250
|
+
const parts = [
|
|
1251
|
+
"SELECT * FROM all_prices_today",
|
|
1252
|
+
"WHERE uuid = $1",
|
|
1253
|
+
"AND date = (SELECT MAX(p2.date) FROM all_prices_today p2 WHERE p2.uuid = $1)"
|
|
1254
|
+
];
|
|
1255
|
+
const params = [uuid];
|
|
1256
|
+
let idx = 2;
|
|
1257
|
+
if (options?.provider) {
|
|
1258
|
+
parts.push(`AND provider = $${idx}`);
|
|
1259
|
+
params.push(options.provider);
|
|
1260
|
+
idx++;
|
|
1261
|
+
}
|
|
1262
|
+
if (options?.finish) {
|
|
1263
|
+
parts.push(`AND finish = $${idx}`);
|
|
1264
|
+
params.push(options.finish);
|
|
1265
|
+
idx++;
|
|
1266
|
+
}
|
|
1267
|
+
if (options?.priceType) {
|
|
1268
|
+
parts.push(`AND price_type = $${idx}`);
|
|
1269
|
+
params.push(options.priceType);
|
|
1270
|
+
idx++;
|
|
1271
|
+
}
|
|
1272
|
+
return this._conn.execute(parts.join(" "), params);
|
|
1273
|
+
}
|
|
1274
|
+
async history(uuid, options) {
|
|
1275
|
+
await this._conn.ensureViews("all_prices");
|
|
1276
|
+
const parts = ["SELECT * FROM all_prices WHERE uuid = $1"];
|
|
1277
|
+
const params = [uuid];
|
|
1278
|
+
let idx = 2;
|
|
1279
|
+
if (options?.provider) {
|
|
1280
|
+
parts.push(`AND provider = $${idx}`);
|
|
1281
|
+
params.push(options.provider);
|
|
1282
|
+
idx++;
|
|
1283
|
+
}
|
|
1284
|
+
if (options?.finish) {
|
|
1285
|
+
parts.push(`AND finish = $${idx}`);
|
|
1286
|
+
params.push(options.finish);
|
|
1287
|
+
idx++;
|
|
1288
|
+
}
|
|
1289
|
+
if (options?.priceType) {
|
|
1290
|
+
parts.push(`AND price_type = $${idx}`);
|
|
1291
|
+
params.push(options.priceType);
|
|
1292
|
+
idx++;
|
|
1293
|
+
}
|
|
1294
|
+
if (options?.dateFrom) {
|
|
1295
|
+
parts.push(`AND date >= $${idx}`);
|
|
1296
|
+
params.push(options.dateFrom);
|
|
1297
|
+
idx++;
|
|
1298
|
+
}
|
|
1299
|
+
if (options?.dateTo) {
|
|
1300
|
+
parts.push(`AND date <= $${idx}`);
|
|
1301
|
+
params.push(options.dateTo);
|
|
1302
|
+
idx++;
|
|
1303
|
+
}
|
|
1304
|
+
parts.push("ORDER BY date ASC");
|
|
1305
|
+
return this._conn.execute(parts.join(" "), params);
|
|
1306
|
+
}
|
|
1307
|
+
async priceTrend(uuid, options) {
|
|
1308
|
+
await this._conn.ensureViews("all_prices");
|
|
1309
|
+
const priceType = options?.priceType ?? "retail";
|
|
1310
|
+
const parts = [
|
|
1311
|
+
"SELECT",
|
|
1312
|
+
" MIN(price) AS min_price,",
|
|
1313
|
+
" MAX(price) AS max_price,",
|
|
1314
|
+
" ROUND(AVG(price), 2) AS avg_price,",
|
|
1315
|
+
" MIN(date) AS first_date,",
|
|
1316
|
+
" MAX(date) AS last_date,",
|
|
1317
|
+
" COUNT(*) AS data_points",
|
|
1318
|
+
"FROM all_prices",
|
|
1319
|
+
"WHERE uuid = $1 AND price_type = $2"
|
|
1320
|
+
];
|
|
1321
|
+
const params = [uuid, priceType];
|
|
1322
|
+
let idx = 3;
|
|
1323
|
+
if (options?.provider) {
|
|
1324
|
+
parts.push(`AND provider = $${idx}`);
|
|
1325
|
+
params.push(options.provider);
|
|
1326
|
+
idx++;
|
|
1327
|
+
}
|
|
1328
|
+
if (options?.finish) {
|
|
1329
|
+
parts.push(`AND finish = $${idx}`);
|
|
1330
|
+
params.push(options.finish);
|
|
1331
|
+
idx++;
|
|
1332
|
+
}
|
|
1333
|
+
const rows = await this._conn.execute(parts.join(" "), params);
|
|
1334
|
+
if (!rows.length || rows[0].data_points === 0) return null;
|
|
1335
|
+
return rows[0];
|
|
1336
|
+
}
|
|
1337
|
+
async cheapestPrinting(name, options) {
|
|
1338
|
+
await this._ensure();
|
|
1339
|
+
await this._conn.ensureViews("cards");
|
|
1340
|
+
const provider = options?.provider ?? "tcgplayer";
|
|
1341
|
+
const finish = options?.finish ?? "normal";
|
|
1342
|
+
const priceType = options?.priceType ?? "retail";
|
|
1343
|
+
const sql = "SELECT c.uuid, c.setCode, c.number, p.price, p.date FROM cards c JOIN all_prices_today p ON c.uuid = p.uuid WHERE c.name = $1 AND p.provider = $2 AND p.finish = $3 AND p.price_type = $4 AND p.date = (SELECT MAX(p2.date) FROM all_prices_today p2 WHERE p2.uuid = c.uuid AND p2.provider = $2 AND p2.finish = $3 AND p2.price_type = $4) ORDER BY p.price ASC LIMIT 1";
|
|
1344
|
+
const rows = await this._conn.execute(sql, [
|
|
1345
|
+
name,
|
|
1346
|
+
provider,
|
|
1347
|
+
finish,
|
|
1348
|
+
priceType
|
|
1349
|
+
]);
|
|
1350
|
+
return rows[0] ?? null;
|
|
1351
|
+
}
|
|
1352
|
+
async cheapestPrintings(options) {
|
|
1353
|
+
await this._ensure();
|
|
1354
|
+
await this._conn.ensureViews("cards");
|
|
1355
|
+
const provider = options?.provider ?? "tcgplayer";
|
|
1356
|
+
const finish = options?.finish ?? "normal";
|
|
1357
|
+
const priceType = options?.priceType ?? "retail";
|
|
1358
|
+
const limit = options?.limit ?? 100;
|
|
1359
|
+
const offset = options?.offset ?? 0;
|
|
1360
|
+
const sql = `SELECT c.name, arg_min(c.setCode, p.price) AS cheapest_set, arg_min(c.number, p.price) AS cheapest_number, arg_min(c.uuid, p.price) AS cheapest_uuid, MIN(p.price) AS min_price FROM cards c JOIN all_prices_today p ON c.uuid = p.uuid WHERE p.provider = $1 AND p.finish = $2 AND p.price_type = $3 AND p.date = (SELECT MAX(date) FROM all_prices_today) GROUP BY c.name ORDER BY min_price ASC LIMIT ${limit} OFFSET ${offset}`;
|
|
1361
|
+
return this._conn.execute(sql, [provider, finish, priceType]);
|
|
1362
|
+
}
|
|
1363
|
+
async mostExpensivePrintings(options) {
|
|
1364
|
+
await this._ensure();
|
|
1365
|
+
await this._conn.ensureViews("cards");
|
|
1366
|
+
const provider = options?.provider ?? "tcgplayer";
|
|
1367
|
+
const finish = options?.finish ?? "normal";
|
|
1368
|
+
const priceType = options?.priceType ?? "retail";
|
|
1369
|
+
const limit = options?.limit ?? 100;
|
|
1370
|
+
const offset = options?.offset ?? 0;
|
|
1371
|
+
const sql = `SELECT c.name, arg_max(c.setCode, p.price) AS priciest_set, arg_max(c.number, p.price) AS priciest_number, arg_max(c.uuid, p.price) AS priciest_uuid, MAX(p.price) AS max_price FROM cards c JOIN all_prices_today p ON c.uuid = p.uuid WHERE p.provider = $1 AND p.finish = $2 AND p.price_type = $3 AND p.date = (SELECT MAX(date) FROM all_prices_today) GROUP BY c.name ORDER BY max_price DESC LIMIT ${limit} OFFSET ${offset}`;
|
|
1372
|
+
return this._conn.execute(sql, [provider, finish, priceType]);
|
|
1373
|
+
}
|
|
1374
|
+
};
|
|
1375
|
+
|
|
1376
|
+
// src/queries/legalities.ts
|
|
1377
|
+
var LegalityQuery = class {
|
|
1378
|
+
_conn;
|
|
1379
|
+
constructor(conn) {
|
|
1380
|
+
this._conn = conn;
|
|
1381
|
+
}
|
|
1382
|
+
async _ensure() {
|
|
1383
|
+
await this._conn.ensureViews("card_legalities");
|
|
1384
|
+
}
|
|
1385
|
+
async _cardsByStatus(formatName, status, options) {
|
|
1386
|
+
await this._ensure();
|
|
1387
|
+
await this._conn.ensureViews("cards");
|
|
1388
|
+
const limit = options?.limit ?? 100;
|
|
1389
|
+
const offset = options?.offset ?? 0;
|
|
1390
|
+
return this._conn.execute(
|
|
1391
|
+
`SELECT c.name, c.uuid FROM cards c JOIN card_legalities cl ON c.uuid = cl.uuid WHERE cl.format = $1 AND cl.status = $2 ORDER BY c.name ASC LIMIT ${limit} OFFSET ${offset}`,
|
|
1392
|
+
[formatName, status]
|
|
1393
|
+
);
|
|
1394
|
+
}
|
|
1395
|
+
async formatsForCard(uuid) {
|
|
1396
|
+
await this._ensure();
|
|
1397
|
+
const rows = await this._conn.execute(
|
|
1398
|
+
"SELECT format, status FROM card_legalities WHERE uuid = $1",
|
|
1399
|
+
[uuid]
|
|
1400
|
+
);
|
|
1401
|
+
const result = {};
|
|
1402
|
+
for (const r of rows) {
|
|
1403
|
+
result[r.format] = r.status;
|
|
1404
|
+
}
|
|
1405
|
+
return result;
|
|
1406
|
+
}
|
|
1407
|
+
async legalIn(formatName, options) {
|
|
1408
|
+
await this._conn.ensureViews("cards", "card_legalities");
|
|
1409
|
+
const limit = options?.limit ?? 100;
|
|
1410
|
+
const offset = options?.offset ?? 0;
|
|
1411
|
+
const sql = `SELECT DISTINCT c.* FROM cards c JOIN card_legalities cl ON c.uuid = cl.uuid WHERE cl.format = $1 AND cl.status = 'Legal' ORDER BY c.name ASC LIMIT ${limit} OFFSET ${offset}`;
|
|
1412
|
+
return await this._conn.execute(sql, [formatName]);
|
|
1413
|
+
}
|
|
1414
|
+
async isLegal(uuid, formatName) {
|
|
1415
|
+
await this._ensure();
|
|
1416
|
+
const result = await this._conn.executeScalar(
|
|
1417
|
+
"SELECT COUNT(*) FROM card_legalities WHERE uuid = $1 AND format = $2 AND status = 'Legal'",
|
|
1418
|
+
[uuid, formatName]
|
|
1419
|
+
);
|
|
1420
|
+
return (result ?? 0) > 0;
|
|
1421
|
+
}
|
|
1422
|
+
async bannedIn(formatName, options) {
|
|
1423
|
+
return this._cardsByStatus(formatName, "Banned", options);
|
|
1424
|
+
}
|
|
1425
|
+
async restrictedIn(formatName, options) {
|
|
1426
|
+
return this._cardsByStatus(formatName, "Restricted", options);
|
|
1427
|
+
}
|
|
1428
|
+
async suspendedIn(formatName, options) {
|
|
1429
|
+
return this._cardsByStatus(formatName, "Suspended", options);
|
|
1430
|
+
}
|
|
1431
|
+
async notLegalIn(formatName, options) {
|
|
1432
|
+
return this._cardsByStatus(formatName, "Not Legal", options);
|
|
1433
|
+
}
|
|
1434
|
+
};
|
|
1435
|
+
|
|
1436
|
+
// src/queries/identifiers.ts
|
|
1437
|
+
var KNOWN_ID_COLUMNS = /* @__PURE__ */ new Set([
|
|
1438
|
+
"cardKingdomEtchedId",
|
|
1439
|
+
"cardKingdomFoilId",
|
|
1440
|
+
"cardKingdomId",
|
|
1441
|
+
"cardsphereId",
|
|
1442
|
+
"cardsphereFoilId",
|
|
1443
|
+
"mcmId",
|
|
1444
|
+
"mcmMetaId",
|
|
1445
|
+
"mtgArenaId",
|
|
1446
|
+
"mtgoFoilId",
|
|
1447
|
+
"mtgoId",
|
|
1448
|
+
"multiverseId",
|
|
1449
|
+
"scryfallId",
|
|
1450
|
+
"scryfallIllustrationId",
|
|
1451
|
+
"scryfallOracleId",
|
|
1452
|
+
"tcgplayerEtchedProductId",
|
|
1453
|
+
"tcgplayerProductId"
|
|
1454
|
+
]);
|
|
1455
|
+
var IdentifierQuery = class {
|
|
1456
|
+
_conn;
|
|
1457
|
+
constructor(conn) {
|
|
1458
|
+
this._conn = conn;
|
|
1459
|
+
}
|
|
1460
|
+
async _ensure() {
|
|
1461
|
+
await this._conn.ensureViews("cards", "card_identifiers");
|
|
1462
|
+
}
|
|
1463
|
+
async _findBy(idColumn, value) {
|
|
1464
|
+
await this._ensure();
|
|
1465
|
+
const sql = `SELECT c.* FROM cards c JOIN card_identifiers ci ON c.uuid = ci.uuid WHERE ci.${idColumn} = $1`;
|
|
1466
|
+
return await this._conn.execute(sql, [value]);
|
|
1467
|
+
}
|
|
1468
|
+
async findBy(idType, value) {
|
|
1469
|
+
if (!KNOWN_ID_COLUMNS.has(idType)) {
|
|
1470
|
+
throw new Error(
|
|
1471
|
+
`Unknown identifier type '${idType}'. Known types: ${[...KNOWN_ID_COLUMNS].sort().join(", ")}`
|
|
1472
|
+
);
|
|
1473
|
+
}
|
|
1474
|
+
return this._findBy(idType, value);
|
|
1475
|
+
}
|
|
1476
|
+
async findByScryfallId(scryfallId) {
|
|
1477
|
+
return this._findBy("scryfallId", scryfallId);
|
|
1478
|
+
}
|
|
1479
|
+
async findByScryfallOracleId(oracleId) {
|
|
1480
|
+
return this._findBy("scryfallOracleId", oracleId);
|
|
1481
|
+
}
|
|
1482
|
+
async findByScryfallIllustrationId(illustrationId) {
|
|
1483
|
+
return this._findBy("scryfallIllustrationId", illustrationId);
|
|
1484
|
+
}
|
|
1485
|
+
async findByTcgplayerId(tcgplayerId) {
|
|
1486
|
+
return this._findBy("tcgplayerProductId", tcgplayerId);
|
|
1487
|
+
}
|
|
1488
|
+
async findByTcgplayerEtchedId(tcgplayerEtchedId) {
|
|
1489
|
+
return this._findBy("tcgplayerEtchedProductId", tcgplayerEtchedId);
|
|
1490
|
+
}
|
|
1491
|
+
async findByMtgoId(mtgoId) {
|
|
1492
|
+
return this._findBy("mtgoId", mtgoId);
|
|
1493
|
+
}
|
|
1494
|
+
async findByMtgoFoilId(mtgoFoilId) {
|
|
1495
|
+
return this._findBy("mtgoFoilId", mtgoFoilId);
|
|
1496
|
+
}
|
|
1497
|
+
async findByMtgArenaId(arenaId) {
|
|
1498
|
+
return this._findBy("mtgArenaId", arenaId);
|
|
1499
|
+
}
|
|
1500
|
+
async findByMultiverseId(multiverseId) {
|
|
1501
|
+
return this._findBy("multiverseId", multiverseId);
|
|
1502
|
+
}
|
|
1503
|
+
async findByMcmId(mcmId) {
|
|
1504
|
+
return this._findBy("mcmId", mcmId);
|
|
1505
|
+
}
|
|
1506
|
+
async findByMcmMetaId(mcmMetaId) {
|
|
1507
|
+
return this._findBy("mcmMetaId", mcmMetaId);
|
|
1508
|
+
}
|
|
1509
|
+
async findByCardKingdomId(ckId) {
|
|
1510
|
+
return this._findBy("cardKingdomId", ckId);
|
|
1511
|
+
}
|
|
1512
|
+
async findByCardKingdomFoilId(ckFoilId) {
|
|
1513
|
+
return this._findBy("cardKingdomFoilId", ckFoilId);
|
|
1514
|
+
}
|
|
1515
|
+
async findByCardKingdomEtchedId(ckEtchedId) {
|
|
1516
|
+
return this._findBy("cardKingdomEtchedId", ckEtchedId);
|
|
1517
|
+
}
|
|
1518
|
+
async findByCardsphereId(csId) {
|
|
1519
|
+
return this._findBy("cardsphereId", csId);
|
|
1520
|
+
}
|
|
1521
|
+
async findByCardsphereFoilId(csFoilId) {
|
|
1522
|
+
return this._findBy("cardsphereFoilId", csFoilId);
|
|
1523
|
+
}
|
|
1524
|
+
async getIdentifiers(uuid) {
|
|
1525
|
+
await this._conn.ensureViews("card_identifiers");
|
|
1526
|
+
const rows = await this._conn.execute(
|
|
1527
|
+
"SELECT * FROM card_identifiers WHERE uuid = $1",
|
|
1528
|
+
[uuid]
|
|
1529
|
+
);
|
|
1530
|
+
return rows[0] ?? null;
|
|
1531
|
+
}
|
|
1532
|
+
};
|
|
1533
|
+
|
|
1534
|
+
// src/queries/decks.ts
|
|
1535
|
+
var DeckQuery = class {
|
|
1536
|
+
_cache;
|
|
1537
|
+
_data = null;
|
|
1538
|
+
constructor(cache) {
|
|
1539
|
+
this._cache = cache;
|
|
1540
|
+
}
|
|
1541
|
+
async _ensure() {
|
|
1542
|
+
if (this._data !== null) return;
|
|
1543
|
+
try {
|
|
1544
|
+
const raw = await this._cache.loadJson("deck_list");
|
|
1545
|
+
this._data = raw.data ?? [];
|
|
1546
|
+
} catch {
|
|
1547
|
+
this._data = [];
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
async list(options) {
|
|
1551
|
+
await this._ensure();
|
|
1552
|
+
let results = this._data;
|
|
1553
|
+
if (options?.setCode) {
|
|
1554
|
+
const codeUpper = options.setCode.toUpperCase();
|
|
1555
|
+
results = results.filter(
|
|
1556
|
+
(d) => (d.code ?? "").toUpperCase() === codeUpper
|
|
1557
|
+
);
|
|
1558
|
+
}
|
|
1559
|
+
if (options?.deckType) {
|
|
1560
|
+
results = results.filter((d) => d.type === options.deckType);
|
|
1561
|
+
}
|
|
1562
|
+
return results;
|
|
1563
|
+
}
|
|
1564
|
+
async search(options) {
|
|
1565
|
+
await this._ensure();
|
|
1566
|
+
let results = this._data;
|
|
1567
|
+
if (options?.name) {
|
|
1568
|
+
const nameLower = options.name.toLowerCase();
|
|
1569
|
+
results = results.filter(
|
|
1570
|
+
(d) => (d.name ?? "").toLowerCase().includes(nameLower)
|
|
1571
|
+
);
|
|
1572
|
+
}
|
|
1573
|
+
if (options?.setCode) {
|
|
1574
|
+
const codeUpper = options.setCode.toUpperCase();
|
|
1575
|
+
results = results.filter(
|
|
1576
|
+
(d) => (d.code ?? "").toUpperCase() === codeUpper
|
|
1577
|
+
);
|
|
1578
|
+
}
|
|
1579
|
+
return results;
|
|
1580
|
+
}
|
|
1581
|
+
async count() {
|
|
1582
|
+
await this._ensure();
|
|
1583
|
+
return this._data.length;
|
|
1584
|
+
}
|
|
1585
|
+
};
|
|
1586
|
+
|
|
1587
|
+
// src/queries/sealed.ts
|
|
1588
|
+
var SealedQuery = class {
|
|
1589
|
+
_conn;
|
|
1590
|
+
constructor(conn) {
|
|
1591
|
+
this._conn = conn;
|
|
1592
|
+
}
|
|
1593
|
+
async _ensure() {
|
|
1594
|
+
await this._conn.ensureViews("sets");
|
|
1595
|
+
}
|
|
1596
|
+
async list(options) {
|
|
1597
|
+
await this._ensure();
|
|
1598
|
+
try {
|
|
1599
|
+
const q = new SQLBuilder("sets");
|
|
1600
|
+
q.select("code", "name AS setName", "sealedProduct");
|
|
1601
|
+
if (options?.setCode) {
|
|
1602
|
+
q.whereEq("code", options.setCode.toUpperCase());
|
|
1603
|
+
}
|
|
1604
|
+
q.limit(options?.limit ?? 100);
|
|
1605
|
+
const [sql, params] = q.build();
|
|
1606
|
+
const rows = await this._conn.execute(sql, params);
|
|
1607
|
+
const products = [];
|
|
1608
|
+
for (const row of rows) {
|
|
1609
|
+
const sealed = row.sealedProduct;
|
|
1610
|
+
if (sealed && Array.isArray(sealed)) {
|
|
1611
|
+
for (const sp of sealed) {
|
|
1612
|
+
if (sp && typeof sp === "object") {
|
|
1613
|
+
const product = sp;
|
|
1614
|
+
if (options?.category && product.category !== options.category) {
|
|
1615
|
+
continue;
|
|
1616
|
+
}
|
|
1617
|
+
product.setCode = row.code;
|
|
1618
|
+
products.push(product);
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
return products;
|
|
1624
|
+
} catch {
|
|
1625
|
+
return [];
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
async get(uuid) {
|
|
1629
|
+
await this._ensure();
|
|
1630
|
+
try {
|
|
1631
|
+
const sql = "SELECT sub.code AS setCode, sub.sp FROM ( SELECT code, UNNEST(sealedProduct) AS sp FROM sets WHERE sealedProduct IS NOT NULL) sub WHERE sub.sp.uuid = $1 LIMIT 1";
|
|
1632
|
+
const rows = await this._conn.execute(sql, [uuid]);
|
|
1633
|
+
if (rows.length === 0) return null;
|
|
1634
|
+
const row = rows[0];
|
|
1635
|
+
const product = row.sp ?? {};
|
|
1636
|
+
if (typeof product === "object") {
|
|
1637
|
+
product.setCode = row.setCode;
|
|
1638
|
+
return product;
|
|
1639
|
+
}
|
|
1640
|
+
return null;
|
|
1641
|
+
} catch {
|
|
1642
|
+
return null;
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
};
|
|
1646
|
+
|
|
1647
|
+
// src/queries/skus.ts
|
|
1648
|
+
var SkuQuery = class {
|
|
1649
|
+
_conn;
|
|
1650
|
+
constructor(conn) {
|
|
1651
|
+
this._conn = conn;
|
|
1652
|
+
}
|
|
1653
|
+
async _ensure() {
|
|
1654
|
+
await this._conn.ensureViews("tcgplayer_skus");
|
|
1655
|
+
}
|
|
1656
|
+
async get(uuid) {
|
|
1657
|
+
await this._ensure();
|
|
1658
|
+
const rows = await this._conn.execute(
|
|
1659
|
+
"SELECT * FROM tcgplayer_skus WHERE uuid = $1",
|
|
1660
|
+
[uuid]
|
|
1661
|
+
);
|
|
1662
|
+
return rows;
|
|
1663
|
+
}
|
|
1664
|
+
async findBySkuId(skuId) {
|
|
1665
|
+
await this._ensure();
|
|
1666
|
+
const rows = await this._conn.execute(
|
|
1667
|
+
"SELECT * FROM tcgplayer_skus WHERE skuId = $1",
|
|
1668
|
+
[skuId]
|
|
1669
|
+
);
|
|
1670
|
+
return rows[0] ?? null;
|
|
1671
|
+
}
|
|
1672
|
+
async findByProductId(productId) {
|
|
1673
|
+
await this._ensure();
|
|
1674
|
+
return this._conn.execute(
|
|
1675
|
+
"SELECT * FROM tcgplayer_skus WHERE productId = $1",
|
|
1676
|
+
[productId]
|
|
1677
|
+
);
|
|
1678
|
+
}
|
|
1679
|
+
};
|
|
1680
|
+
|
|
1681
|
+
// src/queries/enums.ts
|
|
1682
|
+
var EnumQuery = class {
|
|
1683
|
+
_cache;
|
|
1684
|
+
constructor(cache) {
|
|
1685
|
+
this._cache = cache;
|
|
1686
|
+
}
|
|
1687
|
+
async keywords() {
|
|
1688
|
+
const raw = await this._cache.loadJson("keywords");
|
|
1689
|
+
return raw.data ?? {};
|
|
1690
|
+
}
|
|
1691
|
+
async cardTypes() {
|
|
1692
|
+
const raw = await this._cache.loadJson("card_types");
|
|
1693
|
+
return raw.data ?? {};
|
|
1694
|
+
}
|
|
1695
|
+
async enumValues() {
|
|
1696
|
+
const raw = await this._cache.loadJson("enum_values");
|
|
1697
|
+
return raw.data ?? {};
|
|
1698
|
+
}
|
|
1699
|
+
};
|
|
1700
|
+
|
|
1701
|
+
// src/client.ts
|
|
1702
|
+
var MtgjsonSDK = class _MtgjsonSDK {
|
|
1703
|
+
_cache;
|
|
1704
|
+
_conn;
|
|
1705
|
+
_cards = null;
|
|
1706
|
+
_sets = null;
|
|
1707
|
+
_prices = null;
|
|
1708
|
+
_decks = null;
|
|
1709
|
+
_sealed = null;
|
|
1710
|
+
_skus = null;
|
|
1711
|
+
_identifiers = null;
|
|
1712
|
+
_legalities = null;
|
|
1713
|
+
_tokens = null;
|
|
1714
|
+
_enums = null;
|
|
1715
|
+
_booster = null;
|
|
1716
|
+
constructor(options) {
|
|
1717
|
+
this._cache = new CacheManager({
|
|
1718
|
+
cacheDir: options?.cacheDir,
|
|
1719
|
+
offline: options?.offline,
|
|
1720
|
+
timeout: options?.timeout,
|
|
1721
|
+
onProgress: options?.onProgress
|
|
1722
|
+
});
|
|
1723
|
+
}
|
|
1724
|
+
static async create(options) {
|
|
1725
|
+
const sdk = new _MtgjsonSDK(options);
|
|
1726
|
+
await sdk._cache.init();
|
|
1727
|
+
sdk._conn = await Connection.create(sdk._cache);
|
|
1728
|
+
return sdk;
|
|
1729
|
+
}
|
|
1730
|
+
get cards() {
|
|
1731
|
+
if (!this._cards) this._cards = new CardQuery(this._conn);
|
|
1732
|
+
return this._cards;
|
|
1733
|
+
}
|
|
1734
|
+
get sets() {
|
|
1735
|
+
if (!this._sets) this._sets = new SetQuery(this._conn);
|
|
1736
|
+
return this._sets;
|
|
1737
|
+
}
|
|
1738
|
+
get prices() {
|
|
1739
|
+
if (!this._prices) this._prices = new PriceQuery(this._conn);
|
|
1740
|
+
return this._prices;
|
|
1741
|
+
}
|
|
1742
|
+
get decks() {
|
|
1743
|
+
if (!this._decks) this._decks = new DeckQuery(this._cache);
|
|
1744
|
+
return this._decks;
|
|
1745
|
+
}
|
|
1746
|
+
get sealed() {
|
|
1747
|
+
if (!this._sealed) this._sealed = new SealedQuery(this._conn);
|
|
1748
|
+
return this._sealed;
|
|
1749
|
+
}
|
|
1750
|
+
get skus() {
|
|
1751
|
+
if (!this._skus) this._skus = new SkuQuery(this._conn);
|
|
1752
|
+
return this._skus;
|
|
1753
|
+
}
|
|
1754
|
+
get identifiers() {
|
|
1755
|
+
if (!this._identifiers) this._identifiers = new IdentifierQuery(this._conn);
|
|
1756
|
+
return this._identifiers;
|
|
1757
|
+
}
|
|
1758
|
+
get legalities() {
|
|
1759
|
+
if (!this._legalities) this._legalities = new LegalityQuery(this._conn);
|
|
1760
|
+
return this._legalities;
|
|
1761
|
+
}
|
|
1762
|
+
get tokens() {
|
|
1763
|
+
if (!this._tokens) this._tokens = new TokenQuery(this._conn);
|
|
1764
|
+
return this._tokens;
|
|
1765
|
+
}
|
|
1766
|
+
get enums() {
|
|
1767
|
+
if (!this._enums) this._enums = new EnumQuery(this._cache);
|
|
1768
|
+
return this._enums;
|
|
1769
|
+
}
|
|
1770
|
+
get booster() {
|
|
1771
|
+
if (!this._booster) this._booster = new BoosterSimulator(this._conn);
|
|
1772
|
+
return this._booster;
|
|
1773
|
+
}
|
|
1774
|
+
get meta() {
|
|
1775
|
+
return this._cache.loadJson("meta").catch(() => ({}));
|
|
1776
|
+
}
|
|
1777
|
+
get views() {
|
|
1778
|
+
return [...this._conn._registeredViews].sort();
|
|
1779
|
+
}
|
|
1780
|
+
async sql(query, params) {
|
|
1781
|
+
return this._conn.execute(query, params);
|
|
1782
|
+
}
|
|
1783
|
+
async refresh() {
|
|
1784
|
+
if (!await this._cache.isStale()) return false;
|
|
1785
|
+
this._conn._registeredViews.clear();
|
|
1786
|
+
this._cards = null;
|
|
1787
|
+
this._sets = null;
|
|
1788
|
+
this._prices = null;
|
|
1789
|
+
this._decks = null;
|
|
1790
|
+
this._sealed = null;
|
|
1791
|
+
this._skus = null;
|
|
1792
|
+
this._identifiers = null;
|
|
1793
|
+
this._legalities = null;
|
|
1794
|
+
this._tokens = null;
|
|
1795
|
+
this._enums = null;
|
|
1796
|
+
this._booster = null;
|
|
1797
|
+
return true;
|
|
1798
|
+
}
|
|
1799
|
+
async exportDb(path) {
|
|
1800
|
+
const pathStr = path.replace(/\\/g, "/");
|
|
1801
|
+
const raw = this._conn.raw;
|
|
1802
|
+
await raw.run(`ATTACH '${pathStr}' AS export_db`);
|
|
1803
|
+
try {
|
|
1804
|
+
for (const viewName of [...this._conn._registeredViews].sort()) {
|
|
1805
|
+
await raw.run(
|
|
1806
|
+
`CREATE TABLE export_db.${viewName} AS SELECT * FROM ${viewName}`
|
|
1807
|
+
);
|
|
1808
|
+
}
|
|
1809
|
+
} finally {
|
|
1810
|
+
await raw.run("DETACH export_db");
|
|
1811
|
+
}
|
|
1812
|
+
return path;
|
|
1813
|
+
}
|
|
1814
|
+
async close() {
|
|
1815
|
+
await this._conn.close();
|
|
1816
|
+
this._cache.close();
|
|
1817
|
+
}
|
|
1818
|
+
/** For `await using sdk = await MtgjsonSDK.create()` */
|
|
1819
|
+
async [Symbol.asyncDispose]() {
|
|
1820
|
+
await this.close();
|
|
1821
|
+
}
|
|
1822
|
+
};
|
|
1823
|
+
export {
|
|
1824
|
+
BoosterSimulator,
|
|
1825
|
+
CacheManager,
|
|
1826
|
+
CardQuery,
|
|
1827
|
+
Connection,
|
|
1828
|
+
DeckQuery,
|
|
1829
|
+
EnumQuery,
|
|
1830
|
+
IdentifierQuery,
|
|
1831
|
+
LegalityQuery,
|
|
1832
|
+
MtgjsonSDK,
|
|
1833
|
+
PriceQuery,
|
|
1834
|
+
SQLBuilder,
|
|
1835
|
+
SealedQuery,
|
|
1836
|
+
SetQuery,
|
|
1837
|
+
SkuQuery,
|
|
1838
|
+
TokenQuery
|
|
1839
|
+
};
|
|
1840
|
+
//# sourceMappingURL=index.js.map
|