Repository: ViacomInc/data-point
Pull request #158 (2018-01-16 22:04:44)

Model entity
<!--
Thanks for your interest in the project. We appreciate bugs filed and PRs submitted!

Please make sure that you are familiar with and follow the Code of Conduct for
this project (found in the CODE_OF_CONDUCT.md file).

Also, please make sure you're familiar with and follow the instructions in the
contributing guidelines (found in the CONTRIBUTING.md file).

Please fill out the information below to expedite the review and (hopefully)
merge of your pull request!
-->

<!-- What changes are being made? (What feature/bug is being fixed here?) -->
**What**: #144 

<!-- Why are these changes necessary? -->
**Why**: #144 

<!-- How were these changes implemented? -->
**How**: officially create a model entity and document it, also added a new set of reducer helpers to test types

<!-- 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" -->
- [x] Documentation
- [x] Tests
- [x] Ready to be merged <!-- In your opinion, is this ready to be merged as soon as it's reviewed? -->
- [ ] Added username to **all-contributors** list N/A

<!-- feel free to add additional comments -->


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

Coverage remained the same at 100.0% when pulling **1ad266da876ec5704a9919bbdd61f254d4416f10 on acatl:model-entity** into **56d5a0dc6a16d98ea94354968b4e7693b8f2e2e6 on ViacomInc:master**.

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

Coverage remained the same at 100.0% when pulling **a641e411c0afa952b8d7ce1ed71c89d1e4a12705 on acatl:model-entity** into **827798b3ad0fc539f20f47e6c490455a3f3039e8 on ViacomInc:master**.

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

Coverage remained the same at 100.0% when pulling **3afbb570a5bc30ce3dc96190f38e1365ef31c040 on acatl:model-entity** into **827798b3ad0fc539f20f47e6c490455a3f3039e8 on ViacomInc:master**.

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

Coverage remained the same at 100.0% when pulling **88d6e0d3e64edbdd11883428b27bc356556c6dab on acatl:model-entity** into **827798b3ad0fc539f20f47e6c490455a3f3039e8 on ViacomInc:master**.

acatl: @raingerber & @paulmolluzzo I believe I have addressed your suggestions
coveralls: 
[![Coverage Status](https://coveralls.io/builds/15090541/badge)](https://coveralls.io/builds/15090541)

Coverage decreased (-0.05%) to 99.946% when pulling **8c66706426a9a1e9c95eb7a4689112369d4bf945 on acatl:model-entity** into **827798b3ad0fc539f20f47e6c490455a3f3039e8 on ViacomInc:master**.

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

Coverage remained the same at 100.0% when pulling **b6927234520b231c684f8c397f9b6fc49485c420 on acatl:model-entity** into **827798b3ad0fc539f20f47e6c490455a3f3039e8 on ViacomInc:master**.


Patch:
diff --git a/packages/data-point/README.md b/packages/data-point/README.md
index 6020728..3f67e4b 100644
--- a/packages/data-point/README.md
+++ b/packages/data-point/README.md
@@ -39,6 +39,7 @@ npm install --save data-point
   - [dataPoint.addEntities](#api-data-point-add-entities)
   - [Built-in entities](#built-in-entities)
     - [Transform](#transform-entity)
+    - [Model](#model-entity)
     - [Entry](#entry-entity)
     - [Request](#request-entity)
     - [Hash](#hash-entity)
@@ -105,41 +106,66 @@ Based on an initial feed, fetch and aggregate results from multiple remote servi
 
   // add entities to dataPoint instance
   dataPoint.addEntities({
-    // remote request
+    // remote service request
     'request:Planet': {
       // {value.planetId} injects the
       // value from the accumulator
+      // creates: https://swapi.co/api/planets/1/
       url: 'https://swapi.co/api/planets/{value.planetId}'
     },
 
-    // hash entity to resolve a Planet
-    'hash:Planet': {
-      // maps keys
-      mapKeys: {
-        // map name key
-        name: '$name',
-        // residents is an array of urls
-        // where each url gets mapped
-        // to a request:Resident and
-        // its result gets reduced 
-        // by an ObjectReducer
-        residents: [
-          '$residents',
-          map([
-            'request:Resident',
-            {
-              name: '$name',
-              gender: '$gender',
-              birthYear: '$birth_year'
-            }
-          ])
-        ]
-      }
+    // model entity to resolve a Planet
+    'model:Planet': {
+      inputType: 'schema:DataInput',
+      value: [
+        // hit request:Planet data source
+        'request:Planet',
+        // map result to ObjectReducer
+        {
+          // map name key
+          name: '$name',
+          population: '$population',
+          // residents is an array of urls
+          // eg. https://swapi.co/api/people/1/
+          // where each url gets mapped
+          // to a model:Resident
+          residents: ['$residents', map('model:Resident')]
+        }
+      ]
+    },
+
+    // model entity to resolve a Planet
+    'model:Resident': {
+      inputType: 'string',
+      value: [
+        // hit request:Resident
+        'request:Resident',
+        // extract data
+        {
+          name: '$name',
+          gender: '$gender',
+          birthYear: '$birth_year'
+        }
+      ]
     },
 
-    // requests url passed
     'request:Resident': {
+      // check input is string
+      inputType: 'string',
       url: '{value}'
+    },
+
+    // schema to verify data input
+    'schema:DataInput': {
+      schema: {
+        type: 'object',
+        properties: {
+          planetId: {
+            $id: '/properties/planet',
+            type: 'integer'
+          }
+        }
+      }
     }
   })
 
@@ -147,11 +173,10 @@ Based on an initial feed, fetch and aggregate results from multiple remote servi
     planetId: 1
   }
 
-  dataPoint
-    .transform('request:Planet | hash:Planet', input)
+  dataPoint.transform('model:Planet', input)
     .then((acc) => {
-      console.log(acc.value)
       /*
+      acc.value -> 
       { 
         name: 'Tatooine',
         population: 200000,
@@ -1112,6 +1137,7 @@ dataPoint.addEntities({
 DataPoint comes with the following built-in entities: 
 
 - [Transform](#transform-entity)
+- [Model](#model-entity)
 - [Entry](#entry-entity)
 - [Request](#request-entity)
 - [Hash](#hash-entity)
@@ -1125,12 +1151,18 @@ All entities share a common API (except for [Transform](#transform-entity)).
 
 ```js
 {
-  // executes --before-- everything else
+  // type checks the entity's input
+  inputType: String | Reducer,
+
+  // executes --before-- any modifier
   before: Reducer,
   
-  // executes --after-- everything else
+  // executes --after-- any modifier
   after: Reducer,
   
+  // type checks the entity's output
+  outputType: String | Reducer,
+
   // executes in case there is an error at any
   // point of the entire transformation
   error: Reducer,
@@ -1145,18 +1177,125 @@ All entities share a common API (except for [Transform](#transform-entity)).
 
 | Key | Type | Description |
 |:---|:---|:---|
+| *inputType*  | String, [Reducer](#reducers) | type checks the entity's input value, does not mutate value. [Entity Type checking](#entity-type-check). |
 | *before*  | [Reducer](#reducers) | reducer to be resolved **before** the entity resolution |
 | *after*   | [Reducer](#reducers) | reducer to be resolved **after** the entity resolution |
+| *outputType*  | String, [Reducer](#reducers) | type checks the entity's output value, does not mutate value. [Entity Type checking](#entity-type-check). |
 | *error*   | [Reducer](#reducers) | reducer to be resolved in case of an error |
 | *params*  | `Object` | User defined Hash that will be passed to every transform within the context of the transform's execution |
 
-##### <a name="transform-entity">Transform Entity</a>
+
+##### <a name="entity-type-check">Entity Type checking</a>
+
+You may use **inputType** and **outputType** to type check against the values being passed and resolved from an entity. Type checking does not mutate the result. 
+
+**Built in type checks:**
+
+To use built-in type checks you may set the value of **inputType**/**outputType** to: `'string'`, `'number'`, `'boolean'`, `'function'`, `'error'`, `'array'`, or `'object'`. 
+
+<details>
+  <summary>Check if a model outputs an string</summary>
+
+  This example uses a Model Entity, for information on what a model is please go to the [Model Entity](#model-entity) section.
+
+  ```js
+  const dataPoint = DataPoint.create()
+  const helpers = DataPoint.helpers
+
+  dataPoint.addEntities({
+    'model:getName': {
+      value: '$name',
+      outputType: 'string'
+    }
+  })
+
+  const input = {
+    name: 'DataPoint'
+  }
+
+  dataPoint.transform('model:getName', input)
+    .then(acc => {
+      // acc.value -> DataPoint
+    })
+  ```
+
+</details>
+
+**Type Check using Reducer**
+
+To customize type checking you may use a [Reducer](#reducers). If the reducer throws an error, the type check fails. In the case of `inputType` and `outputType` the return value is ignored.
+
+
+<details>
+  <summary>Custom type check with <a href="#function-reducer">FunctionReducer</a></summary>
+
+  ```js
+  const dataPoint = DataPoint.create()
+
+  dataPoint.addEntities({
+    'model:getName': {
+      value: '$name',
+
+      outputType: acc => {
+        if(typeof acc.value === 'string' && acc.value.length > 5) {
+          return trues
+        }
+
+        throw new Error('should be string and length > 5')
+      }
+    }
+  })
+
+  const input = {
+    name: 'DataPoint'
+  }
+
+  dataPoint.transform('model:getName', input)
+    .then(acc => {
+      // acc.value -> DataPoint
+    })
+  ```
+
+</details>
+
+<details>
+  <summary>Custom type check with <a href="#entity-reducer">EntityReducer</a></summary>
+
+  In this example we are using a [Schema Entity](#schema-entity) to check the inputType.
+
+  ```js
+  const dataPoint = DataPoint.create()
+
+  dataPoint.addEntities({
+    'model:getName': {
+      // assume schema:RepoSchema 
+      // exists and checks of the
+      // existence of name
+      inputType: 'schema:RepoSchema',
+      value: '$name'
+    }
+  })
+
+  const input = {
+    name: 'DataPoint'
+  }
+
+  dataPoint.transform('model:getName', input)
+    .then(acc => {
+      // acc.value -> DataPoint
+    })
+  ```
+
+</details>
+
+
+#### <a name="transform-entity">Transform Entity</a>
 
 A Transform entity is meant to be used as a 'snippet' entity that you can re-use in other entities. It does not expose the before/after/error/params API that other entities have.
 
 The value of a Transform entity is a [Reducer](#reducers).
 
-IMPORTANT: Transform Entities **do not support** (extension)[#extending-entities].
+IMPORTANT: Transform Entities **do not support** [extension](#extending-entities).
 
 **SYNOPSIS**
 
@@ -1199,18 +1338,20 @@ dataPoint.addEntities({
 </details>
 
 
-#### <a name="entry-entity">Entry Entity</a>
+#### <a name="model-entity">Model Entity</a>
 
-An Entry entity is where your data manipulation starts. As a best practice, use it as your starting point, and use it to call more complex entities.
+A Model entity is a generic entity that provides the [base modifiers](#entity-base-api).
 
 **SYNOPSIS**
 
 ```js
 dataPoint.addEntities({
-  'entry:<entityId>': {
+  'model:<entityId>': {
+    inputType: String | Reducer,
     before: Reducer,
     value: Reducer,
     after: Reducer,
+    outputType: String | Reducer,
     error: Reducer,
     params: Object
   }
@@ -1221,13 +1362,14 @@ dataPoint.addEntities({
 
 | Key | Type | Description |
 |:---|:---|:---|
+| *inputType*  | String, [Reducer](#reducers) | type checks the entity's input value, does not mutate value. [Entity Type checking](#entity-type-check). |
 | *before*  | [Reducer](#reducers) | reducer to be resolved **before** the entity resolution |
-| *value* | [Reducer](#reducers) | reducer to be resolved
 | *after*   | [Reducer](#reducers) | reducer to be resolved **after** the entity resolution |
+| *outputType*  | String, [Reducer](#reducers) | type checks the entity's output value, does not mutate value. [Entity Type checking](#entity-type-check). |
 | *error*   | [Reducer](#reducers) | reducer to be resolved in case of an error |
-| *params*    | `Object` | user defined Hash that will be passed to every transform within the context of this transform's execution |
+| *params*  | `Object` | User defined Hash that will be passed to every transform within the context of the transform's execution |
 
-##### <a name="entry-value">Entry.value</a>
+##### <a name="model-value">Model.value</a>
 
 <details>
   <summary>Using the `value` property to transform an input</summary>
@@ -1250,13 +1392,13 @@ dataPoint.addEntities({
   }
   
   dataPoint.addEntities({
-    'entry:foo': {
+    'model:foo': {
       value: ['$a.b.c', getMax, multiplyBy(10)]
     }
   })
   
   dataPoint
-    .transform('entry:foo', input)
+    .transform('model:foo', input)
     .then((acc) => {
       assert.equal(acc.value, 30)
     })
@@ -1264,62 +1406,52 @@ dataPoint.addEntities({
 </details>
 
 
-Example at: [examples/entity-entry-basic.js](examples/entity-entry-basic.js)
+Example at: [examples/entity-model-basic.js](examples/entity-model-basic.js)
 
-##### <a name="entry-before">Entry.before</a>
+##### <a name="model-before">Model.before</a>
 
 <details>
   <summary>Checking whether the value passed to an entity is an array</summary>
   
   ```js
-  const isArray = () => (acc, next) => {
-    if (acc.value instanceof Array) {
-      // if the value is valid, then just pass it along
-      return next(null, acc.value)
-    }
-  
-    // Notice how we pass this error object as the FIRST parameter.
-    // This tells DataPoint that there was an error, and to treat it as such.
-    next(new Error(`${acc.value} should be an Array`))
+  const toArray = (acc) => {
+    return Array.isArray(acc.value)
+      ? acc.value
+      : [acc.value]
   }
   
   dataPoint.addEntities({
-    'entry:foo': {
-      before: isArray(),
+    'model:foo': {
+      before: toArray,
       value: '$'
     }
   })
   
   dataPoint
-    .transform('entry:foo', [3, 15])
+    .transform('model:foo', 100)
     .then((acc) => {
-      assert.deepEqual(acc.value, [3, 15])
+      assert.deepEqual(acc.value, [100])
     })
   ```
 </details>
 
 
-Example at: [examples/entity-entry-before.js](examples/entity-entry-before.js)
+Example at: [examples/entity-model-before.js](examples/entity-model-before.js)
 
-##### <a name="entry-after">Entry.after</a>
+##### <a name="model-after">Model.after</a>
 
 <details>
   <summary>Using `after` transform</summary>
   
   ```js
-  const isArray = () => (acc, next) => {
-    if (acc.value instanceof Array) {
-      // if the value is valid, then just pass it along
-      return next(null, acc.value)
-    }
-  
-    // Notice how we pass this error object as the FIRST parameter.
-    // This tells DataPoint that there was an error, and to treat it as such.
-    next(new Error(`${acc.value} should be an Array`))
+  const toArray = (acc) => {
+    return Array.isArray(acc.value)
+      ? acc.value
+      : [acc.value]
   }
   
   dataPoint.addEntities({
-    'entry:foo': {
+    'model:foo': {
       value: '$a.b',
       after: isArray()
     }
@@ -1332,17 +1464,14 @@ Example at: [examples/entity-entry-before.js](examples/entity-entry-before.js)
   }
   
   dataPoint
-    .transform('entry:foo', input)
+    .transform('model:foo', input)
     .then((acc) => {
       assert.deepEqual(acc.value, [3, 15])
     })
   ```
 </details>
 
-
-Example at: [examples/entity-entry-after.js](examples/entity-entry-after.js)
-
-##### <a name="entry-error">Entry.error</a>
+##### <a name="model-error">Model.error</a>
 
 Any error that happens within the scope of the Entity can be handled by the `error` transform. To respect the API, error reducers have the same API, and the value of the error is under `acc.value`.
 
@@ -1352,52 +1481,41 @@ Passing a value as the second argument will stop the propagation of the error.
 
 **EXAMPLES:**
 
-Let's resolve to a NON array value and see how this would be handled.
-
 <details>
   <summary>Handling Entry Errors</summary>
   
+  Let's resolve to a non-array value and see how it would be handled, this example will use [outputType](#entity-type-check) for type checking.
+
   ```js
-  const isArray = () => (acc, next) => {
-    if (acc.value instanceof Array) {
-      // if the value is valid, then just pass it along
-      return next(null, acc.value)
-    }
-  
-    // Notice how we pass this error object as the FIRST parameter.
-    // This tells DataPoint that there was an error, and to treat it as such.
-    next(new Error(`${acc.value} should be an Array`))
-  }
   
   dataPoint.addEntities({
-    'entry:foo': {
+    'model:getArray': {
       // points to a NON Array value
-      value: '$a',
-      after: isArray(),
+      value: '$a.b',
+      outputType: 'isArray',
       error: (acc) => {
-        // prints out the error 
+        // prints out the error
         // message generated by
-        // isArray function
-        console.log(acc.value.message) 
-  
+        // isArray type check
+        console.log(acc.value.message)
+
         console.log('Value is invalid, resolving to empty array')
-  
-        // passing a value as the 
-        // second argument will stop
+
+        // passing a value will stop
         // the propagation of the error
         return []
       }
     }
   })
-  
+
   const input = {
     a: {
-      b: [3, 15]
+      b: 'foo'
     }
   }
-  
+
   dataPoint
-    .transform('entry:foo', input)
+    .transform('model:getArray', input)
     .then((acc) => {
       assert.deepEqual(acc.value, [])
     })
@@ -1405,95 +1523,44 @@ Let's resolve to a NON array value and see how this would be handled.
 </details>
 
 
-Example at: [examples/entity-entry-error-handled.js](examples/entity-entry-error-handled.js)
+Example at: [examples/entity-model-error-handled.js](examples/entity-model-error-handled.js)
 
 <details>
   <summary>Pass the array to be handled somewhere else</summary>
   
   ```js
-  const logError = () => (acc, next) => {
+  const logError = (acc) => {
     // acc.value holds the actual Error Object
     console.log(acc.value.toString())
-    // outputs: Error: [object Object] should be an Array
-  
-    // if we wish to bubble it up, then pass it to
-    // the next() as the first parameter
-    next(acc.value)
-  
-    // if we wished not to bubble it, we could pass
-    // an empty first param, and a second value to
-    // be used as the final resolved value
-    // next(null, false) <-- this is just an example
+    throw acc.value
   }
-  
+
   dataPoint.addEntities({
-    'entry:foo': {
+    'model:getArray': {
       value: '$a',
-      after: isArray(),
-      error: logError()
+      outputType: 'isArray',
+      error: logError
     }
   })
   
   const input = {
     a: {
-      b: [3, 15]
+      b: 'foo'
     }
   }
   
   dataPoint
-    .transform('entry:foo', input)
+    .transform('model:getArray', input)
     .catch((error) => {
       console.log(error.toString())
-      // Error: [object Object] 
-      // should be an Array
-    })
-  ```
-</details>
-
-
-Example at: [examples/entity-entry-error-rethrow.js](examples/entity-entry-error-rethrow.js)
-
-<details>
-  <summary>Resolve entry to a value to prevent the transform from failing</summary>
-  
-  ```js
-  const resolveTo = (value) => (acc) => {
-    // since we don't pass the error
-    // back it will resolve to the
-    // new value
-    return value
-  }
-  
-  dataPoint.addEntities({
-    'entry:foo': {
-      value: '$a',
-      after: isArray(),
-      // in case of error resolve
-      // the value to an empty array
-      error: resolveTo([])
-    }
-  })
-  
-  const input = {
-    a: {
-      b: [3, 15, 6, 3, 8]
-    }
-  }
-  
-  dataPoint
-    .transform('entry:foo', input)
-    .then((acc) => {
-      assert.deepEqual(acc.value, [])
     })
   ```
 </details>
 
 
-Example at: [examples/entity-entry-error-resolved.js](examples/entity-entry-error-resolved.js)
-
-For examples of entry entities, see the ones used in the [Examples](examples), on the unit tests: [Request Definitions](test/definitions/entry.js) and [Integration Examples](test/definitions/integrations.js)
+Example at: [examples/entity-model-error-rethrow.js](examples/entity-model-error-rethrow.js)
 
-##### <a name="entry-params">Entry.params</a>
+##### <a name="model-params">Entry.params</a>
 
 The params object is used to pass custom data to your entity. This Object is exposed as a property of the [Accumulator](#accumulator) Object. Which can be accessed via a [FunctionReducer](#function-reducer), as well as through a [PathReducer](#path-reducer) expression.
 
@@ -1506,7 +1573,7 @@ The params object is used to pass custom data to your entity. This Object is exp
   }
   
   dataPoint.addEntities({
-    'entry:multiply': {
+    'model:multiply': {
       value: multiplyValue,
       params: {
         multiplier: 100
@@ -1515,7 +1582,7 @@ The params object is used to pass custom data to your entity. This Object is exp
   })
   
   dataPoint
-    .transform('entry:multiply', 200)
+    .transform('model:multiply', 200)
     .then((acc) => {
       assert.deepEqual(acc.value, 20000)
     })
@@ -1528,7 +1595,7 @@ The params object is used to pass custom data to your entity. This Object is exp
   
   ```js
   dataPoint.addEntities({
-    'entry:getParam': {
+    'model:getParam': {
       value: '$..params.multiplier',
       params: {
         multiplier: 100
@@ -1536,13 +1603,44 @@ The params object is used to pass custom data to your entity. This Object is exp
     }
   })
   
-  dataPoint.transform('entry:getParam')
+  dataPoint.transform('model:getParam')
     .then((acc) => {
       assert.deepEqual(acc.value, 100)
     })
   ```
 </details>
 
+#### <a name="entry-entity">Entry Entity</a>
+
+This entity is very similar to the [Model entity](#model-entity). Its main difference is that this entity will default to an empty object `{ }` as its initial value if none was passed. As a best practice, use it as your starting point, and use it to call more complex entities.
+
+**SYNOPSIS**
+
+```js
+dataPoint.addEntities({
+  'entry:<entityId>': {
+    inputType: String | Reducer,
+    before: Reducer,
+    value: Reducer,
+    after: Reducer,
+    outputType: String | Reducer,
+    error: Reducer,
+    params: Object
+  }
+})
+```
+
+**Properties exposed:**
+
+| Key | Type | Description |
+|:---|:---|:---|
+| *inputType*  | String, [Reducer](#reducers) | type checks the entity's input value, does not mutate value. [Entity Type checking](#entity-type-check). |
+| *before*  | [Reducer](#reducers) | reducer to be resolved **before** the entity resolution |
+| *after*   | [Reducer](#reducers) | reducer to be resolved **after** the entity resolution |
+| *outputType*  | String, [Reducer](#reducers) | type checks the entity's output value, does not mutate value. [Entity Type checking](#entity-type-check). |
+| *error*   | [Reducer](#reducers) | reducer to be resolved in case of an error |
+| *params*  | `Object` | User defined Hash that will be passed to every transform within the context of the transform's execution |
+
 
 #### <a name="request-entity">Request Entity</a>
 
@@ -1553,11 +1651,13 @@ Requests a remote source, using [request](https://github.com/request/request) be
 ```js
 dataPoint.addEntities({
   'request:<entityId>': {
+    inputType: String | Reducer,
     before: Reducer,
     url: StringTemplate,
     options: TransformObject,
     beforeRequest: Reducer,
     after: Reducer,
+    outputType: String | Reducer,
     error: Reducer,
     params: Object
   }
@@ -1568,12 +1668,14 @@ dataPoint.addEntities({
 
 | Key | Type | Description |
 |:---|:---|:---|
+| *inputType*  | String, [Reducer](#reducers) | type checks the entity's input value, does not mutate value. [Entity Type checking](#entity-type-check). |
 | *before*  | [Reducer](#reducers) | reducer to be resolved **before** the entity resolution |
 | *url*   | [StringTemplate](#string-template) | String value to resolve the request's url |
 | *options* | [TransformObject](#transform-object) | Request's options. These map directly to [request.js](https://github.com/request/request) options
 | *beforeRequest* | [Reducer](#reducers) | `acc.value` at this point will be the request options object being passed to the final request. You may do any modifications here, and then pass to the next reducer |
 | *after*   | [Reducer](#reducers) | reducer to be resolved **after** the entity resolution |
 | *error*   | [Reducer](#reducers) | reducer to be resolved in case of an error |
+| *outputType*  | String, [Reducer](#reducers) | type checks the entity's output value, does not mutate value. [Entity Type checking](#entity-type-check). |
 | *params*    | `Object` | User defined Hash that will be passed to every reducer within the context of the transform function's execution |
 
 ##### <a name="request-url">Request.url</a>
@@ -1789,8 +1891,8 @@ If you want to have more control over the order of execution, you may use the [c
 ```js
 dataPoint.addEntities({
   'hash:<entityId>': {
+    inputType: String | Reducer,
     before: Reducer,
-
     value: Reducer,
     mapKeys: TransformMap,
     omitKeys: String[],
@@ -1798,8 +1900,8 @@ dataPoint.addEntities({
     addKeys: TransformMap,
     addValues: Object,
     compose: ComposeReducer[],
-
     after: Reducer,
+    outputType: String | Reducer,
     error: Reducer,
     params: Object,
   }
@@ -1810,6 +1912,8 @@ dataPoint.addEntities({
 
 | Key | Type | Description |
 |:---|:---|:---|
+| *inputType*  | String, [Reducer](#reducers) | type checks the entity's input value, does not mutate value. [Entity Type checking](#entity-type-check). |
+| *before*  | [Reducer](#reducers) | reducer to be resolved **before** the entity resolution |
 | *value* | [Reducer](#reducers) | The value to which the Entity resolves |
 | *mapKeys* | [ObjectReducer](#object-reducer) | Map to a new set of key/values. Each value accepts a reducer |
 | *omitKeys* | `String[]` | Omits keys from acc.value. Internally, this uses the [omit](#reducer-omit) reducer helper |
@@ -1817,8 +1921,8 @@ dataPoint.addEntities({
 | *addKeys* | [ObjectReducer](#object-reducer) | Add/Override key/values. Each value accepts a reducer. Internally, this uses the [assign](#reducer-assign) reducer helper |
 | *addValues* | `Object` | Add/Override hard-coded key/values. Internally, this uses the [assign](#reducer-assign) reducer helper |
 | *compose* | [ComposeReducer](#compose-reducer)`[]` | Modify the value of accumulator through an Array of `ComposeReducer` objects. Think of it as a [Compose/Flow Operation](https://en.wikipedia.org/wiki/Function_composition_(computer_science)), where the result of one operation gets passed to the next one|
-| *before*  | [Reducer](#reducers) | reducer to be resolved **before** the entity resolution |
 | *after*   | [Reducer](#reducers) | reducer to be resolved **after** the entity resolution |
+| *outputType*  | String, [Reducer](#reducers) | type checks the entity's output value, does not mutate value. [Entity Type checking](#entity-type-check). |
 | *error*   | [Reducer](#reducers) | reducer to be resolved in case of an error |
 | *params*    | `Object` | User-defined Hash that will be passed to every reducer within the context of the transform function's execution |
 
@@ -2117,15 +2221,15 @@ To prevent unexpected results, a **Collection Entity** can only process **Arrays
 ```js
 dataPoint.addEntities({
   'collection:<entityId>': {
+    inputType: String | Reducer,
     before: Reducer,
-
     value: Reducer,
     filter: Reducer,
     map: Reducer,
     find: Reducer,
     compose: ComposeReducer[],
-
     after: Reducer,
+    outputType: String | Reducer,
     error: Reducer,
     params: Object,
   }
@@ -2136,6 +2240,7 @@ dataPoint.addEntities({
 
 | Key | Type | Description |
 |:---|:---|:---|
+| *inputType*  | String, [Reducer](#reducers) | type checks the entity's input value, does not mutate value. [Entity Type checking](#entity-type-check). |
 | *before*  | [Reducer](#reducers) | reducer to be resolved **before** the entity resolution |
 | *value* | [Reducer](#reducers) | The value to which the Entity resolves |
 | *map* | [Reducer](#reducers) | Maps the items of an array. Internally, this uses the [map](#reducer-map) reducer helper |
@@ -2143,6 +2248,7 @@ dataPoint.addEntities({
 | *filter* | [Reducer](#reducers) | Filters the items of an array. Internally, this uses the [filter](#reducer-filter) reducer helper |
 | *compose* | [ComposeReducer](#compose-reducer)`[]` | Modify the value of accumulator through an Array of `ComposeReducer` objects. Think of it as a [Compose/Flow Operation](https://en.wikipedia.org/wiki/Function_composition_(computer_science)), where the result of one object gets passed to the next one |
 | *after* | [Reducer](#reducers) | reducer to be resolved **after** the entity resolution |
+| *outputType*  | String, [Reducer](#reducers) | type checks the entity's output value, does not mutate value. [Entity Type checking](#entity-type-check). |
 | *error* | [Reducer](#reducers) | reducer to be resolved in case of an error |
 | *params* | `Object` | User-defined Hash that will be passed to every reducer within the context of the transform function's execution |
 
@@ -2559,13 +2665,15 @@ The Flow Control entity allows you to control the flow of your transformations.
 ```js
 dataPoint.addEntities({
   'control:<entityId>': {
+    inputType: String | Reducer,
+    before: Reducer,
     select: [
       { case: Reducer, do: Transform },
       ...
       { default: Transform }
     ],
-    before: Reducer,
     after: Reducer,
+    outputType: String | Reducer,
     error: Reducer,
     params: Object,
   }
@@ -2576,10 +2684,12 @@ dataPoint.addEntities({
 
 | Key | Type | Description |
 |:---|:---|:---|
+| *inputType*  | String, [Reducer](#reducers) | type checks the entity's input value, does not mutate value. [Entity Type checking](#entity-type-check). |
+| *before* | [Reducer](#reducers) | reducer to be resolved **before** the entity resolution |
 | *select* | [Case Statements](#case-statements)`[]` | Array of case statements, and a default fallback |
 | *params* | `Object` | User-defined Hash that will be passed to every transform within the context of the transform's execution |
-| *before* | [Reducer](#reducers) | reducer to be resolved **before** the entity resolution |
 | *after* | [Reducer](#reducers) | reducer to be resolved **after** the entity resolution |
+| *outputType*  | String, [Reducer](#reducers) | type checks the entity's output value, does not mutate value. [Entity Type checking](#entity-type-check). |
 | *error* | [Reducer](#reducers) | reducer to be resolved in case of an error |
 
 ##### <a name="case-statements">Case Statements</a>
@@ -2645,12 +2755,13 @@ For examples of control entities, see the ones used on the unit tests: [Control
 ```js
 dataPoint.addEntities({
   'schema:<entityId>': {
+    before: Reducer,
+    inputType: String | Reducer,
     value: Reducer,
     schema: JSONSchema,
     options: Object,
-
-    before: Reducer,
     after: Reducer,
+    outputType: String | Reducer,
     error: Reducer,
     params: Object
   }
@@ -2661,13 +2772,15 @@ dataPoint.addEntities({
 
 | Key | Type | Description |
 |:---|:---|:---|
+| *inputType*  | String, [Reducer](#reducers) | type checks the entity's input value, does not mutate value. [Entity Type checking](#entity-type-check). |
+| *before* | [Reducer](#reducers) | reducer to be resolved **before** the entity resolution |
 | *value* | [Reducer](#reducers) | The value that this entity will pass to the schema validation |
 | *schema* | `Object` | Valid [JSON Schema](http://json-schema.org/documentation.html) object. If the schema is not valid, an error is thrown when creating the entity. |
 | *options* | `Object` | Avj's [options](https://github.com/epoberezkin/ajv#options) object
-| *params* | `Object` | User-defined Hash that will be passed to every transform within the context of the transform's execution |
-| *before* | [Reducer](#reducers) | reducer to be resolved **before** the entity resolution |
 | *after* | [Reducer](#reducers) | reducer to be resolved **after** the entity resolution |
+| *outputType*  | String, [Reducer](#reducers) | type checks the entity's output value, does not mutate value. [Entity Type checking](#entity-type-check). |
 | *error* | [Reducer](#reducers) | reducer to be resolved in case of an error |
+| *params* | `Object` | User-defined Hash that will be passed to every transform within the context of the transform's execution |
 
 **EXAMPLES**
 
diff --git a/packages/data-point/examples/async-example.js b/packages/data-point/examples/async-example.js
index 3419488..80f9a25 100644
--- a/packages/data-point/examples/async-example.js
+++ b/packages/data-point/examples/async-example.js
@@ -1,37 +1,70 @@
 const assert = require('assert')
 const mocks = require('./async-example.mocks')
-
 const DataPoint = require('../')
 const dataPoint = DataPoint.create()
 const { map } = DataPoint.helpers
 
 dataPoint.addEntities({
+  // remote service request
   'request:Planet': {
-    // creates: https://swapi.co/api/people/1/
+    // {value.planetId} injects the
+    // value from the accumulator
+    // creates: https://swapi.co/api/planets/1/
     url: 'https://swapi.co/api/planets/{value.planetId}'
   },
 
-  'hash:Planet': {
-    value: 'request:Planet',
-    mapKeys: {
-      name: '$name',
-      population: '$population',
-      residents: [
-        '$residents',
-        map([
-          'request:Resident',
-          {
-            name: '$name',
-            gender: '$gender',
-            birthYear: '$birth_year'
-          }
-        ])
-      ]
-    }
+  // model entity to resolve a Planet
+  'model:Planet': {
+    inputType: 'schema:DataInput',
+    value: [
+      // hit request:Planet data source
+      'request:Planet',
+      // map result to ObjectReducer
+      {
+        // map name key
+        name: '$name',
+        population: '$population',
+        // residents is an array of urls
+        // eg. https://swapi.co/api/people/1/
+        // where each url gets mapped
+        // to a model:Resident
+        residents: ['$residents', map('model:Resident')]
+      }
+    ]
+  },
+
+  // model entity to resolve a Planet
+  'model:Resident': {
+    inputType: 'string',
+    value: [
+      // hit request:Resident
+      'request:Resident',
+      // extract data
+      {
+        name: '$name',
+        gender: '$gender',
+        birthYear: '$birth_year'
+      }
+    ]
   },
 
   'request:Resident': {
+    // check input is string
+    inputType: 'string',
     url: '{value}'
+  },
+
+  // schema to verify data input
+  'schema:DataInput': {
+    schema: {
+      type: 'object',
+      properties: {
+        planetId: {
+          $id: '/properties/planet',
+          type: 'integer'
+        }
+      }
+    }
   }
 })
 
@@ -42,7 +75,7 @@ const input = {
 // mock actual calls to server
 mocks()
 
-dataPoint.transform('hash:Planet', input).then(acc => {
+dataPoint.transform('model:Planet', input).then(acc => {
   const result = acc.value
   assert.equal(result.name, 'Tatooine')
   assert.equal(result.population, '200000')
diff --git a/packages/data-point/examples/entity-entry-before.js b/packages/data-point/examples/entity-entry-before.js
deleted file mode 100644
index d6e0d92..0000000
--- a/packages/data-point/examples/entity-entry-before.js
+++ /dev/null
@@ -1,23 +0,0 @@
-const dataPoint = require('../').create()
-const _ = require('lodash')
-
-const isArray = () => acc => {
-  if (_.isArray(acc.value)) {
-    // if the value is valid, then just pass it along
-    return acc.value
-  }
-
-  throw new Error(`${acc.value} should be an Array`)
-}
-
-dataPoint.addEntities({
-  'entry:foo': {
-    before: isArray(),
-    value: '$'
-  }
-})
-
-dataPoint.transform('entry:foo', [3, 15, 6, 3, 8]).then(acc => {
-  console.log(acc.value)
-  // [ 3, 15, 6, 3, 8 ]
-})
diff --git a/packages/data-point/examples/entity-entry-error-handled.js b/packages/data-point/examples/entity-entry-error-handled.js
deleted file mode 100644
index 2cdaeff..0000000
--- a/packages/data-point/examples/entity-entry-error-handled.js
+++ /dev/null
@@ -1,38 +0,0 @@
-const dataPoint = require('../').create()
-
-const isArray = () => acc => {
-  if (acc.value instanceof Array) {
-    // if the value is valid, then just pass it along
-    return acc.value
-  }
-
-  // notice how we pass this Error object as the FIRST parameter,
-  // this tells DataPoint there was an error, and treat it as such.
-  throw new Error(`${acc.value} should be an Array`)
-}
-
-dataPoint.addEntities({
-  'entry:foo': {
-    // points to a NON Array value
-    value: '$a',
-    after: isArray(),
-    error: acc => {
-      console.log('Value is invalid, resolving to empty array')
-      // passing a a value as the second argument
-      // will stop the propagation of the error
-      return []
-    }
-  }
-})
-
-const input = {
-  a: {
-    b: [3, 15, 6, 3, 8]
-  }
-}
-
-dataPoint.transform('entry:foo', input).then(acc => {
-  console.log(acc.value)
-  // Value is invalid, resolving to empty array
-  // []
-})
diff --git a/packages/data-point/examples/entity-entry-error-resolved.js b/packages/data-point/examples/entity-entry-error-resolved.js
deleted file mode 100644
index df4905e..0000000
--- a/packages/data-point/examples/entity-entry-error-resolved.js
+++ /dev/null
@@ -1,42 +0,0 @@
-const dataPoint = require('../').create()
-
-const logError = () => (errCtx, next) => {
-  // errCtx.value holds the actual value
-  console.log(errCtx.value.toString())
-
-  // if we wish to bubble it up, then pass it to
-  // the next() as the first parameter
-  next(errCtx.value)
-
-  // if we wished not to bubble it, we could pass
-  // an empty first param, and a second value to
-  // be used as the final resolved value
-  // next(null, 30) <-- this is just an example
-}
-
-const isArray = () => acc => {
-  if (!(acc.value instanceof Array)) {
-    throw new Error(`${acc.value} should be an Array`)
-  }
-
-  return acc.value
-}
-
-dataPoint.addEntities({
-  'entry:foo': {
-    value: '$a',
-    after: isArray(),
-    error: logError()
-  }
-})
-
-const input = {
-  a: {
-    b: [3, 15, 6, 3, 8]
-  }
-}
-
-dataPoint.transform('entry:foo', input).catch(error => {
-  console.log('got ya!', error.toString())
-  // got ya! Error: [object Object] should be an Array
-})
diff --git a/packages/data-point/examples/entity-entry-error-rethrown.js b/packages/data-point/examples/entity-entry-error-rethrown.js
deleted file mode 100644
index f4be76b..0000000
--- a/packages/data-point/examples/entity-entry-error-rethrown.js
+++ /dev/null
@@ -1,37 +0,0 @@
-const dataPoint = require('../').create()
-const assert = require('assert')
-
-const isArray = () => acc => {
-  if (!(acc.value instanceof Array)) {
-    throw new Error(`${acc.value} should be an Array`)
-  }
-
-  return acc.value
-}
-
-const resolveTo = value => errCtx => {
-  // since we dont pass the error back
-  // it will resolve to the new value
-  return value
-}
-
-dataPoint.addEntities({
-  'entry:foo': {
-    value: '$a',
-    after: isArray(),
-    // resolving the value to empty array
-    error: resolveTo([])
-  }
-})
-
-const input = {
-  a: {
-    b: [3, 15, 6, 3, 8]
-  }
-}
-
-dataPoint.transform('entry:foo', input).then(acc => {
-  assert.deepEqual(acc.value, [])
-  console.log(acc.value)
-  // []
-})
diff --git a/packages/data-point/examples/entity-model-basic.js b/packages/data-point/examples/entity-model-basic.js
new file mode 100644
index 0000000..081848f
--- /dev/null
+++ b/packages/data-point/examples/entity-model-basic.js
@@ -0,0 +1,29 @@
+const assert = require('assert')
+const DataPoint = require('../')
+const dataPoint = DataPoint.create()
+
+const input = {
+  a: {
+    b: {
+      c: [1, 2, 3]
+    }
+  }
+}
+
+const getMax = acc => {
+  return Math.max.apply(null, acc.value)
+}
+
+const multiplyBy = number => acc => {
+  return acc.value * number
+}
+
+dataPoint.addEntities({
+  'model:foo': {
+    value: ['$a.b.c', getMax, multiplyBy(10)]
+  }
+})
+
+dataPoint.transform('model:foo', input).then(acc => {
+  assert.equal(acc.value, 30)
+})
diff --git a/packages/data-point/examples/entity-model-before.js b/packages/data-point/examples/entity-model-before.js
new file mode 100644
index 0000000..f51b80f
--- /dev/null
+++ b/packages/data-point/examples/entity-model-before.js
@@ -0,0 +1,17 @@
+const dataPoint = require('../').create()
+const assert = require('assert')
+
+const toArray = acc => {
+  return Array.isArray(acc.value) ? acc.value : [acc.value]
+}
+
+dataPoint.addEntities({
+  'model:foo': {
+    before: toArray,
+    value: '$'
+  }
+})
+
+dataPoint.transform('model:foo', 100).then(acc => {
+  assert.deepEqual(acc.value, [100])
+})
diff --git a/packages/data-point/examples/entity-model-error-handled.js b/packages/data-point/examples/entity-model-error-handled.js
new file mode 100644
index 0000000..9549a0d
--- /dev/null
+++ b/packages/data-point/examples/entity-model-error-handled.js
@@ -0,0 +1,32 @@
+const assert = require('assert')
+const dataPoint = require('../').create()
+
+dataPoint.addEntities({
+  'model:getArray': {
+    // points to a NON Array value
+    value: '$a.b',
+    outputType: 'array',
+    error: acc => {
+      // prints out the error
+      // message generated by
+      // isArray type check
+      console.log(acc.value.message)
+
+      console.log('Value is invalid, resolving to empty array')
+
+      // passing a value will stop
+      // the propagation of the error
+      return []
+    }
+  }
+})
+
+const input = {
+  a: {
+    b: 'foo'
+  }
+}
+
+dataPoint.transform('model:getArray', input).then(acc => {
+  assert.deepEqual(acc.value, [])
+})
diff --git a/packages/data-point/examples/entity-model-error-rethrow.js b/packages/data-point/examples/entity-model-error-rethrow.js
new file mode 100644
index 0000000..011c7f9
--- /dev/null
+++ b/packages/data-point/examples/entity-model-error-rethrow.js
@@ -0,0 +1,34 @@
+const dataPoint = require('../').create()
+const assert = require('assert')
+
+const logError = acc => {
+  // acc.value holds the actual Error Object
+  console.log(acc.value.toString())
+  throw acc.value
+}
+
+dataPoint.addEntities({
+  'model:getArray': {
+    value: '$a',
+    outputType: 'array',
+    error: logError
+  }
+})
+
+const input = {
+  a: {
+    b: 'foo'
+  }
+}
+
+dataPoint
+  .transform('model:getArray', input)
+  .then(() => {
+    // should not execute
+    assert.ok(false)
+  })
+  .catch(error => {
+    console.log(error.toString())
+    // should execute
+    assert.ok(true)
+  })
diff --git a/packages/data-point/examples/model-type-check-custom.js b/packages/data-point/examples/model-type-check-custom.js
new file mode 100644
index 0000000..ed83934
--- /dev/null
+++ b/packages/data-point/examples/model-type-check-custom.js
@@ -0,0 +1,24 @@
+const assert = require('assert')
+const DataPoint = require('../')
+const dataPoint = DataPoint.create()
+
+dataPoint.addEntities({
+  'model:string': {
+    value: '$name',
+    outputType: acc => {
+      const valid = typeof acc.value === 'string' && acc.value.length > 5
+      if (!valid) {
+        throw new Error('string and length > 5')
+      }
+    }
+  }
+})
+
+const input = {
+  name: 'DataPoint'
+}
+
+dataPoint.transform('model:string', input).then(acc => {
+  assert.equal(acc.value, 'DataPoint')
+  console.log(acc.value)
+})
diff --git a/packages/data-point/examples/model-type-check-helper.js b/packages/data-point/examples/model-type-check-helper.js
new file mode 100644
index 0000000..ccd6ea2
--- /dev/null
+++ b/packages/data-point/examples/model-type-check-helper.js
@@ -0,0 +1,19 @@
+const DataPoint = require('../')
+const dataPoint = DataPoint.create()
+const assert = require('assert')
+
+dataPoint.addEntities({
+  'model:string': {
+    value: '$name',
+    outputType: 'string'
+  }
+})
+
+const input = {
+  name: 'DataPoint'
+}
+
+dataPoint.transform('model:string', input).then(acc => {
+  assert.equal(acc.value, 'DataPoint')
+  console.log(acc.value)
+})
diff --git a/packages/data-point/lib/core/core.js b/packages/data-point/lib/core/core.js
index a5b73a1..ce5c519 100644
--- a/packages/data-point/lib/core/core.js
+++ b/packages/data-point/lib/core/core.js
@@ -11,6 +11,7 @@ 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')
@@ -78,7 +79,7 @@ function create (spec) {
   manager.addEntityType('transform', EntityTransform)
   manager.addEntityType('entry', EntityEntry)
   // alias to entry, may be used to process any object type
-  manager.addEntityType('model', EntityEntry)
+  manager.addEntityType('model', EntityModel)
   manager.addEntityType('hash', EntityHash)
   manager.addEntityType('collection', EntityCollection)
   manager.addEntityType('request', EntityRequest)
diff --git a/packages/data-point/lib/entity-types/base-entity/factory.js b/packages/data-point/lib/entity-types/base-entity/factory.js
index 1849758..f6dfd37 100644
--- a/packages/data-point/lib/entity-types/base-entity/factory.js
+++ b/packages/data-point/lib/entity-types/base-entity/factory.js
@@ -1,8 +1,30 @@
 const deepFreeze = require('deep-freeze')
 const defaultTo = require('lodash/defaultTo')
+const typeCheckFunctionReducers = require('../../helpers/type-check-function-reducers')
 
 const createReducer = require('../../reducer-types').create
 
+const typeCheckModifiers = {
+  string: typeCheckFunctionReducers.isString,
+  number: typeCheckFunctionReducers.isNumber,
+  boolean: typeCheckFunctionReducers.isBoolean,
+  function: typeCheckFunctionReducers.isFunction,
+  error: typeCheckFunctionReducers.isError,
+  array: typeCheckFunctionReducers.isArray,
+  object: typeCheckFunctionReducers.isObject
+}
+
+function getTypeModifier (reducer) {
+  const typeCheckModifier = typeCheckModifiers[reducer]
+  if (typeCheckModifier) {
+    return typeCheckModifier
+  }
+
+  return reducer
+}
+
+module.exports.getTypeModifier = getTypeModifier
+
 /**
  * @param {Function} Factory - factory function to create the entity
  * @param {Object} spec - spec for the Entity
@@ -29,6 +51,16 @@ function create (Factory, spec, id) {
     entity.error = createReducer(spec.error)
   }
 
+  if (spec.inputType) {
+    const inputType = getTypeModifier(spec.inputType)
+    entity.inputType = createReducer(inputType)
+  }
+
+  if (spec.outputType) {
+    const outputType = getTypeModifier(spec.outputType)
+    entity.outputType = createReducer(outputType)
+  }
+
   entity.params = deepFreeze(defaultTo(spec.params, {}))
 
   return entity
diff --git a/packages/data-point/lib/entity-types/base-entity/resolve.js b/packages/data-point/lib/entity-types/base-entity/resolve.js
index a2be47c..9eff06a 100644
--- a/packages/data-point/lib/entity-types/base-entity/resolve.js
+++ b/packages/data-point/lib/entity-types/base-entity/resolve.js
@@ -97,6 +97,12 @@ function resolveMiddleware (manager, name, acc) {
 
 module.exports.resolveMiddleware = resolveMiddleware
 
+function typeCheck (manager, acc, reducer, resolveReducer) {
+  // if no error returns original accumulator
+  // this prevents typeCheckTransform from mutating the value
+  return resolveReducer(manager, acc, reducer).return(acc)
+}
+
 /**
  * @param {Object} manager
  * @param {Function} resolveReducer
@@ -137,9 +143,15 @@ function resolveEntity (
     .then(acc =>
       resolveMiddleware(manager, `${reducer.entityType}:before`, acc)
     )
+    .then(acc =>
+      typeCheck(manager, acc, acc.reducer.spec.inputType, resolveReducer)
+    )
     .then(acc => resolveReducer(manager, acc, acc.reducer.spec.before))
     .then(acc => mainResolver(acc, resolveReducerBound))
     .then(acc => resolveReducer(manager, acc, acc.reducer.spec.after))
+    .then(acc =>
+      typeCheck(manager, acc, acc.reducer.spec.outputType, resolveReducer)
+    )
     .then(acc => resolveMiddleware(manager, `${reducer.entityType}:after`, acc))
     .then(acc => resolveMiddleware(manager, `after`, acc))
     .catch(error => {
diff --git a/packages/data-point/lib/entity-types/entity-model/factory.js b/packages/data-point/lib/entity-types/entity-model/factory.js
new file mode 100644
index 0000000..0ab3421
--- /dev/null
+++ b/packages/data-point/lib/entity-types/entity-model/factory.js
@@ -0,0 +1,21 @@
+const createBaseEntity = require('../base-entity').create
+
+/**
+ * @class
+ */
+function EntityModel () {}
+
+module.exports.EntityModel = EntityModel
+
+/**
+ * Creates new Entity Object
+ * @param  {Object} spec - spec
+ * @param {string} id - Entity id
+ * @return {EntityModel} Entity Object
+ */
+function create (spec, id) {
+  const entity = createBaseEntity(EntityModel, spec, id)
+  return Object.freeze(entity)
+}
+
+module.exports.create = create
diff --git a/packages/data-point/lib/entity-types/entity-model/index.js b/packages/data-point/lib/entity-types/entity-model/index.js
new file mode 100644
index 0000000..c1ae7c8
--- /dev/null
+++ b/packages/data-point/lib/entity-types/entity-model/index.js
@@ -0,0 +1,9 @@
+/* eslint global-require: 0 */
+
+const factory = require('./factory')
+const resolve = require('./resolve').resolve
+
+module.exports = {
+  create: factory.create,
+  resolve: resolve
+}
diff --git a/packages/data-point/lib/entity-types/entity-model/resolve.js b/packages/data-point/lib/entity-types/entity-model/resolve.js
new file mode 100644
index 0000000..2ea0206
--- /dev/null
+++ b/packages/data-point/lib/entity-types/entity-model/resolve.js
@@ -0,0 +1,9 @@
+/**
+ * @param {Accumulator} accumulator
+ * @param {Function} resolveReducer
+ */
+function resolve (accumulator, resolveReducer) {
+  return resolveReducer(accumulator, accumulator.reducer.spec.value)
+}
+
+module.exports.resolve = resolve
diff --git a/packages/data-point/lib/helpers/helpers.js b/packages/data-point/lib/helpers/helpers.js
index aa5116f..8ad58ab 100644
--- a/packages/data-point/lib/helpers/helpers.js
+++ b/packages/data-point/lib/helpers/helpers.js
@@ -3,7 +3,24 @@ const Promise = require('bluebird')
 const resolveReducer = require('../reducer-types').resolve
 const AccumulatorFactory = require('../accumulator/factory')
 
-module.exports.helpers = require('../reducer-types/reducer-helpers').stubFactories
+const { stubFactories } = require('../reducer-types/reducer-helpers')
+const typeCheckFunctionReducers = require('./type-check-function-reducers')
+
+module.exports.helpers = {
+  assign: stubFactories.assign,
+  filter: stubFactories.filter,
+  find: stubFactories.find,
+  map: stubFactories.map,
+  omit: stubFactories.omit,
+  pick: stubFactories.pick,
+  isString: typeCheckFunctionReducers.isString,
+  isNumber: typeCheckFunctionReducers.isNumber,
+  isBoolean: typeCheckFunctionReducers.isBoolean,
+  isFunction: typeCheckFunctionReducers.isFunction,
+  isError: typeCheckFunctionReducers.isError,
+  isArray: typeCheckFunctionReducers.isArray,
+  isObject: typeCheckFunctionReducers.isObject
+}
 
 module.exports.isReducer = require('../reducer-types').isReducer
 
diff --git a/packages/data-point/lib/helpers/type-check-function-reducers.js b/packages/data-point/lib/helpers/type-check-function-reducers.js
new file mode 100644
index 0000000..46bd7ea
--- /dev/null
+++ b/packages/data-point/lib/helpers/type-check-function-reducers.js
@@ -0,0 +1,61 @@
+const Util = require('util')
+const _ = require('lodash')
+const utils = require('../utils')
+
+function errorMessage (acc, expectedType) {
+  const entityId = _.get(acc, 'reducer.spec.id', 'value')
+  return Util.format(
+    '%s did not pass type check, type expected: %s but recevied: %s. The value received is: %s',
+    entityId,
+    expectedType,
+    utils.typeOf(acc.value),
+    _.truncate(Util.inspect(acc.value, { breakLength: Infinity }), {
+      length: 30
+    })
+  )
+}
+
+function isString (acc) {
+  if (typeof acc.value === 'string') return acc.value
+  throw new Error(errorMessage(acc, 'string'))
+}
+
+function isNumber (acc) {
+  if (typeof acc.value === 'number') return acc.value
+  throw new Error(errorMessage(acc, 'number'))
+}
+
+function isBoolean (acc) {
+  if (typeof acc.value === 'boolean') return acc.value
+  throw new Error(errorMessage(acc, 'boolean'))
+}
+
+function isFunction (acc) {
+  if (typeof acc.value === 'function') return acc.value
+  throw new Error(errorMessage(acc, 'function'))
+}
+
+function isError (acc) {
+  if (acc.value instanceof Error) return acc.value
+  throw new Error(errorMessage(acc, 'function'))
+}
+
+function isArray (acc) {
+  if (Array.isArray(acc.value)) return acc.value
+  throw new Error(errorMessage(acc, 'Array'))
+}
+
+function isObject (acc) {
+  if (_.isPlainObject(acc.value)) return acc.value
+  throw new Error(errorMessage(acc, 'Object'))
+}
+
+module.exports = {
+  isString,
+  isNumber,
+  isBoolean,
+  isFunction,
+  isError,
+  isArray,
+  isObject
+}
diff --git a/packages/data-point/lib/reducer-types/resolve.js b/packages/data-point/lib/reducer-types/resolve.js
index 5cee57a..dc2deca 100644
--- a/packages/data-point/lib/reducer-types/resolve.js
+++ b/packages/data-point/lib/reducer-types/resolve.js
@@ -1,3 +1,4 @@
+const Promise = require('bluebird')
 const ReducerEntity = require('./reducer-entity')
 const ReducerFunction = require('./reducer-function')
 const ReducerList = require('./reducer-list')


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 19d6e79..bff5c45 100644
--- a/packages/data-point/lib/__snapshots__/index.test.js.snap
+++ b/packages/data-point/lib/__snapshots__/index.test.js.snap
@@ -9,6 +9,13 @@ Object {
     "assign": [Function],
     "filter": [Function],
     "find": [Function],
+    "isArray": [Function],
+    "isBoolean": [Function],
+    "isError": [Function],
+    "isFunction": [Function],
+    "isNumber": [Function],
+    "isObject": [Function],
+    "isString": [Function],
     "map": [Function],
     "omit": [Function],
     "pick": [Function],
diff --git a/packages/data-point/lib/entity-types/base-entity/__snapshots__/resolve.test.js.snap b/packages/data-point/lib/entity-types/base-entity/__snapshots__/resolve.test.js.snap
new file mode 100644
index 0000000..fb87406
--- /dev/null
+++ b/packages/data-point/lib/entity-types/base-entity/__snapshots__/resolve.test.js.snap
@@ -0,0 +1,7 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ResolveEntity.resolveEntity if custom typeCheck throws then fail 1`] = `[Error: custom type error]`;
+
+exports[`ResolveEntity.resolveEntity inputType - throws error if inputType does not pass 1`] = `[Error: model:c.0 did not pass type check, type expected: number but recevied: string. The value received is: 'foo']`;
+
+exports[`ResolveEntity.resolveEntity outputType - throws error if outputType does not pass 1`] = `[Error: model:c.1 did not pass type check, type expected: string but recevied: number. The value received is: 1]`;
diff --git a/packages/data-point/lib/entity-types/base-entity/factory.test.js b/packages/data-point/lib/entity-types/base-entity/factory.test.js
index 2ad2ac2..0869930 100644
--- a/packages/data-point/lib/entity-types/base-entity/factory.test.js
+++ b/packages/data-point/lib/entity-types/base-entity/factory.test.js
@@ -1,11 +1,28 @@
 /* eslint-env jest */
 
-const createBaseEntity = require('./factory').create
+const Factory = require('./factory')
 
-describe('factory.createBaseEntity', () => {
+const typeCheckFunctionReducers = require('../../helpers/type-check-function-reducers')
+
+describe('Factory.getTypeModifier', () => {
+  test('It should return function reducer it matches any of the predefined types', () => {
+    expect(Factory.getTypeModifier('string')).toEqual(
+      typeCheckFunctionReducers.isString
+    )
+    expect(Factory.getTypeModifier('number')).toEqual(
+      typeCheckFunctionReducers.isNumber
+    )
+  })
+
+  test('It should return the reducer -as-is- if no match', () => {
+    expect(Factory.getTypeModifier('foo:bar')).toEqual('foo:bar')
+  })
+})
+
+describe('Factory.create', () => {
   test('It should create entity defaults', () => {
     function FooEntity () {}
-    const entity = createBaseEntity(
+    const entity = Factory.create(
       FooEntity,
       {
         before: '$',
diff --git a/packages/data-point/lib/entity-types/base-entity/resolve.test.js b/packages/data-point/lib/entity-types/base-entity/resolve.test.js
index 975aa8d..164de9c 100644
--- a/packages/data-point/lib/entity-types/base-entity/resolve.test.js
+++ b/packages/data-point/lib/entity-types/base-entity/resolve.test.js
@@ -197,6 +197,49 @@ describe('ResolveEntity.resolveEntity', () => {
         expect(err).toHaveProperty('message', 'test')
       })
   })
+
+  test('inputType - throws error if inputType does not pass', () => {
+    return resolveEntity('model:c.0', 'foo')
+      .catch(e => e)
+      .then(e => {
+        expect(e).toMatchSnapshot()
+      })
+  })
+
+  test('inputType - if typeCheck passes then resolve normal', () => {
+    return resolveEntity('model:c.0', 1).then(ac => {
+      expect(ac.value).toEqual(1)
+    })
+  })
+
+  test('outputType - throws error if outputType does not pass', () => {
+    return resolveEntity('model:c.1', 1)
+      .catch(e => e)
+      .then(e => {
+        expect(e).toMatchSnapshot()
+      })
+  })
+
+  test('outputType - if typeCheck passes then resolve normal', () => {
+    return resolveEntity('model:c.1', 'foo').then(ac => {
+      expect(ac.value).toEqual('foo')
+    })
+  })
+
+  test('typeCheck should not be able to change acc.value', () => {
+    return resolveEntity('model:c.2', 'my string').then(result => {
+      expect(result.value).toEqual('my string')
+    })
+  })
+
+  test('if custom typeCheck throws then fail', () => {
+    return resolveEntity('model:c.3', 123)
+      .catch(e => e)
+      .then(result => {
+        expect(result).toBeInstanceOf(Error)
+        expect(result).toMatchSnapshot()
+      })
+  })
 })
 
 describe('ResolveEntity.resolve', () => {
diff --git a/packages/data-point/lib/entity-types/entity-model/factory.test.js b/packages/data-point/lib/entity-types/entity-model/factory.test.js
new file mode 100644
index 0000000..6bcca6e
--- /dev/null
+++ b/packages/data-point/lib/entity-types/entity-model/factory.test.js
@@ -0,0 +1,11 @@
+/* eslint-env jest */
+
+const Factory = require('./factory')
+
+test('Factory#create', () => {
+  const obj = Factory.create({
+    value: ['$foo', (acc, done) => {}]
+  })
+
+  expect(obj).toHaveProperty('value')
+})
diff --git a/packages/data-point/lib/entity-types/entity-model/resolve.test.js b/packages/data-point/lib/entity-types/entity-model/resolve.test.js
new file mode 100644
index 0000000..e5a1883
--- /dev/null
+++ b/packages/data-point/lib/entity-types/entity-model/resolve.test.js
@@ -0,0 +1,42 @@
+/* eslint-env jest */
+
+const Resolve = require('./resolve')
+
+const FixtureStore = require('../../../test/utils/fixture-store')
+
+const helpers = require('../../helpers')
+
+let dataPoint
+let resolveReducerBound
+
+function transform (entityId, value, options) {
+  const reducer = dataPoint.entities.get(entityId)
+  const accumulator = helpers.createAccumulator(
+    value,
+    Object.assign(
+      {
+        context: reducer
+      },
+      options
+    )
+  )
+  return Resolve.resolve(accumulator, resolveReducerBound)
+}
+
+beforeAll(() => {
+  dataPoint = FixtureStore.create()
+  resolveReducerBound = helpers.createReducerResolver(dataPoint)
+})
+
+describe('Model.resolve', () => {
+  test('Entry#resolve - resolve empty', () => {
+    return transform('model:a.0').then(result => {
+      expect(result.value).toEqual(undefined)
+    })
+  })
+  test('Entry#resolve - resolve any Reducer', () => {
+    return transform('model:a.1', 5).then(ac => {
+      expect(ac.value).toEqual(10)
+    })
+  })
+})
diff --git a/packages/data-point/lib/helpers/__snapshots__/type-check-function-reducers.test.js.snap b/packages/data-point/lib/helpers/__snapshots__/type-check-function-reducers.test.js.snap
new file mode 100644
index 0000000..30c16da
--- /dev/null
+++ b/packages/data-point/lib/helpers/__snapshots__/type-check-function-reducers.test.js.snap
@@ -0,0 +1,17 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`isArray It should throw error when not matched 1`] = `"value did not pass type check, type expected: Array but recevied: string. The value received is: 'foo'"`;
+
+exports[`isBoolean It should throw error when not matched 1`] = `"value did not pass type check, type expected: boolean but recevied: string. The value received is: 'foo'"`;
+
+exports[`isBoolean It should throw error when not matched 2`] = `"value did not pass type check, type expected: boolean but recevied: string. The value received is: 'foo'"`;
+
+exports[`isError It should throw error when not matched 1`] = `"value did not pass type check, type expected: function but recevied: string. The value received is: 'foo'"`;
+
+exports[`isFunction It should throw error when not matched 1`] = `"value did not pass type check, type expected: function but recevied: string. The value received is: 'foo'"`;
+
+exports[`isNumber It should throw error when not matched 1`] = `"value did not pass type check, type expected: number but recevied: string. The value received is: 'foo'"`;
+
+exports[`isObject It should throw error when not matched 1`] = `"value did not pass type check, type expected: Object but recevied: array. The value received is: []"`;
+
+exports[`isString It should throw error when not matched 1`] = `"value did not pass type check, type expected: string but recevied: number. The value received is: 1"`;
diff --git a/packages/data-point/lib/helpers/type-check-function-reducers.test.js b/packages/data-point/lib/helpers/type-check-function-reducers.test.js
new file mode 100644
index 0000000..6001bda
--- /dev/null
+++ b/packages/data-point/lib/helpers/type-check-function-reducers.test.js
@@ -0,0 +1,58 @@
+/* eslint-env jest */
+
+const {
+  isString,
+  isNumber,
+  isBoolean,
+  isFunction,
+  isError,
+  isArray,
+  isObject
+} = require('./type-check-function-reducers')
+
+function testTypeChecker (typeCheck, goodValue, badValue, type) {
+  test('It should return true if type match', () => {
+    const acc = {
+      value: goodValue
+    }
+    expect(typeCheck(acc)).toBe(goodValue)
+  })
+  test(`It should throw error when not matched`, () => {
+    const acc = {
+      value: badValue
+    }
+    expect(() => {
+      typeCheck(acc)
+    }).toThrowErrorMatchingSnapshot()
+  })
+}
+
+describe('isString', () => {
+  testTypeChecker(isString, 'foo', 1, 'string')
+})
+
+describe('isNumber', () => {
+  testTypeChecker(isNumber, 1, 'foo', 'number')
+})
+
+describe('isBoolean', () => {
+  testTypeChecker(isBoolean, true, 'foo', 'boolean')
+  testTypeChecker(isBoolean, false, 'foo', 'boolean')
+})
+
+describe('isFunction', () => {
+  const fn = () => {}
+  testTypeChecker(isFunction, fn, 'foo', 'function')
+})
+
+describe('isError', () => {
+  testTypeChecker(isError, new Error(), 'foo', 'Error')
+})
+
+describe('isArray', () => {
+  testTypeChecker(isArray, [], 'foo', 'Array')
+})
+
+describe('isObject', () => {
+  testTypeChecker(isObject, {}, [], 'Object')
+})
diff --git a/packages/data-point/test/definitions/entities.js b/packages/data-point/test/definitions/entities.js
index d39aba7..a8e2a0b 100644
--- a/packages/data-point/test/definitions/entities.js
+++ b/packages/data-point/test/definitions/entities.js
@@ -3,6 +3,7 @@ module.exports = Object.assign(
   require('./transform'),
   require('./schema'),
   require('./entry'),
+  require('./model'),
   require('./hash'),
   require('./collection'),
   require('./requests'),
diff --git a/packages/data-point/test/definitions/model.js b/packages/data-point/test/definitions/model.js
new file mode 100644
index 0000000..b0c1fc0
--- /dev/null
+++ b/packages/data-point/test/definitions/model.js
@@ -0,0 +1,37 @@
+module.exports = {
+  'model:a.0': {},
+  'model:a.1': {
+    value: acc => acc.value + 5
+  },
+  'model:a.2': {
+    value: acc => acc.value + 5
+  },
+
+  // entities c.* are for testing
+  // inputType and outputType.
+  'model:c.0': {
+    inputType: 'number'
+  },
+
+  'model:c.1': {
+    outputType: 'string'
+  },
+
+  'model:c.2': {
+    // test that its immutable
+    outputType: acc => {
+      return 'cant happen'
+    }
+  },
+
+  'model:c.3': {
+    // custom type checking
+    inputType: acc => {
+      if (typeof acc.value !== 'string') {
+        throw new Error('custom type error')
+      }
+
+      return acc.value
+    }
+  }
+}