Keyrxng

Eliminating the “Union too complex to represent” Failure in the Kernel

Ubiquity OS Kernel · 2024 · Re-architected webhook event typing after months of failed attempts by core devs, replacing an inference-heavy mega union with a mapped indirection that restored compiler stability and preserved payload safety + autocomplete.

So what?
restored tsc compiler stability · >50 functions not added manual guards avoided
Role
TypeScript Engineer (Type System Refactor)
Year
2024
Stack
TypeScript advanced mapped + conditional types, Octokit webhook types
Read narrative
restored tsc compiler stability>50 functions not added manual guards avoided

Problem

The kernel’s webhook context types hit a hard TypeScript ceiling: Expression produces a union type that is too complex to represent. (Issue opened Mar 10, 2024). Repeated attempts (narrowing generics, specific event parameterization, casting, proposed per-event type guards) either failed locally, broke in CI, or threatened a maintenance burden. The failure blocked clean builds and risked a fall-back to inelegant mass type guards while degrading contributor DX.

Context & Stakes

Constraints

Approach (High-Level)

  1. Introduce an indirection layer: build a mapped type that indexes every event key to its resolved WebhookEvent<K> object, eliminating repeated conditional evaluation.
  2. Provide a curated “looser” union surface for plugins to keep autocomplete responsive while kernel internals retain the full strict set.
  3. Remove reliance on WebhookEvent<T>["payload"] where T was an expansive generic forcing TS to expand the entire union.
  4. Collapse inference hotspots by turning conditional chains into a record-like lookup keyed by the event string literal.
  5. (Refinement) Simplify generics per review to a single <TSupportedEvents extends WebhookEventName> generic—dropping the extra inferred intermediate while keeping specificity.

Implementation Highlights

System diagram

flowchart LR
  Webhook[Webhook event received by kernel] --> TypeSystem[Kernel type system receives event]
  TypeSystem --> Indirection[Simplified mapped/conditional indirection]
  Indirection --> Resolved[Resolved underlying event payload]

Design Choices (Why They Worked)

Proof

Code excerpt — mapped indirection (trimmed)

export type SupportedEvents<T extends EmitterWebhookEventName = EmitterWebhookEventName> = {
  [K in EmitterWebhookEventName]: T extends K ? EmitterWebhookEvent<T> : never;
};

export class DelegatedComputeInputs<
  T extends keyof SupportedEvents = keyof SupportedEvents,
  TU extends SupportedEvents[T] = SupportedEvents[T]
> {
  public eventName: T;
  public eventPayload: TU["payload"];
}

Code excerpt — plugin-side curated union + mapping (trimmed)

export type SupportedEventsU =
  | "issues.labeled" | "issues.unlabeled" | "label.edited"
  | "issues.reopened" | "push" | "issue_comment.created"
  | (string & {});

export type SupportedEvents = {
  [K in SupportedEventsU]: K extends WebhookEventName ? WebhookEvent<K> : never;
};

Conversation evidence (excerpts)

"Expression produces a union type that is too complex to represent." (Issue #31)
"Both whilefoo and I gave it our attempts but no luck." (Issue discussion)
"Even typeguards don't work." (Issue #31)
"I am skeptical here because the whole team tried and nobody could figure it out." (PR #52 review)
"This looks promising, I tried it and it works." (PR #52 review)
"fix: union too complex solve" merged (PR #52)

Outcomes

Lessons / Takeaways

References