Created
October 23, 2023 07:54
-
-
Save pshaddel/d10da39b2409fd9426a93d092bc66522 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Step -2 - Bugs | |
// Step -1 - Dirty Way | |
// Step 0 - Projection should only accepts fields from interface | |
// Step 1 - Projection should be typed and only accepts mongodb valid values[0, 1, true, false] | |
// Step 2 - Projection should be optional | |
// Step 3 - Projection should support nested projection | |
// Step 4 - Projection should support nested projection with arrays(i) | |
// Step 5 - Projection should be nested optional | |
// Step 6 - MongoDB Projection rule, inclusion and exclusion cannot be mixed | |
// - Function output should match the projection: | |
// Step 7 - only normal fiels - only inclusion | |
// Step 8 - objects - only inclusion | |
// Step 9 - objects and arrays - only inclusion | |
// Step 10 - normal fields exclusion | |
import mongoose from 'mongoose'; | |
const mongooseSchema = new mongoose.Schema<IUser>({ | |
name: String, | |
email: String, | |
addresses: [ | |
{ | |
street: String, | |
city: String | |
} | |
], | |
accountInfo: { | |
debit: Number, | |
credit: Number, | |
other: { | |
directDebit: Number | |
} | |
} | |
}); | |
const UserModel = mongoose.model<IUser>('User', mongooseSchema); | |
function getUser<T extends Projection<IUser>>(email: string, projection: T) { | |
type OutputTypeInclusion<T, DocType = IUser> = DocType extends Array<infer U> | |
? OutputTypeInclusion<T, U> | |
: { | |
[key in keyof T]: T[key] extends PrimitivValues | |
? DocType[key extends keyof DocType ? key : never] | |
: OutputTypeInclusion<T[key], DocType[key extends keyof DocType ? key : never]>; | |
}; | |
type X = OutputTypeInclusion<T, IUser>; | |
return UserModel.findOne({ email }, { accountInfo: true }).lean() as unknown as X; | |
} | |
type Exclude<T, K> = { | |
[key in keyof T]: key extends keyof K ? never : T[key]; | |
}; | |
type X = Exclude<{ name: string; age: number }, { name: 1 }>; | |
const x: X = {}; | |
function getDebitFromUser(user: { | |
accountInfo: { | |
debit: number; | |
}; | |
}) { | |
return user.accountInfo.debit; | |
} | |
const main = async () => { | |
const user = await getUser('test@gmail.com', { email: 0 }); | |
if (!user) { | |
return; | |
} | |
const debit = getDebitFromUser(user); | |
console.log(debit, user); | |
const exclusUser = await getUser('test@gmail.com', { email: 1 }); | |
console.log(exclusUser); | |
}; | |
main(); | |
interface IUser { | |
name: number; | |
email?: string; | |
tags: string[]; | |
addresses: { | |
street: string; | |
city: string; | |
}[]; | |
accountInfo: { | |
debit: number; | |
credit?: number; | |
other: { | |
directDebit: number; | |
}; | |
}; | |
} | |
type PrimitivValues = string | number | boolean | Date; | |
type Value = 0 | 1 | true | false; | |
type TruthyValue = 1 | true; | |
type FalsyValue = 0 | false; | |
type Project<T, Value> = { | |
[key in keyof T]?: T[key] extends PrimitivValues ? Value : Value | GetElement<Project<T[key], Value>>; | |
}; | |
type Projection<T> = Project<T, TruthyValue> | Project<T, FalsyValue>; | |
type GetElement<T> = T extends Array<infer U> ? U : T; | |
type X1 = GetElement<{ name: string }[]>; | |
// type ReturnType<T> = T extends (...args: any) => infer U ? U : never; | |
const sum = (a: string, b: string) => a + b; | |
type Y = ReturnType<typeof sum>; | |
const pro: Projection<IUser> = { | |
addresses: 0, | |
name: false, | |
accountInfo: { | |
credit: false | |
} | |
}; | |
type ArrayOperators = { $slice: number | [number, number]; $elemMatch?: undefined } | { $elemMatch: object; $slice?: undefined }; | |
type Projector<T, Element> = T extends Array<infer U> | |
? Projector<U, Element> | ArrayOperators | |
: T extends object | |
? { | |
[K in keyof T]?: T[K] extends object ? Projector<T[K], Element> | Element : Element; | |
} | |
: Element; | |
type _IDType = { _id?: boolean | 1 | 0 }; | |
type InclusionProjection<T> = NestedPartial<Projector<NestedRequired<T>, true | 1> & _IDType>; | |
type ExclusionProjection<T> = NestedPartial<Projector<NestedRequired<T>, false | 0> & _IDType>; | |
type NestedRequired<T> = T extends Array<infer U> | |
? Array<NestedRequired<U>> | |
: T extends object | |
? { | |
[K in keyof T]-?: NestedRequired<T[K]>; | |
} | |
: T; | |
type NestedPartial<T> = T extends object | |
? { | |
[K in keyof T]?: NestedPartial<T[K]>; | |
} | |
: T; | |
// https://stackoverflow.com/questions/58434389/typescript-deep-keyof-of-a-nested-object/58436959#58436959 | |
type DotPrefix<T extends string> = T extends '' ? '' : `.${T}`; | |
type DotNestedKeys<T> = ( | |
T extends object ? { [K in Exclude<keyof T, symbol>]: `${K}` | `${K}${DotPrefix<DotNestedKeys<T[K]>>}` }[Exclude<keyof T, symbol>] : '' | |
) extends infer D | |
? Extract<D, string> | |
: never; | |
type FindDottedPathType<T, Path extends string> = Path extends `${infer K}.${infer R}` | |
? K extends keyof T | |
? FindDottedPathType<T[K], R> | |
: never | |
: Path extends keyof T | |
? T[Path] | |
: never; | |
type ExtractNestedArrayElement<T> = T extends (infer U)[] ? ExtractNestedArrayElement<U> : T; | |
type DotKeys<ProjectionType, DocType> = { | |
[key in DotNestedKeys<ProjectionType>]: FindDottedPathType<DocType, key>; | |
}; | |
// type GetArrayElement<T> = T extends (infer U)[] ? U : T; | |
// type ZZ = DotKeys<NestedObjectType, ExtractNestedArrayElement<NestedObjectType>>; | |
type AnyObject = { [key: string]: any }; | |
export type ProjectionType<T> = | |
| ((DotKeys<InclusionProjection<T>, T> | DotKeys<ExtractNestedArrayElement<ExclusionProjection<T>>, T>) & | |
AnyObject) | |
| string | |
| ((...agrs: any) => any); | |
type X = InclusionProjection<{ | |
a: number; | |
b: { | |
c: { | |
d: string; | |
}; | |
}; | |
}>; | |
type NestedObjectType = { | |
a: string | number; | |
b: string; | |
nest: { | |
c: { | |
d: string; | |
h: number; | |
z: { o: number }[]; | |
}[]; | |
}; | |
otherNest: { | |
c: string; | |
}; | |
}; | |
type Extracted = ExtractNestedArrayElement<NestedObjectType>; | |
type Projected = ProjectionType<NestedObjectType>; | |
type FlattenArraysOfObjectsNested |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment