-
Notifications
You must be signed in to change notification settings - Fork 187
Description
Bug Description
When a schema field is typed as Temporal.PlainDate (or any other Temporal type), using it in a .select() projection causes TypeScript to mangle the type — all methods collapse to {}.
Reproduction
import { Temporal } from 'temporal-polyfill'
type Todo = {
id: number
name: string
dueDate: Temporal.PlainDate
project_id: number
}
type Project = {
id: number
name: string
}
const todos = createCollection<Todo>(/* ... */)
const projects = createCollection<Project>(/* ... */)
const liveQuery = createLiveQueryCollection({
query: (q) =>
q
.from({ todo: todos })
.innerJoin({ project: projects }, ({ todo, project }) =>
eq(todo.project_id, project.id),
)
.select(({ todo, project }) => ({
todo,
project,
})),
})
// Type error: dueDate is mangled
const date: Temporal.PlainDate = liveQuery.toArray[0]!.todo.dueDateError:
Type '{ readonly [Symbol.toStringTag]: "Temporal.PlainDate"; toString: {}; equals: {};
add: {}; subtract: {}; ... }' is not assignable to type 'PlainDate'.
Types of property 'equals' are incompatible.
Type '{}' is not assignable to type '(other: string | PlainDate | PlainDateLike) => boolean'.
Root Cause
In packages/db/src/query/builder/types.ts, IsPlainObject<T> determines how each field is processed through the Ref<T> mapped type:
- Plain objects → recursively walked to build nested refs
- Non-plain objects (arrays,
JsBuiltInslikeDate,Map, etc.) → treated as leaf values, type preserved as-is
Temporal.PlainDate extends object but is not in the JsBuiltIns union, so IsPlainObject returns true. The type system then recursively walks its properties through Ref<T>, which turns all method signatures into {}.
This affects all Temporal types: PlainDate, PlainTime, PlainDateTime, ZonedDateTime, Instant, Duration, etc.
Suggested Fix
Add a Symbol.toStringTag check to IsPlainObject:
type IsPlainObject<T> = T extends unknown
? T extends object
? T extends ReadonlyArray<any>
? false
: T extends JsBuiltIns
? false
: T extends { [Symbol.toStringTag]: string }
? false
: true
: false
: falseObjects with Symbol.toStringTag are class instances (Temporal types, typed arrays, etc.), not plain data objects. This covers all Temporal types without requiring imports or maintaining a hardcoded list.