Skip to content

fix(client/typescript): add a tsc gate and fix every defect it surfaced - #33

Open
juicycleff wants to merge 21 commits into
mainfrom
fix/ts-client-generator-phase1
Open

fix(client/typescript): add a tsc gate and fix every defect it surfaced#33
juicycleff wants to merge 21 commits into
mainfrom
fix/ts-client-generator-phase1

Conversation

@juicycleff

Copy link
Copy Markdown
Contributor

Summary

The TypeScript client generator emitted code that did not compile and produced different output on every run. Nothing type-checked the generated output — every test assertion was strings.Contains — which is why commit cc3364e had to be written reactively after a downstream tsc --noEmit broke.

This branch adds a real tsc --noEmit gate to the Go test suite over 8 spec/config fixtures, then fixes every defect the gate surfaced. go test now fails if the generated TypeScript stops compiling.

Measured before this branch, every fixture failed, including the default configuration:

Fixture tsc errors before
default 12
apiname (APIName: "APIClient") 15
odd-keys (content-type, 3dtiles) 5
with-auth 4
no-streaming 3

All 8 fixtures now report zero.

Defects fixed

  • rest.ts hardcoded import { Client } while client.ts exported class <APIName> — broke every non-default APIName
  • AuthConfig referenced but not exported, across 8 generators (generator.go, 5 streaming generators, testing.go, plus websocket.go/sse.go)
  • Property keys emitted unquoted and unescaped — content-type was a syntax error, and a name containing ' broke the whole types.ts
  • Non-deterministic output from unsorted map iteration in types, SSE listeners, WS handlers, package.json deps, and pagination item-type selection
  • Path parameters interpolated without encodeURIComponent
  • insertIntoTree silently discarded an entire namespace when a single-segment operation ID collided with an existing branch
  • toCamelCase("userId") returned "userid"; four near-duplicate copies consolidated into one rune-safe implementation
  • A caller-supplied AbortSignal silently disabled the request timeout
  • Error responses threw a plain object, so instanceof Error failed and the AbortError retry check could never match
  • 9 TS2531 errors in websocket.ts/sse.ts — files that had never been type-checked
  • 124 lines of unreachable code carrying two already-fixed bugs

Verification

go test -short -race -timeout=10m over all packages: exit 0, 41 packages ok, no data races.

The gate is proven to bite, not assumed to: reverting tsPropertyKey in a scratch worktree turns it red with 10 errors. The no-auth-ws-sse fixture was likewise verified to fail on the pre-fix generator with exactly the two TS2694 errors it targets.

Auth-enabled generated output is byte-identical to before this branch across all fixtures — the auth gating changed only the auth-disabled path.

CI now installs Node 20 and typescript@5.8.2 so the gate runs there. It skips (rather than fails) when no TypeScript toolchain is present, so local go test still works without Node.

Needs a decision

ci-summary downgrades a test job failure to a warning with no exit 1, while every other job hard-fails. The gate runs in test. If branch protection requires only "CI Summary", a red gate ships green. Pre-existing, deliberately left untouched here — worth resolving in or right after this PR, otherwise the gate is advisory.

The Windows leg of the test matrix has never run the gate. exec.LookPath should resolve tsc.cmd via PATHEXT, but that is reasoned, not verified — worth watching the first run.

Scope

This is phase 1 of docs/superpowers/specs/2026-07-24-ts-client-generator-design.md. The field-case mapping codec, type-generation depth, and the streaming rework with typed event maps are phases 2–4 and are not in this branch.

Known gaps recorded for those phases: webtransport.ts still has no producer for its IR field and has never been type-checked; the generated tests/ directory sits outside the gate (jest types would require node_modules, and the gate is deliberately hermetic); HTTPError and errors.ts's createError are two parallel hierarchies.

…ller-only, when AbortSignal.any is unavailable
…nerators

Two of the eight generators emitted `auth?: types.AuthConfig;` and read
`this.config.auth` unconditionally, even when config.IncludeAuth is false.
Since the type generator already gates AuthConfig out of types.ts when auth
is disabled, this produced a non-compiling client (TS2694) whenever a spec
has WebSocket or SSE endpoints and --no-auth is passed. All six other
generators (rooms, presence, typing, channels, streaming_client, testing)
were already gated; this closes the last two.

The fixture corpus covered the auth axis and the WS/SSE axis separately but
never crossed them, which is why the gate never caught this. Added a
no-auth-ws-sse fixture crossing both axes, confirmed it reproduces the two
TS2694 errors before the fix, and confirmed all 8 fixtures type-check clean
after.

Also folds in two related review findings:
- streaming_client.go's JSDoc @example still showed `auth: { bearerToken }`
  regardless of config.IncludeAuth, advertising an option that doesn't exist
  on StreamingClientConfig with auth off. Gated the example line.
- rest.go's tsPropertyKey had a dead `json.Marshal` error branch (Marshal
  never errors on a Go string). Removed it; behavior is unchanged for every
  input.

Verified auth-ON output is byte-identical to eb67e91 across all six
auth-enabled fixtures (diff -rq, zero differences). Verified auth-OFF
generated websocket.ts has zero references to auth/AuthConfig; sse.ts has
one inert comment only.
@vercel

vercel Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
forge Ready Ready Preview, Comment Jul 25, 2026 4:40am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

Conventional Commits Validation

PR Title: valid
Commits: some do not follow conventional format

  • caaf4a9: fix(client/typescript): fall back to manual signal forwarding, not caller-only, when AbortSignal.any is unavailable

Format

type(scope): description

Types: feat, fix, docs, style, refactor, test, chore, ci, build, perf, revert

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant