immutable-record  —   typed & immutable Javascript objects

immutable-recordis a small JavaScript library inspired by Facebook's ImmutableJS that allows you to create immutableRecords.

Recordsbehave very much like Objects (evenObject.keys()), but they are typed and immutable.

What does it look like?

// ImmutableRecord() returns a Record class
import ImmutableRecord from 'immutable-record'
const Record = ImmutableRecord({
  foo: { default: 5 },
  optional: { type: 'number' }
  someField: {
    type: value => 'foo' in value,
    required: true
  },
})

// You just pass ordinary objects to the Record constructor
const object = new Record({
  someField: { foo: 'bar' }
  // You don't have to provide optional fields
})
// Defaults will be used if no value is provided
object.foo // 5

// Object.keys() works as if the Record was a normal Object
Object.keys(object) // [ 'foo', 'someField' ]

// Records are immutable, so Record#set() returns a new Record
const another = object.set('optional', 8)
another.optional // 8

// The original Record is unmodified
'optional' in object // false

// Validation happens automatically
object.remove('someField')
// Error: "someField" is missing from the record {"foo":5}

Why pickimmutable-record?

Compared to ImmutableJS, the key feature isautomatic validation.Withimmutable-record, you can specify a type of each field and it will be automatically checked. Additionally:

Installing

# immutable-record is available on npm
npm install --save immutable-record

Importing

import ImmutableRecord from 'immutable-record'

// If you're not using ES6 modules
const ImmutableRecord = require('immutable-record')

Creating Records

The ImmutableRecord() function takes an object whose values describe the validation that is applied to the fields.

const Record = ImmutableRecord({
  optional: { type: 'string' },
  required: { required: true },
  validation: {
    type: value => value > 5,
    default: 6
})

You can also pass a second parameter to the ImmutableRecord() function which specifies a custom name for the Record class.

const Foo = ImmutableRecord({
    foo: { type: 'string' }
}, 'Foo')
Foo.name === 'Foo' // true
console.log(Foo) // [Function: Foo]

There are three validation options available for each field:type,required, anddefault.

Thetypeoption

You have two choices for a field's type:

  1. A primitive string (AKA one of the values returned by typeof). The possible values at the time of writing are:

    'object', 'string', 'number', 'symbol', 'boolean', 'function', 'undefined'
  2. A validation function that takes a single argument (the field's value) and returns a boolean.

const Record = ImmutableRecord({
  // Strings (typeof x === 'string') are valid
  string: { type: 'string' },

  // Arrays of length 4 or greater are valid
  array: {
    type: value => (
      Array.isArray(value) &&
      value.length > 3
    )
  }
})

Thedefaultoption

If a field has a default and a Record is created without the field explicitly set, the default value is used automatically.

const Record = ImmutableRecord({
  withDefault: { default: 5 }
})

// The default is automatically used if the field isn't set
const object = new Record({})
object.withDefault // 5

// undefined is a legal field value, so the default won't be used
const noDefault = new Record({
  withDefault: undefined
})
noDefault.withDefault // undefined

Therequiredoption

Fields marked as "required" must be present on the Record for it to validate.

const Record = ImmutableRecord({
  required: { required: true },
  optional: { required: false }
})

// No problems here
const object = new Record({
  required: 1
})
'optional' in object // false

// This doesn't work
const bad = new Record({
  optional: 1
})
// Error: "required" is missing from the record {"optional":1}

If a field is required and it also has a default, the Record will still validate even if the field isn't set.

const Record = ImmutableRecord({
  field: {
    required: true,
    default: 5
  }
})

// No problems here
const object = new Record({})
object.field // 5

Fields with no options

You can also leave the options out to get optional, untyped fields.

const Record = ImmutableRecord({
  optionalUntyped: {},

  // setting the field equal to null works too
  alsoWorks: null
})

Using Records

Records mostly work just like normal Objects, except they're immutable.

const ABCRecord = ImmutableRecord({
  a: {}, b: {}, c: {}
})

const object = new ABCRecord({ a: 1, c: 3 })
object.a // 1
object.c // 3
object.b // undefined
'b' in object // false

// Object.keys() also works how you would expect
Object.keys(object) // [ 'a', 'c' ]

When you try to set or delete a Record's value directly, the Record will throw.

const object = new ABCRecord({ a: 1, c: 3 })

object.b = 2
// Error: Use the "set" function to update the values of an ImmutableRecord.

delete object.a
// TypeError: Cannot delete property 'a' of [object Object]

Record#set()

Use the set() function to update Record values. set() returns a new Record instance.

const object = new ABCRecord({ a: 1, c: 3 })

const withB = object.set('b', 2)
withB instanceof ABCRecord // true
withB.b // 2

// The original record is unmodified
object.b // undefined

Record#remove()

Use the remove() function to remove Record values. remove() returns a new Record instance.

const object = new ABCRecord({ a: 1, b: 2, c: 3 })

const withoutB = object.remove('b')
withoutB instanceof ABCRecord // true
withoutB.b // undefined

A point about validation

Records are validated when they are constructed, so all of your fields will be validated when you use set() and remove().

Fork me on GitHub