Auto-re-parse game data when parser version changes
All checks were successful
Deploy / deploy (push) Successful in 2m13s

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 <noreply@anthropic.com>
This commit is contained in:
Заид Омар Медхат 2026-08-23 05:40:08 +05:00
parent 7ed585d6a9
commit 9cd17d0934
2 changed files with 31 additions and 13 deletions

View file

@ -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). * 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 @<enchant> (e.g. T6_SHOES_PLATE_SET1@4)
*/
export const PARSER_VERSION = 2;
type Raw = Record<string, any>; type Raw = Record<string, any>;
function asArray<T>(v: T | T[] | undefined | null): T[] { function asArray<T>(v: T | T[] | undefined | null): T[] {
@ -246,6 +254,7 @@ export async function parseGameData(): Promise<ParseStats> {
}; };
await setMeta('gamedata_parsed_at', new Date().toISOString()); await setMeta('gamedata_parsed_at', new Date().toISOString());
await setMeta('gamedata_stats', JSON.stringify(stats)); await setMeta('gamedata_stats', JSON.stringify(stats));
await setMeta('gamedata_parser_version', String(PARSER_VERSION));
cacheClear(); // items/recipes changed → drop every cached read cacheClear(); // items/recipes changed → drop every cached read
return stats; return stats;
} }

View file

@ -26,22 +26,31 @@ const app = Fastify({ logger: { level: 'info' } });
await initDb(); // create schema on startup await initDb(); // create schema on startup
// First-boot convenience: if no game data is loaded yet, download + parse it in // On boot: (re)parse game data if the DB is empty OR was parsed by an older parser
// the background so a fresh deployment is usable without a manual step. // version — so a deploy that fixes parsing auto-heals existing data. Runs in the
// background so it doesn't block startup.
void (async () => { void (async () => {
try { try {
const { get } = await import('./db/index.js'); const { get, getMeta } = await import('./db/index.js');
const row = await get<{ n: number }>('SELECT COUNT(*) AS n FROM items'); const { PARSER_VERSION, parseGameData } = await import('./gamedata/parse.js');
if ((row?.n ?? 0) === 0) { const { downloadGameData } = await import('./gamedata/download.js');
app.log.info('No game data found — downloading and parsing (first boot)...');
const { downloadGameData } = await import('./gamedata/download.js'); const count = (await get<{ n: number }>('SELECT COUNT(*) AS n FROM items'))?.n ?? 0;
const { parseGameData } = await import('./gamedata/parse.js'); const storedVersion = Number((await getMeta('gamedata_parser_version')) ?? 0);
await downloadGameData(false); const empty = count === 0;
const stats = await parseGameData(); const stale = storedVersion !== PARSER_VERSION;
app.log.info(`Game data loaded: ${stats.items} items, ${stats.recipes} recipes.`); 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) { } 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}`);
} }
})(); })();