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

speck-entity

v0.2.1

Published

Domain entities with reactive validation

Downloads

866

Readme

Speck

Speck - Let you create your domain entities with reactive validation based on React propTypes

Build Status

This package let you create entities with schema validation based on React PropTypes.

Installing

$ npm install speck-entity

Using

Sample Entities

const Joi = require('joi')
const joiAdapter = require('validatorAdapters')('joi', Joi)
const Speck = require('speck-entity')

class MyEntity extends Speck {
  static SCHEMA = {
    field: joiAdapter(Joi.string()),
    otherField: {
      validator: joiAdapter(Joi.number()),
      defaultValue: 10
    }
  }
}

class FatherEntity extends Speck {
  static SCHEMA = {
    children: {
      validator: joiAdapter(Joi.array().items(Joi.object().type(MyEntity)))
      type: MyEntity
    }
  }
}

Get default values

const niceInstance = new MyEntity();
console.log(niceInstance.toJSON()); // { field: undefined, otherField: 10 }
console.log(niceInstance.errors); // {}

Validations

const buggedInstance = new MyEntity({ field: 10, otherField: 'value' });
console.log(buggedInstance.toJSON()); // { field: 10, otherField: 'value' }
console.log(buggedInstance.errors); /* or buggedInstance.getErrors() -- but... getErrors also includes children errors
  {
    field: {
      errors: [ 'Invalid undefined `field` of type `number` supplied to `MyEntityEntity`, expected `string`.' ]
    },
    otherField: {
      errors: [ 'Invalid undefined `otherField` of type `string` supplied to `MyEntityEntity`, expected `number`.' ]
    }
  }
*/

Validate on change value

const otherInstance = new MyEntity({ field: 'myString' });
console.log(otherInstance.errors); // {}
console.log(otherInstance.valid); // true

otherInstance.field = 1;
console.log(otherInstance.errors); // {field: { errors: [ 'Invalid undefined `field` of type `number` supplied to `MyEntityEntity`, expected `string`.' ] }}
console.log(otherInstance.valid); // false

Parse children to Entity

const fatherInstance = new FatherEntity({
  children: [{
    field: 'A',
    otherField: 2
  }, {
    field: 'B',
    otherField: 3
  }]  
})
console.log(fatherInstance.children[0]); //An instance of MyEntity
console.log(fatherInstance.children[1].toJSON());
//{ field: 'B', otherField: 3 }

Builder

When you need to create objects with custom verification like

    const elementList = {
        elements: [{
          type: 'product',
          name: true,
          price:  true
        }, {
          type: 'default',
          isDefault: true
        }]
      };

In such cases you can define a builder as follows:

class ElementList extends Speck {}
ElementList.SCHEMA = {
  elements: {
    validator: noop,
    builder: (dataList, Type, dependencies) => dataList.map(data => {
      if (data.type === 'product') return new ProductEntity(data, dependencies);
      if (data.type === 'default') return new FakeEntityWithBoolean(data);
    })
  }
};

And use it like:

new ElementList(elementList, someDependency)

(note that you can pass custom dependencies to your child entities and latter access them on the builder)

By defining builder you tell Speck Entity that you take the responsibility of instansitating and returning a new object of the type which suits you the best. This is a powerful concept as it lets users dynamically create new types on the fly.

Clean unexpected values

const anotherInstance = new MyEntity({ field: 'myString', fake: 'fake' });
console.log(anotherInstance.toJSON()); // { field: 'myString', otherField: 10 }

To understand the validators React PropTypes

Well known issues

  • Create helpers for relationships validations(Like, mininum, maximum)
  • Create identifier and equal comparison
  • Type builders and/or custom builders are not being applied on instance setters

Contextual validation

  class FakeEntityWithExcludeContext extends Speck {
      static SCHEMA = {
          id: joiAdapter(Joi.number().required()),
          requiredProp1: joiAdapter(Joi.number().required()),
          requiredProp2: joiAdapter(Joi.number().required()),
          requiredProp3: joiAdapter(Joi.number().required())
      }

      static CONTEXTS = {
        create: { exclude: [ 'requiredProp2', 'requiredProp3' ] },
        edit: { include: [ 'id', 'requiredProp1', 'requiredProp2' ] },
        onlyId: { include: [ 'id' ] }        
      }
  }

   const myEntity = new FakeEntityWithIncludeContext({ id: 1 });

   const contextCreate = myEntity.validateContext('create');
   console.log(contextCreate.errors); // { requiredProp1: { errors: [ ... ] } }
   console.log(contextCreate.valid); // false

   const contextEdit = myEntity.validateContext('edit');
   console.log(contextEdit.errors); // { requiredProp1: { errors: [ ... ] }, requiredProp2: { errors: [ ... ] } }
   console.log(contextEdit.valid); // false

   const contextOnlyId = myEntity.validateContext('onlyId')
   console.log(contextOnlyId.errors); // {}
   console.log(contextOnlyId.valid); // true

Each context (create and edit in example above), could have include property OR exclude, the include property receives the properties that will be validated in this context, and the exclude property represents the properties that will be ignored on validation.

In the example, the create context, will only check the 'requiredProp1' and 'requiredProp2' fields, and the edit context will check 'requiredProp1', 'requiredProp2' and 'id' properties.

You can't combine include and exclude in the same context definition

##Custom validation You can validate your entity adding the property in fields and setting the new validator

  class Entity extends Speck {
    static SCHEMA = {
      id: joiAdapter(Joi.number().required()),
      requiredProp1: joiAdapter(Joi.number().required())
    }

    static CONTEXTS = {
      create: {
        fields: {
          requiredProp1: (obj, field) => {
            if(obj[field] === -1) return new Error('Error -1');
          }
        }
      }
    }
  }

  const entity = new Entity({
    id: 1,
    requiredProp1: -1
  });

  const contextValidated = entity.validateContext('create');
  console.log(entity.errors.requiredProp1); // undefined
  console.log(contextValidated.requiredProp1); // { errors: [Error: Error -1] }

Hooks

class EntityWithHook extends Speck {
    static SCHEMA = {
        fieldWithHook: {
          validator: joiAdapter(Joi.number()),
          hooks: {
            afterSet(data, fieldName) {
              // data is the whole data of the instance
              // fieldName the current fieldName
              // DO WHATEVER YOU WANT
            }
          }
        },
        anotherFieldWithHook: {
          validator: joiAdapter(Joi.number()),
          hooks: {
            afterSet(data, fieldName) {
              return { anotherField: data[fieldName] * 2 } // if the afterSet hook returns an object is merged to data
            }
          }
        },
        anotherField: joiAdapter(Joi.number()),
    }
}

const myEntity = new EntityWithHook({ fieldWithHook: 'foo', anotherFieldWithHook: 'bar', anotherField: null });

myEntity.anotherFieldWithHook = 10 //according to the after set hook anotherField newValue will be 20