Skip to content

fix(sdk): unbreak typecheck on dev after v2 error widening#28503

Merged
kitlangton merged 1 commit into
anomalyco:devfrom
kitlangton:fix/sse-iterator-return-type
May 20, 2026
Merged

fix(sdk): unbreak typecheck on dev after v2 error widening#28503
kitlangton merged 1 commit into
anomalyco:devfrom
kitlangton:fix/sse-iterator-return-type

Conversation

@kitlangton
Copy link
Copy Markdown
Contributor

@kitlangton kitlangton commented May 20, 2026

Summary

dev has been red since commit 0e118d1961 (chore: generate following #28495). Two distinct issues:

1. @hey-api/openapi-ts SseFn miswiring (latent for ~9 months)

In every generated client bundle, the SDK declares:

type SseFn = <TData, TError, ...>(options) => Promise<ServerSentEventsResult<TData, TError>>
//                                                                                  ^^^^^^

But ServerSentEventsResult<TData, TReturn = void, TNext = unknown> — that second slot is the underlying AsyncGenerator's return value type, not an error channel. HTTP errors are thrown from the initial promise or surfaced via onSseError; an async generator's .return() value has nothing to do with them.

Confirming this is the SDK's own bug, not intentional:

  • The SSE implementation itself declares ServerSentEventsResult<TData> (no TError) — see client-core/bundle/serverSentEvents.ts:80-92.
  • The inner generator is async function* with no explicit return → real TReturn is void.
  • Same bug in all six generated clients (fetch / axios / ky / next / ofetch / angular). Introduced by d43ef3f3b ("feat(client): add support for server-sent events", Aug 2025).

While the v2 GlobalEventErrors union was effectively empty/unknown this was harmless. PR #28495 widened error responses to concrete types, and now every .return(undefined) call and every mock async generator violates the iterator's TReturn contract:

src/cli/cmd/run/stream.transport.ts(421,43): TS2345: Argument of type 'undefined' is not assignable to parameter of type 'GlobalEventErrors | PromiseLike<GlobalEventErrors>'.
src/cli/cmd/run/stream.transport.ts(426,37): TS2345: ...
test/cli/run/stream.transport.test.ts(169,3): TS2322: Type 'AsyncGenerator<GlobalEvent, void>' is not assignable to type 'AsyncGenerator<GlobalEvent, GlobalEventErrors>'.
test/cli/run/stream.transport.test.ts(1095,9): TS2322: ...
test/cli/run/stream.transport.test.ts(1196,9): TS2322: ...

Fix: drop TError from the ServerSentEventsResult<> generics in our generated packages/sdk/js/src/v2/gen/client/types.gen.ts so TReturn defaults to void. Because @hey-api/openapi-ts runs with clean: true and would overwrite that file on every regen, add a post-gen patch step to packages/sdk/js/script/build.ts that reapplies the fix automatically (and throws if the upstream output shape ever changes, so we'll notice). The phantom TError parameter on SseFn is preserved to keep call-site type-argument arity intact.

I'll open an upstream PR to hey-api/openapi-ts separately so we can drop this workaround when it ships.

2. workspace.warp 400 union widened to include InvalidRequestError

Real consumer bug, separate from #1. ExperimentalWorkspaceWarpErrors is now WorkspaceWarpError | VcsApplyError | InvalidRequestError. The first two discriminate via name, but InvalidRequestError uses _tag (mixed conventions in the SDK). The dialog narrowed via result.error.name === "VcsApplyError" and now hits:

src/cli/cmd/tui/component/dialog-workspace-create.tsx(111,24): TS2339: Property 'name' does not exist on type 'InvalidRequestError | VcsApplyError | WorkspaceWarpError'.

Fix: narrow with "name" in result.error && result.error.name === "VcsApplyError" before reading. Behavior unchanged — InvalidRequestError continues to fall through to the toast path, same as any other non-VcsApplyError.

Test plan

  • bun run typecheck (full monorepo) — green
  • bun run build in packages/sdk/js after reverting the patched line to the buggy upstream form — confirms post-gen patch reapplies after a real createClient wipe + regen
  • bun run test test/cli/run/stream.transport.test.ts — 21/21 pass

Two problems surfaced when anomalyco#28495 + the follow-up `chore: generate`
populated previously-empty v2 error response unions.

1. `@hey-api/openapi-ts` codegen passes the endpoint's `TError` into the
   second generic of `ServerSentEventsResult`, which is the underlying
   `AsyncGenerator`'s `TReturn` slot — not an error channel. The SSE
   implementation's own signature uses `ServerSentEventsResult<TData>`,
   so it's only the public `SseFn` wrapper that's wrong. Latent for ~9
   months; harmless while error unions were unknown, but breaks every
   `.return(undefined)` call and every mock async generator the moment
   an SSE endpoint declares a concrete error response. Patch the
   generated `client/types.gen.ts` and add a post-gen step in
   `script/build.ts` so every future `chore: generate` reapplies it.
2. `experimental.workspace.warp` now returns `InvalidRequestError` in
   its 400 union, which uses `_tag` rather than `name` as its
   discriminator. Narrow the error before reading `.name` in
   `dialog-workspace-create.tsx`.

Verified end-to-end: `bun run build` in `packages/sdk/js` wipes and
regenerates v2/gen, then re-applies the SseFn patch automatically;
full monorepo typecheck and stream.transport tests are green.
@kitlangton kitlangton merged commit ba803dd into anomalyco:dev May 20, 2026
11 of 12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant