Giter Club home page Giter Club logo

commodo's Introduction

Prettier license SemVer lerna Commitizen friendly Gitter

Commodo is a library of higher order functions (HOFs) that let you create and compose rich data models.

๐Ÿ“น Videos

Wanna get up to speed quickly? Check out the Commodo videos here!

Quick example

A simple model

The following example shows how to create a simple data model, which you can later use to validate data (e.g. data received as body of an HTTP request):

import { withFields, string, number, boolean } from "@commodo/fields";

const Animal = withFields({
    name: string({
        validation: value => {
            if (!value) {
                throw Error("A pet must have a name!");
            }
        }
    }),
    age: number(),
    isAwesome: boolean(),
    about: fields({
        value: {},
        instanceOf: withFields({
            type: string({ value: "cat" }),
            dangerous: boolean({ value: true })
        })()
    })
})();

const animal = new Animal();
animal.populate({ age: "7" }); // Throws data type error, cannot populate a string with number.

animal.populate({ age: 7 });
await animal.validate(); // Throws a validation error - name must be defined.

animal.name = "Garfield";
await animal.validate(); // All good.
More complex model

Using other HOFs, you can create more complex models, that have a name, attached hooks, and even storage layer, so that you can easily save the data to the database:

import { withFields, string, number, boolean, fields, onSet } from "@commodo/fields";
import { withName } from "@commodo/name";
import { withHooks } from "@commodo/hooks";
import { withStorage } from "@commodo/fields-storage";
import { MongoDbDriver, withId } from "@commodo/fields-storage-mongodb";
import { compose } from "ramda";

// Define User and Verification models.
const Verification = compose(
  withFields({
    verified: boolean(),
    verifiedOn: string()
  })
)();

const User = compose(
  withFields({
    firstName: string(),
    lastName: string(),
    email: compose(
      onSet(value => value.toLowerCase())
    )(string()),
    age: number(),
    scores: number({ list: true }),
    enabled: boolean({ value: false }),
    verification: fields({ instanceOf: Verification })
  }),
  withHooks({
    async beforeCreate() {
      if (await User.count({ query: { email: this.email } })) {
        throw Error("User with same e-mail already exists.");
      }
    }
  }),
  withName("User"), // Utilized by storage layer, to determine collection / table name.
  withId(),
  withStorage({
    driver: new MongoDbDriver({ database })
  })
)();

const user = new User();
user.populate({
  firstName: "Adrian",
  lastName: "Smith",
  email: "[email protected]",
  enabled: true,
  scores: [34, 66, 99],
  verification: {
    verified: true,
    verifiedOn: "2019-01-01"
  }
});

await user.save();

Is Commodo an ORM/ODM?

Fundamentally, Commodo is not an ORM/ODM, but can very quickly become one, by utilizing an additional HOF. You can use the already provided @commodo/fields-storage or even create your own if you don't like the existing one.

Using HOFs is a very flexible approach for defining your data models, because you can append only the functionality you actually need and will use.

Core packages:

The following section shows all of the useful higher order functions that you can use right now.

Package Short Description Version
@commodo/fields The starting point of every model. Provides base string, number, boolean and model fields.
@commodo/name Assign a name to your models.
@commodo/hooks Provides methods for defining and triggering hooks on your models.
@commodo/fields-storage Enables saving models to a database (with an appropriate driver, e.g. MySQL).

Additional packages:

Package Short Description Version
@commodo/fields-storage-ref Provides ref field, for saving references to other models saved in database.
@commodo/fields-storage-mongodb A MongoDB driver for @commodo/fields-storage package.
@commodo/fields-storage-soft-delete Introduces deleted boolean field to mark whether a model was deleted or not, instead of physically deleting the entry in the storage.

Community packages:

Package Short Description Version
commodo-fields-date Provides date field, for saving dates.
commodo-fields-object Provides object field, for saving plain objects.
commodo-fields-int Provides int field, for saving integer numbers.
commodo-fields-float Provides float field, for saving float numbers.
commodo-fields-storage-crud-logs Adds and automatically manages createdOn, updatedOn, savedOn fields.

Contributing

Please see our Contributing Guideline which explains repo organization, linting, testing, and other steps.

License

This project is licensed under the terms of the MIT license.

commodo's People

Contributors

adrians5j avatar ashu96 avatar pavel910 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

commodo's Issues

withFields - list values not checking "dirty" flag correctly

When having a list of values, assigning anything to the field will actually mark the field as dirty, even if a list was completely the same.

This is mainly because fields receive new array instances, and currently that will make the field consider itself dirty, which is in most cases just wrong.

We need to implement a smarter way of checking the received values, and only mark the field dirty when that's truly the case.

How to create model with ref field with self - e.g. User with friends

Hello.
I want to know how create model from the title. Belllow what I want to achive

const User = pipe(
  withName('User'),
  withFields({
    name: string(),
    friends: ref({
      list: true,
      instanceOf: User,
    }),
  })
)();

const user1 = User()
user1.populate({
  name: 'User 1'
})

const user2 = User()
user2.populate({
  name: 'User 2',
  friends: [user1]
})

Adding Table of Content to Readme.md

The idea for Adding Table of Content to Readme.md to add additional enhancement and readability to the docs file.

I would like to work on this issue.

Validation of `list:true` fields - missing indexes

Currently, for list: true fields, we just iterate over values, and if a value is invalid, we throw an error and stop the process.

And even more importantly, the error that is thrown, doesn't tell on which index in the list the error occurred, just the field name.

image

In previous version, we had the exact index, so let's try to bring that back.

withFields - on the "fields" field, add "id" field for better handling of lists

Currently, when you send data to a list of models (using fields field), the new data is just assigned and the old is lost.

This means that the data will always be marked as dirty, when in fact, only one field might have been changed. This prevents the users to do isDirty() checks completely.

By assigning IDs (a simple "id" field for every item), we can actually efficiently compare received data and current data in the field, and completely fix the problem explained above.

P.S. This should also tackle the following value: [] we needed to make here:

fields({ list: true, instanceOf: LockedFieldsModel, value: [] })

withStorage - allow arbitrary and composite PKs

Introduction

Currently, the withStorage layer works with the id field as the entity's PK. The value of the id field is always a simple MongoDB ID.

This is not good for a couple of reasons.

  1. In some databases, like e.g. DynamoDB, the user basically never uses a randomly generated ID for PKs. So, this means we need to ensure that users can use any arbitrary string as the PK.

  2. Users cannot define a composite PK - a key that consists of two or more fields.

The solution

We need to allow users to use any arbitrary string as the PK, and we need to allow defining composite PKs.

Implementation details

TBD

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.