Hi all,
I decided to write my own little bindings for mongoose library and I stuck.
The problem is that it’s quite clear how to write @new binding for external type, but what if I use an external method that returns a constructor?
From mongoose docs:
// define a schema
const personSchema = new Schema({
name: {
first: String,
last: String
}
});
// compile our model
const Person = mongoose.model('Person', personSchema);
// create a document
const axl = new Person({
name: { first: 'Axl', last: 'Rose' }
});
Writing bindings for Schema and compiling model is easy:
type schema<'a>
type model<'a>
@module("mongoose") @new external createSchema: ('a) => schema<'a> = "Schema"
@send external model: (mongoose, string, schema<'a>) => model<'a> = "model"
But I can’t wrap my head around what to do with creating document. I should do something like new model<'a>()
but I have no idea how to model that with Rescript.
You probably want to add a make() function to it for instantiating it.
Here are some old mongoose bindings of my own that are actually in use in a production system:
module Document = {
type t<'a> = 'a
@send
external markModified: (t<'a>, string) => unit = "markModified"
@send
external save: t<'a> => Promise.t<unit> = "save"
@send
external toObject: t<'a> => 'a = "toObject"
}
module Model = {
type t<'a>
// https://mongoosejs.com/docs/api/model.html#model_Model-create
@send
external create: (t<'a>, 'a) => Promise.t<Document.t<'a>> = "create"
// https://mongoosejs.com/docs/api/model.html#model_Model-deleteMany
@send
external deleteMany: (t<'a>, {..}) => Promise.t<{"deletedCount": int}> = "deleteMany"
// https://mongoosejs.com/docs/api/model.html#model_Model-find
@send
external find: (t<'a>, {..}, option<{..}>, option<{..}>) => Promise.t<array<Document.t<'a>>> =
"find"
// https://mongoosejs.com/docs/api/model.html#model_Model-findOne
@send
external findOne: (t<'a>, {..}) => Promise.t<Js.Nullable.t<Document.t<'a>>> = "findOne"
// https://mongoosejs.com/docs/api/model.html#model_Model-findOneAndUpdate
@send
external findOneAndUpdate_: (
t<'a>,
{..},
{..},
option<{..}>,
) => Promise.t<Js.Nullable.t<Document.t<'a>>> = "findOneAndUpdate"
let findOneAndUpdate = (model: t<'a>, conditions: {..}, update: {..}, options: option<{..}>) =>
findOneAndUpdate_(model, conditions, update, options)->Promise.thenResolve(Js.Nullable.toOption)
}
module Schema = {
type t
module Types = {
type t = string
let bool: t = "Boolean"
let date: t = "Date"
let mixed: t = "Mixed"
let objectId: t = "ObjectId"
let number: t = "Number"
let string: t = "String"
}
@module("mongoose") @new
external make: 'a => t = "Schema"
}
module Types = {
module ObjectId = {
type t
@module("mongoose") @scope("Types") @new
external make: string => t = "ObjectId"
}
}
module Connection = {
module ReadyState = {
type t = int
@dead("Connection.ReadyState.+disconnected") let disconnected: t = 0
let connected: t = 1
@dead("Connection.ReadyState.+connecting") let connecting: t = 2
@dead("Connection.ReadyState.+disconnecting") let disconnecting: t = 3
}
type t = {
name: string,
readyState: ReadyState.t,
}
@send
external model: (t, string, Schema.t, string) => Model.t<'a> = "model"
@send
external useDb: (t, string, {"useCache": bool}) => t = "useDb"
let getModel = (conn: t, name): Model.t<'a> => {
let models = Obj.magic(conn)["models"]
switch models->Js.Dict.get(name) {
| Some(model) => model
| None => failwith(`No such model: ${name}`)
}
}
let hasModel = (conn: t, name) => {
let models = Obj.magic(conn)["models"]
models->Js.Dict.get(name)->Belt.Option.isSome
}
}
@module("mongoose") @scope("default")
external connection: Connection.t = "connection"
@module("mongoose")
external connect: string => Promise.t<unit> = "connect"
@module("mongoose")
external set: (string, bool) => unit = "set"
1 Like
I provided equivalent to your Schema make
function:
The problem is not with instantiating a schema but with instantiating a concrete model.
@philiparvidsson you probably omit the problem in your example by using create
method from mongoose that instantiates and saves at the same time, so you don’t need to call the constructor with new on Model.t<'a>.
That’s absolutely valid approach. The problem is not a deal breaker for me, and I didn’t find it common across JS libraries. Even within mongoose you can call Schema({})
to get back a new instance, but for some reason, they didn’t implement that for Model!
I’m asking the question because I’m more curious about how to model a case like that with Rescript. It’s not like I badly need it, it’s just itching my brain
The only way I know is to use raw js.
let makeDocument: (model<'a>, _) => document<'a> = %raw(`(Constructor, options) => new Constructor(options)`)
1 Like
Thanks @vdanchenkov
That’s what I’m using at the moment. It’s not that bad because it’s possible to define types, but I thought maybe there is a better way.
Using %raw
I feel like using unsafe
in Rust
1 Like