Skip to content

Digit-leading operationId silently disables pagination and emits non-compiling code #213

Description

@7nohe

Describe the bug

When an operationId starts with a digit, hey-api names the SDK function and its Data type differently:

// sdk.gen.ts
export const _123NumericLead = <ThrowOnError extends boolean = false>(
  options?: Options<NumericLeadData, ThrowOnError>
) => ...

The function is _123NumericLead (prefixed, because an identifier cannot start with a digit) but the type is NumericLeadData (the digits are dropped). This codegen derives the Data type name from the method name — ${capitalizeFirstLetter(methodName)}Data_123NumericLeadData — which does not exist, so the lookup misses.

Two symptoms follow, and the first is the more damaging one:

1. Pagination is silently disabled. getPaginatableMethods in src/parseOperations.mts keys its map by a name derived from the Data type, so paginatableMethods.get(methodName) misses and isPaginatable stays false. The operation gets no infinite hooks at all even though it has a valid page parameter. There is no error — the feature just isn't there.

2. The generated code does not typecheck. getDataTypeName falls back to unknown, emitting Options<unknown, true>. hey-api's Options<TData extends TDataShape> rejects unknown, producing 6 × TS2344 across common.ts, queries.ts, suspense.ts, prefetch.ts, ensureQueryData.ts and queryOptions.ts.

To Reproduce

  1. Generate from the spec below
  2. Observe openapi/queries/infiniteQueries.ts contains no hook for the /d operation, despite its page query parameter
  3. Run tsc --noEmit over the generated output

OpenAPI spec file

openapi: "3.0.0"
info: { version: 1.0.0, title: probe }
paths:
  /d:
    get:
      operationId: 123numericLead
      parameters:
        - name: page
          in: query
          schema: { type: integer }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { type: array, items: { type: string } }

Actual tsc output:

openapi/queries/common.ts(29,64): error TS2344: Type 'unknown' does not satisfy the constraint 'TDataShape'.
openapi/queries/ensureQueryData.ts(11,95): error TS2344: ...
openapi/queries/prefetch.ts(11,93): error TS2344: ...
openapi/queries/queries.ts(11,170): error TS2344: ...
openapi/queries/queryOptions.ts(11,63): error TS2344: ...
openapi/queries/suspense.ts(11,191): error TS2344: ...

Other awkward operationId shapes were checked and are not affected — spaces (find pet by id), a leading underscore, and SCREAMING_CASE all round-trip correctly. The digit-leading case is the one where the two names diverge.

Expected behavior

The operation should get its infinite hooks, and the generated output should compile.

Environment

  • OS: macOS
  • Version: v3.0.1
  • @hey-api/openapi-ts 0.99.0

Additional context

The fix is to stop deriving the Data type from the method name and instead read it from the SDK function's own signature — the type argument of Options<…> in sdk.gen.ts is already the authoritative answer and is right there in the AST that parseOperations.mts walks. That resolves both symptoms at once.

Substituting Options<TDataShape, true> for the unknown fallback would only paper over symptom 2, and would drag in an import from ../requests/client whose path varies by client — not recommended.

Note for whoever picks this up: the four Options<unknown, true> assertions in tests/tsmorph/buildQueryHooks.test.ts currently pin this broken output, so they document a bug rather than a contract and should be rewritten alongside the fix. #209 added a guard test (should expose a Data type in modelNames for every paginatable operation) that pins the invariant the infinite-query builders depend on; any change to how Data types are resolved must keep it green.

Found while investigating #209, which removed a genuinely unreachable variant of this fallback on the paginatable path. This issue is the reachable, non-paginatable half that remains.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions