‚̧Notoriously Punctual Manatee
    Wondering what‚Äôs next for npm?Check out our public roadmap! ¬Ľ

    speckoloo

    0.10.0¬†‚Äʬ†Public¬†‚Äʬ†Published

    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' ūüėĎ), 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: 'some.email@domain.com',
      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: 'some.email@domain.com',
      password: 'dummyPass@1234'
    })
     
    console.log(user.toJSON('login'))

    Output:

    {
      name: 'SomeUser',
      email: 'some.email@domain.com'
    }

    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.

    Keywords

    Install

    npm i speckoloo

    DownloadsWeekly Downloads

    4

    Version

    0.10.0

    License

    MIT

    Unpacked Size

    432 kB

    Total Files

    41

    Last publish

    Collaborators

    • avatar
    • avatar
    • avatar
    • avatar
    • avatar
    • avatar
    • avatar