├── lib └── versions.txt ├── .travis.yml ├── docs └── 20140703-freaklies │ ├── freaklies.pdf │ ├── Makefile │ ├── figures │ ├── Makefile │ ├── mpxerr.tex │ ├── data-events.mp │ ├── macros.mp │ ├── data-events.svg │ └── data-events.mps │ └── freaklies.tex ├── examples ├── autocomplete │ ├── autocomplete.css │ ├── autocomplete-rxjs.html │ ├── autocomplete.html │ ├── autocomplete-rxjs.js │ └── autocomplete.js ├── suggestions │ ├── Makefile │ ├── style.css │ ├── index.html │ ├── README.md │ └── suggestions.js └── counter │ ├── Makefile │ ├── style.css │ ├── bacon.html │ ├── rxjs.html │ ├── index.html │ ├── bacon-bus.html │ ├── rxjs-behavior.html │ ├── counter-bacon-bus.js │ ├── counter-rxjs.js │ ├── counter-rxjs-behavior.js │ ├── counter.js │ ├── README.md │ ├── counter-bacon.js │ └── bacon.md ├── .npmignore ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── benchmark ├── Makefile ├── README.md └── diamond.js ├── test ├── egal-test.js ├── corner-test.js ├── option-test.js ├── menrva-test.js ├── signal-test.js ├── convenience-test.js ├── lens-test.js └── transaction-test.js ├── .jshintrc ├── .jshintrc.examples ├── src ├── egal.js ├── util.js ├── lens.js ├── option.js ├── menrva.js ├── convenience.js ├── signal.js └── transaction.js ├── scripts └── version.js ├── LICENSE ├── package.json ├── Makefile ├── README.md └── dist ├── menrva.min.js ├── menrva.min.js.map └── menrva.standalone.js /lib/versions.txt: -------------------------------------------------------------------------------- 1 | jquery.js 2.1.1 2 | Bacon.js 0.7.16 3 | rx.lite.js 2.2.25 4 | lodash.js 2.4.1 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | - "0.11" 5 | script: make test coveralls 6 | -------------------------------------------------------------------------------- /docs/20140703-freaklies/freaklies.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phadej/menrva/HEAD/docs/20140703-freaklies/freaklies.pdf -------------------------------------------------------------------------------- /examples/autocomplete/autocomplete.css: -------------------------------------------------------------------------------- 1 | * { 2 | font-family: Helvetica, Arial, Sans-Serif; 3 | font-size: 13px; 4 | } 5 | -------------------------------------------------------------------------------- /docs/20140703-freaklies/Makefile: -------------------------------------------------------------------------------- 1 | all : freaklies.pdf 2 | 3 | freaklies.pdf : freaklies.tex 4 | make -C figures 5 | pdflatex freaklies.tex 6 | -------------------------------------------------------------------------------- /examples/suggestions/Makefile: -------------------------------------------------------------------------------- 1 | LJS=../../node_modules/.bin/ljs 2 | 3 | README.md : suggestions.js 4 | $(LJS) -o README.md suggestions.js 5 | 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .jshintrc 2 | .travis.yml 3 | .npmignore 4 | 5 | Makefile 6 | 7 | dist/ 8 | coverage/ 9 | benchmark/ 10 | test/ 11 | examples/ 12 | docs/ 13 | scripts/ 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | 4 | # tex auxillary files 5 | *.aux 6 | *.log 7 | *.out 8 | *.toc 9 | 10 | # metapos 11 | *.mpx 12 | 13 | # benchmark results 14 | benchmark/*.txt 15 | -------------------------------------------------------------------------------- /examples/counter/Makefile: -------------------------------------------------------------------------------- 1 | LJS=../../node_modules/.bin/ljs 2 | 3 | all : README.md bacon.md 4 | 5 | README.md : counter.js 6 | $(LJS) -o README.md counter.js 7 | 8 | bacon.md : counter-bacon.js 9 | $(LJS) -o bacon.md counter-bacon.js 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | - **0.0.7** `sequence` and `record` 2 | - **0.0.6** Convenience methods 3 | - **0.0.5** Lens 4 | - **0.0.4** Internal improvements 5 | - **0.0.3** Slight changes in API 6 | - **0.0.2** Basic data-flow functionality 7 | - **0.0.1** Name reservation 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | In lieu of a formal styleguide, take care to maintain the existing coding style. 2 | 3 | Add tests for any new or changed functionality. 100% coverage isn't hard. Semantic coverage is important too though. 4 | 5 | Note: `README.md` is autogenerated file. 6 | -------------------------------------------------------------------------------- /benchmark/Makefile: -------------------------------------------------------------------------------- 1 | BENCHMARKS=diamond 2 | 3 | .PHONY : all quick full clean 4 | 5 | all : quick 6 | 7 | quick : $(BENCHMARKS:%=%.quick.txt) 8 | 9 | full : $(BENCHMARKS:%=%.txt) 10 | 11 | clean : 12 | rm -f $(BENCHMARKS:%=%.txt) $(BENCHMARKS:%=%.quick.txt) 13 | 14 | %.txt : %.js 15 | node $< | tee $@ 16 | 17 | %.quick.txt : %.js 18 | node $< --quick | tee $@ 19 | -------------------------------------------------------------------------------- /test/egal-test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var menrva = require("../src/menrva.js"); 4 | var assert = require("assert"); 5 | 6 | describe("egal", function () { 7 | it("consider nan's as equal", function () { 8 | assert(menrva.egal(NaN, NaN)); 9 | }); 10 | 11 | it("consider +0 and -0 non-equal", function () { 12 | assert(!menrva.egal(+0, -0)); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "quotmark": "double", 3 | "camelcase": true, 4 | "shadow": "outer", 5 | "trailing": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "latedef": "nofunc", 10 | "newcap": true, 11 | "noarg": true, 12 | "sub": true, 13 | "undef": true, 14 | "unused": "vars", 15 | "boss": true, 16 | "eqnull": true, 17 | "node": true 18 | } 19 | -------------------------------------------------------------------------------- /docs/20140703-freaklies/figures/Makefile: -------------------------------------------------------------------------------- 1 | FIGURES:=data-events 2 | 3 | all : $(FIGURES:%=%.mps) $(FIGURES:%=%.svg) 4 | 5 | MPOST=mpost 6 | SVGPARAMS=-s outputformat='"svg"' -s outputtemplate='"%j.svg"' 7 | MPSPARAMS=-s outputformat='"mps"' -s outputtemplate='"%j.mps"' 8 | 9 | %.mps : %.mp macros.mp 10 | $(MPOST) $(MPSPARAMS) $< 11 | 12 | %.svg : %.mp macros.mp 13 | $(MPOST) $(SVGPARAMS) $< 14 | -------------------------------------------------------------------------------- /.jshintrc.examples: -------------------------------------------------------------------------------- 1 | { 2 | "quotmark": "double", 3 | "camelcase": true, 4 | "shadow": "outer", 5 | "trailing": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "latedef": "nofunc", 10 | "newcap": true, 11 | "noarg": true, 12 | "sub": true, 13 | "undef": true, 14 | "unused": "vars", 15 | "boss": true, 16 | "eqnull": true, 17 | "browser": true, 18 | "globals": { 19 | "$": true, 20 | "menrva": true, 21 | "Bacon": true, 22 | "Rx": true, 23 | "_": true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /docs/20140703-freaklies/figures/mpxerr.tex: -------------------------------------------------------------------------------- 1 | %&latex 2 | 3 | \gdef\mpxshipout{\shipout\hbox\bgroup 4 | \setbox0=\hbox\bgroup} 5 | \gdef\stopmpxshipout{\egroup \dimen0=\ht0 \advance\dimen0\dp0 6 | \dimen1=\ht0 \dimen2=\dp0 7 | \setbox0=\hbox\bgroup 8 | \box0 9 | \ifnum\dimen0>0 \vrule width1sp height\dimen1 depth\dimen2 10 | \else \vrule width1sp height1sp depth0sp\relax 11 | \fi\egroup 12 | \ht0=0pt \dp0=0pt \box0 \egroup} 13 | \mpxshipout% line 19 ./mvc.mp 14 | \textbf{MODEL}% 15 | \stopmpxshipout 16 | \end{document} 17 | -------------------------------------------------------------------------------- /docs/20140703-freaklies/figures/data-events.mp: -------------------------------------------------------------------------------- 1 | input macros; 2 | 3 | verbatimtex 4 | %&latex 5 | \documentclass{article} 6 | \begin{document} 7 | etex 8 | 9 | dim = 1.5cm; 10 | 11 | beginfig(1); 12 | 13 | path p; 14 | p = (-dim,0)..(0,-dim)..(dim,0); 15 | fill p{up}..(0,0){-1,-2}..{up}cycle; 16 | draw p..(0,dim)..cycle; 17 | 18 | draw thelabel(btex \textbf{data} etex, (-.5dim, 0)) withcolor .0white; 19 | draw thelabel(btex \textbf{events} etex, (.5dim, 0)) withcolor white; 20 | 21 | endfig; 22 | 23 | verbatimtex 24 | \end{document} 25 | etex 26 | end 27 | -------------------------------------------------------------------------------- /src/egal.js: -------------------------------------------------------------------------------- 1 | /* 2 | * menrva 3 | * https://github.com/phadej/menrva 4 | * 5 | * Copyright (c) 2014 Oleg Grenrus 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | "use strict"; 10 | 11 | /** 12 | ### Equalities 13 | 14 | #### egal 15 | 16 | > egal (a, b) : boolean 17 | 18 | Identity check. `Object.is`. http://wiki.ecmascript.org/doku.php?id=harmony:egal 19 | */ 20 | function egal(a, b) { 21 | if (a === 0 && b === 0) { 22 | return 1/a === 1/b; 23 | } else if (a !== a) { 24 | return b !== b; 25 | } else { 26 | return a === b; 27 | } 28 | } 29 | 30 | module.exports = egal; 31 | -------------------------------------------------------------------------------- /examples/counter/style.css: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 200px; 3 | box-sizing: border-box; 4 | padding: 5px; 5 | border: 1px solid #eee; 6 | text-align: center; 7 | } 8 | .container div { 9 | padding: 5px; 10 | } 11 | #counter { 12 | border-bottom: 1px solid #ccc; 13 | font-weight: bold; 14 | font-family: sans-serif; 15 | font-size: 20px; 16 | } 17 | button { 18 | background-color: #369; 19 | color: white; 20 | border: none; 21 | border-radius: 5px; 22 | font-size: 20px; 23 | } 24 | button:active { 25 | background-color: #69c; 26 | } 27 | button:disabled { 28 | background-color: #999; 29 | } 30 | -------------------------------------------------------------------------------- /examples/suggestions/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: sans-serif; 3 | padding: 10px; 4 | } 5 | h2 { 6 | font-weight: bold; 7 | display: inline-block; 8 | } 9 | .refresh { 10 | font-size: 80%; 11 | margin-left: 10px; 12 | } 13 | .header { 14 | background: #ECECEC; 15 | padding: 5px; 16 | } 17 | .suggestions { 18 | border: 2px solid #ECECEC; 19 | } 20 | li { 21 | padding: 5px; 22 | } 23 | li img { 24 | width: 40px; 25 | height: 40px; 26 | border-radius: 20px; 27 | } 28 | li .username, li .close { 29 | display: inline-block; 30 | position: relative; 31 | bottom: 15px; 32 | left: 5px; 33 | } 34 | -------------------------------------------------------------------------------- /examples/counter/bacon.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | – 13 |
14 |
15 | 16 | 17 |
18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /examples/counter/rxjs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | – 13 |
14 |
15 | 16 | 17 |
18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /examples/autocomplete/autocomplete-rxjs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Autocomplete Example 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

Wikipedia search:

13 |
14 | Start typing: 15 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/autocomplete/autocomplete.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Autocomplete Example 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

Wikipedia search:

13 |
14 | Start typing: 15 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/counter/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | – 13 |
14 |
15 | 16 | 17 |
18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /examples/counter/bacon-bus.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | – 13 |
14 |
15 | 16 | 17 |
18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /examples/counter/rxjs-behavior.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | – 13 |
14 |
15 | 16 | 17 |
18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /benchmark/README.md: -------------------------------------------------------------------------------- 1 | # Benchmark results 2 | 3 | ## 2014-07-07 4 | 5 | ``` 6 | Bacon.js x 170 ops/sec ±2.06% (343 runs sampled) 7 | Bacon.js Bus→Property x 169 ops/sec ±2.39% (359 runs sampled) 8 | Bacon.js Model x 153 ops/sec ±2.04% (339 runs sampled) 9 | Rx.JS Cold x 1,100 ops/sec ±2.70% (337 runs sampled) 10 | menrva x 1,994 ops/sec ±1.46% (379 runs sampled) 11 | ``` 12 | 13 | ## 2014-07-26 14 | 15 | ``` 16 | Bacon.js x 103 ops/sec ±2.22% (342 runs sampled) 17 | Bacon.js Bus→Property x 107 ops/sec ±1.29% (344 runs sampled) 18 | Bacon.js Model x 160 ops/sec ±1.61% (347 runs sampled) 19 | Rx.JS Cold x 1,103 ops/sec ±2.02% (338 runs sampled) 20 | menrva x 1,867 ops/sec ±1.11% (365 runs sampled) 21 | ``` 22 | -------------------------------------------------------------------------------- /examples/counter/counter-bacon-bus.js: -------------------------------------------------------------------------------- 1 | /** 2 | Inspired by [simple counter](http://baconjs.github.io/) 3 | */ 4 | $(function () { 5 | "use strict"; 6 | 7 | var MIN_VALUE = 0; 8 | 9 | var $valueBus = new Bacon.Bus(); 10 | var $value = $valueBus.toProperty(0); 11 | 12 | $value.assign($("#counter"), "text"); 13 | 14 | $value.onValue(function (x) { 15 | var down = $("#down"); 16 | if (x <= MIN_VALUE) { 17 | down.attr("disabled", "disabled"); 18 | } else { 19 | down.removeAttr("disabled"); 20 | } 21 | }); 22 | 23 | var $up = $("#up").asEventStream("click"); 24 | var $down = $("#down").asEventStream("click"); 25 | 26 | var $events = $up.map(1).merge($down.map(-1)); 27 | 28 | var $valueState = $events.scan(0, function(x,y) { 29 | return Math.max(MIN_VALUE, x + y); 30 | }); 31 | 32 | $valueBus.plug($valueState); 33 | }); 34 | -------------------------------------------------------------------------------- /examples/counter/counter-rxjs.js: -------------------------------------------------------------------------------- 1 | /** 2 | Inspired by [simple counter](http://baconjs.github.io/) 3 | */ 4 | $(function () { 5 | "use strict"; 6 | 7 | var MIN_VALUE = 0; 8 | 9 | var upButton = $("#up"); 10 | var downButton = $("#down"); 11 | var counterEl = $("#counter"); 12 | 13 | var $up = Rx.Observable.fromEvent(upButton[0], "click"); 14 | var $down = Rx.Observable.fromEvent(downButton[0], "click"); 15 | 16 | var $counter = 17 | // map up to 1, down to -1 18 | $up.map(function () { return 1; }).merge($down.map(function () { return -1; })) 19 | .scan(0, function(x,y) { return Math.max(MIN_VALUE, x + y); }) 20 | .startWith(0); 21 | 22 | $counter.subscribe(function (x) { 23 | counterEl.text(x); 24 | 25 | if (x <= MIN_VALUE) { 26 | downButton.attr("disabled", "disabled"); 27 | } else { 28 | downButton.removeAttr("disabled"); 29 | } 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/corner-test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var menrva = require("../src/menrva.js"); 4 | var assert = require("assert"); 5 | var _ = require("lodash"); 6 | var chai = require("chai"); 7 | 8 | describe("corner cases", function () { 9 | it("combine with itself", function () { 10 | var a = menrva.source(1); 11 | var combineCount = 0; 12 | var b = menrva.combine(a, a, a, function (x, y, z) { 13 | combineCount += 1; 14 | return x + y + z; 15 | }); 16 | var onValueCount = 0; 17 | b.onValue(function () { 18 | onValueCount += 1; 19 | }) 20 | 21 | chai.expect(b.value()).to.equal(3); 22 | chai.expect(combineCount).to.equal(1); 23 | chai.expect(onValueCount).to.equal(1); 24 | 25 | var tx = menrva.transaction(); 26 | a.set(tx, 2); 27 | tx.commit(); 28 | 29 | chai.expect(b.value()).to.equal(6); 30 | chai.expect(combineCount).to.equal(2); 31 | chai.expect(onValueCount).to.equal(2); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /scripts/version.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var fs = require("fs"); 4 | var jsstana = require("jsstana"); 5 | var esprima = require("esprima"); 6 | var estraverse = require("estraverse"); 7 | 8 | var pkg = JSON.parse(fs.readFileSync(__dirname + "/../package.json").toString()); 9 | var version = pkg.version; 10 | 11 | console.log("package.json version:", version); 12 | 13 | var filename = __dirname + "/../src/menrva.js"; 14 | var contents = fs.readFileSync(filename).toString(); 15 | var syntax = esprima.parse(contents, { loc: true, range: true }); 16 | 17 | var versionLocation; 18 | 19 | estraverse.traverse(syntax, { 20 | enter: function (node) { 21 | var m = jsstana.match("(var version (?value string))", node); 22 | if (m) { 23 | versionLocation = m.value.range; 24 | } 25 | }, 26 | }); 27 | 28 | if (versionLocation) { 29 | contents = contents.substr(0, versionLocation[0]) + "\"" + version + "\"" + contents.substr(versionLocation[1]); 30 | 31 | fs.writeFileSync(filename, contents); 32 | } 33 | -------------------------------------------------------------------------------- /examples/counter/counter-rxjs-behavior.js: -------------------------------------------------------------------------------- 1 | /** 2 | Inspired by [simple counter](http://baconjs.github.io/) 3 | */ 4 | $(function () { 5 | "use strict"; 6 | 7 | var MIN_VALUE = 0; 8 | 9 | var upButton = $("#up"); 10 | var downButton = $("#down"); 11 | var counterEl = $("#counter"); 12 | 13 | var $value = new Rx.BehaviorSubject(0); 14 | 15 | $value.subscribe(function (x) { 16 | counterEl.text(x); 17 | 18 | if (x <= MIN_VALUE) { 19 | downButton.attr("disabled", "disabled"); 20 | } else { 21 | downButton.removeAttr("disabled"); 22 | } 23 | }); 24 | 25 | var $up = Rx.Observable.fromEvent(upButton[0], "click"); 26 | var $down = Rx.Observable.fromEvent(downButton[0], "click"); 27 | 28 | var $state = 29 | // map up to 1, down to -1 30 | $up.map(function () { return 1; }).merge($down.map(function () { return -1; })) 31 | .scan(0, function(x,y) { return Math.max(MIN_VALUE, x + y); }) 32 | .startWith(0); 33 | 34 | $state.subscribe(function (value) { 35 | $value.onNext(value); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /docs/20140703-freaklies/figures/macros.mp: -------------------------------------------------------------------------------- 1 | prologues := 3; 2 | 3 | dimX=2.5cm; 4 | dimY=-1.5cm; 5 | 6 | dimC=2pt; 7 | 8 | vardef clippathp(expr p, a, b) = 9 | save s, t, r; 10 | 11 | numeric s; 12 | numeric t; 13 | path r; 14 | 15 | (whatever, t1) = a intersectiontimes p; 16 | (whatever, t2) = b intersectiontimes (reverse p); 17 | 18 | subpath (t1, length(p) - t2) of p 19 | enddef; 20 | 21 | vardef clippath(expr p, a, b) = 22 | clippathp(p, bbox(a), bbox(b)) 23 | enddef; 24 | 25 | def midpoint(expr p) = point 0.5 of p enddef; 26 | 27 | vardef largerbbox(expr p) = 28 | save q, z; 29 | path q; 30 | pair z[]; 31 | 32 | q = bbox p; 33 | 34 | z1 = ulcorner q shifted (-3mm, 1mm); 35 | z2 = urcorner q shifted (3mm, 1mm); 36 | z3 = lrcorner q shifted (3mm, -1mm); 37 | z4 = llcorner q shifted (-3mm, -1mm); 38 | 39 | z1--z2--z3--z4--cycle 40 | enddef; 41 | 42 | color greenColor, blueColor, redColor, orangeColor, greyColor; 43 | 44 | greenColor := (0, 0.5, 0); 45 | blueColor := (0, 0, 0.5); 46 | redColor := (0.5, 0, 0); 47 | orangeColor := (1, 0.5, 0); 48 | greyColor := (0.5, 0.5, 0.5); 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Oleg Grenrus 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /examples/counter/counter.js: -------------------------------------------------------------------------------- 1 | /** 2 | Inspired by [simple counter](http://baconjs.github.io/) 3 | 4 | Implemented in *menrva*, [Bacon.js](https://github.com/baconjs/bacon.js) and [RxJS](https://github.com/Reactive-Extensions/RxJS). 5 | 6 | Bacon.js and RxJS variants, come in two flavours, *direct* and *data-flowish*. 7 | */ 8 | $(function () { 9 | "use strict"; 10 | 11 | var MIN_VALUE = 0; 12 | 13 | var $value = menrva.source(0); 14 | 15 | $value.onValue(function (x) { 16 | $("#counter").text(x); 17 | }); 18 | 19 | $value.onValue(function (x) { 20 | var down = $("#down"); 21 | if (x <= MIN_VALUE) { 22 | down.attr("disabled", "disabled"); 23 | } else { 24 | down.removeAttr("disabled"); 25 | } 26 | }); 27 | 28 | function inc(x) { 29 | return x + 1; 30 | } 31 | 32 | function dec(x) { 33 | return Math.max(MIN_VALUE, x - 1); 34 | } 35 | 36 | $("#up").click(function () { 37 | var tx = menrva.transaction(); 38 | $value.modify(tx, inc); 39 | tx.commit(); 40 | }); 41 | 42 | $("#down").click(function () { 43 | var tx = menrva.transaction(); 44 | $value.modify(tx, dec); 45 | tx.commit(); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /examples/counter/README.md: -------------------------------------------------------------------------------- 1 | Inspired by [simple counter](http://baconjs.github.io/) 2 | 3 | Implemented in *menrva*, [Bacon.js](https://github.com/baconjs/bacon.js) and [RxJS](https://github.com/Reactive-Extensions/RxJS). 4 | 5 | Bacon.js and RxJS variants, come in two flavours, *direct* and *data-flowish*. 6 | 7 | 8 | ```js 9 | $(function () { 10 | "use strict"; 11 | 12 | var MIN_VALUE = 0; 13 | 14 | var $value = menrva.source(0); 15 | 16 | $value.onValue(function (x) { 17 | $("#counter").text(x); 18 | }); 19 | 20 | $value.onValue(function (x) { 21 | var down = $("#down"); 22 | if (x <= MIN_VALUE) { 23 | down.attr("disabled", "disabled"); 24 | } else { 25 | down.removeAttr("disabled"); 26 | } 27 | }) 28 | 29 | function inc(x) { 30 | return x + 1; 31 | } 32 | 33 | function dec(x) { 34 | return Math.max(MIN_VALUE, x - 1); 35 | } 36 | 37 | $("#up").click(function () { 38 | var tx = menrva.transaction(); 39 | $value.modify(tx, inc); 40 | tx.commit(); 41 | }); 42 | 43 | $("#down").click(function () { 44 | var tx = menrva.transaction(); 45 | $value.modify(tx, dec); 46 | tx.commit(); 47 | }); 48 | }); 49 | ``` 50 | -------------------------------------------------------------------------------- /examples/suggestions/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |

Who to follow

Refresh 13 |
14 | 31 |
32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "menrva", 3 | "description": "Ambitious data-flow library", 4 | "version": "0.0.7", 5 | "homepage": "https://github.com/phadej/menrva", 6 | "author": { 7 | "name": "Oleg Grenrus", 8 | "email": "oleg.grenrus@iki.fi", 9 | "url": "http://oleg.fi" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git://github.com/phadej/menrva.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/phadej/menrva/issues" 17 | }, 18 | "licenses": [ 19 | { 20 | "type": "MIT", 21 | "url": "https://github.com/phadej/menrva/blob/master/LICENSE" 22 | } 23 | ], 24 | "main": "src/menrva.js", 25 | "engines": { 26 | "node": ">= 0.10.0" 27 | }, 28 | "scripts": { 29 | "test": "make test" 30 | }, 31 | "devDependencies": { 32 | "uglify-js": "~2.4.14", 33 | "typify-bin": "0.0.5", 34 | "jshint": "~2.5.1", 35 | "istanbul": "~0.3.0", 36 | "mocha": "~1.21.0", 37 | "browserify": "~5.3.0", 38 | "ljs": "~0.2.3", 39 | "coveralls": "~2.11.0", 40 | "lodash": "~2.4.1", 41 | "chai": "~1.9.1", 42 | "benchmark": "~1.0.0", 43 | "rx": "2.3.0", 44 | "baconjs": "0.7.34", 45 | "bacon.model": "0.1.10", 46 | "jsstana": "0.0.21", 47 | "estraverse": "~1.5.0", 48 | "esprima": "~1.2.2", 49 | "sinon": "~1.10.2" 50 | }, 51 | "keywords": [ 52 | "data", 53 | "flow", 54 | "reactive" 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /test/option-test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var menrva = require("../src/menrva.js"); 4 | var assert = require("assert"); 5 | var _ = require("lodash"); 6 | 7 | describe("Option", function () { 8 | describe("map", function () { 9 | describe("is a functor map", function () { 10 | it("— identity law", function () { 11 | var values = [ 12 | menrva.some(1), 13 | menrva.some(2), 14 | menrva.none, 15 | ]; 16 | 17 | _.each(values, function (v) { 18 | assert(v.map(_.identity).equals(v)); 19 | }); 20 | }); 21 | 22 | it("— composition law", function () { 23 | function f(x) { 24 | return x + 1; 25 | } 26 | 27 | function g(x) { 28 | return x * 2; 29 | } 30 | 31 | var values = [ 32 | menrva.some(1), 33 | menrva.some(2), 34 | menrva.none, 35 | ]; 36 | 37 | _.each(values, function (v) { 38 | assert(v.map(f).map(g).equals(v.map(_.compose(g, f)))); 39 | }); 40 | }); 41 | }); 42 | }); 43 | 44 | describe("orElse", function () { 45 | it("unwraps options", function () { 46 | var e = 5; 47 | var values = [ 48 | [menrva.some(1), 1], 49 | [menrva.some(2), 2], 50 | [menrva.none, e], 51 | ]; 52 | 53 | _.each(values, function (p) { 54 | var v = p[0]; 55 | var r = p[1]; 56 | 57 | assert(v.orElse(e) === r); 58 | }); 59 | }); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /examples/autocomplete/autocomplete-rxjs.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | function searchWikipedia (term) { 3 | return $.ajax({ 4 | url: "http://en.wikipedia.org/w/api.php", 5 | dataType: "jsonp", 6 | data: { 7 | action: "opensearch", 8 | format: "json", 9 | search: window.encodeURI(term) 10 | } 11 | }).promise(); 12 | } 13 | 14 | function main() { 15 | var $input = $("#searchtext"), 16 | $results = $("#results"); 17 | 18 | // Get all distinct key up events from the input and only fire if long enough and distinct 19 | var keyup = Rx.Observable.fromEvent($input[0], "keyup") 20 | .map(function (e) { 21 | return e.target.value; // Project the text from the input 22 | }) 23 | .filter(function (text) { 24 | return text.length > 2; // Only if the text is longer than 2 characters 25 | }) 26 | .throttle(750 /* Pause for 750ms */ ) 27 | .distinctUntilChanged(); // Only if the value has changed 28 | 29 | var searcher = keyup.flatMapLatest(searchWikipedia); 30 | 31 | /* var subscription = */ 32 | searcher.subscribe( 33 | function (data) { 34 | var res = data[1]; 35 | 36 | // Append the results 37 | $results.empty(); 38 | 39 | $.each(res, function (_, value) { 40 | $("
  • " + value + "
  • ").appendTo($results); 41 | }); 42 | }, 43 | function (error) { 44 | // Handle any errors 45 | $results.empty(); 46 | 47 | $("
  • Error: " + error + "
  • ").appendTo($results); 48 | }); 49 | } 50 | 51 | main(); 52 | 53 | }); 54 | -------------------------------------------------------------------------------- /examples/counter/counter-bacon.js: -------------------------------------------------------------------------------- 1 | /** 2 | Inspired by [simple counter](http://baconjs.github.io/) 3 | */ 4 | $(function () { 5 | "use strict"; 6 | 7 | var MIN_VALUE = 0; 8 | 9 | var upButton = $("#up"); 10 | var downButton = $("#down"); 11 | var counterEl = $("#counter"); 12 | 13 | var $up = upButton.asEventStream("click"); 14 | var $down = downButton.asEventStream("click"); 15 | 16 | /** 17 | ## Original — direct approach 18 | */ 19 | var $counter = 20 | // map up to 1, down to -1 21 | $up.map(1).merge($down.map(-1)) 22 | // accumulate sum 23 | .scan(0, function(x,y) { return Math.max(MIN_VALUE, x + y); }); 24 | 25 | /** 26 | ## State transformation approach 27 | 28 | This is almost the same as previous. Yet parameters of `scan` are generic. 29 | All of logic is bundled into state transformation functions. 30 | */ 31 | 32 | function constant(x) { 33 | return function () { 34 | return x; 35 | }; 36 | } 37 | 38 | function inc(x) { 39 | return x + 1; 40 | } 41 | 42 | function dec(x) { 43 | return Math.max(MIN_VALUE, x - 1); 44 | } 45 | 46 | $counter = Bacon.mergeAll($up.map(constant(inc)), $down.map(constant(dec))) 47 | .scan(0, function (state, f) { return f(state); }); 48 | 49 | /** 50 | ## Render 51 | 52 | Assign observable value to jQuery property text 53 | */ 54 | $counter.assign(counterEl, "text"); 55 | 56 | $counter.onValue(function (x) { 57 | if (x <= MIN_VALUE) { 58 | downButton.attr("disabled", "disabled"); 59 | } else { 60 | downButton.removeAttr("disabled"); 61 | } 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /examples/counter/bacon.md: -------------------------------------------------------------------------------- 1 | Inspired by [simple counter](http://baconjs.github.io/) 2 | 3 | 4 | ```js 5 | $(function () { 6 | "use strict"; 7 | 8 | var MIN_VALUE = 0; 9 | 10 | var upButton = $("#up"); 11 | var downButton = $("#down"); 12 | var counterEl = $("#counter"); 13 | 14 | var $up = upButton.asEventStream("click"); 15 | var $down = downButton.asEventStream("click"); 16 | ``` 17 | 18 | 19 | ## Original — direct approach 20 | 21 | 22 | ```js 23 | var $counter = 24 | // map up to 1, down to -1 25 | $up.map(1).merge($down.map(-1)) 26 | // accumulate sum 27 | .scan(0, function(x,y) { return Math.max(MIN_VALUE, x + y); }); 28 | ``` 29 | 30 | 31 | ## State transformation approach 32 | 33 | This is almost the same as previous. Yet parameters of `scan` are generic. 34 | All of logic is bundled into state transformation functions. 35 | 36 | 37 | ```js 38 | function constant(x) { 39 | return function () { 40 | return x; 41 | }; 42 | } 43 | 44 | function inc(x) { 45 | return x + 1; 46 | } 47 | 48 | function dec(x) { 49 | return Math.max(MIN_VALUE, x - 1); 50 | } 51 | 52 | $counter = Bacon.mergeAll($up.map(constant(inc)), $down.map(constant(dec))) 53 | .scan(0, function (state, f) { return f(state); }) 54 | ``` 55 | 56 | 57 | ## Render 58 | 59 | Assign observable value to jQuery property text 60 | 61 | 62 | ```js 63 | $counter.assign(counterEl, "text"); 64 | 65 | $counter.onValue(function (x) { 66 | if (x <= MIN_VALUE) { 67 | downButton.attr("disabled", "disabled"); 68 | } else { 69 | downButton.removeAttr("disabled"); 70 | } 71 | }); 72 | }); 73 | ``` 74 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all : test 2 | 3 | SRC=src/*.js 4 | TESTSRC=test 5 | 6 | DISTDIR=dist 7 | DISTPREFIX=menrva 8 | 9 | BUNDLESRC=src/menrva.js 10 | BUNDLEDST=$(DISTDIR)/$(DISTPREFIX).standalone.js 11 | BUNDLEVAR=menrva 12 | 13 | MINSRC=$(BUNDLEDST) 14 | MINDST=$(DISTDIR)/$(DISTPREFIX).min.js 15 | MINMAP=$(DISTDIR)/$(DISTPREFIX).min.js.map 16 | 17 | LJSSRC=src/menrva.js 18 | 19 | .PHONY : all test jshint mocha istanbul browserify typify literate version dist 20 | 21 | BINDIR=node_modules/.bin 22 | 23 | MOCHA=$(BINDIR)/_mocha 24 | ISTANBUL=$(BINDIR)/istanbul 25 | JSHINT=$(BINDIR)/jshint 26 | BROWSERIFY=$(BINDIR)/browserify 27 | UGLIFY=$(BINDIR)/uglifyjs 28 | TYPIFY=$(BINDIR)/typify 29 | LJS=$(BINDIR)/ljs 30 | COVERALLS=$(BINDIR)/coveralls 31 | 32 | test : jshint mocha istanbul typify 33 | 34 | jshint : 35 | $(JSHINT) $(SRC) 36 | $(JSHINT) benchmark 37 | $(JSHINT) -c .jshintrc.examples examples 38 | 39 | mocha : 40 | $(MOCHA) --reporter=spec $(TESTSRC) 41 | 42 | istanbul : 43 | $(ISTANBUL) cover $(MOCHA) $(TESTSRC) 44 | $(ISTANBUL) check-coverage --statements 100 --branches 100 --functions 100 45 | 46 | browserify : $(SRC) 47 | mkdir -p $(DISTDIR) 48 | $(BROWSERIFY) -s $(BUNDLEVAR) -o $(BUNDLEDST) $(BUNDLESRC) 49 | 50 | uglify : browserify $(SRC) 51 | mkdir -p $(DISTDIR) 52 | $(UGLIFY) -o $(MINDST) --source-map $(MINMAP) $(MINSRC) 53 | 54 | typify : 55 | $(TYPIFY) $(MOCHA) $(TESTSRC) 56 | 57 | literate : 58 | $(LJS) -c false -o README.md $(LJSSRC) 59 | 60 | coveralls : 61 | if [ ! -z `node --version | grep v0.10` ]; then cat ./coverage/lcov.info | $(COVERALLS); fi 62 | 63 | version: 64 | node scripts/version.js 65 | 66 | dist : test version uglify literate 67 | git clean -fdx -e node_modules 68 | -------------------------------------------------------------------------------- /test/menrva-test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var menrva = require("../src/menrva.js"); 4 | var assert = require("assert"); 5 | var _ = require("lodash"); 6 | var chai = require("chai"); 7 | 8 | describe("menrva", function () { 9 | it("was an Etruscan goddess of war, art, wisdom and health.", function () { 10 | assert(true); 11 | }); 12 | }); 13 | 14 | describe("triangle shape", function () { 15 | /* 16 | a = source() 17 | b = a + 1 18 | c = a + b = 2 * a + 1 19 | */ 20 | var a, b, c; 21 | beforeEach(function () { 22 | a = menrva.source(1); 23 | b = a.map(function (x) { return x + 1; }); 24 | c = menrva.combine(a, b, function (x, y) { 25 | return x + y; 26 | }); 27 | }); 28 | 29 | describe("initial case", function () { 30 | it("value is calculated already", function () { 31 | chai.expect(c.value()).to.equal(3); 32 | }); 33 | 34 | it("when adding onValue callback, it's executed synchronously", function () { 35 | var x = 2; 36 | var count = 0; 37 | c.onValue(function (y) { 38 | x = y; 39 | count += 1; 40 | }); 41 | chai.expect(x).to.equal(3); 42 | chai.expect(count).to.equal(1); 43 | }); 44 | }); 45 | 46 | it("updating", function () { 47 | var count = 0; 48 | c.onValue(function (y) { 49 | count += 1; 50 | }); 51 | 52 | chai.expect(c.value()).to.equal(3); 53 | chai.expect(count).to.equal(1); 54 | 55 | var tx = menrva.transaction(); 56 | a.set(tx, 2); 57 | 58 | chai.expect(c.value()).to.equal(3); 59 | chai.expect(count).to.equal(1); 60 | 61 | tx.commit(); 62 | 63 | chai.expect(c.value()).to.equal(5); 64 | chai.expect(count).to.equal(2); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | /* 2 | * menrva 3 | * https://github.com/phadej/menrva 4 | * 5 | * Copyright (c) 2014 Oleg Grenrus 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | "use strict"; 10 | 11 | function identity(x) { 12 | return x; 13 | } 14 | 15 | function pluck(arr, property) { 16 | var len = arr.length; 17 | var res = new Array(len); 18 | for (var i = 0; i < len; i++) { 19 | res[i] = arr[i][property]; 20 | } 21 | return res; 22 | } 23 | 24 | function values(obj) { 25 | var arr = []; 26 | for (var k in obj) { 27 | arr.push(obj[k]); 28 | } 29 | return arr; 30 | } 31 | 32 | function objIsEmpty(obj) { 33 | /* jshint unused:false */ 34 | for (var k in obj) { 35 | return false; 36 | } 37 | return true; 38 | } 39 | 40 | function getPath(obj, path) { 41 | var len = path.length; 42 | for (var i = 0; i < len; i++) { 43 | if (obj === undefined || obj === null) { 44 | return obj; 45 | } else { 46 | obj = obj[path[i]]; 47 | } 48 | } 49 | return obj; 50 | } 51 | 52 | function setProperty(obj, property, value) { 53 | var copy = {}; 54 | for (var k in obj) { 55 | copy[k] = obj[k]; 56 | } 57 | copy[property] = value; 58 | return copy; 59 | } 60 | 61 | function setPath(obj, path, value) { 62 | var len = path.length; 63 | var acc = value; 64 | for (var i = len; i > 0; i--) { 65 | var next = getPath(obj, path.slice(0, i - 1)); 66 | acc = setProperty(next, path[i - 1], acc); 67 | } 68 | return acc; 69 | } 70 | 71 | function modifyPath(obj, path, f) { 72 | return setPath(obj, path, f(getPath(obj, path))); 73 | } 74 | 75 | module.exports = { 76 | identity: identity, 77 | pluck: pluck, 78 | values: values, 79 | objIsEmpty: objIsEmpty, 80 | getPath: getPath, 81 | setPath: setPath, 82 | modifyPath: modifyPath, 83 | }; 84 | -------------------------------------------------------------------------------- /src/lens.js: -------------------------------------------------------------------------------- 1 | /* 2 | * menrva 3 | * https://github.com/phadej/menrva 4 | * 5 | * Copyright (c) 2014 Oleg Grenrus 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | "use strict"; 10 | 11 | var util = require("./util.js"); 12 | var signal = require("./signal.js"); 13 | 14 | /** 15 | ### Lens 16 | 17 | Lenses are composable functional references. 18 | They allow you to *access* and *modify* data potentially very deep within a structure! 19 | */ 20 | function Lens(parent, path, eq) { 21 | this.parents = [parent]; 22 | this.path = path.split(/\./); 23 | var value = this.calculate(); 24 | signal.initSignal(this, value, eq); 25 | } 26 | 27 | Lens.prototype = new signal.Signal(); 28 | 29 | Lens.prototype.calculateRank = function () { 30 | return this.parents[0].rank + 1; 31 | }; 32 | 33 | Lens.prototype.calculate = function () { 34 | return util.getPath(this.parents[0].v, this.path); 35 | }; 36 | 37 | /** 38 | #### source.zoom 39 | 40 | > zoom (@ : Source a, path : Path a b, eq = egal : b -> b -> boolean) : Source b 41 | 42 | Zoom (or focus) into part specified by `path` of the original signal. 43 | One can `set` and `modify` zoomed signals, they act as sources. 44 | 45 | ```js 46 | var quux = source.zoom("foo.bar.quux"); 47 | ``` 48 | */ 49 | signal.Source.prototype.zoom = Lens.prototype.zoom = function(f, eq) { 50 | var mapped = new Lens(this, f, eq); 51 | this.children.push(mapped); 52 | return mapped; 53 | }; 54 | 55 | Lens.prototype.set = function (tx, value) { 56 | var path = this.path; 57 | this.parents[0].modify(tx, function (obj) { 58 | return util.setPath(obj, path, value); 59 | }); 60 | }; 61 | 62 | Lens.prototype.modify = function (tx, f) { 63 | var path = this.path; 64 | this.parents[0].modify(tx, function (obj) { 65 | return util.modifyPath(obj, path, f); 66 | }); 67 | }; 68 | -------------------------------------------------------------------------------- /src/option.js: -------------------------------------------------------------------------------- 1 | /* 2 | * menrva 3 | * https://github.com/phadej/menrva 4 | * 5 | * Copyright (c) 2014 Oleg Grenrus 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | "use strict"; 10 | 11 | var egal = require("./egal.js"); 12 | var util = require("./util.js"); 13 | 14 | // typify: instance Option 15 | // Option type 16 | 17 | /** 18 | ### Option 19 | 20 | Also known as `Maybe`. 21 | */ 22 | function Option() {} 23 | 24 | // Some 25 | function Some(x) { 26 | this.value = x; 27 | } 28 | 29 | Some.prototype = new Option(); 30 | 31 | function some(x) { 32 | return new Some(x); 33 | } 34 | 35 | // None 36 | function None() {} 37 | 38 | None.prototype = new Option(); 39 | 40 | var none = new None(); 41 | 42 | // Methods 43 | 44 | /** 45 | #### option.equals 46 | 47 | > equals (@ : option a, other : *, eq = eqal : a -> a -> boolean) : boolean 48 | 49 | Equality check. 50 | */ 51 | Some.prototype.equals = function (other, eq) { 52 | eq = eq || egal; 53 | return other instanceof Some && eq(this.value, other.value); // TODO: use egal 54 | }; 55 | 56 | None.prototype.equals = function (other) { 57 | // only one instance of `none` 58 | return this === other; 59 | }; 60 | 61 | /** 62 | #### option.map 63 | 64 | > map (@ : option a, f : a -> b) : option b 65 | */ 66 | // :: fn -> Option 67 | Some.prototype.map = function (f) { 68 | return some(f(this.value)); 69 | }; 70 | 71 | // :: fn -> Option 72 | None.prototype.map = function (f) { 73 | return none; 74 | }; 75 | 76 | /** 77 | #### option.elim 78 | 79 | > elim (@ : option a, x : b, f : a -> b) : b 80 | 81 | */ 82 | Some.prototype.elim = function (x, f) { 83 | return f(this.value); 84 | }; 85 | 86 | None.prototype.elim = function (x, f) { 87 | return x; 88 | }; 89 | 90 | /** 91 | #### option.orElse 92 | 93 | > orElse (@ : option a, x : a) : a 94 | */ 95 | Option.prototype.orElse = function (x) { 96 | return this.elim(x, util.identity); 97 | }; 98 | 99 | module.exports = { 100 | some: some, 101 | none: none, 102 | }; 103 | -------------------------------------------------------------------------------- /src/menrva.js: -------------------------------------------------------------------------------- 1 | /* 2 | * menrva 3 | * https://github.com/phadej/menrva 4 | * 5 | * Copyright (c) 2014 Oleg Grenrus 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | /** 10 | # menrva 11 | 12 | 13 | 14 | [![Build Status](https://secure.travis-ci.org/phadej/menrva.svg?branch=master)](http://travis-ci.org/phadej/menrva) 15 | [![NPM version](http://img.shields.io/npm/v/menrva.svg)](https://www.npmjs.org/package/menrva) 16 | [![Dependency Status](https://david-dm.org/phadej/menrva.svg)](https://david-dm.org/phadej/menrva) 17 | [![devDependency Status](https://david-dm.org/phadej/menrva/dev-status.svg)](https://david-dm.org/phadej/menrva#info=devDependencies) 18 | [![Coverage Status](https://img.shields.io/coveralls/phadej/menrva.svg)](https://coveralls.io/r/phadej/menrva?branch=master) 19 | [![Code Climate](http://img.shields.io/codeclimate/github/phadej/menrva.svg)](https://codeclimate.com/github/phadej/menrva) 20 | 21 | Ambitious data-flow library. 22 | 23 | ## Getting Started 24 | Install the module with: `npm install menrva` 25 | 26 | ```js 27 | var menrva = require('menrva'); 28 | menrva.some('awe'); // some, as in awesome? 29 | ``` 30 | 31 | ## API 32 | */ 33 | /// include signal.js 34 | /// include transaction.js 35 | /// include lens.js 36 | /// include convenience.js 37 | /// include egal.js 38 | /// include option.js 39 | /** 40 | ## Contributing 41 | */ 42 | /// plain ../CONTRIBUTING.md 43 | /** 44 | ## Release History 45 | */ 46 | /// plain ../CHANGELOG.md 47 | /** 48 | 49 | ## License 50 | 51 | Copyright (c) 2014 Oleg Grenrus. 52 | Licensed under the MIT license. 53 | */ 54 | 55 | "use strict"; 56 | 57 | var egal = require("./egal.js"); 58 | var option = require("./option.js"); 59 | var signal = require("./signal.js"); 60 | var transaction = require("./transaction.js"); 61 | 62 | // extensions 63 | require("./lens.js"); 64 | var convenience = require("./convenience.js"); 65 | 66 | // version 67 | var version = "0.0.7"; 68 | 69 | module.exports = { 70 | egal: egal, 71 | some: option.some, 72 | none: option.none, 73 | Signal: signal.Signal, 74 | source: signal.source, 75 | combine: signal.combine, 76 | tuple: convenience.tuple, 77 | sequence: convenience.sequence, 78 | record: convenience.record, 79 | transaction: transaction, 80 | version: version, 81 | }; 82 | -------------------------------------------------------------------------------- /test/signal-test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var menrva = require("../src/menrva.js"); 4 | var assert = require("assert"); 5 | var _ = require("lodash"); 6 | var chai = require("chai"); 7 | 8 | describe("signal", function () { 9 | /* 10 | a = source() 11 | b = a + 1 12 | c = a + b = 2 * a + 1 13 | */ 14 | var a, b, c; 15 | beforeEach(function () { 16 | a = menrva.source(1); 17 | b = a.map(function (x) { return x + 1; }); 18 | c = menrva.combine(a, b, function (x, y) { 19 | return x + y; 20 | }); 21 | }); 22 | 23 | describe("modify", function () { 24 | it("makes possible to modify current value of the signal", function () { 25 | var count = 0; 26 | c.onValue(function (y) { 27 | count += 1; 28 | }); 29 | 30 | chai.expect(c.value()).to.equal(3); 31 | chai.expect(count).to.equal(1); 32 | 33 | var tx1 = menrva.transaction(); 34 | a.set(tx1, 2); 35 | a.modify(tx1, function (x) { return x * x; }); 36 | tx1.commit(); 37 | 38 | chai.expect(c.value()).to.equal(9); 39 | chai.expect(count).to.equal(2); 40 | 41 | var tx2 = menrva.transaction(); 42 | a.modify(tx2, function (x) { return x * x; }); 43 | tx2.commit(); 44 | 45 | chai.expect(c.value()).to.equal(33); 46 | chai.expect(count).to.equal(3); 47 | }); 48 | }); 49 | 50 | describe("unsubscriber", function () { 51 | it("let you unsubscribe, duh!", function () { 52 | var count = 0; 53 | var unsub = c.onValue(function (y) { 54 | count += 1; 55 | }); 56 | 57 | chai.expect(c.value()).to.equal(3); 58 | chai.expect(count).to.equal(1); 59 | 60 | var tx1 = menrva.transaction(); 61 | a.set(tx1, 2); 62 | tx1.commit(); 63 | 64 | chai.expect(c.value()).to.equal(5); 65 | chai.expect(count).to.equal(2); 66 | 67 | unsub(); 68 | 69 | var tx2 = menrva.transaction(); 70 | a.set(tx2, 3); 71 | tx2.commit(); 72 | 73 | chai.expect(c.value()).to.equal(7); 74 | chai.expect(count).to.equal(2); 75 | 76 | // you can unsub many times - second and following calls are no-op 77 | unsub(); 78 | 79 | var tx3 = menrva.transaction(); 80 | a.set(tx3, 2); 81 | tx3.commit(); 82 | 83 | chai.expect(c.value()).to.equal(5); 84 | chai.expect(count).to.equal(2); 85 | }); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /test/convenience-test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var menrva = require("../src/menrva.js"); 4 | var assert = require("assert"); 5 | var _ = require("lodash"); 6 | var chai = require("chai"); 7 | var sinon = require("sinon"); 8 | 9 | describe("convenience features", function () { 10 | describe("log", function () { 11 | beforeEach(function () { 12 | sinon.stub(console, "log"); 13 | }); 14 | afterEach(function () { 15 | console.log.restore(); 16 | }); 17 | 18 | it("is onValue(console.log)", function () { 19 | var a = menrva.source(1); 20 | a.log("foo", "bar"); 21 | 22 | chai.expect(console.log.args).to.deep.equal([["foo", "bar", 1]]); 23 | }); 24 | }); 25 | 26 | describe("tuple - onSpread", function () { 27 | it("are convenient when you want to react to change in either signal", function () { 28 | var a = menrva.source(1); 29 | var b = menrva.source(2); 30 | 31 | var stub = sinon.stub(); 32 | 33 | menrva.tuple(a, b).onSpread(stub); 34 | 35 | menrva.transaction([a, 3]).commit(); 36 | menrva.transaction([b, 4]).commit(); 37 | menrva.transaction([a, 5, b, 6]).commit(); 38 | 39 | chai.expect(stub.args).to.deep.equal([ 40 | [1, 2], 41 | [3, 2], 42 | [3, 4], 43 | [5, 6], 44 | ]); 45 | }); 46 | }); 47 | 48 | describe("sequence", function () { 49 | it("is Traversable method", function () { 50 | var a = menrva.source(1); 51 | var b = menrva.source(2); 52 | var c = menrva.source(3); 53 | 54 | var stub = sinon.stub(); 55 | 56 | menrva.sequence([a, b, c]).onValue(stub); 57 | 58 | menrva.transaction([a, 4]).commit(); 59 | menrva.transaction([b, 5, c, 6]).commit(); 60 | 61 | chai.expect(stub.args).to.deep.equal([ 62 | [[1, 2, 3]], 63 | [[4, 2, 3]], 64 | [[4, 5, 6]], 65 | ]); 66 | }); 67 | }); 68 | 69 | describe("record", function () { 70 | it("is Traversable method", function () { 71 | var a = menrva.source(1); 72 | var b = menrva.source(2); 73 | var c = menrva.source(3); 74 | 75 | var stub = sinon.stub(); 76 | 77 | menrva.record({k: a, l: b, m: c}).onValue(stub); 78 | 79 | menrva.transaction([a, 4]).commit(); 80 | menrva.transaction([b, 5, c, 6]).commit(); 81 | 82 | chai.expect(stub.args).to.deep.equal([ 83 | [{k: 1, l: 2, m: 3}], 84 | [{k: 4, l: 2, m: 3}], 85 | [{k: 4, l: 5, m: 6}], 86 | ]); 87 | }); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /examples/autocomplete/autocomplete.js: -------------------------------------------------------------------------------- 1 | /** 2 | Inspired by [RxJS autocomplete example](https://github.com/Reactive-Extensions/RxJS/tree/master/examples/autocomplete) 3 | */ 4 | $(function () { 5 | "use strict"; 6 | 7 | var inputElement = $("#searchtext"); 8 | var resultsElement = $("#results"); 9 | 10 | var $searchText = menrva.source(inputElement.val()); 11 | var $searchCache = menrva.source({}, _.isEqual); 12 | var $searchResult = menrva.combine($searchText, $searchCache, function (searchText, searchCache) { 13 | return searchCache[searchText]; 14 | }); 15 | 16 | var LOADING = "loading..."; 17 | 18 | // update $searchText 19 | inputElement.keyup(function (ev) { 20 | var text = ev.target.value; 21 | menrva.transaction() 22 | .set($searchText, text) 23 | .commit(); 24 | }); 25 | 26 | $searchResult.onValue(function (searchResult) { 27 | if (searchResult) { 28 | resultsElement.empty(); 29 | 30 | if (searchResult === LOADING) { 31 | resultsElement.html("
  • Loading...
  • "); 32 | } else { 33 | $.each(searchResult, function (key, value) { 34 | $("
  • ").text(value).appendTo(resultsElement); 35 | }); 36 | } 37 | } 38 | }); 39 | 40 | // Search Wikipedia for a given term 41 | function searchWikipedia (term) { 42 | // set we are searching the term 43 | menrva.transaction() 44 | .modify($searchCache, function (searchCache) { 45 | var add = {}; 46 | add[term] = LOADING; 47 | return _.extend({}, searchCache, add); 48 | }) 49 | .commit(); 50 | 51 | // Perform the fetch 52 | return $.ajax({ 53 | url: "http://en.wikipedia.org/w/api.php", 54 | dataType: "jsonp", 55 | data: { 56 | action: "opensearch", 57 | format: "json", 58 | search: window.encodeURI(term) 59 | } 60 | }) 61 | .then(function (searchResult) { 62 | menrva.transaction() 63 | .modify($searchCache, function (searchCache) { 64 | var newCache = _.extend({}, searchCache); 65 | newCache[term] = searchResult[1]; 66 | return newCache; 67 | }) 68 | .commit(); 69 | }); 70 | } 71 | 72 | var throttledSearchWikipedia = _.throttle(searchWikipedia, 750); 73 | 74 | // if there isn't result, perform it. 75 | menrva.tuple($searchText, $searchResult).onSpread(function (searchText, searchResult) { 76 | if (searchText.length > 2 && !searchResult) { 77 | throttledSearchWikipedia(searchText); 78 | } 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /src/convenience.js: -------------------------------------------------------------------------------- 1 | /* 2 | * menrva 3 | * https://github.com/phadej/menrva 4 | * 5 | * Copyright (c) 2014 Oleg Grenrus 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | "use strict"; 10 | 11 | var signal = require("./signal.js"); 12 | var util = require("./util.js"); 13 | 14 | /** 15 | ### Convenience methods 16 | 17 | #### signal.log 18 | 19 | > signal.log (@ : Signal a, args...) : Unsubscriber 20 | 21 | Essentially `signal.onValue(console.log.bind(console, args...)) 22 | */ 23 | signal.Signal.prototype.log = function () { 24 | var args = Array.prototype.slice.call(arguments); 25 | return this.onValue(function (x) { 26 | var logArgs = args.concat([x]); 27 | console.log.apply(console, logArgs); 28 | }); 29 | }; 30 | 31 | /** 32 | #### signal.onSpread 33 | 34 | > signal.onSpread (@ : Signal [a, b...], callback : a -> b ... -> void) : Unsubscriber 35 | 36 | `onValue` with signal's tuple arguments spread. 37 | */ 38 | signal.Signal.prototype.onSpread = function (callback) { 39 | return this.onValue(function (t) { 40 | callback.apply(undefined, t); 41 | }); 42 | }; 43 | 44 | /** 45 | #### tuple 46 | 47 | > tuple (x : Signal a, y : Signal b...) : Signal [a, b...] 48 | 49 | Combine signals into tuple. 50 | */ 51 | 52 | function tuple() { 53 | var signals = Array.prototype.slice.call(arguments); 54 | var mapped = new (signal.CombinedSignal)(signals, util.identity); 55 | 56 | // connect to parent 57 | signals.forEach(function (parent) { 58 | parent.children.push(mapped); 59 | }); 60 | 61 | return mapped; 62 | } 63 | 64 | /** 65 | #### sequence 66 | 67 | > sequence [Signal a, Signal b, ..] : Signal [a, b...] 68 | 69 | In promise libraries this might be called `all`. 70 | */ 71 | function sequence(signals) { 72 | var mapped = new (signal.CombinedSignal)(signals, util.identity); 73 | 74 | // connect to parent 75 | signals.forEach(function (parent) { 76 | parent.children.push(mapped); 77 | }); 78 | 79 | return mapped; 80 | } 81 | 82 | /** 83 | #### record 84 | 85 | > record {k: Signal a, l: Signal b...} : Signal {k: a, l: b...} 86 | 87 | Like `sequence` but for records i.e. objects. 88 | */ 89 | 90 | function record(rec) { 91 | var keys = []; 92 | var signals = []; 93 | 94 | for (var k in rec) { 95 | // if (Object.prototype.hasOwnProperty.call(rec, k)) { 96 | keys.push(k); 97 | signals.push(rec[k]); 98 | // } 99 | } 100 | 101 | function toObject(values) { 102 | var res = {}; 103 | 104 | for (var i = 0; i < keys.length; i++) { 105 | res[keys[i]] = values[i]; 106 | } 107 | 108 | return res; 109 | } 110 | 111 | var mapped = new (signal.CombinedSignal)(signals, toObject); 112 | 113 | // connect to parent 114 | signals.forEach(function (parent) { 115 | parent.children.push(mapped); 116 | }); 117 | 118 | return mapped; 119 | } 120 | 121 | module.exports = { 122 | tuple: tuple, 123 | sequence: sequence, 124 | record: record, 125 | }; 126 | -------------------------------------------------------------------------------- /examples/suggestions/README.md: -------------------------------------------------------------------------------- 1 | Inspired by [*The introduction to Reactive Programming you've been missing*](http://jsfiddle.net/staltz/8jFJH/48) by [@andrestaltz](https://twitter.com/andrestaltz) 2 | 3 | This is [literated](https://github.com/phadej/ljs) javascript file. 4 | 5 | We start with usual jQuery prelude: 6 | 7 | 8 | ```js 9 | $(function () { 10 | "use strict"; 11 | ``` 12 | 13 | 14 | ## Model 15 | 16 | The global state of this small widget consists of four variables: list of users and three random variables. 17 | 18 | As *signals* always have a value, we initialize the list of users with an empty list. 19 | 20 | As a notation, we use `$foo` for signals, and `foo` for their values. 21 | One may call this [Hungarian notation](http://en.wikipedia.org/wiki/Hungarian_notation). 22 | 23 | 24 | ```js 25 | var $users = menrva.source([]); 26 | var $fst = menrva.source(Math.random()); 27 | var $snd = menrva.source(Math.random()); 28 | var $trd = menrva.source(Math.random()); 29 | ``` 30 | 31 | 32 | ## Derived data 33 | 34 | After we identified the essential data pieces, everything else can be derived from them. 35 | 36 | We `combine` signals to get specific user for each slot in our widget. 37 | 38 | 39 | ```js 40 | function selectUser(users, rnd) { 41 | if (users.length === 0) { 42 | return undefined; 43 | } else { 44 | return users[Math.floor(rnd * users.length)]; 45 | } 46 | } 47 | 48 | var $fstUser = menrva.combine($users, $fst, selectUser); 49 | var $sndUser = menrva.combine($users, $snd, selectUser); 50 | var $trdUser = menrva.combine($users, $trd, selectUser); 51 | ``` 52 | 53 | 54 | ## Rendering 55 | 56 | Showing the suggestions is now easy. We only have to attach `onValue` callbacks to the user signals. 57 | 58 | `renderSuggestion` is exactly the same as in original jsfiddle. 59 | 60 | 61 | ```js 62 | function renderSuggestion(suggestedUser, selector) { 63 | var suggestionEl = document.querySelector(selector); 64 | if (suggestedUser === undefined) { 65 | suggestionEl.style.visibility = 'hidden'; 66 | } else { 67 | suggestionEl.style.visibility = 'visible'; 68 | var usernameEl = suggestionEl.querySelector('.username'); 69 | usernameEl.href = suggestedUser.html_url; 70 | usernameEl.textContent = suggestedUser.login; 71 | var imgEl = suggestionEl.querySelector('img'); 72 | imgEl.src = ""; 73 | imgEl.src = suggestedUser.avatar_url; 74 | } 75 | } 76 | 77 | $fstUser.onValue(function (user) { 78 | renderSuggestion(user, '.suggestion1'); 79 | }); 80 | 81 | $sndUser.onValue(function (user) { 82 | renderSuggestion(user, '.suggestion2'); 83 | }); 84 | 85 | $trdUser.onValue(function (user) { 86 | renderSuggestion(user, '.suggestion3'); 87 | }); 88 | ``` 89 | 90 | 91 | ## Events 92 | 93 | Events make our system tick. And here is the difference to RxJs implementation. 94 | *menrva* doesn't have any tools for working with events. 95 | This is intentional choice. For events one may use anything one likes. 96 | Pure callbacks, promises, even RxJS (it's awesome for event networks). 97 | 98 | As in this example event network is simple, we handle them directly. 99 | 100 | There are two event types: refresh and close. On refresh we fetch new user list, 101 | and set the value of `$users`. 102 | 103 | Here we use promises directly. 104 | 105 | For handling one-to-one asynchronicity promises are very good abstraction. 106 | *menrva*, on the other hand, handles reactiviness. 107 | 108 | 109 | ```js 110 | function refresh() { 111 | var randomOffset = Math.floor(Math.random()*500); 112 | $.getJSON("https://api.github.com/users?since=" + randomOffset).then(function (response) { 113 | var tx = menrva.transaction(); 114 | $users.set(tx, response); 115 | tx.commit(); 116 | }); 117 | } 118 | 119 | function randomize(source) { 120 | return function () { 121 | var tx = menrva.transaction(); 122 | source.set(tx, Math.random()); 123 | tx.commit(); 124 | }; 125 | } 126 | 127 | // Initial populate 128 | refresh(); 129 | 130 | // Event bindings 131 | var refreshButton = $('.refresh'); 132 | var closeButton1 = $('.close1'); 133 | var closeButton2 = $('.close2'); 134 | var closeButton3 = $('.close3'); 135 | 136 | refreshButton.click(refresh); 137 | 138 | closeButton1.click(randomize($fst)); 139 | closeButton2.click(randomize($snd)); 140 | closeButton3.click(randomize($trd)); 141 | }); 142 | ``` 143 | -------------------------------------------------------------------------------- /examples/suggestions/suggestions.js: -------------------------------------------------------------------------------- 1 | /** 2 | Inspired by [*The introduction to Reactive Programming you've been missing*](http://jsfiddle.net/staltz/8jFJH/48) by [@andrestaltz](https://twitter.com/andrestaltz) 3 | 4 | This is [literated](https://github.com/phadej/ljs) javascript file. 5 | 6 | We start with usual jQuery prelude: 7 | */ 8 | $(function () { 9 | "use strict"; 10 | 11 | /** 12 | ## Model 13 | 14 | The global state of this small widget consists of four variables: list of users and three random variables. 15 | 16 | As *signals* always have a value, we initialize the list of users with an empty list. 17 | 18 | As a notation, we use `$foo` for signals, and `foo` for their values. 19 | One may call this [Hungarian notation](http://en.wikipedia.org/wiki/Hungarian_notation). 20 | */ 21 | var $users = menrva.source([]); 22 | var $fst = menrva.source(Math.random()); 23 | var $snd = menrva.source(Math.random()); 24 | var $trd = menrva.source(Math.random()); 25 | 26 | /** 27 | ## Derived data 28 | 29 | After we identified the essential data pieces, everything else can be derived from them. 30 | 31 | We `combine` signals to get specific user for each slot in our widget. 32 | */ 33 | function selectUser(users, rnd) { 34 | if (users.length === 0) { 35 | return undefined; 36 | } else { 37 | return users[Math.floor(rnd * users.length)]; 38 | } 39 | } 40 | 41 | var $fstUser = menrva.combine($users, $fst, selectUser); 42 | var $sndUser = menrva.combine($users, $snd, selectUser); 43 | var $trdUser = menrva.combine($users, $trd, selectUser); 44 | 45 | /** 46 | ## Rendering 47 | 48 | Showing the suggestions is now easy. We only have to attach `onValue` callbacks to the user signals. 49 | 50 | `renderSuggestion` is exactly the same as in original jsfiddle. 51 | */ 52 | 53 | function renderSuggestion(suggestedUser, selector) { 54 | var suggestionEl = document.querySelector(selector); 55 | if (suggestedUser === undefined) { 56 | suggestionEl.style.visibility = "hidden"; 57 | } else { 58 | suggestionEl.style.visibility = "visible"; 59 | var usernameEl = suggestionEl.querySelector(".username"); 60 | usernameEl.href = suggestedUser["html_url"]; 61 | usernameEl.textContent = suggestedUser["login"]; 62 | var imgEl = suggestionEl.querySelector("img"); 63 | imgEl.src = ""; 64 | imgEl.src = suggestedUser["avatar_url"]; 65 | } 66 | } 67 | 68 | $fstUser.onValue(function (user) { 69 | renderSuggestion(user, ".suggestion1"); 70 | }); 71 | 72 | $sndUser.onValue(function (user) { 73 | renderSuggestion(user, ".suggestion2"); 74 | }); 75 | 76 | $trdUser.onValue(function (user) { 77 | renderSuggestion(user, ".suggestion3"); 78 | }); 79 | 80 | /** 81 | ## Events 82 | 83 | Events make our system tick. And here is the difference to RxJs implementation. 84 | *menrva* doesn't have any tools for working with events. 85 | This is intentional choice. For events one may use anything one likes. 86 | Pure callbacks, promises, even RxJS (it's awesome for event networks). 87 | 88 | As in this example event network is simple, we handle them directly. 89 | 90 | There are two event types: refresh and close. On refresh we fetch new user list, 91 | and set the value of `$users`. 92 | 93 | Here we use promises directly. 94 | 95 | For handling one-to-one asynchronicity promises are very good abstraction. 96 | *menrva*, on the other hand, handles reactiviness. 97 | */ 98 | 99 | function refresh() { 100 | var randomOffset = Math.floor(Math.random()*500); 101 | $.getJSON("https://api.github.com/users?since=" + randomOffset).then(function (response) { 102 | var tx = menrva.transaction(); 103 | $users.set(tx, response); 104 | tx.commit(); 105 | }); 106 | } 107 | 108 | function randomize(source) { 109 | return function () { 110 | var tx = menrva.transaction(); 111 | source.set(tx, Math.random()); 112 | tx.commit(); 113 | }; 114 | } 115 | 116 | // Initial populate 117 | refresh(); 118 | 119 | // Event bindings 120 | var refreshButton = $(".refresh"); 121 | var closeButton1 = $(".close1"); 122 | var closeButton2 = $(".close2"); 123 | var closeButton3 = $(".close3"); 124 | 125 | refreshButton.click(refresh); 126 | 127 | closeButton1.click(randomize($fst)); 128 | closeButton2.click(randomize($snd)); 129 | closeButton3.click(randomize($trd)); 130 | }); 131 | -------------------------------------------------------------------------------- /test/lens-test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var menrva = require("../src/menrva.js"); 4 | var assert = require("assert"); 5 | var _ = require("lodash"); 6 | var chai = require("chai"); 7 | 8 | describe("zoom", function () { 9 | var a, b, c, d, e, f; 10 | var bCount, cCount, dCount, eCount, fCount; 11 | beforeEach(function () { 12 | a = menrva.source({ 13 | foo: { 14 | bar: 1, 15 | baz: 2, 16 | }, 17 | quux: "hello world" 18 | }); 19 | 20 | b = a.zoom("foo.bar"); 21 | c = a.zoom("quux"); 22 | d = a.zoom("not.here"); 23 | e = a.zoom("foo", _.isEqual); 24 | f = e.zoom("baz"); 25 | 26 | bCount = cCount = dCount = eCount = fCount = 0; 27 | b.onValue(function () { bCount += 1; }); 28 | c.onValue(function () { cCount += 1; }); 29 | d.onValue(function () { dCount += 1; }); 30 | e.onValue(function () { eCount += 1; }); 31 | f.onValue(function () { fCount += 1; }); 32 | }); 33 | 34 | it("get functionality", function () { 35 | chai.expect(b.value()).to.equal(1); 36 | chai.expect(c.value()).to.equal("hello world"); 37 | chai.expect(d.value()).to.equal(undefined); 38 | }); 39 | 40 | it("set functionality - parent", function () { 41 | chai.expect(bCount).to.equal(1); 42 | chai.expect(cCount).to.equal(1); 43 | chai.expect(dCount).to.equal(1); 44 | chai.expect(eCount).to.equal(1); 45 | chai.expect(fCount).to.equal(1); 46 | 47 | var tx = menrva.transaction(); 48 | a.set(tx, { 49 | foo: { 50 | bar: 2, 51 | baz: 2, 52 | }, 53 | quux: "hello world" 54 | }); 55 | tx.commit(); 56 | 57 | chai.expect(b.value()).to.equal(2); 58 | chai.expect(c.value()).to.equal("hello world"); 59 | chai.expect(d.value()).to.equal(undefined); 60 | chai.expect(e.value()).to.deep.equal({bar: 2, baz: 2}); 61 | chai.expect(f.value()).to.equal(2); 62 | 63 | chai.expect(bCount).to.equal(2); 64 | chai.expect(cCount).to.equal(1); 65 | chai.expect(dCount).to.equal(1); 66 | chai.expect(eCount).to.equal(2); 67 | chai.expect(fCount).to.equal(1); 68 | }); 69 | 70 | it("set functionality - thru lens", function () { 71 | var tx = menrva.transaction(); 72 | b.set(tx, 3); 73 | tx.commit(); 74 | 75 | chai.expect(b.value()).to.equal(3); 76 | chai.expect(e.value()).to.deep.equal({ bar: 3, baz: 2}); 77 | chai.expect(f.value()).to.equal(2); 78 | 79 | chai.expect(bCount).to.equal(2); 80 | chai.expect(cCount).to.equal(1); 81 | chai.expect(dCount).to.equal(1); 82 | chai.expect(eCount).to.equal(2); 83 | chai.expect(fCount).to.equal(1); 84 | }); 85 | 86 | it("set functionality - thru lens, absent path", function () { 87 | var tx = menrva.transaction(); 88 | d.set(tx, "something"); 89 | tx.commit(); 90 | 91 | chai.expect(a.value()).to.deep.equal({ 92 | foo: { 93 | bar: 1, 94 | baz: 2 95 | }, 96 | quux: "hello world", 97 | not: { 98 | here: "something", 99 | } 100 | }); 101 | 102 | chai.expect(bCount).to.equal(1); 103 | chai.expect(cCount).to.equal(1); 104 | chai.expect(dCount).to.equal(2); 105 | chai.expect(eCount).to.equal(1); 106 | chai.expect(fCount).to.equal(1); 107 | }); 108 | 109 | it("modify functionality - thru lens", function () { 110 | var tx = menrva.transaction(); 111 | b.modify(tx, function (x) { 112 | return x * 3; 113 | }); 114 | tx.commit(); 115 | 116 | chai.expect(b.value()).to.equal(3); 117 | chai.expect(e.value()).to.deep.equal({ bar: 3, baz: 2}); 118 | chai.expect(f.value()).to.equal(2); 119 | 120 | chai.expect(bCount).to.equal(2); 121 | chai.expect(cCount).to.equal(1); 122 | chai.expect(dCount).to.equal(1); 123 | chai.expect(eCount).to.equal(2); 124 | chai.expect(fCount).to.equal(1); 125 | }); 126 | 127 | it("set functionality - thru lens, absent path", function () { 128 | var tx = menrva.transaction(); 129 | d.modify(tx, function (x) { 130 | return x; 131 | }); 132 | tx.commit(); 133 | 134 | chai.expect(a.value()).to.deep.equal({ 135 | foo: { 136 | bar: 1, 137 | baz: 2 138 | }, 139 | quux: "hello world", 140 | not: { 141 | here: undefined, // not it exists, though undefined 142 | } 143 | }); 144 | 145 | chai.expect(bCount).to.equal(1); 146 | chai.expect(cCount).to.equal(1); 147 | chai.expect(dCount).to.equal(1); 148 | chai.expect(eCount).to.equal(1); 149 | chai.expect(fCount).to.equal(1); 150 | }); 151 | }); 152 | -------------------------------------------------------------------------------- /benchmark/diamond.js: -------------------------------------------------------------------------------- 1 | var Benchmark = require("benchmark"); 2 | var menrva = require("../src/menrva.js"); 3 | var Bacon = require("baconjs"); 4 | var BaconModel = require("bacon.model"); 5 | var Rx = require("rx"); 6 | 7 | var quick = process.argv.indexOf("--quick") !== -1; 8 | 9 | var WIDTH = 3; 10 | var DEPTH = 5; 11 | 12 | var INITIAL_VALUE = 1; 13 | var SECOND_VALUE = 2; 14 | 15 | function inc(x) { 16 | return x + 1; 17 | } 18 | 19 | function plus() { 20 | var sum = 0; 21 | var len = arguments.length; 22 | for (var i = 0; i < len; i++) { 23 | sum += arguments[i]; 24 | } 25 | return sum; 26 | } 27 | 28 | function diamond(src, width, depth, combinator) { 29 | if (depth === 0) { 30 | return src; 31 | } else { 32 | var branches = []; 33 | for (var i = 0; i < width; i++) { 34 | branches.push(diamond(src.map(inc), width, depth - 1, combinator)); 35 | } 36 | return combinator(branches); 37 | } 38 | } 39 | 40 | // Bacon 41 | var baconBus = new Bacon.Bus(); 42 | function baconCombine(array) { 43 | array = [plus].concat(array); 44 | return Bacon.combineWith.apply(undefined, array); 45 | } 46 | var baconDiamond = diamond(baconBus, WIDTH, DEPTH, baconCombine); 47 | 48 | // Bacon property 49 | var baconPropertyBus = new Bacon.Bus(); 50 | var baconProperty = baconPropertyBus.toProperty(INITIAL_VALUE); 51 | var baconPropertyDiamond = diamond(baconProperty, WIDTH, DEPTH, baconCombine); 52 | 53 | // Bacon model 54 | var baconModel = new BaconModel.Model(INITIAL_VALUE); 55 | function baconModelCombine(array) { 56 | array = [plus].concat(array); 57 | return BaconModel.combineWith.apply(undefined, array); 58 | } 59 | var baconModelDiamond = diamond(baconModel, WIDTH, DEPTH, baconModelCombine); 60 | 61 | // Rx 62 | var rxSubject = new Rx.Subject(); 63 | function rxCombine(array) { 64 | array = array.concat([plus]); 65 | return Rx.Observable.zip.apply(undefined, array); 66 | } 67 | var rxDiamond = diamond(rxSubject, WIDTH, DEPTH, rxCombine); 68 | 69 | // Menrva 70 | var menrvaSource = menrva.source(INITIAL_VALUE); 71 | function menrvaCombine(array) { 72 | array = array.concat([plus]); 73 | return menrva.combine.apply(undefined, array); 74 | } 75 | var menrvaDiamond = diamond(menrvaSource, WIDTH, DEPTH, menrvaCombine); 76 | 77 | // Verify correctness: 78 | var unsub, disposable; 79 | 80 | unsub = baconDiamond.onValue(function (x) { 81 | console.log("Bacon.js: ", x); 82 | }); 83 | baconBus.push(INITIAL_VALUE); 84 | baconBus.push(SECOND_VALUE); 85 | unsub(); 86 | 87 | unsub = baconPropertyDiamond.onValue(function (x) { 88 | console.log("Bacon.js Bus→Property:", x); 89 | }); 90 | baconPropertyBus.push(SECOND_VALUE); 91 | unsub(); 92 | 93 | unsub = baconModelDiamond.onValue(function (x) { 94 | console.log("Bacon.js Model: ", x); 95 | }); 96 | baconModel.set(SECOND_VALUE); 97 | unsub(); 98 | 99 | disposable = rxDiamond.subscribe(function (x) { 100 | console.log("Rx.JS Cold: ", x); 101 | }); 102 | rxSubject.onNext(INITIAL_VALUE); 103 | rxSubject.onNext(SECOND_VALUE); 104 | disposable.dispose(); 105 | 106 | unsub = menrvaDiamond.onValue(function (x) { 107 | console.log("menrva: ", x); 108 | }); 109 | (function () { 110 | var tx = menrva.transaction(); 111 | menrvaSource.set(tx, SECOND_VALUE); 112 | tx.commit(); 113 | }()); 114 | unsub(); 115 | 116 | // Add callack 117 | baconDiamond.onValue(function (x) {}); 118 | baconPropertyDiamond.onValue(function (x) {}); 119 | baconModelDiamond.onValue(function (x) {}); 120 | rxDiamond.subscribe(function (x) {}); 121 | menrvaDiamond.onValue(function (x) {}); 122 | 123 | // Options 124 | if (quick) { 125 | Benchmark.options.maxTime = 2; 126 | Benchmark.options.minSamples = 20; 127 | } else { 128 | Benchmark.options.maxTime = 10; 129 | Benchmark.options.minSamples = 200; 130 | } 131 | 132 | // Suite 133 | var suite = new Benchmark.Suite(); 134 | suite.add("Bacon.js", function () { 135 | baconBus.push(Math.random()); 136 | }); 137 | suite.add("Bacon.js Bus→Property", function () { 138 | baconPropertyBus.push(Math.random()); 139 | }); 140 | suite.add("Bacon.js Model", function () { 141 | baconModel.set(Math.random()); 142 | }); 143 | suite.add("Rx.JS Cold", function () { 144 | rxSubject.onNext(Math.random()); 145 | }); 146 | suite.add("menrva", function (x) { 147 | var tx = menrva.transaction(); 148 | menrvaSource.set(tx, Math.random()); 149 | tx.commit(); 150 | }); 151 | 152 | // Run suite; 153 | suite 154 | .on("cycle", function(event) { 155 | console.log(String(event.target)); 156 | }) 157 | .on("complete", function() { 158 | console.log("Fastest is " + this.filter("fastest").pluck("name")); 159 | }) 160 | // run async 161 | .run(); 162 | -------------------------------------------------------------------------------- /docs/20140703-freaklies/figures/data-events.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/signal.js: -------------------------------------------------------------------------------- 1 | /* 2 | * menrva 3 | * https://github.com/phadej/menrva 4 | * 5 | * Copyright (c) 2014 Oleg Grenrus 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | "use strict"; 10 | 11 | var egal = require("./egal.js"); 12 | var util = require("./util.js"); 13 | 14 | /** 15 | ### Signal 16 | 17 | The core type of menrva. `Signal` is abstract class, and cannot be created explicitly. 18 | 19 | Similar concepts are: *Behaviours* in FRP, *Properties* in bacon.js. 20 | 21 | You can add methods to `Signal`'s prototype. They will be available on all signals. 22 | */ 23 | function Signal() {} 24 | 25 | // Each signal has an unique index. 26 | var index = 0; 27 | 28 | function initSignal(signal, value, eq) { 29 | signal.children = []; 30 | signal.callbacks = []; 31 | signal.v = value; 32 | 33 | // `index` is used to implement faster sets of signals 34 | signal.index = index++; 35 | 36 | // `rank` is used to sort signals topologically 37 | signal.rank = signal.calculateRank(); 38 | 39 | // `eq` is an equality decision function on the signal values 40 | signal.eq = eq || egal; 41 | } 42 | 43 | function CombinedSignal(parents, f, eq) { 44 | this.parents = parents; 45 | this.f = f; 46 | var value = this.calculate(); 47 | initSignal(this, value, eq); 48 | } 49 | 50 | CombinedSignal.prototype = new Signal(); 51 | 52 | // rank of combined signal is 1 + maximum rank of parents 53 | CombinedSignal.prototype.calculateRank = function () { 54 | return Math.max.apply(Math, util.pluck(this.parents, "rank")) + 1; 55 | }; 56 | 57 | CombinedSignal.prototype.calculate = function () { 58 | return this.f.call(undefined, util.pluck(this.parents, "v")); 59 | }; 60 | 61 | /** 62 | #### signal.map 63 | 64 | > map (@ : Signal a, f : a -> b, eq = egal : b -> b -> boolean) : Signal b 65 | */ 66 | Signal.prototype.map = function(f, eq) { 67 | var mapped = new CombinedSignal([this], function (xs) { 68 | return f(xs[0]); 69 | }, eq); 70 | this.children.push(mapped); 71 | return mapped; 72 | }; 73 | 74 | /** 75 | #### signal.onValue 76 | 77 | > onValue (@ : Signal a, callback : a -> void) -> Unsubscriber 78 | 79 | Add value callback. `callback` is immediately executed with the current value of signal. 80 | After than `callback` will be called, each time signal's value changes. 81 | 82 | The return value is a function, which will remove the callback if executed. 83 | */ 84 | Signal.prototype.onValue = function (callback) { 85 | // we wrap callback in function, to make it unique 86 | var wrapped = function (x) { callback(x); }; 87 | 88 | // add to callbacks list 89 | this.callbacks.push(wrapped); 90 | 91 | // execute the callback *synchronously* 92 | callback(this.v); 93 | 94 | // return unsubscriber 95 | var that = this; 96 | return function () { 97 | var index = that.callbacks.indexOf(wrapped); 98 | if (index !== -1) { 99 | that.callbacks.splice(index, 1); 100 | } 101 | }; 102 | }; 103 | 104 | /** 105 | #### signal.value() 106 | 107 | > value (@ : Signal a): Signal a 108 | 109 | Returns the current value of signal. 110 | */ 111 | Signal.prototype.value = function() { 112 | return this.v; 113 | }; 114 | 115 | /** 116 | ### Source 117 | 118 | A signal which value you can set. 119 | 120 | Similar concepts are: *Bacon.Model* in bacon.js, *BehaviourSubject* in Rx. 121 | 122 | 123 | #### source 124 | 125 | > source (initialValue : a, eq = egal : a -> a -> boolean) : Source a 126 | */ 127 | function Source(initialValue, eq) { 128 | initSignal(this, initialValue, eq); 129 | } 130 | 131 | function source(initialValue, eq) { 132 | return new Source(initialValue, eq); 133 | } 134 | 135 | Source.prototype = new Signal(); 136 | 137 | Source.prototype.calculateRank = function () { 138 | return 0; 139 | }; 140 | 141 | /** 142 | #### source.set 143 | 144 | > set (@ : Source a, tx : Transaction, value : a) : void 145 | */ 146 | Source.prototype.set = function (transaction, value) { 147 | transaction.addAction({ 148 | type: "set", 149 | signal: this, 150 | value: value, 151 | }); 152 | }; 153 | 154 | /** 155 | #### source.modify 156 | 157 | > modify (@ : Source a, tx : Transaction, f : a -> a) : void 158 | 159 | Mofify source value. `f` will be called with current value of signal inside the transaction. 160 | */ 161 | Source.prototype.modify = function (transaction, f) { 162 | transaction.addAction({ 163 | type: "modify", 164 | signal: this, 165 | f: f, 166 | }); 167 | }; 168 | 169 | 170 | /** 171 | ### Signal combinators 172 | */ 173 | 174 | /** 175 | #### combine 176 | 177 | > combine (Signal a..., f : a... -> b) : Signal b 178 | 179 | Applicative n-ary lift. Lift pure function to operate on signals: 180 | ```js 181 | var $sum = menrva.combine($a, $b, function (a, b) { 182 | return a + b; 183 | }); 184 | ``` 185 | */ 186 | function combine() { 187 | var signals = Array.prototype.slice.call(arguments, 0, -1); 188 | var f = arguments[arguments.length - 1]; 189 | 190 | var mapped = new CombinedSignal(signals, function (values) { 191 | return f.apply(undefined, values); 192 | }); 193 | 194 | // connect to parent 195 | signals.forEach(function (parent) { 196 | parent.children.push(mapped); 197 | }); 198 | 199 | return mapped; 200 | } 201 | 202 | module.exports = { 203 | Signal: Signal, 204 | Source: Source, 205 | source: source, 206 | combine: combine, 207 | initSignal: initSignal, 208 | // Internal: 209 | CombinedSignal: CombinedSignal, 210 | }; 211 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # menrva 2 | 3 | 4 | 5 | [![Build Status](https://secure.travis-ci.org/phadej/menrva.svg?branch=master)](http://travis-ci.org/phadej/menrva) 6 | [![NPM version](http://img.shields.io/npm/v/menrva.svg)](https://www.npmjs.org/package/menrva) 7 | [![Dependency Status](https://david-dm.org/phadej/menrva.svg)](https://david-dm.org/phadej/menrva) 8 | [![devDependency Status](https://david-dm.org/phadej/menrva/dev-status.svg)](https://david-dm.org/phadej/menrva#info=devDependencies) 9 | [![Coverage Status](https://img.shields.io/coveralls/phadej/menrva.svg)](https://coveralls.io/r/phadej/menrva?branch=master) 10 | [![Code Climate](http://img.shields.io/codeclimate/github/phadej/menrva.svg)](https://codeclimate.com/github/phadej/menrva) 11 | 12 | Ambitious data-flow library. 13 | 14 | ## Getting Started 15 | Install the module with: `npm install menrva` 16 | 17 | ```js 18 | var menrva = require('menrva'); 19 | menrva.some('awe'); // some, as in awesome? 20 | ``` 21 | 22 | ## API 23 | 24 | 25 | ### Signal 26 | 27 | The core type of menrva. `Signal` is abstract class, and cannot be created explicitly. 28 | 29 | Similar concepts are: *Behaviours* in FRP, *Properties* in bacon.js. 30 | 31 | You can add methods to `Signal`'s prototype. They will be available on all signals. 32 | 33 | 34 | #### signal.map 35 | 36 | > map (@ : Signal a, f : a -> b, eq = egal : b -> b -> boolean) : Signal b 37 | 38 | 39 | #### signal.onValue 40 | 41 | > onValue (@ : Signal a, callback : a -> void) -> Unsubscriber 42 | 43 | Add value callback. `callback` is immediately executed with the current value of signal. 44 | After than `callback` will be called, each time signal's value changes. 45 | 46 | The return value is a function, which will remove the callback if executed. 47 | 48 | 49 | #### signal.value() 50 | 51 | > value (@ : Signal a): Signal a 52 | 53 | Returns the current value of signal. 54 | 55 | 56 | ### Source 57 | 58 | A signal which value you can set. 59 | 60 | Similar concepts are: *Bacon.Model* in bacon.js, *BehaviourSubject* in Rx. 61 | 62 | 63 | #### source 64 | 65 | > source (initialValue : a, eq = egal : a -> a -> boolean) : Source a 66 | 67 | 68 | #### source.set 69 | 70 | > set (@ : Source a, tx : Transaction, value : a) : void 71 | 72 | 73 | #### source.modify 74 | 75 | > modify (@ : Source a, tx : Transaction, f : a -> a) : void 76 | 77 | Mofify source value. `f` will be called with current value of signal inside the transaction. 78 | 79 | 80 | ### Signal combinators 81 | 82 | 83 | #### combine 84 | 85 | > combine (Signal a..., f : a... -> b) : Signal b 86 | 87 | Applicative n-ary lift. Lift pure function to operate on signals: 88 | ```js 89 | var $sum = menrva.combine($a, $b, function (a, b) { 90 | return a + b; 91 | }); 92 | ``` 93 | 94 | 95 | 96 | ### Transaction 97 | 98 | One gathers atomic updates into single transaction (to avoid glitches). 99 | 100 | ```js 101 | var tx = menrva.transaction(); 102 | sourceA.set(tx, 42); 103 | sourceB.modify(tx, function (x) { return x + x; }); 104 | tx.commit(); // not necessary, transactions are auto-commited 105 | ``` 106 | 107 | There are also optional syntaxes for simple transactions: 108 | ```js 109 | menrva.transaction() 110 | .set(sourceA, 42) 111 | .modify(sourceB, function (x) { return x + x; }) 112 | .commit(); 113 | ``` 114 | or even 115 | ```js 116 | menrva.transaction([sourceA, 42, sourceB, function(x) { return x + x; }]).commit(); 117 | ``` 118 | 119 | 120 | #### transaction 121 | 122 | > transaction (facts) : Transaction 123 | 124 | Create transaction. 125 | 126 | Shorthand syntax: 127 | 128 | > transaction ([sourceA, valueA, sourceB, valueB ...]) : Transaction 129 | 130 | If `value` is function, `source.modify(tx, value)` is called; otherwise `source.set(tx, value)`. 131 | 132 | 133 | #### transaction.commit 134 | 135 | Commit the transaction, forcing synchronous data propagation. 136 | 137 | 138 | #### transaction.rollback 139 | 140 | Rollback the transaction. Maybe be called multiple times (consecutives calls are no-op). 141 | 142 | *Note: for now `rollback` only resets the pending actions in transactions. Transaction is still valid, and more actions can be added* 143 | 144 | 145 | 146 | ### Lens 147 | 148 | Lenses are composable functional references. 149 | They allow you to *access* and *modify* data potentially very deep within a structure! 150 | 151 | 152 | #### source.zoom 153 | 154 | > zoom (@ : Source a, path : Path a b, eq = egal : b -> b -> boolean) : Source b 155 | 156 | Zoom (or focus) into part specified by `path` of the original signal. 157 | One can `set` and `modify` zoomed signals, they act as sources. 158 | 159 | ```js 160 | var quux = source.zoom("foo.bar.quux"); 161 | ``` 162 | 163 | 164 | 165 | ### Convenience methods 166 | 167 | #### signal.log 168 | 169 | > signal.log (@ : Signal a, args...) : Unsubscriber 170 | 171 | Essentially `signal.onValue(console.log.bind(console, args...)) 172 | 173 | 174 | #### signal.onSpread 175 | 176 | > signal.onSpread (@ : Signal [a, b...], callback : a -> b ... -> void) : Unsubscriber 177 | 178 | `onValue` with signal's tuple arguments spread. 179 | 180 | 181 | #### tuple 182 | 183 | > tuple (x : Signal a, y : Signal b...) : Signal [a, b...] 184 | 185 | Combine signals into tuple. 186 | 187 | 188 | #### sequence 189 | 190 | > sequence [Signal a, Signal b, ..] : Signal [a, b...] 191 | 192 | In promise libraries this might be called `all`. 193 | 194 | 195 | #### record 196 | 197 | > record {k: Signal a, l: Signal b...} : Signal {k: a, l: b...} 198 | 199 | Like `sequence` but for records i.e. objects. 200 | 201 | 202 | 203 | ### Equalities 204 | 205 | #### egal 206 | 207 | > egal (a, b) : boolean 208 | 209 | Identity check. `Object.is`. http://wiki.ecmascript.org/doku.php?id=harmony:egal 210 | 211 | 212 | 213 | ### Option 214 | 215 | Also known as `Maybe`. 216 | 217 | 218 | #### option.equals 219 | 220 | > equals (@ : option a, other : *, eq = eqal : a -> a -> boolean) : boolean 221 | 222 | Equality check. 223 | 224 | 225 | #### option.map 226 | 227 | > map (@ : option a, f : a -> b) : option b 228 | 229 | 230 | #### option.elim 231 | 232 | > elim (@ : option a, x : b, f : a -> b) : b 233 | 234 | 235 | 236 | #### option.orElse 237 | 238 | > orElse (@ : option a, x : a) : a 239 | 240 | 241 | 242 | ## Contributing 243 | 244 | 245 | In lieu of a formal styleguide, take care to maintain the existing coding style. 246 | 247 | Add tests for any new or changed functionality. 100% coverage isn't hard. Semantic coverage is important too though. 248 | 249 | Note: `README.md` is autogenerated file. 250 | 251 | ## Release History 252 | 253 | 254 | - **0.0.7** `sequence` and `record` 255 | - **0.0.6** Convenience methods 256 | - **0.0.5** Lens 257 | - **0.0.4** Internal improvements 258 | - **0.0.3** Slight changes in API 259 | - **0.0.2** Basic data-flow functionality 260 | - **0.0.1** Name reservation 261 | 262 | ## License 263 | 264 | Copyright (c) 2014 Oleg Grenrus. 265 | Licensed under the MIT license. 266 | -------------------------------------------------------------------------------- /src/transaction.js: -------------------------------------------------------------------------------- 1 | /* 2 | * menrva 3 | * https://github.com/phadej/menrva 4 | * 5 | * Copyright (c) 2014 Oleg Grenrus 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | "use strict"; 10 | 11 | var util = require("./util.js"); 12 | 13 | /** 14 | ### Transaction 15 | 16 | One gathers atomic updates into single transaction (to avoid glitches). 17 | 18 | ```js 19 | var tx = menrva.transaction(); 20 | sourceA.set(tx, 42); 21 | sourceB.modify(tx, function (x) { return x + x; }); 22 | tx.commit(); // not necessary, transactions are auto-commited 23 | ``` 24 | 25 | There are also optional syntaxes for simple transactions: 26 | ```js 27 | menrva.transaction() 28 | .set(sourceA, 42) 29 | .modify(sourceB, function (x) { return x + x; }) 30 | .commit(); 31 | ``` 32 | or even 33 | ```js 34 | menrva.transaction([sourceA, 42, sourceB, function(x) { return x + x; }]).commit(); 35 | ``` 36 | */ 37 | function Transaction() { 38 | this.actions = []; 39 | this.commitScheduled = false; 40 | } 41 | 42 | /** 43 | #### transaction 44 | 45 | > transaction (facts) : Transaction 46 | 47 | Create transaction. 48 | 49 | Shorthand syntax: 50 | 51 | > transaction ([sourceA, valueA, sourceB, valueB ...]) : Transaction 52 | 53 | If `value` is function, `source.modify(tx, value)` is called; otherwise `source.set(tx, value)`. 54 | */ 55 | function transaction(facts) { 56 | var tx = new Transaction(); 57 | 58 | if (Array.isArray(facts)) { 59 | var len = facts.length; 60 | for (var i = 0; i < len; i += 2) { 61 | var source = facts[i]; 62 | var value = facts[i + 1]; 63 | if (typeof value === "function") { 64 | source.modify(tx, value); 65 | } else { 66 | source.set(tx, value); 67 | } 68 | } 69 | } 70 | 71 | return tx; 72 | } 73 | 74 | /** 75 | #### transaction.commit 76 | 77 | Commit the transaction, forcing synchronous data propagation. 78 | */ 79 | 80 | function calculateUpdates(actions) { 81 | var updates = {}; 82 | var len = actions.length; 83 | for (var i = 0; i < len; i++) { 84 | var action = actions[i]; 85 | // find update fact for signal 86 | var update = updates[action.signal.index]; 87 | 88 | // if not update found, create new for action's signal 89 | if (!update) { 90 | update = { 91 | signal: action.signal, 92 | value: action.signal.v, 93 | }; 94 | updates[action.signal.index] = update; 95 | } 96 | 97 | // perform action 98 | switch (action.type) { 99 | case "set": 100 | update.value = action.value; 101 | break; 102 | case "modify": 103 | update.value = action.f(update.value); 104 | break; 105 | } 106 | } 107 | 108 | return util.values(updates); 109 | } 110 | 111 | function initialSet(updates) { 112 | var updated = []; 113 | var len = updates.length; 114 | for (var i = 0; i < len; i++) { 115 | var update = updates[i]; 116 | // if different value 117 | if (!update.signal.eq(update.signal.v, update.value)) { 118 | // set it 119 | update.signal.v = update.value; 120 | 121 | // collect updated source signal 122 | updated.push(update.signal); 123 | } 124 | } 125 | return updated; 126 | } 127 | 128 | function triggerOnValue(updated) { 129 | var len = updated.length; 130 | for (var i = 0; i < len; i++) { 131 | var updatedSignal = updated[i]; 132 | var value = updatedSignal.v; 133 | var callbacks = updatedSignal.callbacks; 134 | var callbacksLen = callbacks.length; 135 | for (var j = 0; j < callbacksLen; j++) { 136 | callbacks[j](value); 137 | } 138 | } 139 | } 140 | 141 | Transaction.prototype.commit = function () { 142 | 143 | // clear timeout 144 | if (this.commitScheduled) { 145 | clearTimeout(this.commitScheduled); 146 | this.commitScheduled = false; 147 | } 148 | 149 | // If nothing to do, short circuit 150 | if (this.actions.length === 0) { 151 | return; 152 | } 153 | 154 | // Data flow 155 | 156 | // traverse actions to aquire new values 157 | var updates = calculateUpdates(this.actions); 158 | 159 | // Apply updates, and collect updated signals 160 | var updated = initialSet(updates); 161 | 162 | // seed propagation push-pull propagation with children of updated sources 163 | var signals = {}; 164 | updated.forEach(function (update) { 165 | update.children.forEach(function (child) { 166 | signals[child.index] = child; 167 | }); 168 | }); 169 | 170 | // until there aren't any signals 171 | while (!util.objIsEmpty(signals)) { 172 | // minimum rank 173 | var rank = Infinity; 174 | for (var rankK in signals) { 175 | rank = Math.min(rank, signals[rankK].rank); 176 | } 177 | 178 | var next = []; 179 | var curr = []; 180 | 181 | for (var k in signals) { 182 | var signal = signals[k]; 183 | // skip signals of different (larger!) rank 184 | if (signal.rank !== rank) { 185 | continue; 186 | } 187 | 188 | // new value 189 | var value = signal.calculate(); 190 | 191 | // if value is changed 192 | if (!signal.eq(signal.v, value)) { 193 | // set the value 194 | signal.v = value; 195 | 196 | // add signal to updated list 197 | updated.push(signal); 198 | 199 | // add children of updated signal to list of traversable signals 200 | var childrenlen = signal.children.length; 201 | for (var childIdx = 0; childIdx < childrenlen; childIdx++) { 202 | var child = signal.children[childIdx]; 203 | next.push(child); 204 | } 205 | } 206 | 207 | // we are done with this signal 208 | curr.push(signal.index); 209 | } 210 | 211 | // Remove traversed 212 | var currLen = curr.length; 213 | for (var currIdx = 0; currIdx < currLen; currIdx++) { 214 | delete signals[curr[currIdx]]; 215 | } 216 | 217 | // add next 218 | var nextLen = next.length; 219 | for (var nextIdx = 0; nextIdx < nextLen; nextIdx++) { 220 | signals[next[nextIdx].index] = next[nextIdx]; 221 | } 222 | } 223 | 224 | // Trigger onValue callbacks 225 | triggerOnValue(updated); 226 | 227 | // rest cleanupg 228 | this.actions = []; 229 | }; 230 | 231 | /** 232 | #### transaction.rollback 233 | 234 | Rollback the transaction. Maybe be called multiple times (consecutives calls are no-op). 235 | 236 | *Note: for now `rollback` only resets the pending actions in transactions. Transaction is still valid, and more actions can be added* 237 | */ 238 | Transaction.prototype.rollback = function() { 239 | if (this.commitScheduled) { 240 | clearTimeout(this.commitScheduled); 241 | } 242 | this.actions = []; 243 | this.commitScheduled = false; 244 | }; 245 | 246 | Transaction.prototype.deferCommit = function () { 247 | if (!this.commitScheduled) { 248 | var tx = this; 249 | this.commitScheduled = setTimeout(function () { 250 | tx.commit(); 251 | }); 252 | } 253 | }; 254 | 255 | Transaction.prototype.addAction = function (action) { 256 | this.actions.push(action); 257 | this.deferCommit(); 258 | }; 259 | 260 | Transaction.prototype.set = function (source, value) { 261 | source.set(this, value); 262 | return this; 263 | }; 264 | 265 | Transaction.prototype.modify = function (source, f) { 266 | source.modify(this, f); 267 | return this; 268 | }; 269 | 270 | module.exports = transaction; 271 | -------------------------------------------------------------------------------- /test/transaction-test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var menrva = require("../src/menrva.js"); 4 | var assert = require("assert"); 5 | var _ = require("lodash"); 6 | var chai = require("chai"); 7 | 8 | describe("transaction", function () { 9 | /* 10 | a = source() 11 | b = a + 1 12 | c = a + b = 2 * a + 1 13 | */ 14 | var a, b, c; 15 | beforeEach(function () { 16 | a = menrva.source(1); 17 | b = a.map(function (x) { return x + 1; }); 18 | c = menrva.combine(a, b, function (x, y) { 19 | return x + y; 20 | }); 21 | }); 22 | 23 | describe("multiple sets", function () { 24 | it("last one wins", function () { 25 | var count = 0; 26 | c.onValue(function (y) { 27 | count += 1; 28 | }); 29 | 30 | chai.expect(c.value()).to.equal(3); 31 | chai.expect(count).to.equal(1); 32 | 33 | var tx = menrva.transaction(); 34 | a.set(tx, 2); 35 | a.set(tx, 3); 36 | tx.commit(); 37 | 38 | chai.expect(c.value()).to.equal(7); 39 | chai.expect(count).to.equal(2); 40 | }); 41 | 42 | it("last one wins, equality", function () { 43 | var count = 0; 44 | c.onValue(function (y) { 45 | count += 1; 46 | }); 47 | 48 | chai.expect(c.value()).to.equal(3); 49 | chai.expect(count).to.equal(1); 50 | 51 | var tx = menrva.transaction(); 52 | a.set(tx, 2); 53 | a.set(tx, 1); 54 | tx.commit(); 55 | 56 | chai.expect(c.value()).to.equal(3); 57 | chai.expect(count).to.equal(1); 58 | }); 59 | }); 60 | 61 | describe("rollback", function () { 62 | it("resets the transaction", function () { 63 | var count = 0; 64 | c.onValue(function (y) { 65 | count += 1; 66 | }); 67 | 68 | chai.expect(c.value()).to.equal(3); 69 | chai.expect(count).to.equal(1); 70 | 71 | var tx = menrva.transaction(); 72 | a.set(tx, 2); 73 | tx.rollback(); 74 | 75 | // TODO: we can make this either fail for rollbacked transactions, or let set's do nothing 76 | tx.commit(); 77 | 78 | chai.expect(c.value()).to.equal(3); 79 | chai.expect(count).to.equal(1); 80 | }); 81 | 82 | it("can be called multiple times", function () { 83 | var count = 0; 84 | c.onValue(function (y) { 85 | count += 1; 86 | }); 87 | 88 | chai.expect(c.value()).to.equal(3); 89 | chai.expect(count).to.equal(1); 90 | 91 | var tx = menrva.transaction(); 92 | a.set(tx, 2); 93 | tx.rollback(); 94 | tx.rollback(); 95 | 96 | // TODO: we can make this either fail for rollbacked transactions, or let set's do nothing 97 | tx.commit(); 98 | 99 | chai.expect(c.value()).to.equal(3); 100 | chai.expect(count).to.equal(1); 101 | }); 102 | }); 103 | 104 | it("will eventually auto-commit", function (done) { 105 | var count = 0; 106 | c.onValue(function (y) { 107 | count += 1; 108 | }); 109 | 110 | chai.expect(c.value()).to.equal(3); 111 | chai.expect(count).to.equal(1); 112 | 113 | var tx = menrva.transaction(); 114 | a.set(tx, 2); 115 | 116 | setTimeout(function () { 117 | chai.expect(c.value()).to.equal(5); 118 | chai.expect(count).to.equal(2); 119 | done(); 120 | }, 10) 121 | }); 122 | }); 123 | 124 | describe("transaction - a + b", function () { 125 | /* 126 | a = source() 127 | b = source() 128 | c = a + b 129 | */ 130 | var a, b, c; 131 | beforeEach(function () { 132 | a = menrva.source(1); 133 | b = menrva.source(2); 134 | c = menrva.combine(a, b, function (x, y) { 135 | return x + y; 136 | }); 137 | }); 138 | 139 | describe("multiple sets", function () { 140 | it("inside single transaction", function () { 141 | var count = 0; 142 | c.onValue(function (y) { 143 | count += 1; 144 | }); 145 | 146 | chai.expect(c.value()).to.equal(3); 147 | chai.expect(count).to.equal(1); 148 | 149 | var tx = menrva.transaction(); 150 | a.set(tx, 2); 151 | b.set(tx, 3); 152 | tx.commit(); 153 | 154 | chai.expect(c.value()).to.equal(5); 155 | chai.expect(count).to.equal(2); 156 | }); 157 | 158 | it("skipping duplicates", function () { 159 | var count = 0; 160 | c.onValue(function (y) { 161 | count += 1; 162 | }); 163 | 164 | chai.expect(c.value()).to.equal(3); 165 | chai.expect(count).to.equal(1); 166 | 167 | var tx = menrva.transaction(); 168 | a.set(tx, 2); 169 | b.set(tx, 1); 170 | tx.commit(); 171 | 172 | chai.expect(c.value()).to.equal(3); 173 | chai.expect(count).to.equal(1); 174 | }); 175 | }); 176 | }); 177 | 178 | describe("transaction - a + b + c", function () { 179 | /* 180 | a = source() 181 | b = source() 182 | c = source(); 183 | d = a + b 184 | e = d + c = a + b + c 185 | */ 186 | var a, b, c, d, e; 187 | beforeEach(function () { 188 | a = menrva.source(1); 189 | b = menrva.source(2); 190 | c = menrva.source(3); 191 | d = menrva.combine(a, b, function (x, y) { 192 | return x + y; 193 | }); 194 | e = menrva.combine(d, c, function (x, y) { 195 | return x + y; 196 | }); 197 | }); 198 | 199 | describe("multiple sets", function () { 200 | it("inside single transaction", function () { 201 | var count = 0; 202 | e.onValue(function (y) { 203 | count += 1; 204 | }); 205 | 206 | chai.expect(e.value()).to.equal(6); 207 | chai.expect(count).to.equal(1); 208 | 209 | var tx = menrva.transaction(); 210 | a.set(tx, 2); 211 | b.set(tx, 3); 212 | tx.commit(); 213 | 214 | chai.expect(e.value()).to.equal(8); 215 | chai.expect(count).to.equal(2); 216 | }); 217 | 218 | it("skipping duplicates", function () { 219 | var countD = 0; 220 | d.onValue(function (y) { 221 | countD += 1; 222 | }); 223 | 224 | var countE = 0; 225 | e.onValue(function (y) { 226 | countE += 1; 227 | }); 228 | 229 | chai.expect(d.value()).to.equal(3); 230 | chai.expect(e.value()).to.equal(6); 231 | chai.expect(countD).to.equal(1); 232 | chai.expect(countE).to.equal(1); 233 | 234 | var tx = menrva.transaction(); 235 | a.set(tx, 2); 236 | b.set(tx, 3); 237 | c.set(tx, 1); 238 | tx.commit(); 239 | 240 | chai.expect(d.value()).to.equal(5); 241 | chai.expect(e.value()).to.equal(6); 242 | chai.expect(countD).to.equal(2); 243 | chai.expect(countE).to.equal(1); 244 | }); 245 | }); 246 | 247 | describe("syntax", function () { 248 | var a, b; 249 | 250 | function double(x) { 251 | return x + x; 252 | } 253 | 254 | beforeEach(function () { 255 | a = menrva.source(1); 256 | b = menrva.source(2); 257 | }); 258 | 259 | it("imperative", function () { 260 | chai.expect(a.value()).to.equal(1); 261 | chai.expect(b.value()).to.equal(2); 262 | 263 | var tx = menrva.transaction(); 264 | a.set(tx, 42); 265 | b.modify(tx, double); 266 | tx.commit(); 267 | 268 | chai.expect(a.value()).to.equal(42); 269 | chai.expect(b.value()).to.equal(4); 270 | }); 271 | 272 | it("chain", function () { 273 | chai.expect(a.value()).to.equal(1); 274 | chai.expect(b.value()).to.equal(2); 275 | 276 | menrva.transaction() 277 | .set(a, 42) 278 | .modify(b, double) 279 | .commit(); 280 | 281 | chai.expect(a.value()).to.equal(42); 282 | chai.expect(b.value()).to.equal(4); 283 | }); 284 | 285 | it("short", function () { 286 | chai.expect(a.value()).to.equal(1); 287 | chai.expect(b.value()).to.equal(2); 288 | 289 | menrva.transaction([a, 42, b, double]).commit(); 290 | 291 | chai.expect(a.value()).to.equal(42); 292 | chai.expect(b.value()).to.equal(4); 293 | }); 294 | }); 295 | }); 296 | -------------------------------------------------------------------------------- /docs/20140703-freaklies/freaklies.tex: -------------------------------------------------------------------------------- 1 | \documentclass[10pt,fleqn]{article} 2 | \usepackage[utf8]{inputenc} 3 | \usepackage[T1]{fontenc} 4 | \usepackage{fullpage} 5 | \usepackage{amsmath,amssymb,amsthm,mathtools} 6 | \usepackage{mathrsfs} % mathscr 7 | \usepackage{color} 8 | \usepackage[paperwidth=160mm,paperheight=90mm,margin=10mm]{geometry} 9 | 10 | % multicol 11 | \usepackage{multicol} 12 | \usepackage{array} 13 | 14 | % graphics 15 | \usepackage{graphicx} 16 | \DeclareGraphicsRule{*}{mps}{*}{} 17 | 18 | \usepackage{hyperref} 19 | \hypersetup{ 20 | colorlinks=true 21 | } 22 | 23 | \usepackage{mathpazo} 24 | \usepackage{helvet} 25 | %\usepackage{courier} 26 | \usepackage{inconsolata} 27 | 28 | \usepackage{titling} 29 | \newcommand{\subtitle}[1]{% 30 | \posttitle{% 31 | \par\end{center} 32 | \begin{center}\large#1\end{center} 33 | \vskip0.5em}% 34 | } 35 | 36 | \title{$\mathbb{MENRVA}$} 37 | \subtitle{@futurice freaklies} 38 | \author{Oleg Grenrus} 39 | \date{July 03, 2014} 40 | 41 | \DeclareMathOperator*{\inr}{\mathsf{inr}} 42 | \DeclareMathOperator*{\inl}{\mathsf{inl}} 43 | \DeclareMathOperator*{\none}{\mathsf{none}} 44 | \DeclareMathOperator*{\some}{\mathsf{some}} 45 | \DeclareMathOperator*{\ap}{\mathsf{ap}} 46 | \DeclareMathOperator*{\fst}{\mathsf{fst}} 47 | \DeclareMathOperator*{\snd}{\mathsf{snd}} 48 | 49 | % Theorems 50 | % http://en.wikibooks.org/wiki/LaTeX/Theorems 51 | 52 | \theoremstyle{definition} 53 | \newtheorem{definition}{Definition}[section] 54 | \newtheorem{example}[definition]{Example} 55 | \newtheorem{exercise}[definition]{Exercise} 56 | \newtheorem{note}[definition]{Note} 57 | 58 | \theoremstyle{plain} 59 | \newtheorem{proposition}[definition]{Proposition} 60 | \newtheorem{lemma}[definition]{Lemma} 61 | \newtheorem{theorem}[definition]{Theorem} 62 | 63 | \pagestyle{empty} 64 | 65 | \definecolor{blue}{rgb}{0,0,0.5} 66 | \definecolor{green}{rgb}{0,0.5,0} 67 | \definecolor{red}{rgb}{0.5,0,0} 68 | \definecolor{orange}{rgb}{1,0.5,0} 69 | \definecolor{darkorange}{rgb}{0.5,0.25,0} 70 | \definecolor{lightgrey}{rgb}{0.7,0.7,0.7} 71 | \definecolor{grey}{rgb}{0.5,0.5,0.5} 72 | 73 | \setlength\parindent{0pt} 74 | \setlength{\parskip}{3mm plus1mm minus1mm} 75 | 76 | \newcommand{\identifier}[1]{\texttt{\color{blue}#1}} 77 | \newcommand{\constructor}[1]{\ensuremath{\mathsf{\color{green}#1}}} 78 | \newcommand{\functor}[1]{\ensuremath{\mathcal{\color{darkorange}#1}}} 79 | 80 | \begin{document} 81 | 82 | 83 | \maketitle 84 | \thispagestyle{empty} 85 | \newpage 86 | 87 | {\LARGE$\mathbb{MENRVA}$-- yet another reactive library} 88 | 89 | \href{https://github.com/baconjs/bacon.js}{\textsc{Bacon.js}} is (almost) non-glitchy, but not-so performant. 90 | \href{https://github.com/Reactive-Extensions/RxJS}{\textsc{RxJS}} is glitchy but performant. 91 | 92 | I begin to bias towards using cold-observables of \textsc{RxJS}\footnote{Can use \textsc{Bacon.js} too, but I don't need glitch-free events} (the good parts?) with data-flow propagation of something else ($=$ \textsc{Menrva}). 93 | 94 | \begin{center} 95 | \includegraphics[scale=0.7]{figures/data-events.mps} 96 | \end{center} 97 | 98 | \newpage 99 | 100 | \section*{Semantics} 101 | 102 | Following definitions are from \emph{Push-pull functional reactive programming}\footnote{Conal Elliott, \emph{Push-pull functional reactive programming}, Haskell Symposium, 2009, \url{http://conal.net/papers/push-pull-frp/}}. 103 | 104 | \subsection*{FRP -- Behavior} 105 | 106 | Semantically, a \emph{(reactive) behavior} is just a function of time. In \textsc{Menrva} we call them \emph{signals}. 107 | \begin{align*} 108 | \mathcal{S}\,\alpha = \mathcal{T} \to \alpha 109 | \end{align*} 110 | 111 | Historically in FRP, $\mathcal{T} = \mathbb{R}$. As we’ll see, however, the semantics of behaviors assumes only that $\mathcal{T}$ is totally ordered. 112 | 113 | \newpage 114 | 115 | \subsubsection*{Applicative functor} 116 | 117 | Pure functions can be "lifted" to apply to signals. Classic FRP (CFRP) had a family of lifting combinators: 118 | \begin{align*} 119 | \texttt{lift}_n &:: (\alpha_1 \to \cdots \alpha_n \to \beta) \to (\mathcal{S}\,\alpha_1 \to \cdots \mathcal{S}\,\alpha_n \to \mathcal{S}\,\beta) 120 | \end{align*} 121 | Lifting is pointwise and synchronous: 122 | \begin{align*} 123 | \texttt{lift}_n\,f\,b_1\cdots b_n &= \lambda\,t \mapsto f (b_1\, t)\cdots (b_n\, t) 124 | \end{align*} 125 | The \emph{Functor} \texttt{map} is $\texttt{lift}_1$: 126 | \begin{align*} 127 | \texttt{map}\,f\,b = f \circ b 128 | \end{align*} 129 | 130 | \newpage 131 | 132 | In \textsc{Menrva} \texttt{map} is \texttt{map} and $\texttt{lift}_n$ is \texttt{combine} (with function parameter at the end): 133 | \begin{verbatim} 134 | var $sq = $source.map(function (x) { 135 | return x * x; 136 | }); 137 | 138 | var $quad = menrva.combine($a, $b, $c, function (a, b, c) { 139 | return a * b + c; 140 | }); 141 | \end{verbatim} 142 | 143 | And that's the whole (functional) API. 144 | 145 | {\small There is \texttt{onValue} to observe changes. And \texttt{set} and \texttt{modify} to initiate changes.} 146 | 147 | \newpage 148 | 149 | \subsubsection*{Monad} 150 | 151 | Although Signal is a semantic \emph{Monad} as well, \textsc{Menrva} doesn't have monadic \texttt{join} combinator. 152 | 153 | Semantic of \texttt{join} is simple, but the correct implementation hard. 154 | \begin{align*} 155 | \texttt{join}\,s = t \mapsto s\,t\,t 156 | \end{align*} 157 | 158 | Monad instance would make possible to make dynamic data flow networks. With applicative functor the shape of the network is static (and you can't make loops) $\equiv$ simple and easy. 159 | 160 | \newpage 161 | 162 | \section*{Examples} 163 | 164 | \url{https://github.com/phadej/menrva/tree/master/examples} 165 | 166 | \begin{itemize} 167 | \item \emph{counter} examples (pun not-intended). With data-first approach, you segregate \emph{query} from \emph{control}. 168 | \item \emph{suggestions} \href{https://gist.github.com/staltz/868e7e9bc2a7b8c1f754}{\emph{The introduction to Reactive Programming you've been missing}} 169 | \end{itemize} 170 | 171 | \newpage 172 | 173 | 174 | \section*{What are plans for \textsc{Menrva}?} 175 | 176 | Write more examples, to verify that \textsc{Menrva}'s simplistic approach is enough for the real world. 177 | 178 | \hspace{5mm}\vdots 179 | 180 | Use in the real world application. 181 | 182 | \hspace{5mm}\vdots 183 | 184 | PROFIT! 185 | 186 | \newpage 187 | 188 | \section*{What are you going to add still?} 189 | 190 | Something to handle network destruction. 191 | % Currently all nodes are double linked (parent $\leftrightarrow$ child). 192 | Thus dynamically created (and destroyed) UI components create leaf-subnetwork which should be cleaned up. 193 | \textsc{JavaScript} doesn't have weak references (which might be enough to solve the problem). 194 | So we need some manual mechanism for cleanup. 195 | 196 | One idea is to make own reference counting: 197 | \begin{verbatim} 198 | var $state = menrva.source(...); 199 | var $derived = $state.map(...).retain(); 200 | 201 | var $componentData = $derived.map(...).retain(); 202 | // cleanup: 203 | $componentData.release(); // also removes all callbacks etc. 204 | \end{verbatim} 205 | \newpage 206 | 207 | 208 | \section*{What are you going to add still? -- 2} 209 | 210 | Should we have monadic join? (= \texttt{flatMap}). Do we really need it? Can be worked around, but sometimes it is convenient: 211 | {\small 212 | \begin{verbatim} 213 | $artistImage = $artist.flatmap(getArtistImageSignal); 214 | 215 | function getArtistImageSignal(artist) { 216 | if (!cache[artist.id]) { 217 | cache[artist.id] = menrva.source(placeholderImage); 218 | 219 | // schedule carousel for images... 220 | } 221 | return cache[artist.id]; 222 | } 223 | \end{verbatim} 224 | } 225 | Here the cache (or artist image store) can be signal itself -- no need for \texttt{flatMap}. 226 | 227 | 228 | \section*{What are you going to add still? -- 3} 229 | 230 | \emph{Borrow} goodies from \textsc{Bacon.js} -- Function construction rules: 231 | \begin{verbatim} 232 | $signal.map(".foo"); // shorter way of saying: 233 | $signal.map(function (x) { 234 | return x.foo; 235 | }); 236 | \end{verbatim} 237 | 238 | \newpage 239 | 240 | 241 | \section*{What are you going to add still? -- 4} 242 | 243 | Lenses. Sub-value of source acting as source itself. 244 | \begin{verbatim} 245 | var $foobar = $source.lens("foo.bar"); 246 | 247 | $foobar.set(tx, value); // the same as 248 | $source.modify(tx, function (source) { 249 | return assocIn(source, ["foo", "bar"], value); 250 | }); 251 | \end{verbatim} 252 | \texttt{assocIn} -- \url{http://clojuredocs.org/clojure_core/clojure.core/assoc-in} 253 | 254 | \newpage 255 | 256 | \section*{And why the choice for transactions?} 257 | 258 | You able to update many source simultaneously, avoiding immediate propagation: 259 | \begin{verbatim} 260 | var $a = menrva.source(1); 261 | var $b = menrva.source(2); 262 | 263 | menrva.combine($a, $b, add).onValue(console.log); 264 | 265 | var tx = menrva.transaction(); 266 | $a.set(tx, 2); 267 | $b.set(tx, 3); 268 | tx.commit(); 269 | \end{verbatim} 270 | 271 | \section*{And why the choice for transactions? -- 2} 272 | 273 | Also to mention, transactions arise naturally, if you try to implement anything similar in 274 | \textsc{Haskell} or \textsc{Clojure} (using \textsc{STM}). 275 | 276 | There you will be forced to do all mutation inside STM transaction. 277 | 278 | Changes initiated in transaction are applied only when you commit the transaction: you \emph{always} have consistent data. 279 | 280 | \newpage 281 | {\LARGE the end} 282 | \newpage 283 | 284 | 285 | \end{document} 286 | -------------------------------------------------------------------------------- /dist/menrva.min.js: -------------------------------------------------------------------------------- 1 | !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.menrva=e()}}(function(){var define,module,exports;return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o0;i--){var next=getPath(obj,path.slice(0,i-1));acc=setProperty(next,path[i-1],acc)}return acc}function modifyPath(obj,path,f){return setPath(obj,path,f(getPath(obj,path)))}module.exports={identity:identity,pluck:pluck,values:values,objIsEmpty:objIsEmpty,getPath:getPath,setPath:setPath,modifyPath:modifyPath}},{}]},{},[4])(4)}); 2 | //# sourceMappingURL=dist/menrva.min.js.map -------------------------------------------------------------------------------- /dist/menrva.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"dist/menrva.min.js","sources":["dist/menrva.standalone.js"],"names":["e","exports","module","define","amd","f","window","global","self","menrva","t","n","r","s","o","u","a","require","i","Error","code","l","call","length",1,"signal","util","Signal","prototype","log","args","Array","slice","arguments","this","onValue","x","logArgs","concat","console","apply","onSpread","callback","undefined","tuple","signals","mapped","identity","forEach","parent","children","push","sequence","record","rec","keys","k","toObject","values","res","./signal.js","./util.js",2,"egal","b",3,"Lens","path","eq","parents","split","value","calculate","initSignal","calculateRank","rank","getPath","v","Source","zoom","set","tx","modify","obj","setPath","modifyPath",4,"option","transaction","convenience","version","some","none","source","combine","./convenience.js","./egal.js","./lens.js","./option.js","./transaction.js",5,"Option","Some","None","equals","other","map","elim","orElse",6,"index","callbacks","CombinedSignal","Math","max","pluck","xs","wrapped","that","indexOf","splice","initialValue","addAction","type",7,"Transaction","actions","commitScheduled","facts","isArray","len","calculateUpdates","updates","action","update","initialSet","updated","triggerOnValue","updatedSignal","callbacksLen","j","commit","clearTimeout","child","objIsEmpty","Infinity","rankK","min","next","curr","childrenlen","childIdx","currLen","currIdx","nextLen","nextIdx","rollback","deferCommit","setTimeout",8,"arr","property","setProperty","copy","acc"],"mappings":"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"} -------------------------------------------------------------------------------- /docs/20140703-freaklies/figures/data-events.mps: -------------------------------------------------------------------------------- 1 | %!PS-Adobe-3.0 EPSF-3.0 2 | %%BoundingBox: -43 -43 43 43 3 | %%HiResBoundingBox: -42.76968 -42.76968 42.76968 42.76968 4 | %%Creator: MetaPost 1.504 5 | %%CreationDate: 2014.07.04:1435 6 | %%Pages: 1 7 | %%DocumentResources: procset mpost-minimal 8 | %%+ font GJGAYE-CMBX10 9 | %%DocumentSuppliedResources: procset mpost-minimal 10 | %%+ font GJGAYE-CMBX10 11 | %%EndComments 12 | %%BeginProlog 13 | %%BeginResource: procset mpost-minimal 14 | /bd{bind def}bind def/fshow {exch findfont exch scalefont setfont show}bd 15 | /fcp{findfont dup length dict begin{1 index/FID ne{def}{pop pop}ifelse}forall}bd 16 | /fmc{FontMatrix dup length array copy dup dup}bd/fmd{/FontMatrix exch def}bd 17 | /Amul{4 -1 roll exch mul 1000 div}bd/ExtendFont{fmc 0 get Amul 0 exch put fmd}bd 18 | /ScaleFont{dup fmc 0 get Amul 0 exch put dup dup 3 get Amul 3 exch put fmd}bd 19 | /SlantFont{fmc 2 get dup 0 eq{pop 1}if Amul FontMatrix 0 get mul 2 exch put fmd}bd 20 | %%EndResource 21 | %%BeginResource: font GJGAYE-CMBX10 22 | %!PS-AdobeFont-1.0: CMBX10 003.002 23 | %%Title: CMBX10 24 | %Version: 003.002 25 | %%CreationDate: Mon Jul 13 16:17:00 2009 26 | %%Creator: David M. Jones 27 | %Copyright: Copyright (c) 1997, 2009 American Mathematical Society 28 | %Copyright: (), with Reserved Font Name CMBX10. 29 | % This Font Software is licensed under the SIL Open Font License, Version 1.1. 30 | % This license is in the accompanying file OFL.txt, and is also 31 | % available with a FAQ at: http://scripts.sil.org/OFL. 32 | %%EndComments 33 | FontDirectory/GJGAYE-CMBX10 known{/GJGAYE-CMBX10 findfont dup/UniqueID known{dup 34 | /UniqueID get 5000768 eq exch/FontType get 1 eq and}{pop false}ifelse 35 | {save true}{false}ifelse}{false}ifelse 36 | 11 dict begin 37 | /FontType 1 def 38 | /FontMatrix [0.001 0 0 0.001 0 0 ]readonly def 39 | /FontName /GJGAYE-CMBX10 def 40 | /FontBBox {-56 -250 1164 750 }readonly def 41 | /UniqueID 5000768 def 42 | /PaintType 0 def 43 | /FontInfo 9 dict dup begin 44 | /version (003.002) readonly def 45 | /Notice (Copyright \050c\051 1997, 2009 American Mathematical Society \050\051, with Reserved Font Name CMBX10.) readonly def 46 | /FullName (CMBX10) readonly def 47 | /FamilyName (Computer Modern) readonly def 48 | /Weight (Bold) readonly def 49 | /ItalicAngle 0 def 50 | /isFixedPitch false def 51 | /UnderlinePosition -100 def 52 | /UnderlineThickness 50 def 53 | end readonly def 54 | /Encoding 256 array 55 | 0 1 255 {1 index exch /.notdef put} for 56 | dup 97 /a put 57 | dup 100 /d put 58 | dup 101 /e put 59 | dup 110 /n put 60 | dup 115 /s put 61 | dup 116 /t put 62 | dup 118 /v put 63 | readonly def 64 | currentdict end 65 | currentfile eexec 66 | D9D66F633B846AB284BCF8B0411B772DE5CE3DD325E55798292D7BD972BD75FA 67 | 0E079529AF9C82DF72F64195C9C210DCE34528F540DA1FFD7BEBB9B40787BA93 68 | 51BBFB7CFC5F9152D1E5BB0AD8D016C6CFA4EB41B3C51D091C2D5440E67CFD71 69 | 7C56816B03B901BF4A25A07175380E50A213F877C44778B3C5AADBCC86D6E551 70 | E6AF364B0BFCAAD22D8D558C5C81A7D425A1629DD5182206742D1D082A12F078 71 | 0FD4F5F6D3129FCFFF1F4A912B0A7DEC8D33A57B5AE0328EF9D57ADDAC543273 72 | C01924195A181D03F5054A93B71E5065F8D92FE23794D2D43B233BABF23DF8DB 73 | B6C2BD2F04672F9A3B7FE430263E962F16A948319C51B8ADE6E8A80D3D88023A 74 | 6DEA4D271676C2C8615C4A0EA7DC8F6601610F398673A4D4B905F49EA868FEF6 75 | 39BE073001A36DEA6C08ED51452F062B971740019692E221F4455EDE46AF24B8 76 | 407A98B791F6AD525C72C09776247E194043281D04FE1CD1D8AD8DCEEC3045B4 77 | F95B3B41CD3300768D8A049815348BD7AC1004F5500817E3A267D694AE108BAF 78 | 285B288FC5F28A03E9D34FE5D9B2F9A9BB26ADE66B1CF8EB5BE606E83D213C33 79 | DE083C20D636EF780E761944FCE3B8A950B1E6E7568F33B557C6D59E0CEAF185 80 | 53E609A4F58AC4D5269116F958C4D115C44B5A6DABAB79D3BB6E60BDFCECE108 81 | 74CFBE258779F32C80CD7D9A7CEBA50A0966BD9961F71560119668C4A0C30A5D 82 | ED91ACB30940502B24F33906D6E0F16F81DA87EB6D7FC8B7853BE388C40D75C2 83 | 2CA8F94713AAA1561F5321CE97997CB4AF0E37F44E25B0F73CF4986422B1CD89 84 | 8F861CA623004ADB1C28268D7F8C484AA10C9519B6AEADC95AFAA3304D60E85D 85 | 718B2F67D2B734095E5A92C90785252C98067DC05137BE735220BBCB7C341D61 86 | C4B98BFB1EAF883D38D7A93195A059EF82B42027F23B6CD633231D704B891A9B 87 | 03D11A646F13898F20321D7BC150C63FD6DC6BF9CAFD8DA759E95384B729A0B2 88 | 767B9F3E55C682F7A248BC1184F7D294CFFAE0B246DFCC8F215625DDD4F49F09 89 | FA8D41CBF4A06152FEB152C61539ADF7E70A4D23AF8267D25CE3B12D39D62377 90 | 547E2528D18DC4134FA3BE0437EE0B3509223D71F06D44C6227D62BD01AC0A2A 91 | 3EDA975E894371C07CA1027B102549A7D552FFD25ED2DCC68E29E71BBAB43C62 92 | 57B0BFC6A953ABC2EF703F35D112F6B5460018CDCEAD17F149DBE5B52C2B9E10 93 | 9818EA6D97C8AC884F6841C9B600A7D868F59C1D80E98DE0741D06D69858EC84 94 | 1B33C6C9938B7E8A6FF6C12AD456EECBD3EBAF0D7331536B9F6422019FAFFFA4 95 | 822E79D6D89D6366DA636CB708894FEF904F366E295F1CB808E78E883913C4FB 96 | 1631248ED6A7CF1095C0C61C4F05E4B9DFC47533A5FD24540AD71A0E2907B98B 97 | 28085EB88ABFC3478C9644594C7DC4244ED5A7C1CA8D960B65497D56D174645A 98 | B88F12C2CF0A807DA314017984CF3C4FB7F47392A651EB9CFA961B28E2989893 99 | 9FC4D97171BD5338673F5D1CE2C3C08D48A1B77769F01D5058236C655FFD864B 100 | 80E28F900191D4EB349AA5E400A7B0A0FCA0F3E79D7A7C69775BF754329397B7 101 | D18B20B2683CBC4A19729BA878B3C17EBA0A7E9EE297A5B67E915CAD92C8F356 102 | 582B8299DE6A58E73408F525F7EA895C48A8F0D626A06A96A50348DFBE479D89 103 | 4272576FBB0CD332193D28A8F11503BAE98F8E1D73CF5BCADF23DCD4E6586ABB 104 | 323568F5A34E359661074D50CD8D9DF27191FCE24F10225A5D721EFDE2547E1D 105 | CA998077D2340B1A4ADFFF570AA677CDF3305D5E3A394BB1626EB35074D4EEAC 106 | 2F037CA2EA389F7683FD17A8E07C12B4CB3BA8C249C9B12D297C618009F76717 107 | 0EBF5F2DD39A6BDA10A2E5A811D4E190660F5FDDBA29201B6F8042620397AB2C 108 | E59267A7247B0463891831A6F40582BC3F614E5167B646A8F53D8A31717DD9A1 109 | 9034034E705BA7884F0E0738307AF69D3517147C282747F2788462FDC4336A4F 110 | 9CD222908401A25F0A1F7B13B8DAE622DC965AD0BE62497420B70C04AF432237 111 | E0FDD043456187658ED93B0F9822A3998511DF05E59CC85B7B9992CA0CE3B814 112 | 9723BAE70D2631F32B4BF93511F67179FFAD2075E1591CA5907A4C67701B56CF 113 | A5E5B02EB4A842BA1F18D6864E5677359C2FB4AF5BCBABAFB053F230CC129B45 114 | 8D15413F736EB07C571521C7DE2A13F2AC1C133D491B0A607197BE9AA1231D96 115 | BED7968788246B2E4D2BD330F802810F5BDA3760FEA5210CFC6F54748FB1D921 116 | 5CC3624BBA5B8962AA7D94159651589540B17CF7A785F297264F9C1006D36928 117 | 6E2756D3B623A6087E4B106FBA76255903C624C07E18A1AF4E185A533C640711 118 | 86BB477A906ADD36EB6C8F4A12BC2F01B2B98412E4E105977640930CD998D990 119 | 0254A1E5E9843B7A8ADE0AF6D5871E6D3D666465AE69813A2E26333213FF6713 120 | 6F08D55A90C079A56E1B9AC655F720FC22B5AD8550FFF26DA7B0C5A0B60DDB05 121 | 64E8FAF684F3A455BA9BC9278043D79537D201D520E38750335A4C8FEA887377 122 | 879331B68DAD6B253F4FF9981D0F9B9550ED5179B15EEEB00E560A3DB6E5973B 123 | 63403E4E2F40A3D0B937246E9652000B917B1369741E0F913C14C2D2D6D1FCBE 124 | 2CEC4422177C58523715BD070002EC2E13D383A1DC8C84228862B6C5D3B65667 125 | 9FA97E175239BB7FE7E37E14B96DD7960A8AD49DF428CFC13B5D3CC22E245317 126 | 47B5244DA97F1DF954CED2D552477237CB23D037C0DE728E26C82738954EEA1F 127 | F34FE497DA005AF03746DD2ACF77F6E6F2C224862A1D18AF6F7A5DAF34564387 128 | 9E01DBFF49F8621C058C04C2B3F4F3033FF3E8A977B2CD6B2A3CA4A6C569B19F 129 | C5AC457AE9AF334DA66A730960C7565E93A2D373C0E3DE14646FFDA05DF4C6EB 130 | 6D4CA8ACCA3C3115764F77B842581760BFB9E5C0EBE55308B0577A8F4D968CE2 131 | BA3361D79378D451DD150C34D7E901397AC63B33BD7DB13C50D678F5DE999238 132 | 4B4EA15BD449C46F262D931478F5685CDEEC4C4201FC3EFA607AFB8F27AF6751 133 | 125DE42D2FE2D31DE769B7E7FD8CC8C5D91343B537139A822A5BC4160BB5314E 134 | 37501F65B4FC35475FE9E03E34CBF6795AE86CE409500BD0799DE39FA69978B6 135 | EC74D2197C03632D3F59B85F404DB31240968FA75059B2581B101E028CDECC2E 136 | 7E5E25DFA106E9B8ADB81E82BE9ED3BAA9D03EEB22B7B67AB1262DF6AF5F5EFD 137 | A5627EFEB84F3A5F92EF2557EDA2843D7D18C592635623CEAB14CC3620F33986 138 | 410D6DBAEF9F86E4E6682054540E2B01D8FF2161F10E66851A188BC15BD6666E 139 | 8D3F21709F196A31EE676D28A2D12639CC2E7020A52910F052E61A0710DF09B0 140 | 064171D05611451BD24FAD64716F141E1C41D3218A8115A3D73CA041D02B46D9 141 | 28C3D07DF0FB668E8E91409C8D0A26A65CD737C075E026AC0A974C9BE658199B 142 | 3B9D82ED95E4646977D8F60717DA4C68767DBD7E8320D5AA1D5DEB2E6B009759 143 | 8282F27D64F1F904830AAB501CDA4D9233FC2F12F77F0FBCC46E6B729C71F6D5 144 | E6F3EA02EC35D1048394F4EF2177FC5EB726DE5EF2DE7997166B8BE5B5105D08 145 | EAAC3481FC612665CA112D3F889A0E5B7843EFFCEFACA24A01B6AC2B7DDE02F4 146 | A9295AA2409A3756BAAB44608DACBB56840060037869455BEBA46F10AFC68DD0 147 | 0563843DF111C6D34911CF13AA6023E5E899060B5EC60D0F78FDEF3E981151A9 148 | 24903EB13ED1A67EA1977449716D1A5A7EDE1A2E9465C9C2B20A58AF02D9F373 149 | 73E627CBF296B3A6A4670C39F3B5EA30D76F0362C81020A1777F0ADDBC6B52F7 150 | 213FEE1718214087837049CF2AF00407639657428B9E8B532F68B631611A3501 151 | 3D9DCA38090E227BD0D6D0FB4130EE866DB6B195C873AFD18DDB3B1E40F740C6 152 | B3B375ADCBBF628A07A5FACED539FEDA3379D3B60216C2EA6629BE2F65199D82 153 | FE3AE627D7C67270F3497AE75F7A9514968B5950E2D63C38DA240AF4E6CAE88E 154 | E25167D179108679876E7C80C85FE1D2BCC2EC9B88BE76A8F5736E8E6B3A9CF9 155 | 42E58A4ECB7914865E67C1468CF66D658206830B9380FE346DC2DC4BB56A92CE 156 | 4B5E4EA9036C177869315A2D9E6CFE97E3BFD7CBE0747D40CE5E8A3A0988576B 157 | 8AD2B1E4314C0D8A0CBCA08844A49F7E054D31BA7543730C0A7390BC4A288D10 158 | CE29E389A4791305D3AC1BB6F77C805F1032787306F78FF76A20A9E629899F6D 159 | 13356768D33D7B9E294E8CD50CBFB9CA02A193922BD9B4372C912D1689B6644D 160 | 52CAA30F7421E8114D077288119AD9514EF21E5B9989CCE2ABA0C12549FDF493 161 | FFB39736AC9EB72DAF45E4EA6057527FA9F5AA0A1A3F03C12F7482E465C766D3 162 | 760DA7714D56C91BDAED507A5572BEB51A895F8DD3BD5AAB042650154FC7E4E5 163 | 5EEA6194DF73AC5EE2CBD4EE26E29B1D2D0C458B4850BFE842DDF2EBB4E2A25D 164 | C6A11CA2D8F346E2B736DF88A3D57BC0380B52396A6C039212699F5D3342EB58 165 | 0C3DD5D01D5078479BD9FD10C07925556C0AB0F03606F33796BA72074549EDA6 166 | E33644F62CA35207D7421D2727AD8419AD1772789D33405FCDDC9286BC34C974 167 | A52297F5BBD2E541E8BB473F733AE5097BBC9D5FACF18DE4173B4711E28B23ED 168 | 16E0A6746A60F6FF903026A3900169EDA87D98396E762C2EC963D89197B8CD0C 169 | 25244806BE7CBF46BE60A8F9171731EADFC969C28679B025371E5572E52A0EF8 170 | B3FD9B4638D03E20BFDEC9345E70B8166D38846DCA68E0D0B4B53629C7E7620B 171 | 45E0A610BCD07FEF8814CF915CFB11119F42407D1C6DC1E6353451D40A382C2E 172 | C74DF2A4889ED5A3495C3E973565F7178CA190D22C9693C10EB12C1E7A8679CE 173 | 4AFECFC964CC98111BA4ED2BA9B10292A71D5B11870EB08EB483922CE8628A06 174 | 05E7CF6DF93E112B60EF888AA8DB52994EC33DC7277D7B7A4F913AD30257261A 175 | D6EE80476A9A8D316D190BE6CE0046CBBCED365AB305495284FA921BE0638E00 176 | 63DB2AA4C5F163340BCCD1061B469504DEE350B82FBE1689C1B65D095405614B 177 | 35997D6F0DACA7190D64ABA351705B17B23FE2EE5996FCD607F49F54392463EC 178 | DD5B944A4B82FA2BE3E75E2946D483060DF99277340B0AB65A2042AD088E2B75 179 | BBDAB869D1940F64B50D25078519D18748AD64AC5615EFAAF4F3105B0111AD40 180 | 70EE173ABE6A4ACE486B4E5999158A4377FDA6922FAA6E9305F48570D14BC81F 181 | BFF4C663E1EA9D1E050534F9315A663C4C5DA52CB02EA6408AA473C32CB0CD71 182 | 169BB43C0508A842F400240F0063243B4C459A1FCB3312C41C32ED0EE87F591A 183 | BCB6D5D3830AE4645CB4D40336DB4AB6540B52E70E1EA415CC6D886827EBC5B3 184 | EC35CC5C136243B0C20B3C603B648B132B99D05F9B48263ACFA59A856BE74441 185 | FECF5C6D1FE9D1F4F9942F460961901E16017144C37E83C6822177B2A6C47ECC 186 | 6C47A1104460665E5BCFCF08874008302750EB991CD98D0D8D22B921F90B99B9 187 | 05EE7C39F2BC2A7798157503743C9F2F267BDBE2E8A4CDA7317F81DBF8962E1C 188 | EC02822CC7F770FD4D08D335904375BF0C6DAA0510771627ECB9EE69C0F47D30 189 | 69A87052989DF80D9F4F19F75B070C3689AB3BE0966453F9D56CED6C1745B50D 190 | 813AE6D7E44B73423AB3778ABE4CD2C4DF40E14C5A426043F7057E2DFA2DAA70 191 | EA6723F1C7967FECB1E7C1C0CA283334163FBE31C32254490170C3513580A552 192 | 19A5DD75E6C4ADCB12D33517A03318A6BBC7E4214266E125140D8C40F78A0340 193 | 1F95D9FBEC4DCC55B71E89375AA94B0D55646F6C069561480407D0A3AC127024 194 | D7D1E9ED6B599A2A8766B8792F46D35508B66F302D289405B101A3C6BADA680D 195 | 8C56E2A00B766A4CB155446F862FCF17537A2BE85418E20CD77C4F1F69F70BC6 196 | 17BB5DA8FAA876D0E8BABE273A19C04A8697B3E3CF4725E2C77C8761A9243F24 197 | 96F8AE96399996001A57FD75106745AB4646FB9C6421F1D4EBF3BE533BD11AE8 198 | 14BFBD6D308376B26E08E4ADA490DDCCA94BE8240403D5EB0FE3549061DFB668 199 | 4105B4FE77189546619B6BCF3F9723E278E98D50A17DB8A4C46744FA21760635 200 | 5B332689316BD17C966D466AE737FE3ED7ABC443ADD88D4823A10BC9747ABDEE 201 | 027515AC353A420523F85298029475D8BFD83A2CD00C02CA07974BAA581D2215 202 | A850E6E4C0A5E17E0EDF91C63FAC18C70093F40FEEAF0350B403E2806F4EAE96 203 | BF616A805616EE55C4657418C26CAF54187A6684821B86A76F15088AC4D5B551 204 | 66C3CA8DC61E9810858D1204F899C7E3A1754F483134609F6EEE6364B1CC04FD 205 | 92C86EF194FA3249601AD722D75D1D395CD15A93C768EC60A486AE885683364F 206 | 93DA00A865C1035F913FDA69E7D9A0422880FB81EC23C00427F07A5EA3CCB613 207 | 83C859958AC53FAEA26A6BB39ABA068863CCE3D447720BC31A5136E08EE58963 208 | 093AF587A72112D55853A1048A2B1695DB2D7F13CC924F2F0902071260C33ED6 209 | 30893A04577C0ACF0681C0FEC23E5404F844A83BB5A2F8DE1F0792196139993C 210 | 1152094BC36A6AE5BAB4B8281E506F485F0BAEEBBE71E174B7CED1E52A8242F9 211 | DBDF3E0FBA296538D964EB5B7A4E23B1BB0F59872C9D4FE8498203E3AC08B71E 212 | D2B965AA6CD5D20DA6C83FDC86F4A5D68A58A72C5BB9BFE9BC755C56B24025CE 213 | 6F24443D3CF32CD711D3D94C7C1DC275DDAE3341D840090F97CB6CAEF274C94F 214 | 9F3BD3AAB3F98BA8A7B5CE5E48D1462DAAB37BEB1C10B410E8D33FA42D898183 215 | BD4F807112D78AA94509E33C179BF7C9E82E55AA7D09E128A0DA06A330CF4AF8 216 | 5DC861498CE029CE8C1BD15C923A708F2E7AF98E4F7B34212A0CB417553C86EB 217 | 6DD46B0466F1A21D29FC5111226794ECFCA5DD4240C0B8D106CCD7EA6F03E133 218 | BB7733F055D6FFA04EF5C6F872B4FDA3E42F0F036C4825543D75682ACF71B548 219 | DED160ACD05625274799D0AE201305DA526E01A3D2A719B1B15C05CC09467F3A 220 | 5627860C0F36C503EE392E1786620F3F2287AFE56634E03566B9B1F537FD92A2 221 | 913166228791871A8F8CBA1A1DA634E8224058052A10FE1E67CBD3FD21A6C07E 222 | 243CBF58BDC78577847664EEA5225EB8D6679AB17C563848A9D4D58995EA3609 223 | 51C1443B752A070D9872FE1643F0677019235AC25DC2B29169D38308F2170A1A 224 | A0FDCC59E6602197D2815B914041FFC7106DAAAF30CD97400C6D0826A40385A4 225 | C8520119A065CF32CF2FC5FBD8DFD29222528A7F96FDA533145846B3428F8239 226 | E50277C366418D713F84B12A5FD4F904DC13DB1844A391FDAEB97643A6FD2945 227 | 942FD4FC5A4A35E184F23304B8B4D93D0C37EFCC4E106D4FCD0DA3E5D2117589 228 | 3FFC2BD1D121026562C55C455C3585050B9460891B006F62D9D9B66695C3D348 229 | A467C14C0256FA9621CB056E7CD389505194FF463BCC4010897F9A690EA87D9D 230 | BB3ED4C174FBADB8A4744C6E4A44D773967FD703EC37672F9993DC48BCC8A060 231 | 6CEFE8E6B8F10886E15BA0466AF410B90DF0020FAB88BE493606B6A734EA85BB 232 | 926950EB10D2F2CFDBD182B0F133809612CCF6ACCAD049C8005A42FAF78368B9 233 | E7684F98DE421BE0A3BC0FAEE024A7BE67E15C8394F17FE84DFD8156C2A3E94D 234 | 08259E15CC657E8CE3088395BF6B5F825764E141AE15EBD186DC049261623D26 235 | 8636705E06C6E4A1F8ACAEA59F91B042DF5DB9C2AB986A784384706A43E5F18E 236 | 42C29CC1CA86D4F247B3BBBC89F3633EE074DCA4AC15B1E33EE4822812A62E88 237 | C32B0AA57249980EE17AFC1346074800FA529445D18649A0475246A25CF325A0 238 | BDA06AAF392FD455218B13D9ED577D51A9500B9FB7860716A8E2FB3A8C4BE3B3 239 | 6656C6A5653AEF00184020ACA0BCCBF48BE3BF91E11C8658686C89848E714E6D 240 | DC158DCD1C1BC03B83FF94C60B1DC71CE8A86B46DBE661C9F8F4677F8A2C7CF1 241 | E41A91EBDA2304735147BE66CDFF2673F09D408297302124C127F0B35690CAE9 242 | CE1679120CC4D582FB69550AD34A047DDFCD9D411724554CCED753DB52D6AA7B 243 | 22B0C55EB698ADDBB0F8ED15C971AEF113C74B9E25DA29199237B98DA4023665 244 | C2A63A837E4CAB38F8DF37DBAB5DC80C0C3FA72C8A70DC76B5B36B2EEADDCE74 245 | 23CF794B66E4DD3B35BF99893789063BF7B01D5F186B2FDE518B2CF2EDE51F81 246 | 38244BC64548AC3433A80B86D6A0CA26D77F403C06D65B7394BF1FC7D06D37A5 247 | E70ACD844E3367DE4DB71312CBB85ADD21D5A1F99BB8427F252D90ABB66D7154 248 | EA5AF4A165DF6415A0880AE784071E6B3E2101F0B663DE14DB1ABF8B7CE0E6D9 249 | D24F9CDD9F80028D37C9CB4067A28D41E879AEFECDA71F649EB3C250BFF809D8 250 | 1E427E3BF24E85C75F080D93E0314883988B3A4A2B72A1B4A3D2189AB6ECFFBB 251 | C58151AF05AE335200711ED945E18B4BBCE24A8A162BD9BB26137253BA8B5819 252 | 41E759A7CA7CBA129BAAD438E87189F2F6AE7C86F4EA099DEB23705A500332A7 253 | 4F141D8778EAF3910486B2EA25AAD16B60DD804D0E5BAB0FEBB77BC95EDED08D 254 | B8941E040D99E8F44E70FF842306ACCF65C0AC9673859DB9C3A724238CB8CE62 255 | 255BAF0145692EE3B52643A0DE3E667AD03EEF6C753F57E34AECAB0CFEC7B07A 256 | 150D7151E57BB3A026D50C7A88DF5F480147D87DFEFE463F76122EEB5128796E 257 | 46CB0AF4B537987C2ED552B37D83F393222659DB735F2A293159AD84AF082B95 258 | 6F1454471FC36D805485D619D58FC53FBD6E3F72660ABA559B91ECDEFB267268 259 | 86A75650C3919962B0139409A29F5E3FA70B901CD5D2C49144778CFFF1D5B63E 260 | 099C92AABDAA73D54689812279C95FB7A4F7E840DD53DD3197A4E6D3099446FA 261 | C0032FD40411E8F3300A8A8934B5216B01D916D41DDB32513DC4ACFCEFE43D6B 262 | 22FEF13D3567B047C6B35C477ACF2E172701FDB0FFFBE01DD58D7E54398EF4AF 263 | DA5A404E194BCC39BEADEE5C76D7CD1E602793B950256F25871A9760C80B1EB4 264 | D1E1179C390BC240DA061C9D539B20F4FCFB72DD0C1E860DEA2988E749819787 265 | F04BA7A9CC3EDBF9CDE46895FE31EF0F8DEB63E295E8826BF920C8FEAE3B2080 266 | 8C98DC43DB22C6537028798198E2D3B0453ED725B774686310F635AE6153D9E1 267 | 8A0514882D4CCFE9D2D2465513E42E548F64A50ED78AEC9D62E0F9CF61EBFC9E 268 | E8832D60E91796C916FAFE58F51818B80BBA52C1C06D94E602481654E5378C8D 269 | 137E3A872753CCDE4B2618C031CFB13EE91C91335441C434296DDEF61CDFBF8E 270 | 8FAF25DB3B6D6796FCCE2711938D605AAC00F0A58DD1A03FCE8732DE541E5E8A 271 | 41FC87E1FCA5CD9B5E8D63E7A7D6CEBA67D8A83EAFCF490DB7185AD55ED0F43F 272 | 9A1290E91C463895BD12E8A831DAD661E36E1B01ED4C112B8E1D0991D0294BB9 273 | A13B7E9A8835B12A7133E834379B3477DAD425B7996592FB0395E3B4FEDADF4E 274 | 23A07F6C0E1387DD54F5C8BD071C4E9E4CC98C55882E29B65E5BED61B57EED93 275 | 07FAFD75CF3BB101D1529F83AA8234F70F342B0E531BF23E9A7D1FD112193CAA 276 | 4E377B44F94D9E990C22598C2AE33EA73BD0670A4A000DF78624CE01A25DB30E 277 | 22B0EE3FA892AF673E323BE5D63D0929903FB45F56B11CC718EF5A690A776E28 278 | 8BEED7010ADB1076B23257F9484533DB8A86604B70B9F2A25A7DD70F13B65172 279 | 5C3DAA77885AE348CE4EE02021BDDEF16B58AA7B006877B630C97653DE7C661E 280 | D5D3477A1BBEB61FFB1A113E316EB366EBDDD54C621987FEF09432023A2EA97D 281 | 0B4D38229799F6656F7BB8E67379FC4652ADF65179AACF7FC9CB925A84ABECFC 282 | 00B4212CDF4E88876C2DD4E79099677C4A5A97860D9D1615587D68A4AC7A7204 283 | 47E421FC84C21560E71E1FC313776DC3717FB397BEC643F896029B671A199342 284 | 6378241B3230E538EA282FD38ABE642223D82A657245105757BE84B599C63F2B 285 | 008CE276FC313D6A101898D1A6BB0AD8D5662A85E965F7E78373DDFD3C560DC7 286 | FB06BB67D226D4263EE9491F90E104FC210E9E3DD63F800300FA77DBF9FD0C93 287 | 0B51906FE3A19B317816A4402F7733C8CE2C7502D915352458CFD581FD484523 288 | 62678E94F04F8882948A1C35B484047CAAB70808134426970C83A1C4698C66A8 289 | C900013C14ED7BA85AA037F846CD7C05F0C4B63DC1C10E03B9D34C6727AACF15 290 | 4653EEE73AD08E20791CDD3CF827BA903093EB249DA239F1DFE9775FBAA1495A 291 | 572719400771332BF0154A0C26166D5A49AD37BCFC2E5AE335976E04B58E7646 292 | D2914E385942BF82AF3E3DC997624DC06C800A6F598F073FCAFF807F256B3112 293 | 82C18E8068ED0C375669081A6386CE0BC4F6A3CDF009DBD0976CC1C9D0C7C8B6 294 | 147928A042882E1E8076D42988AB29A83A2458F3CFEE4D4DF2FA84E2F4388F7B 295 | 9FAE2D16C78DB978CA99B7CD78F3DE7518A6AF93A4B40C506C136E5A5F56AEA9 296 | C6C4D28960B5A34CBF2A115403A2E98751715251D4CF99D8DB478BEA75D64BD6 297 | 3E250047E910F5587847B9B4F6BC43819114D9530E8A6887BD14D5580C88C587 298 | 1E9038CC6862A415E904219926DB8079F7CCDC19CBE16973BC49D80BF0F83B4E 299 | B5D4796E42FB7053D4CD9FEEB842D350BCA07575CE998B37E6594CC0A406B20C 300 | 54AE6DF2CA7EEE4F17AAEF29B1E846118AC9F0B2E42C04E3E4FFB1AFB1D8DCB2 301 | B603A43666989974543B3403BE4C0F1E2E9A6A644510454A0A41DEBAE7BF7A16 302 | F17D7ED921FA514C6B9EEF4C5804E7FDFBB5D0A20D24A42737244C9FD4BAC0C5 303 | A0DDEF520D8F11FBF9BAD49EB6AA56C58AEA63B1DE7CEFC00134596638F0312E 304 | A4E4889A6B53CE50F4F1A779EB21F89BE7A498AAEF4F1CBF32C677CBA5B7E1B0 305 | 74AD2A4B6D241002CC42F60DAAE1AA47F6E62E040DA7DB5EBBF4E92E3A44B1EB 306 | 69C07BCB431ED5309F0C40EAC810A64EC8DEDB0B882F1BF80AAB55232C013EF1 307 | DB00E07C9D056792BF62099824474D3A425032182D82767BE860B5A5E9039E91 308 | 62DDBDC562999AE378DF41F4ABF75A6EC87BEDD4D170867E048831A80BDF18D3 309 | 62B0E17CAAC2D341CA3CA1E587A0CE3B2FF777F6F48D679859F4D46B8BD489C4 310 | DBF23BB9D8AEC3E80C08D92B04857F0A0284D0FB9F7F08A2C146A040FB47291E 311 | 14B3231866D2553383A0ECBBB7E7E2D61F448A6F8B3DA1D5FEDBA71BC26F5816 312 | D94F052DDFC5CE593B8500B83522DDDEC7ACD57B6FA9C9FF0939D7E9E348DF5A 313 | 13B4B8FB575789DA50E9777C09DA0E06BF9729D26AFA0626A9869415B6F57593 314 | 3590DA6D78D23C54C6B132080836DC3B8B142A849BBEDDBC291AEFE5F4A12134 315 | A8479BCAEB272DDD32D44966F4438C181FF987B9B8F2AAFF2A23CF53B12E7DEF 316 | AA6C1ECB39E0717C291DE7C860A6C7FAF1AF1F735C820FDC589591C61A0C9A91 317 | 7EFA971BDDF8072B23C90B2D47FCADD515E7E64E81365F8BE470AED1E001A07E 318 | 830AD52A560AE243D9153860C25B8FB6C14A5E03874A69ED8BB13DE79A0B265D 319 | 2E2623D693805EE38EA14A75219ED8A610A827901B39F4D912F7AC6AE0ACD519 320 | 387AF5A3063E4CCF5213D1C0C9F049098089162B0C6B532C5011C15FE5AC5260 321 | 658D5913919E9DE5C77B86EA9D561F57DA1C0FB2CB40E13FA4E79E72D95D5D42 322 | 56ACED0107B74A5C408FDC2DD7272A0B462EEB7CE6915D7734DE823132D4766F 323 | C3E9CA6D6B8DB40F44148524D979013953D158208890D1FF50DE71B19889C067 324 | A88ABA5BA95B1E501AB399AF2C6B076351DAF37ADFE0256D643CBC589E93D00B 325 | 1AB478482F9041012AD73BE96025F5F837BEAE61196EEB161803504844067AA6 326 | 1909FE4DA3CD8387C9C44D40EFA159854BE845A7D9E93A60B6C7B256E4D65086 327 | 34B4195BCD1E7DE8B4A0EBA9D5C17702199E27116E29B761489CDD720A8E4592 328 | E3286A0673790251C793E97A8D8E553332B08E85B240E0B56FE553D12F98A6F3 329 | 88213670B0A1A1AFAD294977296F6CF309DA03D9D4CCC28E347A0505897B57EF 330 | DE926002B5F174F607D390C010EB2508E923FDBA41517DD465B2273E4EBFEC09 331 | BC5C9E7302EBE72B798C9CF080D0B9156CBCDCE05FCE9CC11405203BC774FB98 332 | 4ADEB79258C48CDF801BF41C64260706ACE27FF66E97129BF509F326DC9A4D4A 333 | 7B9C884237AEBA67251EAD29ACF49E7A9089B0C7C5EB8A75656D2673B7DEC3C8 334 | 0D28C656A52E126379A165479BE78E1750A33881D1139AD0702B4F8EDBD52BFB 335 | 3CCD9F29136536DE8CD677F29A0FC188783E85354CF17F2AD464D15C51574D3D 336 | 36DB3C8768BE1F9C7E183BA5CEF0AFCFB42C94512101D595103A9DF37E8427F9 337 | 35199FE6DB4624EAFB396D54D576670A83E16175D20BA8FC33546076E373E87C 338 | 39C05041A854B7DB99413B0B56757507C514A5FA35B1C899AFC32695D77BFC0F 339 | BC69AD9AA9D38A32CC46CF4CE4C1BDC8F18252287C7834FEA7D12F8C0C30104D 340 | 95B042A94F44F50DE7DD3904447A66BBF478CE1CB2A6BD6CF71B6CDE122F4B53 341 | 180D1B9472CE7E0CABEB6147FD766DDBAC319451B9FBC6AB1CEDBC6421246FA8 342 | 9366C761FA1F929F41F1AC607935F19813DC7A3A53 343 | 0000000000000000000000000000000000000000000000000000000000000000 344 | 0000000000000000000000000000000000000000000000000000000000000000 345 | 0000000000000000000000000000000000000000000000000000000000000000 346 | 0000000000000000000000000000000000000000000000000000000000000000 347 | 0000000000000000000000000000000000000000000000000000000000000000 348 | 0000000000000000000000000000000000000000000000000000000000000000 349 | 0000000000000000000000000000000000000000000000000000000000000000 350 | 0000000000000000000000000000000000000000000000000000000000000000 351 | cleartomark 352 | {restore}if 353 | %%EndResource 354 | %%EndProlog 355 | %%BeginSetup 356 | /cmbx10 /GJGAYE-CMBX10 def 357 | %%EndSetup 358 | %%Page: 1 1 359 | 0 0 0 setrgbcolor 360 | newpath -42.51968 0 moveto 361 | -42.51968 -23.48297 -23.48297 -42.51968 0 -42.51968 curveto 362 | 23.48297 -42.51968 42.51968 -23.48297 42.51968 0 curveto 363 | 42.51968 18.1975 12.39862 24.79724 0 0 curveto 364 | -12.39862 -24.79724 -42.51968 -18.1975 -42.51968 0 curveto closepath fill 365 | 0 0.5 dtransform truncate idtransform setlinewidth pop [] 0 setdash 366 | 1 setlinejoin 10 setmiterlimit 367 | newpath -42.51968 0 moveto 368 | -42.51968 -23.48297 -23.48297 -42.51968 0 -42.51968 curveto 369 | 23.48297 -42.51968 42.51968 -23.48297 42.51968 0 curveto 370 | 42.51968 23.48297 23.48297 42.51968 0 42.51968 curveto 371 | -23.48297 42.51968 -42.51968 23.48297 -42.51968 0 curveto closepath stroke 372 | -32.23944 -3.45926 moveto 373 | (data) cmbx10 9.96265 fshow 374 | 1 1 1 setrgbcolor 375 | 5.63379 -3.16275 moveto 376 | (ev) cmbx10 9.96265 fshow 377 | 16.61339 -3.16275 moveto 378 | (en) cmbx10 9.96265 fshow 379 | 27.91129 -3.16275 moveto 380 | (ts) cmbx10 9.96265 fshow 381 | showpage 382 | %%EOF 383 | -------------------------------------------------------------------------------- /dist/menrva.standalone.js: -------------------------------------------------------------------------------- 1 | !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.menrva=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o signal.log (@ : Signal a, args...) : Unsubscriber 21 | 22 | Essentially `signal.onValue(console.log.bind(console, args...)) 23 | */ 24 | signal.Signal.prototype.log = function () { 25 | var args = Array.prototype.slice.call(arguments); 26 | return this.onValue(function (x) { 27 | var logArgs = args.concat([x]); 28 | console.log.apply(console, logArgs); 29 | }); 30 | }; 31 | 32 | /** 33 | #### signal.onSpread 34 | 35 | > signal.onSpread (@ : Signal [a, b...], callback : a -> b ... -> void) : Unsubscriber 36 | 37 | `onValue` with signal's tuple arguments spread. 38 | */ 39 | signal.Signal.prototype.onSpread = function (callback) { 40 | return this.onValue(function (t) { 41 | callback.apply(undefined, t); 42 | }); 43 | }; 44 | 45 | /** 46 | #### tuple 47 | 48 | > tuple (x : Signal a, y : Signal b...) : Signal [a, b...] 49 | 50 | Combine signals into tuple. 51 | */ 52 | 53 | function tuple() { 54 | var signals = Array.prototype.slice.call(arguments); 55 | var mapped = new (signal.CombinedSignal)(signals, util.identity); 56 | 57 | // connect to parent 58 | signals.forEach(function (parent) { 59 | parent.children.push(mapped); 60 | }); 61 | 62 | return mapped; 63 | } 64 | 65 | /** 66 | #### sequence 67 | 68 | > sequence [Signal a, Signal b, ..] : Signal [a, b...] 69 | 70 | In promise libraries this might be called `all`. 71 | */ 72 | function sequence(signals) { 73 | var mapped = new (signal.CombinedSignal)(signals, util.identity); 74 | 75 | // connect to parent 76 | signals.forEach(function (parent) { 77 | parent.children.push(mapped); 78 | }); 79 | 80 | return mapped; 81 | } 82 | 83 | /** 84 | #### record 85 | 86 | > record {k: Signal a, l: Signal b...} : Signal {k: a, l: b...} 87 | 88 | Like `sequence` but for records i.e. objects. 89 | */ 90 | 91 | function record(rec) { 92 | var keys = []; 93 | var signals = []; 94 | 95 | for (var k in rec) { 96 | // if (Object.prototype.hasOwnProperty.call(rec, k)) { 97 | keys.push(k); 98 | signals.push(rec[k]); 99 | // } 100 | } 101 | 102 | function toObject(values) { 103 | var res = {}; 104 | 105 | for (var i = 0; i < keys.length; i++) { 106 | res[keys[i]] = values[i]; 107 | } 108 | 109 | return res; 110 | } 111 | 112 | var mapped = new (signal.CombinedSignal)(signals, toObject); 113 | 114 | // connect to parent 115 | signals.forEach(function (parent) { 116 | parent.children.push(mapped); 117 | }); 118 | 119 | return mapped; 120 | } 121 | 122 | module.exports = { 123 | tuple: tuple, 124 | sequence: sequence, 125 | record: record, 126 | }; 127 | 128 | },{"./signal.js":6,"./util.js":8}],2:[function(require,module,exports){ 129 | /* 130 | * menrva 131 | * https://github.com/phadej/menrva 132 | * 133 | * Copyright (c) 2014 Oleg Grenrus 134 | * Licensed under the MIT license. 135 | */ 136 | 137 | "use strict"; 138 | 139 | /** 140 | ### Equalities 141 | 142 | #### egal 143 | 144 | > egal (a, b) : boolean 145 | 146 | Identity check. `Object.is`. http://wiki.ecmascript.org/doku.php?id=harmony:egal 147 | */ 148 | function egal(a, b) { 149 | if (a === 0 && b === 0) { 150 | return 1/a === 1/b; 151 | } else if (a !== a) { 152 | return b !== b; 153 | } else { 154 | return a === b; 155 | } 156 | } 157 | 158 | module.exports = egal; 159 | 160 | },{}],3:[function(require,module,exports){ 161 | /* 162 | * menrva 163 | * https://github.com/phadej/menrva 164 | * 165 | * Copyright (c) 2014 Oleg Grenrus 166 | * Licensed under the MIT license. 167 | */ 168 | 169 | "use strict"; 170 | 171 | var util = require("./util.js"); 172 | var signal = require("./signal.js"); 173 | 174 | /** 175 | ### Lens 176 | 177 | Lenses are composable functional references. 178 | They allow you to *access* and *modify* data potentially very deep within a structure! 179 | */ 180 | function Lens(parent, path, eq) { 181 | this.parents = [parent]; 182 | this.path = path.split(/\./); 183 | var value = this.calculate(); 184 | signal.initSignal(this, value, eq); 185 | } 186 | 187 | Lens.prototype = new signal.Signal(); 188 | 189 | Lens.prototype.calculateRank = function () { 190 | return this.parents[0].rank + 1; 191 | }; 192 | 193 | Lens.prototype.calculate = function () { 194 | return util.getPath(this.parents[0].v, this.path); 195 | }; 196 | 197 | /** 198 | #### source.zoom 199 | 200 | > zoom (@ : Source a, path : Path a b, eq = egal : b -> b -> boolean) : Source b 201 | 202 | Zoom (or focus) into part specified by `path` of the original signal. 203 | One can `set` and `modify` zoomed signals, they act as sources. 204 | 205 | ```js 206 | var quux = source.zoom("foo.bar.quux"); 207 | ``` 208 | */ 209 | signal.Source.prototype.zoom = Lens.prototype.zoom = function(f, eq) { 210 | var mapped = new Lens(this, f, eq); 211 | this.children.push(mapped); 212 | return mapped; 213 | }; 214 | 215 | Lens.prototype.set = function (tx, value) { 216 | var path = this.path; 217 | this.parents[0].modify(tx, function (obj) { 218 | return util.setPath(obj, path, value); 219 | }); 220 | }; 221 | 222 | Lens.prototype.modify = function (tx, f) { 223 | var path = this.path; 224 | this.parents[0].modify(tx, function (obj) { 225 | return util.modifyPath(obj, path, f); 226 | }); 227 | }; 228 | 229 | },{"./signal.js":6,"./util.js":8}],4:[function(require,module,exports){ 230 | /* 231 | * menrva 232 | * https://github.com/phadej/menrva 233 | * 234 | * Copyright (c) 2014 Oleg Grenrus 235 | * Licensed under the MIT license. 236 | */ 237 | 238 | /** 239 | # menrva 240 | 241 | 242 | 243 | [![Build Status](https://secure.travis-ci.org/phadej/menrva.svg?branch=master)](http://travis-ci.org/phadej/menrva) 244 | [![NPM version](http://img.shields.io/npm/v/menrva.svg)](https://www.npmjs.org/package/menrva) 245 | [![Dependency Status](https://david-dm.org/phadej/menrva.svg)](https://david-dm.org/phadej/menrva) 246 | [![devDependency Status](https://david-dm.org/phadej/menrva/dev-status.svg)](https://david-dm.org/phadej/menrva#info=devDependencies) 247 | [![Coverage Status](https://img.shields.io/coveralls/phadej/menrva.svg)](https://coveralls.io/r/phadej/menrva?branch=master) 248 | [![Code Climate](http://img.shields.io/codeclimate/github/phadej/menrva.svg)](https://codeclimate.com/github/phadej/menrva) 249 | 250 | Ambitious data-flow library. 251 | 252 | ## Getting Started 253 | Install the module with: `npm install menrva` 254 | 255 | ```js 256 | var menrva = require('menrva'); 257 | menrva.some('awe'); // some, as in awesome? 258 | ``` 259 | 260 | ## API 261 | */ 262 | /// include signal.js 263 | /// include transaction.js 264 | /// include lens.js 265 | /// include convenience.js 266 | /// include egal.js 267 | /// include option.js 268 | /** 269 | ## Contributing 270 | */ 271 | /// plain ../CONTRIBUTING.md 272 | /** 273 | ## Release History 274 | */ 275 | /// plain ../CHANGELOG.md 276 | /** 277 | 278 | ## License 279 | 280 | Copyright (c) 2014 Oleg Grenrus. 281 | Licensed under the MIT license. 282 | */ 283 | 284 | "use strict"; 285 | 286 | var egal = require("./egal.js"); 287 | var option = require("./option.js"); 288 | var signal = require("./signal.js"); 289 | var transaction = require("./transaction.js"); 290 | 291 | // extensions 292 | require("./lens.js"); 293 | var convenience = require("./convenience.js"); 294 | 295 | // version 296 | var version = "0.0.7"; 297 | 298 | module.exports = { 299 | egal: egal, 300 | some: option.some, 301 | none: option.none, 302 | Signal: signal.Signal, 303 | source: signal.source, 304 | combine: signal.combine, 305 | tuple: convenience.tuple, 306 | sequence: convenience.sequence, 307 | record: convenience.record, 308 | transaction: transaction, 309 | version: version, 310 | }; 311 | 312 | },{"./convenience.js":1,"./egal.js":2,"./lens.js":3,"./option.js":5,"./signal.js":6,"./transaction.js":7}],5:[function(require,module,exports){ 313 | /* 314 | * menrva 315 | * https://github.com/phadej/menrva 316 | * 317 | * Copyright (c) 2014 Oleg Grenrus 318 | * Licensed under the MIT license. 319 | */ 320 | 321 | "use strict"; 322 | 323 | var egal = require("./egal.js"); 324 | var util = require("./util.js"); 325 | 326 | // typify: instance Option 327 | // Option type 328 | 329 | /** 330 | ### Option 331 | 332 | Also known as `Maybe`. 333 | */ 334 | function Option() {} 335 | 336 | // Some 337 | function Some(x) { 338 | this.value = x; 339 | } 340 | 341 | Some.prototype = new Option(); 342 | 343 | function some(x) { 344 | return new Some(x); 345 | } 346 | 347 | // None 348 | function None() {} 349 | 350 | None.prototype = new Option(); 351 | 352 | var none = new None(); 353 | 354 | // Methods 355 | 356 | /** 357 | #### option.equals 358 | 359 | > equals (@ : option a, other : *, eq = eqal : a -> a -> boolean) : boolean 360 | 361 | Equality check. 362 | */ 363 | Some.prototype.equals = function (other, eq) { 364 | eq = eq || egal; 365 | return other instanceof Some && eq(this.value, other.value); // TODO: use egal 366 | }; 367 | 368 | None.prototype.equals = function (other) { 369 | // only one instance of `none` 370 | return this === other; 371 | }; 372 | 373 | /** 374 | #### option.map 375 | 376 | > map (@ : option a, f : a -> b) : option b 377 | */ 378 | // :: fn -> Option 379 | Some.prototype.map = function (f) { 380 | return some(f(this.value)); 381 | }; 382 | 383 | // :: fn -> Option 384 | None.prototype.map = function (f) { 385 | return none; 386 | }; 387 | 388 | /** 389 | #### option.elim 390 | 391 | > elim (@ : option a, x : b, f : a -> b) : b 392 | 393 | */ 394 | Some.prototype.elim = function (x, f) { 395 | return f(this.value); 396 | }; 397 | 398 | None.prototype.elim = function (x, f) { 399 | return x; 400 | }; 401 | 402 | /** 403 | #### option.orElse 404 | 405 | > orElse (@ : option a, x : a) : a 406 | */ 407 | Option.prototype.orElse = function (x) { 408 | return this.elim(x, util.identity); 409 | }; 410 | 411 | module.exports = { 412 | some: some, 413 | none: none, 414 | }; 415 | 416 | },{"./egal.js":2,"./util.js":8}],6:[function(require,module,exports){ 417 | /* 418 | * menrva 419 | * https://github.com/phadej/menrva 420 | * 421 | * Copyright (c) 2014 Oleg Grenrus 422 | * Licensed under the MIT license. 423 | */ 424 | 425 | "use strict"; 426 | 427 | var egal = require("./egal.js"); 428 | var util = require("./util.js"); 429 | 430 | /** 431 | ### Signal 432 | 433 | The core type of menrva. `Signal` is abstract class, and cannot be created explicitly. 434 | 435 | Similar concepts are: *Behaviours* in FRP, *Properties* in bacon.js. 436 | 437 | You can add methods to `Signal`'s prototype. They will be available on all signals. 438 | */ 439 | function Signal() {} 440 | 441 | // Each signal has an unique index. 442 | var index = 0; 443 | 444 | function initSignal(signal, value, eq) { 445 | signal.children = []; 446 | signal.callbacks = []; 447 | signal.v = value; 448 | 449 | // `index` is used to implement faster sets of signals 450 | signal.index = index++; 451 | 452 | // `rank` is used to sort signals topologically 453 | signal.rank = signal.calculateRank(); 454 | 455 | // `eq` is an equality decision function on the signal values 456 | signal.eq = eq || egal; 457 | } 458 | 459 | function CombinedSignal(parents, f, eq) { 460 | this.parents = parents; 461 | this.f = f; 462 | var value = this.calculate(); 463 | initSignal(this, value, eq); 464 | } 465 | 466 | CombinedSignal.prototype = new Signal(); 467 | 468 | // rank of combined signal is 1 + maximum rank of parents 469 | CombinedSignal.prototype.calculateRank = function () { 470 | return Math.max.apply(Math, util.pluck(this.parents, "rank")) + 1; 471 | }; 472 | 473 | CombinedSignal.prototype.calculate = function () { 474 | return this.f.call(undefined, util.pluck(this.parents, "v")); 475 | }; 476 | 477 | /** 478 | #### signal.map 479 | 480 | > map (@ : Signal a, f : a -> b, eq = egal : b -> b -> boolean) : Signal b 481 | */ 482 | Signal.prototype.map = function(f, eq) { 483 | var mapped = new CombinedSignal([this], function (xs) { 484 | return f(xs[0]); 485 | }, eq); 486 | this.children.push(mapped); 487 | return mapped; 488 | }; 489 | 490 | /** 491 | #### signal.onValue 492 | 493 | > onValue (@ : Signal a, callback : a -> void) -> Unsubscriber 494 | 495 | Add value callback. `callback` is immediately executed with the current value of signal. 496 | After than `callback` will be called, each time signal's value changes. 497 | 498 | The return value is a function, which will remove the callback if executed. 499 | */ 500 | Signal.prototype.onValue = function (callback) { 501 | // we wrap callback in function, to make it unique 502 | var wrapped = function (x) { callback(x); }; 503 | 504 | // add to callbacks list 505 | this.callbacks.push(wrapped); 506 | 507 | // execute the callback *synchronously* 508 | callback(this.v); 509 | 510 | // return unsubscriber 511 | var that = this; 512 | return function () { 513 | var index = that.callbacks.indexOf(wrapped); 514 | if (index !== -1) { 515 | that.callbacks.splice(index, 1); 516 | } 517 | }; 518 | }; 519 | 520 | /** 521 | #### signal.value() 522 | 523 | > value (@ : Signal a): Signal a 524 | 525 | Returns the current value of signal. 526 | */ 527 | Signal.prototype.value = function() { 528 | return this.v; 529 | }; 530 | 531 | /** 532 | ### Source 533 | 534 | A signal which value you can set. 535 | 536 | Similar concepts are: *Bacon.Model* in bacon.js, *BehaviourSubject* in Rx. 537 | 538 | 539 | #### source 540 | 541 | > source (initialValue : a, eq = egal : a -> a -> boolean) : Source a 542 | */ 543 | function Source(initialValue, eq) { 544 | initSignal(this, initialValue, eq); 545 | } 546 | 547 | function source(initialValue, eq) { 548 | return new Source(initialValue, eq); 549 | } 550 | 551 | Source.prototype = new Signal(); 552 | 553 | Source.prototype.calculateRank = function () { 554 | return 0; 555 | }; 556 | 557 | /** 558 | #### source.set 559 | 560 | > set (@ : Source a, tx : Transaction, value : a) : void 561 | */ 562 | Source.prototype.set = function (transaction, value) { 563 | transaction.addAction({ 564 | type: "set", 565 | signal: this, 566 | value: value, 567 | }); 568 | }; 569 | 570 | /** 571 | #### source.modify 572 | 573 | > modify (@ : Source a, tx : Transaction, f : a -> a) : void 574 | 575 | Mofify source value. `f` will be called with current value of signal inside the transaction. 576 | */ 577 | Source.prototype.modify = function (transaction, f) { 578 | transaction.addAction({ 579 | type: "modify", 580 | signal: this, 581 | f: f, 582 | }); 583 | }; 584 | 585 | 586 | /** 587 | ### Signal combinators 588 | */ 589 | 590 | /** 591 | #### combine 592 | 593 | > combine (Signal a..., f : a... -> b) : Signal b 594 | 595 | Applicative n-ary lift. Lift pure function to operate on signals: 596 | ```js 597 | var $sum = menrva.combine($a, $b, function (a, b) { 598 | return a + b; 599 | }); 600 | ``` 601 | */ 602 | function combine() { 603 | var signals = Array.prototype.slice.call(arguments, 0, -1); 604 | var f = arguments[arguments.length - 1]; 605 | 606 | var mapped = new CombinedSignal(signals, function (values) { 607 | return f.apply(undefined, values); 608 | }); 609 | 610 | // connect to parent 611 | signals.forEach(function (parent) { 612 | parent.children.push(mapped); 613 | }); 614 | 615 | return mapped; 616 | } 617 | 618 | module.exports = { 619 | Signal: Signal, 620 | Source: Source, 621 | source: source, 622 | combine: combine, 623 | initSignal: initSignal, 624 | // Internal: 625 | CombinedSignal: CombinedSignal, 626 | }; 627 | 628 | },{"./egal.js":2,"./util.js":8}],7:[function(require,module,exports){ 629 | /* 630 | * menrva 631 | * https://github.com/phadej/menrva 632 | * 633 | * Copyright (c) 2014 Oleg Grenrus 634 | * Licensed under the MIT license. 635 | */ 636 | 637 | "use strict"; 638 | 639 | var util = require("./util.js"); 640 | 641 | /** 642 | ### Transaction 643 | 644 | One gathers atomic updates into single transaction (to avoid glitches). 645 | 646 | ```js 647 | var tx = menrva.transaction(); 648 | sourceA.set(tx, 42); 649 | sourceB.modify(tx, function (x) { return x + x; }); 650 | tx.commit(); // not necessary, transactions are auto-commited 651 | ``` 652 | 653 | There are also optional syntaxes for simple transactions: 654 | ```js 655 | menrva.transaction() 656 | .set(sourceA, 42) 657 | .modify(sourceB, function (x) { return x + x; }) 658 | .commit(); 659 | ``` 660 | or even 661 | ```js 662 | menrva.transaction([sourceA, 42, sourceB, function(x) { return x + x; }]).commit(); 663 | ``` 664 | */ 665 | function Transaction() { 666 | this.actions = []; 667 | this.commitScheduled = false; 668 | } 669 | 670 | /** 671 | #### transaction 672 | 673 | > transaction (facts) : Transaction 674 | 675 | Create transaction. 676 | 677 | Shorthand syntax: 678 | 679 | > transaction ([sourceA, valueA, sourceB, valueB ...]) : Transaction 680 | 681 | If `value` is function, `source.modify(tx, value)` is called; otherwise `source.set(tx, value)`. 682 | */ 683 | function transaction(facts) { 684 | var tx = new Transaction(); 685 | 686 | if (Array.isArray(facts)) { 687 | var len = facts.length; 688 | for (var i = 0; i < len; i += 2) { 689 | var source = facts[i]; 690 | var value = facts[i + 1]; 691 | if (typeof value === "function") { 692 | source.modify(tx, value); 693 | } else { 694 | source.set(tx, value); 695 | } 696 | } 697 | } 698 | 699 | return tx; 700 | } 701 | 702 | /** 703 | #### transaction.commit 704 | 705 | Commit the transaction, forcing synchronous data propagation. 706 | */ 707 | 708 | function calculateUpdates(actions) { 709 | var updates = {}; 710 | var len = actions.length; 711 | for (var i = 0; i < len; i++) { 712 | var action = actions[i]; 713 | // find update fact for signal 714 | var update = updates[action.signal.index]; 715 | 716 | // if not update found, create new for action's signal 717 | if (!update) { 718 | update = { 719 | signal: action.signal, 720 | value: action.signal.v, 721 | }; 722 | updates[action.signal.index] = update; 723 | } 724 | 725 | // perform action 726 | switch (action.type) { 727 | case "set": 728 | update.value = action.value; 729 | break; 730 | case "modify": 731 | update.value = action.f(update.value); 732 | break; 733 | } 734 | } 735 | 736 | return util.values(updates); 737 | } 738 | 739 | function initialSet(updates) { 740 | var updated = []; 741 | var len = updates.length; 742 | for (var i = 0; i < len; i++) { 743 | var update = updates[i]; 744 | // if different value 745 | if (!update.signal.eq(update.signal.v, update.value)) { 746 | // set it 747 | update.signal.v = update.value; 748 | 749 | // collect updated source signal 750 | updated.push(update.signal); 751 | } 752 | } 753 | return updated; 754 | } 755 | 756 | function triggerOnValue(updated) { 757 | var len = updated.length; 758 | for (var i = 0; i < len; i++) { 759 | var updatedSignal = updated[i]; 760 | var value = updatedSignal.v; 761 | var callbacks = updatedSignal.callbacks; 762 | var callbacksLen = callbacks.length; 763 | for (var j = 0; j < callbacksLen; j++) { 764 | callbacks[j](value); 765 | } 766 | } 767 | } 768 | 769 | Transaction.prototype.commit = function () { 770 | 771 | // clear timeout 772 | if (this.commitScheduled) { 773 | clearTimeout(this.commitScheduled); 774 | this.commitScheduled = false; 775 | } 776 | 777 | // If nothing to do, short circuit 778 | if (this.actions.length === 0) { 779 | return; 780 | } 781 | 782 | // Data flow 783 | 784 | // traverse actions to aquire new values 785 | var updates = calculateUpdates(this.actions); 786 | 787 | // Apply updates, and collect updated signals 788 | var updated = initialSet(updates); 789 | 790 | // seed propagation push-pull propagation with children of updated sources 791 | var signals = {}; 792 | updated.forEach(function (update) { 793 | update.children.forEach(function (child) { 794 | signals[child.index] = child; 795 | }); 796 | }); 797 | 798 | // until there aren't any signals 799 | while (!util.objIsEmpty(signals)) { 800 | // minimum rank 801 | var rank = Infinity; 802 | for (var rankK in signals) { 803 | rank = Math.min(rank, signals[rankK].rank); 804 | } 805 | 806 | var next = []; 807 | var curr = []; 808 | 809 | for (var k in signals) { 810 | var signal = signals[k]; 811 | // skip signals of different (larger!) rank 812 | if (signal.rank !== rank) { 813 | continue; 814 | } 815 | 816 | // new value 817 | var value = signal.calculate(); 818 | 819 | // if value is changed 820 | if (!signal.eq(signal.v, value)) { 821 | // set the value 822 | signal.v = value; 823 | 824 | // add signal to updated list 825 | updated.push(signal); 826 | 827 | // add children of updated signal to list of traversable signals 828 | var childrenlen = signal.children.length; 829 | for (var childIdx = 0; childIdx < childrenlen; childIdx++) { 830 | var child = signal.children[childIdx]; 831 | next.push(child); 832 | } 833 | } 834 | 835 | // we are done with this signal 836 | curr.push(signal.index); 837 | } 838 | 839 | // Remove traversed 840 | var currLen = curr.length; 841 | for (var currIdx = 0; currIdx < currLen; currIdx++) { 842 | delete signals[curr[currIdx]]; 843 | } 844 | 845 | // add next 846 | var nextLen = next.length; 847 | for (var nextIdx = 0; nextIdx < nextLen; nextIdx++) { 848 | signals[next[nextIdx].index] = next[nextIdx]; 849 | } 850 | } 851 | 852 | // Trigger onValue callbacks 853 | triggerOnValue(updated); 854 | 855 | // rest cleanupg 856 | this.actions = []; 857 | }; 858 | 859 | /** 860 | #### transaction.rollback 861 | 862 | Rollback the transaction. Maybe be called multiple times (consecutives calls are no-op). 863 | 864 | *Note: for now `rollback` only resets the pending actions in transactions. Transaction is still valid, and more actions can be added* 865 | */ 866 | Transaction.prototype.rollback = function() { 867 | if (this.commitScheduled) { 868 | clearTimeout(this.commitScheduled); 869 | } 870 | this.actions = []; 871 | this.commitScheduled = false; 872 | }; 873 | 874 | Transaction.prototype.deferCommit = function () { 875 | if (!this.commitScheduled) { 876 | var tx = this; 877 | this.commitScheduled = setTimeout(function () { 878 | tx.commit(); 879 | }); 880 | } 881 | }; 882 | 883 | Transaction.prototype.addAction = function (action) { 884 | this.actions.push(action); 885 | this.deferCommit(); 886 | }; 887 | 888 | Transaction.prototype.set = function (source, value) { 889 | source.set(this, value); 890 | return this; 891 | }; 892 | 893 | Transaction.prototype.modify = function (source, f) { 894 | source.modify(this, f); 895 | return this; 896 | }; 897 | 898 | module.exports = transaction; 899 | 900 | },{"./util.js":8}],8:[function(require,module,exports){ 901 | /* 902 | * menrva 903 | * https://github.com/phadej/menrva 904 | * 905 | * Copyright (c) 2014 Oleg Grenrus 906 | * Licensed under the MIT license. 907 | */ 908 | 909 | "use strict"; 910 | 911 | function identity(x) { 912 | return x; 913 | } 914 | 915 | function pluck(arr, property) { 916 | var len = arr.length; 917 | var res = new Array(len); 918 | for (var i = 0; i < len; i++) { 919 | res[i] = arr[i][property]; 920 | } 921 | return res; 922 | } 923 | 924 | function values(obj) { 925 | var arr = []; 926 | for (var k in obj) { 927 | arr.push(obj[k]); 928 | } 929 | return arr; 930 | } 931 | 932 | function objIsEmpty(obj) { 933 | /* jshint unused:false */ 934 | for (var k in obj) { 935 | return false; 936 | } 937 | return true; 938 | } 939 | 940 | function getPath(obj, path) { 941 | var len = path.length; 942 | for (var i = 0; i < len; i++) { 943 | if (obj === undefined || obj === null) { 944 | return obj; 945 | } else { 946 | obj = obj[path[i]]; 947 | } 948 | } 949 | return obj; 950 | } 951 | 952 | function setProperty(obj, property, value) { 953 | var copy = {}; 954 | for (var k in obj) { 955 | copy[k] = obj[k]; 956 | } 957 | copy[property] = value; 958 | return copy; 959 | } 960 | 961 | function setPath(obj, path, value) { 962 | var len = path.length; 963 | var acc = value; 964 | for (var i = len; i > 0; i--) { 965 | var next = getPath(obj, path.slice(0, i - 1)); 966 | acc = setProperty(next, path[i - 1], acc); 967 | } 968 | return acc; 969 | } 970 | 971 | function modifyPath(obj, path, f) { 972 | return setPath(obj, path, f(getPath(obj, path))); 973 | } 974 | 975 | module.exports = { 976 | identity: identity, 977 | pluck: pluck, 978 | values: values, 979 | objIsEmpty: objIsEmpty, 980 | getPath: getPath, 981 | setPath: setPath, 982 | modifyPath: modifyPath, 983 | }; 984 | 985 | },{}]},{},[4])(4) 986 | }); --------------------------------------------------------------------------------