endfield-mcp
Version:
MCP Server for Arknights: Endfield (TypeScript / stdio + Streamable HTTP)
84 lines • 2.93 kB
JavaScript
/**
* Story reader core — store binding and catalog loading.
*
* This module is the shared spine of the story domain. It owns the JsonStore
* reference and the two eager-loaded catalog caches (`index.json`,
* `missions.json`). The chapter, scene, and search sub-modules import the
* lazy loaders exported here so the catalog is parsed exactly once across
* the whole domain.
*
* ## Loading strategy
*
* The catalog (index/missions) loads eagerly into memory at first access
* (~2.4MB). Individual conv files load on-demand by conv key (each tens
* of KB). This mirrors PRTS-MCP's ZipStore pattern and keeps memory
* bounded — we never hold all 97MB of dialogue in memory at once.
*
* ## Cache lifecycle
*
* This module only owns the catalog caches. `clearStoryCatalogCaches()`
* resets just those. The full-domain orchestrators (`bindStoryStore`,
* `clearStoryCaches`) live in `./story.ts` (the barrel facade) so they
* can clear sibling caches atomically without this module reaching into
* its siblings (which would muddy the layering).
*/
// ---------------------------------------------------------------------------
// Store reference + catalog cache (module-level singletons)
// ---------------------------------------------------------------------------
let _store = null;
let _entries = null;
let _missionNames = null;
/**
* Wire the JsonStore. Called once at startup by the barrel's
* `bindStoryStore` orchestrator, which also clears all sibling caches.
*/
export function setStoryStore(store) {
_store = store;
}
/** Reset only the catalog caches owned by this module. */
export function clearStoryCatalogCaches() {
_entries = null;
_missionNames = null;
}
export function hasStoryData() {
try {
return store().exists("index.json");
}
catch {
return false;
}
}
// ---------------------------------------------------------------------------
// Internal accessors — exported for sibling modules (same domain, same layer)
// ---------------------------------------------------------------------------
export function store() {
if (_store === null) {
throw new Error("Story reader used before bindStoryStore() — call it once at startup.");
}
return _store;
}
export function entries() {
if (_entries !== null)
return _entries;
const raw = store().readJson("index.json");
_entries = (raw.entries ?? []).map((e) => ({
key: e.k,
domain: e.d,
mission: e.m,
scene: e.s,
type: e.t,
actorCount: e.a,
lineCount: e.n,
preview: e.p,
tags: e.tags ?? [],
}));
return _entries;
}
export function missionNames() {
if (_missionNames !== null)
return _missionNames;
const raw = store().readJson("missions.json");
_missionNames = raw.missionNames ?? {};
return _missionNames;
}
//# sourceMappingURL=storyCore.js.map