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

speckoloo

v0.10.0

Published

Domain entites inspired by Speck

Downloads

8

Readme

Speckoloo

Node Version Coverage Status JavaScript Style Guide License: MIT

Domain entities inspired by Speck.

ToC

Motivation

Domain Driven Design is a total new beast when it comes to Node.js/Javascript development. There are not many tools out there to help us.

I was presented to Speck around May'17 by @guisouza, a great piece of work made by him and his former colleagues at Sprinkler.

However, I could not agree to some parts of its design rationale, such as the use of classes, react proptypes and reliance on the instanceof operator, so I decided to implement my own version.

To be clear, this is mostly a matter of style and preference. There is a long debate around this and no side is a clear winner, but I tend to agree more with one of them.

Design rationale

This library is based on two key concepts:

I prefer OLOO because attempts on simulating classical inheritance are flawed and unneeded in Javascript. Constructors are essentially broken and the workaround leads to lots of almost-the-same-but-not-quite code, so we cannot rely on .constructor properties from objects.

Nominal typing works (barely) only for primitive types (remember that typeof null === 'object' :expressionless:), let alone for complex types — see instanceof lies — so I also rather use duck typing instead of nominal typing. speckoloo relies on duck typing for entity construction, but exposes a nominal typing feature through the constructor properties of the entities (see docs).

This might cause some discomfort for those used to static-typed languages — coincidentally those where DDD is more widespread — but the main point of duck typing is that it's the caller job to honor his side of the contract. So, if you are using this library, but are being unpolite, it will probably blow up on your face. Still, I'll try to provide the most descriptive error messages as possible.

Furthermore, this library will, as much as possible, avoid code duplication for the clients using it and be validation framework agnostic.

Functionalities

speckoloo provides the following capabilities:

  • State storage: holds data belonging to the entities
  • Data validation: validates entity data against a provided schema.
  • Data (de)serialization: converts data from and to plain old JSON objects.
  • Data composition: allows entities to reference one another.

All functions related to the above concerns will be called synchronously.

Interfaces

Each object created by speckoloo factories implements the Validatable and the JSONSerializable interfaces, defined bellow:

interface ErrorDetail {
  [property: String]: String
}

interface ValidationError {
  name: 'ValidationError'
  message: String,
  details: ErrorDetail
}

interface Validatable {
  validate(context?: String) => this, throws: ValidationError | Error
}

interface JSONSerializable {
  toJSON(context?: String) => Object, throws Error
}
  • toJSON: returns a raw JSON object with the properties present in the entity.
  • validate: returns the object itself if its data is valid or throws a ValidationError

Both toJSON and validate methods are context-aware and may receive an optional context param. If such context doesn't exist, both will throw an Error.

Installation

NPM

npm install --save speckoloo

Manually

git clone https://github.com/hbarcelos/speckoloo.git
cd speckoloo
npm install
npm run build

Usage

Schemas

Basic structure

To define a schema, simply create an object whose keys are the possible properties descriptors:

import { factoryFor } from 'speckoloo'

const mySchema = {
  myProp1: {},
  myProp2: {},
  myProp3: {}
}

const myEntityFactory = factoryFor(mySchema)

const instance = myEntityFactory({
  myProp1: 'a',
  myProp2: 'b',
  myProp3: 'c'
})

Default value

To define a schema with a default value for a given property, add default to the schema definition.

Whenever a property value is missing when the entity factory is called, the default value is set:

import { factoryFor } from 'speckoloo'

const mySchema = {
  myProp1: {
    default: 'myValue'
  }
}

const factory = factoryFor(mySchema)

const instance = factory({})

console.log(instance.toJSON())

Ouptut:

{
  myProp1: 'myValue'
}

NOTICE: if a factory property is set for a given property, it will be called with the default value to obtain the final value of the property:

import { factoryFor } from 'speckoloo'

const mySchema = {
  myProp1: {
    default: '1',
    factory: Number
  }
}

const factory = factoryFor(mySchema)

const instance = factory({})

console.log(typeof instance.myProp1)

Output:

'number'
Ignoring default values

It is possible to make the factory ignore all default values, using the ignoreDefaults option in its second parameter.

This is useful when creating a patch for a given entity, without having to build it entirely first. This way, when such data is merged, the current value is not overwritten.

import { factoryFor } from 'speckoloo'

const mySchema = {
  myProp1: {
    default: 'myValue'
  }
}

const factory = factoryFor(mySchema)

const instance = factory({}, {
  ignoreDefaults: true
})

console.log(instance.toJSON())

Output:

{}

Read-only properties

To define a schema with read-only properties, add readOnly to the schema definition.

When a property is read-only, its value will remain the same as the time of instantiation. Any attempt on set a value for such property will throw a TypeError:

const schema = {
  prop1: {
    readOnly: true
  }
}

const factory = factoryFor(schema)

const instance = factory({
  prop1: '__initial__'
})

instance.prop1 = '__new__' // <-- throws TypeError

Validator

To define a schema with validators, add a property validator of type Validator.

Validators are functions with the following interface:

interface PropertyValidationError {
   error: String | Object
}

Validator(propertyValue: Any, propertyName: String, data: Object) => PropertyValidationError | Any

If the validator returns a ValidationError, then it's interpreted as the validation has failed for that property. If it return anything else, then it's interpreted as it succeeded.

Default validators

speckoloo provides 3 default validators:

  • allowAny: allows any value
  • forbidAny: forbids any value
  • delegate(context?: string, options?:object): delegates the validation to a nested entity.

If no validator is provided for a property, allowAny will be used by default:

const mySchema = {
  myProp1: {}
  myProp2: {
    validator: defaultValidators.allowAny // the same as above
  }
}
Creating custom validators
const requiredString = (value, key) => {
  if (value !== String(value)) {
    return {
        error: `Value ${key} must be a string.`
    }
  }
  // Returning `undefined` means the validation has passed
}

const mySchema = {
  myProp1: {
    validator: requiredString
  },
  myProp2: {
    validator: requiredString
  },
  myProp3: {
    validator: requiredString
  }
}
Using validation adapters

It is possible to use popular validation libraries with speckoloo, by wrapping the validation into a function of type Validator.

Currently the default adapters available are:

  • joiAdapter: adapts a joi function

You need to install joi as a peer dependency to be able to use it:

# Install peer dependency
npm install --save joi

Then:

import { factoryFor } from 'speckoloo'
import joiAdapter from 'speckoloo/dist/adapters/joi'

const schema = {
  id: {
    validator: joiAdapter(Joi.string().alphanum().required())
  }
  ...
}

export default factoryFor(schema)

Optional properties

To make a property optional, there is the skippable property in the schema definition.

When set to true, it means that the validator will not be called when data is present.

This is useful because it doesn't force the definition of two validators to handle the case when an empty value is allowed:

const string = (value, key) => {
  if (value !== String(value)) {
    return {
        error: `Value ${key} must be a string.`
    }
  }
  // Returning `undefined` means the validation has passed
}

const mySchema = {
  myProp1: {
    validator: string,
    skippable: true
  }
  //...
}

When myProp1 is not set, the validator will not run.

This is specially useful when used in context definition (see $skip).

Factory

To transform data on creation, there is the factory property in the schema definition.

Factories are functions with the following signature:

Factory(value: Any) => Any

Example:

import { factoryFor } from 'speckoloo'

const mySchema = {
  myProp1: {
    factory: String // converts any input data to string
  },
  myProp2: {}
}

const MyFactory = factoryFor(mySchema)

const instance = MyFactory({
  myProp1: 1,
  myProp2: 2
})

console.log(instance.myProp1)

Output:

'1' // <--- this is a string

NOTICE: factory is called only when property value is not undefined:

const instance = MyFactory({
  myProp2: 2
})

console.log(instance)

Output:

{ myProp2: 2 }

Methods

An important part of DDD rationale is that entities should be self-contained in terms of business logic that relates only to them.

For example, imagine the domain is a simple drawing application, where there are the entities Square and Circle.. One of the functionalities is ot calculate the area of the drawn objects. The logic for calculating the area of the object should be within itself. To achive this with speckoloo, there is the special property $methods in schema definition:

import { factoryFor } from 'speckoloo'

const circleSchema = {
  radius: {
    validatior: positiveNumber,
    factory: Number
  },
  $methods: {
    getArea() {
      return Math.PI * this.radius * this.radius
    }
  }
}

const squareSchema = {
  side: {
    validatior: positiveNumber,
    factory: Number
  },
  $methods: {
    getArea() {
      return this.side * this.side
    }
  }
}

const circleFactory = factoryFor(circleSchema)
const squareFactory = factoryFor(squareSchema)

const circle = circleFactory({ radius: 2 })
const square = squareFactory({ side: 4 })

console.log('Circle area: ', circle.getArea())
console.log('Square area: ', square.getArea())

Output:

Circle area: 12.566370614359172
Square area: 16

All functions in $methods are attached to the entity — so this refers to the entity itself — as non-enumerable, non-configurable, read-only properties.

Contexts

Entities might have different validation rules for different contexts. Some properties might be required for one context, but not for another.

To create a context, there's a special property called $contexts that can be used in the schema definition. The $contexts object has the following structure:

{
  [contextName]: {
     [operator]: Object | Array
  }
}

Currently available operators are:

  • $include: considers only the given properties for the context.
  • $exclude: removes the given properties from the context.
  • $modify: changes the validators for the specified properties.
  • $skip: skips the validation if data is not present.

Consider a User entity:

const schema = {
  id: requiredString,
  name: requiredString,
  email: requiredEmail,
  password: requiredString,
}
Exclude properties

When creating/registering a User, probably id will not be available yet, so this property should be ignored in context 'create'.

const schema = {
  id: requiredString,
  name: requiredString,
  email: requiredEmail,
  password: requiredString,
  $contexts: {
    create: {
      $exclude: ['id']
    }
  }
}
Include only some properties

Also, when such User is trying to login into an application, the only properties required are email and password. So there is also a login context defined as follows:

const schema = {
  id: requiredString,
  name: requiredString,
  email: requiredEmail,
  password: requiredString,
  $contexts: {
    create: {
      $exclude: ['id']
    },
    login: {
      $include: ['email', 'password']
    }
  }
}

NOTICE: You SHOULD NOT use both $include and $exclude in the same context definition. Doing so will trigger a process warning and $include will take precedence.

Modifying the validator of a property

During creation, password strength must be enforced. For all other contexts, it could simply be a valid string. To achive this, there is the $modify operator, that allows changing the validator for a property only for the context.

Instead of an array, $modify expects an object containing property-validator pairs, such as follows:

const schema = {
  id: requiredString,
  name: requiredString,
  email: requiredEmail,
  password: requiredString,
  $contexts: {
    create: {
      $exclude: ['id'],
      $modify: {
        password: passwordStrengthChecker
      }
    },
    login: {
      $include: ['email', 'password']
    }
  }
}

NOTICE: $modify can be combined with both $includes and $excludes, but will only be applied for properties included or not excluded, respectively. Example:

const schema = {
  prop1: requiredString,
  prop2: requiredString,
  $contexts: {
    context1: {
      $exclude: ['prop1'],
      $modify: {
        prop1: otherValidator // will be silently ignored
      }
    },
    context2: {
      $include: ['prop1'],
      $modify: {
        prop2: otherValidator // will be silently ignored
      }
    }
  }
}
Skipping validation

Sometimes there's a need to allow skipping validation when data is not present. The most common use case is entity patching, when only a subset of the entity is provided.

Until 0.4.x, to do that you needed to redeclare the validator for a property on a given context:

const schema = {
  prop1: requiredString,
  prop2: requiredString,
  $contexts: {
    context1: {
      $modify: {
        prop1: string // <--- a validator that allows a string to be empty
      }
    }
  }
}

As of 0.5.0, there is a new operator called $skip, that shortens the code above to:

const schema = {
  prop1: requiredString,
  prop2: requiredString,
  $contexts: {
    context1: {
      $skip: ['prop1']
    }
  }
}

NOTICE: the main difference between $exclude and $skip is that the former will completely exclude the property from schema definition, regardless it's present on data or not. The latter will only skip the validation when the value is not present (=== undefined); if you provide an invalid value, the validation will fail.

Validation

validate will throw a ValidationError when data is invalid or return the object itself otherwise (use this for chaining).

Default validation

import { factoryFor } from 'speckoloo'

const requiredString = (value, key) => {
  if (value !== String(value)) {
    return {
        error: `${key} must be a string.`
    }
  }
  // Returning `undefined` means the validation has passed
}

const mySchema = {
  myProp1: {
    validator: requiredString
  },
  myProp2: {
    validator: requiredString
  },
  myProp3: {
    validator: requiredString
  }
}

const MyEntityFactory = factoryFor(mySchema)

const instance = MyEntityFactory({
  myProp1: 1,
  myProp2: 2,
  myProp3: 3
})

instance.validate() // throws an error

The code above will throw a ValidationError like the following:

{
  name: 'ValidationError'
  message: 'Validation Error!'
  details: {
    myProp1: 'myProp1 must be a string.',
    myProp2: 'myProp2 must be a string.',
    myProp3: 'myProp3 must be a string.'
  }
}

Context-aware validation

To make use of the defined contexts for validation, there is the context param of validate().

import { factoryFor } from 'speckoloo'

const schema = {
  id: requiredString,
  name: requiredString,
  email: requiredEmail,
  password: requiredString,
  $contexts: {
    create: {
      $exclude: ['id'],
      $modify: {
        password: passwordStrengthChecker
      }
    },
    login: {
      $include: ['email', 'password']
    }
  }
}

const UserFactory = factoryFor(schema)

const user = UserFactory({
  email: '[email protected]',
  password: 'dummyPass@1234'
})

user.validate('login') // doesn't throw!

Serialization

Default serialization

toJSON will return a raw JSON object with the properties present in the entity.

NOTICE: toJSON WILL NOT validate data before returning it. If an invalid value is present for a given property, it will be returned as is.

From the exact same example above:

const instance = MyEntityFactory({
  myProp1: 1,
  myProp2: 2,
  myProp3: 3
})

instance.toJSON() // ignores validation

Output:

{
  myProp1: 1,
  myProp2: 2,
  myProp3: 3
}

Chaining toJSON() after validate() is a way to be sure only valid data is sent forward:

getDataSomehow()
 .then(MyEntityFactory)
 .then(instance =>
    instance.validate()
      .toJSON())

Context-aware serialization

The defined contexts can also be used to build a raw JSON object. There is also the context param of toJSON().

import { factoryFor } from 'speckoloo'

const schema = {
  id: requiredString,
  name: requiredString,
  email: requiredEmail,
  password: requiredString,
  $contexts: {
    create: {
      $exclude: ['id'],
      $modify: {
        password: passwordStrengthChecker
      }
    },
    login: {
      $include: ['email', 'password']
    }
  }
}

const UserFactory = factoryFor(schema)

const user = UserFactory({
  id: '1234'
  name: 'SomeUser',
  email: '[email protected]',
  password: 'dummyPass@1234'
})

console.log(user.toJSON('login'))

Output:

{
  name: 'SomeUser',
  email: '[email protected]'
}

Type Checking

speckoloo factories will optimistically try to coerce entities of compatible schemas through duck typing.

import { factoryFor } from 'speckoloo'

const mySchema1 = {
  myProp1: {},
  myProp2: {}
  // missing myProp3
}

const mySchema2 = {
  myProp1: {},
  myProp2: {},
  myProp3: {}
}

const myEntityFactory = factoryFor(mySchema1)
const anotherEntityFactory = factoryFor(mySchema2)

const anotherInstance = anotherEntityFactory({
  myProp1: 'a',
  myProp2: 'b',
  myProp3: 'c'
})

// Will mostly work if the schemas are compatible:
const duckTypedInstance = myEntityFactory(anotherInstance)

duckTypedInstance.validate() // does not throw because myProp3 is not required
console.log(duckTypedInstance.toJSON()) // { myProp1: "a", myProp2: "b" }

For cases where duck typing is not desired, entities expose a constructor property that point to the entity factory. The easiest way to check if a entity is an instance of a certain factory is by doing identity check (===) in such property:

import { factoryFor } from 'speckoloo'

const mySchema = {
  myProp1: {},
  myProp2: {},
  myProp3: {}
}

const myEntityFactory = factoryFor(mySchema)
const anotherEntityFactory = factoryFor(mySchema)

const instance = myEntityFactory({
  myProp1: 'a',
  myProp2: 'b',
  myProp3: 'c'
})

const anotherInstance = anotherEntityFactory({
  myProp1: 'a',
  myProp2: 'b',
  myProp3: 'c'
})

const duckTypedInstance = myEntityFactory(anotherInstance)

console.log(instance.constructor === myEntityFactory) // true
console.log(anotherInstance.constructor === myEntityFactory) // false (no duck typing here!)
console.log(duckTypedInstance.constructor === myEntityFactory) // true

Composite entities

When an entity contains a reference to other entity, to automatically convert raw data into a nested entity, use the factory property on schema definition:

const childSchema = {
  childProp1: {
    validator: requiredString
  },
  childProp2: {
    validator: requiredString
  }
}

const ChildFactory = factoryFor(childSchema)

const parentSchema = {
  prop1: {
    validator: requiredString
  }
  child: {
    factory: ChildFactory
  }
}

const ParentFactory = factoryFor(parentSchema)

const instance = ParentFactory({
  prop1: 1
  child: {
    childProp1: 'a',
    childProp2: 'b'
  }
})

In the example above, child will be an entity created by ChildFactory.

Composite entities serialization

When calling toJSON() on a composite entity, it will automatically convert the nested entity by calling toJSON() on it as well.

From the example above:

const instance = ParentFactory({
  prop1: 1
  child: {
    childProp1: 'a',
    childProp2: 'b'
  }
})

console.log(instance.toJSON())

Will output:

{
  prop1: 1
  child: {
    childProp1: 'a',
    childProp2: 'b'
  }
}

Composite entities validation

Since the default validator for any property is allowAny, to make a composite entity validate the nested one requires an explicit validator that will delegate the validation process to the latest.

Since this is a rather common use case, speckoloo provides a default validator called delegate.

Redefining the parentSchema above:

import { factoryFor, defaultValidators } from 'speckoloo'

// ...
const parentSchema = {
  prop1: {
    validator: requiredString
  }
  child: {
    factory: ChildFactory,
    validator: defaultValidators.delegate() // <--- Notice that `delegate` is a function!
  }
}

//...

const instance = ParentFactory({
    prop1: 1,
    child: {
        childProp1: 1,
        childProp2: 2
    }
})

instance.validate() // throws an error

Will throw:

{
  name: 'ValidationError'
  message: 'Validation Error!'
  details: {
    prop1: 'prop1 must be a string.',
    child: {
      childProp1: 'childProp1 must be a string.',
      childProp1: 'childProp1 must be a string.'
    }
  }
}

It's possible to specify a context for the delegate function, that will be forwarded to the nested entity validate() method:

// ...

const parentSchema = {
  prop1: {
    validator: requiredString
  }
  child: {
    factory: ChildFactory,
    validator: defaultValidators.delegate('someContext')
  }
}

// ...

instance.validate() // <--- Will call child.validate('someContext')

NOTICE: By default, delegate will not validate the entity when data is missing.

const parentSchema = {
  prop1: {
    validator: requiredString
  }
  child: {
    factory: ChildFactory,
    validator: defaultValidators.delegate()
  }
}

const parentFactory = factoryFor(parentSchema)

const instance = parentFactory({
  prop1: 'a'
})

// ...

instance.validate() // <--- Will return the instance itself

To change this behavior, there is a required param in options that can be used:

const parentSchema = {
  prop1: {
    validator: requiredString
  }
  child: {
    factory: ChildFactory,
    validator: defaultValidators.delegate({ required: true }) // <----- changed here
  }
}

const parentFactory = factoryFor(parentSchema)

const instance = parentFactory({
  prop1: 'a'
})

// ...

instance.validate() // <--- Will throw

Output:

{
  name: 'ValidationError',
  message: 'Validation Error!',
  details: {
    child: '`child` is required'
  }
}

It's also possible to combine both context with options. Use context as first argument and options as second:

defaultValidators.delegate('myContext', { required: true })

A common use case is validating the composite entity on context "A", in which the nested entity must be in context "B". In this case, delegate can be combined with $modify operator for context definition:

// ...

const parentSchema = {
  prop1: {
    validator: requiredString
  }
  child: {
    factory: ChildFactory,
    validator: defaultValidators.delegate()
  },
  $contexts: {
    A: {
      $modify: {
        child: defaultValidators.delegate('B')
      }
    }
  }
}

// ...

instance.validate('A') // <--- Will call child.validate('B')

Collection entities

Collection entities represent a list of individual entities. As an individual entity, they implement both Validatable and JSONSerializable.

It also implements the RandomAccessibleList to allow an array-like access to its individual properties.

interface RandomAccessibleList {
  at(n: Number) => Any
}

Furthermore, they implement the native ES6 Iterable interface.

Example:

import { factoryFor, collectionFactoryFor } from 'speckoloo'

const entitySchema = {
  myProp1: {
    validator: requiredString
  }
}

const entityFactory = factoryFor(entitySchema)

const collectionFactory = collectionFactoryFor(entityFactory)

const collection = collectionFactory([{
    myProp1: 'a'
}, {
    myProp1: 'b'
}])

Get an entity from a collection

Use the at method:

console.log(collection.at(0).toJSON())

Output:

{ myProp1: 'a' }

Iterate over a collection

Collections can be iterated with a for..of loop as regular arrays:

for (let entity of collection) {
  console.log(entity.toJSON())
}

Output:

{ myProp1: 'a' }
{ myProp1: 'b' }

However, common array operations as map, filter or reduce are not implemented. To use them, first convert a collection to a regular array using Array.from:

console.log(Array.from(collection).map(entity => entity.myProp1))

Output:

['a', 'b']

Collection serialization

Calling toJSON on a collection will generate a regular array containing plain JSON objects, created by calling toJSON on each individual entity in the collection:

console.log(collection.toJSON())

Output:

[
  { myProp1: 'a' },
  { myProp1: 'b' }
]

When passing the optional context parameter, its value will be used when calling toJSON on each individual entity.

Collection validation

Calling validate will return a reference to the collection itself if all entities are valid. Otherwise, it will throw an array of ValidationError, containing the validation errors for the invalid entities:

import { factoryFor, collectionFactoryFor } from 'speckoloo'

const entitySchema = {
  myProp1: {
    validator: requiredString
  }
}

const entityFactory = factoryFor(entitySchema)

const collectionFactory = collectionFactoryFor(entityFactory)

const collection = collectionFactory([{
    myProp1: 1
}, {
    myProp1: 'a'
}, {
    myProp1: 2
}])

collection.validate() // <--- will throw!

Output:

{
  'item#0': {
    myProp1: 'Value myProp1 must be a string.'
  },
  'item#2': {
    myProp1: 'Value myProp1 must be a string.'
  }
}

The item#<n> key indicates which of the entities in the collection are invalid.

Nested collections

It's possible to use collections as nested entitties for a composite entity. All it takes is put a collection factory into a factory property from the schema.

Example:

const singleSchema = {
  prop1: {},
  prop2: {}
}

const singleFactory = factoryFor(singleSchema)

const collectionFactory = subject(singleFactory)

const compositeSchema = {
  compositeProp1: {
    validator: requiredString
  },
  nestedCollection: {
    validator: defaultValidators.delegate(),
    factory: collectionFactory
  }
}

const compositeFactory = factoryFor(compositeSchema)

const instance = compositeFactory({
  compositeProp1: 'x'
  nestedCollection: [
    {
        prop1: 'a',
        prop2: 'b'
    },
    {
        prop1: 'c',
        prop2: 'd'
    }
  ]
})

instance.validate() // will call validate for each entity

Contributing

Feel free to open an issue, fork or create a PR.

speckoloo uses StandardJS and I'm willing to keep it production dependency-free.

Before creating a PR, make sure you run:

npm run lint && npm run test

Some missing features:

  • Support for getters and setters in schema definition.
  • Support for general collection methods, such as map, filter, reduce, etc.