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

muesli

v3.0.3

Published

Simple, ORM-agnostic domain driven model management

Downloads

5

Readme

Muesli

Simple, ORM-agnostic domain driven model management

npm version Greenkeeper badge Build Status Code Climate CircleCI dependencies:? devDependencies:?

Installation

npm install muesli

or

yarn add muesli

Optional packages(in TODO)

npm install muesli-filters
npm install muesli-validators
npm install muesli-constraints

Usage

Import Model class from muesli package.

import Model from 'muesli';
// or
const Model = require('muesli');

Define your model's props

class Book extends Model {
  
}
Book.props = {
  ISBN: {
    filter: 'string',
    value: '',
    constraints: [],
    validate: (value) => {},
    json: {},
  },
  title: {
    filter: 'string',
  },
  author: {
    filter: (value) => Author.fromJSON(value),
  },
  reference: {
    filter: (ref) => CustomModelStore.findByRef(ref),
  },
};

Add settings for your model

class Book extends Model {
  
}
Book.propsSettings = {
  nameStrategy: 'camel', // currently affected only during json serialization for attributes' keys
  
  strict: false, // if true is set, then model won't accept non schema attributes. It won't throw an error
  throwOnStrictError: false, // if true it will throw an error when `strict` is true and you are trying to set non schema attribute
  
  useGetters: true, // default to true -- give access to direct access to props via model.values object
  useSetters: true, // default to true

  validators: [ // model validators that can validate all object's values
    MuesliValidators.equalProps(['password', 'password-confirmation'], ['validation-group1']),
    MuesliValidators.validDates(['birthdate'], 'validation-group2')
  ]
};

| Setting name | Default value | Possible values | Description | |---|---|---|---| | nameStrategy | <empty string> | (camel OR pascal OR title OR snake OR lower OR upper OR constant) | Props names' serialization strategy. Look inside package https://www.npmjs.com/package/change-case | | strict | false | true OR false | if true is set, then model won't accept non schema props. | | throwOnStrictError | false | true OR false | if true is set and strict === true, model will throw an error when model tries to set non schema prop | | useGetters | true | true OR false | gives direct access to props values via model.values object | | useSetters | true | true OR false | | | validators | [] | | array of model validators that can validate through whole model |

Model validation

const book = Book.fromJSON({ ISBN: '1232412-123' });

book.validate()
  .then((validationErrors) => {
    if (validationErrors.length) {
      // Model doesn't pass validation constraints
    } else {
      // everything is good
    }
  })
  .catch((error) => {
    // FATAL errors occurred during validation process
    console.error(error);
  });

There is static method to make the same operation quicker

Book.validate({ ISBN: '12345123-123' })
  .then((validationErrors) => {
    if (validationErrors.length) {
      // Model doesn't pass validation constraints
    } else {
      // everything is good
    }
  })
  .catch((error) => {
    // FATAL errors occurred during validation process
    console.error(error);
  });

You can also validate only part of the model using validation group

book.validate(['group1'])
  .then((validationErrors) => {
    if (validationErrors.length) {
      // Model doesn't pass validation constraints
    } else {
      // everything is good
    }
  })
  .catch((error) => {
    // FATAL errors occurred during validation process
    console.error(error);
  });

Custom constraints and validators

Constraints and validators are asynchronous by default, but you not required to use Promises if you don't need to.

const customConstraint = (groups = []) => {
  return (propValue, propName, currentGroup) => {
    return new Promise((resolve, reject) => {
      if (!groups.includes(currentGroup)) {
        // It is very important that you handle validation groups inside custom constraint 
        resolve();
        return;
      }
      if (propValue !== 'custom value') {
        reject(new Error(`${propName} must be equals 'custom value'`));
        return;
      }
      resolve();
    });
  };
};
const customValidator = (groups = []) => {
  return (values, currentGroup) => {
    if (!groups.includes(currentGroup)) {
      // It is very important that you handle validation groups inside custom constraint 
      return;
    }
    if (values.password !== values.password_confirmation) {
      throw new Error('Password and password confirmation must be equal');
    }
    // You don't need to return anything if everything is fine
  };
};

Computed props

class Author extends Model {}
Author.props = {
  firstName: {
    filter: String,
    value: 'Dmytro',
  },
  lastName: {
    filter: String,
  },
  fullName: {
    filter: function (deps = []) {
      return deps.filter((v) => v).join(' ');
    },
    computed: ['firstName', 'lastName'],
  },
};
const author = new Author();

console.log(author.get('fullName')); // output -> 'Dmytro'
author.set('lastName', 'Zelenetskyi');
console.log(author.get('fullName')); // output -> 'Dmytro Zelenetskyi'

Deserializing and serializing model(fromJSON/toJSON)

class Author extends Model {}

Author.props = {
 name: {
   filter: 'string',
   constraints: [MusliConstraints.required()],
 },
 lastName: {
   filter: 'string',
 },
}; 

const horror = Author.fromJSON({
  name: 'Stephen',
  lastName: 'King',
});
console.log(horror instanceof Author);
// outputs `true`
 
console.log(horror.toJSON());
// outputs `{ name: "Stephen", lastName: "King" }`

Using useGetters and useSetters options

if useGetters or/and useSetters options are set to true, then you can use props' values directly via model.values object.

Example:

class Author extends Model {}
Author.props = {
 name: {
   filter: 'string',
   value: 'Default value',
 },
 age: {
   filter: 'number',
   value: 0,
 },
};
Author.propsSettings = {
  useGetters: true,
  useSetters: true,
};

const model = new Author();
console.log(model.values.name); // outputs: 'Default value'
console.log(model.values.age); // outputs: 0

// or you can use setter
model.values.age = 30;
console.log(model.values.age); // outputs: 30

model.values object can't be extended. Only props from schema are available. Setters won't be provided for computed properties.

Model version

With each model mutation model increases it's version, so you can track it.

const author = Author.fromJSON({ name: 'Stephen' });
console.log(model.version); // output: 1
model.set('name', 'Dmytro');
console.log(model.version); // output: 2

Creating ORM-like entities

const pg = require('pg');
const SQL = require('sql-template-strings')

class Entity extends Model {
  static get source() {
    throw new Error('Implement me');
  }
  
  static async findById(id) {
    const rows = await pool.query(SQL`SELECT * FROM "`.append(this.source).append(SQL`" WHERE id=${id}`));
    return this.fromJSON(rows[0]);
  }
}

class UserEntity extends Entity {
  static get source() {
    return 'users';
  }
  
  static get props() {
    return {
      first_name: {
        filter: 'string',
      },
      last_name: {
        filter: 'string',
      },
      password: {
        filter: 'string',
      },
    };
  }
}

const user = UserEntity.findById(1);
console.log(user.get('first_name'));
console.log(user.values);

Events

Model inherits event system from rx-emitter(github link) package that uses rxjs and handles all events as observables.

const book = new Book();

Rx.Observable.combineLatest(
  book.subject('chapter1'),
  book.subject('chapter2'),
)
.subscribe(() => {
  console.log('Two first chapters are ready');
});

book.publish('chapter1');
book.publish('chapter2');

License

Muesli is released under the MIT license.

Donate