Skip to content

fix(db): preserve union types in select result extraction#1512

Open
nathan-muir wants to merge 1 commit intoTanStack:mainfrom
nathan-muir:fix/ref-union-type-collapse
Open

fix(db): preserve union types in select result extraction#1512
nathan-muir wants to merge 1 commit intoTanStack:mainfrom
nathan-muir:fix/ref-union-type-collapse

Conversation

@nathan-muir
Copy link
Copy Markdown

@nathan-muir nathan-muir commented May 4, 2026

Fixes #1511

🎯 Changes

When a collection field is a discriminated union of object types, selecting that field via .select() collapses the union into a single type with only the intersection of keys.

type Document =
  | { type: 'pdf'; url: string; pages: number }
  | { type: 'image'; url: string; width: number; height: number }
  | { type: 'legacy'; path: string }

type Item = { id: number; document: Document }

// Before: result.document is { type: string } — union collapsed, variant keys lost
// After:  result.document is Document — full discriminated union preserved
q.from({ i: items }).select(({ i }) => ({ id: i.id, document: i.document }))

The root cause is in ResultTypeFromSelect: when a Ref<T> holds a union type, ExtractRef recurses into the mapped keys via keyof/Prettify, which collapse the union to only common keys.

The fix detects when a Ref's branded type (RefLeaf<T>) is a union and extracts it directly, bypassing ExtractRef. This preserves Ref<T>'s mapped type structure so that common keys of the union remain accessible in .where() and .orderBy() callbacks (e.g., eq(i.document.type, 'pdf')).

New helpers:

  • IsUnion<T> — detects union types
  • ExtractRefBrand<T> — extracts the raw type from a RefLeaf's brand

Tests added:

  • Select preserves union types on object fields
  • Where clause works on common keys of union fields
  • Select preserves union when collection type is a union

✅ Checklist

  • I have tested this code locally with pnpm test.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

@nathan-muir nathan-muir force-pushed the fix/ref-union-type-collapse branch 2 times, most recently from d56337e to 3436682 Compare May 4, 2026 21:42
@nathan-muir nathan-muir changed the title fix(db): preserve union types in Ref mapped type fix(db): preserve union types in select result extraction May 4, 2026
When a collection field is a union of object types (e.g.,
`{ type: 'pdf'; url: string } | { type: 'image'; width: number }`),
selecting that field via `.select()` collapses the union into a single
type containing only the intersection of keys. This happens because
`Ref<T>` uses a mapped type `[K in keyof T]`, and when `T[K]` is a
union of objects, `IsPlainObject` returns true (it distributes), causing
`Ref` to recurse into the union. But `keyof (A | B | C)` only yields
common keys, destroying the discriminated union.
@nathan-muir nathan-muir force-pushed the fix/ref-union-type-collapse branch from 3436682 to ae0abd1 Compare May 4, 2026 22:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

types: .select() collapses discriminated union types on object fields

1 participant