Skip to content

API: coarse-grained checker queries for tools that extract a whole API surface (getSymbolDocumentations, getMembersOfType)Β #65051

Open

Description

πŸ” Search Terms

typescript/sync api round trips, getPropertiesOfType per member, bulk symbol documentation, getMembersOfType, api extractor performance, jsii typescript 7, out-of-process checker queries

βœ… Viability Checklist

⭐ Suggestion

I am an independent contributor experimenting with running jsii (the AWS CDK's multi-language binding generator) on the TypeScript 7 programmatic API. This proposal comes out of that work, and I think it applies to other tools of the same kind.

Tools that extract an API surface from a whole program (jsii, TypeDoc, API Extractor, documentation generators) visit every exported type and, for every member, ask the checker for the member's properties, its declared type, the type that declares it, and its JSDoc. On the out-of-process API each of those questions is one round trip, so the traversal is bounded by the number of round trips rather than by checker work. jsii spends about 33 s traversing aws-cdk-lib and issues about 700k requests in that time; the server spends only about 6 s of it computing types, and the rest goes to waiting for round trips and handling messages.

I propose two additional methods. One returns the JSDoc of many symbols at once. The other returns, for a single type, everything such a traversal needs about its members (symbol, declaring type, kind, JSDoc, declared type) in one round trip. On aws-cdk-lib this brings the request count from 700k down to 300k and the traversal from 33 s to 28 s. Existing methods are unchanged and the new ones return the same handles, so callers can migrate incrementally.

Both methods run against the existing API checker and return the same symbol and type handles the fine-grained methods return; callers rely on handle identity to correlate types across calls, so that is preserved.

getSymbolDocumentations(symbols[]) returns, per symbol, exactly what getJsDocTags and getDocumentationComment return. One request per type's member list instead of two per member.

getMembersOfType(type, filter) returns the members of a type that satisfy the filter. Each entry carries the member's symbol, the symbol of the type that declares it, its kind (method or constructor parameter property), its JSDoc, and on request its declared type. This is the union of what a member-processing loop asks for individually today.

The filter exists because of a measurement. A first attempt that fetched the declared type of every member and discarded the unwanted ones afterwards was slower than the fine-grained traversal: at aws-cdk-lib scale, private and internal members are the majority, and the cost of materialising types for members the caller throws away cancelled the round-trip savings. To materialise only the types of members that will actually be used, the caller has to tell the server what to drop.

The filter is an options object of independent conditions:

option meaning jsii passes other tools
visibility visibilities to include; ECMAScript private names count as private public, protected a public-API extractor passes public only
kinds member kinds to include (property / accessor / method / parameterProperty) all a documentation tool that renders accessors separately
excludeJsDocTags drop members whose JSDoc carries any of these tags internal (plus deprecated when stripping) TypeDoc's @hidden, API Extractor's @internal / @alpha
excludeNamePattern (RE2) drop members whose name matches ^_ any naming convention
includeInheritedFrom which declaring types contribute members the type's unexported base types all for a flattened view, empty for own members only
includeDeclaredType attach the declared type to each member true false for a name-only listing

πŸ“ƒ Motivating Example

Request mix for jsii's TypeScript 7 backend over aws-cdk-lib (the snapshot jsii-compiler uses for its benchmark, 20,221 types and 97,071 members): getJsDocTags 134k, getTypeAtLocation 125k, getDocumentationComment 117k, getTypeOfSymbolAtLocation 105k, getTypeFromTypeNode 47k, getPropertiesOfType 23k.

A Go execution trace of the server shows the handler for these per-member methods takes 1.6–5 Β΅s each; the round trip the client observes is 26 Β΅s, and the server spends 24 s of a 45 s run blocked waiting for the next request. The traversal is dominated by the number of messages, not by the checker.

The existing mechanisms do not help this pattern. batchRequests cannot bundle a member traversal because its requests depend on each other: the type's property list is needed before the properties' types can be asked for. Client-side caching does not help either: the duplicate rate for documentation reads and getPropertiesOfType is 0%, and memoising getTypeAtLocation (80% duplicates) removed 113k requests but no measurable wall clock, because repeated requests are cheap on the server and the cost is the message itself.

Results, same fork build, c7i.4xlarge, aws-cdk-lib benchmark, with the sync-channel waiting improvement from the separate proposal enabled, median of 3 runs, .jsii output byte-identical between the two paths:

path wall requests transport wait bytes sent
fine-grained (today's methods) 34.5 s 698k 8.6 s 131 MB
getSymbolDocumentations + getMembersOfType 29.4 s 304k 5.4 s 52 MB

A current aws-cdk-lib checkout built from source (2 runs): 43.2 / 43.1 s β†’ 37.8 / 38.2 s, requests 783k β†’ 340k, bytes sent 168 MB β†’ 65 MB, server time unchanged (22.1 β†’ 21.5 s).

The implementation, with Go unit tests, is on the fork branch WinterYukky/TypeScript api/coarse-endpoints-generic-filter-1792824451.

πŸ’» Use Cases

  1. Tools that extract an API surface from a whole program through the API: jsii (multi-language bindings for the AWS CDK), documentation generators such as TypeDoc, and API extractors.
  2. Each member costs several round trips today, and neither batchRequests (the requests depend on each other) nor client-side caching (few duplicates) reduces them.
  3. None; jsii issues the per-member requests and accepts the round-trip cost.

Activity

  1. aetherxeg-source commented on Oct 30, 2026

    @aetherxeg-source

    Thanks for the detailed performance measurements and the proposed coarse-grained checker APIs.

    One correctness boundary seems particularly important for getMembersOfType: preserving the exact API surface produced by the existing fine-grained traversal, especially when server-side filtering happens before declared types are materialized.

    Would it be useful to add a differential regression fixture that runs both API paths against the same program and compares their normalized member results?

    A focused fixture could cover:

    1. Inherited members with an override in the derived type, verifying both member identity and declaring-type provenance.
    2. Method overloads and accessors, ensuring filtering does not collapse or misclassify declarations.
    3. Public/protected/private members, including ECMAScript private names.
    4. Members excluded by JSDoc tags or name patterns, ensuring that filtering does not alter the surviving members' metadata or ordering.

    The comparison could check symbol/type handle identity, declared types, documentation, visibility, and deterministic member ordering.

    The byte-identical .jsii benchmark is useful end-to-end evidence; a small semantic-equivalence regression would complement it by making API-surface drift easier to isolate if the implementation evolves.

    This is a proposal based on reviewing the published issue and its validation details; I haven't executed the prototype or its tests.

  2. WinterYukky commented on Oct 30, 2026

    @WinterYukky
    ContributorAuthor

    Thanks, good suggestion. I'll add a differential fixture comparing both paths on the same program to the PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions