diff --git a/docs/concepts.md b/docs/concepts.md index 65cbbc6..75607f5 100644 --- a/docs/concepts.md +++ b/docs/concepts.md @@ -1,4 +1,19 @@ - # Updux concepts +# Updux concepts + +## actions + +Updux internally uses the package `ts-action` to create action creator +functions. Even if you don't use typescript, I recommend that you use it, +as it does what it does very well. But if you don't want to, no big deal. +Updux will recognize a function as an action creator if it has a `type` +property. So a homegrown creator could be as simple as: + +```js +function action(type) { + return Object.assign( payload => ({type, payload}), { type } ) +} +``` + ## effects diff --git a/package.json b/package.json index 3f4cbc8..61db783 100644 --- a/package.json +++ b/package.json @@ -2,22 +2,23 @@ "dependencies": { "lodash": "^4.17.15", "mobx": "^5.14.2", - "redux": "^4.0.4" + "redux": "^4.0.4", + "ts-action": "^11.0.0", + "updeep": "^1.2.0" }, "devDependencies": { - "docsify": "^4.10.2", - "docsify-cli": "^4.4.0", "@babel/cli": "^7.6.4", "@babel/core": "^7.6.4", "@babel/preset-env": "^7.6.3", "@types/jest": "^24.0.19", "@types/lodash": "^4.14.144", "babel-jest": "^24.9.0", + "docsify": "^4.10.2", + "docsify-cli": "^4.4.0", "jest": "^24.9.0", "ts-jest": "^24.1.0", "tsd": "^0.10.0", - "typescript": "^3.6.4", - "updeep": "^1.2.0" + "typescript": "^3.6.4" }, "license": "MIT", "main": "dist/index.js", diff --git a/src/actions.test.ts b/src/actions.test.ts index 7b90a95..20fec44 100644 --- a/src/actions.test.ts +++ b/src/actions.test.ts @@ -1,26 +1,27 @@ -import Updux, {actionCreator} from '.'; +import { action, payload } from 'ts-action'; import u from 'updeep'; +import Updux from '.'; + const noopEffect = () => () => () => {}; test('actions defined in effects and mutations, multi-level', () => { + const bar = action('bar',(payload,meta) => ({payload,meta}) ); + const foo = action('foo',(limit:number) => ({payload:{ limit} }) ); + const {actions} = new Updux({ - effects: { - foo: noopEffect, - }, - mutations: {bar: () => () => null}, + effects: [ [ foo, noopEffect ] ], + mutations: [ [ bar, () => () => null ] ], subduxes: { mysub: { effects: {baz: noopEffect}, mutations: {quux: () => () => null}, actions: { - foo: (limit: number) => ({limit}), + foo }, }, myothersub: { - effects: { - foo: noopEffect, - }, + effects: [ [foo, noopEffect] ], }, }, }); @@ -32,7 +33,7 @@ test('actions defined in effects and mutations, multi-level', () => { expect(actions.bar()).toEqual({type: 'bar'}); expect(actions.bar('xxx')).toEqual({type: 'bar', payload: 'xxx'}); - expect(actions.bar(undefined, 'yyy')).toEqual({type: 'bar', meta: 'yyy'}); + expect(actions.bar(undefined, 'yyy')).toEqual({type: 'bar', payload: undefined, meta: 'yyy'}); expect(actions.foo(12)).toEqual({type: 'foo', payload: {limit: 12}}); }); @@ -41,23 +42,15 @@ describe('different calls to addAction', () => { const updux = new Updux(); test('string', () => { - updux.addAction('foo'); + updux.addAction( action('foo', payload() )); expect(updux.actions.foo('yo')).toMatchObject({ type: 'foo', payload: 'yo', }); }); - test('actionCreator', () => { - const bar = actionCreator('bar', null); - updux.addAction(bar); - expect(updux.actions.bar()).toMatchObject({ - type: 'bar', - }); - }); - test('actionCreator inlined', () => { - updux.addAction('baz', (x) => ({x})); + updux.addAction( 'baz', (x) => ({payload: {x}})); expect(updux.actions.baz(3)).toMatchObject({ type: 'baz', payload: { x: 3 } }); diff --git a/src/addMutations.test.ts b/src/addMutations.test.ts index 7de8960..70f95e3 100644 --- a/src/addMutations.test.ts +++ b/src/addMutations.test.ts @@ -1,4 +1,6 @@ -import Updux, { actionCreator } from "./updux"; +import { action } from 'ts-action'; + +import Updux from "./updux"; type MyState = { sum: number; @@ -9,7 +11,7 @@ test("added mutation is present", () => { initial: { sum: 0 } }); - const add = actionCreator("add", (n: number) => ({ n })); + const add = action("add", (n: number) => ({ payload: { n } })); updux.addMutation(add, ({ n }, action) => ({ sum }) => ({ sum: sum + n })); diff --git a/src/buildActions/index.ts b/src/buildActions/index.ts index 1aadb92..0961f56 100644 --- a/src/buildActions/index.ts +++ b/src/buildActions/index.ts @@ -1,47 +1,9 @@ import fp from 'lodash/fp'; import { - Action, ActionCreator, - ActionPayloadGenerator, Dictionary, } from '../types'; -export function actionCreator( - type: T, - transform: (...args: any[]) => P -): ActionCreator; -export function actionCreator( - type: T, - transform: null -): ActionCreator; -export function actionCreator( - type: T -): ActionCreator; -export function actionCreator(type: any, transform?: any) { - if (transform) { - return Object.assign( - (...args: any[]) => ({ type, payload: transform(...args) }), - { type } - ); - } - - if (transform === null) { - return Object.assign(() => ({ type }), { type }); - } - - return Object.assign((payload: unknown) => ({ type, payload }), { type }); -} - -export function actionFor(type: string): ActionCreator { - const f = (payload = undefined, meta = undefined) => - fp.pickBy(v => v !== undefined)({ type, payload, meta }) as Action; - - return Object.assign(f, { - _genericAction: true, - type, - }); -} - type ActionPair = [string, ActionCreator]; function buildActions(actions: ActionPair[] = []): Dictionary { diff --git a/src/index.ts b/src/index.ts index fae4d22..126c1e2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,4 @@ import Updux from "./updux"; export { default as Updux } from "./updux"; export { UpduxConfig } from "./types"; -export { actionCreator } from "./buildActions"; - export default Updux; diff --git a/src/middleware.test.ts b/src/middleware.test.ts index e3a0a1c..9f725dc 100644 --- a/src/middleware.test.ts +++ b/src/middleware.test.ts @@ -1,5 +1,7 @@ -import Updux, { actionCreator } from '.'; import u from 'updeep'; +import { action, payload } from 'ts-action'; + +import Updux from '.'; import mwUpdux from './middleware_aux'; test('simple effect', () => { @@ -179,7 +181,7 @@ test('middleware as map', () => { let rootState; let rootFromChild; - const doIt = actionCreator('doIt'); + const doIt = action('doIt', () => ({payload: ''})); const child = new Updux({ initial: '', diff --git a/src/mutations.test.ts b/src/mutations.test.ts index 1909b06..3764f9e 100644 --- a/src/mutations.test.ts +++ b/src/mutations.test.ts @@ -1,7 +1,9 @@ -import Updux, { actionCreator } from "./updux"; +import { action } from 'ts-action'; + +import Updux from "./updux"; describe("as array of arrays", () => { - const doIt = actionCreator("doIt"); + const doIt = action("doIt"); const updux = new Updux({ initial: "", diff --git a/src/test.ts b/src/test.ts index 0578dbf..fef9a28 100644 --- a/src/test.ts +++ b/src/test.ts @@ -13,11 +13,6 @@ test('actions from mutations', () => { expect(foo(true)).toEqual({type: 'foo', payload: true}); - expect(foo({bar: 2}, {timestamp: 613})).toEqual({ - type: 'foo', - payload: {bar: 2}, - meta: {timestamp: 613}, - }); }); test('reducer', () => { diff --git a/src/updux.ts b/src/updux.ts index 9f46c59..5e3cfa8 100644 --- a/src/updux.ts +++ b/src/updux.ts @@ -1,7 +1,8 @@ import fp from "lodash/fp"; import u from "updeep"; +import { action, payload } from 'ts-action'; -import buildActions, { actionFor, actionCreator } from "./buildActions"; +import buildActions from "./buildActions"; import buildInitial from "./buildInitial"; import buildMutations from "./buildMutations"; @@ -24,7 +25,6 @@ import { import { Middleware, Store, PreloadedState } from "redux"; import buildSelectors from "./buildSelectors"; -export { actionCreator } from "./buildActions"; type StoreWithDispatchActions< S = any, @@ -76,9 +76,9 @@ export class Updux { ); const actions = fp.getOr({}, "actions", config); - Object.entries(actions).forEach(([type, payload]: [string, any]): any => + Object.entries(actions).forEach(([type, p]: [string, any]): any => this.addAction( - (payload as any).type ? payload : actionCreator(type, payload as any) + (p as any).type ? p : action(type, p) ) ); @@ -157,14 +157,23 @@ export class Updux { }; } - addMutation( + addMutation( creator: A, mutation: Mutation infer R ? R : never>, isSink?: boolean - ) { - let c = fp.isFunction(creator) ? creator : actionFor(creator); - - this.addAction(c); + ) + addMutation( + creator: string, + mutation: Mutation, + isSink?: boolean + ) + addMutation( + creator, + mutation, + isSink + ) + { + let c = this.addAction(creator); this.localMutations[c.type] = [ this.groomMutations(mutation as any) as Mutation, @@ -177,24 +186,34 @@ export class Updux { middleware: UpduxMiddleware, isGenerator: boolean = false ) { - let c = fp.isFunction(creator) ? creator : actionFor(creator); - - this.addAction(c); - this.localActions[c.type] = c; + const c = this.addAction(creator); this.localEffects.push([c.type, middleware, isGenerator]); } - addAction(action: string, transform?: any): ActionCreator - addAction(action: ActionCreator, transform?: never): ActionCreator - addAction(action: any,transform:any) { - if (typeof action === "string") { - if (!this.localActions[action]) { - this.localActions[action] = actionCreator(action,transform); - } - return this.localActions[action]; + // can be + //addAction( actionCreator ) + // addAction( 'foo', transform ) + addAction(theaction: string, transform?: any): ActionCreator + addAction(theaction: string|ActionCreator, transform?: never): ActionCreator + addAction(theaction: any,transform:any) { + if (typeof theaction === "string") { + if(transform !== undefined ) { + theaction = action(theaction,transform); + } + else { + theaction = this.actions[theaction] || action(theaction,payload()) + } } - return this.localActions[action.type] = action; + const already = this.actions[theaction.type]; + if( already ) { + if ( already !== theaction ) { + throw new Error(`action ${theaction.type} already exists`) + } + return already; + } + + return this.localActions[theaction.type] = theaction; } get _middlewareEntries() {