Most projects kept at once.
Eviction count and the most recent eviction.
Limits in effect.
Number of cached projects.
Returns the cached Project for tsConfigPath; on a miss loads it in
batches of files with one event-loop turn after each, then builds the
program and, after another turn, the type checker. The server keeps
reading requests in between, and an aborted signal leaves the load at
the next turn.
Calls for the same tsconfig while it loads share the load. It stops only
when every caller has left; a stopped load is not cached. A hit behaves
like get().
Cached projects, least recently used first.
Returns the cached Project for tsConfigPath, building it on a miss in
one synchronous piece. Tools use acquire().
Warnings about the loaded project for tsConfigPath: today the library
imports that do not resolve (missing or incomplete node_modules). Checked
once per load, on the first call, and kept until the project is reloaded;
empty when the project is not cached.
LRU cache of ts-morph Projects keyed by tsconfig path. Keeps
Projectinstances alive across tool calls, so repeated investigation calls on the same workspace do not re-parse the whole program each time (seconds on a large Angular app).Remarks
Same idea as
SharedProjectManagerin core (one long-lived Project instead of one per call), but keyed per tsconfig because the MCP server can serve several workspaces in one session. An entry is rebuilt when the tsconfig mtime changes. Source files are kept current file by file: the mtime of every program file is recorded at load and compared on eachget()(about 6 ms for 3939 files); changed files are re-read, deleted ones removed. The type checker then rebuilds lazily on the next query. Files that were not in the program at load (new files) need a tsconfig touch or a server restart.Two limits bound the cache:
maxEntriesprojects, andmaxHeapMbof V8 heap, checked on everyget()because a project keeps growing after the load as the type checker resolves more of it, and on a miss also before the load. Over either limit the least recently used project is evicted; the project being requested never is.The heap is the signal, not the RSS: dropping a Project frees its heap (the TypeScript program, checker and ts-morph wrappers hang off it and nothing else keeps them), but V8 does not reliably unmap the pages, so the RSS can stay at its peak after an eviction and an RSS limit would then evict everything on every call. Freed pages are reused by the next load. When node runs with
--expose-gcthe cache collects once the raw heap reading passes the limit and after each eviction, so garbage does not count against the cache; otherwise it subtracts the heap growth recorded when the evicted project was loaded, and garbage makes it evict earlier.