Skip to content

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

@nathan-muir

Description

@nathan-muir
  • I've validated the bug against the latest version of DB packages

Describe the bug

.select() collapses discriminated union types on object fields into a single type with only the intersection of keys. Fields that are unique to individual union variants become inaccessible (never) in the result type, while the union is correctly preserved when no .select() is used.

To Reproduce

import { createCollection, createLiveQueryCollection } from '@tanstack/db'

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

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

const items = createCollection<Item, number>({ id: 'items', getKey: (i) => i.id })

// ✅ Without .select() — union is preserved
const col1 = createLiveQueryCollection((q) => q.from({ i: items }))
col1.toArray[0]!.document // type: Document (correct union)

// ❌ With .select() — union is collapsed
const col2 = createLiveQueryCollection((q) =>
  q.from({ i: items }).select(({ i }) => ({
    id: i.id,
    document: i.document,
  })),
)
col2.toArray[0]!.document
// Actual type: { type: "pdf" | "image" | "legacy" }
// Variant-specific keys (url, pages, width, height, path) are lost

Expected behavior

result.document should have type Document (the original discriminated union) regardless of whether .select() is used. The union should be preserved so that narrowing (e.g., if (doc.type === 'pdf')) works correctly downstream.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions