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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright © 2018-Present Zachary Halpern
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
# mtgjson-sdk
|
|
2
|
+
|
|
3
|
+
A high-performance, DuckDB-backed TypeScript query client for [MTGJSON](https://mtgjson.com).
|
|
4
|
+
|
|
5
|
+
Unlike traditional SDKs that rely on rate-limited REST APIs, `mtgjson-sdk` implements a local data warehouse architecture. It synchronizes optimized Parquet data from the MTGJSON CDN to your local machine, utilizing DuckDB to execute complex analytics, fuzzy searches, and booster simulations with sub-millisecond latency.
|
|
6
|
+
|
|
7
|
+
## Key Features
|
|
8
|
+
|
|
9
|
+
* **Vectorized Execution**: Powered by DuckDB for high-speed OLAP queries on the full MTG dataset.
|
|
10
|
+
* **Offline-First**: Data is cached locally, allowing for full functionality without an active internet connection.
|
|
11
|
+
* **Fuzzy Search**: Built-in Jaro-Winkler similarity matching to handle typos and approximate name lookups.
|
|
12
|
+
* **Fully Async**: Native async/await API with `Symbol.asyncDispose` for automatic resource cleanup.
|
|
13
|
+
* **Fully Typed**: Complete TypeScript type definitions for all query results and parameters.
|
|
14
|
+
* **Booster Simulation**: Accurate pack opening logic using official MTGJSON weights and sheet configurations.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
TODO: NPM STUFF
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { MtgjsonSDK } from "mtgjson-sdk";
|
|
24
|
+
|
|
25
|
+
const sdk = await MtgjsonSDK.create();
|
|
26
|
+
|
|
27
|
+
// Search for cards
|
|
28
|
+
const bolts = await sdk.cards.getByName("Lightning Bolt");
|
|
29
|
+
console.log(`Found ${bolts.length} printings of Lightning Bolt`);
|
|
30
|
+
|
|
31
|
+
// Get a specific set
|
|
32
|
+
const mh3 = await sdk.sets.get("MH3");
|
|
33
|
+
if (mh3) {
|
|
34
|
+
console.log(`${mh3.name} -- ${mh3.totalSetSize} cards`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Check format legality
|
|
38
|
+
const isLegal = await sdk.legalities.isLegal(bolts[0].uuid, "modern");
|
|
39
|
+
console.log(`Modern legal: ${isLegal}`);
|
|
40
|
+
|
|
41
|
+
// Find the cheapest printing
|
|
42
|
+
const cheapest = await sdk.prices.cheapestPrinting("Lightning Bolt");
|
|
43
|
+
if (cheapest) {
|
|
44
|
+
console.log(`Cheapest: $${cheapest.price} (${cheapest.setCode})`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Raw SQL for anything else
|
|
48
|
+
const rows = await sdk.sql("SELECT name, manaValue FROM cards WHERE manaValue = $1 LIMIT 5", [0]);
|
|
49
|
+
|
|
50
|
+
await sdk.close();
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Architecture
|
|
54
|
+
|
|
55
|
+
By using DuckDB, the SDK leverages columnar storage and vectorized execution, making it significantly faster than SQLite or standard JSON parsing for MTG's relational dataset.
|
|
56
|
+
|
|
57
|
+
1. **Synchronization**: On first use, the SDK lazily downloads Parquet and JSON files from the MTGJSON CDN to a platform-specific cache directory (`~/.cache/mtgjson-sdk` on Linux, `~/Library/Caches/mtgjson-sdk` on macOS, `AppData/Local/mtgjson-sdk` on Windows).
|
|
58
|
+
2. **Virtual Schema**: DuckDB views are registered on-demand. Accessing `sdk.cards` registers the card view; accessing `sdk.prices` registers price data. You only pay the memory cost for the data you query.
|
|
59
|
+
3. **Dynamic Adaptation**: The SDK introspects Parquet metadata to automatically handle schema changes, plural-column array conversion, and format legality unpivoting.
|
|
60
|
+
4. **Materialization**: Queries return fully-typed TypeScript interfaces for individual record ergonomics, or raw `Record<string, unknown>` objects for flexible consumption.
|
|
61
|
+
|
|
62
|
+
## Use Cases
|
|
63
|
+
|
|
64
|
+
### Price Analytics
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
const sdk = await MtgjsonSDK.create();
|
|
68
|
+
|
|
69
|
+
// Find the cheapest printing of any card
|
|
70
|
+
const cheapest = await sdk.prices.cheapestPrinting("Ragavan, Nimble Pilferer");
|
|
71
|
+
|
|
72
|
+
// Price trend over time
|
|
73
|
+
if (cheapest) {
|
|
74
|
+
const trend = await sdk.prices.priceTrend(cheapest.uuid, {
|
|
75
|
+
provider: "tcgplayer",
|
|
76
|
+
finish: "normal",
|
|
77
|
+
});
|
|
78
|
+
console.log(`Range: $${trend.min_price} - $${trend.max_price}`);
|
|
79
|
+
console.log(`Average: $${trend.avg_price} over ${trend.data_points} data points`);
|
|
80
|
+
|
|
81
|
+
// Full price history with date range
|
|
82
|
+
const history = await sdk.prices.history(cheapest.uuid, {
|
|
83
|
+
provider: "tcgplayer",
|
|
84
|
+
dateFrom: "2024-01-01",
|
|
85
|
+
dateTo: "2024-12-31",
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Most expensive printings across the entire dataset
|
|
89
|
+
const priciest = await sdk.prices.mostExpensivePrintings({ limit: 10 });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
await sdk.close();
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Advanced Card Search
|
|
96
|
+
|
|
97
|
+
The `search()` method supports ~20 composable filters that can be combined freely:
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
const sdk = await MtgjsonSDK.create();
|
|
101
|
+
|
|
102
|
+
// Complex filters: Modern-legal red creatures with CMC <= 2
|
|
103
|
+
const aggroCreatures = await sdk.cards.search({
|
|
104
|
+
colors: ["R"],
|
|
105
|
+
types: "Creature",
|
|
106
|
+
manaValueLte: 2.0,
|
|
107
|
+
legalIn: "modern",
|
|
108
|
+
limit: 50,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Typo-tolerant fuzzy search (Jaro-Winkler similarity)
|
|
112
|
+
const results = await sdk.cards.search({
|
|
113
|
+
fuzzyName: "Ligtning Bolt", // still finds it!
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Rules text search using regular expressions
|
|
117
|
+
const burn = await sdk.cards.search({
|
|
118
|
+
textRegex: "deals? \\d+ damage to any target",
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Search by keyword ability across formats
|
|
122
|
+
const flyers = await sdk.cards.search({
|
|
123
|
+
keyword: "Flying",
|
|
124
|
+
colors: ["W", "U"],
|
|
125
|
+
legalIn: "standard",
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Find cards by foreign-language name
|
|
129
|
+
const blitz = await sdk.cards.search({
|
|
130
|
+
localizedName: "Blitzschlag", // German for Lightning Bolt
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
await sdk.close();
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
<details>
|
|
137
|
+
<summary>All <code>search()</code> parameters</summary>
|
|
138
|
+
|
|
139
|
+
| Parameter | Type | Description |
|
|
140
|
+
|---|---|---|
|
|
141
|
+
| `name` | `string` | Name pattern (`%` = wildcard) |
|
|
142
|
+
| `fuzzyName` | `string` | Typo-tolerant Jaro-Winkler match |
|
|
143
|
+
| `localizedName` | `string` | Foreign-language name search |
|
|
144
|
+
| `colors` | `string[]` | Cards containing these colors |
|
|
145
|
+
| `colorIdentity` | `string[]` | Color identity filter |
|
|
146
|
+
| `legalIn` | `string` | Format legality |
|
|
147
|
+
| `rarity` | `string` | Rarity filter |
|
|
148
|
+
| `manaValue` | `number` | Exact mana value |
|
|
149
|
+
| `manaValueLte` | `number` | Mana value upper bound |
|
|
150
|
+
| `manaValueGte` | `number` | Mana value lower bound |
|
|
151
|
+
| `text` | `string` | Rules text substring |
|
|
152
|
+
| `textRegex` | `string` | Rules text regex |
|
|
153
|
+
| `types` | `string` | Type line search |
|
|
154
|
+
| `artist` | `string` | Artist name |
|
|
155
|
+
| `keyword` | `string` | Keyword ability |
|
|
156
|
+
| `isPromo` | `boolean` | Promo status |
|
|
157
|
+
| `availability` | `string` | `"paper"` or `"mtgo"` |
|
|
158
|
+
| `language` | `string` | Language filter |
|
|
159
|
+
| `layout` | `string` | Card layout |
|
|
160
|
+
| `setCode` | `string` | Set code |
|
|
161
|
+
| `setType` | `string` | Set type (joins sets table) |
|
|
162
|
+
| `power` | `string` | Power filter |
|
|
163
|
+
| `toughness` | `string` | Toughness filter |
|
|
164
|
+
| `limit` / `offset` | `number` | Pagination |
|
|
165
|
+
|
|
166
|
+
</details>
|
|
167
|
+
|
|
168
|
+
### Collection & Cross-Reference
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
const sdk = await MtgjsonSDK.create();
|
|
172
|
+
|
|
173
|
+
// Cross-reference by any external ID system
|
|
174
|
+
const cards = await sdk.identifiers.findByScryfallId("f7a21fe4-...");
|
|
175
|
+
const tcgCards = await sdk.identifiers.findByTcgplayerId("12345");
|
|
176
|
+
const mtgoCards = await sdk.identifiers.findByMtgoId("67890");
|
|
177
|
+
|
|
178
|
+
// Get all external identifiers for a card
|
|
179
|
+
const allIds = await sdk.identifiers.getIdentifiers("card-uuid-here");
|
|
180
|
+
// -> Scryfall, TCGPlayer, MTGO, Arena, Cardmarket, Card Kingdom, Cardsphere, ...
|
|
181
|
+
|
|
182
|
+
// TCGPlayer SKU variants (foil, etched, etc.)
|
|
183
|
+
const skus = await sdk.skus.get("card-uuid-here");
|
|
184
|
+
|
|
185
|
+
// Export to a standalone DuckDB file for offline analysis
|
|
186
|
+
await sdk.exportDb("my_collection.duckdb");
|
|
187
|
+
// Now query with: duckdb my_collection.duckdb "SELECT * FROM cards LIMIT 5"
|
|
188
|
+
|
|
189
|
+
await sdk.close();
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Booster Simulation
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
const sdk = await MtgjsonSDK.create();
|
|
196
|
+
|
|
197
|
+
// See available booster types for a set
|
|
198
|
+
const types = await sdk.booster.availableTypes("MH3"); // ["draft", "collector", ...]
|
|
199
|
+
|
|
200
|
+
// Open a single draft pack
|
|
201
|
+
const pack = await sdk.booster.openPack("MH3", "draft");
|
|
202
|
+
for (const card of pack) {
|
|
203
|
+
console.log(` ${card.name} (${card.rarity})`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Open an entire box
|
|
207
|
+
const box = await sdk.booster.openBox("MH3", "draft", 36);
|
|
208
|
+
const totalCards = box.reduce((sum, p) => sum + p.length, 0);
|
|
209
|
+
console.log(`Opened ${box.length} packs, ${totalCards} total cards`);
|
|
210
|
+
|
|
211
|
+
await sdk.close();
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## API Reference
|
|
215
|
+
|
|
216
|
+
### Core Data
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
// Cards
|
|
220
|
+
await sdk.cards.getByUuid("uuid") // single card lookup
|
|
221
|
+
await sdk.cards.getByUuids(["uuid1", "uuid2"]) // batch lookup
|
|
222
|
+
await sdk.cards.getByName("Lightning Bolt") // all printings of a name
|
|
223
|
+
await sdk.cards.search({...}) // composable filters (see above)
|
|
224
|
+
await sdk.cards.getPrintings("Lightning Bolt") // all printings across sets
|
|
225
|
+
await sdk.cards.getAtomic("Lightning Bolt") // oracle data (no printing info)
|
|
226
|
+
await sdk.cards.findByScryfallId("...") // cross-reference shortcut
|
|
227
|
+
await sdk.cards.random(5) // random cards
|
|
228
|
+
await sdk.cards.count() // total (or filtered with kwargs)
|
|
229
|
+
|
|
230
|
+
// Tokens
|
|
231
|
+
await sdk.tokens.getByUuid("uuid")
|
|
232
|
+
await sdk.tokens.getByName("Soldier")
|
|
233
|
+
await sdk.tokens.search({ name: "%Token", setCode: "MH3", colors: ["W"] })
|
|
234
|
+
await sdk.tokens.forSet("MH3")
|
|
235
|
+
await sdk.tokens.count()
|
|
236
|
+
|
|
237
|
+
// Sets
|
|
238
|
+
await sdk.sets.get("MH3")
|
|
239
|
+
await sdk.sets.list({ setType: "expansion" })
|
|
240
|
+
await sdk.sets.search({ name: "Horizons", releaseYear: 2024 })
|
|
241
|
+
await sdk.sets.getFinancialSummary("MH3", { provider: "tcgplayer" })
|
|
242
|
+
await sdk.sets.count()
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Playability
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
// Legalities
|
|
249
|
+
await sdk.legalities.formatsForCard("uuid") // -> { modern: "Legal", ... }
|
|
250
|
+
await sdk.legalities.legalIn("modern") // all modern-legal cards
|
|
251
|
+
await sdk.legalities.isLegal("uuid", "modern") // -> boolean
|
|
252
|
+
await sdk.legalities.bannedIn("modern") // also: restrictedIn, suspendedIn
|
|
253
|
+
|
|
254
|
+
// Decks & Sealed Products
|
|
255
|
+
await sdk.decks.list({ setCode: "MH3" })
|
|
256
|
+
await sdk.decks.search({ name: "Eldrazi" })
|
|
257
|
+
await sdk.decks.count()
|
|
258
|
+
await sdk.sealed.list({ setCode: "MH3" })
|
|
259
|
+
await sdk.sealed.get("uuid")
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Market & Identifiers
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
// Prices
|
|
266
|
+
await sdk.prices.get("uuid") // full nested price data
|
|
267
|
+
await sdk.prices.today("uuid", { provider: "tcgplayer", finish: "foil" })
|
|
268
|
+
await sdk.prices.history("uuid", { provider: "tcgplayer", dateFrom: "2024-01-01" })
|
|
269
|
+
await sdk.prices.priceTrend("uuid") // min/max/avg statistics
|
|
270
|
+
await sdk.prices.cheapestPrinting("Lightning Bolt")
|
|
271
|
+
await sdk.prices.mostExpensivePrintings({ limit: 10 })
|
|
272
|
+
|
|
273
|
+
// Identifiers (supports all major external ID systems)
|
|
274
|
+
await sdk.identifiers.findByScryfallId("...")
|
|
275
|
+
await sdk.identifiers.findByTcgplayerId("...")
|
|
276
|
+
await sdk.identifiers.findByMtgoId("...")
|
|
277
|
+
await sdk.identifiers.findByMtgArenaId("...")
|
|
278
|
+
await sdk.identifiers.findByMultiverseId("...")
|
|
279
|
+
await sdk.identifiers.findByMcmId("...")
|
|
280
|
+
await sdk.identifiers.findByCardKingdomId("...")
|
|
281
|
+
await sdk.identifiers.findBy("scryfallId", "...") // generic lookup
|
|
282
|
+
await sdk.identifiers.getIdentifiers("uuid") // all IDs for a card
|
|
283
|
+
|
|
284
|
+
// SKUs
|
|
285
|
+
await sdk.skus.get("uuid")
|
|
286
|
+
await sdk.skus.findBySkuId(123456)
|
|
287
|
+
await sdk.skus.findByProductId(789)
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Booster & Enums
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
await sdk.booster.availableTypes("MH3")
|
|
294
|
+
await sdk.booster.openPack("MH3", "draft")
|
|
295
|
+
await sdk.booster.openBox("MH3", "draft", 36)
|
|
296
|
+
await sdk.booster.sheetContents("MH3", "draft", "common")
|
|
297
|
+
|
|
298
|
+
await sdk.enums.keywords()
|
|
299
|
+
await sdk.enums.cardTypes()
|
|
300
|
+
await sdk.enums.enumValues()
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### System
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
await sdk.meta // version and build date
|
|
307
|
+
sdk.views // registered view names
|
|
308
|
+
await sdk.refresh() // check CDN for new data -> boolean
|
|
309
|
+
await sdk.exportDb("output.duckdb") // export to persistent DuckDB file
|
|
310
|
+
await sdk.sql(query, params) // raw parameterized SQL
|
|
311
|
+
await sdk.close() // release resources
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## Performance and Memory
|
|
315
|
+
|
|
316
|
+
When querying large datasets (thousands of cards), use raw SQL to avoid materializing large arrays of typed objects in memory.
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
// Use raw SQL for bulk analysis
|
|
320
|
+
const stats = await sdk.sql(`
|
|
321
|
+
SELECT setCode, COUNT(*) as card_count, AVG(manaValue) as avg_cmc
|
|
322
|
+
FROM cards
|
|
323
|
+
GROUP BY setCode
|
|
324
|
+
ORDER BY card_count DESC
|
|
325
|
+
LIMIT 10
|
|
326
|
+
`);
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## Advanced Usage
|
|
330
|
+
|
|
331
|
+
### Async Factory Pattern
|
|
332
|
+
|
|
333
|
+
The SDK uses an async factory since DuckDB initialization is asynchronous:
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
import { MtgjsonSDK } from "mtgjson-sdk";
|
|
337
|
+
|
|
338
|
+
const sdk = await MtgjsonSDK.create({
|
|
339
|
+
cacheDir: "/data/mtgjson-cache",
|
|
340
|
+
offline: false,
|
|
341
|
+
timeout: 300_000,
|
|
342
|
+
onProgress: (filename, downloaded, total) => {
|
|
343
|
+
const pct = total ? ((downloaded / total) * 100).toFixed(1) : "?";
|
|
344
|
+
process.stdout.write(`\r${filename}: ${pct}%`);
|
|
345
|
+
},
|
|
346
|
+
});
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Automatic Resource Cleanup
|
|
350
|
+
|
|
351
|
+
The SDK supports `Symbol.asyncDispose` for automatic cleanup with `await using`:
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
{
|
|
355
|
+
await using sdk = await MtgjsonSDK.create();
|
|
356
|
+
|
|
357
|
+
const cards = await sdk.cards.search({ name: "Lightning%" });
|
|
358
|
+
console.log(cards.length);
|
|
359
|
+
|
|
360
|
+
// sdk.close() is called automatically when the block exits
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Auto-Refresh for Long-Running Services
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
// In a scheduled task or health check:
|
|
368
|
+
const refreshed = await sdk.refresh();
|
|
369
|
+
if (refreshed) {
|
|
370
|
+
console.log("New MTGJSON data detected -- cache refreshed");
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Raw SQL
|
|
375
|
+
|
|
376
|
+
All user input goes through DuckDB parameter binding (`$1`, `$2`, ...):
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
const sdk = await MtgjsonSDK.create();
|
|
380
|
+
|
|
381
|
+
// Ensure views are registered before querying
|
|
382
|
+
await sdk.cards.count();
|
|
383
|
+
|
|
384
|
+
// Parameterized queries
|
|
385
|
+
const rows = await sdk.sql(
|
|
386
|
+
"SELECT name, setCode, rarity FROM cards WHERE manaValue <= $1 AND rarity = $2",
|
|
387
|
+
[2, "mythic"]
|
|
388
|
+
);
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### Web API Example
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
import { MtgjsonSDK } from "mtgjson-sdk";
|
|
395
|
+
import express from "express";
|
|
396
|
+
|
|
397
|
+
const app = express();
|
|
398
|
+
const sdk = await MtgjsonSDK.create();
|
|
399
|
+
|
|
400
|
+
app.get("/card/:name", async (req, res) => {
|
|
401
|
+
const cards = await sdk.cards.getByName(req.params.name);
|
|
402
|
+
res.json(cards);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
app.get("/health", async (_req, res) => {
|
|
406
|
+
const refreshed = await sdk.refresh();
|
|
407
|
+
res.json({ refreshed, views: sdk.views });
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
process.on("SIGTERM", async () => {
|
|
411
|
+
await sdk.close();
|
|
412
|
+
process.exit(0);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
app.listen(3000, () => console.log("Listening on :3000"));
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
## Examples
|
|
419
|
+
|
|
420
|
+
### Next.js Card Search (`examples/next-search`)
|
|
421
|
+
|
|
422
|
+
A full-stack web application built with Next.js 15 and Tailwind CSS that demonstrates the SDK in a server-side rendered context. Features a card search interface with fuzzy matching, filters (color, rarity, type, set, format legality), card detail pages, and responsive image grids powered by Scryfall.
|
|
423
|
+
|
|
424
|
+
**SDK features demonstrated:**
|
|
425
|
+
|
|
426
|
+
| Feature | SDK Method |
|
|
427
|
+
|---------|-----------|
|
|
428
|
+
| Fuzzy card search with filters | `sdk.cards.search({ fuzzyName, colors, rarity, types, setCode, legalIn })` |
|
|
429
|
+
| Total result count with pagination | `sdk.cards.count()` |
|
|
430
|
+
| Random cards on the home page | `sdk.cards.random()` |
|
|
431
|
+
| Card detail lookup | `sdk.cards.getByUuid()` |
|
|
432
|
+
| All printings across sets | `sdk.cards.getPrintings()` |
|
|
433
|
+
| Cross-system identifier lookup | `sdk.identifiers.getIdentifiers()` |
|
|
434
|
+
| Format legality table | `sdk.legalities.formatsForCard()` |
|
|
435
|
+
| Retail price data by provider | `sdk.prices.today()` |
|
|
436
|
+
| Set list for autocomplete filter | `sdk.sets.list()` |
|
|
437
|
+
| Data version attribution | `sdk.meta` |
|
|
438
|
+
|
|
439
|
+
```bash
|
|
440
|
+
cd examples/next-search
|
|
441
|
+
bun install
|
|
442
|
+
bun run dev
|
|
443
|
+
# Open http://localhost:3000
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
> **Note:** The SDK must be built first (`bun run build` in the repo root). First page load downloads parquet data from the MTGJSON CDN (~30s cold start), subsequent loads use the local cache.
|
|
447
|
+
|
|
448
|
+
## Development
|
|
449
|
+
|
|
450
|
+
```bash
|
|
451
|
+
git clone https://github.com/the-muppet2/mtgjson-sdk-typescript.git
|
|
452
|
+
cd mtgjson-sdk-typescript
|
|
453
|
+
bun install
|
|
454
|
+
bun run build
|
|
455
|
+
bun run typecheck
|
|
456
|
+
bun test
|
|
457
|
+
bun run lint
|
|
458
|
+
bun run format
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
## License
|
|
462
|
+
|
|
463
|
+
MIT
|