0.9.6 • Published 5 years ago

@nichoth/observ v0.9.6

Weekly downloads
9
License
ISC
Repository
github
Last release
5 years ago

observ

Several building blocks for creating observable application state. This builds off of observ and observ-struct, and exposes some additional tools. All state changes should be 'immutable', meaning operations emit new objects instead of the same instance.

install

npm install @nichoth/observ

modules

  • observ -- see observ
  • observ/computed
  • observ-struct -- see observ-struct
  • sorted-collection
  • Model -- compose IO and state changes
  • requests -- model request/reply state (this is used by model.js)

examples

// require everything at once
var { Model, struct, observ } = require('@nichoth/observ')
// import specific files
var SortedCollection = require('@nichoth/observ/sorted-collection')

sorted collection

var test = require('tape')
var createSortedCollection = require('@nichoth/observ/sorted-collection')

var MyCollection = createSortedCollection({
    sortBy: 'hello',
    indexBy: 'id'
})

// return initial state, an instance of `observ-struct`
var state = MyCollection()

test('create state', function (t) {
    t.deepEqual(state(), {
        order: 'asc',
        sortBy: 'hello',
        sorted: [],
        indexed: {}
    }, 'should return initial state')
    t.end()
})

// here the name of the sort function is used when serializing state
test('pass in a function to sort things', function (t) {
    t.plan(2)
    var Foos = createSortedCollection({
        sortBy: function hello (foo) {
            return foo.hello.toLowerCase()
        },
        indexBy: 'id'
    })

    var state = Foos()
    Foos.get(state, [
        { hello: 'b', id: 2 },
        { hello: 'a', id: 1 },
        { hello: 'C', id: 3 }
    ])

    var _state = state()
    t.equal(_state.sortBy, 'hello',
        'should use function name as sortBy state')

    t.deepEqual(_state.sorted, [
        { hello: 'a', id: 1 },
        { hello: 'b', id: 2 },
        { hello: 'C', id: 3 }
    ], 'should use the predicate to sort things')
})

// change how items are sorted
test('.sortBy', function (t) {
    t.plan(2)

    var Foos = createSortedCollection({
        sortBy: function hello (foo) {
            return foo.hello.toLowerCase()
        },
        indexBy: 'id'
    })

    var state = Foos()
    Foos.get(state, [
        { hello: 'a', foo: 'e', id: 1 },
        { hello: 'b', foo: 'd', id: 2 },
        { hello: 'C', foo: 'f', id: 3 }
    ])

    Foos.sortBy(state, 'foo')
    t.deepEqual(state.sorted(), [
        { hello: 'b', foo: 'd', id: 2 },
        { hello: 'a', foo: 'e', id: 1 },
        { hello: 'C', foo: 'f', id: 3 }
    ], 'should re-sort collection')

    t.equal(state.sortBy(), 'foo')
})

// change ascending or descending
test('.orderBy', function (t) {
    t.plan(2)

    var Foos = createSortedCollection({
        sortBy: function hello (foo) {
            return foo.hello.toLowerCase()
        },
        indexBy: 'id'
    })

    var state = Foos()
    Foos.get(state, [
        { hello: 'b', id: 2 },
        { hello: 'a', id: 1 },
        { hello: 'C', id: 3 }
    ])

    Foos.orderBy(state, 'desc')
    t.deepEqual([
        { hello: 'C', id: 3 },
        { hello: 'b', id: 2 },
        { hello: 'a', id: 1 }
    ], state.sorted(), 'should reverse the order')
    t.equal(state.order(), 'desc')
})

// set the collection of items. The method name `get` is confusing. It's an
// artifact from the api call to a /get endpoint
test('.get', function (t) {
    MyCollection.get(state, [
        { hello: 'b', id: 2 },
        { hello: 'a', id: 1 }
    ])
    t.deepEqual(state(), {
        order: 'asc',
        sortBy: 'hello',
        sorted: [
            { hello: 'a', id: 1 },
            { hello: 'b', id: 2 }
        ],
        indexed: {
            '1': { hello: 'a', id: 1 },
            '2': { hello: 'b', id: 2 }
        }
    }, 'should set state')

    t.end()
})

// pass in the `order` option
test('sort descending', function (t) {
    var MyCollection = createSortedCollection({
        order: 'desc',
        sortBy: 'hello',
        indexBy: 'id'
    })
    var state = MyCollection()

    MyCollection.get(state, [
        { hello: 'b', id: 2 },
        { hello: 'a', id: 1 }
    ])

    t.deepEqual(state(), {
        order: 'desc',
        sortBy: 'hello',
        sorted: [
            { hello: 'b', id: 2 },
            { hello: 'a', id: 1 }
        ],
        indexed: {
            '1': { hello: 'a', id: 1 },
            '2': { hello: 'b', id: 2 }
        }
    }, 'should set state sorted descending')

    t.end()
})

// update an existing item
test('.edit', function (t) {
    MyCollection.edit(state, { id: 1, hello: 'world' })

    t.deepEqual(state(), {
        order: 'asc',
        sortBy: 'hello',
        sorted: [
            { hello: 'b', id: 2 },
            { hello: 'world', id: 1 }
        ],
        indexed: {
            '1': { hello: 'world', id: 1 },
            '2': { hello: 'b', id: 2 }
        }
    }, 'should re-sort if you edit the field it\'s sorted by')

    t.end()
})

// add an element in the correct sort position
test('.add', function (t) {
    MyCollection.add(state, { id: 3, hello: 'foo' })

    t.deepEqual(state(), {
        order: 'asc',
        sortBy: 'hello',
        sorted: [
            { hello: 'b', id: 2 },
            { hello: 'foo', id: 3 },
            { hello: 'world', id: 1 }
        ],
        indexed: {
            '1': { hello: 'world', id: 1 },
            '2': { hello: 'b', id: 2 },
            '3': { hello: 'foo', id: 3 }
        }
    }, 'should add items in the right sort position')

    t.end()
})

test('.delete', function (t) {
    MyCollection.delete(state, { id: 3 })

    t.deepEqual(state(), {
        order: 'asc',
        sortBy: 'hello',
        sorted: [
            { hello: 'b', id: 2 },
            { hello: 'world', id: 1 }
        ],
        indexed: {
            '1': { hello: 'world', id: 1 },
            '2': { hello: 'b', id: 2 }
        }
    }, 'should delete items by their index field')

    t.end()
})

model

Compose async functions with state changes. This is a convenient way to model async function call state, so that the application code only deals with updating the domain state on successful responses. IO state is modelled with ./requests.

var Model = require('@nichoth/observ/model')

var model = Model({
    state: {
        hello: observ('world')
    },
    io: {
        foo: function (data, cb) {
            // echo
            process.nextTick(() => cb(null, data))
        }
    },
    update: {
        // every key in `io` must have a corresponding key under `update`.
        // these functions are called on any non-error response.
        // however, you can also include synchronous functions here
        foo: function (state, res) {
            state.hello.set(res)
        }
    }
})

var state = Model.getState(model)

// the given state is extended with two keys, `hasFetched` and `requests`
assert.deepEqual(state(), {
    hello: 'world',
    hasFetched: false,
    requests: {
        isResolving: false,
        resolving: {},
        error: null
    }
})

model.foo('ok', function (err, res) {
    // this is called after the request is done and the state
    // has been updated
    assert.equal(res, 'ok')
    assert.equal(state().hello, 'ok')
})
0.9.6

5 years ago

0.9.5

5 years ago

0.9.4

5 years ago

0.9.3

5 years ago

0.9.2

5 years ago

0.9.1

5 years ago

0.9.0

5 years ago

0.8.0

5 years ago

0.7.0

5 years ago

0.6.0

5 years ago

0.5.0

6 years ago

0.4.5

6 years ago

0.4.4

6 years ago

0.4.3

6 years ago

0.4.2

6 years ago

0.4.1

6 years ago

0.4.0

6 years ago

0.3.7

6 years ago

0.3.6

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.1

6 years ago

0.2.0

6 years ago

0.1.0

6 years ago

0.0.6

6 years ago

0.0.5

6 years ago

0.0.4

6 years ago

0.0.3

6 years ago

0.0.2

6 years ago

0.0.1

6 years ago

0.0.0

6 years ago