From 50ce69ec2e32ae0ab8bf558f4a7c503cd0b1e919 Mon Sep 17 00:00:00 2001 From: Nathan Hunzaker Date: Fri, 16 Jun 2017 14:23:18 -0400 Subject: [PATCH] Allow top level generator types for actions This commit adds a short-hand for generator actions. For example: ```javascript let count = n => n function * range (repo, start, end) { while (start <= end) yield repo.push(count, start++) } } repo.push(range, 1, 10) // 1,2,3,4,5,6,7,8,9,10 ``` --- examples/react-router/.flowconfig | 2 - examples/react-router/app/actions/items.js | 12 ++---- examples/react-router/app/boot.js | 32 +++++++--------- examples/react-router/app/domains/domain.js | 17 +++------ examples/react-router/app/domains/items.js | 14 +------ examples/react-router/app/domains/lists.js | 13 +------ examples/react-router/app/models/lists.js | 33 +++++------------ .../app/views/lists/parts/item-form.js | 1 - .../app/views/lists/parts/item-list.js | 1 - examples/react-router/flow-typed/model.js | 12 ------ package.json | 4 +- src/coroutine.js | 31 ++++++++++------ .../middleware/generator-middleware.test.js | 37 +++++++++++++++++++ 13 files changed, 93 insertions(+), 116 deletions(-) delete mode 100644 examples/react-router/.flowconfig delete mode 100644 examples/react-router/flow-typed/model.js diff --git a/examples/react-router/.flowconfig b/examples/react-router/.flowconfig deleted file mode 100644 index 92f6426a..00000000 --- a/examples/react-router/.flowconfig +++ /dev/null @@ -1,2 +0,0 @@ -[libs] -flow-typed/ diff --git a/examples/react-router/app/actions/items.js b/examples/react-router/app/actions/items.js index 525270af..c082597c 100644 --- a/examples/react-router/app/actions/items.js +++ b/examples/react-router/app/actions/items.js @@ -1,13 +1,9 @@ import Items from '../domains/items' -export function addItem(params) { - return function*(repo) { - yield repo.push(Items.create, params) - } +export function* addItem(repo, params) { + yield repo.push(Items.create, params) } -export function removeItem(id) { - return function*(repo) { - yield repo.push(Items.destroy, id) - } +export function* removeItem(repo, id) { + yield repo.push(Items.destroy, id) } diff --git a/examples/react-router/app/boot.js b/examples/react-router/app/boot.js index 83db1b59..405b066f 100644 --- a/examples/react-router/app/boot.js +++ b/examples/react-router/app/boot.js @@ -2,27 +2,21 @@ import React from 'react' import DOM from 'react-dom' import { Router } from 'react-router' import createBrowserHistory from 'history/createBrowserHistory' -import { AppContainer } from 'react-hot-loader' import Repo from './repo' -import Layout from './views/layout' +import Application from './views/application' -const el = document.getElementById('app') +// We're creating a browser history first, so that we can pass it +// into Microcosm. This lets us take advantage of the router when +// dispatching actions. +// +// See ./effects/routing.js const browserHistory = createBrowserHistory() -const repo = new Repo({ maxHistory: Infinity, browserHistory }) -function render() { - DOM.render( - - - - - , - el - ) -} +const repo = new Repo({ browserHistory }) -render() - -if (module.hot) { - module.hot.accept('./views/layout', render) -} +DOM.render( + + + , + document.getElementById('app') +) diff --git a/examples/react-router/app/domains/domain.js b/examples/react-router/app/domains/domain.js index 3feacf11..fd667796 100644 --- a/examples/react-router/app/domains/domain.js +++ b/examples/react-router/app/domains/domain.js @@ -1,25 +1,18 @@ -/** - * @flow - */ - -type Collection = Array -type Record = Object - class Domain { - getInitialState(): Collection { + getInitialState() { return [] } - add(items: Collection, params: Record) { + add(items, params) { return items.concat(params) } - remove(items: Collection, unwanted: string) { + remove(items, unwanted) { return items.filter(i => i.id !== unwanted) } - removeBy(key: string) { - return (items: Collection, value: *) => { + removeBy(key) { + return (items, value) => { return items.filter(item => item[key] !== value) } } diff --git a/examples/react-router/app/domains/items.js b/examples/react-router/app/domains/items.js index de179e89..52134efe 100644 --- a/examples/react-router/app/domains/items.js +++ b/examples/react-router/app/domains/items.js @@ -1,23 +1,13 @@ -/** - * @flow - */ - import uid from 'uid' import Lists from './lists' import Domain from './domain' -export type Item = { - id?: string, - name: string, - list: string -} - class Items extends Domain { - static create(params): Item { + static create(params) { return { id: uid(), ...params } } - static destroy(id: string) { + static destroy(id) { return id } diff --git a/examples/react-router/app/domains/lists.js b/examples/react-router/app/domains/lists.js index fe5b9828..151450c6 100644 --- a/examples/react-router/app/domains/lists.js +++ b/examples/react-router/app/domains/lists.js @@ -1,21 +1,12 @@ -/** - * @flow - */ - import uid from 'uid' import Domain from './domain' -export type List = { - id?: string, - name: string -} - class Lists extends Domain { - static create(params): List { + static create(params) { return { id: uid(), ...params } } - static destroy(id: string) { + static destroy(id) { return id } diff --git a/examples/react-router/app/models/lists.js b/examples/react-router/app/models/lists.js index c343d4f4..17ec7528 100644 --- a/examples/react-router/app/models/lists.js +++ b/examples/react-router/app/models/lists.js @@ -1,18 +1,7 @@ -/** - * @flow - */ +import { set } from 'microcosm' -import type Presenter from 'microcosm/addons/presenter' -import { set } from 'microcosm' // eslint-ignore-line - -type Snapshot = { [string]: * } - -interface Model { - call(presenter: Presenter, state: Snapshot, repo: Microcosm): * -} - -export class ListsWithCounts implements Model { - call(_presenter: Presenter, state: Snapshot) { +export class ListsWithCounts { + call(_presenter, state) { const { lists, items } = state return lists.map(function(list) { @@ -23,28 +12,24 @@ export class ListsWithCounts implements Model { } } -export class List implements Model { - id: string - - constructor(id: string) { +export class List { + constructor(id) { this.id = id } - call(_presenter: Presenter, state: Snapshot) { + call(_presenter, state) { const { lists } = state return lists.find(list => list.id === this.id) } } -export class ListItems implements Model { - id: string - - constructor(id: string) { +export class ListItems { + constructor(id) { this.id = id } - call(_presenter: Presenter, state: Snapshot) { + call(_presenter, state) { const { items } = state return items.filter(item => item.list === this.id) diff --git a/examples/react-router/app/views/lists/parts/item-form.js b/examples/react-router/app/views/lists/parts/item-form.js index b2ea3d80..74c74378 100644 --- a/examples/react-router/app/views/lists/parts/item-form.js +++ b/examples/react-router/app/views/lists/parts/item-form.js @@ -1,6 +1,5 @@ import React from 'react' import ActionForm from 'microcosm/addons/action-form' - import { addItem } from '../../../actions/items' class ItemForm extends React.PureComponent { diff --git a/examples/react-router/app/views/lists/parts/item-list.js b/examples/react-router/app/views/lists/parts/item-list.js index 5a21532f..87aa6861 100644 --- a/examples/react-router/app/views/lists/parts/item-list.js +++ b/examples/react-router/app/views/lists/parts/item-list.js @@ -1,6 +1,5 @@ import React from 'react' import ActionButton from 'microcosm/addons/action-button' - import { removeItem } from '../../../actions/items' function Item({ id, name }) { diff --git a/examples/react-router/flow-typed/model.js b/examples/react-router/flow-typed/model.js deleted file mode 100644 index b2736917..00000000 --- a/examples/react-router/flow-typed/model.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @flow - */ - -import Microcosm from '../../../src/microcosm' -import Presenter from '../../../src/addons/presenter' - -type Snapshot = { [string]: * } - -declare interface Model { - call(presenter: Presenter, state: Snapshot, repo: Microcosm): * -} diff --git a/package.json b/package.json index a6220c74..a8c55523 100644 --- a/package.json +++ b/package.json @@ -47,8 +47,7 @@ ] }, "dependencies": { - "form-serialize": "^0.7.2", - "react-router": "^4.1.1" + "form-serialize": "^0.7.2" }, "devDependencies": { "babel-core": "^6.25.0", @@ -84,6 +83,7 @@ "react": "^15.6.1", "react-dom": "^15.6.1", "react-hot-loader": "next", + "react-router": "^4.1.1", "react-router-dom": "^4.1.1", "react-test-renderer": "^15.6.1", "rollup": "^0.43.0", diff --git a/src/coroutine.js b/src/coroutine.js index 837741c1..774d6b9d 100644 --- a/src/coroutine.js +++ b/src/coroutine.js @@ -13,11 +13,12 @@ import { isFunction, isPromise, isGeneratorFn } from './utils' function processGenerator( action: Action, body: (repo: Microcosm) => Generator, - repo: * + repo: *, + params: *[] ) { - action.open() + action.open(...params) - let iterator = body(repo) + let iterator = body(repo, ...params) function step(payload: mixed) { let next = iterator.next(payload) @@ -29,21 +30,23 @@ function processGenerator( } } - function progress(subAction: Action | Action[]): Action { + function progress(subAction): Action { + let subject = subAction + if (Array.isArray(subAction)) { - return progress(repo.parallel(subAction)) + subject = repo.parallel(subAction) } console.assert( - subAction instanceof Action, - `Iteration of generator expected an Action. Instead got ${typeof subAction}` + subject instanceof Action, + `Iteration of generator expected an Action. Instead got ${typeof subject}` ) - subAction.onDone(step) - subAction.onCancel(action.cancel, action) - subAction.onError(action.reject, action) + subject.onDone(step) + subject.onCancel(action.cancel, action) + subject.onError(action.reject, action) - return subAction + return subject } step() @@ -65,6 +68,10 @@ export default function coroutine( return action.resolve(...params) } + if (isGeneratorFn(command)) { + return processGenerator(action, command, repo, params) + } + let body = command.apply(null, params) /** @@ -92,7 +99,7 @@ export default function coroutine( * in order */ if (isGeneratorFn(body)) { - return processGenerator(action, body, repo) + return processGenerator(action, body, repo, params) } /** diff --git a/test/unit/middleware/generator-middleware.test.js b/test/unit/middleware/generator-middleware.test.js index f456be5b..2b01903c 100644 --- a/test/unit/middleware/generator-middleware.test.js +++ b/test/unit/middleware/generator-middleware.test.js @@ -1,6 +1,19 @@ import Microcosm from '../../../src/microcosm' describe('Generator Middleware', function() { + it('opens with the parameters', function() { + let repo = new Microcosm() + + function* stall(repo, n) { + yield repo.push(() => new Promise(() => {})) + } + + let action = repo.push(stall, 2) + + expect(action).toHaveStatus('open') + expect(action.payload).toEqual(2) + }) + it('processes actions sequentially', function() { expect.assertions(1) @@ -235,4 +248,28 @@ describe('Generator Middleware', function() { }) }) }) + + it('allows the top level action description to be a generator', function() { + let count = n => n + + function* sequence(repo, start) { + yield repo.push(count, 1) + yield repo.push(count, 2) + yield repo.push(count, 3) + } + + class Repo extends Microcosm { + register() { + return { + [count]: (_last, next) => next + } + } + } + + let repo = new Repo() + + repo.push(sequence, 1) + + expect(repo.state).toEqual(3) + }) })