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

value-object

v0.4.3

Published

value-object.js - simple value objects

Downloads

555

Readme

value-object.js

Value Object - objects that matter only as the combination of their properties. Two value objects with the same values for all their properties are considered equal.

This library provides a convenient way to define strict, immutable value objects.

Install

npm install value-object

Defining value objects

Use subclasses to define value objects with type constraints:

const ValueObject = require('value-object')

class Currency extends ValueObject.define({
  code: 'string',
  name: 'string'
}) {}

class Money extends ValueObject.define({
  currency: Currency,
  amount: 'number'
}) {}

...or don't use classes, if you prefer:

const Money = ValueObject.define({
  amount: 'number',
  currency: { code: 'string' }
})

Instantiating value objects

Use the new keyword, passing values for each property:

const gbp = new Currency({ code: 'GBP', name: 'British Pounds' })
const price = new Money({ currency: gbp, amount: 12.34 })
const other = new Money({ currency: { code: 'USD', name: 'US Dollars' }, amount: 14.56 })

Constraints prevent value objects from being instantiated with invalid property values.

Unexpected types

Property values with unexpected types are rejected:

> new Currency({ code: 'USD', name: 123 })
Error: Currency was constructed with invalid property values
  Expected: { code:string, name:string }
  Actual:   { code:string, name:number }
    name is invalid:
      Expected string, was number

Unrecognised properties

Value objects cannot be instantiated with unrecognised properties:

> new Currency({ code: 'NZD', name: 'New Zealand Dollars', colour: 'All black' })
Error: Currency was constructed with invalid property values
  Expected: { code:string, name:string }
  Actual:   { code:string, name:string, colour:string }
    colour is invalid:
      Property is unexpected

Missing properties

Value objects cannot be instantiated with missing properties (unless they are optional):

> new Money({ amount: 123 })
Error: Money was constructed with invalid property values
  Expected: { currency:Currency, amount:number }
  Actual:   { amount:number }
    currency is invalid:
      Property is missing

Setting properties to null

Properties can be set to null:

> new Money({ currency: null, amount: null })
Money { currency: null, amount: null }

Setting properties to undefined

Properties cannot be set to undefined (unless they are optional):

> new Money({ currency: null, amount: undefined })
Error: Money was constructed with invalid property values
  Expected: { currency:Currency, amount:number }
  Actual:   { currency:null, amount:undefined }
    amount is invalid:
      Expected number, was undefined

Built-in property types

Properties can be declared with built-in type constraints:

class Manager extends ValueObject.define({
  firstName: 'string',
  age: 'number',
  trained: 'boolean',
  subordinates: 'object',
  preferences: 'any'
}) {}
  • string: expects a value where typeof value === 'string'
  • number: expects a value where typeof value === 'number'
  • boolean: expects a value where typeof value === 'boolean'
  • object: expects a value where typeof value === 'object'
  • any: expects any non-null value

Optional properties

Properties declared with ? can be set to null or undefined, or omitted altogether:

class Options extends ValueObject.define({
  age: 'number?',
  aliases: 'object?',
  colour: 'string?',
  checked: 'boolean?'
}) {}

new Options({ age: null, aliases: {}, colour: undefined })
// => Options { age: null, aliases: {}, colour: undefined }

Optional properties can also be declared with ValueObject.optional():

class IceCream extends ValueObject.define({
  flavours: ValueObject.optional(['string'])
}) {}

new IceCream({ flavours: ['mint', 'chocolate'] })
// => IceCream { flavours: [ 'mint', 'chocolate' ] }

new IceCream({})
// => IceCream {}

Array properties

Arrays with arbitrary elements can be declared with the type Array:

class Person extends ValueObject.define({
  favouriteThings: Array
}) {}

new Person({ favouriteThings: ['cheese', 69, null] })

Generic array properties

Arrays with value constraints are declared by wrapping the type definition (e.g. 'number', Date) in []:

class Point extends ValueObject.define({
  x: 'number',
  y: 'number'
}) {}

class Polygon extends ValueObject.define({
  vertices: [Point] // instances of Point
}) {}

new Polygon({
  vertices: [
    new Point({ x: 1, y: 2 },
    new Point({ x: 3, y: 4 }
  )]
})

User-defined properties

Custom property types can be defined with ValueObject.definePropertyType() and then used later by name in ValueObject.define():

ValueObject.definePropertyType('money', () => ({
  coerce(value) {
    if (typeof value === 'string') {
      const parts = value.split(' ')
      return { value: { amount: Number(parts[0]), currency: parts[1] } }
    }
    return { failure: 'Only string values allowed' }
  },

  areEqual(a, b) {
    return a.currency == b.currency && a.amount == b.amount
  },

  describe() {
    return '<money>'
  }
}))
class Allowance extends ValueObject.define({ cash: 'money' }) {}

Property constraints are expressed as a function that returns a value with the following methods:

  • .coerce(value) converts an arbitrary value to the final property value. Expected to return { value } when converting the property value is successful or { failure } with a message when converting fails.
  • .areEqual(a, b) returns true if two instances of the type are considered equal, or false otherwise.
  • .describe() returns a string used in error messages mentioning the property.

The constraint is used to convert property values from other types according to its .coerce(value) method:

> new Allowance({ cash: '123.00 GBP' })
Allowance { cash: { amount: 123, currency: 'GBP' } }

...and its .describe() method is used in error messages:

> new Allowance({ cash: 666 })
Error: Allowance was constructed with invalid property values
   Expected: { cash:<money> }
   Actual:   { cash:number }
   cash is invalid:
     Only string values allowed

Equality

Value objects are considered to be equal if their properties are equal. Equality of two objects is tested by calling valueObject.isEqualTo(otherValueObject):

gbp.isEqualTo(new Currency({ code: 'GBP', name: 'British Pounds' }))
// => true

gbp.isEqualTo(new Currency({ code: 'EUR', name: 'Euros' }))
// => false

const gbpPrice = new Money({ amount: 123, currency: gbp })
const eurPrice = new Money({ amount: 123, currency: eur })
gbpPrice.isEqualTo(eurPrice)
// => false

eurPrice.isEqualTo(new Money({ amount: 123, currency: eur }))
// => true

Reflection

ValueObject types have a schema property that allows reflection over properties and arbitrary metadata associated with each property:

class Product extends ValueObject.define({
  name: 'string',
  stockLevel: ValueObject.property('number', {
    default: 0,
    description: 'units in stock'
  })
}) {}

> Product.schema.properties.stockLevel
Property {
  constraint: Primitive { cast: [Function: Number], name: 'number' },
  metadata: { default: 0, description: 'units in stock' },
  optional: false }

Creating new value objects from existing value objects

Use with(newAttributes) to create new value objects, with new values for a specific set of properties:

const salePrice = price.with({ amount: 12.0 })
salePrice.currency.code
// => 'GBP'

Converting value objects to plain objects

Use toPlainObject() to create a plain old mutable object from a value object's property values:

> JSON.stringify(gbp.toPlainObject())
{ "code": "GBP", "name": "British Pounds" }

Any value-object instances will be converted using their schemas. Any objects that are not value-object instances will be cloned using JSON.parse(JSON.stringify(object)) by default. Pass in an optional clone function to override this behaviour:

valueObject.toPlainObject(fancyDeepCloneFunction)

Converting value objects to JSON

Use toJSON() to create an object with __type__ properties for subsequent deserialization:

> JSON.stringify(gbp.toJSON())
{ "__type__": "Currency", "code": "GBP", "name": "British Pounds" }

Converting value objects from JSON

Use ValueObject.deserializeForNamespaces() to create a deserialize function that can turn the resulting JSON string back into objects

const deserialize = ValueObject.deserializeForNamespaces([{ Currency }])
const gbp2 = deserialize('{"__type__":"Currency","code":"GBP","name":"British Pounds"}')
gbp2.isEqualTo(gbp)
// => true

Immutability

Value objects cannot be updated. Use strict mode to throw errors when attempts to set property values are made.

gbp.code = 'USD'
// TypeError:Cannot assign to read only property 'amount' of object '#<Currency>