Home > Blockchain >  Using Array<keyoff T> as parameter and Partial in generic function
Using Array<keyoff T> as parameter and Partial in generic function

Time:08-17

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:

  1. I can skip a column in findFields which will lead to an error, all columns should be listed to return the proper tablePartial Is there a way so in findFields all properties must be listed

  2. Is there a way to not repeat twice columns in type tablePartial and findFields, 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")
  }
}

playground

  • Related