Skip to content

Instantly share code, notes, and snippets.

@sagar88826
Last active October 20, 2024 09:06
Show Gist options
  • Save sagar88826/e137a291be930c9a149ba2d5ce4433a6 to your computer and use it in GitHub Desktop.
Save sagar88826/e137a291be930c9a149ba2d5ce4433a6 to your computer and use it in GitHub Desktop.
import mongoose, { InferSchemaType, Require_id } from "mongoose";
// ##########################################################################################
// ######################################BLUEPRINT START#####################################
// ##########################################################################################
// Speciality of this blueprint
// 1. It will convert the document object array type to lean object array type
// 2. It will handle both optional and required fields of type array and object as well as nested object and array
// 3. No manual document interface creation is required
// Rules
// 1. Always use `as const` to define schema definition
// 2. Always use `InferSchemaType<typeof schema>` to get the raw object type
// 3. Always use `LeanDocumentArray<T>` to get the lean object type
// 4. Always use `mongoose.HydratedDocument` to define the hydrated object type
// 5. Always use `mongoose.Model` to define the model type
// Definitions
// 1. SchemaTypeOrder: This is the raw object type of the schema
// 2. LeanedTypeOrder: This is the lean object type of the schema
// 3. OrderHydratedDocument: This is the hydrated object type of the schema
/**
* Now it will convert the document object array type to lean object array type
* It will handle both optional and required fields of type array and object as
* well as nested object and array and this utility is only for ARRAYS
*/
export type LeanDocumentArray<T> = {
[K in keyof T]: T[K] extends mongoose.Types.DocumentArray<infer U>
? Array<Require_id<U>>
: T[K] extends mongoose.Types.DocumentArray<infer U> | null | undefined
? Array<Require_id<U>> | null | undefined
: T[K] extends Date
? Date
: T[K] extends Date | null | undefined
? Date | null | undefined
: T[K] extends Record<string, any>
? LeanDocumentArray<T[K]>
: T[K] extends Record<string, any> | null | undefined
? LeanDocumentArray<T[K]> | null | undefined
: T[K];
};
// ######################################HANDLING ARRAYS#####################################
const orderSchemaDefinition = {
rname: { type: String, required: true },
oname: String,
remail: { type: String, required: true },
oemail: String,
ravatar: { type: String, required: true },
oavatar: String,
rtags: {
type: [{ name: { type: String, required: true } }],
required: true,
},
otags: [{ name: { type: String, required: true } }],
rObj: {
type: {
rname: { type: String, required: true },
rtag: {
type: [
{
name: { type: String, required: true },
},
],
required: true,
},
},
required: true,
},
oObj: {
oname: String,
otag: {
type: [
{
name: { type: String, required: false },
},
],
required: false,
},
},
} as const;
const orderSchema = new mongoose.Schema(orderSchemaDefinition);
// ######################################Types#####################################
// Schema object type
type SchemaTypeOrder = InferSchemaType<typeof orderSchema>; // This can be used for picking Types.DocumentArray type only
// Lean object type
type LeanedTypeOrder = LeanDocumentArray<SchemaTypeOrder>; // These are the type which is return by toObject() method and lean() method
// Hydrated Schema object Type
type HydratedDocumentTypeOrder = mongoose.HydratedDocument<
LeanedTypeOrder, // lean type and it is used by toObject() method directly so therefore anything which you want to change then do it 2nd argument of HydratedDocument
SchemaTypeOrder
>;
// Model type
type ModelTypeOrder = mongoose.Model<
LeanedTypeOrder,
{},
{},
{},
HydratedDocumentTypeOrder
>;
// ################################################################################
// Model
const OrderModel = mongoose.model<LeanedTypeOrder, ModelTypeOrder>(
"Order",
orderSchema
);
const doc = new OrderModel({ tags: [{ name: "test" }] });
doc._id; // mongoose.Types.ObjectId // this should be string
doc.rname; // string
doc.oname; // string | null | undefined
doc.ravatar; // string
doc.oavatar; // string | null | undefined
doc.rtags; // mongoose.Types.DocumentArray<{ name: string }> // this should be Array<{ name: string, _id:string }> and this will come from
doc.otags; // mongoose.Types.DocumentArray<{ name: string }> | null | undefined // this should be Array<{ name: string, _id:string }> and this will come from
doc.toObject().rtags; // ({ name: string } & { _id: mongoose.Types.ObjectId })[]
// ##################################HANDLING POPULATION###################################
const parentSchemaDefintion = {
name: String,
children: { type: mongoose.Schema.Types.ObjectId, ref: "Child" },
} as const;
const parentSchema = new mongoose.Schema(parentSchemaDefintion);
// ######################################Types#####################################
// Schema object type
type SchemaTypeParent = InferSchemaType<typeof parentSchema>; // This can be used for model creation of hydrate object type
// Lean object type
type LeanedTypeParent = LeanDocumentArray<SchemaTypeParent>; // This can be used for lean object type
// Hydrated Schema object Type
type ParentHydratedDocument = mongoose.HydratedDocument<
LeanedTypeParent,
SchemaTypeParent
>;
// Model type
type ParentModelType = mongoose.Model<
LeanedTypeParent,
{},
{},
{},
ParentHydratedDocument
>;
// ################################################################################
// Model
export const ParentModel = mongoose.model<LeanedTypeParent, ParentModelType>(
"Parent",
parentSchema
);
const childSchemaDefinition = {
name: String,
rtag: { type: [{ name: { type: String, required: true } }], required: true },
} as const;
const childSchema = new mongoose.Schema(childSchemaDefinition);
// ######################################Types#####################################
// Schema object type
type SchemaTypeChild = InferSchemaType<typeof childSchema>; // This can be used for model creation of hydrate object type
// Lean object type
type LeanedTypeChild = LeanDocumentArray<SchemaTypeChild>; // This can be used for lean object type
// Hydrated object Type
type HydratedDocumentTypeChild = mongoose.HydratedDocument<
LeanedTypeChild,
SchemaTypeChild
>;
// Model type
type ChildModelType = mongoose.Model<
LeanedTypeChild,
{},
{},
{},
HydratedDocumentTypeChild
>;
// ################################################################################
// Model
export const ChildModel = mongoose.model<LeanedTypeChild, ChildModelType>(
"Child",
childSchema
);
// Now testing will begin
export async function test() {
const parent = await ParentModel.create({ name: "parent" });
const child = await ChildModel.create({ name: "child" });
parent.children = child._id;
await parent.save();
const populatedParentDoc = await ParentModel.findById(parent._id)
.populate<{ children: HydratedDocumentTypeChild | null }>("children")
.orFail();
const plainPopulatedParentObj = {
...populatedParentDoc.toObject(), // because its an instance of Mongoose document
children: populatedParentDoc.children
? populatedParentDoc.children.toObject()
: null,
};
if (plainPopulatedParentObj.children) {
plainPopulatedParentObj.children._id; // mongoose.Types.ObjectId (required field)
plainPopulatedParentObj.children.name; // string | null | undefined (optional field)
plainPopulatedParentObj.children.rtag; // ({ name: string } & { _id: mongoose.Types.ObjectId })[] (required field)
}
}
// ########################################################################################
// ######################################BLUEPRINT END#####################################
// ########################################################################################
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment