├── .gitignore ├── README.md ├── main.js ├── package.json ├── src ├── index.html └── scripts │ ├── complexActions.js │ ├── components │ ├── createWord.js │ └── main.js │ ├── index.js │ └── updates.js └── www ├── app.js └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # csp-architecture 2 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | import {chan, go, take, offer, put, putAsync, buffers, timeout} from 'js-csp'; 2 | 3 | let ch = chan(); 4 | 5 | let gen = id => function* () { 6 | console.log('Init id: ', id); 7 | while(true) { 8 | const v = yield take(ch); 9 | console.log(id, '===>', v); 10 | yield timeout(100); 11 | } 12 | } 13 | 14 | go(gen(1)); 15 | //go(gen(2)); 16 | //go(gen(3)); 17 | //go(gen(4)); 18 | //go(gen(5)); 19 | //go(gen(6)); 20 | //go(gen(7)); 21 | // go(gen(8)); 22 | // go(gen(9)); 23 | // go(gen(10)); 24 | 25 | go(function* () { 26 | let counter = 0; 27 | console.time('processes'); 28 | while(counter < 100) { 29 | yield put(ch, counter); 30 | counter++; 31 | yield timeout(10); 32 | } 33 | console.timeEnd('processes'); 34 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "csp-js", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "main.js", 6 | "dependencies": { 7 | "js-csp": "^0.5.0", 8 | "react": "^0.13.3" 9 | }, 10 | "devDependencies": { 11 | "babel": "^5.8.23", 12 | "babelify": "^6.3.0", 13 | "browserify": "^11.2.0", 14 | "http-server": "^0.8.4", 15 | "onchange": "^2.0.0", 16 | "watchify": "^3.4.0" 17 | }, 18 | "scripts": { 19 | "test": "echo \"Error: no test specified\" && exit 1", 20 | "build": "npm run js && npm run html", 21 | "build:watch": "npm run js:watch && npm run html:watch", 22 | "html": "cp src/index.html www/", 23 | "html:watch": "onchange 'src/*.html' -v -- npm run html", 24 | "js": "browserify --debug src/scripts/index.js -t babelify --outfile www/app.js", 25 | "js:watch": "watchify --debug src/scripts/index.js -t babelify --outfile www/app.js -v", 26 | "start": "http-server ./www -p 3010", 27 | "deploy": "git subtree push --prefix www origin gh-pages" 28 | }, 29 | "author": "", 30 | "license": "ISC" 31 | } 32 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Use CSP! 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /src/scripts/complexActions.js: -------------------------------------------------------------------------------- 1 | import {go, put, timeout} from 'js-csp'; 2 | 3 | export const dbInsert = (updateChannels, newWord) => { 4 | go(function* () { 5 | yield put(updateChannels.loading, true); 6 | 7 | // do something costly 8 | yield timeout(1000); 9 | yield put(updateChannels.add, newWord); 10 | 11 | yield put(updateChannels.loading, false); 12 | }); 13 | }; -------------------------------------------------------------------------------- /src/scripts/components/createWord.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {putAsync} from 'js-csp'; 3 | 4 | export default class CreateWord extends React.Component { 5 | constructor(props) { 6 | super(props); 7 | this.state = {inputText: ''}; 8 | } 9 | 10 | handleChange(event) { 11 | this.setState({inputText: event.target.value}); 12 | } 13 | 14 | create() { 15 | const text = this.state.inputText.trim(); 16 | const ch = this.props.complexActionsChannels.dbInsert; 17 | putAsync(ch, text); 18 | this.setState({inputText: ''}); 19 | } 20 | 21 | render() { 22 | const loading = this.props.loading; 23 | return loading ?

Adding word...

: 24 |

25 | 26 | Add... 27 |

28 | } 29 | } -------------------------------------------------------------------------------- /src/scripts/components/main.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {putAsync} from 'js-csp'; 3 | 4 | import CreateWord from './createWord'; 5 | 6 | export default class Main extends React.Component { 7 | view(direction) { 8 | const ch = this.props.updateChannels.view; 9 | return () => putAsync(ch, direction); 10 | } 11 | 12 | render() { 13 | const state = this.props.appState; 14 | const currentWord = state.words[state.current]; 15 | return
16 |

Current word: { currentWord }

17 |

18 | Previous 19 | Next 20 |

21 | 22 |
{ JSON.stringify(state, null, '  ') }
23 |
24 | } 25 | } -------------------------------------------------------------------------------- /src/scripts/index.js: -------------------------------------------------------------------------------- 1 | import Polyfill from 'babel/polyfill'; 2 | import {chan, go, take, put, putAsync, buffers} from 'js-csp'; 3 | 4 | import React from 'react'; 5 | import Main from './components/main'; 6 | 7 | import * as Updates from './updates'; 8 | import * as ComplexActions from './complexActions'; 9 | 10 | const loadApp = () => ({ 11 | state: { 12 | words: ['first', 'second', 'last'], 13 | current: 0, 14 | loading: false 15 | }, 16 | updates: { 17 | channels: { 18 | view: chan(), 19 | add: chan(), 20 | loading: chan() 21 | }, 22 | consumers: { 23 | view: Updates.view, 24 | add: Updates.add, 25 | loading: Updates.loading 26 | } 27 | }, 28 | complexActions: { 29 | channels: { 30 | dbInsert: chan() 31 | }, 32 | consumers: { 33 | dbInsert: ComplexActions.dbInsert 34 | } 35 | }, 36 | renderCh: chan(buffers.sliding(1)) 37 | }); 38 | 39 | const initUpdates = app => { 40 | Object.keys(app.updates.consumers).forEach(k => { 41 | const updateFn = app.updates.consumers[k]; 42 | const ch = app.updates.channels[k]; 43 | go(function* () { 44 | while (true) { 45 | const value = yield take(ch); 46 | console.log(`On update channel [ ${k} ] received value [ ${JSON.stringify(value)} ]`); 47 | app.state = updateFn(app.state, value); 48 | yield put(app.renderCh, app.state); 49 | } 50 | }); 51 | }); 52 | }; 53 | 54 | const initComplexActions = app => { 55 | Object.keys(app.complexActions.consumers).forEach(k => { 56 | const complexActionFn = app.complexActions.consumers[k]; 57 | const ch = app.complexActions.channels[k]; 58 | go(function* () { 59 | while (true) { 60 | const value = yield take(ch); 61 | console.log(`On complex action channel [ ${k} ] received value [ ${JSON.stringify(value)} ]`); 62 | complexActionFn(app.updates.channels, value); 63 | } 64 | }); 65 | }); 66 | }; 67 | 68 | const initRender = (app, element) => { 69 | putAsync(app.renderCh, app.state); 70 | 71 | go(function* () { 72 | while(true) { 73 | const state = yield take(app.renderCh); 74 | const finishRender = chan(); 75 | React.render( 76 |
, 80 | element, 81 | () => window.requestAnimationFrame(() => putAsync(finishRender, {}))); 82 | yield take(finishRender); 83 | } 84 | }); 85 | }; 86 | 87 | const start = () => { 88 | let app = loadApp(); 89 | window.app = app; // for debugging and testing 90 | initUpdates(app); 91 | initComplexActions(app); 92 | initRender(app, document.getElementById('main')); 93 | }; 94 | 95 | start(); 96 | 97 | window.csp = require('js-csp'); -------------------------------------------------------------------------------- /src/scripts/updates.js: -------------------------------------------------------------------------------- 1 | // 2 | // util functions 3 | // 4 | const clone = obj => JSON.parse(JSON.stringify(obj)); // naive but cool! 5 | 6 | const assoc = (obj, prop, value) => { 7 | const cl = clone(obj); 8 | cl[prop] = value; 9 | return cl; 10 | }; 11 | 12 | const append = (array, value) => { 13 | const cl = clone(array); 14 | cl.push(value); 15 | return cl; 16 | }; 17 | 18 | // 19 | // update functions 20 | // 21 | export const view = (state, direction) => { 22 | const nextCurrent = direction === 'next' ? 23 | Math.min(state.current + 1, state.words.length - 1) : 24 | Math.max(state.current - 1, 0); 25 | 26 | return assoc(state, 'current', nextCurrent); 27 | }; 28 | 29 | export const add = (state, newWord) => 30 | assoc(state, 'words', append(state.words, newWord)); 31 | 32 | export const loading = (state, loadingState) => 33 | assoc(state, 'loading', loadingState); -------------------------------------------------------------------------------- /www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Use CSP! 5 | 6 | 7 |
8 | 9 | 10 | --------------------------------------------------------------------------------