I made mongoose-like driver in kotlin

Introduction

Node.js is the best when it comes to community support and
simplicity.
But, when it comes to the language itself and the builtin
features, Yuk!
I was used to a lot of libraries in Node.js and that what
held me from
going all in server-side Kotlin.
After the introduction of ktor and kgraphql, it was mongoose
the only
library left to have an alternative in kotlin!

So, I made mongoose in Kotlin :drooling_face:

Link GitHub - cufyorg/mangaka: A mongoose equivalent for kotlin

I am seeking attention because I need you guys to help me
with it with your ideas and to test the library.
Thank you <3

Starting the connection

The following is how to do the mangaka connection on the
default mangaka instance.

suspend fun foo() {
    Mangaka.connect("mongodb://localhost:27017", "Mangaka")
}

Schema Definition

Mangaka was made to mimic the style of mongoose as possible.
Obviously a real mongoose in kotlin is impossible to be maid
since type-unions are not a supported thing in kotlin. But,
the thing that kotlin has instead is extension functions.
So, mangaka took a good advantage of that.

For example, the following with mongoose:

export interface Entity extends Document {
    value?: string;
    friendId: ObjectId;
    list: string[];
}

export const EntitySchema = new Schema<Entity>({
    value: {
        type: SchemaTypes.String,
        default: () => "Initialized",
        validate: value => value === "Invalid",
        immutable: value => value === "Immutable"
    },
    friendId: {
        type: SchemaTypes.ObjectId,
        ref: () => "Entity",
        exists: true // using module 'mongoose-extra-validators'
    },
    list: {
        type: [SchemaTypes.String],
        default: () => ["FirstElement"]
    }
});

export const EntityModel = model("Entity", EntitySchema, "EntityCol");

Has the following equivalent with mangaka:

@Serializable
data class Entity(
    var value: String? = null,
    var friendId: Id<Entity> = Id("62a49540988ff286898c46b5"),
    var list: MutableList<String?> = mutableListOf()
) : Document

val EntitySchema = DocumentSchema(Entity::class) {
    field(Entity::value) {
        this extends StringSchema()

        default { "Initialized" }
        validate { it != "Invalid" }
        immutable { it == "Frozen" }
    }
    field(Entity::friendId) {
        this extends ObjectIdSchema()

        exists { this.model }
    }
    field(Entity::list) {
        this extends ArraySchema()

        default { listOf("FirstElement") }

        items {
            this extends StringSchema()
        }
    }
}

val EntityModel = Model("Entity", EntitySchema, "EntityCol")

Model Usage

The model is a tricky one, since in mongoose the model has
mongoose internal stuff with javascript specific wizardry
which is not even needed in mangaka. But, still the
developer experience the first priority here in mangaka.
So, the code appears the same but internal it is not.

For example, the following code with mangaka:

async function foo() {
    const entity = new EntityModel()
    await EntityModel.create({
        value: "SomeValue"
    })
    await EntityModel.findOne({
        value: "SomeValue"
    })
}

Has the following equivalent with mangaka:

suspend fun foo() {
    val entity = EntityModel()
    EntityModel.create(
        Entity::value eq "SomeValue"
    )
    EntityModel.findOne(
        Entity::value eq "SomeValue"
    )
}

Document Usage

One of the best things about mongoose is the interface
Document that has shortcuts for saving, deleting and
validating values without the need to know the client,
database or collection they came from.
This is one of the easiest to mimic features that was
implemented in mangaka.

For example, the following with mongoose:

async function foo(document: Document) {
    await document.validate()
    await document.save()
    await document.remove()
}

Has the following equivalent with mangaka:

suspend fun foo(document: Document) {
    document.validate()
    document.save()
    document.remove()
}

Static and Member functions

This feature doesn’t event need to be implemented in
mangaka.
The best thing about kotlin extension functions is that
anyone can write their own extension function.

The following is an example for extension functions:

// example virtual value
val Entity.firstElement: String?
    get() = list.firstOrNull()

// example member function
suspend fun Entity.findFriend(): Entity? {
    return model.findOne(Filters.eq("_id", friendId))
}

// example static function
suspend fun Model<Entity>.findByValue(value: String): Entity? {
    return findOne(Entity::value eq value)
}

Extension Order

When applying an extension to a schema. The applying order
is very important.

The following are examples of the
importance of applying order:

  • The ignore (and immutable) extension is expected to be
    before the required and default extensions. Since the
    ignore extension will emit null which the required
    and default extensions will receive the null and think
    the value is missing.
  • The extends extension is expected to be the first
    extension applied. Since, the extends extension
    will override ALL the schema functions.
1 Like

Looks cool! :+1:

I’m not the best person to provide feedback but I did enjoy reading the notes/docs.

1 Like