Skip to content

Instantly share code, notes, and snippets.

@FujiHaruka
Created February 8, 2020 02:53
Show Gist options
  • Select an option

  • Save FujiHaruka/8396e1bfcdf9553e08e9c9dfbb3bf9a2 to your computer and use it in GitHub Desktop.

Select an option

Save FujiHaruka/8396e1bfcdf9553e08e9c9dfbb3bf9a2 to your computer and use it in GitHub Desktop.
How to get Model type from Schema definition object using generic type in mongoose
import { Schema, model, Document } from 'mongoose'
// ----------------------------
// Definition of Modeled<T>
// ----------------------------
// Object that has "type" property
type TypeObjectEnd = { type: any }
// Nested object that has TypeObjectPrimitive recursively
type TypeObject =
| TypeObjectEnd
| {
[key: string]: TypeObject
}
// Generic type to convert constructor type to instance type
type InstanceOf<T> = T extends StringConstructor
? string
: T extends NumberConstructor
? number
: T extends BooleanConstructor
? boolean
: T extends new (...args: any[]) => infer R // other constructors
? R
: any
// Schema definitions allow to use expressions such as [String]
type DefinitionField<T> = T extends (infer R)[]
? DefinitionField<R>[]
: InstanceOf<T>
/**
* Generic type to generate Model type from Schema definition
*/
type Modeled<T> = {
[P in keyof T]: T[P] extends TypeObject
? T[P] extends TypeObjectEnd
? DefinitionField<T[P]['type']>
: Modeled<T[P]>
: DefinitionField<T[P]>
}
// ----------------------------
// Test of Modeled<T>
// ----------------------------
// Enabels to check type equals type Y
// https://github.com/Microsoft/TypeScript/issues/27024#issuecomment-421529650
type Equals<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y
? 1
: 2
? true
: false
const ExampleSchemaDefinition = {
name: String,
binary: Buffer,
living: Boolean,
updated: { type: Date, default: Date.now },
age: { type: Number, min: 18, max: 65 },
mixed: Schema.Types.Mixed,
_someId: Schema.Types.ObjectId,
decimal: Schema.Types.Decimal128,
array: [] as any[],
ofString: [String],
ofNumber: [Number],
ofDates: [Date],
ofBuffer: [Buffer],
ofBoolean: [Boolean],
ofMixed: [Schema.Types.Mixed],
ofObjectId: [Schema.Types.ObjectId],
ofArrays: [[]] as any[][],
ofArrayOfNumbers: [[Number]],
nested: {
stuff: { type: String, lowercase: true, trim: true },
},
}
type ExampleModel = Modeled<typeof ExampleSchemaDefinition>
// If two types are not the same, TypeScript tells us it as a compile error.
const assertion: Equals<
ExampleModel,
{
name: string
binary: Buffer
living: boolean
updated: Date
age: number
mixed: Schema.Types.Mixed
_someId: Schema.Types.ObjectId
decimal: Schema.Types.Decimal128
array: any[]
ofString: string[]
ofNumber: number[]
ofDates: Date[]
ofBuffer: Buffer[]
ofBoolean: boolean[]
ofMixed: Schema.Types.Mixed[]
ofObjectId: Schema.Types.ObjectId[]
ofArrays: any[][]
ofArrayOfNumbers: number[][]
nested: {
stuff: string
}
}
> = true
// ----------------------------
// Example usage
// ----------------------------
const UserDefinition = {
name: String,
age: Number,
active: Boolean,
activatedAt: {
type: Date,
default: Date.now,
},
additonalInfo: {
interests: {
type: [String],
},
},
}
const UserSchema = new Schema(UserDefinition)
type UserModel = Modeled<typeof UserDefinition>
const userModel = model<UserModel & Document>('User', UserSchema)
userModel.findOne({}).then((found) => {
if (!found) {
return null
}
const user = found.toObject()
// user has properties below
user.name
user.age
user.active
user.activatedAt
user.additonalInfo.interests
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment