FIRST OF ALL it's a typescript only question. I don't care about the js part.
My final goal is to type a simple mongoose find with a projection object.
let's say movie as 3 fields :
- name -> string,
- email -> string
- isGood -> boolean
Movie.findOne({ year: { $gte: 1980, $lte: 1989 }, {isGood: -1}, function(err, movie) {
// Here I want ts compiler to understand that returned object does not have the isGood property
});
I want my returned object to be typed like this (without the isGood property since we omit it in the projection object.
{
name: string,
email: string
}
Right now I have this code :
type Diff<T, U> = T extends U ? never : T; // Remove types from T that are assignable to U
interface Test {
blob: number;
pop: number;
}
interface UserProfile {
id: string
email: string
image: string | null
isAdmin: boolean
username: string
reviews: string[],
test: Test
}
type ExcludedTypes<T, U> = {
[K in Exclude<keyof T, keyof U>]: T[K]
}
type PartialFrom<T> = {
[P in keyof T]?: number | PartialFrom<T[P]>;
}
const excludeFn = <K extends Record<string, any>, T extends PartialFrom<K>>(user: K, projection: T): ExcludedTypes<K, T> => {
const newUser: K = {
...user
};
Object.keys(projection).forEach((key) => {
delete newUser[key];
})
return user;
}
const full: UserProfile = {
id: "reareaz",
email: "rezrza",
image: null,
isAdmin: false,
username: "rezaraz",
reviews: ["test"],
test: {
blob: 1221,
pop: 122,
}
}
const projection = {
email: -1,
image: -1,
reviews: -1,
}
const filteredObject = excludeFn(full, projection);
interface PartialUserProfile {
id: string
isAdmin: boolean
username: string
}
// this is just used to test that filteredObject is properly typed
const ppp = (a: PartialUserProfile): void => { }
// it should fail because email is not in filtered object anymore
ppp(filteredObject);
So the goal here is to create a types function that take a full object as first argument, a partial object and return fields of full object that are NOT in partial object.
My exemple above work only on first level properties :
It work :
const full = {
image: 'string',
isFalse: true
}
const projection = {
image: -1
}
// will not have image property
const filteredObject = excludeFn(full, projection);
Won't work :
const full = {
image: 'string',
pasta: {
isGood: true,
brand: 'mypastabrand'
}
}
const projection = {
pasta: {
isGood: -1
}
}
const filteredObject = excludeFn(full, projection);
This won't work because my type :
type ExcludedTypes<T, U> = {
[K in Exclude<keyof T, keyof U>]: T[K]
}
Does not go more than one level deep and that's where I'm stuck