npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

monarch-orm

v0.5.3

Published

Type safe Object Document Mapper (ODM) for MongoDB

Downloads

349

Readme

Monarch ORM

Monarch ORM is a type-safe ORM for MongoDB, designed to provide a seamless and efficient way to interact with your MongoDB database in a type-safe manner. Monarch ensures that your data models are strictly enforced, reducing the risk of runtime errors and enhancing code maintainability.

Table of Contents

Features

  • Strongly Typed: Ensures type safety across your MongoDB operations.
  • Easy Integration: Simple setup and configuration.
  • Powerful Schema Modifiers: Define schemas with optional and required fields.
  • Intuitive API: Designed to be easy to use and understand.

Installation

NPM:

  npm install monarch-orm

Or Yarn:

  yarn add monarch-orm

Basic Usage

import { boolean, createClient, createDatabase, createSchema, number, string } from "monarch-orm";

    const UserSchema = createSchema("users", {
      name: string().nullable(),
      email: string().lowercase().optional(),
      age: number().optional().default(10),
      isVerified: boolean(),
    });

    const client = createClient(/** db uri **//)
    const { collections } = createDatabase(client.db(), {
      users: UserSchema,
    });

    const newUser = await collections.users
      .insert()
      .values({
        name: "anon",
        email: "[email protected]",
        age: 0,
        isVerified: true,
      })
      .exec();

    const users = await collections.users.find().where({}).exec();

Quick Start

Defining Schemas and connecting to the database

Use the createSchema function to define the structure of your model. Specify the fields and their types, using the available types and modifiers.

const UserSchema = createSchema("users", {
  name: string(),
  isVerified: boolean(),
});

Create a database instance using any client you deem fit and drop it into the createDatabase function

Or you can use the built-in createClient function.

Then you pass your schemas to the second arguement

const { collections } = createDatabase(client.db(), {
  users: UserSchema,
});

Inserting Documents

You can insert new documents into your collection using the insert method. Ensure that the data conforms to the defined schema.

Example: Inserting a new user

const newUser = await collections.users
  .insert()
  .values({
    name: "Alice",
    email: "[email protected]",
    age: 25,
    isVerified: true,
  })
  .exec();

Querying Documents

Retrieve documents from your collection using the find or findOne methods.

Example: Querying all users

const users = await collections.users.find().where({}).exec();
console.log(users);

// Or just...
const users = await collections.users.find({}).exec();
console.log(users);


// For finding one

const user = await collections.users.find().where({
  name: "Alice"
}).exec();
console.log(users);

// Or...
const user = await collections.users.findOne({
  name: "Alice"
}).exec();
console.log(users);

Updating Documents

Update documents in your collection using the update method. You can update a single document or multiple documents based on a filter.

Example: Updating a single user's email

const updatedUser = await collections.users
  .updateOne()
  .set({
    email: "[email protected]",
  })
  .where({
    name: "Alice",
  })
  .exec();
console.log(updatedUser);

Example: Updating multiple users' isVerified field

const updatedUsers = await collections.users
  .updateMany()
  .set({
    isVerified: true,
  })
  .where({
    isVerified: false,
  })
  .exec();
console.log(updatedUsers);

Note: The update method returns the number of documents updated.

Alternative setup

You can also decentralize the models

const { db } = createDatabase(client);

const UserSchema = createSchema("users", {
  name: string(),
  isVerified: boolean(),
});

const UserModel = db(UserSchema);
export default UserModel;

And use it like this

const user = await UserModel.findOne({
  name: "Alice"
}).exec();
console.log(users);

Types

Primitives

String - string()

Defines a field that accepts string values.

const UserSchema = createSchema("users", {
  name: string().required(),
});
  • .lowercase(): Transforms the value to lowercase before storing.
  • .uppercase(): Transforms the value to uppercase before storing.
const UserSchema = createSchema("users", {
  name: string().lowercase(),
});

Number - number()

Defines a field that accepts numeric values.

const UserSchema = createSchema("users", {
  age: number().optional(),
});

Boolean - boolean()

Defines a field that accepts boolean values (true or false).

const UserSchema = createSchema("users", {
  isVerified: boolean(),
});

Date - date()

Defines a field that accepts JavaScript Date objects.

const UserSchema = createSchema("users", {
  birthDate: date(),
});

Date String - dateString()

Defines a field that accepts date strings in ISO format.

const UserSchema = createSchema("users", {
  registrationDate: dateString(),
});

General Modifiers

  • .nullable(): Allows the field to accept null values.
  • .default(): Sets a default value if none is provided.
  • .optional(): Makes the field optional, allowing it to be omitted.

Literals

The literal() type allows you to define a schema with fixed possible values, similar to enums in TypeScript. This is useful for enforcing specific, predefined values for a field.

  const UserRoleSchema = createSchema("userRoles", {
  role: literal("admin", "moderator", "customer"),
});

const user = {
  role: "admin", // Valid
};

// Invalid example will throw a type error
const invalidUser = {
  role: "guest", // Error: Type '"guest"' is not assignable to type '"admin" | "moderator" | "customer"'
};

Objects

 
// all properties are required by default
const UserSchema = object({
  name: string(),
  age: number(),
});

// extract the inferred type like this
type User = InferSchemaInput<typeof UserSchema>;

// equivalent to:
type User = {
  name: string;
  age: number;
};

Records

A record() allows you to define a flexible schema where each user can have a varying number of subjects and grades without needing to define a fixed schema for each subject.

 
// Define the User schema with a record for grades
const UserSchema = createSchema("users", {
  name: string().required(),
  email: string().required(),
  grades: record(number()), // Each subject will have a numeric grade
});


// Example of inserting a user with grades
const { collections } = createDatabase(client.db(), {
  users: UserSchema,
});

// Inserting a new user with grades for different subjects
const newUser = await collections.users
  .insert()
  .values({
    name: "Alice",
    email: "[email protected]",
    grades: {
      math: 90,
      science: 85,
      history: 88,
    },
  })
  .exec();

// Querying the user to retrieve grades
const user = await collections.users.findOne().where({ email: "[email protected]" }).exec();
console.log(user.grades); 
// Output: { math: 90, science: 85, history: 88 }

Arrays

 
// For Example
const ResultSchema = object({
  name: string(),
  scores: array(number()),
});

// extract the inferred type like this
type Result = InferSchemaInput<typeof ResultSchema>;

// equivalent to:
type Result = {
  name: string;
  scores: number[];
};

Tuples

Unlike arrays, A tuple() has a fixed number of elements but each element can have a different type.

 
// all properties are required by default
const ControlSchema = object({
  location: tuple([number(), number()]),
});

// extract the inferred type like this
type Control = InferSchemaInput<typeof ControlSchema>;

// equivalent to:
type Control = {
  location: [number, number];
};

Tagged Union

The taggedUnion() allows you to define a schema for related types, each with its own structure, distinguished by a common "tag" field. This is useful for representing variable types in a type-safe manner.


// You need:
// - a tag: A string identifying the type
// value: An object containing specific fields for that type.

const NotificationSchema = createSchema("notifications", {
  notification: taggedUnion({
    email: object({
      subject: string(),
      body: string(),
    }),
    sms: object({
      phoneNumber: string(),
      message: string(),
    }),
    push: object({
      title: string(),
      content: string(),
    }),
  }),
});

const notification = ;
await collections.notifications.insert().values({ notification: {
  tag: "email",
  value: {
    subject: "Welcome!",
    body: "Thank you for joining us.",
  },
} }).exec();

Union

The union() type allows you to define a field that can accept multiple different types. It's useful when a field can legitimately contain values of different types. Each type provided to union() acts as a possible variant for the field.


const MilfSchema = createSchema("milf", {
  phoneOrEmail: union(string(), number()),
});

// Output Type : { 
//   phoneOrEmail: string | number
// }

Mixed

Mixed

The mixed() type allows you to define a field that can accept any type of value. This is useful when you need maximum flexibility for a field's contents. However, use it sparingly as it bypasses TypeScript's type checking.


const AnythingSchema = createSchema("help", {
  anything: mixed(),
});