Comparing version 1.1.0 to 1.2.0
@@ -7,2 +7,4 @@ 'use strict'; | ||
var _pluginHelpers = require('./pluginHelpers'); | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
@@ -92,7 +94,7 @@ | ||
Atmover.prototype.construct = function construct(p, args) { | ||
return this._plugin.createInstanceAtom(_fastCreate.fastCreateObject, this._protoCache.get(p), args || []); | ||
return this._plugin.createInstanceAtom(new _pluginHelpers.InstanceFactory(args, this._protoCache.get(p), _fastCreate.fastCreateObject)); | ||
}; | ||
Atmover.prototype.factory = function factory(p, args) { | ||
return this._plugin.createInstanceAtom(_fastCreate.fastCall, this._protoCache.get(p), args || []); | ||
return this._plugin.createInstanceAtom(new _pluginHelpers.InstanceFactory(args, this._protoCache.get(p), _fastCreate.fastCall)); | ||
}; | ||
@@ -99,0 +101,0 @@ |
'use strict'; | ||
exports.__esModule = true; | ||
var metaKey = exports.metaKey = Symbol('aovr:atom'); | ||
var onUpdate = exports.onUpdate = Symbol('aovr:onUpdate'); | ||
var metaKey = exports.metaKey = Symbol('ao:atom'); | ||
var onUpdate = exports.onUpdate = Symbol('ao:upd'); | ||
//# sourceMappingURL=interfaces.js.map |
'use strict'; | ||
exports.__esModule = true; | ||
exports.AtomError = undefined; | ||
exports.InstanceFactory = exports.AtomError = undefined; | ||
exports.createAttachMeta = createAttachMeta; | ||
exports.createInstanceFactory = createInstanceFactory; | ||
exports.invokeDerivable = invokeDerivable; | ||
exports.createListener = createListener; | ||
@@ -16,6 +13,6 @@ var _interfaces = require('./interfaces'); | ||
var oldValue = null; | ||
return function attachMeta(value, deps) { | ||
return function attachMeta(value) { | ||
value[_interfaces.metaKey] = selfAtom; // eslint-disable-line | ||
if (oldValue && oldValue[_interfaces.onUpdate]) { | ||
oldValue[_interfaces.onUpdate].call(oldValue, value, deps); | ||
oldValue[_interfaces.onUpdate].call(oldValue, value); | ||
} | ||
@@ -27,13 +24,65 @@ oldValue = value; | ||
function createInstanceFactory(create, args, protoAtom, attachMeta) { | ||
return function instanceFactory() { | ||
var deps = []; | ||
function normalizeArgs(args) { | ||
var result = []; | ||
for (var i = 0, l = args.length; i < l; i++) { | ||
var _value = args[i]; | ||
if (!_value.get) { | ||
var values = []; | ||
for (var key in _value) { | ||
// eslint-disable-line | ||
values.push({ key: key, value: _value[key] }); | ||
} | ||
result.push({ | ||
id: 2, | ||
values: values | ||
}); | ||
} else { | ||
result.push({ | ||
id: 1, | ||
value: _value | ||
}); | ||
} | ||
} | ||
return result; | ||
} | ||
var ArgsAtomGetter = function () { | ||
function ArgsAtomGetter(rawArgs) { | ||
_classCallCheck(this, ArgsAtomGetter); | ||
if (rawArgs && rawArgs.id === 3) { | ||
this._args = rawArgs.args; | ||
} else { | ||
this._args = rawArgs ? normalizeArgs(rawArgs) : []; | ||
} | ||
this._deps = new Array(this._args.length); | ||
} | ||
ArgsAtomGetter.prototype.get = function get() { | ||
var args = this._args; | ||
var deps = this._deps; | ||
for (var i = 0, l = args.length; i < l; i++) { | ||
deps[i] = args[i].get(); | ||
var arg = args[i]; | ||
if (arg.id === 1) { | ||
deps[i] = arg.value.get(); | ||
} else { | ||
var values = arg.values; | ||
var target = deps[i]; | ||
if (!target) { | ||
target = deps[i] = {}; | ||
} | ||
for (var j = 0, k = values.length; j < k; j++) { | ||
var rec = values[j]; | ||
target[rec.key] = rec.value.get(); | ||
} | ||
} | ||
} | ||
return attachMeta(create(protoAtom.get(), deps), deps); | ||
return deps; | ||
}; | ||
} | ||
return ArgsAtomGetter; | ||
}(); | ||
var AtomError = exports.AtomError = function AtomError(error) { | ||
@@ -45,23 +94,58 @@ _classCallCheck(this, AtomError); | ||
function invokeDerivable(fn) { | ||
try { | ||
return fn(); | ||
} catch (error) { | ||
/* eslint-disable no-console */ | ||
console.error(error); | ||
return new AtomError(error); | ||
var InstanceFactory = exports.InstanceFactory = function () { | ||
function InstanceFactory(args, protoAtom, create) { | ||
var _this = this; | ||
_classCallCheck(this, InstanceFactory); | ||
this._isSafe = false; | ||
this.get = function () { | ||
return _this._isSafe ? _this._getSafe() : _this._get(); | ||
}; | ||
this._args = new ArgsAtomGetter(args); | ||
this._protoAtom = protoAtom; | ||
this._create = create; | ||
} | ||
} | ||
function createListener(fn, err) { | ||
return function listener(v) { | ||
if (v instanceof AtomError) { | ||
if (err) { | ||
err(v.error); | ||
} | ||
} else { | ||
fn(v); | ||
InstanceFactory.prototype.setAtom = function setAtom(atom) { | ||
this._attachMeta = createAttachMeta(atom); | ||
return this; | ||
}; | ||
InstanceFactory.prototype.setSafeMode = function setSafeMode(isSafe) { | ||
this._isSafe = isSafe; | ||
return this; | ||
}; | ||
InstanceFactory.prototype._get = function _get() { | ||
return this._attachMeta(this._create(this._protoAtom.get(), this._args.get())); | ||
}; | ||
InstanceFactory.prototype._getSafe = function _getSafe() { | ||
try { | ||
return this._get(); | ||
} catch (error) { | ||
/* eslint-disable no-console */ | ||
console.error(error); | ||
return new AtomError(error); | ||
} | ||
}; | ||
} | ||
InstanceFactory.prototype.createListener = function createListener(fn, err) { | ||
return function listener(v) { | ||
if (v instanceof AtomError) { | ||
if (err) { | ||
err(v.error); | ||
} | ||
} else { | ||
fn(v); | ||
} | ||
}; | ||
}; | ||
return InstanceFactory; | ||
}(); | ||
//# sourceMappingURL=pluginHelpers.js.map |
@@ -32,16 +32,2 @@ 'use strict'; | ||
CellxValueAtom.prototype.subscribe = function subscribe(fn) { | ||
var _this = this; | ||
function changeListener(event) { | ||
return fn(event.value.v); | ||
} | ||
this._value('addChangeListener', changeListener); | ||
var unsubscribe = function unsubscribe() { | ||
_this._value('removeChangeListener', changeListener); | ||
}; | ||
return unsubscribe; | ||
}; | ||
return CellxValueAtom; | ||
@@ -51,10 +37,8 @@ }(); | ||
var CellxInstanceAtom = function () { | ||
function CellxInstanceAtom(cellx, create, proto, args) { | ||
function CellxInstanceAtom(cellx, factory) { | ||
_classCallCheck(this, CellxInstanceAtom); | ||
this._isHandleErrors = false; | ||
var createInstance = (0, _pluginHelpers.createInstanceFactory)(create, args, proto, (0, _pluginHelpers.createAttachMeta)(this)); | ||
this._value = cellx(createInstance); | ||
factory.setAtom(this); | ||
this._factory = factory; | ||
this._value = cellx(factory.get); | ||
} // CellxAtom<BoxedValue<V>> | ||
@@ -73,3 +57,2 @@ | ||
var value = this._value; | ||
this._isHandleErrors = true; | ||
@@ -83,3 +66,3 @@ function listener(error, evt) { | ||
} | ||
this._value('subscribe', listener); | ||
value('subscribe', listener); | ||
@@ -99,10 +82,8 @@ return function unsubscribe() { | ||
this._cellx = cellx; | ||
this.transact = function _transact(f) { | ||
f(); | ||
cellx.Cell.forceRelease(); | ||
}; | ||
cellx.configure({ asynchronous: false }); | ||
this.transact = cellx.transact; | ||
} | ||
CellxPlugin.prototype.createInstanceAtom = function createInstanceAtom(create, protoAtom, argsAtom) { | ||
return new CellxInstanceAtom(this._cellx, create, protoAtom, argsAtom); | ||
CellxPlugin.prototype.createInstanceAtom = function createInstanceAtom(factory) { | ||
return new CellxInstanceAtom(this._cellx, factory); | ||
}; | ||
@@ -109,0 +90,0 @@ |
@@ -13,3 +13,2 @@ 'use strict'; | ||
this._createAtom = derivable.atom; | ||
this._attachMeta = (0, _pluginHelpers.createAttachMeta)(this); | ||
@@ -27,14 +26,2 @@ this._value = derivable.atom(this._attachMeta(value)); | ||
DerivableValueAtom.prototype.subscribe = function subscribe(fn) { | ||
var until = this._createAtom(false); | ||
this._value.react(fn, { | ||
skipFirst: true, | ||
until: until | ||
}); | ||
return function unsubscribe() { | ||
until.set(true); | ||
}; | ||
}; | ||
return DerivableValueAtom; | ||
@@ -44,18 +31,10 @@ }(); | ||
var DerivableInstanceAtom = function () { | ||
function DerivableInstanceAtom(derivable, create, proto, args) { | ||
var _this = this; | ||
function DerivableInstanceAtom(derivable, factory) { | ||
_classCallCheck(this, DerivableInstanceAtom); | ||
this._isHandleErrors = false; | ||
this._createAtom = derivable.atom; | ||
factory.setAtom(this); | ||
var createInstance = (0, _pluginHelpers.createInstanceFactory)(create, args, proto, (0, _pluginHelpers.createAttachMeta)(this)); | ||
var createHandledInstance = function createHandledInstance() { | ||
return _this._isHandleErrors ? (0, _pluginHelpers.invokeDerivable)(createInstance) : createInstance(); | ||
}; | ||
this._value = derivable.derivation(createHandledInstance); | ||
this._value = derivable.derivation(factory.get); | ||
this._factory = factory; | ||
} | ||
@@ -77,4 +56,5 @@ | ||
var until = this._createAtom(false); | ||
this._isHandleErrors = true; | ||
this._value.react((0, _pluginHelpers.createListener)(fn, err), { | ||
var f = this._factory; | ||
f.setSafeMode(true); | ||
this._value.react(f.createListener(fn, err), { | ||
skipFirst: true, | ||
@@ -100,4 +80,4 @@ until: until | ||
DerivablePlugin.prototype.createInstanceAtom = function createInstanceAtom(create, protoAtom, argsAtom) { | ||
return new DerivableInstanceAtom(this._derivable, create, protoAtom, argsAtom); | ||
DerivablePlugin.prototype.createInstanceAtom = function createInstanceAtom(factory) { | ||
return new DerivableInstanceAtom(this._derivable, factory); | ||
}; | ||
@@ -104,0 +84,0 @@ |
@@ -31,9 +31,2 @@ 'use strict'; | ||
MobxValueAtom.prototype.subscribe = function subscribe(fn) { | ||
var unboxValue = function unboxValue(v) { | ||
return fn(v.v); | ||
}; | ||
return this._value.observe(unboxValue); | ||
}; | ||
return MobxValueAtom; | ||
@@ -43,16 +36,8 @@ }(); | ||
var MobxInstanceAtom = function () { | ||
function MobxInstanceAtom(mobx, create, proto, args) { | ||
var _this = this; | ||
function MobxInstanceAtom(mobx, factory) { | ||
_classCallCheck(this, MobxInstanceAtom); | ||
this._isHandleErrors = false; | ||
var createInstance = (0, _pluginHelpers.createInstanceFactory)(create, args, proto, (0, _pluginHelpers.createAttachMeta)(this)); | ||
var createHandledInstance = function createHandledInstance() { | ||
return _this._isHandleErrors ? (0, _pluginHelpers.invokeDerivable)(createInstance) : createInstance(); | ||
}; | ||
this._value = mobx.computed(createHandledInstance); | ||
this._factory = factory; | ||
factory.setAtom(this); | ||
this._value = mobx.computed(factory.get); | ||
} | ||
@@ -73,4 +58,3 @@ | ||
MobxInstanceAtom.prototype.subscribe = function subscribe(fn, err) { | ||
this._isHandleErrors = true; | ||
return this._value.observe((0, _pluginHelpers.createListener)(fn, err)); | ||
return this._value.observe(this._factory.setSafeMode(true).createListener(fn, err)); | ||
}; | ||
@@ -89,4 +73,4 @@ | ||
MobxPlugin.prototype.createInstanceAtom = function createInstanceAtom(create, protoAtom, argsAtom) { | ||
return new MobxInstanceAtom(this._mobx, create, protoAtom, argsAtom); | ||
MobxPlugin.prototype.createInstanceAtom = function createInstanceAtom(factory) { | ||
return new MobxInstanceAtom(this._mobx, factory); | ||
}; | ||
@@ -93,0 +77,0 @@ |
{ | ||
"name": "atmover", | ||
"version": "1.1.0", | ||
"version": "1.2.0", | ||
"description": "Abstraction layer on top of mobx, cellx, derivable with hot reload support", | ||
@@ -65,3 +65,3 @@ "publishConfig": { | ||
"babel-preset-stage-0": "^6.16.0", | ||
"cellx": "^1.6.56", | ||
"cellx": "^1.6.60", | ||
"derivable": "^0.12.1", | ||
@@ -73,3 +73,3 @@ "eslint": "^3.9.1", | ||
"husky": "^0.11.9", | ||
"mobx": "^2.6.1", | ||
"mobx": "^2.6.2", | ||
"mocha": "^3.1.2", | ||
@@ -76,0 +76,0 @@ "power-assert": "^1.4.1", |
259
README.md
# atmover | ||
Atom overlay: abstraction layer on top of [mobx][mobx], [cellx][cellx], [derivable][derivable] with hot reload support. | ||
On current moment supported only objects, get, set, subscribre, transact, replace prototype methods and onUpdate hook. | ||
Atom overlay: abstraction layer on top of [mobx][mobx], [cellx][cellx], [derivable][derivable] with hot reload support and error handling. | ||
[mobx]: mobxjs.github.io/mobx/ | ||
Some limitations: Only object and functions as atom values: atmover attaches to them metadata. No observable collections, maps, etc. | ||
[mobx]: https://github.com/mobxjs/mobx | ||
[cellx]: https://github.com/Riim/cellx | ||
[derivable]: https://github.com/ds300/derivablejs | ||
## Example for mobx: | ||
## Setup | ||
### mobx | ||
```js | ||
@@ -21,3 +24,37 @@ // @flow | ||
const atmover = new Atmover(new MobxPlugin(mobx), hotReloadingEnabled) | ||
``` | ||
### derivable | ||
```js | ||
// @flow | ||
import {Atmover, getAtom} from 'atmover' | ||
import CellxPlugin from 'atmover/DerivablePlugin' | ||
import type {Atom} from 'atmover' | ||
import derivable from 'derivable' | ||
const hotReloadingEnabled = true | ||
const atmover = new Atmover(new DerivablePlugin(derivable), hotReloadingEnabled) | ||
/// ... | ||
``` | ||
### cellx: | ||
```js | ||
// @flow | ||
import {Atmover, getAtom} from 'atmover' | ||
import CellxPlugin from 'atmover/CellxPlugin' | ||
import type {Atom} from 'atmover' | ||
import cellx from 'cellx' | ||
const hotReloadingEnabled = true | ||
const atmover = new Atmover(new CellxPlugin(cellx), hotReloadingEnabled) | ||
/// ... | ||
``` | ||
## Value get/set | ||
```js | ||
// @flow | ||
interface BOpts { | ||
@@ -28,82 +65,198 @@ a: number | ||
const aAtom: Atom<BOpts> = atmover.value(({a: 1}: BOpts)) | ||
const bAtom: Atom<BOpts> = atmover.value(({a: 20}: BOpts)) | ||
aAtom.get() // {a: 1} | ||
aAtom.set({a: 2}) | ||
``` | ||
class B { | ||
## Get atom from object metadata | ||
```js | ||
// @flow | ||
const a = aAtom.get() | ||
a.a === 1 | ||
const atom: Atom<BOpts> = getAtom(a) | ||
``` | ||
## Transactions | ||
```js | ||
// @flow | ||
const bAtom: Atom<BOpts> = atmover.value(({a: 10}: BOpts)) | ||
atmover.transact(() => { | ||
aAtom.set({a: 3}) | ||
bAtom.set({a: 11}) | ||
}) | ||
``` | ||
## Computable class | ||
```js | ||
// @flow | ||
class C { | ||
v: number | ||
v2: number | ||
constructor(opts1: BOpts, opts2: BOpts) { | ||
this.v = opts1.a | ||
this.v2 = opts2.a | ||
this.v = opts1.a + opts2.a | ||
} | ||
} | ||
const bAtom: Atom<B> = atmover.construct(B, [aAtom, bAtom]) | ||
const b: B = bAtom.get() | ||
assert(b.v === 1) | ||
const cAtom: Atom<C> = atmover.construct(C, [aAtom, bAtom]) | ||
const c: C = cAtom.get() | ||
assert(c.v === 14) | ||
``` | ||
const unsubscribe: () => void = b.subscribe((b: B) => { | ||
console.log('reinit B', b) | ||
## Objects in constructor arguments | ||
```js | ||
// @flow | ||
class C { | ||
v: number | ||
constructor(opts1: BOpts, opts2: {b: BOpts}) { | ||
this.v = opts1.a + opts2.b.a | ||
} | ||
} | ||
const cAtom: Atom<C> = atmover.construct(C, [aAtom, {b: bAtom}]) | ||
const c: C = cAtom.get() | ||
assert(c.v === 14) | ||
``` | ||
## Computable function | ||
```js | ||
// @flow | ||
interface CResult { | ||
v: number; | ||
} | ||
function factoryC(opts: BOpts): CResult { | ||
return { | ||
v: opts.a | ||
} | ||
} | ||
const fAtom: Atom<CResult> = atmover.factory(factoryC, [aAtom]) | ||
const f: CResult = fAtom.get() | ||
assert(f.v === 3) | ||
``` | ||
## Listen changes | ||
```js | ||
// @flow | ||
const unsubscribe: () => void = cAtom.subscribe((c: C) => { | ||
console.log('c.v = ' + c.v) | ||
}) | ||
aAtom.set({a: 4}) // console: c.v = 15 | ||
unsubscribe() | ||
``` | ||
## Error handling in computable | ||
```js | ||
// @flow | ||
class D { | ||
v: number | ||
constructor(opts1: BOpts, opts2: BOpts) { | ||
this.v = opts1.a + opts2.a | ||
if (this.v === 0) { | ||
throw new Error('Example error') | ||
} | ||
} | ||
} | ||
const dAtom: Atom<D> = atmover.construct(C, [aAtom, bAtom]) | ||
const unsubscribe: () => void = dAtom.subscribe((c: C) => { | ||
console.log('d.v = ' + d.v) | ||
}, (err: Error) => { | ||
console.log(err) | ||
console.error(err) | ||
}) | ||
atmover.transact(() => { | ||
aAtom.set({a: 2}) // reinit B | ||
bAtom.set({a: 20}) | ||
aAtom.set({a: 0}) | ||
bAtom.set({a: 0}) | ||
}) | ||
// console: Error: Example error | ||
// Get atom from object metadata: | ||
assert(getAtom(b).get().v === 2) | ||
dAtom.get() === undefined | ||
class C extends B { | ||
v1: number | ||
constructor(opts: BOpts) { | ||
super(opts) | ||
this.v1 = opts.a + 1 | ||
unsubscribe() | ||
``` | ||
## onUpdate hook | ||
```js | ||
// @flow | ||
class E { | ||
v: number | ||
some: number | ||
constructor(opts1: BOpts) { | ||
this.v = opts1.a | ||
} | ||
setSome(some: number): void { | ||
this.some = some | ||
} | ||
// $FlowFixMe: computed property key not supported, see https://github.com/facebook/flow/issues/2286 | ||
[onUpdate](next: C) { | ||
console.log('Before update hook') | ||
[onUpdate](next: E) { | ||
next.setSome(this.some) | ||
} | ||
} | ||
// Hot reloading: | ||
atmover.replaceProto(B, C) | ||
// console: Before update hook | ||
// console: reinit B | ||
const eAtom: Atom<E> = atmover.construct(E, [aAtom]) | ||
assert(getAtom(b).get() instanceof C) | ||
assert(getAtom(b).get().v1 === 3) | ||
const oldValue: E = eAtom.get() | ||
unsubscribe() | ||
// ... | ||
``` | ||
oldValue.setSome(33) | ||
## Example for derivable: | ||
aAtom.set({a: 10}) | ||
```js | ||
// @flow | ||
import {Atmover, getAtom} from 'atmover' | ||
import DerivablePlugin from 'atmover/DerivablePlugin' | ||
import type {Atom} from 'atmover' | ||
import derivable from 'derivable' | ||
const newValue: E = eAtom.get() | ||
const hotReloadingEnabled = true | ||
const atmover = new Atmover(new DerivablePlugin(derivable), hotReloadingEnabled) | ||
/// ... | ||
assert(oldValue !== newValue) | ||
assert(newValue.some === 33) | ||
``` | ||
## Example for cellx: | ||
## Replacing prototype | ||
```js | ||
// @flow | ||
import {Atmover, getAtom} from 'atmover' | ||
import CellxPlugin from 'atmover/CellxPlugin' | ||
import type {Atom} from 'atmover' | ||
import cellx from 'cellx' | ||
class B1 { | ||
v: number | ||
const hotReloadingEnabled = true | ||
const atmover = new Atmover(new DerivablePlugin(cellx), hotReloadingEnabled) | ||
/// ... | ||
constructor(opts: BOpts) { | ||
this.v = opts.a | ||
} | ||
} | ||
class B2 extends B1 { | ||
v: number | ||
constructor(opts: BOpts) { | ||
super(opts) | ||
this.v = this.v * 2 | ||
} | ||
} | ||
const b1Atom: Atom<B1> = atmover.construct(B1, [aAtom]) | ||
b1Atom.get().v === 10 | ||
// Hot reloading: | ||
atmover.replaceProto(B1, B2) | ||
b1Atom.get().v === 20 | ||
``` |
@@ -30,2 +30,21 @@ // @flow | ||
it('object as arg', () => { | ||
type BOpts = {a: number} | ||
const a = atmover.value(({a: 1}: BOpts)) | ||
const b = atmover.value(({a: 2}: BOpts)) | ||
class C { | ||
_b: number | ||
_b2: number | ||
constructor(opts: BOpts, rec: {b: BOpts}) { | ||
this._b = opts.a | ||
this._b2 = rec.b.a | ||
} | ||
} | ||
const c = atmover.construct(C, [a, {b}]) | ||
assert(c.get()._b2 === 2) | ||
}) | ||
it('factory', () => { | ||
@@ -32,0 +51,0 @@ type BOpts = {a: number} |
@@ -11,24 +11,5 @@ // @flow | ||
plugins.forEach(([name, atmover]: Rec) => { | ||
const pass = (v: any) => v | ||
describe(`${name} subscribe`, () => { | ||
it('value', () => { | ||
const v1 = {a: 1} | ||
const atom = atmover.value(v1) | ||
const listener = sinon.spy() | ||
const unsubscribe = atom.subscribe(listener) | ||
const v2 = {a: 2} | ||
atom.set(v2) | ||
return new Promise((resolve: () => void) => { | ||
setTimeout(() => { | ||
assert(listener.calledOnce) | ||
assert(listener.firstCall.calledWith( | ||
sinon.match.same(v2) | ||
)) | ||
unsubscribe() | ||
resolve() | ||
}, 0) | ||
}) | ||
}) | ||
it('instance', () => { | ||
@@ -47,14 +28,8 @@ class A { | ||
return new Promise((resolve: () => void) => { | ||
setTimeout(() => { | ||
assert(listener.calledOnce) | ||
assert(listener.firstCall.calledWith( | ||
sinon.match.instanceOf(A) | ||
.and(sinon.match({v: 2})) | ||
)) | ||
unsubscribe() | ||
resolve() | ||
}, 0) | ||
}) | ||
assert(listener.calledOnce) | ||
assert(listener.firstCall.calledWith( | ||
sinon.match.instanceOf(A) | ||
.and(sinon.match({v: 2})) | ||
)) | ||
unsubscribe() | ||
}) | ||
@@ -65,3 +40,4 @@ | ||
const listener = sinon.spy() | ||
const unsubscribe = atom.subscribe(listener) | ||
const computable = atmover.factory(pass, [atom]) | ||
const unsubscribe = computable.subscribe(listener) | ||
unsubscribe() | ||
@@ -78,27 +54,24 @@ const v2 = {a: 2} | ||
const listener2 = sinon.spy() | ||
const unsubscribe1 = atom.subscribe(listener1) | ||
const unsubscribe2 = atom.subscribe(listener2) | ||
const computable = atmover.factory(pass, [atom]) | ||
const unsubscribe1 = computable.subscribe(listener1) | ||
const unsubscribe2 = computable.subscribe(listener2) | ||
const v2 = {a: 2} | ||
atom.set(v2) | ||
return new Promise((resolve: () => void) => { | ||
setTimeout(() => { | ||
assert(listener1.calledOnce) | ||
assert(listener1.firstCall.calledWith( | ||
sinon.match.same(v2) | ||
)) | ||
assert(listener1.calledOnce) | ||
assert(listener1.firstCall.calledWith( | ||
sinon.match.same(v2) | ||
)) | ||
assert(listener2.calledOnce) | ||
assert(listener2.firstCall.calledWith( | ||
sinon.match.same(v2) | ||
)) | ||
assert(listener2.calledOnce) | ||
assert(listener2.firstCall.calledWith( | ||
sinon.match.same(v2) | ||
)) | ||
unsubscribe1() | ||
unsubscribe2() | ||
resolve() | ||
}, 0) | ||
}) | ||
unsubscribe1() | ||
unsubscribe2() | ||
}) | ||
}) | ||
}) |
// @flow | ||
import type {Transact, Atom, Fn, AtmoverPlugin, ProtoCache, AtomGetter, AtomSetter} from './interfaces' | ||
import type {Transact, Atom, Fn, AtmoverPlugin, ProtoCache, AtomSetter, AtomArg, NormalizedAtomArgs} from './interfaces' | ||
import {fastCreateObject, fastCall} from './fastCreate' | ||
import {InstanceFactory} from './pluginHelpers' | ||
@@ -73,3 +74,3 @@ class FakeAtomSetter<V> { | ||
value<V: Object>(v: V): Atom<V> { | ||
value<V: Object>(v: V): AtomSetter<V> { | ||
return this._plugin.createValueAtom(v) | ||
@@ -82,17 +83,13 @@ } | ||
construct<V: Object>(p: Class<V>, args?: AtomGetter<*>[]): Atom<V> { | ||
construct<V: Object>(p: Class<V>, args?: (AtomArg[] | NormalizedAtomArgs)): Atom<V> { | ||
return this._plugin.createInstanceAtom( | ||
fastCreateObject, | ||
this._protoCache.get(p), | ||
args || [] | ||
new InstanceFactory(args, this._protoCache.get(p), fastCreateObject) | ||
) | ||
} | ||
factory<V: Object>(p: Fn<V>, args?: AtomGetter<*>[]): Atom<V> { | ||
factory<V: Object>(p: Fn<V>, args?: (AtomArg[] | NormalizedAtomArgs)): Atom<V> { | ||
return this._plugin.createInstanceAtom( | ||
fastCall, | ||
this._protoCache.get(p), | ||
args || [] | ||
new InstanceFactory(args, this._protoCache.get(p), fastCall) | ||
) | ||
} | ||
} |
@@ -11,3 +11,3 @@ // @flow | ||
export interface Atom<V> extends AtomSetter<V> { | ||
export interface Atom<V> extends AtomGetter<V> { | ||
subscribe(fn: (v: V) => void, err?: (e: Error) => void): () => void; | ||
@@ -22,9 +22,20 @@ } | ||
export interface IAtomError { | ||
error: Error; | ||
} | ||
export interface IInstanceFactory<V> extends AtomGetter<V> { | ||
setAtom(atom: Atom<V>): IInstanceFactory<V>; | ||
setSafeMode(isSafe: boolean): IInstanceFactory<V>; | ||
createListener( | ||
fn: (v: V) => void, | ||
err?: (e: Error) => void | ||
): (v: V) => void; | ||
} | ||
export interface AtmoverPlugin { | ||
createInstanceAtom<V: Object | Function>( | ||
create: CreateInstance<V>, | ||
protoAtom: AtomGetter<Function>, | ||
args: AtomGetter<*>[] | ||
instanceFactory: IInstanceFactory<V> | ||
): Atom<V>; | ||
createValueAtom<V: Object | Function>(value: V): Atom<V>; | ||
createValueAtom<V: Object | Function>(value: V): AtomSetter<V>; | ||
transact: Transact; | ||
@@ -38,3 +49,20 @@ } | ||
export const metaKey = Symbol('aovr:atom') | ||
export const onUpdate = Symbol('aovr:onUpdate') | ||
type ValuesRec = {key: string, value: AtomGetter<*>} | ||
export type NormalizedAtomArg = { | ||
id: 1; | ||
value: AtomGetter<*>; | ||
} | { | ||
id: 2; | ||
values: ValuesRec[] | ||
} | ||
export type NormalizedAtomArgs = { | ||
id: 3; | ||
args: NormalizedAtomArg[]; | ||
} | ||
export type AtomArg = AtomGetter<*> | {[id: string]: AtomGetter<*>} | ||
export const metaKey = Symbol('ao:atom') | ||
export const onUpdate = Symbol('ao:upd') |
// @flow | ||
import {metaKey, onUpdate} from './interfaces' | ||
import type {AtomGetter, Atom} from './interfaces' | ||
import type {AtomGetter, Atom, AtomArg, NormalizedAtomArg, NormalizedAtomArgs} from './interfaces' | ||
type AttachMeta<V> = (value: V, deps?: ?mixed[]) => V | ||
export function createAttachMeta<V: Object>(selfAtom: Atom<V>): AttachMeta<V> { | ||
export function createAttachMeta<V: Object>(selfAtom: AtomGetter<V>): AttachMeta<V> { | ||
let oldValue: ?V = null | ||
return function attachMeta(value: V, deps?: ?mixed[]): V { | ||
return function attachMeta(value: V): V { | ||
value[metaKey] = selfAtom // eslint-disable-line | ||
if (oldValue && oldValue[onUpdate]) { | ||
oldValue[onUpdate].call(oldValue, value, deps) | ||
oldValue[onUpdate].call(oldValue, value) | ||
} | ||
@@ -20,15 +20,60 @@ oldValue = value | ||
export function createInstanceFactory<V: Object>( | ||
create: (proto: Class<V>, deps: mixed[]) => V, | ||
args: AtomGetter<*>[], | ||
protoAtom: AtomGetter<Function>, | ||
attachMeta: AttachMeta<V> | ||
): () => V { | ||
return function instanceFactory(): V { | ||
const deps: mixed[] = [] | ||
function normalizeArgs(args: AtomArg[]): NormalizedAtomArg[] { | ||
const result: NormalizedAtomArg[] = [] | ||
for (let i = 0, l = args.length; i < l; i++) { | ||
const value = args[i] | ||
if (!value.get) { | ||
const values = [] | ||
for (let key in value) { // eslint-disable-line | ||
values.push({key, value: (value: any)[key]}) | ||
} | ||
result.push({ | ||
id: 2, | ||
values | ||
}) | ||
} else { | ||
result.push({ | ||
id: 1, | ||
value | ||
}) | ||
} | ||
} | ||
return result | ||
} | ||
class ArgsAtomGetter { | ||
_args: NormalizedAtomArg[] | ||
_deps: any[] | ||
constructor(rawArgs: ?(AtomArg[] | NormalizedAtomArgs)) { | ||
if (rawArgs && (rawArgs: any).id === 3) { | ||
this._args = (rawArgs: any).args | ||
} else { | ||
this._args = rawArgs ? normalizeArgs((rawArgs: any)) : [] | ||
} | ||
this._deps = new Array(this._args.length) | ||
} | ||
get(): mixed[] { | ||
const args = this._args | ||
const deps = this._deps | ||
for (let i = 0, l = args.length; i < l; i++) { | ||
deps[i] = args[i].get() | ||
const arg: NormalizedAtomArg = args[i] | ||
if (arg.id === 1) { | ||
deps[i] = arg.value.get() | ||
} else { | ||
const values = arg.values | ||
let target: {[id: string]: NormalizedAtomArg} = deps[i] | ||
if (!target) { | ||
target = deps[i] = {} | ||
} | ||
for (let j = 0, k = values.length; j < k; j++) { | ||
const rec = values[j] | ||
target[rec.key] = rec.value.get() | ||
} | ||
} | ||
} | ||
return attachMeta(create(protoAtom.get(), deps), deps) | ||
return deps | ||
} | ||
@@ -45,25 +90,63 @@ } | ||
export function invokeDerivable<V: Object | Function>(fn: () => V): V | AtomError { | ||
try { | ||
return fn() | ||
} catch (error) { | ||
/* eslint-disable no-console */ | ||
console.error(error) | ||
return new AtomError(error) | ||
export class InstanceFactory<V: Object> { | ||
_args: AtomGetter<mixed[]> | ||
_protoAtom: AtomGetter<Function> | ||
_create: (proto: Class<V>, deps: mixed[]) => V | ||
_attachMeta: AttachMeta<V> | ||
_isSafe: boolean = false | ||
constructor( | ||
args: ?(AtomArg[] | NormalizedAtomArgs), | ||
protoAtom: AtomGetter<Function>, | ||
create: (proto: Class<V>, deps: mixed[]) => V | ||
) { | ||
this._args = new ArgsAtomGetter(args) | ||
this._protoAtom = protoAtom | ||
this._create = create | ||
} | ||
} | ||
export function createListener<V>( | ||
fn: (v: V) => void, | ||
err?: (e: Error) => void | ||
): (v: V | AtomError) => void { | ||
return function listener(v: V | AtomError): void { | ||
if (v instanceof AtomError) { | ||
if (err) { | ||
err(v.error) | ||
setAtom(atom: Atom<V>): InstanceFactory<V> { | ||
this._attachMeta = createAttachMeta(atom) | ||
return this | ||
} | ||
get: () => V = () => { | ||
return this._isSafe ? this._getSafe() : this._get() | ||
} | ||
setSafeMode(isSafe: boolean): InstanceFactory<V> { | ||
this._isSafe = isSafe | ||
return this | ||
} | ||
_get(): V { | ||
return this._attachMeta(this._create(this._protoAtom.get(), this._args.get())) | ||
} | ||
_getSafe(): V { | ||
try { | ||
return this._get() | ||
} catch (error) { | ||
/* eslint-disable no-console */ | ||
console.error(error) | ||
return (new AtomError(error): any) | ||
} | ||
} | ||
createListener( | ||
fn: (v: V) => void, | ||
err?: (e: Error) => void | ||
): (v: V) => void { | ||
return function listener(v: V): void { | ||
if (v instanceof AtomError) { | ||
if (err) { | ||
err(v.error) | ||
} | ||
} else { | ||
fn(v) | ||
} | ||
} else { | ||
fn(v) | ||
} | ||
} | ||
} |
// @flow | ||
import type {Atom, Transact, CreateInstance, AtomGetter} from '../interfaces' | ||
import {createInstanceFactory, createAttachMeta} from '../pluginHelpers' | ||
import type {Atom, AtomSetter, Transact, IInstanceFactory} from '../interfaces' | ||
import {createAttachMeta} from '../pluginHelpers' | ||
@@ -46,14 +46,2 @@ interface CellxEvent<V> { | ||
} | ||
subscribe(fn: (v: V) => void): () => void { | ||
function changeListener(event: CellxEvent<BoxedValue<V>>): void { | ||
return fn(event.value.v) | ||
} | ||
this._value('addChangeListener', changeListener) | ||
const unsubscribe = () => { | ||
this._value('removeChangeListener', changeListener) | ||
} | ||
return unsubscribe | ||
} | ||
} | ||
@@ -63,18 +51,11 @@ | ||
_value: any // CellxAtom<BoxedValue<V>> | ||
_isHandleErrors: boolean = false | ||
_factory: IInstanceFactory<V> | ||
constructor( | ||
cellx: Cellx, | ||
create: CreateInstance<V>, | ||
proto: AtomGetter<Function>, | ||
args: AtomGetter<*>[] | ||
factory: IInstanceFactory<V> | ||
) { | ||
const createInstance: () => V = createInstanceFactory( | ||
create, | ||
args, | ||
proto, | ||
createAttachMeta(this) | ||
) | ||
this._value = cellx(createInstance) | ||
factory.setAtom(this) | ||
this._factory = factory | ||
this._value = cellx(factory.get) | ||
} | ||
@@ -92,11 +73,7 @@ | ||
const value = this._value | ||
this._isHandleErrors = true | ||
function listener( | ||
error: Error, | ||
evt: { | ||
type: string; | ||
value: V | ||
} | ||
): void { | ||
function listener(error: Error, evt: { | ||
type: string; | ||
value: V | ||
}): void { | ||
if (error && err) { | ||
@@ -108,3 +85,3 @@ err(error) | ||
} | ||
this._value('subscribe', listener) | ||
value('subscribe', listener) | ||
@@ -123,19 +100,15 @@ return function unsubscribe(): void { | ||
this._cellx = cellx | ||
this.transact = function _transact(f: () => void): void { | ||
f() | ||
cellx.Cell.forceRelease() | ||
} | ||
cellx.configure({asynchronous: false}) | ||
this.transact = cellx.transact | ||
} | ||
createInstanceAtom<V: Object | Function>( | ||
create: CreateInstance<V>, | ||
protoAtom: AtomGetter<Function>, | ||
argsAtom: AtomGetter<*>[] | ||
factory: IInstanceFactory<V> | ||
): Atom<V> { | ||
return new CellxInstanceAtom(this._cellx, create, protoAtom, argsAtom) | ||
return new CellxInstanceAtom(this._cellx, factory) | ||
} | ||
createValueAtom<V: Object | Function>(value: V): Atom<V> { | ||
createValueAtom<V: Object | Function>(value: V): AtomSetter<V> { | ||
return new CellxValueAtom(this._cellx, value) | ||
} | ||
} |
// @flow | ||
import type {Atom, Transact, CreateInstance, AtomGetter} from '../interfaces' | ||
import {createInstanceFactory, createAttachMeta, invokeDerivable, AtomError, createListener} from '../pluginHelpers' | ||
import type {Atom, AtomSetter, Transact, IInstanceFactory} from '../interfaces' | ||
import {createAttachMeta, AtomError} from '../pluginHelpers' | ||
@@ -31,3 +31,2 @@ interface LifeCycle { | ||
class DerivableValueAtom<V: Object | Function> { | ||
_createAtom: CreateAtom<*> | ||
_value: DerivableAtom<V> | ||
@@ -40,3 +39,2 @@ _attachMeta: (value: V) => V | ||
) { | ||
this._createAtom = derivable.atom | ||
this._attachMeta = createAttachMeta(this) | ||
@@ -53,14 +51,2 @@ this._value = derivable.atom(this._attachMeta(value)) | ||
} | ||
subscribe(fn: (v: V) => void): () => void { | ||
const until = this._createAtom(false) | ||
this._value.react(fn, { | ||
skipFirst: true, | ||
until | ||
}) | ||
return function unsubscribe(): void { | ||
until.set(true) | ||
} | ||
} | ||
} | ||
@@ -71,26 +57,13 @@ | ||
_value: Derivable<V> | ||
_isHandleErrors: boolean = false | ||
_factory: IInstanceFactory<V> | ||
constructor( | ||
derivable: DerivableJS, | ||
create: CreateInstance<V>, | ||
proto: AtomGetter<Function>, | ||
args: AtomGetter<*>[] | ||
factory: IInstanceFactory<V> | ||
) { | ||
this._createAtom = derivable.atom | ||
factory.setAtom(this) | ||
const createInstance: () => V = createInstanceFactory( | ||
create, | ||
args, | ||
proto, | ||
createAttachMeta(this) | ||
) | ||
const createHandledInstance: () => V = () => { | ||
return this._isHandleErrors | ||
? (invokeDerivable(createInstance): any) | ||
: createInstance() | ||
} | ||
this._value = derivable.derivation(createHandledInstance) | ||
this._value = derivable.derivation(factory.get) | ||
this._factory = factory | ||
} | ||
@@ -112,4 +85,5 @@ | ||
const until = this._createAtom(false) | ||
this._isHandleErrors = true | ||
this._value.react(createListener(fn, err), { | ||
const f = this._factory | ||
f.setSafeMode(true) | ||
this._value.react(f.createListener(fn, err), { | ||
skipFirst: true, | ||
@@ -135,12 +109,10 @@ until | ||
createInstanceAtom<V: Object | Function>( | ||
create: CreateInstance<V>, | ||
protoAtom: AtomGetter<Function>, | ||
argsAtom: AtomGetter<*>[] | ||
factory: IInstanceFactory<V> | ||
): Atom<V> { | ||
return new DerivableInstanceAtom(this._derivable, create, protoAtom, argsAtom) | ||
return new DerivableInstanceAtom(this._derivable, factory) | ||
} | ||
createValueAtom<V: Object | Function>(value: V): Atom<V> { | ||
createValueAtom<V: Object | Function>(value: V): AtomSetter<V> { | ||
return new DerivableValueAtom(this._derivable, value) | ||
} | ||
} |
// @flow | ||
import type {Atom, CreateInstance, AtomGetter, Transact} from '../interfaces' | ||
import {createInstanceFactory, createAttachMeta, invokeDerivable, AtomError, createListener} from '../pluginHelpers' | ||
import type {Atom, AtomSetter, Transact, IInstanceFactory} from '../interfaces' | ||
import {createAttachMeta, AtomError} from '../pluginHelpers' | ||
@@ -45,7 +45,2 @@ interface MobxAtom<V> { | ||
} | ||
subscribe(fn: (v: V) => void): () => void { | ||
const unboxValue = (v: BoxedValue<V>) => fn(v.v) | ||
return this._value.observe(unboxValue) | ||
} | ||
} | ||
@@ -55,24 +50,11 @@ | ||
_value: MobxAtom<V> | ||
_isHandleErrors: boolean = false | ||
_factory: IInstanceFactory<V> | ||
constructor( | ||
mobx: Mobx, | ||
create: CreateInstance<V>, | ||
proto: AtomGetter<Function>, | ||
args: AtomGetter<*>[] | ||
factory: IInstanceFactory<V> | ||
) { | ||
const createInstance: () => V = createInstanceFactory( | ||
create, | ||
args, | ||
proto, | ||
createAttachMeta(this) | ||
) | ||
const createHandledInstance: () => V = () => { | ||
return this._isHandleErrors | ||
? (invokeDerivable(createInstance): any) | ||
: createInstance() | ||
} | ||
this._value = mobx.computed(createHandledInstance) | ||
this._factory = factory | ||
factory.setAtom(this) | ||
this._value = mobx.computed(factory.get) | ||
} | ||
@@ -93,4 +75,3 @@ | ||
subscribe(fn: (v: V) => void, err?: (e: Error) => void): () => void { | ||
this._isHandleErrors = true | ||
return this._value.observe(createListener(fn, err)) | ||
return this._value.observe(this._factory.setSafeMode(true).createListener(fn, err)) | ||
} | ||
@@ -109,12 +90,10 @@ } | ||
createInstanceAtom<V: Object | Function>( | ||
create: CreateInstance<V>, | ||
protoAtom: AtomGetter<Function>, | ||
argsAtom: AtomGetter<*>[] | ||
factory: IInstanceFactory<V> | ||
): Atom<V> { | ||
return new MobxInstanceAtom(this._mobx, create, protoAtom, argsAtom) | ||
return new MobxInstanceAtom(this._mobx, factory) | ||
} | ||
createValueAtom<V: Object | Function>(value: V): Atom<V> { | ||
createValueAtom<V: Object | Function>(value: V): AtomSetter<V> { | ||
return new MobxValueAtom(this._mobx, value) | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
93998
1573
261