Skip to content

Since 0.6.6, queries don't typecheck when the collection's row type is a generic type parameter #1677

Description

@obeattie
  • I've validated the bug against the latest version of DB packages

Describe the bug

If you write a function that works on any Collection<T> (where T is a type parameter rather than a concrete type), the query builder's types break: inside where/join/select callbacks, the refs have no properties at all. Even properties that T's constraint guarantees (like id in T extends { id: string }) can't be accessed. This worked fine up to 0.6.5 and broke in 0.6.6.

The cause seems to be this type, added in 0.6.6 as part of unionAll (#1537):

type RefForContextValue<T, Nullable extends boolean = false> =
  IsPlainObject<T> extends true ? Ref<T, Nullable> : RefLeaf<T, Nullable>;

When T is still an unresolved type parameter, TypeScript can't pick a side of that conditional (same for the IsNonExactOptional/IsNonExactNullable conditionals in RefsForContext), so the ref ends up as a union of every possible branch. One of those branches is RefLeaf, which has no row properties — and a union only exposes what all its members share, so the ref exposes nothing. Before 0.6.6, refs were always Ref<...>, so generic row types still worked.

To Reproduce

Typecheck this with tsc --strict against @tanstack/db 0.6.6 or later:

import { createLiveQueryCollection, eq, type Collection } from "@tanstack/db";

export function findById<T extends { id: string }>(items: Collection<T, string>, id: string) {
  return createLiveQueryCollection((q) => q.from({ items }).where(({ items }) => eq(items.id, id)));
}
error TS2339: Property 'id' does not exist on type
  'Ref<NonNullable<WithVirtualProps<T, string>>, true> | RefLeaf<NonNullable<WithVirtualProps<T, string>>, true> | ...'.
  Property 'id' does not exist on type 'RefLeaf<NonNullable<WithVirtualProps<T, string>>, true>'.

The same root cause also breaks joins, though the error looks unrelated at first: using a subquery over a generic collection as a join source fails because its QueryBuilder<ContextFromSource<...>> no longer satisfies leftJoin's QueryBuilder<Context> parameter (the RefLeaf branch is missing the $synced/$origin/$key/$collectionId properties that every ref is required to have):

export function withDiff<T extends { id: string }>(a: Collection<T, string>, b: Collection<{ id: string }, string>) {
  return createLiveQueryCollection((q) => {
    const sub = q.from({ a });
    return q.from({ b }).leftJoin({ sub }, ({ b, sub }) => eq(b.id, sub.id)); // TS2322 on `{ sub }`
  });
}

Expected behavior

Both snippets compile, as they did on 0.6.5. A ref for a generic row type should expose at least the properties its constraint guarantees.

Desktop (please complete the following information):

  • @tanstack/db: broken in 0.6.6 through 0.6.14 (latest), works in 0.6.5
  • TypeScript: 5.9.3 (--strict)
  • No browser/OS involved — this is compile-time only

Additional context

I tested every release from 0.6.5 through 0.6.14: 0.6.5 compiles, and every version from 0.6.6 onward fails. The Ref<T> distributivity change in 0.6.13 (#1597) changes the wording of the error but isn't where it started.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions