hearthstone-oracle 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.
Files changed (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +73 -0
  3. package/dist/data/db.d.ts +49 -0
  4. package/dist/data/db.d.ts.map +1 -0
  5. package/dist/data/db.js +109 -0
  6. package/dist/data/db.js.map +1 -0
  7. package/dist/data/hearthstone.d.ts +40 -0
  8. package/dist/data/hearthstone.d.ts.map +1 -0
  9. package/dist/data/hearthstone.js +58 -0
  10. package/dist/data/hearthstone.js.map +1 -0
  11. package/dist/data/pipeline.d.ts +36 -0
  12. package/dist/data/pipeline.d.ts.map +1 -0
  13. package/dist/data/pipeline.js +90 -0
  14. package/dist/data/pipeline.js.map +1 -0
  15. package/dist/data/schema.sql +111 -0
  16. package/dist/data/strategy-seed.d.ts +3 -0
  17. package/dist/data/strategy-seed.d.ts.map +1 -0
  18. package/dist/data/strategy-seed.js +512 -0
  19. package/dist/data/strategy-seed.js.map +1 -0
  20. package/dist/format.d.ts +63 -0
  21. package/dist/format.d.ts.map +1 -0
  22. package/dist/format.js +331 -0
  23. package/dist/format.js.map +1 -0
  24. package/dist/knowledge/archetype-classifier.d.ts +19 -0
  25. package/dist/knowledge/archetype-classifier.d.ts.map +1 -0
  26. package/dist/knowledge/archetype-classifier.js +232 -0
  27. package/dist/knowledge/archetype-classifier.js.map +1 -0
  28. package/dist/server.d.ts +5 -0
  29. package/dist/server.d.ts.map +1 -0
  30. package/dist/server.js +269 -0
  31. package/dist/server.js.map +1 -0
  32. package/dist/tools/analyze-deck.d.ts +45 -0
  33. package/dist/tools/analyze-deck.d.ts.map +1 -0
  34. package/dist/tools/analyze-deck.js +159 -0
  35. package/dist/tools/analyze-deck.js.map +1 -0
  36. package/dist/tools/decode-deck.d.ts +30 -0
  37. package/dist/tools/decode-deck.d.ts.map +1 -0
  38. package/dist/tools/decode-deck.js +127 -0
  39. package/dist/tools/decode-deck.js.map +1 -0
  40. package/dist/tools/explain-concept.d.ts +26 -0
  41. package/dist/tools/explain-concept.d.ts.map +1 -0
  42. package/dist/tools/explain-concept.js +34 -0
  43. package/dist/tools/explain-concept.js.map +1 -0
  44. package/dist/tools/get-archetype.d.ts +29 -0
  45. package/dist/tools/get-archetype.d.ts.map +1 -0
  46. package/dist/tools/get-archetype.js +47 -0
  47. package/dist/tools/get-archetype.js.map +1 -0
  48. package/dist/tools/get-card.d.ts +13 -0
  49. package/dist/tools/get-card.d.ts.map +1 -0
  50. package/dist/tools/get-card.js +71 -0
  51. package/dist/tools/get-card.js.map +1 -0
  52. package/dist/tools/get-class-identity.d.ts +43 -0
  53. package/dist/tools/get-class-identity.d.ts.map +1 -0
  54. package/dist/tools/get-class-identity.js +69 -0
  55. package/dist/tools/get-class-identity.js.map +1 -0
  56. package/dist/tools/get-keyword.d.ts +30 -0
  57. package/dist/tools/get-keyword.d.ts.map +1 -0
  58. package/dist/tools/get-keyword.js +88 -0
  59. package/dist/tools/get-keyword.js.map +1 -0
  60. package/dist/tools/get-matchup.d.ts +32 -0
  61. package/dist/tools/get-matchup.d.ts.map +1 -0
  62. package/dist/tools/get-matchup.js +41 -0
  63. package/dist/tools/get-matchup.js.map +1 -0
  64. package/dist/tools/search-cards.d.ts +43 -0
  65. package/dist/tools/search-cards.d.ts.map +1 -0
  66. package/dist/tools/search-cards.js +167 -0
  67. package/dist/tools/search-cards.js.map +1 -0
  68. package/package.json +50 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 gregario
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,73 @@
1
+ <!-- mcp-name: io.github.gregario/hearthstone-oracle -->
2
+
3
+ <p align="center">
4
+ <a href="https://www.npmjs.com/package/hearthstone-oracle"><img src="https://img.shields.io/npm/v/hearthstone-oracle.svg" alt="npm version"></a>
5
+ <a href="https://www.npmjs.com/package/hearthstone-oracle"><img src="https://img.shields.io/npm/dm/hearthstone-oracle.svg" alt="npm downloads"></a>
6
+ <a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg" alt="Node.js 18+"></a>
7
+ <a href="https://modelcontextprotocol.io"><img src="https://img.shields.io/badge/MCP-compatible-purple.svg" alt="MCP Compatible"></a>
8
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License"></a>
9
+ <a href="https://github.com/sponsors/gregario"><img src="https://img.shields.io/badge/sponsor-♥-ea4aaa.svg" alt="Sponsor"></a>
10
+ </p>
11
+
12
+ # hearthstone-oracle
13
+
14
+ Hearthstone MCP server with card search, deck analysis, and strategy coaching. Gives LLMs access to every Hearthstone card plus built-in strategy knowledge for deck building and gameplay advice.
15
+
16
+ ## Features
17
+
18
+ - **Complete card database** from HearthstoneJSON — every collectible card, auto-updated
19
+ - **Deck decoding and analysis** — paste a deck code, get full card list, mana curve, and archetype breakdown
20
+ - **Strategy knowledge** — archetypes, class identities, matchup dynamics, and game concepts
21
+ - **9 MCP tools** — search, lookup, decode, analyze, coach
22
+
23
+ ## Quick Start
24
+
25
+ ```bash
26
+ claude mcp add hearthstone-oracle -- npx -y hearthstone-oracle
27
+ ```
28
+
29
+ Card data is downloaded automatically from HearthstoneJSON on first run and stored locally in `~/.hearthstone-oracle/`. No API key required.
30
+
31
+ ### Claude Desktop
32
+
33
+ Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json`):
34
+
35
+ ```json
36
+ {
37
+ "mcpServers": {
38
+ "hearthstone-oracle": {
39
+ "command": "npx",
40
+ "args": ["-y", "hearthstone-oracle"]
41
+ }
42
+ }
43
+ }
44
+ ```
45
+
46
+ ## Tools
47
+
48
+ | Tool | Description |
49
+ |------|-------------|
50
+ | `search_cards` | Search cards by name, text, class, cost, type, rarity, set, or keyword |
51
+ | `get_card` | Get complete details for a specific card with fuzzy matching |
52
+ | `get_keyword` | Look up keyword/mechanic definitions with related cards |
53
+ | `decode_deck` | Decode a deck code into full card list with mana curve |
54
+ | `analyze_deck` | Analyze a deck's archetype, gameplan, strengths, and matchup profile |
55
+ | `get_archetype` | Learn about deck archetypes (aggro, control, combo, midrange, tempo, value) |
56
+ | `get_class_identity` | Strategic identity of each Hearthstone class |
57
+ | `get_matchup` | Theoretical matchup dynamics between archetypes |
58
+ | `explain_concept` | Fundamental game concepts explained for Hearthstone |
59
+
60
+ ## How It Works
61
+
62
+ Two-layer architecture:
63
+
64
+ 1. **Card data layer** — Full card database from HearthstoneJSON, stored in a local SQLite database with FTS5 full-text search. Cards are fetched on first run and updated when newer game builds are available.
65
+ 2. **Strategy layer** — Built-in strategy knowledge covering archetypes, class identities, matchup theory, and core game concepts. Enables the server to provide coaching and analysis, not just raw card data.
66
+
67
+ ## Data Source
68
+
69
+ Card data provided by [HearthstoneJSON](https://hearthstonejson.com) by [HearthSim](https://hearthsim.info). Auto-extracted from game files. Unrestricted use.
70
+
71
+ ## License
72
+
73
+ MIT
@@ -0,0 +1,49 @@
1
+ import Database from 'better-sqlite3';
2
+ export interface CardRow {
3
+ id: string;
4
+ card_id: string | null;
5
+ name: string;
6
+ mana_cost: number | null;
7
+ type: string | null;
8
+ card_set: string | null;
9
+ set_name: string | null;
10
+ player_class: string | null;
11
+ rarity: string | null;
12
+ attack: number | null;
13
+ health: number | null;
14
+ durability: number | null;
15
+ armor: number | null;
16
+ text: string | null;
17
+ flavor: string | null;
18
+ artist: string | null;
19
+ collectible: number;
20
+ elite: number;
21
+ race: string | null;
22
+ spell_school: string | null;
23
+ keywords: string | null;
24
+ }
25
+ /**
26
+ * Opens (or creates) a SQLite database and initializes the schema.
27
+ *
28
+ * @param dataDir - Custom data directory. Defaults to ~/.hearthstone-oracle/.
29
+ * Pass ':memory:' for an in-memory database (tests).
30
+ * @returns A better-sqlite3 Database instance with schema applied.
31
+ */
32
+ export declare function getDatabase(dataDir?: string): Database.Database;
33
+ /**
34
+ * Insert a single card into the cards table.
35
+ */
36
+ export declare function insertCard(db: Database.Database, card: CardRow): void;
37
+ /**
38
+ * Insert multiple cards in batched transactions (500 per batch).
39
+ */
40
+ export declare function insertCards(db: Database.Database, cards: CardRow[]): void;
41
+ /**
42
+ * Check if the cards table has any rows.
43
+ */
44
+ export declare function hasExistingData(db: Database.Database): boolean;
45
+ /**
46
+ * Get all table names in the database (excluding internal SQLite tables).
47
+ */
48
+ export declare function getTableNames(db: Database.Database): string[];
49
+ //# sourceMappingURL=db.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/data/db.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAQtC,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAyBD;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAoB/D;AAgCD;;GAEG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAGrE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,IAAI,CAYzE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,OAAO,CAG9D;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,MAAM,EAAE,CAK7D"}
@@ -0,0 +1,109 @@
1
+ import Database from 'better-sqlite3';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import os from 'node:os';
5
+ import { fileURLToPath } from 'node:url';
6
+ // --- Constants ---
7
+ const DEFAULT_DATA_DIR = path.join(os.homedir(), '.hearthstone-oracle');
8
+ const DB_FILENAME = 'cards.sqlite';
9
+ const BATCH_SIZE = 500;
10
+ // --- Schema loading ---
11
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
+ const SCHEMA_PATH = path.join(__dirname, 'schema.sql');
13
+ function loadSchema() {
14
+ return fs.readFileSync(SCHEMA_PATH, 'utf-8');
15
+ }
16
+ // --- Database management ---
17
+ function ensureDataDir(dataDir) {
18
+ if (!fs.existsSync(dataDir)) {
19
+ fs.mkdirSync(dataDir, { recursive: true });
20
+ }
21
+ }
22
+ /**
23
+ * Opens (or creates) a SQLite database and initializes the schema.
24
+ *
25
+ * @param dataDir - Custom data directory. Defaults to ~/.hearthstone-oracle/.
26
+ * Pass ':memory:' for an in-memory database (tests).
27
+ * @returns A better-sqlite3 Database instance with schema applied.
28
+ */
29
+ export function getDatabase(dataDir) {
30
+ let db;
31
+ if (dataDir === ':memory:') {
32
+ db = new Database(':memory:');
33
+ }
34
+ else {
35
+ const dir = dataDir ?? DEFAULT_DATA_DIR;
36
+ ensureDataDir(dir);
37
+ const dbPath = path.join(dir, DB_FILENAME);
38
+ db = new Database(dbPath);
39
+ }
40
+ // Performance pragmas
41
+ db.pragma('journal_mode = WAL');
42
+ db.pragma('foreign_keys = ON');
43
+ // Initialize schema (idempotent via IF NOT EXISTS)
44
+ initializeSchema(db);
45
+ return db;
46
+ }
47
+ /**
48
+ * Runs the schema SQL against the database.
49
+ * Safe to call multiple times due to IF NOT EXISTS clauses.
50
+ */
51
+ function initializeSchema(db) {
52
+ let schema;
53
+ try {
54
+ schema = loadSchema();
55
+ }
56
+ catch {
57
+ throw new Error(`Could not load schema.sql from ${SCHEMA_PATH}. ` +
58
+ 'Ensure schema.sql is copied to the dist/data/ directory during build.');
59
+ }
60
+ db.exec(schema);
61
+ }
62
+ // --- Query helpers ---
63
+ const INSERT_CARD_SQL = `
64
+ INSERT OR REPLACE INTO cards
65
+ (id, card_id, name, mana_cost, type, card_set, set_name, player_class,
66
+ rarity, attack, health, durability, armor, text, flavor, artist,
67
+ collectible, elite, race, spell_school, keywords)
68
+ VALUES
69
+ (@id, @card_id, @name, @mana_cost, @type, @card_set, @set_name, @player_class,
70
+ @rarity, @attack, @health, @durability, @armor, @text, @flavor, @artist,
71
+ @collectible, @elite, @race, @spell_school, @keywords)
72
+ `;
73
+ /**
74
+ * Insert a single card into the cards table.
75
+ */
76
+ export function insertCard(db, card) {
77
+ const stmt = db.prepare(INSERT_CARD_SQL);
78
+ stmt.run(card);
79
+ }
80
+ /**
81
+ * Insert multiple cards in batched transactions (500 per batch).
82
+ */
83
+ export function insertCards(db, cards) {
84
+ const stmt = db.prepare(INSERT_CARD_SQL);
85
+ for (let i = 0; i < cards.length; i += BATCH_SIZE) {
86
+ const batch = cards.slice(i, i + BATCH_SIZE);
87
+ const transaction = db.transaction((rows) => {
88
+ for (const row of rows) {
89
+ stmt.run(row);
90
+ }
91
+ });
92
+ transaction(batch);
93
+ }
94
+ }
95
+ /**
96
+ * Check if the cards table has any rows.
97
+ */
98
+ export function hasExistingData(db) {
99
+ const row = db.prepare('SELECT COUNT(*) as cnt FROM cards').get();
100
+ return row.cnt > 0;
101
+ }
102
+ /**
103
+ * Get all table names in the database (excluding internal SQLite tables).
104
+ */
105
+ export function getTableNames(db) {
106
+ const rows = db.prepare("SELECT name FROM sqlite_master WHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%' ORDER BY name").all();
107
+ return rows.map(r => r.name);
108
+ }
109
+ //# sourceMappingURL=db.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/data/db.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import type Database from 'better-sqlite3';
2
+ import { type CardRow } from './db.js';
3
+ export interface HearthstoneCard {
4
+ id: string;
5
+ dbfId: number;
6
+ name: string;
7
+ cost: number;
8
+ type: string;
9
+ set: string;
10
+ rarity: string;
11
+ attack?: number;
12
+ health?: number;
13
+ durability?: number;
14
+ armor?: number;
15
+ cardClass?: string;
16
+ text?: string;
17
+ flavor?: string;
18
+ artist?: string;
19
+ collectible?: boolean;
20
+ elite?: boolean;
21
+ mechanics?: string[];
22
+ race?: string;
23
+ spellSchool?: string;
24
+ }
25
+ /**
26
+ * Transform a HearthstoneJSON card into our CardRow schema.
27
+ */
28
+ export declare function transformCard(card: HearthstoneCard): CardRow;
29
+ /**
30
+ * Transform and batch-insert HearthstoneJSON cards into the database.
31
+ */
32
+ export declare function ingestCards(db: Database.Database, cards: HearthstoneCard[]): void;
33
+ /**
34
+ * Fetch cards from HearthstoneJSON.
35
+ *
36
+ * @param fetchFn - Optional fetch implementation (for testing/DI).
37
+ * Defaults to global fetch.
38
+ */
39
+ export declare function fetchCards(fetchFn?: typeof fetch): Promise<HearthstoneCard[]>;
40
+ //# sourceMappingURL=hearthstone.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hearthstone.d.ts","sourceRoot":"","sources":["../../src/data/hearthstone.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAe,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC;AAIpD,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AASD;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAwB5D;AAID;;GAEG;AACH,wBAAgB,WAAW,CACzB,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,KAAK,EAAE,eAAe,EAAE,GACvB,IAAI,CAGN;AAID;;;;;GAKG;AACH,wBAAsB,UAAU,CAC9B,OAAO,GAAE,OAAO,KAAa,GAC5B,OAAO,CAAC,eAAe,EAAE,CAAC,CAa5B"}
@@ -0,0 +1,58 @@
1
+ import { insertCards } from './db.js';
2
+ // --- Constants ---
3
+ const HEARTHSTONE_JSON_URL = 'https://api.hearthstonejson.com/v1/latest/enUS/cards.json';
4
+ // --- Transform ---
5
+ /**
6
+ * Transform a HearthstoneJSON card into our CardRow schema.
7
+ */
8
+ export function transformCard(card) {
9
+ return {
10
+ id: String(card.dbfId),
11
+ card_id: card.id,
12
+ name: card.name,
13
+ mana_cost: card.cost ?? null,
14
+ type: card.type ?? null,
15
+ card_set: card.set ?? null,
16
+ set_name: null,
17
+ player_class: card.cardClass ?? 'NEUTRAL',
18
+ rarity: card.rarity ?? null,
19
+ attack: card.attack ?? null,
20
+ health: card.health ?? null,
21
+ durability: card.durability ?? null,
22
+ armor: card.armor ?? null,
23
+ text: card.text ?? null,
24
+ flavor: card.flavor ?? null,
25
+ artist: card.artist ?? null,
26
+ collectible: card.collectible ? 1 : 0,
27
+ elite: card.elite ? 1 : 0,
28
+ race: card.race ?? null,
29
+ spell_school: card.spellSchool ?? null,
30
+ keywords: card.mechanics?.length ? JSON.stringify(card.mechanics) : null,
31
+ };
32
+ }
33
+ // --- Ingestion ---
34
+ /**
35
+ * Transform and batch-insert HearthstoneJSON cards into the database.
36
+ */
37
+ export function ingestCards(db, cards) {
38
+ const rows = cards.map(transformCard);
39
+ insertCards(db, rows);
40
+ }
41
+ // --- Fetch ---
42
+ /**
43
+ * Fetch cards from HearthstoneJSON.
44
+ *
45
+ * @param fetchFn - Optional fetch implementation (for testing/DI).
46
+ * Defaults to global fetch.
47
+ */
48
+ export async function fetchCards(fetchFn = fetch) {
49
+ console.error(`Fetching cards from ${HEARTHSTONE_JSON_URL}...`);
50
+ const response = await fetchFn(HEARTHSTONE_JSON_URL);
51
+ if (!response.ok) {
52
+ throw new Error(`Failed to fetch cards: ${response.status} ${response.statusText}`);
53
+ }
54
+ const cards = (await response.json());
55
+ console.error(`Fetched ${cards.length} cards from HearthstoneJSON`);
56
+ return cards;
57
+ }
58
+ //# sourceMappingURL=hearthstone.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hearthstone.js","sourceRoot":"","sources":["../../src/data/hearthstone.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAgB,MAAM,SAAS,CAAC;AA2BpD,oBAAoB;AAEpB,MAAM,oBAAoB,GACxB,2DAA2D,CAAC;AAE9D,oBAAoB;AAEpB;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,IAAqB;IACjD,OAAO;QACL,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC;QACtB,OAAO,EAAE,IAAI,CAAC,EAAE;QAChB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,SAAS,EAAE,IAAI,CAAC,IAAI,IAAI,IAAI;QAC5B,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,IAAI;QACvB,QAAQ,EAAE,IAAI,CAAC,GAAG,IAAI,IAAI;QAC1B,QAAQ,EAAE,IAAI;QACd,YAAY,EAAE,IAAI,CAAC,SAAS,IAAI,SAAS;QACzC,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,IAAI;QAC3B,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,IAAI;QAC3B,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,IAAI;QAC3B,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,IAAI;QACnC,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,IAAI;QACzB,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,IAAI;QACvB,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,IAAI;QAC3B,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,IAAI;QAC3B,WAAW,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACrC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACzB,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,IAAI;QACvB,YAAY,EAAE,IAAI,CAAC,WAAW,IAAI,IAAI;QACtC,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI;KACzE,CAAC;AACJ,CAAC;AAED,oBAAoB;AAEpB;;GAEG;AACH,MAAM,UAAU,WAAW,CACzB,EAAqB,EACrB,KAAwB;IAExB,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IACtC,WAAW,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;AACxB,CAAC;AAED,gBAAgB;AAEhB;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,UAAwB,KAAK;IAE7B,OAAO,CAAC,KAAK,CAAC,uBAAuB,oBAAoB,KAAK,CAAC,CAAC;IAChE,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAErD,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CACb,0BAA0B,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CACnE,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAsB,CAAC;IAC3D,OAAO,CAAC,KAAK,CAAC,WAAW,KAAK,CAAC,MAAM,6BAA6B,CAAC,CAAC;IACpE,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,36 @@
1
+ import type Database from 'better-sqlite3';
2
+ export interface LastUpdate {
3
+ hearthstone?: string;
4
+ }
5
+ export interface PipelineOptions {
6
+ force?: boolean;
7
+ dataDir?: string;
8
+ fetchFn?: typeof fetch;
9
+ }
10
+ /**
11
+ * Load last_update.json from the data directory.
12
+ * Returns an empty object if the file does not exist.
13
+ */
14
+ export declare function loadLastUpdate(dataDir?: string): LastUpdate;
15
+ /**
16
+ * Save last_update.json to the data directory.
17
+ */
18
+ export declare function saveLastUpdate(lastUpdate: LastUpdate, dataDir?: string): void;
19
+ /**
20
+ * Returns true if no hearthstone data has ever been fetched.
21
+ */
22
+ export declare function isFirstRun(lastUpdate: LastUpdate): boolean;
23
+ /**
24
+ * Run the data pipeline: fetch cards from HearthstoneJSON and ingest into DB.
25
+ *
26
+ * Logic:
27
+ * 1. Load last_update.json
28
+ * 2. If we have data and not forced refresh, skip fetch (return existing count)
29
+ * 3. Otherwise fetch cards, clear existing data, re-ingest
30
+ * 4. On failure: if first run and no data, throw. Otherwise graceful degradation
31
+ * 5. Save updated timestamp
32
+ *
33
+ * @returns Number of cards in the database after pipeline completes
34
+ */
35
+ export declare function runPipeline(db: Database.Database, options?: PipelineOptions): Promise<number>;
36
+ //# sourceMappingURL=pipeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/data/pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAS3C,MAAM,WAAW,UAAU;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACxB;AASD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,UAAU,CAU3D;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,UAAU,EAAE,UAAU,EACtB,OAAO,CAAC,EAAE,MAAM,GACf,IAAI,CASN;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAE1D;AAID;;;;;;;;;;;GAWG;AACH,wBAAsB,WAAW,CAC/B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,MAAM,CAAC,CAmDjB"}
@@ -0,0 +1,90 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ import { hasExistingData } from './db.js';
5
+ import { fetchCards, ingestCards } from './hearthstone.js';
6
+ // --- Constants ---
7
+ const DEFAULT_DATA_DIR = path.join(os.homedir(), '.hearthstone-oracle');
8
+ const LAST_UPDATE_FILENAME = 'last_update.json';
9
+ // --- Last update management ---
10
+ /**
11
+ * Load last_update.json from the data directory.
12
+ * Returns an empty object if the file does not exist.
13
+ */
14
+ export function loadLastUpdate(dataDir) {
15
+ const dir = dataDir ?? DEFAULT_DATA_DIR;
16
+ const filePath = path.join(dir, LAST_UPDATE_FILENAME);
17
+ try {
18
+ const content = fs.readFileSync(filePath, 'utf-8');
19
+ return JSON.parse(content);
20
+ }
21
+ catch {
22
+ return {};
23
+ }
24
+ }
25
+ /**
26
+ * Save last_update.json to the data directory.
27
+ */
28
+ export function saveLastUpdate(lastUpdate, dataDir) {
29
+ const dir = dataDir ?? DEFAULT_DATA_DIR;
30
+ if (!fs.existsSync(dir)) {
31
+ fs.mkdirSync(dir, { recursive: true });
32
+ }
33
+ const filePath = path.join(dir, LAST_UPDATE_FILENAME);
34
+ fs.writeFileSync(filePath, JSON.stringify(lastUpdate, null, 2), 'utf-8');
35
+ }
36
+ /**
37
+ * Returns true if no hearthstone data has ever been fetched.
38
+ */
39
+ export function isFirstRun(lastUpdate) {
40
+ return !lastUpdate.hearthstone;
41
+ }
42
+ // --- Pipeline orchestration ---
43
+ /**
44
+ * Run the data pipeline: fetch cards from HearthstoneJSON and ingest into DB.
45
+ *
46
+ * Logic:
47
+ * 1. Load last_update.json
48
+ * 2. If we have data and not forced refresh, skip fetch (return existing count)
49
+ * 3. Otherwise fetch cards, clear existing data, re-ingest
50
+ * 4. On failure: if first run and no data, throw. Otherwise graceful degradation
51
+ * 5. Save updated timestamp
52
+ *
53
+ * @returns Number of cards in the database after pipeline completes
54
+ */
55
+ export async function runPipeline(db, options) {
56
+ const dataDir = options?.dataDir ?? DEFAULT_DATA_DIR;
57
+ const force = options?.force ?? false;
58
+ const fetchFn = options?.fetchFn;
59
+ const lastUpdate = loadLastUpdate(dataDir);
60
+ const existingData = hasExistingData(db);
61
+ // If we have data and not forced, skip fetch
62
+ if (existingData && !force && !isFirstRun(lastUpdate)) {
63
+ const row = db.prepare('SELECT COUNT(*) as cnt FROM cards').get();
64
+ console.error(`Skipping fetch — ${row.cnt} cards already loaded (last update: ${lastUpdate.hearthstone})`);
65
+ return row.cnt;
66
+ }
67
+ try {
68
+ const cards = await fetchCards(fetchFn);
69
+ // Clear existing data before re-ingestion
70
+ db.exec('DELETE FROM cards');
71
+ ingestCards(db, cards);
72
+ // Save timestamp
73
+ lastUpdate.hearthstone = new Date().toISOString();
74
+ saveLastUpdate(lastUpdate, dataDir);
75
+ const row = db.prepare('SELECT COUNT(*) as cnt FROM cards').get();
76
+ console.error(`Pipeline complete: ${row.cnt} cards ingested`);
77
+ return row.cnt;
78
+ }
79
+ catch (error) {
80
+ if (!existingData) {
81
+ // First run with no cached data — fatal
82
+ throw error;
83
+ }
84
+ // Graceful degradation — use cached data
85
+ console.error(`Failed to fetch cards, using cached data: ${error instanceof Error ? error.message : String(error)}`);
86
+ const row = db.prepare('SELECT COUNT(*) as cnt FROM cards').get();
87
+ return row.cnt;
88
+ }
89
+ }
90
+ //# sourceMappingURL=pipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../src/data/pipeline.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAc3D,oBAAoB;AAEpB,MAAM,gBAAgB,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,qBAAqB,CAAC,CAAC;AACxE,MAAM,oBAAoB,GAAG,kBAAkB,CAAC;AAEhD,iCAAiC;AAEjC;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,OAAgB;IAC7C,MAAM,GAAG,GAAG,OAAO,IAAI,gBAAgB,CAAC;IACxC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,oBAAoB,CAAC,CAAC;IAEtD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACnD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAe,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAC5B,UAAsB,EACtB,OAAgB;IAEhB,MAAM,GAAG,GAAG,OAAO,IAAI,gBAAgB,CAAC;IAExC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzC,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,oBAAoB,CAAC,CAAC;IACtD,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;AAC3E,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,UAAsB;IAC/C,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC;AACjC,CAAC;AAED,iCAAiC;AAEjC;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,EAAqB,EACrB,OAAyB;IAEzB,MAAM,OAAO,GAAG,OAAO,EAAE,OAAO,IAAI,gBAAgB,CAAC;IACrD,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,IAAI,KAAK,CAAC;IACtC,MAAM,OAAO,GAAG,OAAO,EAAE,OAAO,CAAC;IAEjC,MAAM,UAAU,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IAC3C,MAAM,YAAY,GAAG,eAAe,CAAC,EAAE,CAAC,CAAC;IAEzC,6CAA6C;IAC7C,IAAI,YAAY,IAAI,CAAC,KAAK,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QACtD,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,mCAAmC,CAAC,CAAC,GAAG,EAE9D,CAAC;QACF,OAAO,CAAC,KAAK,CACX,oBAAoB,GAAG,CAAC,GAAG,uCAAuC,UAAU,CAAC,WAAW,GAAG,CAC5F,CAAC;QACF,OAAO,GAAG,CAAC,GAAG,CAAC;IACjB,CAAC;IAED,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,CAAC;QAExC,0CAA0C;QAC1C,EAAE,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;QAE7B,WAAW,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QAEvB,iBAAiB;QACjB,UAAU,CAAC,WAAW,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAClD,cAAc,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAEpC,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,mCAAmC,CAAC,CAAC,GAAG,EAE9D,CAAC;QACF,OAAO,CAAC,KAAK,CAAC,sBAAsB,GAAG,CAAC,GAAG,iBAAiB,CAAC,CAAC;QAC9D,OAAO,GAAG,CAAC,GAAG,CAAC;IACjB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,wCAAwC;YACxC,MAAM,KAAK,CAAC;QACd,CAAC;QAED,yCAAyC;QACzC,OAAO,CAAC,KAAK,CACX,6CAA6C,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACtG,CAAC;QACF,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,mCAAmC,CAAC,CAAC,GAAG,EAE9D,CAAC;QACF,OAAO,GAAG,CAAC,GAAG,CAAC;IACjB,CAAC;AACH,CAAC"}
@@ -0,0 +1,111 @@
1
+ -- Layer 1: Card Data (from HearthstoneJSON)
2
+ CREATE TABLE IF NOT EXISTS cards (
3
+ id TEXT PRIMARY KEY, -- dbfId as string
4
+ card_id TEXT, -- Blizzard card ID string (e.g. "CS2_029")
5
+ name TEXT NOT NULL,
6
+ mana_cost INTEGER,
7
+ type TEXT, -- MINION, SPELL, WEAPON, HERO, LOCATION
8
+ card_set TEXT,
9
+ set_name TEXT,
10
+ player_class TEXT, -- Class name or NEUTRAL
11
+ rarity TEXT, -- FREE, COMMON, RARE, EPIC, LEGENDARY
12
+ attack INTEGER,
13
+ health INTEGER,
14
+ durability INTEGER, -- For weapons
15
+ armor INTEGER, -- For hero cards
16
+ text TEXT, -- Card text (with markup)
17
+ flavor TEXT,
18
+ artist TEXT,
19
+ collectible INTEGER DEFAULT 0,
20
+ elite INTEGER DEFAULT 0, -- 1 if legendary
21
+ race TEXT, -- Minion tribe
22
+ spell_school TEXT,
23
+ keywords TEXT -- JSON array of keywords
24
+ );
25
+
26
+ -- FTS5 full-text search
27
+ CREATE VIRTUAL TABLE IF NOT EXISTS cards_fts USING fts5(
28
+ name, text,
29
+ content='cards', content_rowid='rowid'
30
+ );
31
+
32
+ -- FTS sync triggers
33
+ CREATE TRIGGER IF NOT EXISTS cards_ai AFTER INSERT ON cards BEGIN
34
+ INSERT INTO cards_fts(rowid, name, text) VALUES (new.rowid, new.name, new.text);
35
+ END;
36
+ CREATE TRIGGER IF NOT EXISTS cards_ad AFTER DELETE ON cards BEGIN
37
+ INSERT INTO cards_fts(cards_fts, rowid, name, text) VALUES('delete', old.rowid, old.name, old.text);
38
+ END;
39
+ CREATE TRIGGER IF NOT EXISTS cards_au AFTER UPDATE ON cards BEGIN
40
+ INSERT INTO cards_fts(cards_fts, rowid, name, text) VALUES('delete', old.rowid, old.name, old.text);
41
+ INSERT INTO cards_fts(rowid, name, text) VALUES (new.rowid, new.name, new.text);
42
+ END;
43
+
44
+ -- Keywords reference
45
+ CREATE TABLE IF NOT EXISTS keywords (
46
+ name TEXT PRIMARY KEY,
47
+ description TEXT NOT NULL,
48
+ related_keywords TEXT -- JSON array
49
+ );
50
+
51
+ -- Card sets
52
+ CREATE TABLE IF NOT EXISTS sets (
53
+ id TEXT PRIMARY KEY,
54
+ name TEXT NOT NULL,
55
+ release_date TEXT,
56
+ type TEXT,
57
+ is_standard INTEGER DEFAULT 0
58
+ );
59
+
60
+ -- Layer 2: Strategy Knowledge
61
+ CREATE TABLE IF NOT EXISTS archetypes (
62
+ name TEXT PRIMARY KEY,
63
+ description TEXT NOT NULL,
64
+ gameplan TEXT NOT NULL,
65
+ win_conditions TEXT NOT NULL, -- JSON array
66
+ strengths TEXT NOT NULL, -- JSON array
67
+ weaknesses TEXT NOT NULL, -- JSON array
68
+ example_decks TEXT -- JSON array
69
+ );
70
+
71
+ CREATE TABLE IF NOT EXISTS class_identities (
72
+ class TEXT PRIMARY KEY,
73
+ identity TEXT NOT NULL,
74
+ hero_power_name TEXT NOT NULL,
75
+ hero_power_cost INTEGER NOT NULL DEFAULT 2,
76
+ hero_power_effect TEXT NOT NULL,
77
+ hero_power_implications TEXT NOT NULL,
78
+ historical_archetypes TEXT NOT NULL, -- JSON array
79
+ strengths TEXT NOT NULL, -- JSON array
80
+ weaknesses TEXT NOT NULL, -- JSON array
81
+ early_game TEXT NOT NULL,
82
+ mid_game TEXT NOT NULL,
83
+ late_game TEXT NOT NULL
84
+ );
85
+
86
+ CREATE TABLE IF NOT EXISTS matchup_framework (
87
+ archetype_a TEXT NOT NULL,
88
+ archetype_b TEXT NOT NULL,
89
+ favoured TEXT NOT NULL,
90
+ reasoning TEXT NOT NULL,
91
+ key_tension TEXT NOT NULL,
92
+ archetype_a_priority TEXT NOT NULL,
93
+ archetype_b_priority TEXT NOT NULL,
94
+ PRIMARY KEY (archetype_a, archetype_b)
95
+ );
96
+
97
+ CREATE TABLE IF NOT EXISTS game_concepts (
98
+ name TEXT PRIMARY KEY,
99
+ category TEXT NOT NULL,
100
+ description TEXT NOT NULL,
101
+ hearthstone_application TEXT NOT NULL
102
+ );
103
+
104
+ -- Indexes
105
+ CREATE INDEX IF NOT EXISTS idx_cards_class ON cards(player_class);
106
+ CREATE INDEX IF NOT EXISTS idx_cards_cost ON cards(mana_cost);
107
+ CREATE INDEX IF NOT EXISTS idx_cards_type ON cards(type);
108
+ CREATE INDEX IF NOT EXISTS idx_cards_rarity ON cards(rarity);
109
+ CREATE INDEX IF NOT EXISTS idx_cards_set ON cards(card_set);
110
+ CREATE INDEX IF NOT EXISTS idx_cards_collectible ON cards(collectible);
111
+ CREATE INDEX IF NOT EXISTS idx_cards_race ON cards(race);
@@ -0,0 +1,3 @@
1
+ import type Database from 'better-sqlite3';
2
+ export declare function seedStrategyKnowledge(db: Database.Database): void;
3
+ //# sourceMappingURL=strategy-seed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strategy-seed.d.ts","sourceRoot":"","sources":["../../src/data/strategy-seed.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAoe3C,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,IAAI,CA6CjE"}