Skip to content
This repository has been archived by the owner on Jun 21, 2020. It is now read-only.
/ vana Public archive

Observe your immutable state trees πŸŒ²πŸ‘€ (great with React)

License

Notifications You must be signed in to change notification settings

alloc/vana

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

npm Build status codecov Bundle size Code style: Prettier Donate

Observe your immutable state trees.

Vana adds observability to normal objects without mutating their public appearance. The only exception to that rule is that all objects are made immutable. This is Vana at its core.

Β 

Β 

Basic usage

import { o } from 'vana'

let state = o({
  /* Any data can go here */
})

The returned state is immutable and observable.

Before we make any changes, let's observe our state object:

import { tap } from 'vana'

// This callback is called synchronously whenever `state` changes.
tap(state, newState => {
  state = newState
})

The first kind of mutation passes a callback to the revise function. Any changes made within the callback are used to create an immutable copy of our state object.

import { revise } from 'vana'

const base = state
const copy = revise(state, draft => {
  /* Mutate our immutable state in here */
})

assert(base !== copy)
assert(copy === state) // Our `state` variable is updated.

The copy object is now observed by whoever was observing the base object, and revising the base object is now forbidden.

The second kind of mutation passes an object to the revise function. This is essentially the Object.assign of Vana.

const copy = revise(state, {
  /* Any data can go here */
})

Those are the basics. Here is a sandbox you can play with:

Β 

Install

yarn add vana

Β 

Integrations

Β 

Advanced usage

Here are some advanced use cases.

Cloning an observable object

Clone an observable object by passing it to the o function.

const base = o({ a: 1 })
const copy = o(base)

// The "copy" has its own observable identity.
assert(isObservable(copy))
assert(base !== copy)

// The "base" is still observable and revisable.
assert(isObservable(base))
tap(base, console.log)
revise(base, { a: 2 })

Β 

Controlled observables

Pass a callback to the o function to create a controlled observable.

// This pattern is great for memoized subscriptions.
const foo = o<number>(next => {
  next(0) // Always provide an initial value.
  let n = 0
  let id = setInterval(() => next(++n), 1000)
  return () => clearInterval(id)
})
// Log every new value.
foo.tap(console.log)

Β 

The latest function

Give any value and receive the latest revision (if possible), else return it as-is.

const o1 = o({ foo: true })
const o2 = revise(o1, { foo: false })
const o3 = revise(o2, { foo: true })

assert(o1 !== o3)
assert(latest(o1) === o3)

// Trash in, trash out
assert(latest(1) === 1)

Β 

The keepAlive function

Use the keepAlive function to make a "living" object out of an observable object. The returned object is a mirror of the observable's current value.

import { keepAlive } from 'vana'

let initialState = o({ a: 1 })
let state = keepAlive(initialState)

// The latest revision is always reflected
const lastState = revise(initialState, { a: 2 })
assert(state.a === 2)

// Can be passed to `latest`
assert(latest(state) === lastState)

// Be sure to call dispose when appropriate
state.dispose()

Β 

The watch function

This function lets you watch any property path in a state tree.

import { watch } from 'vana'

// An observable that updates when `obj.a.b.c` is changed
const prop = watch(obj).a.b.c

// Run a function when the property value is changed
const observer = prop.tap(console.log)

// Get the current property value
prop.get()

// Shallow properties can be observed more efficiently
const prop = watch(obj, 'a')

// You can use a selector function if you want
const prop = watch(obj, obj => obj.a.b.c)

Every property in the path is observed.

The property value is deeply observed if possible.

Β 

Array helpers

These functions are for working with immutable arrays. Each function works for any array regardless of mutability. They always return a modified copy of the given array (or the given array if no changes were made).

import { append, prepend, insert, concat, remove } from 'vana'

// Append one or more values to a copy of the given array.
append(arr, ...values)

// Prepend one or more values to a copy of the given array.
prepend(arr, ...values)

// Insert one or more values into a copy of the given array.
insert(arr, index, ...values)

// Merge one or more arrays into a copy of the given array.
concat(arr, ...values)

// Remove one or more values from a copy of the given array.
remove(arr, index, (count = 1))

Β 

Custom immutable classes

Any class can be made compatible with Vana's cloning logic.

import { immerable, o, revise, keepAlive, latest } from 'vana'

class Foo {
  readonly bar: number = 0

  // Let `Foo` instances be assumed readonly
  static [immerable] = true

  // Mutate yourself with `revise`
  setBar(bar: number) {
    return revise(this, { bar })
  }
}

let foo = o(new Foo())
assert(Object.isFrozen(foo))
assert(foo.bar === 0)

foo = foo.setBar(1)
assert(foo.bar === 1)

// works with `keepAlive`
const fooLatest = keepAlive(foo)

const foo3 = foo.setBar(2)
assert(foo3 !== foo)
assert(foo3.bar === 2)
assert(fooLatest.bar === 2)

// and with `latest`
foo = latest(foo)
assert(foo === foo3)

Β 

TODO: Provide more advanced use cases