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

schema-llama

v0.1.3

Published

A more purely javascript es6 class generator. Takes the work out of validation, type checking, and more.

Downloads

15

Readme

Schema-Llama

npm install --save [email protected]

TOC

  1. Preamble
  2. Introduction
  3. Rational
  4. Usage
  5. Api

Preamble

~~We have not written this project yet. It's currently in its conception and is going down the experimental development mode. Star the repo and see when we get it done.~~

v0.0.8: We just finished the initial alpha build of this package. It's pushed to NPM, but things could change some more.

Thanks!

Introduction

This project is spawned out of a desire for pure javascript libraries that save time in long term projects. The basic concept behind this library is to take a "logically described schema" and transform it into an es6 class you can play with like any other es6 class you love. Love ES6 More!

So...take my schema:

const llamaSchema = {
  name: String,
  dob: Date,
  age: Number
}

And use powerful llama powers to create this class with baked-in validation:

class Llama {
  name: String;
  dob: Date;
  age: Number;
  constructor({ name, dob, age }) {
    this.name = name;
    this.dob = dob;
    this.age = age;
  }
  set name(value) {
    if(value.constructor !== String) {
      throw TypeError('Invalid name provided.');
    }
    this.name = value;
  }
  set dob(value) {
    if(value.constructor !== String || value.constructor !== Date) {
      throw TypeError('You must provide a valid date value.');
    }
    if(value.constructor === String) { value = new Date(value); }

    return value;
  }
  set age(value) {
    if(value.constructor !== String || value.constructor !== Number) {
      throw TypeError('You must provide a valid number');
    }
    if(value.constructor === String) { value = new Number(value); }
    return value;
  }
}

Rational

WHY DO THIS!?

Good question. Because we can. No, just kidding: because I had a difficult time finding a library that treats schemas like classes. Commonly other libraries manage their schema methods and statics like this:

const Llama = new Schema({
  name: String,
  dob: Date,
  age: Number
})

Llama.methods.eatGrass = function () {
  /**Implement eating grass?**/
}
Llama.statics.birthLlama() = function () {
  /** Implement static method to create a new lama. **/
}

This is OK, I guess; but I want the clean feeling of ES6 classes with my method declarations:


class Llama extends Schema({
  name: String,
  dob: Date,
  age: Number
})(/** we can pass in other settings here.**/) {
  eatGrass() {
    return `Llama ${this.name} ate some grass.`;
  }
  static birthLlama(name) {
    return new Lama({
      name,
      dob: Date.now(),
      age: 0
    });
  }
}

Essentially, the backbone of this project is a single factory function. The implementation of this class creator (Class Factory) function is as follows (Simplified):

export const convertSchemaToClass = schema => {
  return (ParentClass) => {
    if(ParentClass && ParentClass.constructor !== Function) { throw TypeError('You can only schemify classes.'); }

    const primitives = [ String, Date, Number, Symbol ];
    const classer = ParentClass ? (
      class extends ParentClass { constructor(props) { super(props); } }      
    ) : (
      class { constructor(props) { super(props); } }
    );

    const keys = Object.keys(schema);
    /** Do some magic to turn schema into class accessor methods. **/
    for(const key of keys) {
      const keySymbol = Symbol(key);
      Object.defineProperty(classer.prototype, key, {
        enumerable: true,
        configurable: false,
        set: function(value) {
          const Primitive = primitives.find(Primitive => schema[key] === Primitive);
          if(Primitive) {
            value = new Primitive(value);
          }
          if(!(value.constructor === schema[key])) {
            throw new TypeError(`You cannot set ${key} value to type ${value.constructor.name}. Use ${schema[key].name}`);
          }
          this[keySymbol] = value;
        },
        get: function() { return this[keySymbol] }
      });
    }

    return classer;
  }
}

Usage

You don't really need to know how it works for you to use it:

Steps

  1. Import the class factory.
import Schema from 'schema-llama';
  1. Create a schema object.

Objects contain properties with constructors that define what kind of item can be stored in that property in your class. You can set a schema property to either 1) a class constructor or 2) a validator function.

const schemaObject = {
  name: String,
  dob: Date,
  vehicle: Vehicle, //Imaginary constructor
  email: EmailValidator //Imaginary validator in the form (value) => { return value; }
}
  1. Create the es6 class with options. Schema(schemaObject)(options [, ParentClass ])
const schemaOptions = {
  attemptCast: false,
  mapNullToEmptyArray: false
}

const SchemaClass = Schema(schemaObject)(schemaOptions);

class MyNewClass extends SchemaClass {
  /** Add methods and such. **/
}

Schema Options:

  • attemptCast: Boolean - default: false
  • required: ?Array - Array of property names that are required.
  1. (optional) You can put all of this together into one sleek call:
class Parent {
  /** Parent class methods and props. **/
}

class Child extends Schema({
  name: String,
  dob: Date,
  vehicle: Vehicle, //Imaginary constructor
  email: EmailValidator //Imaginary validator in the form (value) => { return value; }
})({
  attemptCast: false
}, Parent) {
  /** Add class methods and such. **/
}

Examples

Simple Example

import Schema from 'schema-llama';

const Llama = Schema({
  name: String,
  dob: Date,
  age: Number
})();

class FunnyLlama extends Llama { //Class that becomes a subclass of llama schema.
  constructor(props) {
    super(props); //Do annoying magic.
  }
  getNextAge() {
    return this.age + 1;
  }
}

Embedded Schemas Example

import Schema from 'schema-llama';

class Llama extends Schema({
  name: String,
  dob: Date,
  age: Number,
  panchos: [ { price: Number } ]
})() {
  constructor(props) {
    super(props);
  }
  sumPanchos() {
    return panchos.reduce((a, b) => a.price + b.price, 0);
  }
}

Embedded ES6 Class Example:

This is really where the rubber hits the road with this idea. This schema would intuitively allow you to pass class definitions as "Types" for your property definitions.

Let's say that we want to handle pancho operations instead of having them act like pure objects?

class Pancho extends Schema({
  price: Number
})() {
  constructor(props) {
    super(props);
  }
  getPriceWithTax(tax) {
    return this.price * tax + 0.13;
  }
}

//NOTE: You do not have to use Schema! You can just as easily use a pure JS class.

class Llama extends Schema({
  name: String,
  dob: Date,
  age: Number,
  panchos: [ Pancho ],
  email: EmailAddress({ minLength: 10, maxLength: 100, allowedServers: ['gmail' ] })
})() {
  constructor(props) {
    super(props);
  }
  sumPanchos(withTax = false) {
    if(withTax) {
      return panchos.reduce((a,b) => a + b.getPriceWithTax(), 0);
    }
    return panchos.reduce((a, b) => a + b.price, 0);
  }
}

Embedded Validator Example:

So, let's say I want to create a String validator called EmailAddress?

import Schema from 'schema-llama';

const SimpleEmailAddress = function(value) {
  //Throw an error if the email address is invalid.
  return String(value);
}

//We can even be smart and do settings with a factory
const EmailAddress = ({ minLength, maxLength, allowedServers}) => {
  return function(value) {
    //Do validation logic...
    return new String(value)
  }
}

class Llama extends Schema({
  name: String,
  dob: Date,
  age: Number,
  panchos: [ { price: Number } ],
  email: EmailAddress({ minLength: 10, maxLength: 100, allowedServers: ['gmail' ] })
})() {
  constructor(props) {
    super(props);
  }
  sumPanchos() {
    return panchos.reduce((a, b) => a.price + b.price, 0);
  }
}

Validator Helper Library

import Schema, { Validators } from 'schema-llama';
  1. Enum([ String ])
  2. Number(settings: { min: Number, max: Number, type: ['int', 'double'] })
  3. String(settings: { min: Number, max: Number, match: RegExp })

Custom Error Handling

Before using the schema code anywhere, use this to override the error classes.

import Schema from 'schema-llama';

//Override Schema ErrorHandling
Schema.Error = MyErrorClass;
Schema.TypeError = MyTypeErrorClass;
Schema.ValidationError = MyValidationErrorClass;

Get/Set Hooks

This is probably most useful to me, but I could see it being useful for some architectures.

For example, we may want to store a vanilla JS Date in the entity, but would like to have a Moment object when we access the data.

class Llama extends Schema({
  name: String,
  dob: Date,
  age: Number,
})({
  get: (value, TypeClass, key) => {
    if(TypeClass === Date) {
      return moment(value);
    }
  },
  set: (value, TypeClass, key) => {
    if(TypeClass === Date) {
      return value.toDate()
    }
  }
}) {
  constructor(props) {
    super(props);
  }
}

const llamaface = new Llama();
llamaface.name = 'JerryLlama';
llamaface.dob = moment('Jan 1, 2017', 'MMM D, YYYY'); //Stores pure JS date.
llamaface.age = 10;

API

All object instantiated using schema-llama will have the following methods built in:

  • toJSON(): Returns a pure object minus any schema-llama methods and hidden properties.