diff --git a/examples/todo/components/Form.jsx b/examples/todo/components/Form.jsx
new file mode 100644
index 0000000..f72740b
--- /dev/null
+++ b/examples/todo/components/Form.jsx
@@ -0,0 +1,56 @@
+import * as React from 'react'
+import { View, LocalState } from '../../../src'
+
+const {
+ state,
+ setState
+} = LocalState({
+ schema: {
+ name: "",
+ age: 0,
+ },
+
+ setter: (mutation) => {
+ return {
+ name: mutation.name ? mutation.name.toLowerCase() : "",
+ age: mutation.age
+ ? mutation.age > 0
+ ? mutation.age * 2
+ : mutation.age * 10
+ : mutation.age + 1,
+ }
+ },
+
+ getter: (state) => {
+ const [
+ firstName, lastName
+ ] = state.name.split(" ")
+
+ return {
+ firstName,
+ lastName,
+ }
+ }
+})
+
+export default View(
+ () =>
+
+)
diff --git a/examples/todo/components/TodoList.jsx b/examples/todo/components/TodoList.jsx
index 7d8aad4..f818e45 100644
--- a/examples/todo/components/TodoList.jsx
+++ b/examples/todo/components/TodoList.jsx
@@ -4,10 +4,18 @@ import { createTodo, login, logout } from '../actions.jsx'
import { userState, todoState } from '../state.jsx'
import ListItem from './ListItem.jsx'
+import Form from "./Form"
+
+console.log({
+ todoState, userState
+})
export default View(() => {
return (
+
+
+
Hi, we have {todoState.size} total todos.
Pending:
diff --git a/src/index.js b/src/index.js
index 25b9cef..9b1005d 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,9 +1,6 @@
-import { configure } from 'mobx'
-
-configure({ enforceActions: 'strict', isolatedGlobalState: true })
-
export * from 'mobx'
export { Model, Propose } from './model'
export { State } from './state'
-export { observer as View } from 'mobx-react'
\ No newline at end of file
+export { observer as View } from 'mobx-react'
+export { LocalState } from "./local-state"
diff --git a/src/local-state.js b/src/local-state.js
new file mode 100644
index 0000000..569165b
--- /dev/null
+++ b/src/local-state.js
@@ -0,0 +1,195 @@
+/**
+ *
+ * @example:
+ * import { LocalState } from "samx"
+ *
+ * const { state, setState } = LocalState({
+ * schema: {
+ * },
+ * setter: (mutation) => {
+ * return mutation
+ * },
+ * getter: (state) => {
+ * return state
+ * },
+ * })
+ * */
+
+import { observable, set } from "mobx"
+
+const LocalStateError = (msg) => new Error(msg)
+
+const Errors = {
+ setter: {
+ InvalidMutation: LocalStateError("Invalid Mutation"),
+ notAllowed: LocalStateError("Operation not allowed"),
+ },
+ setState: {
+ args: {
+ one: LocalStateError("Invalid arguments. First argument should be either a string prop name or a mutation object"),
+ two: LocalStateError("Invalid arguments. First argument must be a string, second must be an object/number/string"),
+ invalid: LocalStateError("Invalid argument list.")
+ }
+ },
+ Constructor: {
+ InvalidArgs: {
+ first: {
+ schema: LocalStateError("Schema must be an object"),
+ }
+ }
+ },
+ getter: {
+ prop: {
+ invalid: LocalStateError("Invalid property access. No such property is defined in the getter."),
+ }
+ },
+}
+
+/**
+ * Encapsulated Mobx.set for validation and interfacing purposes
+ *
+ * @param {Mobx.observable} observable
+ * @param {Object} mutation
+ */
+const Setter = (interceptor) => (observableState, mutation) => {
+ console.log(observableState, mutation)
+ Object.keys(mutation).forEach(
+ (key) => {
+ if (typeof mutation[key] === "function") {
+ throw Errors.setter.InvalidMutation
+ }
+ }
+ )
+
+ if (typeof interceptor === "function") {
+ const newMutation = interceptor(toJS(observableState), mutation)
+ set(observableState, newMutation)
+ } else {
+ set(observableState, mutation)
+ }
+
+ return true
+}
+
+/**
+ *
+ * @example:
+ * first:
+ * setState({ property: value })
+ *
+ * second:
+ * setState("property")(value)
+ *
+ * third:
+ * setState("property", value)
+ */
+const SetState = (observableState, interceptor) => (...args) => {
+ const interceptedSetter = Setter(interceptor)
+ const argsLength = args.length
+
+ switch (argsLength) {
+ case 1:
+ const arg = args[0]
+ if (typeof arg === "object") {
+ return interceptedSetter(observableState, arg)
+ } else if (typeof arg === "string") {
+ return (mutation) => interceptedSetter(observableState, mutation)
+ } else throw Errors.setState.args.one
+
+ case 2 || argsLength > 2:
+ const [ first, second ] = args
+
+ if (
+ typeof first === "string"
+ && (
+ typeof second === "object"
+ || typeof second === "string"
+ || typeof second === "number"
+ )
+ ) {
+ return interceptedSetter(observableState, {
+ [first]: second,
+ })
+ } else throw Errors.setState.args.two
+
+ default:
+ throw Errors.setState.args.invalid
+ }
+}
+
+/**
+ *
+ * @param {Object} opts
+ * @param {Object} opts.schema
+ * @optional {Function} opts.setter
+ * @optional {Function} opts.getter
+ *
+ */
+const LocalState = (opts) => {
+ const options = {}
+
+ if (typeof opts.schema !== "object") {
+ throw Errors.Constructor.InvalidArgs.first.schema
+ }
+
+ options.schema = opts.schema
+
+ if (opts.setter && typeof opts.setter === "Function") {
+ options.setter = opts.setter
+ }
+
+ if (opts.getter && typeof opts.getter === "Function") {
+ options.getter = opts.getter
+ }
+
+ const observableState = observable(options.schema)
+
+ const state = new Proxy({
+ __data: observableState,
+
+ getCurrentState() {
+ return toJS(observableState)
+ },
+
+ __getObservable() {
+ return observableState
+ },
+
+ }, {
+ get(target, prop) {
+ console.log({ target, prop })
+ if (typeof target[prop] === "function") {
+ const r = Reflect.get(...arguments)
+ console.log({ target, prop, value: target[prop], r })
+ return r
+ }
+
+ if (options.getter) {
+ const currentValue = options.getter(target.__data)
+
+ if (prop in Object.keys(currentValue)) {
+ return currentValue[prop]
+ } else {
+ throw Errors.getter.prop.invalid
+ }
+ } else {
+ return target.__data[prop]
+ }
+ },
+
+ set() {
+ throw Errors.setter.notAllowed
+ }
+ })
+
+ const setState = SetState(state.__getObservable(), options.setter || undefined)
+
+ return {
+ state,
+ setState,
+ }
+}
+
+export {
+ LocalState,
+}