Access table field dynamically by an index - help with types

Hello! I'm further extending the DAO from https://discord.com/channels/1043890932593987624/1147439990683488268 , and currently I'm trying to implement custom ordering. I've already written the code which works, and the only thing I'm missing is type-compliance - accessing table column directly by an index gives me this TS error:
TS2536: Type 'string | number | (keyof TTable["_"]["columns"] & string)' cannot be used to index type 'TTable'.
TS2536: Type 'string | number | (keyof TTable["_"]["columns"] & string)' cannot be used to index type 'TTable'.
The code which errors:
interface FieldOrder<TTableFields extends string | number> {
field: TTableFields;
order: "asc" | "desc";
}

// This is a method in the class
findAllPaginated(
page: number,
pageSize: number,
orderBy?: FieldOrder<keyof InferSelectModel<TTable>>[],
): Promise<InferSelectModel<TTable>[]> {
const customOrderBy = orderBy?.map(({ field, order }) => {
// this part right here
const sqlField = this.table[field];

return order === "desc" ? desc(sqlField) : asc(sqlField);
});
const defaultOrderBy = [desc(this.sqlCreatedAt)];

return this.database
.select()
.from(this.table)
.where(this.isNotDeleted)
.orderBy(...(customOrderBy ?? defaultOrderBy))
.limit(pageSize)
.offset(pageSize * (page - 1)) as Promise<InferSelectModel<TTable>[]>;
}
interface FieldOrder<TTableFields extends string | number> {
field: TTableFields;
order: "asc" | "desc";
}

// This is a method in the class
findAllPaginated(
page: number,
pageSize: number,
orderBy?: FieldOrder<keyof InferSelectModel<TTable>>[],
): Promise<InferSelectModel<TTable>[]> {
const customOrderBy = orderBy?.map(({ field, order }) => {
// this part right here
const sqlField = this.table[field];

return order === "desc" ? desc(sqlField) : asc(sqlField);
});
const defaultOrderBy = [desc(this.sqlCreatedAt)];

return this.database
.select()
.from(this.table)
.where(this.isNotDeleted)
.orderBy(...(customOrderBy ?? defaultOrderBy))
.limit(pageSize)
.offset(pageSize * (page - 1)) as Promise<InferSelectModel<TTable>[]>;
}
The question is - how can I tell typescript, that this is valid? I'm fine with any type casting solutions, I just don't know how can I take this further.
4 Replies
Kuba
KubaOP10mo ago
Casting it to AnyColumns works:
interface FieldOrder<TTable extends PgTable> {
field: keyof TTable;
order: "asc" | "desc";
}

// ...

const customOrderBy = orderBy?.map(({ field, order }) => {
const sqlField = this.table[field] as AnyColumn;

return order === "desc" ? desc(sqlField) : asc(sqlField);
});
interface FieldOrder<TTable extends PgTable> {
field: keyof TTable;
order: "asc" | "desc";
}

// ...

const customOrderBy = orderBy?.map(({ field, order }) => {
const sqlField = this.table[field] as AnyColumn;

return order === "desc" ? desc(sqlField) : asc(sqlField);
});
I also changed the FieldOrder field to keyof directly from TTable instead of InferSelectModel. This makes sure that TS2322 is shown if field name doesn't exists in the table. The drawback is, that it also shows attributes, such as _, and methods (getSQL())
Kuba
KubaOP10mo ago
No description
Kuba
KubaOP10mo ago
Alright, this can be further narrowed down with TS Omit helper:
type InternalFields = "_" | "$inferSelect" | "$inferInsert" | "getSQL";

interface FieldOrder<TTable extends PgTable> {
field: keyof Omit<TTable, InternalFields>;
order: "asc" | "desc";
}
type InternalFields = "_" | "$inferSelect" | "$inferInsert" | "getSQL";

interface FieldOrder<TTable extends PgTable> {
field: keyof Omit<TTable, InternalFields>;
order: "asc" | "desc";
}
Kuba
KubaOP10mo ago
tada ✨
No description
Want results from more Discord servers?
Add your server