Skip to content

Instantly share code, notes, and snippets.

@heyitsmass
Last active February 1, 2024 10:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save heyitsmass/790ac391b960cf03f43adce24540d541 to your computer and use it in GitHub Desktop.
Save heyitsmass/790ac391b960cf03f43adce24540d541 to your computer and use it in GitHub Desktop.
Typesafe Mongoose Definitions
import mongoose from "mongoose";
import { fakerEN_CA, fakerEN_US } from "@faker-js/faker";
import { coinToss } from "./utils";
import {
IPhone,
PhoneParseResult,
TPhone,
TPhoneSchema,
phoneOptions
} from "./phone";
export const phoneSchema: TPhoneSchema = new mongoose.Schema( //Schema is completely defined
{
countryCode: {
type: String,
required: true,
set: function (this: Phone, value: string) {
return value.replace(/[\+\s.-]*/g, "");
},
default: "1"
},
areaCode: {
type: String,
required: true
},
number: {
type: String,
required: true
},
ext: String,
notes: String
},
phoneOptions //options are included
);
phoneSchema.static("random", function (this, country, areaCode) { //inferred already
if (areaCode && !areaCode.match($areaCode)) {
throw new Error(`Invalid area code ${areaCode}`);
}
//this.random(); //OK (EXISTS)
//this.full; //ERROR
const faker = {
Canada: fakerEN_CA,
"United States": fakerEN_US
}[country];
return {
countryCode: "1",
areaCode: areaCode ?? faker.helpers.fromRegExp($areaCode),
number: faker.helpers.fromRegExp($number),
ext: coinToss(faker.helpers.fromRegExp($extension), undefined),
notes: coinToss(faker.lorem.sentence, undefined)
}
});
export const Phone =
(mongoose.models.Phone as TPhone) ||
mongoose.model<IPhone, TPhone>("Phone", phoneSchema);
const phone = new Phone(); // === HydratedDocument<Phone, TPhoneVirtuals & TPhoneMethods>
phone.random(); //ERROR
console.log(phone.full) //OK
let tmp = phone.toJSON();
console.log('full' in tmp); //Output: False
tmp = phone.toJSON({virtuals:true});
console.log('full' in tmp); //Output: True
import {
AddThisParameter,
ApplySchemaOptions,
DefaultSchemaOptions,
HydratedDocument,
Model,
ObtainDocumentType,
ResolveSchemaOptions,
Schema
} from "mongoose";
export type TDocType<
TEnforcedDocType = any,
TSchemaOptions = DefaultSchemaOptions,
DocDefinition = any
> = ApplySchemaOptions<
ObtainDocumentType<
DocDefinition,
TEnforcedDocType,
ResolveSchemaOptions<TSchemaOptions>
>,
ResolveSchemaOptions<TSchemaOptions>
>;
/** Refined Hydrated Doc Type expects TVirtuals and TInstanceMethods to override into the hydrated document type */
export type THydratedDocType<TRawDocType, TVirtuals, TInstanceMethods> = //Removed TQueryHelpers
HydratedDocument<TRawDocType, TVirtuals & TInstanceMethods>;
export type TModel<
TRawDocType,
TSchemaOptions = DefaultSchemaOptions,
TInstanceMethods = {},
TVirtuals = {},
TStatics extends Model<TRawDocType> = {} & Model<TRawDocType>, //Forcefully extend statics
DocType = TDocType<TRawDocType & TVirtuals, TSchemaOptions> //Force the DocType to contain virtuals for .toJSON({virtuals:true});
> = Model<
TRawDocType,
{},
TInstanceMethods,
TVirtuals,
THydratedDocType<DocType, TVirtuals, TInstanceMethods>
> &
TStatics; //Add Statics to the Model
export type TSchema<
TEnforcedDocType,
TSchemaOptions = DefaultSchemaOptions,
TInstanceMethods = {},
TVirtuals = {},
TStaticMethods extends TModel<TEnforcedDocType> = {} & TModel<TEnforcedDocType>,
DocType extends TDocType<
TEnforcedDocType,
TSchemaOptions,
DocType
> = TDocType<TEnforcedDocType, TSchemaOptions>,
TModelType extends Model<any> = TModel<
TEnforcedDocType,
TSchemaOptions,
TInstanceMethods,
TVirtuals,
TStaticMethods,
DocType
>
> = Schema<
TEnforcedDocType,
TModelType,
TInstanceMethods,
{},
TVirtuals,
AddThisParameter<TModelType, TStaticMethods>, //Provides static functions "this"
TSchemaOptions,
DocType,
THydratedDocType<DocType, TVirtuals, TInstanceMethods>
>;
import { Model } from "mongoose";
import { TModel, TSchema } from "../Mongo";
type IntrinsicAttributes = {
/** Country Code */
countryCode: string;
/** Area Code */
areaCode: string;
/** Phone number */
number: string;
/** Extension */
ext?: string;
};
interface Phone extends IntrinsicAttributes {
/** Notes */
notes?: string;
}
const phoneOptions = {
timestamps: true,
discriminatorKey: "kind"
} as const;
type TPhoneVirtuals = {
full: string;
};
interface TPhoneStatics extends Model<IPhone> {
random: (
country: string,
areaCode?: string,
) => Phone;
}
type TPhoneInstanceMethods = {};
export type TPhone = TModel<
IPhone,
typeof phoneOptions,
TPhoneInstanceMethods,
TPhoneVirtuals,
TPhoneStatics
>;
export type TPhoneSchema = TSchema<
IPhone,
typeof phoneOptions,
TPhoneInstanceMethods,
TPhoneVirtuals,
TPhoneStatics
>;
function which<T>(value: FunctionOrValue<T>) {
return value instanceof Function ? value() : value;
}
type FunctionOrValue<T> = T | ((...args: any[]) => T);
export function coinToss<T, K>(
winner: FunctionOrValue<T>,
loser?: FunctionOrValue<K>,
bias: boolean = true
): T | K {
return bias && Math.random() < 0.5
? (which(winner) as T)
: (which(loser) as K);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment