Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

mobservable

Package Overview
Dependencies
Maintainers
1
Versions
79
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

mobservable - npm Package Compare versions

Comparing version 1.0.0-alpha.4 to 1.0.0-alpha.5

test.js

4

CHANGELOG.md

@@ -0,2 +1,6 @@

# 1.0
* Introduces a second `strictMode` parameter to transaction.
* dropped the default export of observable
* Fixes issue where non-`strict` mode and `logLevel` where not always honored.

@@ -3,0 +7,0 @@ # 0.7.0

243

dist/mobservable.js

@@ -66,4 +66,2 @@ (function webpackUniversalModuleDefinition(root, factory) {

__export(__webpack_require__(11));
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = core.observable;
var core_1 = __webpack_require__(1);

@@ -76,20 +74,24 @@ exports.isObservable = core_1.isObservable;

exports.asStructure = core_1.asStructure;
exports.observe = core_1.observe;
exports.observeUntil = core_1.observeUntil;
exports.observeAsync = core_1.observeAsync;
exports.autorun = core_1.autorun;
exports.autorunUntil = core_1.autorunUntil;
exports.autorunAsync = core_1.autorunAsync;
exports.expr = core_1.expr;
exports.transaction = core_1.transaction;
exports.toJSON = core_1.toJSON;
exports.logLevel = core_1.logLevel;
exports.strict = core_1.strict;
exports.isReactive = core_1.isObservable;
exports.makeReactive = core_1.observable;
exports.extendReactive = core_1.extendObservable;
exports.observe = core_1.autorun;
exports.observeUntil = core_1.autorunUntil;
exports.observeAsync = core_1.autorunAsync;
Object.defineProperties(module.exports, {
strict: {
enumerable: true,
get: function () { return core.strict; },
set: function (v) { return core.strict = v; }
get: core.getStrict,
set: core.setStrict
},
logLevel: {
enumerable: true,
get: function () { return core.logLevel; },
set: function (v) { return core.logLevel = v; }
get: core.getLogLevel,
set: core.setLogLevel
}

@@ -108,4 +110,4 @@ });

};
//# sourceMappingURL=index.js.map
/***/ },

@@ -172,3 +174,3 @@ /* 1 */

exports.isObservable = isObservable;
function observe(view, scope) {
function autorun(view, scope) {
var _a = getValueModeFromValue(view, ValueMode.Recursive), mode = _a[0], unwrappedView = _a[1];

@@ -183,3 +185,3 @@ var observable = new observableview_1.ObservableView(unwrappedView, scope, {

});
if (exports.logLevel >= 2 && observable.observing.length === 0)
if (logLevel >= 2 && observable.observing.length === 0)
console.warn("[mobservable.observe] not a single observable was used inside the observing function. This observer is now a no-op.");

@@ -189,5 +191,5 @@ disposer.$mobservable = observable;

}
exports.observe = observe;
function observeUntil(predicate, effect, scope) {
var disposer = observe(function () {
exports.autorun = autorun;
function autorunUntil(predicate, effect, scope) {
var disposer = autorun(function () {
if (predicate.call(scope)) {

@@ -200,8 +202,8 @@ disposer();

}
exports.observeUntil = observeUntil;
function observeAsync(view, effect, delay, scope) {
exports.autorunUntil = autorunUntil;
function autorunAsync(view, effect, delay, scope) {
if (delay === void 0) { delay = 1; }
var latestValue = undefined;
var timeoutHandle;
var disposer = observe(function () {
var disposer = autorun(function () {
latestValue = view.call(scope);

@@ -221,3 +223,3 @@ if (!timeoutHandle) {

}
exports.observeAsync = observeAsync;
exports.autorunAsync = autorunAsync;
function expr(expr, scope) {

@@ -234,33 +236,51 @@ if (!dnode_1.isComputingView())

function observableDecorator(target, key, baseDescriptor) {
var isDecoratingProperty = baseDescriptor && !baseDescriptor.hasOwnProperty("value");
var descriptor = baseDescriptor || {};
var baseValue = isDecoratingProperty ? descriptor.get : descriptor.value;
// - In typescript, observable annotations are invoked on the prototype, not on actual instances,
// so upon invocation, determine the 'this' instance, and define a property on the
// instance as well (that hides the propotype property)
// - In typescript, the baseDescriptor is empty for attributes without initial value
// - In babel, the initial value is passed as the closure baseDiscriptor.initializer'
var isDecoratingGetter = baseDescriptor && baseDescriptor.hasOwnProperty("get");
var descriptor = {};
var baseValue = undefined;
if (baseDescriptor) {
if (baseDescriptor.hasOwnProperty('get'))
baseValue = baseDescriptor.get;
else if (baseDescriptor.hasOwnProperty('value'))
baseValue = baseDescriptor.value;
else if (baseDescriptor.initializer) {
baseValue = baseDescriptor.initializer();
if (typeof baseValue === "function")
baseValue = asReference(baseValue);
}
}
if (!target || typeof target !== "object")
throw new Error("The @observable decorator can only be used on objects");
if (!isDecoratingProperty && typeof baseValue === "function")
throw new Error("@observable functions are not supported. Use @observable on a getter function if you want to create a view, or wrap the value in 'asReference' if you want to store a value (found on member '" + key + "').");
if (isDecoratingProperty) {
if (isDecoratingGetter) {
if (typeof baseValue !== "function")
throw new Error("@observable expects a getter function if used on a property (found on member '" + key + "').");
throw new Error("@observable expects a getter function if used on a property (in member: '" + key + "').");
if (descriptor.set)
throw new Error("@observable properties cannot have a setter (found on member '" + key + "').");
throw new Error("@observable properties cannot have a setter (in member: '" + key + "').");
if (baseValue.length !== 0)
throw new Error("@observable getter functions should not take arguments (found on member '" + key + "').");
throw new Error("@observable getter functions should not take arguments (in member: '" + key + "').");
}
descriptor.configurable = true;
descriptor.enumerable = true;
delete descriptor.value;
delete descriptor.writable;
descriptor.get = function () {
var baseStrict = strict;
strict = false;
observableobject_1.ObservableObject.asReactive(this, null, ValueMode.Recursive).set(key, baseValue);
strict = baseStrict;
return this[key];
};
descriptor.set = isDecoratingProperty
? observableview_1.throwingViewSetter
descriptor.set = isDecoratingGetter
? observableview_1.throwingViewSetter(key)
: function (value) {
observableobject_1.ObservableObject.asReactive(this, null, ValueMode.Recursive).set(key, value);
observableobject_1.ObservableObject.asReactive(this, null, ValueMode.Recursive).set(key, typeof value === "function" ? asReference(value) : value);
};
if (!isDecoratingProperty) {
if (!baseDescriptor) {
Object.defineProperty(target, key, descriptor);
}
else {
return descriptor;
}
}

@@ -282,11 +302,27 @@ function toJSON(source) {

exports.toJSON = toJSON;
function transaction(action) {
return scheduler_1.transaction(action);
function transaction(action, strict) {
return scheduler_1.transaction(action, strict);
}
exports.transaction = transaction;
exports.logLevel = 1;
exports.strict = true;
var logLevel = 1;
function getLogLevel() {
return logLevel;
}
exports.getLogLevel = getLogLevel;
function setLogLevel(newLogLevel) {
logLevel = newLogLevel;
}
exports.setLogLevel = setLogLevel;
var strict = true;
function getStrict() {
return strict;
}
exports.getStrict = getStrict;
function setStrict(newStrict) {
strict = newStrict;
}
exports.setStrict = setStrict;
setTimeout(function () {
if (exports.logLevel > 0)
console.info("Welcome to mobservable. Current logLevel = " + exports.logLevel + ". Change mobservable.logLevel according to your needs: 0 = production, 1 = development, 2 = debugging. Strict mode is " + (exports.strict ? 'enabled' : 'disabled') + ".");
if (logLevel > 0)
console.info("Welcome to mobservable. Current logLevel = " + logLevel + ". Change mobservable.logLevel according to your needs: 0 = production, 1 = development, 2 = debugging. Strict mode is " + (strict ? 'enabled' : 'disabled') + ".");
}, 1);

@@ -411,2 +447,4 @@ (function (ValueType) {

function assertUnwrapped(value, message) {
if (logLevel === 0)
return;
if (value instanceof AsReference || value instanceof AsStructure || value instanceof AsFlat)

@@ -416,4 +454,4 @@ throw new Error("[mobservable] asStructure / asReference / asFlat cannot be used here. " + message);

exports.assertUnwrapped = assertUnwrapped;
//# sourceMappingURL=core.js.map
/***/ },

@@ -439,3 +477,3 @@ /* 2 */

function checkIfStateIsBeingModifiedDuringView(context) {
if (isComputingView() && core_1.strict === true) {
if (core_1.getLogLevel() > 0 && isComputingView() && core_1.getStrict() === true) {
var ts = __mobservableViewStack;

@@ -580,5 +618,14 @@ throw new Error("[mobservable] It is not allowed to change the state during the computation of a reactive view if 'mobservable.strict' mode is enabled:\nShould the data you are trying to modify actually be a view?\nView name: " + context.name + ".\nCurrent stack size is " + ts.length + ", active view: \"" + ts[ts.length - 1].toString() + "\".");

extras_1.reportTransition(this, "PENDING");
var stateDidChange = this.compute();
this.bindDependencies();
this.markReady(stateDidChange);
var hasError = true;
try {
var stateDidChange = this.compute();
hasError = false;
}
finally {
if (hasError)
console.error("[mobservable.view '" + this.context.name + "'] There was an uncaught error during the computation of " + this.toString());
this.isComputing = false;
this.bindDependencies();
this.markReady(stateDidChange);
}
};

@@ -595,3 +642,3 @@ ViewNode.prototype.compute = function () {

__mobservableViewStack.length -= 1;
if (this.observing.length === 0 && core_1.logLevel > 1 && !this.isDisposed) {
if (this.observing.length === 0 && core_1.getLogLevel() > 1 && !this.isDisposed) {
console.error("[mobservable] You have created a view function that doesn't observe any values, did you forget to make its dependencies observable?");

@@ -604,3 +651,3 @@ }

var dependency = added[i];
if (dependency instanceof ViewNode && dependency.findCycle(this)) {
if (core_1.getLogLevel() > 0 && dependency instanceof ViewNode && dependency.findCycle(this)) {
this.hasCycle = true;

@@ -648,4 +695,4 @@ this.observing.splice(this.observing.indexOf(added[i]), 1);

var scheduler_1 = __webpack_require__(10);
//# sourceMappingURL=dnode.js.map
/***/ },

@@ -756,4 +803,4 @@ /* 3 */

exports.trackTransitions = trackTransitions;
//# sourceMappingURL=extras.js.map
/***/ },

@@ -818,4 +865,4 @@ /* 4 */

exports.ObservableObject = ObservableObject;
//# sourceMappingURL=observableobject.js.map
/***/ },

@@ -837,6 +884,7 @@ /* 5 */

var simpleeventemitter_1 = __webpack_require__(6);
var core_1 = __webpack_require__(1);
var utils_1 = __webpack_require__(7);
function throwingViewSetter() {
throw new Error("[mobservable.view '" + this.context.name + "'] View functions do not accept new values");
function throwingViewSetter(name) {
return function () {
throw new Error("[mobservable.view '" + name + "'] View functions do not accept new values");
};
}

@@ -852,3 +900,2 @@ exports.throwingViewSetter = throwingViewSetter;

this.isComputing = false;
this.hasError = false;
this.changeEvent = new simpleeventemitter_1.default();

@@ -874,32 +921,12 @@ }

throw new Error("[mobservable.view '" + this.context.name + "'] Cycle detected");
if (this.hasError) {
if (core_1.logLevel > 0)
console.error("[mobservable.view '" + this.context.name + "'] Rethrowing caught exception to observer: " + this._value + (this._value.cause || ''));
throw this._value;
}
return this._value;
};
ObservableView.prototype.set = function () {
throwingViewSetter.call(this);
throwingViewSetter(this.context.name)();
};
ObservableView.prototype.compute = function () {
var newValue;
try {
if (this.isComputing)
throw new Error("[mobservable.view '" + this.context.name + "'] Cycle detected");
this.isComputing = true;
newValue = this.func.call(this.scope);
this.hasError = false;
}
catch (e) {
this.hasError = true;
console.error("[mobservable.view '" + this.context.name + "'] Caught error during computation: ", e, "View function:", this.func.toString());
console.trace();
if (e instanceof Error)
newValue = e;
else {
newValue = new Error(("[mobservable.view '" + this.context.name + "'] Error during computation (see error.cause) in ") + this.func.toString());
newValue.cause = e;
}
}
if (this.isComputing)
throw new Error("[mobservable.view '" + this.context.name + "'] Cycle detected");
this.isComputing = true;
var newValue = this.func.call(this.scope);
this.isComputing = false;

@@ -933,3 +960,3 @@ var changed = this.compareStructural ? !utils_1.deepEquals(newValue, this._value) : newValue !== this._value;

get: function () { return _this.get(); },
set: throwingViewSetter
set: throwingViewSetter(this.context.name)
};

@@ -943,4 +970,4 @@ };

exports.ObservableView = ObservableView;
//# sourceMappingURL=observableview.js.map
/***/ },

@@ -993,4 +1020,4 @@ /* 6 */

exports.default = SimpleEventEmitter;
//# sourceMappingURL=simpleeventemitter.js.map
/***/ },

@@ -1107,4 +1134,4 @@ /* 7 */

exports.quickDiff = quickDiff;
//# sourceMappingURL=utils.js.map
/***/ },

@@ -1181,6 +1208,9 @@ /* 8 */

ObservableArray.prototype.updateLength = function (oldLength, delta) {
if (delta < 0)
if (delta < 0) {
dnode_1.checkIfStateIsBeingModifiedDuringView(this.$mobservable.context);
for (var i = oldLength + delta; i < oldLength; i++)
delete this[i];
}
else if (delta > 0) {
dnode_1.checkIfStateIsBeingModifiedDuringView(this.$mobservable.context);
if (oldLength + delta > OBSERVABLE_ARRAY_BUFFER_SIZE)

@@ -1214,4 +1244,4 @@ reserveArrayBuffer(oldLength + delta);

var lengthDelta = newItems.length - deleteCount;
this.updateLength(length, lengthDelta);
var res = (_a = this.$mobservable.values).splice.apply(_a, [index, deleteCount].concat(newItems));
this.updateLength(length, lengthDelta);
this.notifySplice(index, res, newItems);

@@ -1239,3 +1269,2 @@ return res;

ObservableArray.prototype.notifyChanged = function () {
dnode_1.checkIfStateIsBeingModifiedDuringView(this.$mobservable.context);
this.$mobservable.markStale();

@@ -1364,8 +1393,9 @@ this.$mobservable.markReady(true);

function createArrayBufferItem(index) {
var prop = {
enumerable: false,
configurable: false,
var prop = ENUMERABLE_PROPS[index] = {
enumerable: true,
configurable: true,
set: function (value) {
core_1.assertUnwrapped(value, "Modifiers cannot be used on array values. For non-reactive array values use makeReactive(asFlat(array)).");
if (index < this.$mobservable.values.length) {
dnode_1.checkIfStateIsBeingModifiedDuringView(this.$mobservable.context);
var oldValue = this.$mobservable.values[index];

@@ -1381,6 +1411,6 @@ var changed = this.$mobservable.mode === core_1.ValueMode.Structure ? !utils_1.deepEquals(oldValue, value) : oldValue !== value;

else
throw new Error("[mobservable.array] Index out of bounds, " + index + " is larger than " + this.values.length);
throw new Error("[mobservable.array] Index out of bounds, " + index + " is larger than " + this.$mobservable.values.length);
},
get: function () {
if (index < this.$mobservable.values.length) {
if (this.$mobservable && index < this.$mobservable.values.length) {
this.$mobservable.notifyObserved();

@@ -1392,6 +1422,8 @@ return this.$mobservable.values[index];

};
Object.defineProperty(ObservableArray.prototype, "" + index, prop);
prop.enumerable = true;
prop.configurable = true;
ENUMERABLE_PROPS[index] = prop;
Object.defineProperty(ObservableArray.prototype, "" + index, {
enumerable: false,
configurable: true,
get: prop.get,
set: prop.set
});
}

@@ -1404,4 +1436,4 @@ function reserveArrayBuffer(max) {

reserveArrayBuffer(1000);
//# sourceMappingURL=observablearray.js.map
/***/ },

@@ -1472,8 +1504,9 @@ /* 9 */

exports.ObservableValue = ObservableValue;
//# sourceMappingURL=observablevalue.js.map
/***/ },
/* 10 */
/***/ function(module, exports) {
/***/ function(module, exports, __webpack_require__) {
var core = __webpack_require__(1);
var inBatch = 0;

@@ -1502,4 +1535,7 @@ var tasks = [];

}
function transaction(action) {
function transaction(action, strict) {
var preStrict = core.getStrict();
inBatch += 1;
if (strict !== undefined)
core.setStrict(strict);
try {

@@ -1514,7 +1550,8 @@ return action();

}
core.setStrict(preStrict);
}
}
exports.transaction = transaction;
//# sourceMappingURL=scheduler.js.map
/***/ },

@@ -1524,3 +1561,3 @@ /* 11 */

//# sourceMappingURL=interfaces.js.map

@@ -1527,0 +1564,0 @@ /***/ }

@@ -13,11 +13,13 @@ import { Lambda, IObservableArray, IObservableValue, IContextInfoStruct } from './interfaces';

export declare function isObservable(value: any): boolean;
export declare function observe(view: Lambda, scope?: any): Lambda;
export declare function observeUntil(predicate: () => boolean, effect: Lambda, scope?: any): Lambda;
export declare function observeAsync<T>(view: () => T, effect: (latestValue: T) => void, delay?: number, scope?: any): Lambda;
export declare function autorun(view: Lambda, scope?: any): Lambda;
export declare function autorunUntil(predicate: () => boolean, effect: Lambda, scope?: any): Lambda;
export declare function autorunAsync<T>(view: () => T, effect: (latestValue: T) => void, delay?: number, scope?: any): Lambda;
export declare function expr<T>(expr: () => T, scope?: any): T;
export declare function extendObservable<A extends Object, B extends Object>(target: A, properties: B, context?: IContextInfoStruct): A & B;
export declare function toJSON(source: any): any;
export declare function transaction<T>(action: () => T): T;
export declare var logLevel: number;
export declare var strict: boolean;
export declare function transaction<T>(action: () => T, strict?: boolean): T;
export declare function getLogLevel(): number;
export declare function setLogLevel(newLogLevel: any): void;
export declare function getStrict(): boolean;
export declare function setStrict(newStrict: any): void;
export declare enum ValueType {

@@ -24,0 +26,0 @@ Reference = 0,

@@ -1,8 +0,7 @@

import * as core from './core';
import { IDependencyTree, IObserverTree, ITransitionEvent, Lambda } from './interfaces';
import SimpleEventEmitter from './simpleeventemitter';
export * from './interfaces';
declare var _default: typeof core.observable;
export default _default;
export { isObservable, observable, extendObservable, asReference, asFlat, asStructure, observe, observeUntil, observeAsync, expr, transaction, toJSON, logLevel, strict } from './core';
export { isObservable, observable, extendObservable, asReference, asFlat, asStructure, autorun, autorunUntil, autorunAsync, expr, transaction, toJSON, isObservable as isReactive, observable as makeReactive, extendObservable as extendReactive, autorun as observe, autorunUntil as observeUntil, autorunAsync as observeAsync } from './core';
export declare var strict: boolean;
export declare var logLevel: string;
export declare const _: {

@@ -9,0 +8,0 @@ isComputingView: () => boolean;

import { ViewNode } from './dnode';
import SimpleEventEmitter from './simpleeventemitter';
import { IContextInfoStruct, Lambda } from './interfaces';
export declare function throwingViewSetter(): void;
export declare function throwingViewSetter(name: any): Lambda;
export declare class ObservableView<T> extends ViewNode {

@@ -10,3 +10,2 @@ protected func: () => T;

private isComputing;
private hasError;
protected _value: T;

@@ -13,0 +12,0 @@ protected changeEvent: SimpleEventEmitter;

import { Lambda } from './interfaces';
export declare function schedule(func: Lambda): void;
export declare function transaction<T>(action: () => T): T;
export declare function transaction<T>(action: () => T, strict?: boolean): T;
{
"name": "mobservable",
"version": "1.0.0-alpha.4",
"description": "Observable data. Observing views.",
"version": "1.0.0-alpha.5",
"description": "Observable data. Reactive functions. No staleness.",
"main": "dist/mobservable.js",

@@ -12,3 +12,5 @@ "typings": "dist/typings/index",

"build": "rm -rf dist && mkdir -p dist/typings && ./node_modules/typescript/bin/tsc && NODE_ENV=production ./node_modules/webpack/bin/webpack.js && cp -r .build/*.d.ts dist/typings",
"buildtest": "cd test && ../node_modules/typescript/bin/tsc && cd ..",
"buildtest": "npm run build-typescript-tests && npm run build-babel-tests",
"build-typescript-tests": "./node_modules/typescript/bin/tsc -m commonjs -t es5 --experimentalDecorators --outDir test test/typescript/typescript-test.ts",
"build-babel-tests": "./node_modules/babel/bin/babel.js --stage 0 test/babel/babel-test.js -o test/babel-test.js",
"perf": "npm run build && ./node_modules/nodeunit/bin/nodeunit test/perf/*.js"

@@ -27,2 +29,3 @@ },

"devDependencies": {
"babel": "^5.8.23",
"coveralls": "^2.11.4",

@@ -29,0 +32,0 @@ "istanbul": "^0.3.21",

@@ -5,3 +5,3 @@ # mobservable

##### _Keeps views automatically in sync with state. Unobtrusively._
##### _Makes data observable. And functions reactive. Whut? Like Excel._

@@ -13,7 +13,8 @@ [![Build Status](https://travis-ci.org/mweststrate/mobservable.svg?branch=master)](https://travis-ci.org/mweststrate/mobservable)

<br/>
### New to Mobservable? Take the [five minute, interactive introduction](https://mweststrate.github.io/mobservable/getting-started.html)
New to Mobservable? Take the [five minute, interactive introduction](https://mweststrate.github.io/mobservable/getting-started.html)
## Introduction
Mobservable is a library to create reactive state and views. Mobservable updates views automatically when the state changes, and thereby achieves [inversion of control](https://en.wikipedia.org/wiki/Inversion_of_control). This has major benefits for the simplicity, maintainability and performance of your code. This is the promise of Mobservable:
Mobservable enables your data structures to become observable.
Next to that it can make your functions reactive, so that they re-evaluate whenever relevant data is altered. This has major benefits for the simplicity, maintainability and performance of your code. This is the promise of Mobservable:
* Write complex applications which unmatched simple code.

@@ -31,5 +32,5 @@ * Enable unobtrusive state management: be free to use mutable objects, cyclic references, classes and real references to store state.

```javascript
var timerData = {
var timerData = mobservable.observable({
secondsPassed: 0
};
});

@@ -40,7 +41,7 @@ setInterval(function() {

var Timer = React.createClass({
var Timer = mobservable.observer(React.createClass({
render: function() {
return (<span>Seconds passed: { this.props.timerData.secondsPassed } </span> )
}
});
}));

@@ -96,16 +97,2 @@ React.render(<Timer timerData={timerData} />, document.body);

## What others are saying...
> _Elegant! I love it!_
> &dash; Johan den Haan, CTO of Mendix
> _We ported the book Notes and Kanban examples to Mobservable. Check out [the source](https://github.com/survivejs/mobservable-demo) to see how this worked out. Compared to the original I was definitely positively surprised. Mobservable seems like a good fit for these problems._
> &dash; Juho Vepsäläinen, author of "SurviveJS - Webpack and React" and jster.net curator
> _Great job with Mobservable! Really gives current conventions and libraries a run for their money._
> &dash; Daniel Dunderfelt
> _I was reluctant to abandon immutable data and the PureRenderMixin, but I no longer have any reservations. I can't think of any reason not to do things the simple, elegant way you have demonstrated._
> &dash;David Schalk, fpcomplete.com
## Resources

@@ -132,5 +119,7 @@

Mobservable is inspired by Microsoft Excel and existing TFRP implementations like MeteorJS tracker, knockout and Vue.js.
## Top level api
For the full api, see the [API documentation](https://mweststrate.github.io/mobservable/refguide/api.html).
For the full api, see the [API documentation](https://mweststrate.github.io/mobservable/refguide/observable.html).
This is an overview of most important functions available in the `mobservable` namespace:

@@ -151,20 +140,21 @@

## Runtime behavior
* Reactive views always update synchronously (unless `transaction is used`)
* Reactive views always update atomically, intermediate values will never be visible.
* Reactive functions evaluate lazily and are not processed if they aren't observed.
* Dependency detection is based on actual values to real-time minify the amount of dependencies.
* Cycles are detected automatically.
* Exceptions during computations are propagated to consumers.
## Roadmap
## What others are saying...
* Write documentation, including how to organize projects
* Write blog about inner workings
* Introduce options for asynchronous views and structurally compare view results
> _Elegant! I love it!_
> &dash; Johan den Haan, CTO of Mendix
> _We ported the book Notes and Kanban examples to Mobservable. Check out [the source](https://github.com/survivejs/mobservable-demo) to see how this worked out. Compared to the original I was definitely positively surprised. Mobservable seems like a good fit for these problems._
> &dash; Juho Vepsäläinen, author of "SurviveJS - Webpack and React" and jster.net curator
> _Great job with Mobservable! Really gives current conventions and libraries a run for their money._
> &dash; Daniel Dunderfelt
> _I was reluctant to abandon immutable data and the PureRenderMixin, but I no longer have any reservations. I can't think of any reason not to do things the simple, elegant way you have demonstrated._
> &dash;David Schalk, fpcomplete.com
## Contributing
* Feel free to send pr requests.
* Use `npm start` to run the basic test suite, `npm test` for the test suite with coverage and `npm run perf` for the performance tests.
* Use `npm start` to run the basic test suite, `npm test` for the test suite with coverage and `npm run perf` for the performance tests.
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc