@cerebral/fluent
Advanced tools
Comparing version 1.0.0-1518417962068 to 1.0.0-1518472516428
@@ -13,2 +13,5 @@ import { ObservableMap as MobxObservableMap } from 'mobx'; | ||
export { Module, Provider, CerebralError } from 'cerebral'; | ||
export declare type LegacyState = { | ||
set(path: string, value: any): void; | ||
}; | ||
export declare class FluentController<State = {}, Signals = {}> extends BaseControllerClass { | ||
@@ -19,6 +22,12 @@ state: State; | ||
contextProviders: any; | ||
constructor(rootModule: ModuleClass, options: ControllerOptions); | ||
useLegacyStateApi: boolean; | ||
constructor(rootModule: ModuleClass, options: ControllerOptions & { | ||
useStrict?: boolean; | ||
useLegacyStateApi?: boolean; | ||
}); | ||
addModule(path: string, module: ModuleClass): void; | ||
removeModule(path: string): void | null; | ||
} | ||
export declare function Controller<State = {}, Signals = {}>(rootModule: ModuleClass, options?: ControllerOptions): FluentController<State, Signals>; | ||
export declare function Controller<State = {}, Signals = {}>(rootModule: ModuleClass, options?: ControllerOptions & { | ||
useStrict?: boolean; | ||
}): FluentController<State, Signals>; |
@@ -1,2 +0,2 @@ | ||
import { observable, untracked, transaction, extras, ObservableMap as MobxObservableMap } from 'mobx'; | ||
import { observable, untracked, transaction, extras, ObservableMap as MobxObservableMap, useStrict } from 'mobx'; | ||
import { BaseControllerClass } from 'cerebral'; | ||
@@ -23,2 +23,3 @@ import { extractModuleProp, getModule, throwError } from 'cerebral/internal'; | ||
}); | ||
useStrict(typeof options.useStrict === 'undefined' ? true : options.useStrict); | ||
this.state = this.model.state; | ||
@@ -76,3 +77,3 @@ this.signals = extractModuleProp(this.module, 'signals', (signals, module) => { | ||
if (module.providers) { | ||
Object.keys(module.providers).forEach(provider => { | ||
Object.keys(module.providers).forEach((provider) => { | ||
delete this.contextProviders[provider]; | ||
@@ -79,0 +80,0 @@ }); |
@@ -16,2 +16,39 @@ /* eslint-env mocha */ | ||
}); | ||
it('should expose traditional model API', () => { | ||
const rootModule = Module({ | ||
state: { | ||
foo: 'bar', | ||
bool: true, | ||
list: ['foo', 'bar', 'baz'], | ||
object: Dictionary({ | ||
foo: 'bar' | ||
}), | ||
count: 1 | ||
}, | ||
signals: { | ||
test: [ | ||
function test({ state }) { | ||
state.set('foo', state.get('foo') + '2'); | ||
state.toggle('bool'); | ||
state.push('list', 'foo2'); | ||
state.merge('object', { bar: 'baz' }); | ||
state.pop('list'); | ||
state.shift('list'); | ||
state.unshift('list', 'foo3'); | ||
state.splice('list', 1, 1); | ||
state.unset('object.foo'); | ||
state.concat('list', ['apple']); | ||
state.increment('count', 1); | ||
} | ||
] | ||
} | ||
}); | ||
const controller = Controller(rootModule); | ||
controller.signals.test(); | ||
assert.equal(controller.state.foo, 'bar2'); | ||
assert.equal(controller.state.bool, false); | ||
assert.deepEqual(controller.state.list.map((val) => val), ['foo3', 'baz', 'apple']); | ||
assert.deepEqual(controller.state.object.toJSON(), { bar: 'baz' }); | ||
assert.equal(controller.state.count, 2); | ||
}); | ||
it('should instantiate with observable maps', () => { | ||
@@ -34,3 +71,5 @@ const rootModule = Module({ | ||
}); | ||
const controller = Controller(rootModule); | ||
const controller = Controller(rootModule, { | ||
useStrict: false | ||
}); | ||
autorun(() => { | ||
@@ -45,3 +84,3 @@ return controller.state.foo; | ||
const SequenceWithProps = SequenceWithPropsFactory(); | ||
const sequence = SequenceWithProps(s => s.action(function test({ state, props }) { | ||
const sequence = SequenceWithProps((s) => s.action(function test({ state, props }) { | ||
state.foo = 'bar2'; | ||
@@ -65,3 +104,3 @@ })); | ||
const Sequence = SequenceFactory(); | ||
const signal = Sequence(s => s.action(function test({ state }) { | ||
const signal = Sequence((s) => s.action(function test({ state }) { | ||
state.foo = 'bar2'; | ||
@@ -93,3 +132,3 @@ })); | ||
const Sequence = SequenceFactory(); | ||
const sequence = Sequence(s => s.action(function testAction({ state }) { | ||
const sequence = Sequence((s) => s.action(function testAction({ state }) { | ||
state.foo = 'bar2'; | ||
@@ -99,7 +138,7 @@ })); | ||
state: { | ||
foo: 'bar', | ||
foo: 'bar' | ||
}, | ||
signals: { | ||
test: sequence | ||
}, | ||
} | ||
}); | ||
@@ -113,37 +152,37 @@ const controller = Controller(rootModule); | ||
const Sequence = SequenceFactory(); | ||
const sequence = Sequence(s => s | ||
const sequence = Sequence((s) => s | ||
.action(function test({ state }) { | ||
state.set = 'bar2'; | ||
state._set = 'bar2'; | ||
}) | ||
.action(function test({ state }) { | ||
state.push.push('bar2'); | ||
state._push.push('bar2'); | ||
}) | ||
.action(function test({ state }) { | ||
state.pop.pop(); | ||
state._pop.pop(); | ||
}) | ||
.action(function test({ state }) { | ||
state.shift.shift(); | ||
state._shift.shift(); | ||
}) | ||
.action(function test({ state }) { | ||
state.splice.splice(1, 0, 'baz'); | ||
state._splice.splice(1, 0, 'baz'); | ||
}) | ||
.action(function test({ state }) { | ||
state.unshift.unshift('bar'); | ||
state._unshift.unshift('bar'); | ||
}) | ||
.action(function test({ state }) { | ||
state.observableMapSet.set('foo', 'bar'); | ||
state._observableMapSet.set('foo', 'bar'); | ||
})); | ||
const rootModule = Module({ | ||
state: { | ||
set: 'foo', | ||
push: [], | ||
pop: ['foo'], | ||
shift: ['foo'], | ||
splice: ['foo', 'bar'], | ||
unshift: ['foo'], | ||
observableMapSet: Dictionary({}) | ||
_set: 'foo', | ||
_push: [], | ||
_pop: ['foo'], | ||
_shift: ['foo'], | ||
_splice: ['foo', 'bar'], | ||
_unshift: ['foo'], | ||
_observableMapSet: Dictionary({}) | ||
}, | ||
signals: { | ||
test: sequence | ||
}, | ||
} | ||
}); | ||
@@ -161,3 +200,3 @@ const controller = Controller(rootModule, { | ||
method: 'set', | ||
args: [['set'], 'bar2'] | ||
args: [['_set'], 'bar2'] | ||
}); | ||
@@ -169,3 +208,3 @@ break; | ||
method: 'push', | ||
args: [['push'], 'bar2'] | ||
args: [['_push'], 'bar2'] | ||
}); | ||
@@ -177,3 +216,3 @@ break; | ||
method: 'pop', | ||
args: [['pop']] | ||
args: [['_pop']] | ||
}); | ||
@@ -185,3 +224,3 @@ break; | ||
method: 'shift', | ||
args: [['shift']] | ||
args: [['_shift']] | ||
}); | ||
@@ -193,3 +232,3 @@ break; | ||
method: 'splice', | ||
args: [['splice'], 1, 0, 'baz'] | ||
args: [['_splice'], 1, 0, 'baz'] | ||
}); | ||
@@ -201,3 +240,3 @@ break; | ||
method: 'unshift', | ||
args: [['unshift'], 'bar'] | ||
args: [['_unshift'], 'bar'] | ||
}); | ||
@@ -209,3 +248,3 @@ break; | ||
method: 'set', | ||
args: [['observableMapSet', 'foo'], 'bar'] | ||
args: [['_observableMapSet', 'foo'], 'bar'] | ||
}); | ||
@@ -216,12 +255,12 @@ break; | ||
} | ||
}, | ||
} | ||
}); | ||
controller.signals.test(); | ||
assert.equal(controller.state.set, 'bar2'); | ||
assert.equal(controller.state.push[0], 'bar2'); | ||
assert.equal(controller.state.pop.length, 0); | ||
assert.equal(controller.state.shift.length, 0); | ||
assert.equal(controller.state.splice.join('.'), 'foo.baz.bar'); | ||
assert.equal(controller.state.unshift[0], 'bar'); | ||
assert.equal(controller.state.observableMapSet.get('foo'), 'bar'); | ||
assert.equal(controller.state._set, 'bar2'); | ||
assert.equal(controller.state._push[0], 'bar2'); | ||
assert.equal(controller.state._pop.length, 0); | ||
assert.equal(controller.state._shift.length, 0); | ||
assert.equal(controller.state._splice.join('.'), 'foo.baz.bar'); | ||
assert.equal(controller.state._unshift[0], 'bar'); | ||
assert.equal(controller.state._observableMapSet.get('foo'), 'bar'); | ||
assert.equal(actionCount, 7); | ||
@@ -232,3 +271,3 @@ }); | ||
const SequenceWithProps = SequenceWithPropsFactory(); | ||
const sequence = SequenceWithProps(s => s.action(function test({ state, props }) { | ||
const sequence = SequenceWithProps((s) => s.action(function test({ state, props }) { | ||
state.foo = 'bar2'; | ||
@@ -268,4 +307,3 @@ })); | ||
const Sequence = SequenceFactory(); | ||
const sequence = Sequence(s => s | ||
.action(function test({ state }) { | ||
const sequence = Sequence((s) => s.action(function test({ state }) { | ||
const item = state.observableMapSet.get('item'); | ||
@@ -279,3 +317,3 @@ if (item) { | ||
observableMapSet: Dictionary({ | ||
'item': { | ||
item: { | ||
foo: 'string' | ||
@@ -287,3 +325,3 @@ } | ||
test: sequence | ||
}, | ||
} | ||
}); | ||
@@ -303,3 +341,3 @@ const controller = Controller(rootModule, { | ||
} | ||
}, | ||
} | ||
}); | ||
@@ -315,4 +353,3 @@ controller.signals.test(); | ||
const Sequence = SequenceFactory(); | ||
const sequence = Sequence(s => s | ||
.action(function test({ state }) { | ||
const sequence = Sequence((s) => s.action(function test({ state }) { | ||
const item = state.observableMapSet.delete('item'); | ||
@@ -323,3 +360,3 @@ })); | ||
observableMapSet: Dictionary({ | ||
'item': { | ||
item: { | ||
foo: 'string' | ||
@@ -331,3 +368,3 @@ } | ||
test: sequence | ||
}, | ||
} | ||
}); | ||
@@ -347,3 +384,3 @@ const controller = Controller(rootModule, { | ||
} | ||
}, | ||
} | ||
}); | ||
@@ -356,4 +393,3 @@ controller.signals.test(); | ||
const Sequence = SequenceFactory(); | ||
const sequence = Sequence(s => s | ||
.action(function test({ state }) { | ||
const sequence = Sequence((s) => s.action(function test({ state }) { | ||
const foo = state.foo; | ||
@@ -376,3 +412,3 @@ foo.bar.baz = 'mip2'; | ||
test: sequence | ||
}, | ||
} | ||
}); | ||
@@ -401,3 +437,3 @@ const controller = Controller(rootModule, { | ||
} | ||
}, | ||
} | ||
}); | ||
@@ -410,4 +446,3 @@ controller.signals.test(); | ||
const Sequence = SequenceFactory(); | ||
const sequence = Sequence(s => s | ||
.action(function test({ state }) { | ||
const sequence = Sequence((s) => s.action(function test({ state }) { | ||
const hasKey = state.observableMapSet.has('item'); | ||
@@ -430,3 +465,3 @@ const keys = state.observableMapSet.keys(); | ||
observableMapSet: Dictionary({ | ||
'item': { | ||
item: { | ||
foo: 'string', | ||
@@ -439,3 +474,3 @@ bar: 'string2' | ||
test: sequence | ||
}, | ||
} | ||
}); | ||
@@ -494,3 +529,3 @@ const controller = Controller(rootModule, { | ||
} | ||
}, | ||
} | ||
}); | ||
@@ -497,0 +532,0 @@ controller.signals.test(); |
119
lib/Model.js
import { extractModuleProp, isObject, BaseModel } from 'cerebral/internal'; | ||
import { Provider } from 'cerebral'; | ||
import { observable, isObservable, isObservableMap, extendObservable, ObservableMap, useStrict, } from 'mobx'; | ||
import { observable, isObservable, isObservableMap, extendObservable, ObservableMap } from 'mobx'; | ||
import { ComputedClass } from './Computed'; | ||
import { updateIn, traverse } from './utils'; | ||
const mutationMethods = [ | ||
'concat', | ||
'pop', | ||
'push', | ||
'shift', | ||
'splice', | ||
'unshift', | ||
]; | ||
const mutationMethods = ['concat', 'pop', 'push', 'shift', 'splice', 'unshift']; | ||
const nonMutationObservableMapKeys = [ | ||
@@ -174,3 +167,4 @@ 'has', | ||
}, execution, functionDetails, props); | ||
return Reflect.set(target, key, value); | ||
Reflect.set(target, key, value); | ||
return true; | ||
} | ||
@@ -180,7 +174,47 @@ }; | ||
} | ||
function CreateStateProvider(state, devtools) { | ||
const legacyApi = [ | ||
'get', | ||
'set', | ||
'toggle', | ||
'push', | ||
'merge', | ||
'pop', | ||
'shift', | ||
'unshift', | ||
'splice', | ||
'concat', | ||
'increment', | ||
'unset' | ||
]; | ||
function CreateStateProvider(state, devtools, model) { | ||
if (!devtools) { | ||
legacyApi.forEach((method) => { | ||
Object.defineProperty(state, method, { | ||
value(path, ...args) { | ||
return model[method](path.split('.'), ...args); | ||
} | ||
}); | ||
}); | ||
} | ||
return Provider(state, { | ||
wrap: devtools ? (context) => { | ||
return new Proxy(state, createValidator(state, context.execution, context.functionDetails, context.props, devtools)); | ||
} : false, | ||
wrap: devtools | ||
? (context) => { | ||
legacyApi.forEach((method) => { | ||
Object.defineProperty(state, method, { | ||
writable: true, | ||
value(path, ...args) { | ||
if (method !== 'get') { | ||
devtools.sendExecutionData({ | ||
type: 'mutation', | ||
method: method, | ||
args: [path].concat(args.map((value) => (isObservable(value) ? value.value : value))) | ||
}, context.execution, context.functionDetails, context.props); | ||
} | ||
return model[method](path.split('.'), ...args); | ||
} | ||
}); | ||
}); | ||
return new Proxy(state, createValidator(state, context.execution, context.functionDetails, context.props, devtools)); | ||
} | ||
: false, | ||
ignoreDefinition: true | ||
@@ -192,14 +226,65 @@ }); | ||
super(controller); | ||
useStrict(false); | ||
this.state = extractModuleProp(controller.module, 'state', (state, module) => { | ||
return this.observeState(state); | ||
}); | ||
this.StateProvider = (devtools) => CreateStateProvider(this.state, devtools); | ||
this.StateProvider = (devtools) => CreateStateProvider(this.state, devtools, this); | ||
} | ||
// Used by initial state changed | ||
set(path, value) { | ||
updateIn(this.state, path.split('.'), (parentState, key) => { | ||
updateIn(this.state, path, (parentState, key) => { | ||
parentState[key] = value; | ||
}); | ||
} | ||
toggle(path) { | ||
updateIn(this.state, path, (parentState, key) => { | ||
parentState[key] = !parentState[key]; | ||
}); | ||
} | ||
push(path, value) { | ||
updateIn(this.state, path, (parentState, key) => { | ||
parentState[key].push(value); | ||
}); | ||
} | ||
merge(path, mergeValue, ...values) { | ||
const value = Object.assign(mergeValue, ...values); | ||
updateIn(this.state, path, (parentState, key) => { | ||
parentState[key].merge(value); | ||
}); | ||
} | ||
pop(path) { | ||
updateIn(this.state, path, (parentState, key) => { | ||
parentState[key].pop(); | ||
}); | ||
} | ||
shift(path) { | ||
updateIn(this.state, path, (parentState, key) => { | ||
parentState[key].shift(); | ||
}); | ||
} | ||
unshift(path, value) { | ||
updateIn(this.state, path, (parentState, key) => { | ||
parentState[key].unshift(value); | ||
}); | ||
} | ||
splice(path, ...args) { | ||
updateIn(this.state, path, (parentState, key) => { | ||
parentState[key].splice(...args); | ||
}); | ||
} | ||
unset(path) { | ||
const deleteKey = path.pop(); | ||
updateIn(this.state, path, (parentState, key) => { | ||
parentState[key].delete(deleteKey); | ||
}); | ||
} | ||
concat(path, value) { | ||
updateIn(this.state, path, (parentState, key) => { | ||
parentState[key] = parentState[key].concat(value); | ||
}); | ||
} | ||
increment(path, delta = 1) { | ||
updateIn(this.state, path, (parentState, key) => { | ||
parentState[key] += delta; | ||
}); | ||
} | ||
get(path) { | ||
@@ -206,0 +291,0 @@ if (!path) { |
@@ -1,2 +0,2 @@ | ||
import { isObservable } from 'mobx'; | ||
import { isObservable, isObservableMap } from 'mobx'; | ||
import { ComputedClass } from './Computed'; | ||
@@ -12,3 +12,3 @@ import { throwError, isObject } from 'cerebral/internal'; | ||
} | ||
return currentValue[key]; | ||
return isObservableMap(currentValue) ? currentValue.get(key) : currentValue[key]; | ||
}, obj); | ||
@@ -15,0 +15,0 @@ } |
@@ -5,3 +5,13 @@ import { BaseModel } from 'cerebral/internal'; | ||
constructor(controller: BaseControllerClass); | ||
set(path: string, value: any): void; | ||
set(path: string[], value: any): void; | ||
toggle(path: string[]): void; | ||
push(path: string[], value: any): void; | ||
merge(path: string[], mergeValue: {}, ...values: {}[]): void; | ||
pop(path: string[]): void; | ||
shift(path: string[]): void; | ||
unshift(path: string[], value: any): void; | ||
splice(path: string[], ...args: any[]): void; | ||
unset(path: string[]): void; | ||
concat(path: string[], value: any[]): void; | ||
increment(path: string[], delta?: number): void; | ||
get(path: string[]): any; | ||
@@ -8,0 +18,0 @@ observeState(state: {}): { |
{ | ||
"name": "@cerebral/fluent", | ||
"version": "1.0.0-1518417962068", | ||
"version": "1.0.0-1518472516428", | ||
"description": "Makes Cerebral typescript friendly", | ||
@@ -27,3 +27,3 @@ "main": "index.js", | ||
"dependencies": { | ||
"cerebral": "^4.2.0-1518417962068", | ||
"cerebral": "^4.2.0-1518472516428", | ||
"mobx": "^3.4.1" | ||
@@ -30,0 +30,0 @@ }, |
59257
1446