0.3.18 • Published 5 years ago

wrap-data v0.3.18

Weekly downloads
47
License
MIT
Repository
-
Last release
5 years ago

wrap-data

Wrap data object into reactive streams, with helpers like unwrap, get, set, unset etc.

Build Status NPM Version Join the chat at https://gitter.im/wrap-data/Lobby

Install

NPM

npm i -S wrap-data

Browser

<script src="https://unpkg.com/wrap-data"></script>
<script>
    // wrapData is a global
    wrapData(...)
</script>

Usage

First you need a stream helper function or library, which conforms to the fantasy land applicative specification, flyd is recommended.

- Convert existing data and use wrapped data

const flyd = require('flyd')
const wrapData = require('wrap-data')
const data = {
    firstName: 'Hello',
    lastName: 'World'
}
const model = wrapData(flyd.stream)(data)
// model, and everything inside model is a stream!

// manually access a data
model().firstName  // stream(Hello)
model().lastName  // stream(World)

model.set('address', {city: 'Mercury'})  // set model.address

model().address().city()  // get value: 'Mercury'
model().address().city('Mars')  // set value: 'Mars'

const city = model.get('address.city')  //stream(Mars)
city()  // get value: 'Mars'
city('Earth')  // set value: 'Earth'

model.unwrap('address')  // {city: 'Earth'}
model.unset('address')   // unset model.address

model.unwrap() // {firstName: 'Hello', lastName: 'World'}

- Observe data changes

The root model has a change stream, you can get callback from every data changes.

// start observe model changes
const update = model.change.map(({value, type})=>{
    console.log('data mutated:', value.path, type, value.unwrap())
})

model.set('address.city', 'Mars')
// [console] data mutated: [ 'address', 'city' ] add Mars
model.get('address.city')('Earth')
// [console] data mutated: [ 'address', 'city' ] change Earth
model.unset('address.city')
// [console] data mutated: [ 'address', 'city' ] delete Earth

// stop observe model changes
update.end(true)

- Define data relations

You can define data relations using combine, scan etc., and unwrap will unwrap them automatically, you can nest any level of streams.

const firstName = model.get('firstName')
const lastName = model.get('lastName')
const fullName = flyd.combine(
  (a, b) => a() + ' ' + b(),
  [firstName, lastName]
)
model.set('fullName', fullName)
fullName.map(console.log)   // [console] Hello World
firstName('Green')          // [console] Green World

model.set('age', flyd.stream(flyd.stream(20)))
model.unwrap()
// {firstName:'Green', lastName:'World', fullName:'Green World', age:20}

- Use in React

const model = wrapData(flyd.stream)({user: {name: 'earth'}})

class App extends React.Component {
    constructor(props){
        super(props)
        const {model} = this.props
        
        this.update = model.change.map(({value, type})=>{
            this.forceUpdate()
        })
        
        this.onChange = e => {
            const {name, value} = e.target
            model.set(name, value)
        }
    }
  
    componentWillUnmount(){
        this.update.end(true)
    }
    
    render(){
        const {model} = this.props
        const userName = model.unwrap('user.name')
        return <div>
            <h3>Hello {userName}</h3>
            <input name='user.name' value={userName} onChange={this.onChange} />
        </div>
    }
}

ReactDOM.render(<App model={model} />, app)

You can play with the demo here

API

- wrapData = require('wrap-data')

The lib expose a default wrapData function to use

- wrapFactory = wrapData(stream)

the wrapFactory is used to turn data into wrapped_data.

A wrapped_data is just a stream, with some helper methods added to it, like get, set etc.

return: function(data) -> wrapped_data

var flyd = require('flyd')
var wrapFactory = wrapData(flyd.stream)

- root = wrapFactory(data: any)

the root is a wrapped_data, with all nested data wrapped.

return: wrapped_data for data

root.change is also a stream, you can map it to receive any data changes inside.

Any data inside root is a wrapped_data, and may be contained by {} or [] stream, keep the same structure as before.

Any wrapped_data have root and path propperties, get, set, ... helper functions.

var root = wrapFactory({x: {y: {z: 1}}})
root().x().y().z()  // 1
root.change.map(({value, type})=>{ console.log(value, type) })
root().x().y().z(2)

- wrapped_data.get(path: string|string[])

get nested wrapped data from path, path is array of string or dot(".") seperated string.

return: wrapped_data at path

var z = root.get('x.y.z')
// or
var z = root.get(['x','y','z'])
z() //2
z(10)

- wrapped_data.set(path?: string|string[], value?: any, descriptor?: object)

set nested wrapped data value from path, same rule as get method. The descriptor only applied when path not exists.

return: wrapped_data for value, at path

path can contain a.[3] alike string denote 3 is an array element of a.

value can be any data types, if path is omitted, set value into wrapped_data itself.

If value is a stream, then it's an atom data, which will not be wrapped inside.

descriptor is optional, same as 3rd argument of Object.defineProperty, this can e.g. create non-enumerable stream which will be hidden when unwrap.

If data not exist in path, all intermediate object will be created.

var z = root.set('x.a', 10)
z()  // 10

// same as: (only if x.a exits)
root.get('x.a').set(10)
root.get('x.a')(10)

var z = root.set('x.c', [], {enumerable: false})  // c is non-enumerable
Object.keys( z.get('x')() )  // ['a']

root.unwrap()  // {x: {y: {z: 1}}, a: 10}  // `c` is hidden!

root.set(`arr.[0]`, 10)
root.get('arr.0')()  // 10

root.unwrap()  // {x: {y: {z: 1}}, a: 10, arr:[10]}  // `arr` is array!

- wrapped_data.getset(path?: string|string[], function(prevValue, empty?: boolean)->newValue, descriptor: object)

like set, but value is from a function, it let you set value based on previous value, the descriptor only applied when empty is true.

return: wrapped_data for newValue, at path

var z = root.getset('x.a', val=>val+1)
z()  // 11

- wrapped_data.ensure(path: string|string[], value?: any, descriptor?: object)

like set, but only set when the path not exists, otherwise perform a get operation.

return: wrapped_data at path

var z = root.ensure('x.a', 5)
// x.a exists, so perform a get, `5` ignored
z()  // 11

var z = root.ensure('x.b', 5)
// x.b not exists, so perform a `set`
z()  // 5

- wrapped_data.unset(path: string|string[])

delete wrapped_data or value in path

*return: deleted data been unwrapped*

var z = root.unset('x.b')
z // 5

- wrapped_data.unwrap(path?: string|string[], config?: {json: true})

unwrap data and nested data while keep data structure, any level of wrapper on any data will be stripped.

If set config arg with {json: true}, then any circular referenced data will be set undefined, suitable for JSON.stringify.

return: unwrapped data

var z = root.unwrap()

z // {x: {y: {z: 11}}, a: [10]},   x.c is hidden

- wrapped_array.push(value: any)

push new value into wrapped data when it's array, all the inside will be wrapped.

return: newly pushed wrapped_data

var z = root.set('d', [])
z.push({v: 10})
z.get('d.0.v')()  // 10

- wrapped_array.pop()

pop and unwrap last element in wrapped array.

return: unwrapped data in last array element

var z = root.ensure('d', [])
z.get('d').pop()  // {v: 10}
0.3.18

5 years ago

0.6.10

5 years ago

0.6.9

6 years ago

0.6.8

6 years ago

0.6.7

6 years ago

0.6.6

6 years ago

0.6.4

6 years ago

0.6.3

6 years ago

0.6.2

6 years ago

0.6.1

6 years ago

0.6.0

6 years ago

0.5.8

6 years ago

0.5.7

6 years ago

0.5.6

6 years ago

0.5.5

6 years ago

0.5.4

6 years ago

0.5.3

6 years ago

0.5.2

6 years ago

0.5.1

6 years ago

0.5.0

6 years ago

0.4.7

6 years ago

0.4.6

6 years ago

0.4.5

6 years ago

0.4.4

6 years ago

0.4.2

6 years ago

0.4.1

6 years ago

0.3.16

6 years ago

0.3.15

6 years ago

0.3.14

6 years ago

0.3.13

6 years ago

0.3.12

6 years ago

0.3.11

6 years ago

0.3.10

6 years ago

0.3.9

6 years ago

0.3.8

6 years ago

0.3.7

6 years ago

0.3.5

6 years ago

0.3.4

6 years ago

0.3.3

6 years ago

0.3.2

6 years ago

0.3.1

6 years ago

0.3.0

6 years ago

0.2.5

6 years ago

0.2.4

6 years ago

0.2.1

6 years ago

0.2.0

6 years ago

0.0.1

6 years ago