Skip to content

Commit

Permalink
3.0.0
Browse files Browse the repository at this point in the history
- Remove createPureProduct in favor of pure
No need to create multiple pureProduct, only one is required
- Talent are now called with the product they will output
It looks a bit like a constructor with this set to the created object so that you got a pointer on it
- Restore valueOf on product passed to talent
We need a pointer to the talentedProduct in many case.
- Product are now frozen objects
Cannot add new properties
Cannot reconfigure existing properties
- Much more documentation
- One talent per product
This is an implementation detail but now a product does not hold a list of talent
  • Loading branch information
dmail authored Dec 21, 2017
1 parent c5a27a6 commit d3a775a
Show file tree
Hide file tree
Showing 10 changed files with 275 additions and 177 deletions.
80 changes: 47 additions & 33 deletions docs/api.md
Original file line number Diff line number Diff line change
@@ -1,51 +1,45 @@
# api

* [createPureProduct()](#createpureproduct)
* [pure](#pure)
* [isProduct(value)](#isproductvalue)
* [mixin(product, ...talents)](#mixinproduct-talents)
* [hasTalent(talent, product)](#hastalenttalent-product)
* [createFactory(fn)](#createfactoryfn)
* [isProducedBy(factory, value)](#isproducedbyfactory-value)
* [replicate(product)](#replicateproduct)

## createPureProduct()
## pure

Returns a product without any talent (see this as an empty object).
An object without any talent

```javascript
import { createPureProduct } from "@dmail/mixin"

const pureProduct = createPureProduct()
import { pure } from "@dmail/mixin"
```

[source](../src/mixin.js) | [test](../src/mixin.test.js)

## isProduct(value)

```javascript
import { isProduct, createPureProduct } from "@dmail/mixin"
import { isProduct, pure } from "@dmail/mixin"

isProduct(null) // false
isProduct({}) // false
isProduct(createPureProduct()) // true
isProduct(pure) // true
```

[source](../src/mixin.js) | [test](../src/mixin.test.js)

## mixin(product, ...talents)

```javascript
import { createPureProduct, mixin } from "@dmail/mixin"

const product = mixin(
createPureProduct(),
() => {
return { getAnswer: () => 42 }
},
({ getAnswer }) => {
return { getAnswerOpposite: () => getAnswer() * -1 }
},
)
import { pure, mixin } from "@dmail/mixin"

const answerToEverythingTalent = () => ({ getAnswer: () => 42 })
const oppositeAnswerTalent = ({ getAnswer }) => ({ getAnswerOpposite: () => getAnswer() * -1 })

const intermediateProduct = mixin(pure, answerToEverythingTalent)
const product = mixin(intermediateProduct, oppositeAnswerTalent)

product.getAnswer() // 42
product.getAnswerOpposite() // -42
Expand All @@ -56,49 +50,48 @@ product.getAnswerOpposite() // -42
## hasTalent(talent, product)

```javascript
import { createPureProduct, mixin, hasTalent } from "@dmail/mixin"
import { pure, mixin, hasTalent } from "@dmail/mixin"

const pureProduct = createPureProduct()
const talent = () => null
const talentedProduct = mixin(pureProduct, talent)
const talentedProduct = mixin(pure, talent)

hasTalent(talent, pureProduct) // false
hasTalent(talent, pure) // false
hasTalent(talent, talentedProduct) // true
```

[source](../src/mixin.js) | [test](../src/mixin.test.js)

## createFactory(fn)
## createFactory(talent)

Returns a function which, when called, will return a talented product

```javascript
import { createFactory } from "@dmail/mixin"

const createCounter = createFactory(({ count = 0 }) => {
const get = () => count
const increment = () => {
count++
return count
}

return { get, increment }
return { increment }
})

const counter = createCounter()
const counter = createCounter({ count: 1 })
counter.increment() // 2
```

[source](../src/factory.js) | [test](../src/factory.test.js)

## isProducedBy(factory, product)

```javascript
import { createFactory, createPureProduct } from "@dmail/mixin"
import { createFactory, pure } from "@dmail/mixin"

const factory = createFactory()

const pureProduct = createPureProduct()
const factoryProduct = factory()

isProducedBy(factory, pureProduct) // false
isProducedBy(factory, pure) // false
isProducedBy(factory, factoryProduct) // true
```

Expand Down Expand Up @@ -127,6 +120,27 @@ const counterClone = replicate(counter)
counterClone.increment() // 11
```

[source](../src/factory.js) | [test](../src/factory.test.js)
[source](../src/mixin.js) | [test](../src/mixin.test.js)

### Replicate expect talents to be pure functions

To replicate a product, replicate will reuse talents.
Consequently if some talent functions behaves differently when reused, the resulting product will inherit thoose differences.

#### Unpure talent example

```javascript
import { miwin, pure, replicate } from "@dmail/mixin"

const properties = {}
const unpureTalent = () => properties

const productModel = mixin(pure, unpureTalent)
properties.foo = true // mutate talent return value
const productCopy = replicate(productModel)

productModel.foo // undefined
productCopy.foo // true
```

Please note you can also use replicate on product returned by createPureProduct() or mixin()
Because of the mutation `productCopy` is not equivalent to `productModel`
27 changes: 27 additions & 0 deletions docs/talent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Talent

A talent is a function that should

* read properties from argument using param destructuring
* write properties returning an object

```javascript
// wrong
const talentWithoutPattern = (product) => {
product.value++
}

// right
const talentWithPattern = ({ value }) => {
return {
value: value + 1,
}
}
```

## Talent pattern advantages

* Destructuring param shows in a glimpse what you talent needs to read
* Returned object show in a glimpse what you talent needs to write
* Destructuring param prevent temptation to mutate talent argument (the product)
* Returned object is internally used to set non configurable and non writable properties
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@dmail/mixin",
"version": "2.1.0",
"version": "3.0.0",
"license": "MIT",
"repository": {
"type": "git",
Expand Down
28 changes: 14 additions & 14 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,34 @@
[![build](https://travis-ci.org/dmail/mixin.svg?branch=master)](http://travis-ci.org/dmail/mixin)
[![codecov](https://codecov.io/gh/dmail/mixin/branch/master/graph/badge.svg)](https://codecov.io/gh/dmail/mixin)

Factory functions composition
Object composition helpers

## Example

```javascript
import { createFactory, mixin } from "@dmail/mixin"
import { mixin, pure } from "@dmail/mixin"

const walkTalent = ({ getName }) => {
const walkTalent = ({ name }) => {
return {
walk: () => `${getName()} walk`,
walk: () => `${name} walk`,
}
}

const flyTalent = ({ getName }) => {
const flyTalent = ({ name }) => {
return {
fly: () => `${getName()} fly`,
fly: () => `${name} fly`,
}
}

const createAnimal = createFactory(({ name }) => {
const getName = () => name
return { getName }
})
const dog = mixin(pure, () => ({ name: "dog" }), walkTalent)
const duck = mixin(pure, () => ({ name: "duck" }), walkTalent, flyTalent)

const animal = mixin(createAnimal({ name: "foo" }), walkTalent, flyTalent)
dog.walk() // dog walk
dog.fly // undefined

animal.walk() // foo walk
animal.fly() // foo fly
duck.walk() // duck walk
duck.fly() // duck fly
```

Check the [API Documentation](./docs/api.md) for more
* [API documentation](./docs/api.md)
* [Talent documentation](./docs/talent.md)
18 changes: 13 additions & 5 deletions src/factory.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import { createPureProduct, mixin, hasTalent } from "./mixin.js"
import { pure, mixin, hasTalent } from "./mixin.js"

export const createFactory = (talent) => {
const pureProduct = createPureProduct()
const factory = (...args) => {
const parametrizedTalent = () => talent(...args)
parametrizedTalent.wrappedTalent = talent
return mixin(pureProduct, parametrizedTalent)
const { length } = args
if (length === 0) {
return mixin(pure, talent)
}
if (length === 1) {
const [arg] = args
if (typeof arg !== "object") {
throw new TypeError(`factory first argument must be an object`)
}
return mixin(pure, () => arg, talent)
}
throw new Error(`factory must be called with 1 or zero argument`)
}
factory.wrappedTalent = talent
return factory
Expand Down
50 changes: 42 additions & 8 deletions src/factory.test.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,55 @@
import { createFactory, isProducedBy } from "./factory.js"
import { replicate } from "./mixin.js"
import { createTest } from "@dmail/test"
import { expectMatch, expectFunction, expectProperties, expectChain } from "@dmail/expect"
import {
expectMatch,
expectFunction,
expectChain,
expectThrowWith,
matchErrorWith,
matchTypeErrorWith,
} from "@dmail/expect"

export const test = createTest({
"createFactory(fn) returns a function": () => {
const factory = createFactory(() => {})
return expectFunction(factory)
},
"createFactory(fn) returned function args are passed to factory": () => {
let passedArgs
const factory = createFactory((...args) => {
passedArgs = args
"createFactory(fn) calling returned factory with an object": () => {
let passedObject
const factory = createFactory((arg) => {
passedObject = arg
})
const args = [0, 1]
factory(...args)
return expectProperties(passedArgs, args)
const object = { foo: true }
factory(object)

return expectChain(
() => expectMatch(passedObject.foo, true),
() => expectMatch(Object.isExtensible(passedObject), false),
() => {
// you can still mutate original object (but should not do it)
object.bar = true
return expectMatch(object.bar, true)
},
)
},
"calling returned factory with 1 argument which is not an object": () => {
const factory = createFactory(() => {})
return expectThrowWith(
() => factory(true),
matchTypeErrorWith({
message: "factory first argument must be an object",
}),
)
},
"calling factory with more than 2 argument": () => {
const factory = createFactory(() => {})
return expectThrowWith(
() => factory(true, true),
matchErrorWith({
message: "factory must be called with 1 or zero argument",
}),
)
},
"createFactory can return properties which are set on return value": () => {
const method = () => {}
Expand Down
Loading

0 comments on commit d3a775a

Please sign in to comment.