├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── docs ├── crown.svg ├── index.html └── raj.svg ├── index.js ├── package.json └── test └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | package-lock.json 61 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - node 4 | deploy: 5 | provider: npm 6 | email: christopher.andrejewski@gmail.com 7 | api_key: 8 | secure: rgQRT6Sd89F0ywRkIKSLHPoOTuRPgzODKud79vOiX7Xt3aqmVAPFTxZbpZlSyjPwT+I31YTeJxdRdNKnuJKqxrlmsTWEkcCWFzrbQ9VGfCzK0V00VWOU1sdUZseBSSIevSfD9j5835RWhnLbOo8OR3PTgvkhVcr4+E9szP3vEtCN4T1XVrm5wkx2hgQWFpGBBw2lqrckzUoioiHuh360vtcplrqPszVgLXqXmsPzd0M6VEe/+yBIxGN+yZUIRTrjIbyeNHiCoR60liAOxfzx/tt/q2hvnxhiKqyGREGb8qryiYTGygO457Rp9DideVDtjtyawVotaYNvnaBCdVv9LJeSH2VkMM29+bfoRcQ5XzJDG8XPVRgrogwlPIPMKi7XGngVBxI9QFyGcBnWtmp/z/aFDpJnfgKFesR6x9ThZDr2eOXhBS7fR0q5N+IRjWAcowmS/fgBY9VN/CgToCsu5be7/XYvVJwHBYnDre3ORhIiXs2P5zaJHa2br4rPY0bxipfieKFAksmxC5mm3N0WCxP3nUnLi/kjItBt3GfmWhEv3WlZIMdU+m4s779l+4//xA3lcGv23aZfQ0RlHSqCuilrK54UbA/fyBCqMj6rNnHWpH3zPBf8DivBdf1/WpnjIxkAUGpvnmVnBxlDC66mECP4svDDCfS4F373nuqTHj4= 9 | on: 10 | tags: true 11 | repo: andrejewski/raj 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Chris Andrejewski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Raj 3 |
4 |
5 |

6 | 7 | > The Elm Architecture for JavaScript 8 | 9 | ```sh 10 | npm install raj 11 | ``` 12 | 13 | [![npm](https://img.shields.io/npm/v/raj.svg)](https://www.npmjs.com/package/raj) 14 | [![Build Status](https://travis-ci.org/andrejewski/raj.svg?branch=master)](https://travis-ci.org/andrejewski/raj) 15 | [![Greenkeeper badge](https://badges.greenkeeper.io/andrejewski/raj.svg)](https://greenkeeper.io/) 16 | 17 | ## Features 18 | 19 | - **Understandable** 20 |
Raj is 34 lines; 190 bytes minified. This framework can fit in your head or even a tweet. 21 | 22 | - **Testable** 23 |
Raj forces us to design for better separated concerns, simpler logic, and easier tests. 24 | 25 | - **Minimal** 26 |
Raj provides a tiny foundation for libraries and applications. 27 | 28 | - **Portable** 29 |
Raj is view layer agnostic. The view is a side effect of state. 30 | 31 | Check out the [homepage](https://jew.ski/raj/) for resources and ecosystem packages. 32 | 33 | ## Example 34 | A counter that increments by one every time the user confirms. 35 | 36 | ```js 37 | import { runtime } from 'raj' 38 | 39 | runtime({ 40 | init: [0], // State is an integer to count 41 | update (message, state) { 42 | return [state + 1] // Increment the state 43 | }, 44 | view (state, dispatch) { 45 | const keepCounting = window.confirm(`Count is ${state}. Increment?`) 46 | if (keepCounting) { 47 | dispatch() 48 | } 49 | } 50 | }) 51 | ``` 52 | 53 | *Note:* Raj is view layer agnostic. 54 | Here we use the browser's built-in view to play the part. 55 | 56 | ## Architecture 57 | 58 | Raj applications are structured as programs. 59 | 60 | Every program begins with an initial state, which can be anything, and an optional effect. 61 | These are put into an array which is the `init` property of the program. 62 | 63 | ```js 64 | const init = [initialState, /* optional */ initialEffect] 65 | ``` 66 | 67 | "Effects" are functions which receive a function `dispatch`. 68 | Effects handle asynchronous work like data-fetching, timers, and managing event listeners. 69 | They can pass `dispatch` messages and Raj uses those to update the state. 70 | 71 | ```js 72 | function effect (dispatch) { 73 | // do anything or nothing; preferably something asynchronous 74 | // call dispatch 0, 1, or N times 75 | dispatch(message) 76 | } 77 | ``` 78 | 79 | A "message" can be anything; a server response, the current time, even `undefined`. 80 | 81 | When a message is dispatched, Raj passes that message and the current state to `update`. 82 | The `update` function returns a new state and optional effect. 83 | The business logic of the program is handled with this function. 84 | 85 | ```js 86 | function update (message, currentState) { 87 | return [newState, /* optional */ effect] 88 | } 89 | ``` 90 | 91 | The `view` is a special effect that receives both the current state and the `dispatch` function. 92 | The `view` can return anything. 93 | For the [React view layer](https://github.com/andrejewski/raj-react), the `view` returns React elements to be rendered. 94 | 95 | ```js 96 | function view (currentState, dispatch) { 97 | // anything, depending on choice of view library 98 | } 99 | ``` 100 | 101 | The `init`, `update`, and `view` form a "program" which is just an object with those properties: 102 | 103 | ```js 104 | const program = { 105 | init: [initialState, /* optional */ initialEffect], 106 | update (message, currentState) { 107 | return [newState, /* optional */ effect] 108 | }, 109 | view (currentState, dispatch) { 110 | // anything, depending on choice of view library 111 | } 112 | }; 113 | ``` 114 | 115 | Building any program follows the same steps: 116 | 117 | 1. Define the initial state and effect with `init` 118 | 1. Define the state transitions and effects with `update(message, state)` 119 | 1. Define the view with `view(state, dispatch)` 120 | 1. Tie it all together into a `program` 121 | 122 | Programs compose, so a parent program might contain child programs. 123 | 124 | - The parent's `init` may contain the child's `init`. 125 | - The parent's `update` may call the child's `update` with messages for the child and the child's state. 126 | - The parent's `view` may call the child's `view` with the child's state and `dispatch`. 127 | 128 | In this way, programs most often compose into a tree structure. 129 | 130 | The root program is passed to Raj's `runtime`. 131 | The runtime calls the program, manages its state, and runs its effects. 132 | 133 | ```js 134 | import { runtime } from 'raj' 135 | import { program } from './app' 136 | 137 | runtime(program) 138 | ``` 139 | 140 | The [Raj by Example](https://github.com/andrejewski/raj-by-example) documentation covers this in greater detail. 141 | -------------------------------------------------------------------------------- /docs/crown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Rectangle 7 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Raj - The Elm Architecture for JavaScript 4 | 5 | 6 | 7 | 249 | 250 | 251 | 252 |
253 |
254 |
255 |
256 |
257 |

Raj

258 |

The Elm Architecture for JavaScript

259 |
260 | View on Github 261 |
262 |
263 | An elephant with a crown is the Raj framework mascot. 269 |
270 |
271 |
272 | 273 |
274 |

Features

275 | 311 |
312 | 313 |
314 |

Resources

315 | 357 |
358 | 359 |
360 |

Packages

361 | 409 |
410 | 411 |
412 |
413 |

Have you adopted Raj into your software?

414 |

Have you built a library for the Raj ecosystem?

415 |

Have you written or presented about Raj?

416 | 417 | Submit your thingy 418 |
419 | 420 | Submit all the Raj things 421 |
422 | 423 | 426 | 427 |
428 | 429 | 430 | 431 | -------------------------------------------------------------------------------- /docs/raj.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | raj 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 47 | 48 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | exports.runtime = function (program) { 2 | var update = program.update 3 | var view = program.view 4 | var done = program.done 5 | var state 6 | var isRunning = true 7 | 8 | function dispatch (message) { 9 | if (isRunning) { 10 | change(update(message, state)) 11 | } 12 | } 13 | 14 | function change (change) { 15 | state = change[0] 16 | var effect = change[1] 17 | if (effect) { 18 | effect(dispatch) 19 | } 20 | view(state, dispatch) 21 | } 22 | 23 | change(program.init) 24 | 25 | return function end () { 26 | if (isRunning) { 27 | isRunning = false 28 | if (done) { 29 | done(state) 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "raj", 3 | "description": "The Elm Architecture for JavaScript", 4 | "version": "1.0.0", 5 | "author": "Chris Andrejewski ", 6 | "bugs": { 7 | "url": "https://github.com/andrejewski/raj/issues" 8 | }, 9 | "dependencies": {}, 10 | "devDependencies": { 11 | "ava": "^0.25.0", 12 | "fixpack": "^2.3.1", 13 | "prettier": "^1.13.5", 14 | "standard": "^11.0.0" 15 | }, 16 | "homepage": "https://jew.ski/raj/", 17 | "keywords": [ 18 | "architecture", 19 | "elm", 20 | "framework", 21 | "runtime" 22 | ], 23 | "license": "MIT", 24 | "main": "index.js", 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/andrejewski/raj.git" 28 | }, 29 | "scripts": { 30 | "lint": "fixpack && prettier index.js test/**/*.js --write && standard --fix", 31 | "test": "npm run lint && ava" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { runtime } from '../' 3 | 4 | test('runtime() should call view() initially', t => { 5 | const initialState = 1 6 | return new Promise(resolve => { 7 | runtime({ 8 | init: [initialState], 9 | view (state) { 10 | t.is(state, initialState) 11 | resolve() 12 | } 13 | }) 14 | }) 15 | }) 16 | 17 | test('runtime() should call view() after dispatch', t => { 18 | let count = 0 19 | return new Promise(resolve => { 20 | runtime({ 21 | init: ['init'], 22 | update (msg) { 23 | return [msg] 24 | }, 25 | view (state, dispatch) { 26 | count++ 27 | if (state === 'init') { 28 | return dispatch('next') 29 | } 30 | if (state === 'next') { 31 | return dispatch('done') 32 | } 33 | if (state === 'done') { 34 | resolve() 35 | } 36 | } 37 | }) 38 | }).then(() => t.is(count, 3)) 39 | }) 40 | 41 | test('runtime() should call done() when killed', t => { 42 | t.plan(1) 43 | return new Promise(resolve => { 44 | const initialState = 'state' 45 | const kill = runtime({ 46 | init: [initialState], 47 | update (msg, state) { 48 | return state 49 | }, 50 | view () {}, 51 | done (state) { 52 | t.is(state, initialState, 'the state is passed') 53 | resolve() 54 | } 55 | }) 56 | 57 | kill() 58 | }) 59 | }) 60 | 61 | test('runtime() should not call update/view if killed', t => { 62 | t.plan(2) 63 | let initialRender = true 64 | const initialState = 'state' 65 | return new Promise(resolve => { 66 | const afterKillEffect = dispatch => { 67 | t.is(typeof dispatch, 'function', 'dispatch is passed') 68 | setTimeout(() => { 69 | dispatch() 70 | resolve() 71 | }, 10) 72 | } 73 | const kill = runtime({ 74 | init: [initialState, afterKillEffect], 75 | update () { 76 | t.fail('update() should not be called') 77 | }, 78 | view () { 79 | if (initialRender) { 80 | initialRender = false 81 | t.pass('view() is called once') 82 | return 83 | } 84 | 85 | t.fail('view() should not be called more than once') 86 | } 87 | }) 88 | 89 | kill() 90 | }) 91 | }) 92 | 93 | test('runtime() should only call done() once', t => { 94 | let initialCall = true 95 | const kill = runtime({ 96 | init: [], 97 | update () {}, 98 | view () {}, 99 | done () { 100 | if (initialCall) { 101 | initialCall = false 102 | t.pass('done() was called once') 103 | return 104 | } 105 | 106 | t.fail('done() should not be called more than once') 107 | } 108 | }) 109 | 110 | kill() 111 | kill() 112 | }) 113 | --------------------------------------------------------------------------------