I am working with Typescript and firebase and I have a small abstraction layer with this function to search for a unique document base on its field name and its value.
where<K extends keyof (T & DocumentEntity)>(fieldName: K, operator: WhereFilterOp, value: unknown): Query<T> {
this.addCriterion(new WhereCriterion(fieldName as string, operator, value));
return this;
}
This works well when I want to query with a field at the base of the document, for example:
Model:
order: Order = {
orderId: baseId
item: { ... }
price: { ... }
restaurant: {
restaurantId: nestedId
name: chezGaston
}
}
Query:
const order = await this.documentPersistence.findUnique(
new Query<order>().where('orderId', '==', incomingOrderId)
);
But now I want to query base on the id of a nested object.
const order = await this.documentPersistence.findUnique(
new Query<order>()
.where('restaurant.restaurantId', '==', integration),
);
And this gives me a static error TS2345: Argument of type '"restaurant.restaurantId"' is not assignable to parameter of type 'keyof Order'.
How can I fix my function so it accepts Nested object as keyof my object?
I don't want to use // @ts-ignore
You can do this as of TypeScript 4.1.
Click the playground example to see it in action:
Here's the relevant code:
type PathImpl<T, K extends keyof T> =
K extends string
? T[K] extends Record<string, any>
? T[K] extends ArrayLike<any>
? K | `${K}.${PathImpl<T[K], Exclude<keyof T[K], keyof any[]>>}`
: K | `${K}.${PathImpl<T[K], keyof T[K]>}`
: K
: never;
type Path<T> = PathImpl<T, keyof T> | keyof T;
type PathValue<T, P extends Path<T>> =
P extends `${infer K}.${infer Rest}`
? K extends keyof T
? Rest extends Path<T[K]>
? PathValue<T[K], Rest>
: never
: never
: P extends keyof T
? T[P]
: never;
declare function get<T, P extends Path<T>>(obj: T, path: P): PathValue<T, P>;
const object = {
firstName: "Diego",
lastName: "Haz",
age: 30,
projects: [
{ name: "Reakit", contributors: 68 },
{ name: "Constate", contributors: 12 },
]
} as const;
get(object, "firstName"); // works
get(object, "projects.0"); // works
get(object, "projects.0.name"); // works
get(object, "role"); // type error