isNotNull TypeScript automatic type Narrowing

Hey guys! I'm having some trouble with the typing of my DB.
const reservations = await db
.select({
id: reservationsTable.id,
_id: reservationsTable._id,
reserveTotal: reservationsTable.reserveTotal,
checkInDate: reservationsTable.checkInDate,
checkOutDate: reservationsTable.checkOutDate,
partnerName: reservationsTable.partnerName,
})
.from(reservationsTable)
.where(
and(
eq(reservationsTable.listingId, listing.id),
eq(reservationsTable.type, "booked"),
isNotNull(reservationsTable.reserveTotal), <--
),
);
const reservations = await db
.select({
id: reservationsTable.id,
_id: reservationsTable._id,
reserveTotal: reservationsTable.reserveTotal,
checkInDate: reservationsTable.checkInDate,
checkOutDate: reservationsTable.checkOutDate,
partnerName: reservationsTable.partnerName,
})
.from(reservationsTable)
.where(
and(
eq(reservationsTable.listingId, listing.id),
eq(reservationsTable.type, "booked"),
isNotNull(reservationsTable.reserveTotal), <--
),
);
Basically I have this select that gets reservations from my DB. The reserveTotal is nullable, but I added in the where the condition isNotNull to it. The problem is that typescript is not recognizing that the reservationTotal is not null and I'm being forced to make a type assertion:
const reservations = (await db
.select({
id: reservationsTable.id,
_id: reservationsTable._id,
reserveTotal: reservationsTable.reserveTotal,
checkInDate: reservationsTable.checkInDate,
checkOutDate: reservationsTable.checkOutDate,
partnerName: reservationsTable.partnerName,
})
.from(reservationsTable)
.where(
and(
eq(reservationsTable.listingId, listing.id),
eq(reservationsTable.type, "booked"),
isNotNull(reservationsTable.reserveTotal),
),
)) as Array<
Pick<
Reservation,
"id" | "_id" | "checkInDate" | "checkOutDate" | "partnerName"
> & {
reserveTotal: number; // Override to make it non-null since we filter with isNotNull
}
>;
const reservations = (await db
.select({
id: reservationsTable.id,
_id: reservationsTable._id,
reserveTotal: reservationsTable.reserveTotal,
checkInDate: reservationsTable.checkInDate,
checkOutDate: reservationsTable.checkOutDate,
partnerName: reservationsTable.partnerName,
})
.from(reservationsTable)
.where(
and(
eq(reservationsTable.listingId, listing.id),
eq(reservationsTable.type, "booked"),
isNotNull(reservationsTable.reserveTotal),
),
)) as Array<
Pick<
Reservation,
"id" | "_id" | "checkInDate" | "checkOutDate" | "partnerName"
> & {
reserveTotal: number; // Override to make it non-null since we filter with isNotNull
}
>;
Is there a way to do this without using type assertion? If not, is there a better way to do this?
4 Replies
Gabriel Santana
Gabriel SantanaOP4mo ago
Also, this get's worse when I need to make a more complex query, like this:
const allPayments = await db.query.payments.findMany({
where: sql`DATE_TRUNC('day', probable_cash_in_date::timestamp) >= DATE_TRUNC('day', ${startDate}::timestamp)
AND DATE_TRUNC('day', probable_cash_in_date::timestamp) <= DATE_TRUNC('day', ${endDate}::timestamp)
AND reservation.type = 'booked'
AND reservation.reserve_total IS NOT NULL
`,
columns: {
id: true,
type: true,
amount: true,
expectedPayment: true,
probableCashInDate: true,
status: true,
paymentDate: true,
},
with: {
reservation: {
columns: {
id: true,
_id: true,
partnerName: true,
checkInDate: true,
checkOutDate: true,
reserveTotal: true,
extraServicesTotal: true,
sellPriceCorrected: true,
buyPrice: true,
companyCommision: true,
},
with: {
listing: {
columns: {
id: true,
internalName: true,
discountChannelCommissionsBeforeCalculation: true,
},
},
fees: {
columns: {
id: true,
feeType: true,
value: true,
description: true,
},
},
payments: {
columns: {
id: true,
type: true,
status: true,
amount: true,
paymentDate: true,
probableCashInDate: true,
expectedPayment: true,
},
},
},
},
},
})
const allPayments = await db.query.payments.findMany({
where: sql`DATE_TRUNC('day', probable_cash_in_date::timestamp) >= DATE_TRUNC('day', ${startDate}::timestamp)
AND DATE_TRUNC('day', probable_cash_in_date::timestamp) <= DATE_TRUNC('day', ${endDate}::timestamp)
AND reservation.type = 'booked'
AND reservation.reserve_total IS NOT NULL
`,
columns: {
id: true,
type: true,
amount: true,
expectedPayment: true,
probableCashInDate: true,
status: true,
paymentDate: true,
},
with: {
reservation: {
columns: {
id: true,
_id: true,
partnerName: true,
checkInDate: true,
checkOutDate: true,
reserveTotal: true,
extraServicesTotal: true,
sellPriceCorrected: true,
buyPrice: true,
companyCommision: true,
},
with: {
listing: {
columns: {
id: true,
internalName: true,
discountChannelCommissionsBeforeCalculation: true,
},
},
fees: {
columns: {
id: true,
feeType: true,
value: true,
description: true,
},
},
payments: {
columns: {
id: true,
type: true,
status: true,
amount: true,
paymentDate: true,
probableCashInDate: true,
expectedPayment: true,
},
},
},
},
},
})
Sillvva
Sillvva4mo ago
There is no way for TypeScript to know that isNotNull makes the result non-nullable. The return-type of isNotNull() is just SQL. The SQL type does not tell TS anything about what you're requesting. So regardless of what conditions you use, the return type is based solely on which columns were requested and the types of those columns defined by the schema. A solution would be define the expected column type like this:
const reservations = await db
.select({
id: reservationsTable.id,
_id: reservationsTable._id,
- reserveTotal: reservationsTable.reserveTotal,
+ reserveTotal: sql<number>`${reservationsTable.reserveTotal}`,
checkInDate: reservationsTable.checkInDate,
checkOutDate: reservationsTable.checkOutDate,
partnerName: reservationsTable.partnerName,
})
.from(reservationsTable)
.where(
and(
eq(reservationsTable.listingId, listing.id),
eq(reservationsTable.type, "booked"),
isNotNull(reservationsTable.reserveTotal),
),
);
const reservations = await db
.select({
id: reservationsTable.id,
_id: reservationsTable._id,
- reserveTotal: reservationsTable.reserveTotal,
+ reserveTotal: sql<number>`${reservationsTable.reserveTotal}`,
checkInDate: reservationsTable.checkInDate,
checkOutDate: reservationsTable.checkOutDate,
partnerName: reservationsTable.partnerName,
})
.from(reservationsTable)
.where(
and(
eq(reservationsTable.listingId, listing.id),
eq(reservationsTable.type, "booked"),
isNotNull(reservationsTable.reserveTotal),
),
);
Sillvva
Sillvva4mo ago
With RQB, you can use custom fields to do the same https://orm.drizzle.team/docs/rqb#include-custom-fields
Drizzle ORM - Query
Drizzle ORM is a lightweight and performant TypeScript ORM with developer experience in mind.
Gabriel Santana
Gabriel SantanaOP4mo ago
Thanks!! I ended up finding this solution as well. I'll check it out the link you sent.

Did you find this page helpful?