From 9cd17d0934ed5a2d41c6ea82183326540eea3524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=97=D0=B0=D0=B8=D0=B4=20=D0=9E=D0=BC=D0=B0=D1=80=20?= =?UTF-8?q?=D0=9C=D0=B5=D0=B4=D1=85=D0=B0=D1=82?= Date: Sun, 23 Aug 2026 05:40:08 +0500 Subject: [PATCH] Auto-re-parse game data when parser version changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first-boot parse only ran on an empty DB, so deploying a parser fix left existing rows stale (e.g. enchanted ingredients still flat T6). Add PARSER_VERSION, stored in meta on parse; on boot the server re-parses when the stored version differs, auto-healing existing data after a deploy — no manual "Re-download game data" needed. Bumped to v2 for the enchanted-ingredient id fix. Co-Authored-By: Claude Fable 5 --- server/src/gamedata/parse.ts | 9 +++++++++ server/src/index.ts | 35 ++++++++++++++++++++++------------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/server/src/gamedata/parse.ts b/server/src/gamedata/parse.ts index ccb86d7..1cda9d6 100644 --- a/server/src/gamedata/parse.ts +++ b/server/src/gamedata/parse.ts @@ -8,6 +8,14 @@ import { gamedataFilePath } from './download.js'; * attributes appear as "@name" keys, and any child element may be an object or an array). */ +/** + * Bump whenever the parsing logic changes in a way that alters stored items/recipes. + * The server re-parses on boot when the DB's stored version differs, so a deploy that + * fixes parsing auto-heals existing data without a manual "re-download". + * v2: enchanted resource/ingredient ids carry @ (e.g. T6_SHOES_PLATE_SET1@4) + */ +export const PARSER_VERSION = 2; + type Raw = Record; function asArray(v: T | T[] | undefined | null): T[] { @@ -246,6 +254,7 @@ export async function parseGameData(): Promise { }; await setMeta('gamedata_parsed_at', new Date().toISOString()); await setMeta('gamedata_stats', JSON.stringify(stats)); + await setMeta('gamedata_parser_version', String(PARSER_VERSION)); cacheClear(); // items/recipes changed → drop every cached read return stats; } diff --git a/server/src/index.ts b/server/src/index.ts index 3e9e89c..14fefd0 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -26,22 +26,31 @@ const app = Fastify({ logger: { level: 'info' } }); await initDb(); // create schema on startup -// First-boot convenience: if no game data is loaded yet, download + parse it in -// the background so a fresh deployment is usable without a manual step. +// On boot: (re)parse game data if the DB is empty OR was parsed by an older parser +// version — so a deploy that fixes parsing auto-heals existing data. Runs in the +// background so it doesn't block startup. void (async () => { try { - const { get } = await import('./db/index.js'); - const row = await get<{ n: number }>('SELECT COUNT(*) AS n FROM items'); - if ((row?.n ?? 0) === 0) { - app.log.info('No game data found — downloading and parsing (first boot)...'); - const { downloadGameData } = await import('./gamedata/download.js'); - const { parseGameData } = await import('./gamedata/parse.js'); - await downloadGameData(false); - const stats = await parseGameData(); - app.log.info(`Game data loaded: ${stats.items} items, ${stats.recipes} recipes.`); - } + const { get, getMeta } = await import('./db/index.js'); + const { PARSER_VERSION, parseGameData } = await import('./gamedata/parse.js'); + const { downloadGameData } = await import('./gamedata/download.js'); + + const count = (await get<{ n: number }>('SELECT COUNT(*) AS n FROM items'))?.n ?? 0; + const storedVersion = Number((await getMeta('gamedata_parser_version')) ?? 0); + const empty = count === 0; + const stale = storedVersion !== PARSER_VERSION; + if (!empty && !stale) return; + + app.log.info( + empty + ? 'No game data found — downloading and parsing (first boot)...' + : `Game data parsed by an older version (v${storedVersion} → v${PARSER_VERSION}) — re-parsing...`, + ); + await downloadGameData(false); // uses cached files if already present + const stats = await parseGameData(); + app.log.info(`Game data loaded: ${stats.items} items, ${stats.recipes} recipes.`); } catch (e) { - app.log.error(`First-boot game data load failed: ${e instanceof Error ? e.message : e}`); + app.log.error(`Startup game data load failed: ${e instanceof Error ? e.message : e}`); } })();