I have the following type definition for a database schema:
type Schema<T> = {
[K in keyof T]: SchemaType<T[K]>
}
interface SchemaType<T> {
optional: boolean
validate(t: T): boolean
}
Here's an object that implements it:
const userSchema = {
name: {
optional: true,
validate(name: string) {
return name.length > 0 && name.length < 30
}
}
}
I'd like to be able to infer the generic type T in Schema based on the userSchema object. This is doable without too much effort using TypeScript's infer keyword:
type extractType<T> = T extends Schema<infer X> ? X : null
// User is inferred as { name: string }
type User = extractType<typeof userSchema>
Even better, I'm able to infer the User type indirectly:
class Model<T> {
constructor(private schema: Schema<T>) {}
}
// userModel is inferred as type Model<{name: string}>
const userModel = new Model(userSchema)
However, I would like to be able to infer User as { name?: String }, where name is optional because its optional property is set to true. Is there a way to do this?
Some related TypeScript features
Here are some potentially useful TypeScript features I ran across while working on this.
TypeScript has discriminated unions, which is similar to the kind of type inference I'm trying to achieve.
Although the
optionalfield on theuserSchemaabove is inferred as aboolean, it's possible to infer it as a boolean literal usingas const.// userSchema is inferred as // { name: { optional: true, validate(name: string): boolean } } // instead of // { name: { optional: boolean, validate(name: string): boolean } } const userSchema = { name: { optional: true as const, validate(name: string) { return name.length > 0 && name.length < 30 } } }Requiring this isn't ideal, but I'm not sure it's avoidable.
I tried using conditional types like in the
extractTypeimplementation above to defineSchemain a more limiting way:// The below interface essentially says: // if T[K] is an optional property // then -> optional is set to true // else -> optional is set to false. // I was hoping TypeScript could reverse this when inferring the type: // if optional is set to true // then -> T[K] is an optional property // else -> T[K] is a required property type Schema<T> = { [K in keyof T]: undefined extends T[K] ? OptionalSchemaType<T[K]> // Same as SchemaType but with optional: true : RequiredSchemaType<T[K]> // Same as SchemaType but with optional: false } const userModel = new Model(userSchema) // Error!Unfortunately, TypeScript's type inference isn't smart enough to be able to infer a type this complex.
I'm not sure if what I'm trying to achieve is possible, but any help or alternatives are very much appreciated. :)