/** * Per-process schema cache for the graph-mcp proxy. * * Caches { labels, relationshipTypes } from the connected Neo4j instance so * the cypher validator can look up tokens without a round-trip per call. * * Refresh triggers: * - boot — async refresh kicked off by start(); validator fails * OPEN until the first refresh resolves. * - interval — every 60s (configurable) while the process is alive. * - stale-miss — an unknown token with a near match (edit distance ≤ 2) * triggers a debounced refresh, catching fresh schema * additions without requiring operators to wait out the * interval. * * Failure posture: * - A refresh that throws preserves the last good snapshot and leaves * ready() as it was. `[schema-cache] refresh failure ...` is logged * loudly so operators see persistent Neo4j unreachability. * - A boot refresh that fails leaves ready()=false and the snapshot empty. * The validator's own fail-open branch handles this (empty snapshot → * ok=true, no rejections), so the graph-mcp proxy keeps working against * a still-reachable Neo4j instance that's just slow to respond to the * initial `db.labels()` call. */ import type { SchemaSnapshot, UnknownToken } from "./cypher-validate.js"; export interface SchemaFetcher { labels(): Promise; relationshipTypes(): Promise; } export interface SchemaCacheOptions { /** Interval between automatic refreshes. 0 disables the timer (tests). */ refreshIntervalMs?: number; /** Minimum ms between stale-miss-triggered refreshes. Default 5000. */ staleMissDebounceMs?: number; /** Emit a log line. Default: stderr. Tests inject to capture. */ emit?: (line: string) => void; } export declare class SchemaCache { private readonly fetcher; private readonly options; private _labels; private _rels; private _ready; private _timer; private _refreshInFlight; private _lastStaleMissAt; constructor(fetcher: SchemaFetcher, options?: SchemaCacheOptions); snapshot(): SchemaSnapshot; ready(): boolean; start(): Promise; stop(): void; /** * Refresh the cache. Single-flight: concurrent calls await the same in-flight * promise rather than triggering overlapping Neo4j reads. Returns true on * successful update, false on failure (last good snapshot preserved). */ refresh(reason: string): Promise; /** * If the validator returned unknown tokens whose nearest known token is * within STALE_MISS_DISTANCE_CEILING edits, trigger an out-of-band refresh * (debounced). The heuristic: a near-miss is likely a fresh schema * change (renamed / newly added label/edge), where a far-miss is almost * certainly a typo and a refresh would not change the outcome. Returns * true iff a refresh was actually run this call. */ maybeRebuildOnStaleMiss(unknown: UnknownToken[]): Promise; private emit; } export declare function getSharedDriver(uri: string, user: string, password: string): Promise; /** * Build a SchemaFetcher over the shared neo4j-driver Driver. Each call * opens a fresh session (cheap, sub-ms) and closes it in finally. */ export declare function neo4jSchemaFetcher(uri: string, user: string, password: string): Promise; //# sourceMappingURL=schema-cache.d.ts.map