diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f06235c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+node_modules
+dist
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..43c97e7
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1 @@
+package-lock=false
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8add7bd
--- /dev/null
+++ b/README.md
@@ -0,0 +1,149 @@
+<div align="center">
+
+# Reherit
+Reactive state management with prototypal inheritance
+
+</div>
+
+## About
+Reherit is a reactive state manager intended to be used with interactive
+interfaces made up of many seperate components. Each components state (called
+store) is inherited from it's parent using prototypal inheritance.
+
+Stores can be accessed and manipulated by any component in the tree. Whenever a
+store is changed, an update is issued and subscribers are notified.
+
+### Features
+- **small API:** you'll get by only learning three functions
+- **it's tiny:** just over 1kb, you'll barely know it's there
+- **no tooling:** no compilation required, works out of the box
+- **runs anywhere:** anywhere ES modules are supported, that is
+
+## Usage
+
+```javascript
+import { use, store, watch } from 'reherit'
+
+var dog = use(Dog, { name: 'Maja' }).resolve(console.log)
+
+console.log(dog)
+
+dog.pet()
+
+function Dog () {
+  var [name] = store('name')
+  var [mood, setMood] = store('mood', 'sad')
+
+  watch('mood', function (mood) {
+    console.log(`${name} is ${mood}`)
+  })
+
+  return { name, mood, pet: () => setMood('happy') }
+}
+```
+
+Running the above example will print the following to the console:
+
+```bash
+-> Maja is sad
+-> { name: 'Maja', mood: 'sad' }
+-> Maja is happy
+-> { name: 'Maja', mood: 'happy' }
+```
+
+### Installation
+Reherit is distributed as a ES module and can be installed with your favourite package manager, e.g.:
+
+```bash
+npm install reherit
+```
+
+It can also be imported from unpkg.com
+
+```javascript
+import { use, store, watch } from 'https://unpkg.com/reherit/dist/index.js'
+```
+
+## API
+While resolving a component tree, Reherit builds a "stack" of "layers" which
+reflects the inheritance of stores, one layer inherits from the one before it,
+and so on. However, there are only three functions you really need to learn to
+use Reherit.
+
+### `use(Function, [store, [...args]])`
+Creates a [`Layer`](#layer) which can be [`resolved`](#layerresolvefunction)
+into the rendered component. Takes a component function as it's first argument
+followed by an optional store and arguments. The store (if provided) will be
+assigned onto the inherited store. Arguments are forwarded to the component
+function. Returns [`Layer`](#layer).
+
+### `store([key, [initialValue]])`
+Read from component store, optionally providing an initial value if none is set
+already. Returns a touple (fancy word for array with two values) with the value
+and a function for updating the value.
+
+If the key being requested is not set to the current component store, it will
+walk up the prototype chain looking for it. If found on a parent store, that
+value will be returned and the update function will update that parent value.
+
+If the key is not found in the prototype chain, but an initial value is
+provided, the initial value will be set to the current component store and the
+update function will only update the current component.
+
+If no key is provided, the store object will be returned and the update function
+will assign properties directly to the store.
+
+```javascript
+var [state, setState] = store()
+var [value, setValue] = store('key', 'hello')
+
+setState({ key: 'hi' }) // <- These two have the same effect
+setValue('hi')          // <-
+```
+
+### `watch(key, [Function])`
+Listen for changes to the store. If key is a function the function will be
+attached as a listener for any changes to the store. Key can also be an array of
+keys to watch. Listeners are called *after* the component has rendered and on
+every update to the given key(s).
+
+The listener may return a function which will be called on the next update of
+the given key(s) *before* the component is rendered.
+
+```javascript
+watch('key', function (value) {
+  console.log('value is', value)
+  return function (value) {
+    console.log('value has been updated to', value)
+  }
+})
+```
+
+### Layer
+A layer is the container for a component, holding its store, listeners and
+children.
+
+#### `Layer#resolve(Function)`
+Call component function, calling any listeners and handling state in the
+process. Generators and promises returned/yielded from the component function
+are awaited for and resolved.
+
+If provided, the function passed to resolve will be called on subsequent
+asynchronous updates.
+
+#### `Layer#assign(store, args)`
+Update layer internal store and cached arguments without issuing an update.
+
+#### `Layer#update(key, value)`
+Update the store with given key/value pair. If no value is provided, the key is
+assigned onto the store.
+
+#### `Layer#subscribe(key, Function)`
+Subscribe to updates made to the layer store.
+
+#### `Layer#emit(key, value)`
+Call all subsribers registered for the given key. The value will be passed to
+the listeners.
+
+## License
+MIT
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..3c8adf3
--- /dev/null
+++ b/index.js
@@ -0,0 +1,334 @@
+const ROOT = Symbol('root')
+const UPDATE = Symbol('update')
+const CANCEL = Symbol('cancel')
+const INTERRUPT = Symbol('interrupt')
+
+/** Pool of used children per parent */
+const pool = new WeakMap()
+
+/** Use with {@link Layer#subscribe} to listen for any store changes */
+export const ANY = Symbol('any')
+
+/** Current stack of layers being resolved */
+export const stack = []
+
+/**
+ * Read from store. Returns value
+ * @param {any} [key] Store key name, omitting key yields store object
+ * @param {any} [initial] Initial value if not found in store
+ * @returns {Array} A touple of current value and an update function
+ */
+export function store (key, initial) {
+  console.assert(stack.length, 'store used outside render cycle')
+
+  var layer = stack[0]
+  if (!key) return [layer.store, (next) => layer.update(next)]
+  if (typeof key === 'string' && key[0] === '$') {
+    const _key = key.substring(1)
+    let value
+    if (hasOwnProperty(layer.store[_key])) {
+      value = layer.store[_key]
+    } else if (typeof initial !== 'undefined') {
+      value = layer.store[_key] = initial
+      layer.changes.add(_key)
+    }
+    return [value, (next) => layer.update(key, next)]
+  }
+  var value = layer.store[key]
+  if (typeof value === 'undefined' && typeof initial !== 'undefined') {
+    value = layer.store[key] = initial
+    layer.changes.add(key)
+  }
+  return [value, (next) => layer.update(key, next)]
+}
+
+/**
+ * Watch store for changes
+ * @param {any} key store key to watch or function to call on any change
+ * @param {Function} [fn] Function ot call when value of key change
+ * @returns {void}
+ */
+export function watch (key, fn) {
+  console.assert(stack.length, 'watch used outside render cycle')
+
+  if (Array.isArray(key)) {
+    return key.forEach((_key) => watch(_key, function () {
+      var layer = stack[0]
+      return fn(key.map((__key) => layer.store[__key]))
+    }))
+  } else if (typeof key === 'function') {
+    fn = key
+    key = ANY
+  }
+  if (key === ANY) {
+    stack.forEach((layer) => layer.subscribe(key, fn))
+  } else {
+    const layer = stack.find((layer) => hasOwnProperty(layer.store, key))
+    if (layer) {
+      layer.subscribe(key, fn)
+      if (layer.fresh) layer.changes.add(key)
+    }
+  }
+}
+
+/**
+ * Creates a layer for a component
+ * @param {Function} fn Component render Function
+ * @param {Object} store Store to use for component
+ * @param {...any} args Arguments to forward to component
+ * @returns {Layer}
+ */
+export function use (fn, store, ...args) {
+  var layer = stack[0]
+  if (store == null) store = {}
+  if (!layer) return new Layer(ROOT, fn, store, args)
+  if (!layer.children.has(fn)) layer.children.set(fn, new Set())
+  if (!pool.get(layer).has(fn)) pool.get(layer).set(fn, new Set())
+
+  var child
+  var children = layer.children.get(fn)
+  var candidates = pool.get(layer).get(fn)
+  var key = typeof store.key === 'undefined' ? children.size + 1 : store.key
+
+  for (const candidate of candidates) {
+    if (candidate.key === key) {
+      child = candidate
+      child.assign(store, args)
+      break
+    }
+  }
+
+  if (!child) {
+    store = Object.assign(Object.create(layer.store), store)
+    child = new Layer(key, fn, store, args)
+  }
+
+  children.add(child)
+  return child
+}
+
+export class Layer {
+  /**
+   * Create a layer
+   * @param {any} key Unique identifier for component
+   * @param {Function} fn Component render Function
+   * @param {Object} store Store to use for Component
+   * @param {Array} args Arguments to forward to component
+   */
+  constructor (key, fn, store, args) {
+    this.key = key
+    this.args = args
+    this.store = store
+    this.render = render
+    this.fresh = true
+    this.stack = [...stack]
+    this.changes = new Set()
+    this.children = new Map()
+    this.listeners = new Map()
+    pool.set(this, new WeakMap())
+
+    var queued = false
+    var running = false
+
+    /**
+     * Call component render function, recursively rerunning on every update
+     * @returns {any}
+     */
+    function render () {
+      if (running) {
+        queued = true
+      } else {
+        try {
+          queued = false
+          running = true
+          var res = unwind(fn(...this.args))
+        } catch (err) {
+          if (err === INTERRUPT) {
+            queued = true
+          } else if (err === CANCEL) {
+            var cancel = true
+          } else {
+            throw err
+          }
+        } finally {
+          running = false
+        }
+
+        if (cancel) return
+        if (queued) res = this.render()
+        return res
+      }
+    }
+  }
+
+  /**
+   * Make updates to layer internals on reuse
+   * @param {Object} store Properties with which to extend store
+   * @param {Array} args Arguments to forward to component
+   * @returns {void}
+   */
+  assign (store, args) {
+    this.args = args
+    Object.assign(this.store, store)
+  }
+
+  /**
+   * Resolve component
+   * @param {Function} callback Function to call on async updates
+   * @returns {any}
+   */
+  resolve (callback) {
+    if (!stack.includes(this)) {
+      stack.unshift(this)
+    }
+
+    if (typeof callback === 'function') {
+      this.subscribe(UPDATE, function onupdate (res) {
+        callback(res)
+        return onupdate
+      })
+    }
+
+    try {
+      for (const key of this.changes) {
+        this.emit(key, this.store[key])
+      }
+
+      const res = this.render()
+
+      for (const key of this.changes) {
+        this.emit(key, this.store[key])
+      }
+
+      return res
+    } finally {
+      const pooled = pool.get(this)
+      for (const [fn, children] of this.children) {
+        pooled.set(fn, new Set(children))
+      }
+      this.fresh = false
+      this.children.clear()
+      this.changes.clear()
+      stack.shift()
+    }
+  }
+
+  /**
+   * Update store, issuing an async render
+   * @param {any} key Key of the value to update or a new store to assign
+   * @param {any} [value] New value to assign for key
+   * @returns {void}
+   */
+  update (key, value) {
+    if (typeof value === 'undefined') {
+      Object.assign(this.store, key)
+      key = ANY
+    } else if (typeof key === 'string' && key[0] === '$') {
+      key = key.substring(1)
+      this.store[key] = value
+    } else {
+      if (hasOwnProperty(this.store, key)) {
+        this.store[key] = value
+      } else {
+        const parent = this.stack.find(function (parent) {
+          return hasOwnProperty(parent.store, key)
+        })
+        if (!parent) {
+          this.store[key] = value
+        } else {
+          parent.changes.add(key)
+          parent.emit(UPDATE, parent.resolve(), { any: false })
+          if (stack.includes(this)) throw CANCEL
+          return
+        }
+      }
+    }
+
+    this.changes.add(key)
+    if (stack.includes(this)) throw INTERRUPT
+    this.emit(UPDATE, this.resolve(), { any: false })
+  }
+
+  /**
+   * Subscribe to changes made to store. The listener function may return
+   * a callback function which will be called on next change, before render.
+   * @param {any} key Key to subscribe to
+   * @param {Function} fn Function to call on change
+   * @returns {void}
+   */
+  subscribe (key, fn) {
+    if (!this.listeners.has(fn)) {
+      this.listeners.set(fn, new Set())
+    }
+    this.listeners.get(fn).add(key)
+  }
+
+  /**
+   * Emit change
+   * @param {any} key Emit an event to subscribed listeners
+   * @param {any} value New value for subscribed key
+   * @param {Object} [opts] Configure behavior
+   * @param {boolean} [opts.any=true] Trigger listerns for {@link ANY}
+   */
+  emit (key, value, opts = {}) {
+    var { any = true } = opts
+    var listeners = []
+    for (const [fn, keys] of this.listeners.entries()) {
+      if (!keys.has(key) && (!any || !keys.has(ANY))) continue
+      const cleanup = keys.has(key) && key !== ANY ? fn(value) : fn()
+      this.listeners.delete(fn)
+      if (typeof cleanup === 'function') listeners.push(cleanup)
+    }
+
+    for (const listener of listeners) {
+      this.subscribe(key, listener)
+    }
+  }
+}
+
+/**
+ * Resolve nested generator and promises
+ * @param {any} obj
+ * @param {any} [value]
+ * @returns {any}
+ */
+function unwind (obj, value) {
+  if (isGenerator(obj)) {
+    const res = obj.next(value)
+    if (res.done) return res.value
+    if (isPromise(res.value)) {
+      return res.value.then(unwind).then((val) => unwind(obj, val))
+    }
+    return unwind(obj, res.value)
+  } else if (isPromise(obj)) {
+    return obj.then(unwind)
+  }
+  return obj
+}
+
+/**
+ * Determin if object is promise
+ * @param {any} obj
+ * @returns {boolean}
+ */
+function isPromise (obj) {
+  return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function'
+}
+
+/**
+ * Determine if object is generator
+ * @param {any} obj
+ * @return {boolean}
+ */
+function isGenerator (obj) {
+  return obj && typeof obj.next === 'function' && typeof obj.throw === 'function'
+}
+
+/**
+ * Check if object has key set on self
+ * @param {Object} obj The object to check
+ * @param {any} key The key to look for
+ */
+function hasOwnProperty (obj, key) {
+  return Object.prototype.hasOwnProperty.call(obj, key)
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..cedfaa9
--- /dev/null
+++ b/package.json
@@ -0,0 +1,27 @@
+{
+  "name": "reherit",
+  "version": "1.0.0",
+  "description": "Reactive state management with prototypal inheritance",
+  "main": "index.js",
+  "type": "module",
+  "files": [
+    "index.js"
+  ],
+  "scripts": {
+    "test": "node test.js && standard",
+    "build": "rollup -p rollup-plugin-strip -p rollup-plugin-terser -m -o dist/index.js index.js",
+    "postbuild": "cat dist/index.js | gzip --best | wc -c | pretty-bytes",
+    "prepublishOnly": "npm run build"
+  },
+  "author": "Carl Törnqvist <carl@tornqv.ist>",
+  "repository": "github:tornqvist/reherit",
+  "license": "MIT",
+  "devDependencies": {
+    "pretty-bytes-cli": "^2.0.0",
+    "rollup": "^2.23.1",
+    "rollup-plugin-strip": "^1.2.2",
+    "rollup-plugin-terser": "^7.0.0",
+    "standard": "^14.3.4",
+    "tape": "^5.0.1"
+  }
+}
diff --git a/test.js b/test.js
new file mode 100644
index 0000000..7cbbdb6
--- /dev/null
+++ b/test.js
@@ -0,0 +1,248 @@
+import { createRequire } from 'module'
+import { use, store, watch, Layer, ANY, stack } from './index.js'
+
+const require = createRequire(import.meta.url)
+const test = require('tape')
+
+test('API', function (t) {
+  t.plan(16)
+
+  t.equal(typeof use, 'function', 'exports use function')
+  t.equal(typeof store, 'function', 'exports store function')
+  t.equal(typeof watch, 'function', 'exports watch function')
+  t.equal(typeof Layer, 'function', 'exports Layer class')
+  t.equal(typeof ANY, 'symbol', 'exports ANY symbol')
+  t.ok(Array.isArray(stack), 'exports layer stack')
+
+  var value = 1
+  var layer = use(function (arg) {
+    t.equal(arg, value, 'value was forwarded')
+    if (value === 1) {
+      t.equal(stack.length, 1, 'stack is populated during resolve')
+      t.equal(stack[0], layer, 'stack has layer currently being resolved')
+    } else if (value === 2) {
+      t.equal(layer.store.one, 1, 'store was assigned')
+      t.equal(layer.store.two, 2, 'store was updated')
+    } else {
+      t.fail()
+    }
+    return arg
+  }, null, value)
+
+  t.ok(layer instanceof Layer, 'exposes layer')
+
+  var res = layer.resolve()
+  t.equal(res, value, 'returns result')
+
+  layer.assign({ one: 1 }, [++value])
+  layer.update('two', 2)
+  layer.subscribe('three', function (value) {
+    t.equal(value, 3, 'value forwarded to keyed listener')
+  })
+  layer.subscribe(ANY, function (value) {
+    t.equal(typeof value, 'undefined', 'value not forwarded to unkeyed listener')
+  })
+  layer.emit('three', 3)
+  layer.emit('four', 4, { any: false })
+})
+
+test('async components', async function (t) {
+  t.plan(1)
+
+  var res = await Promise.all([
+    use(async () => 1).resolve(),
+    use(function * () {
+      var res = yield 2
+      return res
+    }).resolve(),
+    use(function * () {
+      var res = yield Promise.resolve(3)
+      return res
+    }).resolve()
+  ])
+
+  t.deepEqual(res, [1, 2, 3], 'all async components resolved')
+})
+
+test('store default value', function (t) {
+  t.plan(1)
+  var layer = use(Main)
+  layer.resolve()
+
+  function Main () {
+    var [value] = store('value', 'fallback')
+    t.equal(value, 'fallback', 'uses fallback value')
+  }
+})
+
+test('providing a store', function (t) {
+  t.plan(1)
+  var layer = use(Main, { value: 'hi' })
+  layer.resolve()
+
+  function Main () {
+    var [value] = store('value')
+    t.equal(value, 'hi', 'provided store was used')
+  }
+})
+
+test('manipulating stores', function (t) {
+  t.plan(3)
+
+  var step = 0
+  var steps = [1, 2, 3, 4]
+  var layer = use(Main)
+  var res = layer.resolve()
+
+  t.equal(res, steps[2], 'layer is resolved synchronously')
+
+  function Main () {
+    var [value, setValue] = store('value', steps[0])
+    var [, setState] = store()
+    if (step === 0) {
+      setState({ value: steps[++step] })
+      t.fail('should interrupt on update mid-render')
+    } else if (step === 1) {
+      t.equal(value, steps[1], 'can set state')
+      setValue(steps[++step])
+      t.fail('should interrupt on update mid-render')
+    } else if (step === 2) {
+      t.equal(value, steps[2], 'can set property by key')
+    }
+    return value
+  }
+})
+
+test('top level store is mutated', function (t) {
+  t.plan(1)
+  var state = {}
+
+  use(Main, state).resolve()
+  t.equal(state.value, 'hi', 'state was mutated')
+
+  function Main () {
+    store('value', 'hi')
+  }
+})
+
+test('callback', function (t) {
+  t.plan(1)
+
+  var layer = use(Main)
+
+  layer.resolve(function (res) {
+    t.equal(res, 2, 'resolve callback called on async update')
+  })
+
+  function Main () {
+    var [value, setValue] = store('value', 1)
+    if (value === 1) setTimeout(() => setValue(2), 100)
+    return value
+  }
+})
+
+test('watching store', function (t) {
+  t.plan(9)
+  var expected = 0
+  var layer = use(Main)
+  layer.resolve()
+
+  function Main () {
+    var [value, setValue] = store('value', 0)
+    watch('value', function (_value) {
+      t.equal(_value, expected, `keyed watcher called with new value after ${value ? 'update' : 'render'}`)
+      return function () {
+        t.pass('keyed cleanup called before update')
+      }
+    })
+    watch(['value'], function (values) {
+      t.deepEqual(values, [expected], `array of keys watcher called with new values after ${value ? 'update' : 'render'}`)
+      return function () {
+        t.pass('array of keys cleanup called before update')
+      }
+    })
+    watch(function () {
+      t.equal(arguments.length, 0, `no value passed to unkeyed watcher after ${value ? 'update' : 'render'}`)
+      return function () {
+        t.pass('unkeyed cleanup called before update')
+      }
+    })
+    if (value === 0) {
+      setTimeout(function () {
+        setValue(++expected)
+      }, 100)
+    }
+  }
+})
+
+test('children', function (t) {
+  t.plan(8)
+  var children = Array(3).fill().map((_, i) => i)
+  var layer = use(Main, { root: 0 })
+  var tree = layer.resolve(function (tree) {
+    t.deepEqual(tree, {
+      root: 1,
+      children: [{
+        key: 2,
+        index: 0,
+        local: 2
+      }, {
+        key: 1,
+        index: 1,
+        local: 1
+      }, {
+        key: 0,
+        index: 2,
+        local: 0
+      }]
+    }, 'reversed tree maintained stores')
+  })
+
+  t.deepEqual(tree, {
+    root: 0,
+    children: [{
+      key: 0,
+      index: 0,
+      local: 0
+    }, {
+      key: 1,
+      index: 1,
+      local: 1
+    }, {
+      key: 2,
+      index: 2,
+      local: 2
+    }]
+  }, 'tree match')
+
+  function Main () {
+    var [root, setRoot] = store('root')
+
+    if (root === 0) {
+      setTimeout(function () {
+        children.reverse()
+        setRoot(1)
+      }, 100)
+    }
+
+    return {
+      root,
+      children: children.map(function (key, i) {
+        const layer = use(child, { key }, i)
+        return layer.resolve(function () {
+          t.fail('childrens callback should not be called')
+        })
+      })
+    }
+  }
+
+  function child (index) {
+    var [key] = store('key')
+    var [local] = store('local', index)
+    watch('root', function (value) {
+      if (!value) t.pass('child watcher for parent store called after render')
+      else t.pass('child watcher for parent store called after update')
+    })
+    return { index, key, local }
+  }
+})