npm package: @nfd/model
Description: Simple model objects.
Author: Nathan
Latest version: 0.3.0

# model

[Installation](#installation) · [Usage](#usage)

```js
import model from '@nfd/model'

const vivian = model({name: 'Vivian', job: 'Software Engineer', age: 23})

vivian.on('change', console.log)

++vivian.age
/* { target: vivian,
     name: 'age',
     value: 24,
     oldValue: 23 } */

vivian.favoriteColor = 'blue'
/* { target: vivian,
     name: 'favoriteColor',
     value: 'blue',
     oldValue: undefined } */

Object.defineProperty(vivian, 'favoriteBook', {
  value: 'The Sun Also Rises',
  configurable: true,
  writable: true,
})
/* { target: vivian,
     name: 'favoriteBook',
     value: 'The Sun Also Rises',
     oldValue: undefined } */

vivian.on('favoriteBookChange', e =>
  console.log(`Favorite book was: ${e.oldValue}`))

delete vivian.favoriteBook
/* Favorite book was: The Sun Also Rises */
/* { target: vivian,
     name: 'favoriteBook',
     value: undefined,
     oldValue: 'The Sun Also Rises' } */
```

## Installation

```sh
$ npm i @nfd/model
```

## Usage

[model(…)](#modelobject---deep--false-tree--false-fulltree--false) · [class extends model { … }](#class-extends-model---) · [new model](#new-model) · [model.is(…)](#modelisobject) · [model.deep(…)](#modeldeep) · [model.tree(…)](#modeltree) · [model.fullTree(…)](#modelfulltree)

### model([object = {}[, {deep = false, tree = false, fullTree = false}]])

Returns a proxy for `object` which emits events when its properties are created, deleted, or changed. Each change emits a `change` event and a <code><var>name</var>Change</code> event, both with event object `{target, name, value, oldValue}`, where:

- **target** is the model proxy;
- **name** is the property name;
- **value** is the new property value; and
- **oldValue** is the previous property value.

```js
const kate = model({name: 'Kate', age: 31})
kate.on('change', console.log)
++kate.age
/* { target: kate, name: 'age', value: 32, oldValue: 31 } */
```

**Arrays** — in addition to the normal `change` and <code><var>name</var>Change</code> events, models based on Arrays emit `splice` events describing high-level structural changes to their contents. Each event object comprises `{target, index, removed, added}`, where:

- **target** is the model proxy;
- **index** is the index of the first removed or added element;
- **removed** is an array of the removed elements; and
- **added** is the number of elements added in their place.

```js
const fruits = model([])
fruits.on('splice', console.log)

fruits.push('apple', 'pear', 'mango', 'banana', 'kiwi')
/* { target: fruits, index: 0, removed: [], added: 3 } */

fruits.shift()
/* { target: fruits, index: 0, removed: ['apple'], added: 0 } */

fruits.splice(1, 2, 'guava')
/* { target: fruits, index: 1, removed: [ 'mango', 'banana' ], added: 1 } */

fruits[0] = 'orange'
/* { target: fruits, index: 0, removed: [ 'pear' ], added: 1 } */

fruits.length = 0
/* { target: fruits, index: 0, removed: [ 'orange', 'guava', 'kiwi' ], added: 0 } */
```

**deep** or `model.deep(…)` — if true, all properties with object values will be automatically coerced on creation and assignment to model proxies.

```js
const olivia = model({
  name: 'Olivia',
  favoriteBook: {title: 'Anna Karenina', author: 'Leo Tolstoy'},
})
olivia.favoriteBook = {title: 'The Grapes of Wrath', author: 'John Steinbeck'}
olivia.favoriteBook.on('change', console.log)

olivia.favoriteBook.year = 1939
/* { target: olivia.favoriteBook,
     name: 'year',
     value: 1939,
     oldValue: undefined }
```

**tree** or `model.tree(…)` (implies `deep`) — if true, the root model proxy will emit `mutate` events for all mutations to itself and its subtree. Each event object comprises `{target, localTarget, path, name, value, oldValue}`, where:

- **target** is the root model proxy;
- **localTarget** is the subtree proxy whose property changed;
- **path** is the key path from the root to the changed property, with keys separated by `.`s;
- **names** is the list of property names in `path`;
- **depth** is number of intermediate objects in the path, starting at `0` for direct mutations to the root;
- **name** is the property name;
- **value** is the new property value; and
- **oldValue** is the previous property value.

```js
const olivia = model({
  name: 'Olivia',
  favoriteColor: 'blue',
  favoriteBook: {title: 'The Sun Also Rises', authors: [{name: 'Ernest Hemingway'}]},
}, {tree: true})

olivia.on('mutate', console.log)

olivia.favoriteColor = 'red'
/* { target: olivia,
     localTarget: olivia,
     path: 'favoriteColor',
     names: ['favoriteColor'],
     depth: 0,
     name: 'favoriteColor',
     value: 'red',
     oldValue: 'blue' } */

olivia.favoriteBook.authors[0].born = 1899
/* { target: olivia,
     localTarget: olivia.favoriteBook.authors[0],
     path: 'favoriteBook.authors.0.born',
     names: ['favoriteBook', 'authors', '0', 'born'],
     depth: 3,
     name: 'born',
     value: 1899,
     oldValue: undefined } */
```

**fullTree** or `model.fullTree(…)` (implies `deep` and `tree`) — if true, in addition to the root object, all subtree model proxies will also emit `mutate` events for mutations to themselves and their respective subtrees.

```js
const olivia = model({
  name: 'Olivia',
  favoriteColor: 'blue',
  favoriteBook: {title: 'The Sun Also Rises', authors: [{name: 'Ernest Hemingway'}]},
}, {fullTree: true})

olivia.favoriteBook.on('mutate', console.log)

olivia.favoriteBook.authors[0].born = 1899
/* { target: olivia.favoriteBook,
     localTarget: olivia.favoriteBook.authors[0],
     path: 'authors.0.born',
     names: ['authors', '0', 'born'],
     depth: 2,
     name: 'born',
     value: 1899,
     oldValue: undefined } */
```

### class extends model { … }

The `model` function can also be used as a base class.

```js
class Person extends model {/* … */}
const kate = new Person('Kate', 31)
kate.on('change', console.log)
++kate.age
/* { target: kate, name: 'age', value: 32, oldValue: 31 } */
```

### new model

Constructs a new, empty model.

```js
const kate = new model
kate.on('change', console.log)
kate.name = 'Kate'
/* { target: kate, name: 'name', value: 'Kate', oldValue: undefined } */
kate.age = 31
/* { target: kate, name: 'age', value: 31, oldValue: undefined } */
```

### model.is(object)

Returns `true` if `object` is a model proxy.

```js
model.is({name: 'Kate', age: 31}) /* false */
model.is(model({name: 'Kate', age: 31})) /* true */
model.is(model([])) /* true */

const fruits = model([{fruit: 'kiwi', quantity: 3}])
models.is(fruits[0]) /* false */

const fruits = model([{fruit: 'kiwi', quantity: 3}], {deep: true})
model.is(fruits[0]) /* true */
```

### model.deep

Alias function for `model(object, {deep: true})`. Can be used:

- As a function: `model.deep([object = {}])`
- As a base class: `class extends model.deep {…}`
- As a constructor: `new model.deep`

### model.tree

Alias function for `model(object, {tree: true})`. Can be used:

- As a function: `model.tree([object = {}])`
- As a base class: `class extends model.tree {…}`
- As a constructor: `new model.tree`

### model.fullTree

Alias function for `model(object, {fullTree: true})`. Can be used:

- As a function: `model.fullTree([object = {}])`
- As a base class: `class extends model.fullTree {…}`
- As a constructor: `new model.fullTree`