Describe the bug
collection.createIndex() (and the collection-config where callback) type their row argument with SingleRowRefProxy<T>. Its mapped type only descends into a field when the field type extends Record<string, any>:
export type SingleRowRefProxy<T, TKey extends string | number = string | number> =
T extends Record<string, any>
? {
[K in keyof T]: T[K] extends Record<string, any>
? SingleRowRefProxy<T[K], TKey> & RefProxy<T[K]>
: RefLeaf<T[K]>
} & RefProxy<T> & VirtualPropsRefProxy<TKey>
: RefProxy<T> & VirtualPropsRefProxy<TKey>
For an optional object field, T[K] is Foo | undefined, the conditional fails, and the field collapses into an opaque RefLeaf<Foo | undefined> - so nested member access does not typecheck:
type Row = { id: string; updatedAt?: { seconds: bigint; nanos: number } }
const c = createCollection(localOnlyCollectionOptions<Row>({ getKey: (r) => r.id }))
c.createIndex((row) => row.updatedAt?.seconds)
// TS2339: Property 'seconds' does not exist on type
// '{ readonly [RefBrand]?: { seconds: bigint; nanos: number } | undefined }'
At runtime this works correctly: the callback receives a path-recording proxy, the expression compiles to PropRef(["updatedAt", "seconds"]), and undefined rows are compared using the null-settings.
The query builder's Ref type already solved this exact problem, which is why the identical expression typechecks in .where() / .orderBy() / .select() but not in createIndex().
Proposed fix
Give SingleRowRefProxy the same optionality handling RefBranch, hoist null/undefined out of the field type before the Record check, and re-add it outside the ref:
export type SingleRowRefProxy<T, TKey extends string | number = string | number> =
T extends Record<string, any>
? {
[K in keyof T]:
| SingleRowField<NonNullable<T[K]>, TKey>
| Extract<T[K], null | undefined>
} & RefProxy<T> & VirtualPropsRefProxy<TKey>
: RefProxy<T> & VirtualPropsRefProxy<TKey>
type SingleRowField<V, TKey extends string | number> =
V extends Record<string, any>
? SingleRowRefProxy<V, TKey> & RefProxy<V>
: RefLeaf<V>
Describe the bug
collection.createIndex()(and the collection-configwherecallback) type their row argument withSingleRowRefProxy<T>. Its mapped type only descends into a field when the field type extends Record<string, any>:For an optional object field,
T[K] is Foo | undefined, the conditional fails, and the field collapses into an opaqueRefLeaf<Foo | undefined>- so nested member access does not typecheck:At runtime this works correctly: the callback receives a path-recording proxy, the expression compiles to
PropRef(["updatedAt", "seconds"]), and undefined rows are compared using thenull-settings.The query builder's
Reftype already solved this exact problem, which is why the identical expression typechecks in.where() / .orderBy() / .select()but not increateIndex().Proposed fix
Give
SingleRowRefProxythe same optionality handlingRefBranch, hoist null/undefined out of the field type before theRecordcheck, and re-add it outside the ref: