Skip to content

Latest commit

 

History

History
509 lines (462 loc) · 51.6 KB

NODE_API.md

File metadata and controls

509 lines (462 loc) · 51.6 KB

Node programmatic API (tsrs --api)

JavaScript API

Install the beta with pnpm add -D [email protected]. require("tsrs") exports version, typescriptVersion and typescriptCommit.

The package also ships TypeScript 7's programmatic API, copied from microsoft/TypeScript packages/typescript under the same subpaths as the typescript 7 package. Both the compiler and SDK follow 3e9236361fa9 (TypeScript 7.1 beta); UPSTREAM.json records the copied SDK revision and adaptations.

subpath contents
tsrs/sync API with synchronous calls (MessagePack over the server's stdio)
tsrs/async API with promise-returning calls (JSON-RPC)
tsrs/ast (/is, /factory, /utils, /scanner, /visitor, /clone) AST types, guards, factory and traversal
tsrs/fs, /proto filesystem callbacks/request filesystems, protocol types
tsrs/path strongly typed path constructors and helpers
tsrs/vscode createTypeScriptModuleLoader and upstream editor SDK types; tsrs does not bundle the VS Code extension
import { API } from "tsrs/sync";

const api = new API({ cwd: process.cwd() });            // spawns `tsrs --api`; tsserverPath overrides the binary
const snapshot = api.createSnapshot({ openProjects: ["/abs/path/tsconfig.json"] });
const project = snapshot.getConfiguredProject("/abs/path/tsconfig.json");
console.log(project.program.getSemanticDiagnostics("/abs/path/src/index.ts"));
project.program.emit();                                  // follows the project's compiler options
api.close();

createSnapshot and updateSnapshot accept userPreferences for language-service behavior and prepareAutoImports with the file to index in the new snapshot. Request that preparation before getCompletionsAtPosition with includeSymbol: true when auto-imports may be needed: the API then returns symbols owned by the requested snapshot. An unprepared snapshot reports a client error for that request.

The sync and async APIs expose getSymbol(declaration), and each package exports a standalone getSymbol that uses the remote declaration's owning API. It returns the binder symbol from the exact cached source file; synthetic and configuration declarations have no cached file owner, and a released or replaced file cannot be looked up after its cached result is gone.

The API reports caseSensitivity as TypeScript's enum in its initialize response. Inferred projects omit configFileName; snapshot program and transpile requests accept raw compiler options, with relative option paths resolved from the API server's current directory. Document identifiers must contain a non-empty file name or URI.

The code is TypeScript's, with one intentional difference: if the server process dies, the async API rejects every pending and later request with an error starting API server connection lost: instead of leaving them pending forever (see npm packaging).

The module loader accepts full typescript/* specifiers and resolves them through the selected package manifest. Passing a tsrs installation's package.json loads tsrs/* from that installation. This is a package-side helper; tsrs does not supply the upstream VS Code extension's SDK event, API-pipe lifecycle, or in-place-upgrade restart handling.

The 0.20.0 beta uses the upstream beta entry-point names. It removes deprecated openProject, Project.rootFiles / .compilerOptions, and the Snapshot.internal formatting alias; use openProjects, project.parsedCommandLine, and project.languageService.formatNodeForInsertion. The server's project response no longer sends the removed duplicate fields. The API is served by the tsrs binary (tsrs --api), whose coverage of the protocol is still incomplete. The method inventory records requests from the pinned SDK suite, and the coverage matrix records implementation status and gaps.

Sync API generators are single-use across API.batch calls. Reusing an instance, even after its first batch completes, throws Cannot execute the same generator instance more than once, as in upstream TypeScript. TS_API_TIMING_DIR set to a nonempty directory enables timing collection even with collectTiming: false. Each sync or async API client writes one uniquely named JSON snapshot when closed, with client timing and either server timing or a server timing error. Repeated close() calls do not write another snapshot. An external socket server with timing disabled still yields the client's measured snapshot. After a synchronous filesystem callback throws, later channel requests rethrow that error rather than reading an out-of-sequence response; closing still writes the client snapshot. TypeScript consumers need esnext.disposable (or a newer lib) for the declarations' Symbol.dispose and Symbol.asyncDispose members. The async Snapshot, Program, and BuildOrchestrator use Symbol.asyncDispose, so await using waits for the returned cleanup Promise. The JS API runs on the package's minimum Node (16.20; checked on 16.20, 18, 20, 22 and 24). Where Node lacks the disposal symbols, call close() / dispose() explicitly.

Implementation and status

Port of the TypeScript 7 native API through the workspace Cargo.toml pin 3e9236361fa97a9e6c685f4833ac457ae2d8c50a: server side tsc/internal/api + tsc/internal/ipc, client side packages/typescript/src/{api,ast} exported as sync and async. The wire format, method names, params and response schemas follow the ported Go revision; tsrs does not define its own protocol or a one-shot CLI wrapper, and it is not compatible with TS5 createProgram objects.

Status: shipped (in the npm package since 0.3.0, docs/STATUS.md), with the gaps below. The matrix at the end is the source of truth: supported means a core Rust test against a real compiler session exercises it; partial means it is implemented and dispatched but only covered by the upstream Node suites, component-level checks, or has a documented divergence. Unimplemented methods return an explicit api: unsupported: ... error, never a fake success.

The recorded beta SDK inventory has 968/968 passing tests on both tsgo and tsrs, with successful responses for 170 of 177 methods. Methods absent from that suite need separate evidence; passing client assertions alone does not establish byte-level response parity. See API validation for the oracle comparison and historical runs.

Historical evidence from the initial API merge (pinned upstream client, node tools/node-api/run-upstream.mjs --binary target/debug/tsrs --label tsrs --suite upstream --filter <file>): test/sync/api.test.ts 339/339 (three consecutive runs; one earlier run had a single unreproduced failure), test/async/api.test.ts 348/348, sync/ast 111/111, sync/astnav + async/astnav 4/4 each, sync/api-generators 43/43, parity rss 2/2. These are suite results, not byte-level response parity; the parity lane's Go-oracle comparison is the authority for that.

Allocator-perturbation run (MALLOC_PERTURB_=165 TSRS_ARENA_POISON=1 cargo test --no-fail-fast -p <pkg> -- --skip released_snapshots --skip create_and_release --skip repeated_transpile --skip repeated_config; the four skipped tests measure RSS, which poison mode makes grow by design), counted per test binary at c2abd68: tsrs_api lib unit tests 27 (mostly the checker lane's), batch_test 1, callbackfs_test 2, config_test 7, module_resolution_test 2, program_test 9, requestfs_test 6, sourcefile_test 3, transpile_test 2 (tsrs_api total 59); tsrs_project 102 (2 ignored); tsrs_lsp 41. "197"/"202" in earlier reports are these three packages summed at different heads; a count restricted to core-owned tsrs_api integration binaries is 32. All pass, 0 fail.

Areas

The API was developed in parallel lanes; the names stay in use below and in the matrix.

area code
core crates/tsrs_api (except src/checker.rs, src/checker/**), crates/tsrs_cli/src/api.rs, --api dispatch in main.rs
runtime crates/tsrs_api_transport (MessagePack tuple protocol, JSON-RPC framing, sync/async conns, stdio/pipe transports, timing)
codec crates/tsrs_api_codec (AST binary encoder/decoder, string table, node index tables, source-file id)
checker crates/tsrs_api/src/checker.rs, crates/tsrs_api/src/checker/**, necessary checker exports
sdk npm/** (ported sync / async client, AST package, Node tests)
parity tools/node-api/**, the node-api-clients job in .github/workflows/ci.yml; the older standalone node-api.yml.bak workflow is disabled

Integration contract (Rust)

Session boundary (core, tsrs_api::handler)

pub enum Response { Json(String), Binary(Vec<u8>) }   // Go `any` result / `RawBinary`
pub struct ApiError { pub kind: ErrorKind, pub message: String } // Display == Go err.Error()
pub enum ErrorKind { InvalidRequest, ClientError, Unsupported, Internal }
pub trait Handler: Send + Sync {                         // Go ipc.Handler
    fn handle_request(&self, method: &str, params: &[u8]) -> ApiResult<Response>; // params = raw JSON bytes ("" = absent)
    fn handle_notification(&self, method: &str, params: &[u8]) -> ApiResult<()>;
}
pub trait ClientConn: Send + Sync {                      // Go ipc.Conn.Call (server -> client)
    fn call(&self, method: &str, params: &str) -> ApiResult<String>; // JSON text in/out
}
tsrs_api::Session::new(SessionOptions { cwd, default_library_path, fs, binary_responses, run_external_code }) -> Arc<Session>
session.set_connection(Arc<dyn ClientConn>); session.close();  // Session: Handler
  • binary_responses is true for the sync MessagePack protocol (Go UseBinaryResponses); source-file responses are then Response::Binary, otherwise {"data": base64} JSON.
  • echo and ping are handled before method lookup exactly like Go. Unknown methods are InvalidRequest (unknown API method "x").
  • Both protocols put errors on the wire as jsonrpc.CodeInternalError with err.to_string().
  • Handler panics are caught and returned as panic: ... errors (Go conn recovers too).

Runtime (tsrs_api_transport)

The transport does not depend on tsrs_api. It defines its own handler/conn traits with the same shape; crates/tsrs_cli/src/api.rs (core) adapts them. Entry points:

  • a sync conn running the msgpack [type, method, payload] tuple protocol over stdin/stdout or a pipe, supporting re-entrant call from inside a request (filesystem callbacks), with concurrent callbacks answering nested requests in stack order, and an async JSON-RPC conn with concurrent request dispatch and outstanding server->client calls;
  • RawBinary-equivalent results written as msgpack bin; getServerTiming/resetServerTiming handled in the conn as in Go ipc/timing.go;
  • deterministic shutdown: EOF on input ends run with Ok, malformed frames end it with an error without writing non-protocol bytes to stdout.

--api --pipe <path> creates a new private directory on Unix, with a socket at <path>/socket (0700 directory, 0600 socket); an existing path is an error. On Windows, <path> is a named pipe restricted to the current user. The spawned sync client gives its Windows pipe a random 128-bit suffix. LSP custom/initializeAPISession returns the same private pipe transport for an API session over the language server's project session; an API-client disconnect releases its resources, and an explicit LSP shutdown stops pending and connected sessions. Like pinned Go (3e9236361fa9), LSP EOF or context cancellation alone does not stop a pending API accept. Its listener remains until explicitly stopped or the process exits, and the private Unix socket directory survives process exit. See the LSP known gaps. getCurrentLanguageServerSnapshot flushes pending editor changes and applies the API client's opens, closes and program changes. Each client owns its open references and synthetic programs; closing it releases those resources. Standalone API sessions reject this method with Go's client error. Build orchestration is also available through this connection. As in Go, builds use the session host's filesystem and current directory, reading saved files rather than editor overlays.

Codec (tsrs_api_codec)

  • encode_source_file(&'static SourceFile) -> Result<(Vec<u8>, NodeIndexTable), EncodeError> (Go encoder.EncodeSourceFile) and set_source_file_id(&mut [u8], u64);
  • a per-file node index table (Go encoder.GetNodeIndexTable) mapping index <-> node so core can produce/resolve node handles "<index>.<kind>.<path>";
  • decode_nodes(&[u8]) (Go encoder.DecodeNodes) for printNode / formatNodeForInsertion and createSourceFile round trips.
  • Positions: the encoder itself writes UTF-16 positions (as the pinned Go encoder does); request positions from the client are UTF-16 and are converted with the file's position map.

Checker hook (checker lane, crates/tsrs_api/src/checker.rs)

#[derive(Default)] pub struct CheckerSnapshotState { .. }   // per-snapshot registries, dropped with the snapshot
pub(crate) fn handle(session: &Session, method: &str, params: &json::Value) -> Option<ApiResult<Response>>;

Core provides (stable API for the checker lane):

  • Session::snapshot_data(SnapshotID) -> ApiResult<Arc<SnapshotData>> (pins the snapshot);
  • Session::setup_checker(SnapshotID, &ProjectID) -> ApiResult<CheckerSetup> with fields sd, snapshot, project, program: &'static Program, checker: CheckerHandle; the setup keeps the snapshot alive and holds the API-lifetime checker until dropped (not reentrant);
  • SnapshotData::get_program, SnapshotData::checker_state;
  • session::parse_params, session::json_response; node handles through CheckerHost::node_handle / resolve_node_handle (core wraps the codec's node_handle / resolve_node_index).

handle receives every method whose methods::METHODS owner is Checker (119 methods). None means "not recognized" and becomes ApiError::unsupported.

Lifetimes

  • Snapshot handles map to Arc<SnapshotData>; release drops the registry reference and the project snapshot is dereferenced when the last in-flight request finishes.
  • Region pointers (P<T>, &'static Program) never cross the wire and are only reached through a live SnapshotData. Handles from another session or from a released snapshot fail with api: client error.

Coverage matrix

The pinned protocol has 177 Method constants (core 58, checker 119). Kept in sync with crates/tsrs_api/src/methods.rs.

Checker-scoped methods include getMergedSymbol, getSymbolOfNode, getSymbolOfDeclarationForChecker, and getParentOfSymbolForChecker. All resolve handles in the requested snapshot and project. The node/declaration and parent methods return null when no symbol exists; the existing core getSymbolOfDeclaration instead retrieves a binder symbol from a cached source file.

# method owner status evidence / gap
1 release core supported program_test (refcounted; stale/cross-session handles rejected)
2 releaseSourceFile core supported sourcefile_test
3 retainSourceFile core partial descriptor validation tested; positive path via upstream suites
4 getCachedSourceFile core partial via upstream suites only
5 getSymbolOfDeclaration core supported sourcefile_test and upstream sync/async declaration-symbol, cache, and stale-descriptor cases
6 batchRequests core supported batch_test (nesting error, pagination with continuation tokens); binary-in-batch base64 path untested
7 initialize core supported config_test
8 createSnapshot core supported program_test, module_resolution_test, requestfs_test (full/layer request filesystems, removedPaths)
9 updateSnapshot core supported program_test, requestfs_test (layers compacted over full, retained base, release of base, fileNotifications expanded to request-symlink aliases)
10 getCurrentLanguageServerSnapshot core supported session_apistate_test (port of Go's session_apistate_test.go: pending LSP changes, per-session open/close ownership, created programs released on close, standalone sessions rejected); tsrs_lsp server_apisession_test (custom/initializeAPISession, open-file overlays over the socket, shutdown)
11 createBuildOrchestrator core supported tsrs_cli api_test and tsrs_lsp server_apisession_test (shared in-process build backend; fresh orchestrator per call instead of Go's recheckAllProjects reuse)
12 disposeBuildOrchestrator core supported tsrs_cli api_test
13 build core supported tsrs_cli api_test (references, up-to-date rebuild)
14 buildReferences core supported tsrs_cli api_test
15 cleanBuild core supported tsrs_cli api_test
16 cleanReferences core partial ported Go clean(onlyReferences); no dedicated test
17 createModuleResolver core supported module_resolution_test (default, static entries, callback; custom resolutions skip disk-layout import diagnostics under rewriteRelativeImportExtensions)
18 releaseModuleResolver core supported module_resolution_test
19 resolveModuleName core supported module_resolution_test (standalone, snapshot-scoped callback); inProgressSnapshot path exercised only via callbacks during program build
20 parseCommandLine core supported config_test
21 readConfigFile core supported config_test
22 parseJsonConfigFileContent core supported config_test
23 parseConfigFile core supported config_test
24 createSourceFile core supported sourcefile_test, upstream sync suite
25 createSourceFileFromFile core partial dispatches; exercised by upstream suites
26 transpileModule core supported transpile_test
27 transpileModuleFromFile core supported transpile_test
28 transpileDeclaration core supported transpile_test (upstream api.test.ts expectations); observed: no TS9007 for export function g() { return 1; } under isolatedDeclarations, unverified against tsgo
29 transpileDeclarationFromFile core supported transpile_test
30 getDefaultProjectForFile core supported program_test
31 getSymbolAtPosition checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
32 getSymbolsAtPositions checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
33 getSymbolAtLocation checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
34 getSymbolsAtLocations checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
35 getSymbolOfSourceFile checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
36 getSymbolsOfSourceFiles checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
37 getTypeOfSymbol checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
38 getTypesOfSymbols checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
39 getDeclaredTypeOfSymbol checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
40 getNonMissingTypeOfSymbol checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
41 getSourceFile core supported sourcefile_test (binary + base64, decoded by tsrs_api_codec), upstream sync/async suites
42 getSourceFileNames core supported program_test
43 getSourceFileMetadata core supported program_test
44 getModeForUsageLocation core partial via upstream sync suite (Program resolved modules)
45 getModeForResolutionAtIndex core partial via upstream sync suite
46 getResolvedModule core partial via upstream sync suite
47 getResolvedModuleFromModuleSpecifier core partial via upstream sync suite
48 getResolvedTypeReferenceDirective core partial via upstream sync suite
49 getResolvedTypeReferenceDirectiveFromTypeReferenceDirective core partial via upstream sync suite
50 getConfigFileNames core supported program_test (extends chain, synthetic null)
51 getConfigSourceFile core partial dispatches (root + extended configs); exercised only by upstream suites
52 resolveName checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
53 getSymbolsInScope checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
54 getSignaturesOfType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
55 getResolvedSignature checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
56 getTypeAtLocation checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
57 getTypeAtLocations checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
58 getTypeAtPosition checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
59 getTypesAtPositions checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
60 getParentOfSymbol checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
61 getMembersOfSymbol checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
62 getExportsOfSymbol checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
63 getExportSymbolOfSymbol checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
64 getSymbolOfType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
65 getTargetOfType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
66 getFreshTypeOfType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
67 getRegularTypeOfType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
68 getTypesOfType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
69 getTypeParametersOfType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
70 getOuterTypeParametersOfType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
71 getLocalTypeParametersOfType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
72 getThisTypeOfType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
73 getAliasTypeArgumentsOfType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
74 getAliasSymbolOfType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
75 getObjectTypeOfType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
76 getIndexTypeOfType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
77 getCheckTypeOfType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
78 getExtendsTypeOfType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
79 getBaseTypeOfType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
80 getConstraintOfType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
81 getTypeParameterOfMappedType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
82 getConstraintTypeOfMappedType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
83 getNameTypeOfMappedType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
84 getTemplateTypeOfMappedType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
85 getTypeParametersOfSignature checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
86 getParametersOfSignature checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
87 getThisParameterOfSignature checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
88 getTargetOfSignature checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
89 getContextualType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
90 getContextualTypeForArgument checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
91 getAwaitedType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
92 getBaseTypeOfLiteralType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
93 getNonNullableType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
94 getTypeFromTypeNode checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
95 getWidenedType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
96 getParameterType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
97 getTypeParameterAtPosition checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
98 isArrayLikeType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
99 isTypeAssignableTo checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
100 getShorthandAssignmentValueSymbol checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
101 getTypeOfSymbolAtLocation checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
102 typeToTypeNode checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
103 signatureToSignatureDeclaration checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
104 typeToString checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
105 isContextSensitive checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
106 getReturnTypeOfSignature checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
107 getRestTypeOfSignature checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
108 getTypePredicateOfSignature checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
109 getBaseTypes checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
110 getPropertiesOfType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
111 getApparentPropertiesOfType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
112 getApparentType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
113 getReducedType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
114 getPropertyOfType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
115 getTypeOfPropertyOfType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
116 getIndexInfoOfType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
117 getIndexInfosOfType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
118 getConstraintOfTypeParameter checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
119 getDefaultFromTypeParameter checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
120 getBaseConstraintOfType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
121 getTypeArguments checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
122 getImportAdderEdits checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
123 getTrueTypeOfConditionalType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
124 getFalseTypeOfConditionalType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
125 getConstantValue checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
126 getSignatureFromDeclaration checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
127 getExportSpecifierLocalTargetSymbol checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
128 getAliasedSymbol checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
129 getImmediateAliasedSymbol checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
130 getTargetSymbol checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
131 getMergedSymbol checker partial checker lane: source 29 real-program Rust test and upstream sync/async API cases; no pinned-Go golden or byte-level response comparison
132 getSymbolOfNode checker partial checker lane: source 29 real-program Rust test and upstream sync/async API cases; no pinned-Go golden or byte-level response comparison
133 getSymbolOfDeclarationForChecker checker partial checker lane: source 29 real-program Rust test and upstream sync/async API cases; no pinned-Go golden or byte-level response comparison
134 getParentOfSymbolForChecker checker partial checker lane: source 29 real-program Rust test and upstream sync/async API cases; no pinned-Go golden or byte-level response comparison
135 getExportSymbolOfSymbolForChecker checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
136 getFullyQualifiedName checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
137 getExportsOfModule checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
138 getMemberInModuleExports checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
139 getJsDocTags checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
140 getDocumentationComment checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
141 isArrayType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
142 isReadonlySymbol checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
143 getReferencesToSymbolInFile checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
144 getReferencedSymbolsForNode checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
145 getSignatureUsages checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
146 getCompletionsAtPosition checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
147 getSyntacticDiagnostics core supported program_test
148 getBindDiagnostics core supported program_test
149 getSemanticDiagnostics core supported program_test
150 getSuggestionDiagnostics core partial dispatches; no fixture asserting a specific suggestion yet
151 getDeclarationDiagnostics core supported program_test
152 getProgramDiagnostics core supported program_test (empty case)
153 getGlobalDiagnostics core supported program_test (empty case)
154 getConfigFileParsingDiagnostics core supported program_test (client-supplied diagnostics round trip)
155 printNode core partial upstream sync printNode/printFile tests pass; recovered printer panics use Go's Kind… names (printing.rs unit test)
156 formatNodeForInsertion core partial upstream sync formatNodeForInsertion tests pass
157 emit core supported program_test (write-through, no TSRS_EMIT), requestfs_test (full filesystem returns emittedFilesContents, no disk write)
158 emitToString core supported program_test
159 getJavaScriptEmit core supported program_test
160 getDeclarationEmit core supported program_test
161 getAnyType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
162 getStringType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
163 getNumberType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
164 getBooleanType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
165 getVoidType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
166 getUndefinedType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
167 getNullType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
168 getNeverType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
169 getUnknownType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
170 getBigIntType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
171 getESSymbolType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
172 getNonPrimitiveType checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
173 getWellKnownSymbols checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
174 getWellKnownSignatures checker partial checker lane: real-snapshot unit tests + pinned-Go golden subset; dispatched through core Session; not byte-compared end-to-end
175 startCPUProfile core not implemented
176 stopCPUProfile core not implemented
177 saveHeapProfile core not implemented

Request-filesystem regression fixtures

cargo test -p tsrs_api --test integration requestfs_differential runs the production request filesystem against 11 pinned-Go scenarios (20 updates, 142 filesystem queries and every update's change summary), plus layering over real files and symlinks. No Cargo feature is needed. Fixtures and their generator are in crates/tsrs_api/tests/fixtures/: place requestfs_gen.go.txt in the pinned tsc/internal/api/requestfilesystem package and run GOWORK=off SCENARIOS=<requestfs_scenarios.json> SCENARIOS_OUT=<out> go test -run ZZDump ./internal/api/requestfilesystem/.

Known gaps (core lane, tracked for follow-up)

  • Request filesystems are ported (crates/tsrs_api/src/requestfs.rs) and enter the project snapshot as Go's layered, rebasable file system: overlays are rebased under them and client fileNotifications are expanded to request-symlink aliases (Go ExpandFileChanges; requestfs_test). Callback filesystems (--callbacks, Go callbackfs.go) are ported in crates/tsrs_api_transport/src/callbackfs.rs; like Go, invalid callback responses panic and become request errors (a panic on a worker thread can poison shared caches; not yet hardened).
  • API builds differ from tsrs -b in how they run, not in what they write: each build call uses a fresh CLI orchestrator (the state Go's recheckAllProjects leaves; reuse across builds comes from the .tsbuildinfo files on disk, as for any rebuild), runs one project at a time on the request thread, and builds programs single-threaded, so --builders and checker parallelism do not speed up API builds and singleThreaded is forced on (it does not appear in outputs or build info). The output files, .tsbuildinfo included, and the exit status are byte-identical to tsrs -b on the same graph (api_build_outputs_match_cli_build), and up-to-date rebuilds build nothing (api_test). This is what makes per-build memory freeable; it trades build throughput for bounded memory.
  • Build orchestration runs the tsc -b orchestrator in-process through tsrs_api::build::BuildBackend. Every API session installs this backend by default, including library and LSP-attached sessions; embedding hosts can replace it. Clean uses the last build's graph like Go; clean existence checks use the uncached filesystem.
  • Profiling (startCPUProfile, stopCPUProfile, saveHeapProfile) is not implemented (no pprof equivalent).
  • Node index tables are cached per live source file keyed by the file's address (lookups never assign the file's lazy node id) and evicted when the file's region is freed; Go stores them on the file itself.
  • Re-entrancy attribution: snapshot builds read files through a ScopedFs that enters the creating request's transport RequestScope, so client callbacks from compiler worker threads are attributed to that request; API builds run their tasks on the request thread. Remaining divergence: a request whose own slow client callback holds a contended resource cannot be told apart from callback re-entry by attribution alone, so a waiter can still get the re-entrancy error after the transport's configurable grace period where Go would wait (or hang). Reads made later by a retained snapshot count against its finished request.
  • Re-entrancy: nested requests from client callbacks are served; the build orchestrator lock uses the transport's lock_for_request (bounded error instead of deadlock); the checker lane gates its API checker lease the same way. Request params are validated with Go's strict JSON (unpaired surrogates, duplicate members) before decoding; null params decode to the zero-value struct. Field-type mismatches have Go's invalid-request class and failed to unmarshal *api.<Type> prefix, but not Go's exact jsontext wording. Every method's top-level params fields (generated from pinned proto.go into paramfields.rs) are checked in Go's field order before any lookup, for core and checker methods alike (predecode.rs); unknown keys are ignored. Integer fields are checked against the raw number lexeme and the Go type's range (1e3/1.0 are invalid syntax; int32/uint32/int/uint64 bounds), array element kinds are checked, and null elements of []string-like arrays are zero values, as in Go. Remaining wording differences (runtime matrix on f70371e): Go includes the number literal and : invalid syntax, reports the first invalid field in document order (this reports struct order), qualifies element types ([]api.BatchRequest) and prints alias names (core.ModuleKind). Nested api-package structs (program options, symbol references, import adder actions, module-resolution specs, diagnostics, file notifications) are checked the same way before any lookup, and project.SyntheticProjectID values go through Go's decoder rules (null and non-synthetic text are decode errors). Integer IDs are read from their own literal (by position in the parsed params tree), so values above 2^53 are exact at any depth; batch items keep their raw params bytes (Go json.Value). Structs from other Go packages are decoded from the parsed value with Go's rules, checked against runtime's c246 session review: integer fields of core.CompilerOptions (int32 enums, *int) and core.BuildOptions.builders need plain integer syntax (1e1 is invalid). target, module and jsx are int32 newtypes (as in Go), so any number Go accepts is kept, echoed and compiled with Go's semantics: switch defaults and comparisons see the raw value (e.g. target:12345 transforms maximally, module:-1 emits CommonJS, jsx:12345 keeps JSX without the "--jsx not set" error); checked against runtime-f2-review's cases. moduleResolution is an int32 newtype too: a number with no named kind is kept (createPrograms, createBuildOrchestrator, builds of import-free projects and their outputs/cleans work as in Go), and resolving a module with it reaches the ported resolver's Unexpected moduleResolution panic at the same point as pinned Go (resolver.go ResolveModuleName); Go's server crashes there, tsrs converts that panic to the stable client error unsupported moduleResolution value N (not a ModuleResolutionKind) and stays usable (orchestrator map lock poisoning is tolerated). Unknown moduleDetection/newLine numbers are echoed and compile like Go's fallback (runtime f552 review). Go *int options (maxNodeModuleJsDepth, builders, checkers) take any int64, exact from the request literal and echoed exactly (json::Value::Integer); above int64 is out of range. checkers is clamped like Go (max(min(n, files, 256), 1); tsrs used to turn a negative count into the file count). Prior limitation, not API-specific: a tsconfig float far outside int64 (9.3e18, 1e19) for these options saturates in Rust; Go's float-to-int conversion is architecture dependent there (amd64 gives MinInt64, arm64 saturates). Malformed tristates and unknown options are ignored, as in Go. Request filesystems are type-checked before any lookup (kind, files, directories, symlinks.target/host, removedPaths), with null values and elements as zero values. Pinned Go crashes on a relative, empty or null project reference path and on a missing referenced project; tsrs instead returns a client error for non-absolute paths and reports a missing project as a File '…' not found program diagnostic. Error wording is encoding/json v1 style.
  • Response shapes from paired runs (parity f703): updateSnapshot omits an empty changes (json/v2 omitempty); cleanBuild keeps Go's per-orchestrator existence answers until the next build, so a clean after a clean lists the project's outputs again.
  • Unwinding builds (a panic while building a program that the API answers as an error, today the module resolver's Unexpected moduleResolution): a snapshot clone rolls back the parse-cache references its programs took (journal in ProjectCollectionBuilder), the file loader frees its project reference mapper, createSnapshot releases its root reference and an API build frees its fresh orchestrator, so repeated failures stay flat (codec d9be067 probe; failed_program_builds_release_their_files, failed_builds_free_their_orchestrator). Other state an arbitrary panic could leave is not audited.
  • Memory: snapshots free their programs, checkers, emit allocations and a full build's shared data (processed files, project-reference mapper, loader and dts-faking resolution hosts) with the build's base region (createSnapshot+release cycles stay flat; tests/memory_test.rs). The first attempt at freeing the mapper exposed a pre-existing use-after-free in tsrs_project (update_program read the replaced program after dropping its owner); fixed, with a perturbation/ASAN-validated regression test (inferred_project_rebuild_frees_safely). Transpile frees its one-file program including the compiler checker pool's checkers (previously leaked; ~5 KiB/call of allocator retention remains), and config requests run in scratch regions. API builds allocate in per-task regions (one builder, tasks run on the request thread, programs single-threaded and freed after each project) and each build handle keeps only its last build (for cleans); 120 rebuilds retain no more than one (memory_tests in crates/tsrs_cli/src/api.rs; ~19 MiB per rebuild before). File texts parsed inside a freeable region are copied into it and unregistered with it instead of leaked. Still retained: the request thread's arena use outside these paths. Unregistered source texts free their memory but not their registry slot; slots are never reused, so a very long-lived session that parses more than 2^20 files in freeable regions falls back to the slower text lookup path.