From 7265e94c0b7156d2f9ca70db14e97d550ac7b178 Mon Sep 17 00:00:00 2001 From: Akshay Nair Date: Sat, 16 Feb 2019 13:01:06 +0530 Subject: [PATCH 1/4] Adds Store, tests and some playground mess --- packages/ae-redux/README.md | 3 + packages/ae-redux/build/Store.js | 47 ++++++++++++++ packages/ae-redux/build/index.js | 32 ++++++++++ packages/ae-redux/index.js | 1 + packages/ae-redux/package.json | 26 ++++++++ packages/ae-redux/src/Store.js | 20 ++++++ packages/ae-redux/src/index.js | 15 +++++ packages/ae-redux/test/Store.test.js | 95 ++++++++++++++++++++++++++++ packages/ae-redux/test/index.test.js | 2 + yarn.lock | 15 ++++- 10 files changed, 255 insertions(+), 1 deletion(-) create mode 100644 packages/ae-redux/README.md create mode 100644 packages/ae-redux/build/Store.js create mode 100644 packages/ae-redux/build/index.js create mode 100644 packages/ae-redux/index.js create mode 100644 packages/ae-redux/package.json create mode 100644 packages/ae-redux/src/Store.js create mode 100644 packages/ae-redux/src/index.js create mode 100644 packages/ae-redux/test/Store.test.js create mode 100644 packages/ae-redux/test/index.test.js diff --git a/packages/ae-redux/README.md b/packages/ae-redux/README.md new file mode 100644 index 0000000..abeb426 --- /dev/null +++ b/packages/ae-redux/README.md @@ -0,0 +1,3 @@ +# @algebraic-effects/effects +A collection of effects to use while writing your program with algebraic effects in javascript + diff --git a/packages/ae-redux/build/Store.js b/packages/ae-redux/build/Store.js new file mode 100644 index 0000000..680f001 --- /dev/null +++ b/packages/ae-redux/build/Store.js @@ -0,0 +1,47 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _core = require("@algebraic-effects/core"); + +var _utils = require("@algebraic-effects/utils"); + +var Store = (0, _core.createEffect)('Store', { + dispatch: (0, _core.func)(['action']), + getState: (0, _core.func)([], 'state'), + selectState: (0, _core.func)(['?state -> a'], 'a'), + waitFor: (0, _core.func)(['actionType']) +}); + +Store.of = function (_ref) { + var subscribe = _ref.subscribe, + _dispatch = _ref.dispatch, + _getState = _ref.getState; + return Store.handler({ + dispatch: function dispatch(_ref2) { + var resume = _ref2.resume; + return (0, _utils.compose)(resume, _dispatch); + }, + getState: function getState(_ref3) { + var resume = _ref3.resume; + return (0, _utils.compose)(resume, _getState); + }, + selectState: function selectState(_ref4) { + var resume = _ref4.resume; + return function (fn) { + return (0, _utils.compose)(resume, fn || _utils.identity, _getState)(); + }; + }, + waitFor: function waitFor(_ref5) { + var resume = _ref5.resume, + end = _ref5.end; + return function (type) {}; + } + }); +}; + +var _default = Store; +exports.default = _default; \ No newline at end of file diff --git a/packages/ae-redux/build/index.js b/packages/ae-redux/build/index.js new file mode 100644 index 0000000..1a77a92 --- /dev/null +++ b/packages/ae-redux/build/index.js @@ -0,0 +1,32 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +Object.defineProperty(exports, "Store", { + enumerable: true, + get: function get() { + return _Store.default; + } +}); +exports.createEffectsMiddleware = void 0; + +var _Store = _interopRequireDefault(require("./Store")); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var createEffectsMiddleware = function createEffectsMiddleware(program, handler) { + var middleware = function middleware(store) { + return function (next) { + return function (action) { + _Store.default.of(store).with(handler).run(program).fork(console.error, console.log); + + return next(action); + }; + }; + }; + + return middleware; +}; + +exports.createEffectsMiddleware = createEffectsMiddleware; \ No newline at end of file diff --git a/packages/ae-redux/index.js b/packages/ae-redux/index.js new file mode 100644 index 0000000..e1c0647 --- /dev/null +++ b/packages/ae-redux/index.js @@ -0,0 +1 @@ +module.exports = require('./build/index'); \ No newline at end of file diff --git a/packages/ae-redux/package.json b/packages/ae-redux/package.json new file mode 100644 index 0000000..3ae0270 --- /dev/null +++ b/packages/ae-redux/package.json @@ -0,0 +1,26 @@ +{ + "name": "@algebraic-effects/redux", + "version": "0.0.2", + "description": "Redux middleware to allow using algebraic effects to write your actions", + "main": "index.js", + "isBuildTarget": true, + "repository": "https://github.com/phenax/algebraic-effects", + "author": "Akshay Nair ", + "license": "MIT", + "dependencies": { + "@algebraic-effects/core": "0.0.2" + }, + "keywords": [ + "functional", + "algebraic", + "effects", + "generators", + "fp", + "redux", + "saga", + "middleware" + ], + "peerDependencies": { + "redux": "^4.0.1" + } +} diff --git a/packages/ae-redux/src/Store.js b/packages/ae-redux/src/Store.js new file mode 100644 index 0000000..982c3de --- /dev/null +++ b/packages/ae-redux/src/Store.js @@ -0,0 +1,20 @@ +import { createEffect, func } from '@algebraic-effects/core'; +import { compose, identity } from '@algebraic-effects/utils'; + +const Store = createEffect('Store', { + dispatch: func(['action']), + getState: func([], 'state'), + selectState: func(['?state -> a'], 'a'), + waitFor: func(['actionType']), +}); + +Store.of = ({ subscribe, dispatch, getState }) => Store.handler({ + dispatch: ({ resume }) => compose(resume, dispatch), + getState: ({ resume }) => compose(resume, getState), + selectState: ({ resume }) => fn => compose(resume, fn || identity, getState)(), + waitFor: ({ resume, end }) => type => { + // subscribe() + }, +}); + +export default Store; diff --git a/packages/ae-redux/src/index.js b/packages/ae-redux/src/index.js new file mode 100644 index 0000000..81845fa --- /dev/null +++ b/packages/ae-redux/src/index.js @@ -0,0 +1,15 @@ +import Store from './Store'; + +export { Store }; + +export const createEffectsMiddleware = (program, handler) => { + const middleware = store => next => action => { + Store.of(store) + .with(handler) + .run(program) + .fork(console.error, console.log); + return next(action); + }; + + return middleware; +}; diff --git a/packages/ae-redux/test/Store.test.js b/packages/ae-redux/test/Store.test.js new file mode 100644 index 0000000..b3fc7d0 --- /dev/null +++ b/packages/ae-redux/test/Store.test.js @@ -0,0 +1,95 @@ +import { createStore } from 'redux'; +import Store from '../src/Store'; + +describe('Store effect', () => { + + describe('.dispatch', () => { + + it('should dispatch action passed to it', done => { + function *program() { + yield Store.dispatch({ type: 'Yo', payload: { a: 'b' } }); + yield Store.dispatch({ type: 'Broo', payload: { c: 'd' } }); + yield Store.dispatch({ type: 'Done' }); + } + + const reducer = (state, action) => { + switch(action.type) { + case 'Yo': + return expect(action.payload).toEqual({ a: 'b' }); + case 'Bro': + return expect(action.payload).toEqual({ c: 'd' }); + case 'Done': + return done(); + } + }; + + Store.of(createStore(reducer)) + .run(program) + .fork(done, () => {}); + }); + }); + + describe('.getState', () => { + + it('should get the state from the redux store', done => { + function *program() { + const before = yield Store.getState(); + yield Store.dispatch({ type: '++' }); + yield Store.dispatch({ type: '++' }); + yield Store.dispatch({ type: '++' }); + yield Store.dispatch({ type: '++' }); + yield Store.dispatch({ type: '--' }); + const after = yield Store.getState(); + yield Store.dispatch({ type: 'Done', payload: { before, after } }); + } + + const reducer = (state = 0, action) => { + switch(action.type) { + case '++': return state + 1; + case '--': return state - 1; + case 'Done': + expect(state).toBe(3); + expect(action.payload).toEqual({ before: 0, after: 3 }); + return done(); + default: return state; + } + }; + + Store.of(createStore(reducer)) + .run(program) + .fork(done, () => {}); + }); + }); + + describe('.selectState', () => { + + it('should select the required value from the state', done => { + function *program() { + const before = yield Store.selectState(state => state.count); + yield Store.dispatch({ type: '++' }); + yield Store.dispatch({ type: '++' }); + yield Store.dispatch({ type: '++' }); + yield Store.dispatch({ type: '++' }); + yield Store.dispatch({ type: '--' }); + const after = yield Store.selectState(); + yield Store.dispatch({ type: 'Done', payload: { before, after } }); + } + + const reducer = (state = { count: 0 }, action) => { + switch(action.type) { + case '++': return { count: state.count + 1 }; + case '--': return { count: state.count - 1 }; + case 'Done': + expect(state).toEqual({ count: 3 }); + expect(action.payload).toEqual({ before: 0, after: { count: 3 } }); + return done(); + default: return state; + } + }; + + Store.of(createStore(reducer)) + .run(program) + .fork(done, () => {}); + }); + }); +}); diff --git a/packages/ae-redux/test/index.test.js b/packages/ae-redux/test/index.test.js new file mode 100644 index 0000000..8588ae5 --- /dev/null +++ b/packages/ae-redux/test/index.test.js @@ -0,0 +1,2 @@ + +it('dummy', () => {}); diff --git a/yarn.lock b/yarn.lock index aaea3e4..4aece1d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4627,7 +4627,7 @@ lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5 resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -5847,6 +5847,14 @@ realpath-native@^1.0.0: dependencies: util.promisify "^1.0.0" +redux@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.1.tgz#436cae6cc40fbe4727689d7c8fae44808f1bfef5" + integrity sha512-R7bAtSkk7nY6O/OYMVR9RiBI+XghjF9rlbl5806HJbQph0LJVHZrU5oaO4q70eUKiqMRqm4y07KLTlMZ2BlVmg== + dependencies: + loose-envify "^1.4.0" + symbol-observable "^1.2.0" + regenerate-unicode-properties@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-7.0.0.tgz#107405afcc4a190ec5ed450ecaa00ed0cafa7a4c" @@ -6651,6 +6659,11 @@ swap-case@^1.1.0: lower-case "^1.1.1" upper-case "^1.1.1" +symbol-observable@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" + integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== + symbol-tree@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6" From cf6ee48167739c35e6a0ff1545215e50bb88bc68 Mon Sep 17 00:00:00 2001 From: Akshay Nair Date: Sat, 16 Feb 2019 13:57:41 +0530 Subject: [PATCH 2/4] Adds .take operation --- packages/ae-redux/build/Store.js | 22 +++++++++++----- packages/ae-redux/build/index.js | 5 +++- packages/ae-redux/src/Store.js | 15 ++++++++--- packages/ae-redux/src/index.js | 2 +- packages/ae-redux/test/Store.test.js | 39 +++++++++++++++++++++++++--- 5 files changed, 68 insertions(+), 15 deletions(-) diff --git a/packages/ae-redux/build/Store.js b/packages/ae-redux/build/Store.js index 680f001..66225fa 100644 --- a/packages/ae-redux/build/Store.js +++ b/packages/ae-redux/build/Store.js @@ -13,13 +13,14 @@ var Store = (0, _core.createEffect)('Store', { dispatch: (0, _core.func)(['action']), getState: (0, _core.func)([], 'state'), selectState: (0, _core.func)(['?state -> a'], 'a'), - waitFor: (0, _core.func)(['actionType']) + take: (0, _core.func)(['actionType | Action -> Boolean']) }); Store.of = function (_ref) { - var subscribe = _ref.subscribe, - _dispatch = _ref.dispatch, - _getState = _ref.getState; + var _ref$store = _ref.store, + _dispatch = _ref$store.dispatch, + _getState = _ref$store.getState, + action = _ref.action; return Store.handler({ dispatch: function dispatch(_ref2) { var resume = _ref2.resume; @@ -35,10 +36,19 @@ Store.of = function (_ref) { return (0, _utils.compose)(resume, fn || _utils.identity, _getState)(); }; }, - waitFor: function waitFor(_ref5) { + take: function take(_ref5) { var resume = _ref5.resume, end = _ref5.end; - return function (type) {}; + return function (filter) { + var isMatch = function isMatch() { + if (!action) return false; + if (typeof filter === 'string') return filter === action.type; + if (typeof filter === 'function') return filter(action); + return false; + }; + + return isMatch() ? resume(action) : end(action); + }; } }); }; diff --git a/packages/ae-redux/build/index.js b/packages/ae-redux/build/index.js index 1a77a92..60da76b 100644 --- a/packages/ae-redux/build/index.js +++ b/packages/ae-redux/build/index.js @@ -19,7 +19,10 @@ var createEffectsMiddleware = function createEffectsMiddleware(program, handler) var middleware = function middleware(store) { return function (next) { return function (action) { - _Store.default.of(store).with(handler).run(program).fork(console.error, console.log); + _Store.default.of({ + store: store, + action: action + }).with(handler).run(program).fork(console.error, console.log); return next(action); }; diff --git a/packages/ae-redux/src/Store.js b/packages/ae-redux/src/Store.js index 982c3de..66a780b 100644 --- a/packages/ae-redux/src/Store.js +++ b/packages/ae-redux/src/Store.js @@ -5,15 +5,22 @@ const Store = createEffect('Store', { dispatch: func(['action']), getState: func([], 'state'), selectState: func(['?state -> a'], 'a'), - waitFor: func(['actionType']), + take: func(['actionType | Action -> Boolean']), }); -Store.of = ({ subscribe, dispatch, getState }) => Store.handler({ +Store.of = ({ store: { dispatch, getState }, action }) => Store.handler({ dispatch: ({ resume }) => compose(resume, dispatch), getState: ({ resume }) => compose(resume, getState), selectState: ({ resume }) => fn => compose(resume, fn || identity, getState)(), - waitFor: ({ resume, end }) => type => { - // subscribe() + take: ({ resume, end }) => filter => { + const isMatch = () => { + if (!action) return false; + if (typeof filter === 'string') return filter === action.type; + if (typeof filter === 'function') return filter(action); + return false; + }; + + return isMatch() ? resume(action) : end(action); }, }); diff --git a/packages/ae-redux/src/index.js b/packages/ae-redux/src/index.js index 81845fa..698bd94 100644 --- a/packages/ae-redux/src/index.js +++ b/packages/ae-redux/src/index.js @@ -4,7 +4,7 @@ export { Store }; export const createEffectsMiddleware = (program, handler) => { const middleware = store => next => action => { - Store.of(store) + Store.of({ store, action }) .with(handler) .run(program) .fork(console.error, console.log); diff --git a/packages/ae-redux/test/Store.test.js b/packages/ae-redux/test/Store.test.js index b3fc7d0..6eff903 100644 --- a/packages/ae-redux/test/Store.test.js +++ b/packages/ae-redux/test/Store.test.js @@ -3,6 +3,10 @@ import Store from '../src/Store'; describe('Store effect', () => { + beforeEach(() => { + global.jasmine.DEFAULT_TIMEOUT_INTERVAL = 500; + }); + describe('.dispatch', () => { it('should dispatch action passed to it', done => { @@ -23,7 +27,7 @@ describe('Store effect', () => { } }; - Store.of(createStore(reducer)) + Store.of({ store: createStore(reducer) }) .run(program) .fork(done, () => {}); }); @@ -55,7 +59,7 @@ describe('Store effect', () => { } }; - Store.of(createStore(reducer)) + Store.of({ store: createStore(reducer) }) .run(program) .fork(done, () => {}); }); @@ -87,9 +91,38 @@ describe('Store effect', () => { } }; - Store.of(createStore(reducer)) + Store.of({ store: createStore(reducer) }) .run(program) .fork(done, () => {}); }); }); + + describe('.take', () => { + + it('should select the required value from the state', done => { + function *program() { + yield Store.take('Hello'); + yield Store.dispatch({ type: 'Done', payload: 'World' }); + } + + const reducer = (state = 0, action) => { + switch(action.type) { + case 'Hello': return state; + case 'Done': + expect(action.payload).toBe('World'); + return done(); + default: return state; + } + }; + + const store = createStore(reducer); + const dispatch = action => { + Store.of({ store, action }) + .run(program) + .fork(done, () => {}); + }; + + dispatch({ type: 'Hello' }); + }); + }); }); From b4ccf20e76e0d184b8806a82d48b352befff8e73 Mon Sep 17 00:00:00 2001 From: Akshay Nair Date: Sat, 16 Feb 2019 14:25:18 +0530 Subject: [PATCH 3/4] Adds more .take test case --- packages/ae-redux/build/Store.js | 2 + packages/ae-redux/src/Store.js | 9 +++ packages/ae-redux/test/Store.test.js | 98 ++++++++++++++++++++++++++-- 3 files changed, 103 insertions(+), 6 deletions(-) diff --git a/packages/ae-redux/build/Store.js b/packages/ae-redux/build/Store.js index 66225fa..c7d9580 100644 --- a/packages/ae-redux/build/Store.js +++ b/packages/ae-redux/build/Store.js @@ -7,6 +7,8 @@ exports.default = void 0; var _core = require("@algebraic-effects/core"); +var _generic = require("@algebraic-effects/core/generic"); + var _utils = require("@algebraic-effects/utils"); var Store = (0, _core.createEffect)('Store', { diff --git a/packages/ae-redux/src/Store.js b/packages/ae-redux/src/Store.js index 66a780b..2af7a33 100644 --- a/packages/ae-redux/src/Store.js +++ b/packages/ae-redux/src/Store.js @@ -1,4 +1,5 @@ import { createEffect, func } from '@algebraic-effects/core'; +import { call as callProgram } from '@algebraic-effects/core/generic'; import { compose, identity } from '@algebraic-effects/utils'; const Store = createEffect('Store', { @@ -6,6 +7,7 @@ const Store = createEffect('Store', { getState: func([], 'state'), selectState: func(['?state -> a'], 'a'), take: func(['actionType | Action -> Boolean']), + // takeEvery: func(['actionType | Action -> Boolean', 'program', '...args']), }); Store.of = ({ store: { dispatch, getState }, action }) => Store.handler({ @@ -22,6 +24,13 @@ Store.of = ({ store: { dispatch, getState }, action }) => Store.handler({ return isMatch() ? resume(action) : end(action); }, + // takeEvery: ({ resume, call }) => (filter, program, ...args) => { + // call(function*() { + // yield Store.take(filter); + // yield callProgram(program, ...args); + // }).fork(() => {}, () => {}); + // resume(); + // }, }); export default Store; diff --git a/packages/ae-redux/test/Store.test.js b/packages/ae-redux/test/Store.test.js index 6eff903..2c1993b 100644 --- a/packages/ae-redux/test/Store.test.js +++ b/packages/ae-redux/test/Store.test.js @@ -99,12 +99,12 @@ describe('Store effect', () => { describe('.take', () => { - it('should select the required value from the state', done => { - function *program() { - yield Store.take('Hello'); - yield Store.dispatch({ type: 'Done', payload: 'World' }); - } + function *program() { + yield Store.take('Hello'); + yield Store.dispatch({ type: 'Done', payload: 'World' }); + } + const getStore = (done) => { const reducer = (state = 0, action) => { switch(action.type) { case 'Hello': return state; @@ -114,8 +114,12 @@ describe('Store effect', () => { default: return state; } }; + + return createStore(reducer); + }; - const store = createStore(reducer); + it('should only execute the rest of the program if the action type matches', done => { + const store = getStore(done); const dispatch = action => { Store.of({ store, action }) .run(program) @@ -124,5 +128,87 @@ describe('Store effect', () => { dispatch({ type: 'Hello' }); }); + + it('should skip the rest of the program if action type doesnt match', done => { + const store = getStore(done); + const dispatch = action => { + Store.of({ store, action }) + .run(program) + .fork(done, x => { + expect(x.type).toBe('NotHello'); + done(); + }); + }; + + dispatch({ type: 'NotHello' }); + }); + + describe('with function filter', () => { + function *program() { + yield Store.take(x => x.type === 'Hello'); + yield Store.dispatch({ type: 'Done', payload: 'World' }); + } + + it('should only execute the rest of the program if the action type matches', done => { + const store = getStore(done); + const dispatch = action => { + Store.of({ store, action }) + .run(program) + .fork(done, () => {}); + }; + + dispatch({ type: 'Hello' }); + }); + + it('should skip the rest of the program if action type doesnt match', done => { + const store = getStore(done); + const dispatch = action => { + Store.of({ store, action }) + .run(program) + .fork(done, x => { + expect(x.type).toBe('NotHello'); + done(); + }); + }; + + dispatch({ type: 'NotHello' }); + }); + }); }); + + // describe('.takeEvery', () => { + + // function *helloProgram() { + // yield Store.dispatch({ type: 'Done', payload: 'World' }); + // } + // function *program() { + // const isHello = ({ type }) => type === 'Hello'; + // yield Store.takeEvery(isHello, helloProgram); + // } + + // const getStore = () => { + // const reducer = (state = 0, action) => { + // switch(action.type) { + // case 'Hello': return state; + // case 'Done': + // expect(action.payload).toBe('World'); + // return done(); + // default: return state; + // } + // }; + + // return createStore(reducer); + // }; + + // it('should execute the rest of the program every time the dispatch matches', done => { + // const store = getStore(); + // const dispatch = action => { + // Store.of({ store, action }) + // .run(program) + // .fork(done, () => {}); + // }; + + // dispatch({ type: 'Hello' }); + // }); + // }); }); From 905b8dda167557de6622edfa6ed56aa7ee18b151 Mon Sep 17 00:00:00 2001 From: Akshay Nair Date: Fri, 15 Mar 2019 20:54:43 +0530 Subject: [PATCH 4/4] Adds getAction --- packages/ae-redux/build/Store.js | 30 ++++++------ packages/ae-redux/build/utils.js | 34 ++++++++++++++ packages/ae-redux/src/Store.js | 27 +++-------- packages/ae-redux/src/utils.js | 19 ++++++++ packages/ae-redux/test/Store.test.js | 68 ++++++++++++---------------- packages/ae-redux/test/utils.test.js | 33 ++++++++++++++ 6 files changed, 136 insertions(+), 75 deletions(-) create mode 100644 packages/ae-redux/build/utils.js create mode 100644 packages/ae-redux/src/utils.js create mode 100644 packages/ae-redux/test/utils.test.js diff --git a/packages/ae-redux/build/Store.js b/packages/ae-redux/build/Store.js index c7d9580..8721c9f 100644 --- a/packages/ae-redux/build/Store.js +++ b/packages/ae-redux/build/Store.js @@ -7,15 +7,16 @@ exports.default = void 0; var _core = require("@algebraic-effects/core"); -var _generic = require("@algebraic-effects/core/generic"); - var _utils = require("@algebraic-effects/utils"); +var _utils2 = require("./utils"); + var Store = (0, _core.createEffect)('Store', { dispatch: (0, _core.func)(['action']), getState: (0, _core.func)([], 'state'), selectState: (0, _core.func)(['?state -> a'], 'a'), - take: (0, _core.func)(['actionType | Action -> Boolean']) + getAction: (0, _core.func)([], 'action'), + waitFor: (0, _core.func)(['actionType | Action -> Boolean']) }); Store.of = function (_ref) { @@ -26,7 +27,7 @@ Store.of = function (_ref) { return Store.handler({ dispatch: function dispatch(_ref2) { var resume = _ref2.resume; - return (0, _utils.compose)(resume, _dispatch); + return (0, _utils.compose)(resume, _dispatch, _utils2.decorateAction); }, getState: function getState(_ref3) { var resume = _ref3.resume; @@ -38,18 +39,17 @@ Store.of = function (_ref) { return (0, _utils.compose)(resume, fn || _utils.identity, _getState)(); }; }, - take: function take(_ref5) { - var resume = _ref5.resume, - end = _ref5.end; + getAction: function getAction(_ref5) { + var resume = _ref5.resume; + return function () { + return resume(action); + }; + }, + waitFor: function waitFor(_ref6) { + var resume = _ref6.resume, + end = _ref6.end; return function (filter) { - var isMatch = function isMatch() { - if (!action) return false; - if (typeof filter === 'string') return filter === action.type; - if (typeof filter === 'function') return filter(action); - return false; - }; - - return isMatch() ? resume(action) : end(action); + return (0, _utils2.filterAction)(filter, action) ? resume(action) : end(action); }; } }); diff --git a/packages/ae-redux/build/utils.js b/packages/ae-redux/build/utils.js new file mode 100644 index 0000000..3bcc0a5 --- /dev/null +++ b/packages/ae-redux/build/utils.js @@ -0,0 +1,34 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.filterAction = exports.isEffectfulAction = exports.decorateAction = exports.AE_REDUX_ACTION = void 0; + +var _utils = require("@algebraic-effects/utils"); + +var AE_REDUX_ACTION = (0, _utils.createSymbol)('algebraic-effects/redux-action'); +exports.AE_REDUX_ACTION = AE_REDUX_ACTION; + +var decorateAction = function decorateAction(action) { + action && (action.$$type = AE_REDUX_ACTION); + return action; +}; + +exports.decorateAction = decorateAction; + +var isEffectfulAction = function isEffectfulAction(action) { + return action ? action.$$type === AE_REDUX_ACTION : false; +}; + +exports.isEffectfulAction = isEffectfulAction; + +var filterAction = function filterAction(filter, action) { + if (!action) return false; + if (isEffectfulAction(action)) return false; + if (typeof filter === 'string') return filter === action.type; + if (typeof filter === 'function') return filter(action); + return false; +}; + +exports.filterAction = filterAction; \ No newline at end of file diff --git a/packages/ae-redux/src/Store.js b/packages/ae-redux/src/Store.js index 2af7a33..01fd39a 100644 --- a/packages/ae-redux/src/Store.js +++ b/packages/ae-redux/src/Store.js @@ -1,36 +1,21 @@ import { createEffect, func } from '@algebraic-effects/core'; -import { call as callProgram } from '@algebraic-effects/core/generic'; import { compose, identity } from '@algebraic-effects/utils'; +import { decorateAction, filterAction } from './utils'; const Store = createEffect('Store', { dispatch: func(['action']), getState: func([], 'state'), selectState: func(['?state -> a'], 'a'), - take: func(['actionType | Action -> Boolean']), - // takeEvery: func(['actionType | Action -> Boolean', 'program', '...args']), + getAction: func([], 'action'), + waitFor: func(['actionType | Action -> Boolean']), }); Store.of = ({ store: { dispatch, getState }, action }) => Store.handler({ - dispatch: ({ resume }) => compose(resume, dispatch), + dispatch: ({ resume }) => compose(resume, dispatch, decorateAction), getState: ({ resume }) => compose(resume, getState), selectState: ({ resume }) => fn => compose(resume, fn || identity, getState)(), - take: ({ resume, end }) => filter => { - const isMatch = () => { - if (!action) return false; - if (typeof filter === 'string') return filter === action.type; - if (typeof filter === 'function') return filter(action); - return false; - }; - - return isMatch() ? resume(action) : end(action); - }, - // takeEvery: ({ resume, call }) => (filter, program, ...args) => { - // call(function*() { - // yield Store.take(filter); - // yield callProgram(program, ...args); - // }).fork(() => {}, () => {}); - // resume(); - // }, + getAction: ({ resume }) => () => resume(action), + waitFor: ({ resume, end }) => filter => filterAction(filter, action) ? resume(action) : end(action), }); export default Store; diff --git a/packages/ae-redux/src/utils.js b/packages/ae-redux/src/utils.js new file mode 100644 index 0000000..c01cd7a --- /dev/null +++ b/packages/ae-redux/src/utils.js @@ -0,0 +1,19 @@ + +import { createSymbol } from '@algebraic-effects/utils'; + +export const AE_REDUX_ACTION = createSymbol('algebraic-effects/redux-action'); + +export const decorateAction = action => { + action && (action.$$type = AE_REDUX_ACTION); + return action; +}; + +export const isEffectfulAction = action => action ? action.$$type === AE_REDUX_ACTION : false; + +export const filterAction = (filter, action) => { + if (!action) return false; + if (isEffectfulAction(action)) return false; + if (typeof filter === 'string') return filter === action.type; + if (typeof filter === 'function') return filter(action); + return false; +}; diff --git a/packages/ae-redux/test/Store.test.js b/packages/ae-redux/test/Store.test.js index 2c1993b..2bd2d0f 100644 --- a/packages/ae-redux/test/Store.test.js +++ b/packages/ae-redux/test/Store.test.js @@ -65,6 +65,32 @@ describe('Store effect', () => { }); }); + describe('.getAction', () => { + + it('should select the required value from the state', done => { + function *program() { + const action = yield Store.getAction(); + yield Store.dispatch({ type: 'Done', payload: action }); + } + + const reducer = (state, action) => { + switch(action.type) { + case 'Done': + expect(action.payload.type).toEqual('Hello world'); + return done(); + default: return state; + } + }; + + const dispatch = action => + Store.of({ store: createStore(reducer), action }) + .run(program) + .fork(done, () => {}); + + dispatch({ type: 'Hello world' }); + }); + }); + describe('.selectState', () => { it('should select the required value from the state', done => { @@ -97,10 +123,10 @@ describe('Store effect', () => { }); }); - describe('.take', () => { + describe('.waitFor', () => { function *program() { - yield Store.take('Hello'); + yield Store.waitFor('Hello'); yield Store.dispatch({ type: 'Done', payload: 'World' }); } @@ -145,7 +171,7 @@ describe('Store effect', () => { describe('with function filter', () => { function *program() { - yield Store.take(x => x.type === 'Hello'); + yield Store.waitFor(x => x.type === 'Hello'); yield Store.dispatch({ type: 'Done', payload: 'World' }); } @@ -175,40 +201,4 @@ describe('Store effect', () => { }); }); }); - - // describe('.takeEvery', () => { - - // function *helloProgram() { - // yield Store.dispatch({ type: 'Done', payload: 'World' }); - // } - // function *program() { - // const isHello = ({ type }) => type === 'Hello'; - // yield Store.takeEvery(isHello, helloProgram); - // } - - // const getStore = () => { - // const reducer = (state = 0, action) => { - // switch(action.type) { - // case 'Hello': return state; - // case 'Done': - // expect(action.payload).toBe('World'); - // return done(); - // default: return state; - // } - // }; - - // return createStore(reducer); - // }; - - // it('should execute the rest of the program every time the dispatch matches', done => { - // const store = getStore(); - // const dispatch = action => { - // Store.of({ store, action }) - // .run(program) - // .fork(done, () => {}); - // }; - - // dispatch({ type: 'Hello' }); - // }); - // }); }); diff --git a/packages/ae-redux/test/utils.test.js b/packages/ae-redux/test/utils.test.js new file mode 100644 index 0000000..a38ac6b --- /dev/null +++ b/packages/ae-redux/test/utils.test.js @@ -0,0 +1,33 @@ + +import { decorateAction, AE_REDUX_ACTION, isEffectfulAction} from '../src/utils'; + +describe('decorateAction', () => { + it('should attach a $$type property to given action', () => { + + const action = decorateAction({ type: 'wow', payload: { wow: 'wow' } }); + expect(action.$$type).toBe(AE_REDUX_ACTION); + }); + + it('should attach a $$type property for null or undefined cases, return as is', () => { + expect(decorateAction(null)).toBe(null); + expect(decorateAction(undefined)).toBe(undefined); + expect(decorateAction(0)).toBe(0); + expect(decorateAction('')).toBe(''); + }); +}); + +describe('isEffectfulAction', () => { + it('should return true for decorated actions, else false', () => { + const action = { type: 'Hello', payload: 'World' }; + expect(isEffectfulAction(action)).toBe(false); + expect(isEffectfulAction(decorateAction(action))).toBe(true); + }); + + it('should return false for empty values', () => { + expect(isEffectfulAction(null)).toBe(false); + expect(isEffectfulAction(undefined)).toBe(false); + expect(isEffectfulAction(false)).toBe(false); + expect(isEffectfulAction('')).toBe(false); + }); +}); +