I am trying to create a class and methods that will be used to retrieve data from database. For instance:
export default abstract class BaseRepository<T> {
public async findEntities(conditionFieldsObj: Partial<T>): Promise<T[]>
and call is:
table1.findEntities( { column1filter: "something" })
this will return all fields for the table generating select * from table where column1filter = "something"
and the return type will be <T>
this works fine
I also want to have additional method to be able to restrict list of columns and return Partial type so I do:
export default abstract class BaseRepository<T> {
async findEntitiesPartial<D extends Partial<T>>(conditionFieldsObj: Partial<D>, findFields: Array<keyof D>): Promise<D[]> {}
and call is:
type tablePartial = Pick<tableEntity, "column1" | "column2" >
const result = await table1.findEntitiesPartial<tablePartial>( { column1filter: "something" }, ["column1", "column2"])
findFields: Array<keyof D>
helps to prevent adding properties that do not exist in tablePartial
and in tableEntity
type and have a list of columns to be queried but I have the following issues:
I can skip a column in
findFields
which will lead to an error, all columns should be listed to return the propertablePartial
Is there a way so infindFields
all properties must be listedIs there a way to not repeat twice columns in type
tablePartial
andfindFields
, otherwise I have to list those twice which is ugly. As far as I understood I cannot get type properties in runtime but maybe there is a way to somehow do this scenario nicely.
Thanks
CodePudding user response:
I don't know if I've understood you correctly but here is a solution wehre you don't need to specify the ´´´findfields```- property twice but you can to make it more explicit
export default abstract class BaseRepository<T> {
public async findEntities(conditionFieldsObj: Partial<T>): Promise<T[]> {
throw Error("not implemented")
}
public async findEntitiesPartial<F extends (keyof T)[]>(conditionFieldsObj: Partial<T>, findFields: F): Promise<Pick<T, F[number]>[]> {
throw Error("not implemented")
}
}
class Table1 extends BaseRepository<TableEntity>{
}
type TableEntity = { column1: string, column2: string, column3: string }
type tablePartial = Pick<TableEntity, "column1" | "column2">
const table1 = new Table1()
// infered restriction
const result = (await table1.findEntitiesPartial({ column1: "something", column2: "a" }, ["column1", "column2",])) //Pick<TableEntity, "column1" | "column2">[]
const result1 = (await table1.findEntitiesPartial({ column1: "something", column2: "a" }, ["column1", "column2", "column4"])) //error
// restrict by generic
const result2 = (await table1.findEntitiesPartial<["column1", "column2"]>({ column1: "something", column2: "a" }, ["column1", "column2", "column3"])) //error
const result3 = (await table1.findEntitiesPartial<["column1", "column3"]>({ column1: "something", column2: "a" }, ["column1", "column3"])) ///Pick<TableEntity, "column1" | "column3">[]
Edit:
// So how does work
export default abstract class BaseRepository<T> {
public async findEntitiesPartial<F extends (keyof T)[]>(conditionFieldsObj: Partial<T>, findFields: F): Promise<Pick<T, F[number]>[]> {
throw Error("not implemented")
}
}
// We restrict our generic to be an array of keys of our table
// F could be ["column1","column2","column2"] or "column1"[]
// but it could never be ["something else"]
// Now it's important to know that a type constraint F extends (keyof T)[] (everything after F)
// is no type assignment nothing will get infered by this constraint.
// It's just a placeholder that's restricted.
// You can assign a value to that placeholder by using the generic or by type inference.
function test<T>(input:T):T{
return input
}
const a = test("asd") //"asd"
const b = test<"asd">("") //Argument of type '""' is not assignable to parameter of type '"asd"'
// Ok,now we explicitly tell typescript which value to use e.g.:["column1","column2","column2"]
// let's replace F with our example and let's get rid of our generic F and replace T with our TableEntity
type TableEntity = { column1: string, column2: string, column3: string }
export default abstract class BaseRepository2 {
public async findEntitiesPartial(
conditionFieldsObj: Partial<TableEntity>,
findFields: ["column1", "column2", "column2"]):
Promise<Pick<TableEntity, ["column1", "column2", "column2"][number]>[]> {
throw Error("not implemented")
}
}
// ["column1","column2","column2"][number] will resolve to "column1"|"column2"|"column2" which is widened to "column1"|"column2"
export default abstract class BaseRepository2 {
public async findEntitiesPartial(
conditionFieldsObj: Partial<TableEntity>,
findFields: ["column1", "column2", "column2"]):
Promise<Pick<TableEntity, "column1" | "column2">[]> {
throw Error("not implemented")
}
}