Repository: ViacomInc/data-point
Pull request #238 (2018-03-06 21:30:39)

feat(data-point): Add entity factories
<!-- What changes are being made? (What feature/bug is being fixed here?) -->
**What**: Add entity factory functions

<!-- Why are these changes necessary? -->
**Why**: Gives an easy method for importing/exporting individual entity definitions

<!-- How were these changes implemented? -->
**How**: Adds a `createEntityFactory` function and exports a factory for each entity type on the `DataPoint.entityFactories` object

<!-- Have you done all of these things?  -->
**Checklist**:
<!-- add "N/A" to the end of each line that's irrelevant to your changes -->
<!-- to check an item, place an "x" in the box like so: "- [x] Documentation" -->
- [ ] Has Breaking changes
- [x] Documentation
- [x] Tests
- [x] Ready to be merged


coveralls: 
[![Coverage Status](https://coveralls.io/builds/15840047/badge)](https://coveralls.io/builds/15840047)

Coverage remained the same at 100.0% when pulling **b8176edcafb5db6b4725bad584ca8d8f1fbcb36d on raingerber:entity-factories** into **cd7701998000a7cff0bca64e494f814293a6d5c3 on ViacomInc:master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/15840047/badge)](https://coveralls.io/builds/15840047)

Coverage remained the same at 100.0% when pulling **b8176edcafb5db6b4725bad584ca8d8f1fbcb36d on raingerber:entity-factories** into **cd7701998000a7cff0bca64e494f814293a6d5c3 on ViacomInc:master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/15839982/badge)](https://coveralls.io/builds/15839982)

Coverage remained the same at 100.0% when pulling **b85a0ad4d2a60cd9617e5b8daef618261136cf67 on raingerber:entity-factories** into **cd7701998000a7cff0bca64e494f814293a6d5c3 on ViacomInc:master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/15839982/badge)](https://coveralls.io/builds/15839982)

Coverage remained the same at 100.0% when pulling **b85a0ad4d2a60cd9617e5b8daef618261136cf67 on raingerber:entity-factories** into **cd7701998000a7cff0bca64e494f814293a6d5c3 on ViacomInc:master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/15840123/badge)](https://coveralls.io/builds/15840123)

Coverage remained the same at 100.0% when pulling **49aaa66fc6286af1df117deb74ec7aaa065e853f on raingerber:entity-factories** into **cd7701998000a7cff0bca64e494f814293a6d5c3 on ViacomInc:master**.


Patch:
diff --git a/.travis.yml b/.travis.yml
index 5b7d83b..9be444b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,6 +3,8 @@ node_js:
   - 6
   - 8
 sudo: false
+install:
+  - npm install -g codecov
 cache:
   yarn: true
   directories:
@@ -13,4 +15,4 @@ script:
   - "yarn test -- --maxWorkers=4"
   - "scripts/is-it-pretty.sh"
 after_script:
-  - "test -e ./coverage/lcov.info && npm install coveralls@2 && cat ./coverage/lcov.info | coveralls"
+  - codecov
diff --git a/README.md b/README.md
index 9a99283..e699348 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 # DataPoint
 
-[![Build Status](https://travis-ci.org/ViacomInc/data-point.svg?branch=master)](https://travis-ci.org/ViacomInc/data-point) [![Coverage Status](https://coveralls.io/repos/github/ViacomInc/data-point/badge.svg?branch=master)](https://coveralls.io/github/ViacomInc/data-point?branch=master) [![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors)
+[![Build Status](https://travis-ci.org/ViacomInc/data-point.svg?branch=master)](https://travis-ci.org/ViacomInc/data-point) [![codecov](https://codecov.io/gh/ViacomInc/data-point/branch/master/graph/badge.svg)](https://codecov.io/gh/ViacomInc/data-point) [![Coverage Status](https://coveralls.io/repos/github/ViacomInc/data-point/badge.svg?branch=master)](https://coveralls.io/github/ViacomInc/data-point?branch=master) [![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors)
 
 > JavaScript Utility for collecting, processing and transforming data.
 
diff --git a/packages/bench-trial/README.md b/packages/bench-trial/README.md
index d4ac74d..ef40592 100644
--- a/packages/bench-trial/README.md
+++ b/packages/bench-trial/README.md
@@ -1,5 +1,7 @@
 # Bench trial
 
+[![Build Status](https://travis-ci.org/ViacomInc/data-point.svg?branch=master)](https://travis-ci.org/ViacomInc/data-point) [![codecov](https://codecov.io/gh/ViacomInc/data-point/branch/master/graph/badge.svg)](https://codecov.io/gh/ViacomInc/data-point) [![Coverage Status](https://coveralls.io/repos/github/ViacomInc/data-point/badge.svg?branch=master)](https://coveralls.io/github/ViacomInc/data-point?branch=master) [![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](https://github.com/ViacomInc/data-point#contributors)
+
 > Runs one or multiple benchmark tests
 
 ## Install
diff --git a/packages/data-point-cache/README.md b/packages/data-point-cache/README.md
index 8a8a827..155c769 100644
--- a/packages/data-point-cache/README.md
+++ b/packages/data-point-cache/README.md
@@ -1,7 +1,6 @@
 # data-point-cache
 
-[![Build Status](https://travis-ci.org/ViacomInc/data-point.svg?branch=master)](https://travis-ci.org/ViacomInc/data-point) [![Coverage Status](https://coveralls.io/repos/github/ViacomInc/data-point/badge.svg?branch=master)](https://coveralls.io/github/ViacomInc/data-point?branch=master)
-
+[![Build Status](https://travis-ci.org/ViacomInc/data-point.svg?branch=master)](https://travis-ci.org/ViacomInc/data-point) [![codecov](https://codecov.io/gh/ViacomInc/data-point/branch/master/graph/badge.svg)](https://codecov.io/gh/ViacomInc/data-point) [![Coverage Status](https://coveralls.io/repos/github/ViacomInc/data-point/badge.svg?branch=master)](https://coveralls.io/github/ViacomInc/data-point?branch=master) [![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors)
 
 > DataPoint cache layer
 
diff --git a/packages/data-point-codemods/README.md b/packages/data-point-codemods/README.md
index 2df8236..e55f364 100644
--- a/packages/data-point-codemods/README.md
+++ b/packages/data-point-codemods/README.md
@@ -1,5 +1,7 @@
 # DataPoint codemods
 
+[![Build Status](https://travis-ci.org/ViacomInc/data-point.svg?branch=master)](https://travis-ci.org/ViacomInc/data-point) [![codecov](https://codecov.io/gh/ViacomInc/data-point/branch/master/graph/badge.svg)](https://codecov.io/gh/ViacomInc/data-point) [![Coverage Status](https://coveralls.io/repos/github/ViacomInc/data-point/badge.svg?branch=master)](https://coveralls.io/github/ViacomInc/data-point?branch=master) [![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](https://github.com/ViacomInc/data-point#contributors)
+
 > Codemods to help upgrade to newer versions of [data-point](https://github.com/ViacomInc/data-point/)
 
 Codemods, or [jscodeshift](https://github.com/facebook/jscodeshift) transforms, are small programs that help automate changes in a codebase. Think of them as search and replace on steroids. 
diff --git a/packages/data-point-express/README.md b/packages/data-point-express/README.md
index 4224230..3e4d99c 100644
--- a/packages/data-point-express/README.md
+++ b/packages/data-point-express/README.md
@@ -1,7 +1,6 @@
 # DataPoint Express
 
-[![Build Status](https://travis-ci.org/ViacomInc/data-point.svg?branch=master)](https://travis-ci.org/ViacomInc/data-point) [![Coverage Status](https://coveralls.io/repos/github/ViacomInc/data-point/badge.svg?branch=master)](https://coveralls.io/github/ViacomInc/data-point?branch=master)
-
+[![Build Status](https://travis-ci.org/ViacomInc/data-point.svg?branch=master)](https://travis-ci.org/ViacomInc/data-point) [![codecov](https://codecov.io/gh/ViacomInc/data-point/branch/master/graph/badge.svg)](https://codecov.io/gh/ViacomInc/data-point) [![Coverage Status](https://coveralls.io/repos/github/ViacomInc/data-point/badge.svg?branch=master)](https://coveralls.io/github/ViacomInc/data-point?branch=master) [![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](https://github.com/ViacomInc/data-point#contributors)
 
 > Create DataPoint service with Express middleware support
 
diff --git a/packages/data-point-service/README.md b/packages/data-point-service/README.md
index a098bf6..4172384 100644
--- a/packages/data-point-service/README.md
+++ b/packages/data-point-service/README.md
@@ -1,6 +1,6 @@
 # DataPoint Factory
 
-[![Build Status](https://travis-ci.org/ViacomInc/data-point.svg?branch=master)](https://travis-ci.org/ViacomInc/data-point) [![Coverage Status](https://coveralls.io/repos/github/ViacomInc/data-point/badge.svg?branch=master)](https://coveralls.io/github/ViacomInc/data-point?branch=master)
+[![Build Status](https://travis-ci.org/ViacomInc/data-point.svg?branch=master)](https://travis-ci.org/ViacomInc/data-point) [![codecov](https://codecov.io/gh/ViacomInc/data-point/branch/master/graph/badge.svg)](https://codecov.io/gh/ViacomInc/data-point) [![Coverage Status](https://coveralls.io/repos/github/ViacomInc/data-point/badge.svg?branch=master)](https://coveralls.io/github/ViacomInc/data-point?branch=master) [![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](https://github.com/ViacomInc/data-point#contributors)
 
 > Creates a DataPoint instance with redis support
 
diff --git a/packages/data-point/README.md b/packages/data-point/README.md
index 1f1d672..7bd8e1c 100644
--- a/packages/data-point/README.md
+++ b/packages/data-point/README.md
@@ -1,6 +1,6 @@
 # DataPoint
 
-[![Build Status](https://travis-ci.org/ViacomInc/data-point.svg?branch=master)](https://travis-ci.org/ViacomInc/data-point) [![Coverage Status](https://coveralls.io/repos/github/ViacomInc/data-point/badge.svg?branch=master)](https://coveralls.io/github/ViacomInc/data-point?branch=master)
+[![Build Status](https://travis-ci.org/ViacomInc/data-point.svg?branch=master)](https://travis-ci.org/ViacomInc/data-point) [![codecov](https://codecov.io/gh/ViacomInc/data-point/branch/master/graph/badge.svg)](https://codecov.io/gh/ViacomInc/data-point) [![Coverage Status](https://coveralls.io/repos/github/ViacomInc/data-point/badge.svg?branch=master)](https://coveralls.io/github/ViacomInc/data-point?branch=master) [![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](https://github.com/ViacomInc/data-point#contributors)
 
 > JavaScript Utility for collecting, processing and transforming data.
 
@@ -41,6 +41,7 @@ npm install --save data-point
   - [withDefault](#reducer-default)
 - [Entities](#entities)
   - [dataPoint.addEntities](#api-data-point-add-entities)
+  - [Entity factories](#entity-factories)
   - [Built-in entities](#built-in-entities)
     - [Reducer / Transform](#reducer-entity)
     - [Model](#model-entity)
@@ -1354,6 +1355,87 @@ dataPoint.addEntities({
 | *EntityType* | `string` | valid entity type to associate with the EntityObject |
 | *EntityId* | `string` | unique entity ID associated with the EntityObject |
 
+### <a name="entity-factories">Entity factories</a>
+
+Entities can be defined with object literals, or with these factory functions:
+
+```js
+const {
+  Entry,
+  Model,
+  Reducer,
+  Collection,
+  Hash,
+  Request,
+  Control,
+  Schema
+} = require('data-point').entityFactories
+```
+
+Each factory has the following signature:
+
+```js
+Factory(name:String, spec:Object):Object
+```
+
+**ARGUMENTS**
+
+| Argument | Type | Description |
+|:---|:---|:---|
+| *name* | `string` | The name of the entity; this will be used to generate an entity ID with the format `<entityType>:<name>` |
+| *spec* | `Object` | The source for generating the entity |
+
+Using these factories is optional, and the following examples are equivalent:
+
+**Example #1 (with object literal)**
+
+```js
+dataPoint.addEntities({
+  'model:hello-world': {
+    value: input => ({
+      hello: 'world'
+    })
+  }
+})
+```
+
+**Example #2 (with factory and assignment)**
+
+```js
+const { Model } = DataPoint.entityFactories
+
+const model = Model('hello-world', {
+  value: input => ({
+    hello: 'world'
+  })
+})
+
+const entities = {}
+
+assert.equal(model.id, 'model:hello-world')
+
+entities[model.id] = model.spec
+
+dataPoint.addEntities(entities)
+```
+
+**Example #3 (with factory and spread syntax)**
+
+```js
+const { Model } = DataPoint.entityFactories
+
+const model = Model('hello-world', {
+  value: input => ({
+    hello: 'world'
+  })
+})
+
+const entities = { ...model }
+
+dataPoint.addEntities(entities)
+```
+
+
 ### <a name="built-in-entities">Built-in Entities</a> 
 
 DataPoint comes with the following built-in entities: 
diff --git a/packages/data-point/lib/core/core.js b/packages/data-point/lib/core/core.js
index 4f0c133..2a860b3 100644
--- a/packages/data-point/lib/core/core.js
+++ b/packages/data-point/lib/core/core.js
@@ -1,22 +1,14 @@
 const _ = require('lodash')
 
-const normalizeEntities = require('./normalize-entities')
 const Transform = require('./transform')
+const normalizeEntities = require('./normalize-entities')
+const entities = require('../entity-types').definitions
 
 const storeValues = require('../stores/values')
 const storeEntities = require('../stores/entities')
 const storeEntityTypes = require('../stores/entity-types')
 const storeMiddleware = require('../stores/middleware')
 
-const EntityTransform = require('../entity-types/entity-transform')
-const EntityEntry = require('../entity-types/entity-entry')
-const EntityHash = require('../entity-types/entity-hash')
-const EntityModel = require('../entity-types/entity-model')
-const EntityCollection = require('../entity-types/entity-collection')
-const EntityRequest = require('../entity-types/entity-request')
-const EntityControl = require('../entity-types/entity-control')
-const EntitySchema = require('../entity-types/entity-schema')
-
 /**
  * @param {Object} store
  * @param {Object} items
@@ -72,18 +64,13 @@ function create (spec) {
   manager.addEntities = _.partial(addEntitiesToStore, manager.entities)
 
   // built-in entity types
-  manager.addEntityType('reducer', EntityTransform)
-  manager.addEntityType('entry', EntityEntry)
-  // alias to entry, may be used to process any object type
-  manager.addEntityType('model', EntityModel)
-  manager.addEntityType('hash', EntityHash)
-  manager.addEntityType('collection', EntityCollection)
-  manager.addEntityType('request', EntityRequest)
+  _.forOwn(entities, (definition, key) => {
+    manager.addEntityType(key.toLowerCase(), definition)
+  })
+
   // for backwards compatibility
-  manager.addEntityType('transform', EntityTransform)
-  manager.addEntityType('source', EntityRequest)
-  manager.addEntityType('control', EntityControl)
-  manager.addEntityType('schema', EntitySchema)
+  manager.addEntityType('transform', entities.Reducer)
+  manager.addEntityType('source', entities.Request)
 
   addToStore(manager.values, options.values, true)
 
diff --git a/packages/data-point/lib/entity-types/factory.js b/packages/data-point/lib/entity-types/factory.js
new file mode 100644
index 0000000..0d1aafd
--- /dev/null
+++ b/packages/data-point/lib/entity-types/factory.js
@@ -0,0 +1,38 @@
+const capitalize = require('lodash/capitalize')
+
+/**
+ * @param {String} _type
+ * @return {Function}
+ */
+function createEntityFactory (_type) {
+  const type = _type.toLowerCase()
+  /**
+   * @param {String} name
+   * @param {Object} spec
+   * @return {Object}
+   */
+  function Factory (name, spec) {
+    const id = `${type}:${name}`
+    const result = { [id]: spec }
+    // these properties are non-enumerable
+    // so that we can do nifty things like:
+    // dataPoint.create({ entities: { ...Hash('name', spec) } })
+    Object.defineProperty(result, 'id', { value: id })
+    Object.defineProperty(result, 'type', { value: type })
+    Object.defineProperty(result, 'name', { value: name })
+    Object.defineProperty(result, 'spec', { value: spec })
+    return result
+  }
+
+  Object.defineProperty(Factory, 'name', {
+    value: `${capitalize(type)}Factory`
+  })
+
+  Object.defineProperty(Factory, 'type', {
+    value: type
+  })
+
+  return Factory
+}
+
+module.exports.createEntityFactory = createEntityFactory
diff --git a/packages/data-point/lib/entity-types/index.js b/packages/data-point/lib/entity-types/index.js
new file mode 100644
index 0000000..1f0ac08
--- /dev/null
+++ b/packages/data-point/lib/entity-types/index.js
@@ -0,0 +1,23 @@
+const mapValues = require('lodash/mapValues')
+
+const { createEntityFactory } = require('./factory')
+
+const definitions = {
+  Entry: require('./entity-entry'),
+  Model: require('./entity-model'),
+  Reducer: require('./entity-transform'),
+  Collection: require('./entity-collection'),
+  Hash: require('./entity-hash'),
+  Request: require('./entity-request'),
+  Control: require('./entity-control'),
+  Schema: require('./entity-schema')
+}
+
+module.exports = {
+  definitions,
+  // this creates a factory for each entity type, which
+  // will be exposed through DataPoint.entityHelpers
+  factories: mapValues(definitions, (value, key) => {
+    return createEntityFactory(key.toLowerCase())
+  })
+}
diff --git a/packages/data-point/lib/helpers/helpers.js b/packages/data-point/lib/helpers/helpers.js
index 59b27a0..4d4a5e4 100644
--- a/packages/data-point/lib/helpers/helpers.js
+++ b/packages/data-point/lib/helpers/helpers.js
@@ -17,6 +17,8 @@ module.exports.helpers = {
   withDefault: stubFactories.withDefault
 }
 
+module.exports.entityFactories = require('../entity-types').factories
+
 module.exports.isReducer = require('../reducer-types').isReducer
 
 module.exports.createReducer = require('../reducer-types').create
diff --git a/packages/data-point/lib/index.js b/packages/data-point/lib/index.js
index 1e69385..87eb77e 100644
--- a/packages/data-point/lib/index.js
+++ b/packages/data-point/lib/index.js
@@ -8,6 +8,7 @@ module.exports = {
   helpers: helpers.helpers,
   createEntity: helpers.createEntity,
   resolveEntity: helpers.resolveEntity,
+  entityFactories: helpers.entityFactories,
   reducify: helpers.reducify,
   reducifyAll: helpers.reducifyAll,
   createReducer: helpers.createReducer,


Test patch:
diff --git a/packages/data-point/lib/__snapshots__/index.test.js.snap b/packages/data-point/lib/__snapshots__/index.test.js.snap
index ca27b25..4a404d7 100644
--- a/packages/data-point/lib/__snapshots__/index.test.js.snap
+++ b/packages/data-point/lib/__snapshots__/index.test.js.snap
@@ -6,6 +6,16 @@ Object {
   "createEntity": [Function],
   "createReducer": [Function],
   "createReducerResolver": [Function],
+  "entityFactories": Object {
+    "Collection": [Function],
+    "Control": [Function],
+    "Entry": [Function],
+    "Hash": [Function],
+    "Model": [Function],
+    "Reducer": [Function],
+    "Request": [Function],
+    "Schema": [Function],
+  },
   "helpers": Object {
     "assign": [Function],
     "constant": [Function],
diff --git a/packages/data-point/lib/entity-types/__snapshots__/factory.test.js.snap b/packages/data-point/lib/entity-types/__snapshots__/factory.test.js.snap
new file mode 100644
index 0000000..4341e79
--- /dev/null
+++ b/packages/data-point/lib/entity-types/__snapshots__/factory.test.js.snap
@@ -0,0 +1,9 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`creating a model with createEntityFactory entity factory should return an object with the expected shape 1`] = `
+Object {
+  "model:nuts": Object {
+    "value": [Function],
+  },
+}
+`;
diff --git a/packages/data-point/lib/entity-types/factory.test.js b/packages/data-point/lib/entity-types/factory.test.js
new file mode 100644
index 0000000..8abac42
--- /dev/null
+++ b/packages/data-point/lib/entity-types/factory.test.js
@@ -0,0 +1,49 @@
+/* eslint-env jest */
+
+const { createEntityFactory } = require('./factory')
+
+let factory
+let spec
+let instance
+
+beforeAll(() => {
+  factory = createEntityFactory('model')
+  spec = { value: input => input }
+  instance = factory('nuts', spec)
+})
+
+describe('creating a model with createEntityFactory', () => {
+  test('createEntityFactory return value should be a function', () => {
+    expect(createEntityFactory).toBeInstanceOf(Function)
+  })
+
+  test('createEntityFactory return value should have the correct "name" property', () => {
+    expect(factory.name).toBe('ModelFactory')
+  })
+
+  test('createEntityFactory return value should have the correct "type" property', () => {
+    expect(factory.type).toBe('model')
+  })
+
+  test('entity factory should return an object with the expected shape', () => {
+    // the 'model:nuts' key should appear in this snapshot,
+    // but other keys shouldn't because they're non-enumerable
+    expect(instance).toMatchSnapshot()
+  })
+
+  test('model should have the correct "id" property', () => {
+    expect(instance.id).toBe('model:nuts')
+  })
+
+  test('model should have the correct "type" property', () => {
+    expect(instance.type).toBe('model')
+  })
+
+  test('model should have the correct "name" property', () => {
+    expect(instance.name).toBe('nuts')
+  })
+
+  test('model should have the correct "spec" property', () => {
+    expect(instance.spec).toEqual(spec)
+  })
+})
diff --git a/packages/data-point/lib/helpers/__snapshots__/helpers.test.js.snap b/packages/data-point/lib/helpers/__snapshots__/helpers.test.js.snap
index 0c788f4..08e1c0a 100644
--- a/packages/data-point/lib/helpers/__snapshots__/helpers.test.js.snap
+++ b/packages/data-point/lib/helpers/__snapshots__/helpers.test.js.snap
@@ -6,6 +6,16 @@ Object {
   "createEntity": [Function],
   "createReducer": [Function],
   "createReducerResolver": [Function],
+  "entityFactories": Object {
+    "Collection": [Function],
+    "Control": [Function],
+    "Entry": [Function],
+    "Hash": [Function],
+    "Model": [Function],
+    "Reducer": [Function],
+    "Request": [Function],
+    "Schema": [Function],
+  },
   "helpers": Object {
     "assign": [Function],
     "constant": [Function],