├── packages
└── core
│ ├── README.md
│ ├── es
│ ├── types
│ │ ├── model.js
│ │ ├── model.js.map
│ │ ├── Action.js.map
│ │ ├── Action.js
│ │ ├── notification.d.ts
│ │ ├── notification.js.map
│ │ ├── Action.d.ts
│ │ ├── notification.js
│ │ └── model.d.ts
│ ├── constants
│ │ ├── symbols.d.ts
│ │ ├── meta.d.ts
│ │ ├── symbols.js
│ │ ├── meta.js.map
│ │ ├── symbols.js.map
│ │ ├── meta.js
│ │ ├── notification.d.ts
│ │ ├── notification.js.map
│ │ ├── notification.js
│ │ ├── actionTypes.js.map
│ │ ├── actionTypes.js
│ │ └── actionTypes.d.ts
│ ├── utils
│ │ ├── function.d.ts
│ │ ├── logic.js.map
│ │ ├── function.js.map
│ │ ├── logic.d.ts
│ │ ├── logic.js
│ │ ├── function.js
│ │ ├── action.d.ts
│ │ ├── action.js.map
│ │ └── action.js
│ ├── reducers
│ │ ├── error.d.ts
│ │ ├── loading.d.ts
│ │ ├── error.js.map
│ │ ├── loading.js.map
│ │ ├── error.js
│ │ └── loading.js
│ ├── operators
│ │ ├── endTo.js.map
│ │ ├── endTo.d.ts
│ │ ├── end.js.map
│ │ ├── end.d.ts
│ │ ├── endTo.js
│ │ ├── end.js
│ │ ├── createService.d.ts
│ │ ├── createService.js.map
│ │ └── createService.js
│ ├── index.d.ts
│ ├── index.js.map
│ └── index.js
│ ├── lib
│ ├── types
│ │ ├── model.js.map
│ │ ├── model.js
│ │ ├── Action.js.map
│ │ ├── Action.js
│ │ ├── notification.d.ts
│ │ ├── notification.js.map
│ │ ├── Action.d.ts
│ │ ├── notification.js
│ │ └── model.d.ts
│ ├── constants
│ │ ├── symbols.d.ts
│ │ ├── meta.d.ts
│ │ ├── meta.js.map
│ │ ├── symbols.js.map
│ │ ├── notification.d.ts
│ │ ├── notification.js.map
│ │ ├── symbols.js
│ │ ├── meta.js
│ │ ├── notification.js
│ │ ├── actionTypes.js.map
│ │ ├── actionTypes.d.ts
│ │ └── actionTypes.js
│ ├── utils
│ │ ├── function.d.ts
│ │ ├── logic.js.map
│ │ ├── function.js.map
│ │ ├── logic.d.ts
│ │ ├── action.d.ts
│ │ ├── logic.js
│ │ ├── function.js
│ │ ├── action.js.map
│ │ └── action.js
│ ├── reducers
│ │ ├── error.d.ts
│ │ ├── loading.d.ts
│ │ ├── error.js.map
│ │ ├── loading.js.map
│ │ ├── error.js
│ │ └── loading.js
│ ├── operators
│ │ ├── endTo.js.map
│ │ ├── end.js.map
│ │ ├── endTo.d.ts
│ │ ├── end.d.ts
│ │ ├── endTo.js
│ │ ├── end.js
│ │ ├── createService.d.ts
│ │ ├── createService.js.map
│ │ └── createService.js
│ ├── index.d.ts
│ └── index.js.map
│ ├── test
│ ├── mocha.opts
│ ├── action.test.ts
│ ├── operator.test.ts
│ ├── init.test.ts
│ ├── redux.test.ts
│ ├── flow.test.ts
│ └── model.test.ts
│ ├── src
│ ├── constants
│ │ ├── symbols.ts
│ │ ├── meta.ts
│ │ ├── notification.ts
│ │ └── actionTypes.ts
│ ├── utils
│ │ ├── function.ts
│ │ ├── logic.ts
│ │ └── action.ts
│ ├── types
│ │ ├── action.ts
│ │ ├── Notification.ts
│ │ └── Model.ts
│ ├── operators
│ │ ├── endTo.ts
│ │ ├── end.ts
│ │ └── createService.ts
│ ├── reducers
│ │ ├── error.ts
│ │ └── loading.ts
│ └── index.ts
│ ├── index.d.ts
│ ├── package.json
│ └── package-lock.json
├── .vscode
└── settings.json
├── logo.png
├── .coveralls.yml
├── docs
├── logo.png
├── public
│ ├── error.png
│ ├── spinner.png
│ ├── error-notify.png
│ └── success-notify.png
├── README.md
├── introduction
│ ├── README.md
│ ├── get-started.md
│ └── concepts.md
├── .vuepress
│ └── config.js
└── basics
│ ├── error-state.md
│ ├── loading-state.md
│ ├── README.md
│ ├── change-and-patch.md
│ └── custom-service.md
├── mocha.opts
├── lerna.json
├── examples
├── README.md
├── github
│ ├── src
│ │ ├── index.html
│ │ ├── models
│ │ │ ├── common.ts
│ │ │ ├── user.ts
│ │ │ └── repo.ts
│ │ ├── services
│ │ │ ├── user.ts
│ │ │ ├── repo.ts
│ │ │ └── types.ts
│ │ ├── modules
│ │ │ ├── repo
│ │ │ │ ├── index.tsx
│ │ │ │ ├── Table.tsx
│ │ │ │ └── ActionPanel.tsx
│ │ │ ├── user
│ │ │ │ ├── index.tsx
│ │ │ │ ├── Table.tsx
│ │ │ │ └── ActionPanel.tsx
│ │ │ └── app
│ │ │ │ └── index.tsx
│ │ └── index.tsx
│ ├── tsconfig.json
│ └── webpack.config.js
└── package.json
├── .travis.yml
├── tsconfig.json
├── TODO.md
├── .editorconfig
├── tslint.json
├── README.md
└── package.json
/packages/core/README.md:
--------------------------------------------------------------------------------
1 | # reobservable core
2 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true
3 | }
--------------------------------------------------------------------------------
/packages/core/es/types/model.js:
--------------------------------------------------------------------------------
1 | //# sourceMappingURL=Model.js.map
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reobservable/reobservable/HEAD/logo.png
--------------------------------------------------------------------------------
/.coveralls.yml:
--------------------------------------------------------------------------------
1 | service_name: travis-pro
2 | repo_token: PdN5FiwxBcKCiYXIqoy3qA4Iri5ij7F1P
--------------------------------------------------------------------------------
/docs/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reobservable/reobservable/HEAD/docs/logo.png
--------------------------------------------------------------------------------
/docs/public/error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reobservable/reobservable/HEAD/docs/public/error.png
--------------------------------------------------------------------------------
/docs/public/spinner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reobservable/reobservable/HEAD/docs/public/spinner.png
--------------------------------------------------------------------------------
/docs/public/error-notify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reobservable/reobservable/HEAD/docs/public/error-notify.png
--------------------------------------------------------------------------------
/docs/public/success-notify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reobservable/reobservable/HEAD/docs/public/success-notify.png
--------------------------------------------------------------------------------
/mocha.opts:
--------------------------------------------------------------------------------
1 | --compilers ts-node/register
2 | --require source-map-support/register
3 | --bail
4 | --colors
5 | packages/**/test/*.test.ts
6 |
--------------------------------------------------------------------------------
/packages/core/es/types/model.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"Model.js","sourceRoot":"","sources":["../../src/types/Model.ts"],"names":[],"mappings":""}
--------------------------------------------------------------------------------
/packages/core/lib/types/model.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"Model.js","sourceRoot":"","sources":["../../src/types/Model.ts"],"names":[],"mappings":""}
--------------------------------------------------------------------------------
/packages/core/lib/types/model.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | //# sourceMappingURL=Model.js.map
--------------------------------------------------------------------------------
/packages/core/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --compilers ts-node/register
2 | --require source-map-support/register
3 | --full-trace
4 | --bail
5 | --colors
6 | test/*.test.ts
7 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.3",
3 | "packages": [
4 | "packages/*",
5 | "examples"
6 | ],
7 | "lerna": "2.11.0",
8 | "npmClient": "npm"
9 | }
10 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | # reobservable examples
2 |
3 | ## Install
4 |
5 | ```
6 | tnpm i
7 | ```
8 |
9 | ## Run Demo
10 |
11 | ```
12 | tnpm run start:github
13 | ```
14 |
--------------------------------------------------------------------------------
/packages/core/src/constants/symbols.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * symbols
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-09-19 16:55:51
5 | */
6 | export const ALIAS = Symbol('alias')
7 |
--------------------------------------------------------------------------------
/packages/core/es/constants/symbols.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * symbols
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-09-19 16:55:51
5 | */
6 | export declare const ALIAS: unique symbol;
7 |
--------------------------------------------------------------------------------
/packages/core/lib/constants/symbols.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * symbols
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-09-19 16:55:51
5 | */
6 | export declare const ALIAS: unique symbol;
7 |
--------------------------------------------------------------------------------
/packages/core/lib/types/Action.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"action.js","sourceRoot":"","sources":["../../src/types/action.ts"],"names":[],"mappings":";;AACA;;;;GAIG;AACH,0CAAsD;AACtD,gDAA4C"}
--------------------------------------------------------------------------------
/examples/github/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Demo Board
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/packages/core/es/constants/meta.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * meta constants
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 11:30:32
5 | */
6 | export declare const FLOW_END_INDICATOR: unique symbol;
7 |
--------------------------------------------------------------------------------
/packages/core/lib/constants/meta.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * meta constants
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 11:30:32
5 | */
6 | export declare const FLOW_END_INDICATOR: unique symbol;
7 |
--------------------------------------------------------------------------------
/packages/core/es/constants/symbols.js:
--------------------------------------------------------------------------------
1 | /**
2 | * symbols
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-09-19 16:55:51
5 | */
6 | export var ALIAS = Symbol('alias');
7 | //# sourceMappingURL=symbols.js.map
--------------------------------------------------------------------------------
/packages/core/lib/constants/meta.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"meta.js","sourceRoot":"","sources":["../../src/constants/meta.ts"],"names":[],"mappings":";AACA;;;;GAIG;;AAEU,QAAA,kBAAkB,GAAG,MAAM,CAAC,oBAAoB,CAAC,CAAA"}
--------------------------------------------------------------------------------
/packages/core/src/constants/meta.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * meta constants
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 11:30:32
5 | */
6 |
7 | export const FLOW_END_INDICATOR = Symbol('FLOW_END_INDICATOR')
8 |
--------------------------------------------------------------------------------
/packages/core/es/constants/meta.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"meta.js","sourceRoot":"","sources":["../../src/constants/meta.ts"],"names":[],"mappings":"AACA;;;;GAIG;AAEH,MAAM,CAAC,IAAM,kBAAkB,GAAG,MAAM,CAAC,oBAAoB,CAAC,CAAA"}
--------------------------------------------------------------------------------
/packages/core/lib/constants/symbols.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"symbols.js","sourceRoot":"","sources":["../../src/constants/symbols.ts"],"names":[],"mappings":";;AAAA;;;;GAIG;AACU,QAAA,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,CAAA"}
--------------------------------------------------------------------------------
/packages/core/es/constants/symbols.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"symbols.js","sourceRoot":"","sources":["../../src/constants/symbols.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,IAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,CAAA"}
--------------------------------------------------------------------------------
/packages/core/es/utils/function.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * function utils
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-13 15:32:55
5 | */
6 | /**
7 | * noop
8 | */
9 | export declare function noop(...args: any[]): any;
10 |
--------------------------------------------------------------------------------
/packages/core/es/constants/meta.js:
--------------------------------------------------------------------------------
1 | /**
2 | * meta constants
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 11:30:32
5 | */
6 | export var FLOW_END_INDICATOR = Symbol('FLOW_END_INDICATOR');
7 | //# sourceMappingURL=meta.js.map
--------------------------------------------------------------------------------
/packages/core/lib/utils/function.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * function utils
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-13 15:32:55
5 | */
6 | /**
7 | * noop
8 | */
9 | export declare function noop(...args: any[]): any;
10 |
--------------------------------------------------------------------------------
/packages/core/es/types/Action.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"action.js","sourceRoot":"","sources":["../../src/types/action.ts"],"names":[],"mappings":"AACA;;;;GAIG;AACH,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AACtD,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAA"}
--------------------------------------------------------------------------------
/packages/core/src/utils/function.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * function utils
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-13 15:32:55
5 | */
6 |
7 | /**
8 | * noop
9 | */
10 | export function noop(...args): any {
11 | // empty
12 | }
13 |
--------------------------------------------------------------------------------
/packages/core/src/constants/notification.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * notification constants
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-13 14:45:50
5 | */
6 |
7 | export const LEVEL = {
8 | silent: 0,
9 | error: 1,
10 | all: 2
11 | }
12 |
--------------------------------------------------------------------------------
/packages/core/es/utils/logic.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"logic.js","sourceRoot":"","sources":["../../src/utils/logic.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;GAKG;AACH,MAAM,UAAU,KAAK,CAAC,GAAQ;IAC5B,OAAO,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS,CAAA;AAC1C,CAAC"}
--------------------------------------------------------------------------------
/packages/core/es/utils/function.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"function.js","sourceRoot":"","sources":["../../src/utils/function.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;GAEG;AACH,MAAM,UAAU,IAAI;IAAC,cAAO;SAAP,UAAO,EAAP,qBAAO,EAAP,IAAO;QAAP,yBAAO;;IAC1B,QAAQ;AACV,CAAC"}
--------------------------------------------------------------------------------
/packages/core/lib/utils/logic.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"logic.js","sourceRoot":"","sources":["../../src/utils/logic.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;AAEH;;;;;GAKG;AACH,SAAgB,KAAK,CAAC,GAAQ;IAC5B,OAAO,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS,CAAA;AAC1C,CAAC;AAFD,sBAEC"}
--------------------------------------------------------------------------------
/packages/core/es/constants/notification.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * notification constants
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-13 14:45:50
5 | */
6 | export declare const LEVEL: {
7 | silent: number;
8 | error: number;
9 | all: number;
10 | };
11 |
--------------------------------------------------------------------------------
/packages/core/es/types/Action.js:
--------------------------------------------------------------------------------
1 | /**
2 | * action types
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 11:19:14
5 | */
6 | import { FLOW_END_INDICATOR } from '../constants/meta';
7 | import { ALIAS } from '../constants/symbols';
8 | //# sourceMappingURL=action.js.map
--------------------------------------------------------------------------------
/packages/core/lib/constants/notification.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * notification constants
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-13 14:45:50
5 | */
6 | export declare const LEVEL: {
7 | silent: number;
8 | error: number;
9 | all: number;
10 | };
11 |
--------------------------------------------------------------------------------
/packages/core/lib/constants/notification.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"notification.js","sourceRoot":"","sources":["../../src/constants/notification.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;AAEU,QAAA,KAAK,GAAG;IACnB,MAAM,EAAE,CAAC;IACT,KAAK,EAAE,CAAC;IACR,GAAG,EAAE,CAAC;CACP,CAAA"}
--------------------------------------------------------------------------------
/packages/core/lib/utils/function.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"function.js","sourceRoot":"","sources":["../../src/utils/function.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;AAEH;;GAEG;AACH,SAAgB,IAAI;IAAC,cAAO;SAAP,UAAO,EAAP,qBAAO,EAAP,IAAO;QAAP,yBAAO;;IAC1B,QAAQ;AACV,CAAC;AAFD,oBAEC"}
--------------------------------------------------------------------------------
/packages/core/es/constants/notification.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"notification.js","sourceRoot":"","sources":["../../src/constants/notification.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,CAAC,IAAM,KAAK,GAAG;IACnB,MAAM,EAAE,CAAC;IACT,KAAK,EAAE,CAAC;IACR,GAAG,EAAE,CAAC;CACP,CAAA"}
--------------------------------------------------------------------------------
/packages/core/es/constants/notification.js:
--------------------------------------------------------------------------------
1 | /**
2 | * notification constants
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-13 14:45:50
5 | */
6 | export var LEVEL = {
7 | silent: 0,
8 | error: 1,
9 | all: 2
10 | };
11 | //# sourceMappingURL=notification.js.map
--------------------------------------------------------------------------------
/packages/core/lib/constants/symbols.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | /**
4 | * symbols
5 | * @author yoyoyohamapi
6 | * @ignore created 2018-09-19 16:55:51
7 | */
8 | exports.ALIAS = Symbol('alias');
9 | //# sourceMappingURL=symbols.js.map
--------------------------------------------------------------------------------
/packages/core/es/reducers/error.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * error reducer
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 12:43:11
5 | */
6 | import { Action } from '../types/action';
7 | export default function error(state: {
8 | flows: {};
9 | services: {};
10 | }, action: Action): Object;
11 |
--------------------------------------------------------------------------------
/packages/core/lib/reducers/error.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * error reducer
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 12:43:11
5 | */
6 | import { Action } from '../types/action';
7 | export default function error(state: {
8 | flows: {};
9 | services: {};
10 | }, action: Action): Object;
11 |
--------------------------------------------------------------------------------
/packages/core/es/reducers/loading.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * loading reducer
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 12:36:49
5 | */
6 | import { Action } from '../types/action';
7 | export default function loading(state: {
8 | services: {};
9 | flows: {};
10 | }, action: Action): Object;
11 |
--------------------------------------------------------------------------------
/packages/core/es/utils/logic.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * logic utils
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 13:13:16
5 | */
6 | /**
7 | * is obj is nil(null or nil)
8 | * @param {any} obj
9 | * @returns {boolean}
10 | * @method isNil
11 | */
12 | export declare function isNil(obj: any): boolean;
13 |
--------------------------------------------------------------------------------
/packages/core/lib/constants/meta.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | /**
3 | * meta constants
4 | * @author yoyoyohamapi
5 | * @ignore created 2018-08-03 11:30:32
6 | */
7 | Object.defineProperty(exports, "__esModule", { value: true });
8 | exports.FLOW_END_INDICATOR = Symbol('FLOW_END_INDICATOR');
9 | //# sourceMappingURL=meta.js.map
--------------------------------------------------------------------------------
/packages/core/lib/reducers/loading.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * loading reducer
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 12:36:49
5 | */
6 | import { Action } from '../types/action';
7 | export default function loading(state: {
8 | services: {};
9 | flows: {};
10 | }, action: Action): Object;
11 |
--------------------------------------------------------------------------------
/packages/core/lib/utils/logic.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * logic utils
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 13:13:16
5 | */
6 | /**
7 | * is obj is nil(null or nil)
8 | * @param {any} obj
9 | * @returns {boolean}
10 | * @method isNil
11 | */
12 | export declare function isNil(obj: any): boolean;
13 |
--------------------------------------------------------------------------------
/examples/github/src/models/common.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * common model
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-12 23:54:27
5 | */
6 |
7 | export enum Order {
8 | Desc = 'desc',
9 | Asc = 'asc'
10 | }
11 |
12 | export interface Pagination {
13 | page: number
14 | pageSize: number
15 | total: number
16 | }
17 |
--------------------------------------------------------------------------------
/examples/github/src/services/user.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * user service
3 | * @author yoyoyohamapi
4 | * @ingore created 2018-08-14 00:01:23
5 | */
6 | import axios from 'axios'
7 | import { SearchParam } from '@models/user'
8 |
9 | export const fetch = (params: SearchParam) =>
10 | axios.get('https://api.github.com/search/users', { params })
11 |
--------------------------------------------------------------------------------
/packages/core/lib/operators/endTo.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"endTo.js","sourceRoot":"","sources":["../../src/operators/endTo.ts"],"names":[],"mappings":";;;;;;;;;;;;;AAAA;;;;GAIG;AACH,6BAAsD;AACtD,4CAAsC;AAEtC,0CAAsD;AAEtD;;;;;GAKG;AACH,SAAwB,KAAK,CAAC,MAAc;;IAC1C,OAAO,WAAI,CACT,iBAAK,cACA,MAAM,eACR,yBAAkB,IAAG,IAAI,OAC1B,CACH,CAAA;AACH,CAAC;AAPD,wBAOC"}
--------------------------------------------------------------------------------
/examples/github/src/services/repo.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * repo service
3 | * @author yoyoyohamapi
4 | * @ingore created 2018-08-14 00:01:23
5 | */
6 | import axios from 'axios'
7 | import { SearchParam } from '@models/repo'
8 |
9 | export const fetch = (params: SearchParam) =>
10 | axios.get('https://api.github.com/search/repositories', { params })
11 |
--------------------------------------------------------------------------------
/packages/core/lib/types/Action.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | /**
4 | * action types
5 | * @author yoyoyohamapi
6 | * @ignore created 2018-08-03 11:19:14
7 | */
8 | var meta_1 = require("../constants/meta");
9 | var symbols_1 = require("../constants/symbols");
10 | //# sourceMappingURL=action.js.map
--------------------------------------------------------------------------------
/packages/core/lib/constants/notification.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | /**
3 | * notification constants
4 | * @author yoyoyohamapi
5 | * @ignore created 2018-08-13 14:45:50
6 | */
7 | Object.defineProperty(exports, "__esModule", { value: true });
8 | exports.LEVEL = {
9 | silent: 0,
10 | error: 1,
11 | all: 2
12 | };
13 | //# sourceMappingURL=notification.js.map
--------------------------------------------------------------------------------
/packages/core/src/utils/logic.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * logic utils
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 13:13:16
5 | */
6 |
7 | /**
8 | * is obj is nil(null or nil)
9 | * @param {any} obj
10 | * @returns {boolean}
11 | * @method isNil
12 | */
13 | export function isNil(obj: any): boolean {
14 | return obj === null || obj === undefined
15 | }
16 |
--------------------------------------------------------------------------------
/packages/core/es/types/notification.d.ts:
--------------------------------------------------------------------------------
1 | declare type notificate = (content: string, duration?: number) => void;
2 | export interface Notification {
3 | info?: notificate;
4 | success?: notificate;
5 | warn?: notificate;
6 | error?: notificate;
7 | }
8 | export declare enum NotificationLevel {
9 | silent,
10 | erorr,
11 | all
12 | }
13 | export {};
14 |
--------------------------------------------------------------------------------
/packages/core/lib/types/notification.d.ts:
--------------------------------------------------------------------------------
1 | declare type notificate = (content: string, duration?: number) => void;
2 | export interface Notification {
3 | info?: notificate;
4 | success?: notificate;
5 | warn?: notificate;
6 | error?: notificate;
7 | }
8 | export declare enum NotificationLevel {
9 | silent,
10 | erorr,
11 | all
12 | }
13 | export {};
14 |
--------------------------------------------------------------------------------
/packages/core/es/utils/logic.js:
--------------------------------------------------------------------------------
1 | /**
2 | * logic utils
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 13:13:16
5 | */
6 | /**
7 | * is obj is nil(null or nil)
8 | * @param {any} obj
9 | * @returns {boolean}
10 | * @method isNil
11 | */
12 | export function isNil(obj) {
13 | return obj === null || obj === undefined;
14 | }
15 | //# sourceMappingURL=logic.js.map
--------------------------------------------------------------------------------
/packages/core/lib/constants/actionTypes.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"actionTypes.js","sourceRoot":"","sources":["../../src/constants/actionTypes.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;AAEU,QAAA,oBAAoB,GAAG,mBAAmB,CAAA;AAC1C,QAAA,kBAAkB,GAAG,iBAAiB,CAAA;AACtC,QAAA,4BAA4B,GAAG,2BAA2B,CAAA;AAC1D,QAAA,0BAA0B,GAAG,yBAAyB,CAAA;AACtD,QAAA,gBAAgB,GAAG,eAAe,CAAA;AAClC,QAAA,wBAAwB,GAAG,uBAAuB,CAAA"}
--------------------------------------------------------------------------------
/packages/core/lib/types/notification.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"Notification.js","sourceRoot":"","sources":["../../src/types/Notification.ts"],"names":[],"mappings":";;AAAA;;;;GAIG;AACH,0DAAiD;AAWjD,IAAY,iBAIX;AAJD,WAAY,iBAAiB;IAC3B,gDAAS,oBAAK,CAAC,MAAM,YAAA,CAAA;IACrB,+CAAQ,oBAAK,CAAC,KAAK,WAAA,CAAA;IACnB,6CAAM,oBAAK,CAAC,GAAG,SAAA,CAAA;AACjB,CAAC,EAJW,iBAAiB,GAAjB,yBAAiB,KAAjB,yBAAiB,QAI5B"}
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Reobservable
4 |
5 | Redux + rxjs + redux-obersvable best practice. Inspired by dva, rematch.
6 |
7 | ---------------
8 |
9 | ## Installation
10 |
11 | ```
12 | npm install @reobservable/core --save
13 | ```
14 |
15 | ## Examples
16 |
17 | See [reobservble examples](https://github.com/reobservable/reobservable/tree/master/examples)
18 |
--------------------------------------------------------------------------------
/packages/core/es/utils/function.js:
--------------------------------------------------------------------------------
1 | /**
2 | * function utils
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-13 15:32:55
5 | */
6 | /**
7 | * noop
8 | */
9 | export function noop() {
10 | var args = [];
11 | for (var _i = 0; _i < arguments.length; _i++) {
12 | args[_i] = arguments[_i];
13 | }
14 | // empty
15 | }
16 | //# sourceMappingURL=function.js.map
--------------------------------------------------------------------------------
/packages/core/lib/operators/end.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"end.js","sourceRoot":"","sources":["../../src/operators/end.ts"],"names":[],"mappings":";;;;;;;;;;;;;AAAA;;;;GAIG;AACH,6BAAsD;AACtD,4CAAoC;AAEpC,0CAAsD;AAEtD;;;;;GAKG;AACH,SAAwB,GAAG,CAAC,OAA+B;IACzD,OAAO,WAAI,CACT,eAAG,CAAC,UAAC,MAAc;;QAAK,OAAA,cACnB,OAAO,CAAC,MAAM,CAAC,eACjB,yBAAkB,IAAG,IAAI,OAC1B;IAHsB,CAGtB,CAAC,CACJ,CAAA;AACH,CAAC;AAPD,sBAOC"}
--------------------------------------------------------------------------------
/packages/core/es/types/notification.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"Notification.js","sourceRoot":"","sources":["../../src/types/Notification.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAA;AAWjD,MAAM,CAAN,IAAY,iBAIX;AAJD,WAAY,iBAAiB;IAC3B,gDAAS,KAAK,CAAC,MAAM,YAAA,CAAA;IACrB,+CAAQ,KAAK,CAAC,KAAK,WAAA,CAAA;IACnB,6CAAM,KAAK,CAAC,GAAG,SAAA,CAAA;AACjB,CAAC,EAJW,iBAAiB,KAAjB,iBAAiB,QAI5B"}
--------------------------------------------------------------------------------
/packages/core/es/operators/endTo.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"endTo.js","sourceRoot":"","sources":["../../src/operators/endTo.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA;;;;GAIG;AACH,OAAO,EAAE,IAAI,EAA6B,MAAM,MAAM,CAAA;AACtD,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAEtC,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAEtD;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,KAAK,CAAC,MAAc;;IAC1C,OAAO,IAAI,CACT,KAAK,cACA,MAAM,eACR,kBAAkB,IAAG,IAAI,OAC1B,CACH,CAAA;AACH,CAAC"}
--------------------------------------------------------------------------------
/packages/core/es/constants/actionTypes.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"actionTypes.js","sourceRoot":"","sources":["../../src/constants/actionTypes.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,CAAC,IAAM,oBAAoB,GAAG,mBAAmB,CAAA;AACvD,MAAM,CAAC,IAAM,kBAAkB,GAAG,iBAAiB,CAAA;AACnD,MAAM,CAAC,IAAM,4BAA4B,GAAG,2BAA2B,CAAA;AACvE,MAAM,CAAC,IAAM,0BAA0B,GAAG,yBAAyB,CAAA;AACnE,MAAM,CAAC,IAAM,gBAAgB,GAAG,eAAe,CAAA;AAC/C,MAAM,CAAC,IAAM,wBAAwB,GAAG,uBAAuB,CAAA"}
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "10"
4 |
5 | cache:
6 | directories:
7 | - "node_modules"
8 |
9 | script:
10 | - npm run bootstrap
11 | - npm run docs:build
12 | - npm run test
13 | - npm run coverage
14 |
15 | deploy:
16 | - provider: pages
17 | skip-cleanup: true
18 | local-dir: docs/.vuepress/dist
19 | github-token: $GITHUB_TOKEN
20 | keep-history: true
21 | on:
22 | branch: master
--------------------------------------------------------------------------------
/examples/github/src/services/types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * service
3 | * @author yoyoyohamapi
4 | * @ingore created 2018-08-13 23:55:23
5 | */
6 | import { AxiosResponse, AxiosError } from 'axios'
7 | import { ServiceConfig, ServiceFunc } from '@reobservable/core'
8 |
9 | export interface ApiService
10 | extends ServiceConfig, AxiosError> {}
11 | export interface ApiServiceFunc
12 | extends ServiceFunc, AxiosError> {}
13 |
--------------------------------------------------------------------------------
/packages/core/es/operators/endTo.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * endTo operator
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 10:31:20
5 | */
6 | import { UnaryFunction, Observable } from 'rxjs';
7 | import { Action, EndAction } from '../types/action';
8 | /**
9 | * endTo
10 | * @param {Action} action
11 | * @returns {Observable}
12 | * @method endTo
13 | */
14 | export default function endTo(action: Action): UnaryFunction, Observable>;
15 |
--------------------------------------------------------------------------------
/packages/core/es/utils/action.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * action utils
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 13:10:52
5 | */
6 | import { Action } from '../types/action';
7 | /**
8 | * get action payload
9 | * @param {Action} action
10 | */
11 | export declare function getPayload(action: Action): any;
12 | /**
13 | * action sanitizer for redux-dev-tools
14 | * @param {Action} action
15 | */
16 | export declare function actionSanitizer(action: any): any;
17 |
--------------------------------------------------------------------------------
/packages/core/lib/operators/endTo.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * endTo operator
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 10:31:20
5 | */
6 | import { UnaryFunction, Observable } from 'rxjs';
7 | import { Action, EndAction } from '../types/action';
8 | /**
9 | * endTo
10 | * @param {Action} action
11 | * @returns {Observable}
12 | * @method endTo
13 | */
14 | export default function endTo(action: Action): UnaryFunction, Observable>;
15 |
--------------------------------------------------------------------------------
/packages/core/lib/utils/action.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * action utils
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 13:10:52
5 | */
6 | import { Action } from '../types/action';
7 | /**
8 | * get action payload
9 | * @param {Action} action
10 | */
11 | export declare function getPayload(action: Action): any;
12 | /**
13 | * action sanitizer for redux-dev-tools
14 | * @param {Action} action
15 | */
16 | export declare function actionSanitizer(action: any): any;
17 |
--------------------------------------------------------------------------------
/packages/core/src/types/action.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * action types
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 11:19:14
5 | */
6 | import { FLOW_END_INDICATOR } from '../constants/meta'
7 | import { ALIAS } from '../constants/symbols'
8 |
9 | export interface Action {
10 | type: string
11 | payload?: any
12 | [ALIAS]?: string
13 | }
14 |
15 | export interface EndAction {
16 | type: string
17 | [FLOW_END_INDICATOR]: boolean
18 | payload?: any
19 | }
20 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "sourceMap": true,
4 | "moduleResolution": "node",
5 | "declaration": true,
6 | "target": "es5",
7 | "lib": [
8 | "es6"
9 | ],
10 | "types": [
11 | "node",
12 | "mocha"
13 | ]
14 | },
15 | "include": [
16 | "packages/**/src/**/*"
17 | ],
18 | "exclude": [
19 | "node_modules",
20 | // "./test/**/*",
21 | "**/*.spec.ts"
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/packages/core/es/operators/end.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"end.js","sourceRoot":"","sources":["../../src/operators/end.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA;;;;GAIG;AACH,OAAO,EAAE,IAAI,EAA6B,MAAM,MAAM,CAAA;AACtD,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAA;AAEpC,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAEtD;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,GAAG,CAAC,OAA+B;IACzD,OAAO,IAAI,CACT,GAAG,CAAC,UAAC,MAAc;;QAAK,OAAA,cACnB,OAAO,CAAC,MAAM,CAAC,eACjB,kBAAkB,IAAG,IAAI,OAC1B;IAHsB,CAGtB,CAAC,CACJ,CAAA;AACH,CAAC"}
--------------------------------------------------------------------------------
/packages/core/es/types/Action.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * action types
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 11:19:14
5 | */
6 | import { FLOW_END_INDICATOR } from '../constants/meta';
7 | import { ALIAS } from '../constants/symbols';
8 | export interface Action {
9 | type: string;
10 | payload?: any;
11 | [ALIAS]?: string;
12 | }
13 | export interface EndAction {
14 | type: string;
15 | [FLOW_END_INDICATOR]: boolean;
16 | payload?: any;
17 | }
18 |
--------------------------------------------------------------------------------
/packages/core/lib/types/Action.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * action types
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 11:19:14
5 | */
6 | import { FLOW_END_INDICATOR } from '../constants/meta';
7 | import { ALIAS } from '../constants/symbols';
8 | export interface Action {
9 | type: string;
10 | payload?: any;
11 | [ALIAS]?: string;
12 | }
13 | export interface EndAction {
14 | type: string;
15 | [FLOW_END_INDICATOR]: boolean;
16 | payload?: any;
17 | }
18 |
--------------------------------------------------------------------------------
/packages/core/lib/utils/logic.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | /**
3 | * logic utils
4 | * @author yoyoyohamapi
5 | * @ignore created 2018-08-03 13:13:16
6 | */
7 | Object.defineProperty(exports, "__esModule", { value: true });
8 | /**
9 | * is obj is nil(null or nil)
10 | * @param {any} obj
11 | * @returns {boolean}
12 | * @method isNil
13 | */
14 | function isNil(obj) {
15 | return obj === null || obj === undefined;
16 | }
17 | exports.isNil = isNil;
18 | //# sourceMappingURL=logic.js.map
--------------------------------------------------------------------------------
/packages/core/lib/utils/function.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | /**
3 | * function utils
4 | * @author yoyoyohamapi
5 | * @ignore created 2018-08-13 15:32:55
6 | */
7 | Object.defineProperty(exports, "__esModule", { value: true });
8 | /**
9 | * noop
10 | */
11 | function noop() {
12 | var args = [];
13 | for (var _i = 0; _i < arguments.length; _i++) {
14 | args[_i] = arguments[_i];
15 | }
16 | // empty
17 | }
18 | exports.noop = noop;
19 | //# sourceMappingURL=function.js.map
--------------------------------------------------------------------------------
/packages/core/es/operators/end.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * end operator
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 10:31:20
5 | */
6 | import { UnaryFunction, Observable } from 'rxjs';
7 | import { Action, EndAction } from '../types/action';
8 | /**
9 | * end operator
10 | * @param {function(value: Action): EndAction} project
11 | * @returns {Observable}
12 | * @method end
13 | */
14 | export default function end(project: (value: any) => Action): UnaryFunction, Observable>;
15 |
--------------------------------------------------------------------------------
/packages/core/lib/operators/end.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * end operator
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 10:31:20
5 | */
6 | import { UnaryFunction, Observable } from 'rxjs';
7 | import { Action, EndAction } from '../types/action';
8 | /**
9 | * end operator
10 | * @param {function(value: Action): EndAction} project
11 | * @returns {Observable}
12 | * @method end
13 | */
14 | export default function end(project: (value: any) => Action): UnaryFunction, Observable>;
15 |
--------------------------------------------------------------------------------
/packages/core/lib/utils/action.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"action.js","sourceRoot":"","sources":["../../src/utils/action.ts"],"names":[],"mappings":";;AAMA,iCAA+B;AAC/B,gDAA4C;AAE5C;;;GAGG;AACH,SAAgB,UAAU,CAAC,MAAc;IACvC,OAAO,aAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAA;AACpD,CAAC;AAFD,gCAEC;AAED;;;GAGG;AACH,SAAgB,eAAe,CAAC,MAAM;IACpC,OAAO,MAAM,CAAC,eAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE;QAC/C,IAAI,EAAE,MAAM,CAAC,eAAK,CAAC;KACpB,CAAC,CAAC,CAAC,CAAC,MAAM,CAAA;AACb,CAAC;AAJD,0CAIC"}
--------------------------------------------------------------------------------
/packages/core/src/constants/actionTypes.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * action types
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 11:27:09
5 | */
6 |
7 | export const LOADING_START_ACTION = '@@LOADING_START@@'
8 | export const LOADING_END_ACTION = '@@LOADING_END@@'
9 | export const SERVICE_LOADING_START_ACTION = '@@SERVICE_LOADING_START@@'
10 | export const SERVICE_LOADING_END_ACTION = '@@SERVICE_LOADING_END@@'
11 | export const ERROR_SET_ACTION = '@@ERROR_SET@@'
12 | export const SERVICE_ERROR_SET_ACTION = '@@SERVICE_ERROR_SET@@'
13 |
--------------------------------------------------------------------------------
/packages/core/src/types/Notification.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * notification type
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-13 14:16:13
5 | */
6 | import { LEVEL } from '../constants/notification'
7 |
8 | type notificate = (content: string, duration?: number) => void
9 |
10 | export interface Notification {
11 | info?: notificate
12 | success?: notificate
13 | warn?: notificate
14 | error?: notificate
15 | }
16 |
17 | export enum NotificationLevel {
18 | silent = LEVEL.silent,
19 | erorr = LEVEL.error,
20 | all = LEVEL.all
21 | }
22 |
--------------------------------------------------------------------------------
/packages/core/es/constants/actionTypes.js:
--------------------------------------------------------------------------------
1 | /**
2 | * action types
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 11:27:09
5 | */
6 | export var LOADING_START_ACTION = '@@LOADING_START@@';
7 | export var LOADING_END_ACTION = '@@LOADING_END@@';
8 | export var SERVICE_LOADING_START_ACTION = '@@SERVICE_LOADING_START@@';
9 | export var SERVICE_LOADING_END_ACTION = '@@SERVICE_LOADING_END@@';
10 | export var ERROR_SET_ACTION = '@@ERROR_SET@@';
11 | export var SERVICE_ERROR_SET_ACTION = '@@SERVICE_ERROR_SET@@';
12 | //# sourceMappingURL=actionTypes.js.map
--------------------------------------------------------------------------------
/packages/core/es/utils/action.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"action.js","sourceRoot":"","sources":["../../src/utils/action.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC/B,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAA;AAE5C;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,MAAc;IACvC,OAAO,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAA;AACpD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,MAAM;IACpC,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE;QAC/C,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC;KACpB,CAAC,CAAC,CAAC,CAAC,MAAM,CAAA;AACb,CAAC"}
--------------------------------------------------------------------------------
/packages/core/es/constants/actionTypes.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * action types
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 11:27:09
5 | */
6 | export declare const LOADING_START_ACTION = "@@LOADING_START@@";
7 | export declare const LOADING_END_ACTION = "@@LOADING_END@@";
8 | export declare const SERVICE_LOADING_START_ACTION = "@@SERVICE_LOADING_START@@";
9 | export declare const SERVICE_LOADING_END_ACTION = "@@SERVICE_LOADING_END@@";
10 | export declare const ERROR_SET_ACTION = "@@ERROR_SET@@";
11 | export declare const SERVICE_ERROR_SET_ACTION = "@@SERVICE_ERROR_SET@@";
12 |
--------------------------------------------------------------------------------
/packages/core/lib/constants/actionTypes.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * action types
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 11:27:09
5 | */
6 | export declare const LOADING_START_ACTION = "@@LOADING_START@@";
7 | export declare const LOADING_END_ACTION = "@@LOADING_END@@";
8 | export declare const SERVICE_LOADING_START_ACTION = "@@SERVICE_LOADING_START@@";
9 | export declare const SERVICE_LOADING_END_ACTION = "@@SERVICE_LOADING_END@@";
10 | export declare const ERROR_SET_ACTION = "@@ERROR_SET@@";
11 | export declare const SERVICE_ERROR_SET_ACTION = "@@SERVICE_ERROR_SET@@";
12 |
--------------------------------------------------------------------------------
/packages/core/es/utils/action.js:
--------------------------------------------------------------------------------
1 | import { isNil } from './logic';
2 | import { ALIAS } from '../constants/symbols';
3 | /**
4 | * get action payload
5 | * @param {Action} action
6 | */
7 | export function getPayload(action) {
8 | return isNil(action.payload) ? {} : action.payload;
9 | }
10 | /**
11 | * action sanitizer for redux-dev-tools
12 | * @param {Action} action
13 | */
14 | export function actionSanitizer(action) {
15 | return action[ALIAS] ? Object.assign({}, action, {
16 | type: action[ALIAS]
17 | }) : action;
18 | }
19 | //# sourceMappingURL=action.js.map
--------------------------------------------------------------------------------
/packages/core/es/types/notification.js:
--------------------------------------------------------------------------------
1 | /**
2 | * notification type
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-13 14:16:13
5 | */
6 | import { LEVEL } from '../constants/notification';
7 | export var NotificationLevel;
8 | (function (NotificationLevel) {
9 | NotificationLevel[NotificationLevel["silent"] = LEVEL.silent] = "silent";
10 | NotificationLevel[NotificationLevel["erorr"] = LEVEL.error] = "erorr";
11 | NotificationLevel[NotificationLevel["all"] = LEVEL.all] = "all";
12 | })(NotificationLevel || (NotificationLevel = {}));
13 | //# sourceMappingURL=Notification.js.map
--------------------------------------------------------------------------------
/packages/core/lib/constants/actionTypes.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | /**
3 | * action types
4 | * @author yoyoyohamapi
5 | * @ignore created 2018-08-03 11:27:09
6 | */
7 | Object.defineProperty(exports, "__esModule", { value: true });
8 | exports.LOADING_START_ACTION = '@@LOADING_START@@';
9 | exports.LOADING_END_ACTION = '@@LOADING_END@@';
10 | exports.SERVICE_LOADING_START_ACTION = '@@SERVICE_LOADING_START@@';
11 | exports.SERVICE_LOADING_END_ACTION = '@@SERVICE_LOADING_END@@';
12 | exports.ERROR_SET_ACTION = '@@ERROR_SET@@';
13 | exports.SERVICE_ERROR_SET_ACTION = '@@SERVICE_ERROR_SET@@';
14 | //# sourceMappingURL=actionTypes.js.map
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | ## Feature
2 |
3 | - [x] create model's reducers
4 | - [x] create model's selectors
5 | - [x] create model's flows
6 | - [x] support flow loading
7 | - [x] support error loading
8 | - [x] support custom service & server creator
9 | - [x] support service loading
10 | - [x] support service error
11 | - [x] support custom redux config
12 | - [x] support action type alias
13 | - [ ] support global `state$` observing
14 | - [ ] support `model.change()` ...
15 | - [ ] `@reobservable/test`
16 | - [ ] `@reobservable/hooks`
17 |
18 |
19 | ## ecosystem
20 |
21 | - [ ] command line tools
22 | - [ ] vscode plugin
23 | - [ ] snippet
24 | - [x] github demo
25 |
26 |
--------------------------------------------------------------------------------
/packages/core/src/operators/endTo.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * endTo operator
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 10:31:20
5 | */
6 | import { pipe, UnaryFunction, Observable } from 'rxjs'
7 | import { mapTo } from 'rxjs/operators'
8 | import { Action, EndAction } from '../types/action'
9 | import { FLOW_END_INDICATOR } from '../constants/meta'
10 |
11 | /**
12 | * endTo
13 | * @param {Action} action
14 | * @returns {Observable}
15 | * @method endTo
16 | */
17 | export default function endTo(
18 | action: Action
19 | ): UnaryFunction, Observable> {
20 | return pipe(
21 | mapTo({
22 | ...action,
23 | [FLOW_END_INDICATOR]: true
24 | })
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/packages/core/src/utils/action.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * action utils
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 13:10:52
5 | */
6 | import { Action } from '../types/action'
7 | import { isNil } from './logic'
8 | import { ALIAS } from '../constants/symbols'
9 |
10 | /**
11 | * get action payload
12 | * @param {Action} action
13 | */
14 | export function getPayload(action: Action) {
15 | return isNil(action.payload) ? {} : action.payload
16 | }
17 |
18 | /**
19 | * action sanitizer for redux-dev-tools
20 | * @param {Action} action
21 | */
22 | export function actionSanitizer(action) {
23 | return action[ALIAS]
24 | ? Object.assign({}, action, {
25 | type: action[ALIAS]
26 | })
27 | : action
28 | }
29 |
--------------------------------------------------------------------------------
/packages/core/lib/types/notification.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | /**
4 | * notification type
5 | * @author yoyoyohamapi
6 | * @ignore created 2018-08-13 14:16:13
7 | */
8 | var notification_1 = require("../constants/notification");
9 | var NotificationLevel;
10 | (function (NotificationLevel) {
11 | NotificationLevel[NotificationLevel["silent"] = notification_1.LEVEL.silent] = "silent";
12 | NotificationLevel[NotificationLevel["erorr"] = notification_1.LEVEL.error] = "erorr";
13 | NotificationLevel[NotificationLevel["all"] = notification_1.LEVEL.all] = "all";
14 | })(NotificationLevel = exports.NotificationLevel || (exports.NotificationLevel = {}));
15 | //# sourceMappingURL=Notification.js.map
--------------------------------------------------------------------------------
/packages/core/lib/utils/action.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | var logic_1 = require("./logic");
4 | var symbols_1 = require("../constants/symbols");
5 | /**
6 | * get action payload
7 | * @param {Action} action
8 | */
9 | function getPayload(action) {
10 | return logic_1.isNil(action.payload) ? {} : action.payload;
11 | }
12 | exports.getPayload = getPayload;
13 | /**
14 | * action sanitizer for redux-dev-tools
15 | * @param {Action} action
16 | */
17 | function actionSanitizer(action) {
18 | return action[symbols_1.ALIAS] ? Object.assign({}, action, {
19 | type: action[symbols_1.ALIAS]
20 | }) : action;
21 | }
22 | exports.actionSanitizer = actionSanitizer;
23 | //# sourceMappingURL=action.js.map
--------------------------------------------------------------------------------
/examples/github/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "sourceMap": true,
4 | "noImplicitAny": false,
5 | "moduleResolution": "node",
6 | "allowSyntheticDefaultImports": true,
7 | "baseUrl": "./src",
8 | "paths": {
9 | "@config/*": ["./config/*"],
10 | "@components/*": ["./components/*"],
11 | "@utils/*": ["./utils/*"],
12 | "@modules/*": ["./modules/*"],
13 | "@models/*": ["./models/*"],
14 | "@services/*": ["./services/*"]
15 | },
16 | "lib": [
17 | "es2015",
18 | "es6",
19 | "dom",
20 | "dom.iterable"
21 | ],
22 | "jsx": "react",
23 | },
24 | "include": [
25 | "./src/**/*"
26 | ],
27 | "exclude": [
28 | "node_modules",
29 | "**/*.spec.ts"
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/packages/core/src/operators/end.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * end operator
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 10:31:20
5 | */
6 | import { pipe, UnaryFunction, Observable } from 'rxjs'
7 | import { map } from 'rxjs/operators'
8 | import { Action, EndAction } from '../types/action'
9 | import { FLOW_END_INDICATOR } from '../constants/meta'
10 |
11 | /**
12 | * end operator
13 | * @param {function(value: Action): EndAction} project
14 | * @returns {Observable}
15 | * @method end
16 | */
17 | export default function end(
18 | project: (value: any) => Action
19 | ): UnaryFunction, Observable> {
20 | return pipe(
21 | map((action: Action) => ({
22 | ...project(action),
23 | [FLOW_END_INDICATOR]: true
24 | }))
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: https://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Unix-style newlines with a newline ending every file
7 | [*]
8 | end_of_line = lf
9 | insert_final_newline = true
10 |
11 | # Matches multiple files with brace expansion notation
12 | # Set default charset
13 | [*.{js,py}]
14 | charset = utf-8
15 |
16 | # 2 space indentation
17 | [*.py]
18 | indent_style = space
19 | indent_size = 2
20 |
21 | # Tab indentation (no size specified)
22 | [Makefile]
23 | indent_style = tab
24 |
25 | # Indentation override for all JS under lib directory
26 | [lib/**.js]
27 | indent_style = space
28 | indent_size = 2
29 |
30 | # Matches the exact files either package.json or .travis.yml
31 | [{package.json,.travis.yml}]
32 | indent_style = space
33 | indent_size = 2
--------------------------------------------------------------------------------
/examples/github/src/modules/repo/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * repo
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-08 14:50:22
5 | */
6 | import * as React from 'react'
7 | import { connect, DispatchProp } from 'react-redux'
8 | import ActionPanel from './ActionPanel'
9 | import Table from './Table'
10 |
11 | class Repo extends React.Component {
12 | componentDidMount() {
13 | this.props.dispatch({
14 | type: 'repo/fetch'
15 | })
16 | }
17 |
18 | componentWillUnmount() {
19 | this.props.dispatch({
20 | type: 'repo/stopPolling'
21 | })
22 | }
23 |
24 | render() {
25 | return (
26 | <>
27 |
28 |
29 | >
30 | )
31 | }
32 | }
33 |
34 | const mapStateToProps = state => ({})
35 |
36 | export default connect(mapStateToProps)(Repo)
37 |
--------------------------------------------------------------------------------
/examples/github/src/modules/user/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * user module
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-07-17 21:15:39
5 | */
6 | import * as React from 'react'
7 | import { connect, DispatchProp } from 'react-redux'
8 | import ActionPanel from './ActionPanel'
9 | import Table from './Table'
10 |
11 | class User extends React.Component {
12 | componentDidMount() {
13 | this.props.dispatch({
14 | type: 'user/fetch'
15 | })
16 | }
17 |
18 | componentWillUnmount() {
19 | this.props.dispatch({
20 | type: 'user/stopPolling'
21 | })
22 | }
23 |
24 | render() {
25 | return (
26 | <>
27 |
28 |
29 | >
30 | )
31 | }
32 | }
33 |
34 | const mapStateToProps = state => ({})
35 |
36 | export default connect(mapStateToProps)(User)
37 |
--------------------------------------------------------------------------------
/packages/core/lib/reducers/error.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"error.js","sourceRoot":"","sources":["../../src/reducers/error.ts"],"names":[],"mappings":";;;;;;;;;;;;;AAMA,wDAAyI;AAEzI,SAAwB,KAAK,CAAC,KAAiC,EAAE,MAAc;IAAjD,sBAAA,EAAA,UAAS,KAAK,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAC;;IACrD,IAAA,kBAAI,CAAW;IACvB,IAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAA;IACpD,QAAQ,IAAI,EAAE;QACZ,eAAe;QACf,KAAK,kCAAoB,CAAC,CAAC;YACzB,oBACK,KAAK,IACR,KAAK,eACA,KAAK,CAAC,KAAK,eACb,OAAO,CAAC,IAAI,IAAG,IAAI,UAEvB;SACF;QACD,KAAK,0CAA4B,CAAC,CAAC;YACjC,oBACK,KAAK,IACR,QAAQ,eACH,KAAK,CAAC,QAAQ,eAChB,OAAO,CAAC,OAAO,IAAG,IAAI,UAE1B;SACF;QACD,KAAK,8BAAgB,CAAC,CAAC;YACrB,oBACK,KAAK,IACR,KAAK,eACA,KAAK,CAAC,KAAK,eACb,OAAO,CAAC,IAAI,IAAG,OAAO,CAAC,KAAK,UAEhC;SACF;QACD,KAAK,sCAAwB,CAAC,CAAC;YAC7B,oBACK,KAAK,IACR,QAAQ,eACH,KAAK,CAAC,QAAQ,eAChB,OAAO,CAAC,OAAO,IAAG,OAAO,CAAC,KAAK,UAEnC;SACF;QACD;YACE,OAAO,KAAK,CAAA;KACf;AACH,CAAC;AA5CD,wBA4CC"}
--------------------------------------------------------------------------------
/docs/introduction/README.md:
--------------------------------------------------------------------------------
1 | # Motivation
2 |
3 | I'm a big fan of [dvajs](https://github.com/dvajs/dva), it helps me get rid out of the boilerplate of redux. But I think redux-saga in dva sometimes can be a little bit verbose since I prefer functional reactive programming(frp) than imperative programming.
4 |
5 | I know that reinvent the weel could be a terrible practice, but in dva community, there is no plan to support frp. At last, I decided to create **reobservable**, which, in a world, is a state manager mixined dva architecture and frp features(RxJS + redux-observable. In reobservable, dva architecture wiped out the boilerplate code of redux, RxJS played an elite role in managing asynchronous task.
6 |
7 | If you:
8 |
9 | - love redux concept, love the predicatable, centralized, debuggable features
10 | - stuking with the boilerplate code of redux
11 | - want to use RxJS to managing asynchronous task
12 |
13 | try reobservable!
--------------------------------------------------------------------------------
/packages/core/es/reducers/error.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"error.js","sourceRoot":"","sources":["../../src/reducers/error.ts"],"names":[],"mappings":";;;;;;;;;;;AAMA,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,4BAA4B,EAAE,MAAM,0BAA0B,CAAA;AAEzI,MAAM,CAAC,OAAO,UAAU,KAAK,CAAC,KAAiC,EAAE,MAAc;IAAjD,sBAAA,EAAA,UAAS,KAAK,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAC;;IACrD,IAAA,kBAAI,CAAW;IACvB,IAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAA;IACpD,QAAQ,IAAI,EAAE;QACZ,eAAe;QACf,KAAK,oBAAoB,CAAC,CAAC;YACzB,oBACK,KAAK,IACR,KAAK,eACA,KAAK,CAAC,KAAK,eACb,OAAO,CAAC,IAAI,IAAG,IAAI,UAEvB;SACF;QACD,KAAK,4BAA4B,CAAC,CAAC;YACjC,oBACK,KAAK,IACR,QAAQ,eACH,KAAK,CAAC,QAAQ,eAChB,OAAO,CAAC,OAAO,IAAG,IAAI,UAE1B;SACF;QACD,KAAK,gBAAgB,CAAC,CAAC;YACrB,oBACK,KAAK,IACR,KAAK,eACA,KAAK,CAAC,KAAK,eACb,OAAO,CAAC,IAAI,IAAG,OAAO,CAAC,KAAK,UAEhC;SACF;QACD,KAAK,wBAAwB,CAAC,CAAC;YAC7B,oBACK,KAAK,IACR,QAAQ,eACH,KAAK,CAAC,QAAQ,eAChB,OAAO,CAAC,OAAO,IAAG,OAAO,CAAC,KAAK,UAEnC;SACF;QACD;YACE,OAAO,KAAK,CAAA;KACf;AACH,CAAC"}
--------------------------------------------------------------------------------
/packages/core/es/operators/endTo.js:
--------------------------------------------------------------------------------
1 | var __assign = (this && this.__assign) || function () {
2 | __assign = Object.assign || function(t) {
3 | for (var s, i = 1, n = arguments.length; i < n; i++) {
4 | s = arguments[i];
5 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6 | t[p] = s[p];
7 | }
8 | return t;
9 | };
10 | return __assign.apply(this, arguments);
11 | };
12 | /**
13 | * endTo operator
14 | * @author yoyoyohamapi
15 | * @ignore created 2018-08-03 10:31:20
16 | */
17 | import { pipe } from 'rxjs';
18 | import { mapTo } from 'rxjs/operators';
19 | import { FLOW_END_INDICATOR } from '../constants/meta';
20 | /**
21 | * endTo
22 | * @param {Action} action
23 | * @returns {Observable}
24 | * @method endTo
25 | */
26 | export default function endTo(action) {
27 | var _a;
28 | return pipe(mapTo(__assign({}, action, (_a = {}, _a[FLOW_END_INDICATOR] = true, _a))));
29 | }
30 | //# sourceMappingURL=endTo.js.map
--------------------------------------------------------------------------------
/packages/core/test/action.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * action test
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-09-19 17:10:46
5 | */
6 | import { expect } from 'chai'
7 | import { Symbols } from '../src'
8 | import { actionSanitizer, getPayload } from '../src/utils/action'
9 | import { Action } from '../src/types/action'
10 |
11 | describe('action', () => {
12 | it('should payload be {} when payload is nil', () => {
13 | expect(getPayload({ type: 'undefined payload' })).to.deep.equal({})
14 | expect(getPayload({ type: 'null payload`', payload: null })).to.deep.equal(
15 | {}
16 | )
17 | })
18 |
19 | it('should support action alias', () => {
20 | const action: Action = {
21 | type: 'foo',
22 | [Symbols.ALIAS]: 'FOO',
23 | payload: {}
24 | }
25 |
26 | expect(actionSanitizer(action)).to.deep.include({
27 | type: 'FOO'
28 | })
29 |
30 | expect(actionSanitizer({ type: 'foo', payload: {} })).to.deep.include({
31 | type: 'foo'
32 | })
33 | })
34 | })
35 |
--------------------------------------------------------------------------------
/packages/core/index.d.ts:
--------------------------------------------------------------------------------
1 | import { Middleware, Reducer, Store } from 'redux'
2 | import Model, { Selectors } from './src/types/Model'
3 | import { Notification } from './src/types/Notification'
4 | import {
5 | init,
6 | getSelectors,
7 | getService,
8 | NOTIFICATION_LEVEL,
9 | notificate
10 | } from './src'
11 | import * as Symbols from './src/constants/symbols'
12 | import { ServiceConfig, ServiceFunc } from './src/operators/createService'
13 |
14 | export as namespace ReObservable;
15 |
16 | export {
17 | Model,
18 | Notification,
19 | init,
20 | getSelectors,
21 | getService,
22 | notificate,
23 | ServiceConfig,
24 | ServiceFunc,
25 | NOTIFICATION_LEVEL,
26 | Symbols
27 | }
28 |
29 | export interface LoadingState {
30 | flows: {
31 | [stateName: string]: boolean;
32 | }
33 | services: {
34 | [stateName: string]: boolean;
35 | }
36 | }
37 |
38 | export interface ErrorState {
39 | flows: {
40 | [stateName: string]: any;
41 | }
42 | services: {
43 | [stateName: string]: any;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/packages/core/es/operators/end.js:
--------------------------------------------------------------------------------
1 | var __assign = (this && this.__assign) || function () {
2 | __assign = Object.assign || function(t) {
3 | for (var s, i = 1, n = arguments.length; i < n; i++) {
4 | s = arguments[i];
5 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6 | t[p] = s[p];
7 | }
8 | return t;
9 | };
10 | return __assign.apply(this, arguments);
11 | };
12 | /**
13 | * end operator
14 | * @author yoyoyohamapi
15 | * @ignore created 2018-08-03 10:31:20
16 | */
17 | import { pipe } from 'rxjs';
18 | import { map } from 'rxjs/operators';
19 | import { FLOW_END_INDICATOR } from '../constants/meta';
20 | /**
21 | * end operator
22 | * @param {function(value: Action): EndAction} project
23 | * @returns {Observable}
24 | * @method end
25 | */
26 | export default function end(project) {
27 | return pipe(map(function (action) {
28 | var _a;
29 | return (__assign({}, project(action), (_a = {}, _a[FLOW_END_INDICATOR] = true, _a)));
30 | }));
31 | }
32 | //# sourceMappingURL=end.js.map
--------------------------------------------------------------------------------
/packages/core/lib/operators/endTo.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __assign = (this && this.__assign) || function () {
3 | __assign = Object.assign || function(t) {
4 | for (var s, i = 1, n = arguments.length; i < n; i++) {
5 | s = arguments[i];
6 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7 | t[p] = s[p];
8 | }
9 | return t;
10 | };
11 | return __assign.apply(this, arguments);
12 | };
13 | Object.defineProperty(exports, "__esModule", { value: true });
14 | /**
15 | * endTo operator
16 | * @author yoyoyohamapi
17 | * @ignore created 2018-08-03 10:31:20
18 | */
19 | var rxjs_1 = require("rxjs");
20 | var operators_1 = require("rxjs/operators");
21 | var meta_1 = require("../constants/meta");
22 | /**
23 | * endTo
24 | * @param {Action} action
25 | * @returns {Observable}
26 | * @method endTo
27 | */
28 | function endTo(action) {
29 | var _a;
30 | return rxjs_1.pipe(operators_1.mapTo(__assign({}, action, (_a = {}, _a[meta_1.FLOW_END_INDICATOR] = true, _a))));
31 | }
32 | exports.default = endTo;
33 | //# sourceMappingURL=endTo.js.map
--------------------------------------------------------------------------------
/packages/core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@reobservable/core",
3 | "version": "0.2.3",
4 | "description": "reobservable core package",
5 | "main": "./lib/index.js",
6 | "module": "./es/index.js",
7 | "scripts": {
8 | "lint": "tslint src/**/*.ts",
9 | "build:cjs": "cross-env tsc --rootDir ./src --outDir lib --module commonjs",
10 | "build:es": "cross-env tsc --rootDir ./src --outDir es --module es6",
11 | "clean": "rimraf lib es dist",
12 | "prepare": "npm run clean && npm run build:cjs && npm run build:es"
13 | },
14 | "typings": "index.d.ts",
15 | "author": " ",
16 | "license": "MIT",
17 | "dependencies": {
18 | "invariant": "^2.2.4",
19 | "lodash.clonedeep": "^4.5.0",
20 | "lodash.merge": "^4.6.1",
21 | "lodash.mergewith": "^4.6.1",
22 | "redux": "^4.0.0",
23 | "redux-devtools-extension": "^2.13.5",
24 | "redux-observable": "^1.0.0",
25 | "rxjs": "^6.2.2"
26 | },
27 | "sideEffects": false,
28 | "devDependencies": {
29 | "axios": "^0.18.0"
30 | },
31 | "publishConfig": {
32 | "access": "public"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/packages/core/lib/reducers/loading.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"loading.js","sourceRoot":"","sources":["../../src/reducers/loading.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;;;;;;;;;;;AAGH,wDAOiC;AAEjC,SAAwB,OAAO,CAAC,KAAiC,EAAE,MAAc;IAAjD,sBAAA,EAAA,UAAS,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAC;;IACvD,IAAA,kBAAI,CAAW;IACvB,IAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAA;IACpD,QAAQ,IAAI,EAAE;QACZ,KAAK,kCAAoB,CAAC,CAAC;YACzB,oBACK,KAAK,IACR,KAAK,eACA,KAAK,CAAC,KAAK,eACb,OAAO,CAAC,IAAI,IAAG,IAAI,UAEvB;SACF;QAED,KAAK,gCAAkB,CAAC,CAAC;YACvB,oBACK,KAAK,IACR,KAAK,eACA,KAAK,CAAC,KAAK,eACb,OAAO,CAAC,IAAI,IAAG,KAAK,UAExB;SACF;QAED,KAAK,0CAA4B,CAAC,CAAC;YACjC,oBACK,KAAK,IACR,QAAQ,eACH,KAAK,CAAC,QAAQ,eAChB,OAAO,CAAC,OAAO,IAAG,IAAI,UAE1B;SACF;QAED,KAAK,wCAA0B,CAAC,CAAC;YAC/B,oBACK,KAAK,IACR,QAAQ,eACH,KAAK,CAAC,QAAQ,eAChB,OAAO,CAAC,OAAO,IAAG,KAAK,UAE3B;SACF;QAED,sBAAsB;QACtB,KAAK,8BAAgB,CAAC,CAAC;YACrB,oBACK,KAAK,IACR,KAAK,eACA,KAAK,CAAC,KAAK,eACb,OAAO,CAAC,IAAI,IAAG,KAAK,UAExB;SACF;QAED,KAAK,sCAAwB,CAAC,CAAC;YAC7B,oBACK,KAAK,IACR,QAAQ,eACH,KAAK,CAAC,QAAQ,eAChB,OAAO,CAAC,OAAO,IAAG,KAAK,UAE3B;SACF;QAED;YACE,OAAO,KAAK,CAAA;KACf;AACH,CAAC;AApED,0BAoEC"}
--------------------------------------------------------------------------------
/packages/core/lib/operators/end.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __assign = (this && this.__assign) || function () {
3 | __assign = Object.assign || function(t) {
4 | for (var s, i = 1, n = arguments.length; i < n; i++) {
5 | s = arguments[i];
6 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7 | t[p] = s[p];
8 | }
9 | return t;
10 | };
11 | return __assign.apply(this, arguments);
12 | };
13 | Object.defineProperty(exports, "__esModule", { value: true });
14 | /**
15 | * end operator
16 | * @author yoyoyohamapi
17 | * @ignore created 2018-08-03 10:31:20
18 | */
19 | var rxjs_1 = require("rxjs");
20 | var operators_1 = require("rxjs/operators");
21 | var meta_1 = require("../constants/meta");
22 | /**
23 | * end operator
24 | * @param {function(value: Action): EndAction} project
25 | * @returns {Observable}
26 | * @method end
27 | */
28 | function end(project) {
29 | return rxjs_1.pipe(operators_1.map(function (action) {
30 | var _a;
31 | return (__assign({}, project(action), (_a = {}, _a[meta_1.FLOW_END_INDICATOR] = true, _a)));
32 | }));
33 | }
34 | exports.default = end;
35 | //# sourceMappingURL=end.js.map
--------------------------------------------------------------------------------
/packages/core/es/index.d.ts:
--------------------------------------------------------------------------------
1 | import { Store, Middleware, Reducer } from 'redux';
2 | import Model, { Selectors } from './types/Model';
3 | import { ServiceConfig, ServiceFunc } from './operators/createService';
4 | import { Notification } from './types/Notification';
5 | import { LEVEL as NOTIFICATION_LEVEL } from './constants/notification';
6 | import * as Symbols from './constants/symbols';
7 | interface Models {
8 | [modelName: string]: Model;
9 | }
10 | interface ReduxConfig {
11 | middleware?: Middleware | [Middleware];
12 | rootReducer?: (reducer: Reducer, reducers: {
13 | [key: string]: Reducer;
14 | }) => Reducer;
15 | }
16 | interface Config {
17 | models?: Models;
18 | redux?: ReduxConfig;
19 | notification?: Notification;
20 | services?: {
21 | [serviceName: string]: ServiceConfig;
22 | };
23 | }
24 | export declare type InitFunc = (config: Config) => Store;
25 | export declare const init: InitFunc;
26 | export declare function getSelectors(model: string): Selectors;
27 | export declare function getService(service: string): ServiceFunc;
28 | export declare function notificate(type: string, message: any): any;
29 | export { NOTIFICATION_LEVEL, Symbols };
30 |
--------------------------------------------------------------------------------
/packages/core/lib/index.d.ts:
--------------------------------------------------------------------------------
1 | import { Store, Middleware, Reducer } from 'redux';
2 | import Model, { Selectors } from './types/Model';
3 | import { ServiceConfig, ServiceFunc } from './operators/createService';
4 | import { Notification } from './types/Notification';
5 | import { LEVEL as NOTIFICATION_LEVEL } from './constants/notification';
6 | import * as Symbols from './constants/symbols';
7 | interface Models {
8 | [modelName: string]: Model;
9 | }
10 | interface ReduxConfig {
11 | middleware?: Middleware | [Middleware];
12 | rootReducer?: (reducer: Reducer, reducers: {
13 | [key: string]: Reducer;
14 | }) => Reducer;
15 | }
16 | interface Config {
17 | models?: Models;
18 | redux?: ReduxConfig;
19 | notification?: Notification;
20 | services?: {
21 | [serviceName: string]: ServiceConfig;
22 | };
23 | }
24 | export declare type InitFunc = (config: Config) => Store;
25 | export declare const init: InitFunc;
26 | export declare function getSelectors(model: string): Selectors;
27 | export declare function getService(service: string): ServiceFunc;
28 | export declare function notificate(type: string, message: any): any;
29 | export { NOTIFICATION_LEVEL, Symbols };
30 |
--------------------------------------------------------------------------------
/packages/core/es/reducers/loading.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"loading.js","sourceRoot":"","sources":["../../src/reducers/loading.ts"],"names":[],"mappings":"AAAA;;;;GAIG;;;;;;;;;;;;AAGH,OAAO,EACL,oBAAoB,EACpB,kBAAkB,EAClB,4BAA4B,EAC5B,0BAA0B,EAC1B,gBAAgB,EAChB,wBAAwB,EACzB,MAAM,0BAA0B,CAAA;AAEjC,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,KAAiC,EAAE,MAAc;IAAjD,sBAAA,EAAA,UAAS,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAC;;IACvD,IAAA,kBAAI,CAAW;IACvB,IAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAA;IACpD,QAAQ,IAAI,EAAE;QACZ,KAAK,oBAAoB,CAAC,CAAC;YACzB,oBACK,KAAK,IACR,KAAK,eACA,KAAK,CAAC,KAAK,eACb,OAAO,CAAC,IAAI,IAAG,IAAI,UAEvB;SACF;QAED,KAAK,kBAAkB,CAAC,CAAC;YACvB,oBACK,KAAK,IACR,KAAK,eACA,KAAK,CAAC,KAAK,eACb,OAAO,CAAC,IAAI,IAAG,KAAK,UAExB;SACF;QAED,KAAK,4BAA4B,CAAC,CAAC;YACjC,oBACK,KAAK,IACR,QAAQ,eACH,KAAK,CAAC,QAAQ,eAChB,OAAO,CAAC,OAAO,IAAG,IAAI,UAE1B;SACF;QAED,KAAK,0BAA0B,CAAC,CAAC;YAC/B,oBACK,KAAK,IACR,QAAQ,eACH,KAAK,CAAC,QAAQ,eAChB,OAAO,CAAC,OAAO,IAAG,KAAK,UAE3B;SACF;QAED,sBAAsB;QACtB,KAAK,gBAAgB,CAAC,CAAC;YACrB,oBACK,KAAK,IACR,KAAK,eACA,KAAK,CAAC,KAAK,eACb,OAAO,CAAC,IAAI,IAAG,KAAK,UAExB;SACF;QAED,KAAK,wBAAwB,CAAC,CAAC;YAC7B,oBACK,KAAK,IACR,QAAQ,eACH,KAAK,CAAC,QAAQ,eAChB,OAAO,CAAC,OAAO,IAAG,KAAK,UAE3B;SACF;QAED;YACE,OAAO,KAAK,CAAA;KACf;AACH,CAAC"}
--------------------------------------------------------------------------------
/examples/github/src/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Github Demo
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-08 15:50:46
5 | */
6 | import * as React from 'react'
7 | import { render } from 'react-dom'
8 | import { Provider } from 'react-redux'
9 | import { routerMiddleware, connectRouter } from 'connected-react-router'
10 | import { message } from 'antd'
11 |
12 | import { init } from '@reobservable/core'
13 | import App from '@modules/app'
14 | import userModel from '@models/user'
15 | import repoModel from '@models/repo'
16 | import { ApiService } from '@services/types'
17 |
18 | import createHistory from 'history/createHashHistory'
19 |
20 | import 'antd/dist/antd.css'
21 |
22 | const history = createHistory()
23 |
24 | const store = init({
25 | redux: {
26 | middleware: [routerMiddleware(history)],
27 | rootReducer(rootReducer) {
28 | return connectRouter(history)(rootReducer)
29 | }
30 | },
31 | models: {
32 | user: userModel,
33 | repo: repoModel
34 | },
35 | notification: message,
36 | services: {
37 | api: {
38 | templates: {
39 | success: resp => 'success',
40 | error: error => `error`
41 | }
42 | } as ApiService
43 | }
44 | })
45 |
46 | render(
47 |
48 |
49 | ,
50 | document.getElementById('app')
51 | )
52 |
--------------------------------------------------------------------------------
/docs/.vuepress/config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | title: 'Reobservable',
3 | description: 'Redux + rxjs + redux-obersvable best practice. Inspired by dva, rematch.',
4 | base: '/reobservable/',
5 | themeConfig: {
6 | repo: 'reobservable/reobservable',
7 | docsDir: 'docs',
8 | locales: {
9 | '/': {
10 | nav: [
11 | { text: 'Introduction', link: '/introduction/' },
12 | { text: 'Basics', link: '/basics/'},
13 | { test: 'Advanced', link: '/advanced/'},
14 | { text: 'API Reference', link: '/api/'}
15 | ],
16 | sidebar: {
17 | '/introduction/': [{
18 | title: 'Introduction',
19 | collapsable: false,
20 | children: [
21 | '',
22 | 'concepts',
23 | 'get-started',
24 | ]
25 | }],
26 | '/basics/': [{
27 | title: 'Basics',
28 | collapsable: false,
29 | children: [
30 | 'custom-service',
31 | 'change-and-patch',
32 | 'loading-state',
33 | 'error-state'
34 | ]
35 | }],
36 | '/advanced/': [{
37 | title: 'Advanced',
38 | collapsable: false,
39 | children: [
40 | ]
41 | }]
42 | }
43 | }
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/examples/github/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const HtmlWebpackPlugin = require("html-webpack-plugin");
3 |
4 | module.exports = {
5 | entry: path.resolve(__dirname, "./src/index.tsx"),
6 | output: {
7 | path: path.resolve(__dirname, "./dist"),
8 | filename: "bundle.js",
9 | publicPath: path.resolve(__dirname, "/")
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.tsx?$/,
15 | exclude: /^node_modules/,
16 | use: {
17 | loader: "ts-loader",
18 | options: {
19 | configFile: path.resolve(__dirname, "./tsconfig.json")
20 | }
21 | }
22 | },
23 | {
24 | test: /\.css?$/,
25 | loaders: ["style-loader", "css-loader"]
26 | }
27 | ]
28 | },
29 | resolve: {
30 | extensions: [".ts", ".tsx", ".js", ".json"],
31 | alias: {
32 | "@config": path.resolve(__dirname, "./src/config"),
33 | "@components": path.resolve(__dirname, "./src/components"),
34 | "@modules": path.resolve(__dirname, "./src/modules"),
35 | "@models": path.resolve(__dirname, "./src/models"),
36 | "@utils": path.resolve(__dirname, "./src/utils"),
37 | "@services": path.resolve(__dirname, "./src/services")
38 | }
39 | },
40 | plugins: [
41 | new HtmlWebpackPlugin({
42 | template: path.resolve(__dirname, "./src/index.html")
43 | })
44 | ]
45 | };
46 |
--------------------------------------------------------------------------------
/packages/core/es/types/model.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * model interface
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 12:47:25
5 | */
6 | import { ActionsObservable, StateObservable } from 'redux-observable';
7 | import { Action } from './action';
8 | import endTo from '../operators/endTo';
9 | import end from '../operators/end';
10 | import { Observable } from 'rxjs';
11 | import { ServiceFunc } from '../operators/createService';
12 | export interface Reducers {
13 | readonly [reducerName: string]: (state: S, payload: any) => S;
14 | }
15 | export interface Dependencies {
16 | end: typeof end;
17 | endTo: typeof endTo;
18 | services: {
19 | [serviceName: string]: ServiceFunc;
20 | };
21 | }
22 | export declare type Flow = (flow$: Observable, action$: ActionsObservable, state$: StateObservable, dependencies: Dependencies) => Observable;
23 | export interface Flows {
24 | readonly [flowName: string]: Flow;
25 | }
26 | export declare type Selector = (state: T, props?: P) => any;
27 | export interface Selectors {
28 | readonly [selectorName: string]: Selector;
29 | }
30 | export default interface Model {
31 | readonly name: string;
32 | readonly state?: S;
33 | readonly reducers?: Reducers;
34 | readonly flows?: Flows;
35 | readonly selectors?: Selectors;
36 | }
37 |
--------------------------------------------------------------------------------
/packages/core/lib/types/model.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * model interface
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 12:47:25
5 | */
6 | import { ActionsObservable, StateObservable } from 'redux-observable';
7 | import { Action } from './action';
8 | import endTo from '../operators/endTo';
9 | import end from '../operators/end';
10 | import { Observable } from 'rxjs';
11 | import { ServiceFunc } from '../operators/createService';
12 | export interface Reducers {
13 | readonly [reducerName: string]: (state: S, payload: any) => S;
14 | }
15 | export interface Dependencies {
16 | end: typeof end;
17 | endTo: typeof endTo;
18 | services: {
19 | [serviceName: string]: ServiceFunc;
20 | };
21 | }
22 | export declare type Flow = (flow$: Observable, action$: ActionsObservable, state$: StateObservable, dependencies: Dependencies) => Observable;
23 | export interface Flows {
24 | readonly [flowName: string]: Flow;
25 | }
26 | export declare type Selector = (state: T, props?: P) => any;
27 | export interface Selectors {
28 | readonly [selectorName: string]: Selector;
29 | }
30 | export default interface Model {
31 | readonly name: string;
32 | readonly state?: S;
33 | readonly reducers?: Reducers;
34 | readonly flows?: Flows;
35 | readonly selectors?: Selectors;
36 | }
37 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "curly": true,
4 | "eofline": false,
5 | "align": [
6 | true,
7 | "parameters"
8 | ],
9 | "class-name": true,
10 | "indent": [
11 | true,
12 | "spaces"
13 | ],
14 | "max-line-length": [
15 | true,
16 | 150
17 | ],
18 | "no-consecutive-blank-lines": true,
19 | "no-trailing-whitespace": true,
20 | "no-duplicate-variable": true,
21 | "no-duplicate-imports": true,
22 | "no-var-keyword": true,
23 | "no-empty": true,
24 | "no-var-requires": true,
25 | "no-require-imports": true,
26 | "one-line": [
27 | true,
28 | "check-else",
29 | "check-whitespace",
30 | "check-open-brace"
31 | ],
32 | "quotemark": [
33 | true,
34 | "single",
35 | "avoid-escape"
36 | ],
37 | "semicolon": [
38 | true,
39 | "never"
40 | ],
41 | "typedef-whitespace": [
42 | true,
43 | {
44 | "call-signature": "nospace",
45 | "index-signature": "nospace",
46 | "parameter": "nospace",
47 | "property-declaration": "nospace",
48 | "variable-declaration": "nospace"
49 | }
50 | ],
51 | "import-spacing": true,
52 | "whitespace": [
53 | true,
54 | "check-branch",
55 | "check-decl",
56 | "check-operator",
57 | "check-separator",
58 | "check-type"
59 | ]
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/packages/core/src/types/Model.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * model interface
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 12:47:25
5 | */
6 | import { ActionsObservable, StateObservable } from 'redux-observable'
7 | import { Action } from './action'
8 | import endTo from '../operators/endTo'
9 | import end from '../operators/end'
10 | import { Observable } from 'rxjs'
11 | import { ServiceFunc } from '../operators/createService'
12 |
13 | export interface Reducers {
14 | readonly [reducerName: string]: (state: S, payload: any) => S
15 | }
16 |
17 | export interface Dependencies {
18 | end: typeof end
19 | endTo: typeof endTo
20 | services: {
21 | [serviceName: string]: ServiceFunc;
22 | }
23 | }
24 |
25 | export type Flow = (
26 | flow$: Observable,
27 | action$: ActionsObservable,
28 | state$: StateObservable,
29 | dependencies: Dependencies
30 | ) => Observable
31 |
32 | export interface Flows {
33 | readonly [flowName: string]: Flow
34 | }
35 |
36 | export type Selector = (state: T, props?: P) => any
37 |
38 | export interface Selectors {
39 | readonly [selectorName: string]: Selector
40 | }
41 |
42 | export default interface Model {
43 | readonly name: string
44 | readonly state?: S
45 | readonly reducers?: Reducers
46 | readonly flows?: Flows
47 | readonly selectors?: Selectors
48 | }
49 |
--------------------------------------------------------------------------------
/packages/core/src/reducers/error.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * error reducer
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 12:43:11
5 | */
6 | import { Action } from '../types/action'
7 | import {
8 | LOADING_START_ACTION,
9 | ERROR_SET_ACTION,
10 | SERVICE_ERROR_SET_ACTION,
11 | SERVICE_LOADING_START_ACTION
12 | } from '../constants/actionTypes'
13 |
14 | export default function error(
15 | state = { flows: {}, services: {} },
16 | action: Action
17 | ): Object {
18 | const { type } = action
19 | const payload = action.payload ? action.payload : {}
20 | switch (type) {
21 | // flow 开始,重置错误
22 | case LOADING_START_ACTION: {
23 | return {
24 | ...state,
25 | flows: {
26 | ...state.flows,
27 | [payload.flow]: null
28 | }
29 | }
30 | }
31 | case SERVICE_LOADING_START_ACTION: {
32 | return {
33 | ...state,
34 | services: {
35 | ...state.services,
36 | [payload.service]: null
37 | }
38 | }
39 | }
40 | case ERROR_SET_ACTION: {
41 | return {
42 | ...state,
43 | flows: {
44 | ...state.flows,
45 | [payload.flow]: payload.error
46 | }
47 | }
48 | }
49 | case SERVICE_ERROR_SET_ACTION: {
50 | return {
51 | ...state,
52 | services: {
53 | ...state.services,
54 | [payload.service]: payload.error
55 | }
56 | }
57 | }
58 | default:
59 | return state
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/packages/core/es/operators/createService.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * fromService creator
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-13 14:26:55
5 | */
6 | import { Observable } from 'rxjs';
7 | import { Store } from 'redux';
8 | import { Notification, NotificationLevel } from '../types/Notification';
9 | interface ServiceTemplates {
10 | success: (resp: T) => string;
11 | error: (error: E) => string;
12 | }
13 | export interface ServiceConfig {
14 | templates?: ServiceTemplates;
15 | isSuccess?: (resp: T) => boolean;
16 | errorSelector?: (error: any) => any;
17 | }
18 | interface ServiceOptions {
19 | /** 等级 */
20 | level: NotificationLevel;
21 | /** 消息模板 */
22 | templates?: ServiceTemplates;
23 | /** 重试次数 */
24 | retry?: number;
25 | /** 重试延迟 */
26 | retryDelay?: number;
27 | /** 加载延迟 */
28 | loadingDelay?: number;
29 | }
30 | interface Result {
31 | resp?: T | undefined;
32 | error?: E | undefined;
33 | success?: boolean | undefined;
34 | }
35 | export declare type ServiceFunc = (serviceName: string, service: Promise | Observable, options?: ServiceOptions) => [Observable>, Observable>];
36 | export declare function partition(source$: Observable, predicate: (value: T, index: number) => boolean): Observable[];
37 | /**
38 | * 创建 fromService creator
39 | * @param {Notification} notification
40 | */
41 | export default function createFromService(notification: Notification, serviceConfig: ServiceConfig, store: Store): ServiceFunc;
42 | export {};
43 |
--------------------------------------------------------------------------------
/packages/core/lib/operators/createService.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * fromService creator
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-13 14:26:55
5 | */
6 | import { Observable } from 'rxjs';
7 | import { Store } from 'redux';
8 | import { Notification, NotificationLevel } from '../types/Notification';
9 | interface ServiceTemplates {
10 | success: (resp: T) => string;
11 | error: (error: E) => string;
12 | }
13 | export interface ServiceConfig {
14 | templates?: ServiceTemplates;
15 | isSuccess?: (resp: T) => boolean;
16 | errorSelector?: (error: any) => any;
17 | }
18 | interface ServiceOptions {
19 | /** 等级 */
20 | level: NotificationLevel;
21 | /** 消息模板 */
22 | templates?: ServiceTemplates;
23 | /** 重试次数 */
24 | retry?: number;
25 | /** 重试延迟 */
26 | retryDelay?: number;
27 | /** 加载延迟 */
28 | loadingDelay?: number;
29 | }
30 | interface Result {
31 | resp?: T | undefined;
32 | error?: E | undefined;
33 | success?: boolean | undefined;
34 | }
35 | export declare type ServiceFunc = (serviceName: string, service: Promise | Observable, options?: ServiceOptions) => [Observable>, Observable>];
36 | export declare function partition(source$: Observable, predicate: (value: T, index: number) => boolean): Observable[];
37 | /**
38 | * 创建 fromService creator
39 | * @param {Notification} notification
40 | */
41 | export default function createFromService(notification: Notification, serviceConfig: ServiceConfig, store: Store): ServiceFunc;
42 | export {};
43 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Reobservable
2 |
3 | Redux + rxjs + redux-obersvable best practice. Inspired by dva, rematch.
4 |
5 | https://reobservable.github.io/reobservable/
6 |
7 | ---------------
8 |
9 | [](https://coveralls.io/github/reobservable/reobservable?branch=master)
10 | [](https://travis-ci.com/reobservable/reobservable)
11 |
12 | ## Motivation
13 |
14 | I'm a big fan of [dvajs](https://github.com/dvas/dva), it helps me get rid out of the boilerplate of redux. But I think redux-saga in dva sometimes can be a little bit verbose since I prefer functional reactive programming(frp) than imperative programming.
15 |
16 | I know that reinvent the weel could be a terrible practice, but in dva community, there is no plan to support frp. At last, I decided to create **reobservable**, which, in a world, is a state manager mixined dva architecture and frp features(RxJS + redux-observable. In reobservable, dva architecture wiped out the boilerplate code of redux, RxJS played an elite role in managing asynchronous task.
17 |
18 | If you:
19 |
20 | - love redux concept, love the predicatable, centralized, debuggable features
21 | - stuking with the boilerplate code of redux
22 | - want to use RxJS to managing asynchronous task
23 |
24 | try reobservable!
25 |
26 | ## Installation
27 |
28 | ```
29 | npm install @reobservable/core --save
30 | ```
31 |
32 | ## Examples
33 |
34 | See [reobservble examples](https://github.com/reobservable/reobservable/tree/master/examples)
35 |
--------------------------------------------------------------------------------
/examples/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@reobservable/examples",
3 | "version": "0.2.3",
4 | "description": "",
5 | "main": "index.js",
6 | "directories": {
7 | "example": "examples"
8 | },
9 | "scripts": {
10 | "start:github": "cross-env webpack-dev-server --open --mode development --config ./github/webpack.config.js",
11 | "start:todo": "cross-env webpack --config ./todo/webpack.config.js --mode=development"
12 | },
13 | "author": " ",
14 | "license": "MIT",
15 | "dependencies": {
16 | "@reobservable/core": "^0.2.3",
17 | "antd": "^3.7.0",
18 | "axios": "^0.18.0",
19 | "connected-react-router": "^4.3.0",
20 | "history": "^4.7.2",
21 | "invariant": "^2.2.4",
22 | "lodash.merge": "^4.6.1",
23 | "react": "^16.4.1",
24 | "react-dom": "^16.4.1",
25 | "react-redux": "^5.0.7",
26 | "react-router": "^4.3.1",
27 | "react-router-dom": "^4.3.1",
28 | "redux": "^4.0.0",
29 | "redux-observable": "^1.0.0",
30 | "reselect": "^3.0.1",
31 | "rxjs": "^6.2.2"
32 | },
33 | "devDependencies": {
34 | "@types/node": "^10.5.7",
35 | "@types/react": "^16.4.8",
36 | "@types/react-dom": "^16.0.7",
37 | "@types/react-redux": "^6.0.6",
38 | "cross-env": "^5.2.0",
39 | "css-loader": "^1.0.0",
40 | "html-webpack-plugin": "^3.2.0",
41 | "style-loader": "^0.21.0",
42 | "ts-loader": "^4.4.2",
43 | "tsconfig-paths-webpack-plugin": "^3.2.0",
44 | "typescript": "^3.0.1",
45 | "webpack": "^4.16.1",
46 | "webpack-cli": "^3.0.8",
47 | "webpack-dev-server": "^3.1.4"
48 | },
49 | "publishConfig": {
50 | "access": "public"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/docs/basics/error-state.md:
--------------------------------------------------------------------------------
1 | # ErrorState State
2 |
3 | Sometimes, you may want know if a service or flow failed. If so, you may display a error message on your web page:
4 |
5 | 
6 |
7 | In reobservable, there is a built-in sub-state called `error` in the root state:
8 |
9 | ```ts
10 | {
11 | error: {
12 | services: {
13 | fetchUser: 'bad request'
14 | },
15 | flows: {
16 | 'user/fetch': 'cannot read id of undefined'
17 | }
18 | }
19 | }
20 | ```
21 |
22 | As you can see, there are two type of error state in reobservable.
23 |
24 | ## Service Error
25 |
26 | Service error indicates if a service is failed. You call a service inside a flow like:
27 |
28 | ```ts
29 | const model = {
30 | name: 'user',
31 | // ....
32 | flows: {
33 | fetch(flow$, action$, payload$, dependencies) {
34 | const { api } = dependencies.services
35 |
36 | return flow$.pipe(
37 | switchMap(action => {
38 | const [success$, error$] = api(
39 | // service name
40 | 'fetchUser',
41 | service.fetchUser(params)
42 | )
43 | // ...
44 | })
45 | )
46 | }
47 | }
48 | }
49 | ```
50 |
51 | Because we named the service `fetchUser`, then we can inspect service error by:
52 |
53 | ```ts
54 | const mapStateToProps = (state: IState) => {
55 | const userFetchError = state.error.services['fetchUser']
56 | }
57 | ```
58 |
59 | ## Flow Error
60 |
61 | Flow error indicates if a flow is failed. Like service error, you can access flow error by:
62 |
63 | ```ts
64 | const mapStateToProps = (state: IState) => {
65 | const nameChangingError = state.loading.flows['user/changeName']
66 | }
67 | ```
--------------------------------------------------------------------------------
/packages/core/es/reducers/error.js:
--------------------------------------------------------------------------------
1 | var __assign = (this && this.__assign) || function () {
2 | __assign = Object.assign || function(t) {
3 | for (var s, i = 1, n = arguments.length; i < n; i++) {
4 | s = arguments[i];
5 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6 | t[p] = s[p];
7 | }
8 | return t;
9 | };
10 | return __assign.apply(this, arguments);
11 | };
12 | import { LOADING_START_ACTION, ERROR_SET_ACTION, SERVICE_ERROR_SET_ACTION, SERVICE_LOADING_START_ACTION } from '../constants/actionTypes';
13 | export default function error(state, action) {
14 | if (state === void 0) { state = { flows: {}, services: {} }; }
15 | var _a, _b, _c, _d;
16 | var type = action.type;
17 | var payload = action.payload ? action.payload : {};
18 | switch (type) {
19 | // flow 开始,重置错误
20 | case LOADING_START_ACTION: {
21 | return __assign({}, state, { flows: __assign({}, state.flows, (_a = {}, _a[payload.flow] = null, _a)) });
22 | }
23 | case SERVICE_LOADING_START_ACTION: {
24 | return __assign({}, state, { services: __assign({}, state.services, (_b = {}, _b[payload.service] = null, _b)) });
25 | }
26 | case ERROR_SET_ACTION: {
27 | return __assign({}, state, { flows: __assign({}, state.flows, (_c = {}, _c[payload.flow] = payload.error, _c)) });
28 | }
29 | case SERVICE_ERROR_SET_ACTION: {
30 | return __assign({}, state, { services: __assign({}, state.services, (_d = {}, _d[payload.service] = payload.error, _d)) });
31 | }
32 | default:
33 | return state;
34 | }
35 | }
36 | //# sourceMappingURL=error.js.map
--------------------------------------------------------------------------------
/packages/core/lib/reducers/error.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __assign = (this && this.__assign) || function () {
3 | __assign = Object.assign || function(t) {
4 | for (var s, i = 1, n = arguments.length; i < n; i++) {
5 | s = arguments[i];
6 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7 | t[p] = s[p];
8 | }
9 | return t;
10 | };
11 | return __assign.apply(this, arguments);
12 | };
13 | Object.defineProperty(exports, "__esModule", { value: true });
14 | var actionTypes_1 = require("../constants/actionTypes");
15 | function error(state, action) {
16 | if (state === void 0) { state = { flows: {}, services: {} }; }
17 | var _a, _b, _c, _d;
18 | var type = action.type;
19 | var payload = action.payload ? action.payload : {};
20 | switch (type) {
21 | // flow 开始,重置错误
22 | case actionTypes_1.LOADING_START_ACTION: {
23 | return __assign({}, state, { flows: __assign({}, state.flows, (_a = {}, _a[payload.flow] = null, _a)) });
24 | }
25 | case actionTypes_1.SERVICE_LOADING_START_ACTION: {
26 | return __assign({}, state, { services: __assign({}, state.services, (_b = {}, _b[payload.service] = null, _b)) });
27 | }
28 | case actionTypes_1.ERROR_SET_ACTION: {
29 | return __assign({}, state, { flows: __assign({}, state.flows, (_c = {}, _c[payload.flow] = payload.error, _c)) });
30 | }
31 | case actionTypes_1.SERVICE_ERROR_SET_ACTION: {
32 | return __assign({}, state, { services: __assign({}, state.services, (_d = {}, _d[payload.service] = payload.error, _d)) });
33 | }
34 | default:
35 | return state;
36 | }
37 | }
38 | exports.default = error;
39 | //# sourceMappingURL=error.js.map
--------------------------------------------------------------------------------
/packages/core/test/operator.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * operator test
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-14 19:12:37
5 | */
6 | import { expect } from 'chai'
7 | import { TestScheduler } from 'rxjs/testing'
8 | import { FLOW_END_INDICATOR } from '../src/constants/meta'
9 | import end from '../src/operators/end'
10 | import endTo from '../src/operators/endTo'
11 |
12 | describe('operator', () => {
13 | let scheduler
14 |
15 | beforeEach(() => {
16 | scheduler = new TestScheduler((actual, expected) => {
17 | expect(actual).deep.equal(expected)
18 | })
19 | })
20 |
21 | it('#end', () => {
22 | scheduler.run(({ cold, expectObservable }) => {
23 | const current$ = cold('-a', {
24 | a: 1
25 | })
26 | const endAction$ = current$.pipe(
27 | end(current => ({
28 | type: 'SET_CURRENT_USER',
29 | payload: {
30 | current
31 | }
32 | }))
33 | )
34 |
35 | expectObservable(endAction$).toBe('-e', {
36 | e: {
37 | type: 'SET_CURRENT_USER',
38 | payload: {
39 | current: 1
40 | },
41 | [FLOW_END_INDICATOR]: true
42 | }
43 | })
44 | })
45 | })
46 |
47 | it('#endTo', () => {
48 | scheduler.run(({ cold, expectObservable }) => {
49 | const clear$ = cold('-a')
50 | const endAction$ = clear$.pipe(
51 | endTo({
52 | type: 'SET_CURRENT_USER',
53 | payload: {
54 | current: null
55 | }
56 | })
57 | )
58 |
59 | expectObservable(endAction$).toBe('-e', {
60 | e: {
61 | type: 'SET_CURRENT_USER',
62 | payload: {
63 | current: null
64 | },
65 | [FLOW_END_INDICATOR]: true
66 | }
67 | })
68 | })
69 | })
70 | })
71 |
--------------------------------------------------------------------------------
/packages/core/test/init.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * init test
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-10-08 10:23:13
5 | */
6 | import { expect } from 'chai'
7 | import { init, notificate } from '../src'
8 | import Model from '../src/types/Model'
9 |
10 | describe('#init', () => {
11 | it('should throw error when model name conflicted', () => {
12 | expect(function() {
13 | init({
14 | models: {
15 | user: { name: 'user', state: {} },
16 | duplicatedUser: { name: 'user', state: {} }
17 | }
18 | })
19 | }).to.throw('model user has been defined')
20 | })
21 |
22 | it('should support empty init', () => {
23 | expect(function() {
24 | init({})
25 | }).to.not.throw()
26 | })
27 |
28 | it('should support custom notification', () => {
29 | const notification = {
30 | info(msg) {
31 | return msg
32 | },
33 | success(msg) {
34 | return msg
35 | }
36 | }
37 |
38 | const user: Model<{ name: string }> = {
39 | name: 'user'
40 | }
41 |
42 | init({
43 | models: { user },
44 | notification
45 | })
46 |
47 | expect(notificate('success', 'ok')).to.equal('ok')
48 | expect(notificate('info', 'none')).to.equal('none')
49 | expect(notificate('error', 'wrong')).to.be.undefined
50 | expect(notificate('none', 'none')).to.equal('none')
51 | })
52 |
53 | it('should not notificate when message is nil', () => {
54 | const notification = {
55 | info(msg) {
56 | return msg
57 | },
58 | success(msg) {
59 | return msg
60 | }
61 | }
62 |
63 | const user: Model<{ name: string }> = {
64 | name: 'user'
65 | }
66 |
67 | init({
68 | models: { user },
69 | notification
70 | })
71 |
72 | expect(notificate('info', null)).to.be.undefined
73 | expect(notificate('info', undefined)).to.be.undefined
74 | })
75 | })
76 |
--------------------------------------------------------------------------------
/packages/core/src/reducers/loading.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * loading reducer
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 12:36:49
5 | */
6 |
7 | import { Action } from '../types/action'
8 | import {
9 | LOADING_START_ACTION,
10 | LOADING_END_ACTION,
11 | SERVICE_LOADING_START_ACTION,
12 | SERVICE_LOADING_END_ACTION,
13 | ERROR_SET_ACTION,
14 | SERVICE_ERROR_SET_ACTION
15 | } from '../constants/actionTypes'
16 |
17 | export default function loading(
18 | state = { services: {}, flows: {} },
19 | action: Action
20 | ): Object {
21 | const { type } = action
22 | const payload = action.payload ? action.payload : {}
23 | switch (type) {
24 | case LOADING_START_ACTION: {
25 | return {
26 | ...state,
27 | flows: {
28 | ...state.flows,
29 | [payload.flow]: true
30 | }
31 | }
32 | }
33 |
34 | case LOADING_END_ACTION: {
35 | return {
36 | ...state,
37 | flows: {
38 | ...state.flows,
39 | [payload.flow]: false
40 | }
41 | }
42 | }
43 |
44 | case SERVICE_LOADING_START_ACTION: {
45 | return {
46 | ...state,
47 | services: {
48 | ...state.services,
49 | [payload.service]: true
50 | }
51 | }
52 | }
53 |
54 | case SERVICE_LOADING_END_ACTION: {
55 | return {
56 | ...state,
57 | services: {
58 | ...state.services,
59 | [payload.service]: false
60 | }
61 | }
62 | }
63 |
64 | // flow 出错时,停止 loading
65 | case ERROR_SET_ACTION: {
66 | return {
67 | ...state,
68 | flows: {
69 | ...state.flows,
70 | [payload.flow]: false
71 | }
72 | }
73 | }
74 |
75 | case SERVICE_ERROR_SET_ACTION: {
76 | return {
77 | ...state,
78 | services: {
79 | ...state.services,
80 | [payload.service]: false
81 | }
82 | }
83 | }
84 |
85 | default:
86 | return state
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/examples/github/src/modules/repo/Table.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * repo list
3 | * @author yoyoyohamapi
4 | * @ignore 2018-08-08 14:51:42
5 | */
6 | import * as React from 'react'
7 | import { connect, DispatchProp } from 'react-redux'
8 | import { Table as AntTable } from 'antd'
9 | import { PaginationProps } from 'antd/lib/pagination'
10 | import { TableProps, ColumnProps } from 'antd/lib/table'
11 | import { LoadingState, getSelectors, Symbols } from '@reobservable/core'
12 | import { Repo, RepoState } from '@models/repo'
13 |
14 | interface StateMapper {
15 | repo: RepoState
16 | loading: LoadingState
17 | }
18 |
19 | const columns: ColumnProps[] = [
20 | {
21 | key: '_rank',
22 | title: '排名',
23 | width: 150,
24 | render: (text: string, record, index: number) => index + 1
25 | },
26 | {
27 | key: 'name',
28 | title: 'Repo',
29 | dataIndex: 'name',
30 | render: (text: string, record) => {
31 | return (
32 |
33 | {text}
34 |
35 | )
36 | }
37 | }
38 | ]
39 |
40 | const Table: React.SFC & DispatchProp> = props => {
41 | const { dataSource, pagination, loading, dispatch } = props
42 | const handleChange = (pagination: PaginationProps) => {
43 | dispatch({
44 | type: 'repo/patch',
45 | [Symbols.ALIAS]: 'repo/changePagination',
46 | payload: {
47 | pagination: {
48 | page: pagination.current,
49 | pageSize: pagination.pageSize
50 | }
51 | }
52 | })
53 | }
54 |
55 | return (
56 |
66 | )
67 | }
68 |
69 | const mapStateToProps = (state: StateMapper): TableProps => {
70 | const { repo, loading } = state
71 | return {
72 | dataSource: repo.list,
73 | pagination: getSelectors('repo').pagination(state),
74 | loading: repo.isSilentLoading ? false : loading.services['repo/fetch']
75 | }
76 | }
77 |
78 | export default connect(mapStateToProps)(Table)
79 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reobservable",
3 | "version": "0.1.1",
4 | "description": "Redux + rxjs + redux-obersvable best practice",
5 | "main": "index.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/reobservable/reobservable"
9 | },
10 | "directories": {
11 | "example": "examples"
12 | },
13 | "scripts": {
14 | "lint": "lerna run lint",
15 | "bootstrap": "lerna bootstrap --hoist",
16 | "publish": "npm run test && lerna publish",
17 | "test": "nyc mocha --opts mocha.opts",
18 | "coverage": "nyc report --reporter=text-lcov | coveralls",
19 | "docs:dev": "vuepress dev docs",
20 | "docs:build": "vuepress build docs"
21 | },
22 | "author": " ",
23 | "license": "ISC",
24 | "devDependencies": {
25 | "@types/chai": "^4.1.5",
26 | "@types/isomorphic-fetch": "^0.0.34",
27 | "@types/mocha": "^5.2.5",
28 | "@types/nock": "^9.3.0",
29 | "@types/sinon": "^5.0.1",
30 | "chai": "^4.1.2",
31 | "coveralls": "^3.0.2",
32 | "cross-env": "^5.2.0",
33 | "css-loader": "^2.1.0",
34 | "eslint": "^5.0.1",
35 | "eslint-config-standard": "^11.0.0",
36 | "eslint-plugin-import": "^2.13.0",
37 | "eslint-plugin-node": "^6.0.1",
38 | "eslint-plugin-promise": "^3.8.0",
39 | "eslint-plugin-standard": "^3.1.0",
40 | "fetch-mock": "^6.5.2",
41 | "isomorphic-fetch": "^2.2.1",
42 | "lerna": "^2.11.0",
43 | "mocha": "^5.2.0",
44 | "nock": "^9.6.1",
45 | "nyc": "^13.0.1",
46 | "pre-commit": "^1.2.2",
47 | "rimraf": "^2.6.2",
48 | "sinon": "^6.1.5",
49 | "source-map-support": "^0.5.9",
50 | "ts-node": "^7.0.1",
51 | "tslint": "^5.11.0",
52 | "typescript": "^3.0.1",
53 | "vuepress": "^0.14.4"
54 | },
55 | "pre-commit": [
56 | "lint"
57 | ],
58 | "nyc": {
59 | "extension": [
60 | ".ts"
61 | ],
62 | "include": [
63 | "packages/**/src/**/*.ts"
64 | ],
65 | "exclude": [
66 | "**/*.d.ts"
67 | ],
68 | "reporter": [
69 | "text-summary",
70 | "html"
71 | ],
72 | "sourceMap": true,
73 | "instrument": true
74 | },
75 | "publishConfig": {
76 | "access": "public"
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/examples/github/src/modules/user/Table.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * user list
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-07-17 21:18:38
5 | */
6 | import * as React from 'react'
7 | import { connect, DispatchProp } from 'react-redux'
8 | import { Table as AntTable } from 'antd'
9 | import { PaginationProps } from 'antd/lib/pagination'
10 | import { TableProps, ColumnProps } from 'antd/lib/table'
11 | import { LoadingState, getSelectors, Symbols } from '@reobservable/core'
12 | import { User, UserState } from '@models/user'
13 |
14 | interface StateMapper {
15 | user: UserState
16 | loading: LoadingState
17 | }
18 |
19 | const columns: ColumnProps[] = [
20 | {
21 | key: '_rank',
22 | title: '排名',
23 | width: 150,
24 | render: (text: string, record: User, index: number) => index + 1
25 | },
26 | {
27 | key: 'username',
28 | dataIndex: 'login',
29 | title: '用户',
30 | render: (text: string, record: User) => {
31 | return (
32 |
33 | {text}
34 |
35 | )
36 | }
37 | }
38 | ]
39 |
40 | const Table: React.SFC & DispatchProp> = props => {
41 | const { dataSource, pagination, loading, dispatch } = props
42 |
43 | const handleChange = (pagination: PaginationProps) => {
44 | dispatch({
45 | type: 'user/patch',
46 | [Symbols.ALIAS]: 'user/changePagination',
47 | payload: {
48 | pagination: {
49 | page: pagination.current,
50 | pageSize: pagination.pageSize
51 | }
52 | }
53 | })
54 | }
55 |
56 | return (
57 |
67 | )
68 | }
69 |
70 | const mapStateToProps = (state: StateMapper): TableProps => {
71 | const { user, loading } = state
72 | return {
73 | dataSource: user.list,
74 | pagination: getSelectors('user').pagination(state),
75 | loading: user.isSilentLoading ? false : loading.services['user/fetch']
76 | }
77 | }
78 |
79 | export default connect(mapStateToProps)(Table)
80 |
--------------------------------------------------------------------------------
/examples/github/src/modules/app/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Github demo
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-07-17 20:24:38
5 | */
6 | import * as React from 'react'
7 | import { connect } from 'react-redux'
8 | import { Route, Redirect } from 'react-router'
9 | import { Link } from 'react-router-dom'
10 | import { ConnectedRouter, RouterState } from 'connected-react-router'
11 | import { Layout, Menu } from 'antd'
12 | import createHistory from 'history/createHashHistory'
13 | import User from '@modules/user'
14 | import Repo from '@modules/repo'
15 |
16 | interface Props {
17 | path: string
18 | }
19 |
20 | const history = createHistory()
21 |
22 | const { Header, Content, Footer } = Layout
23 |
24 | class App extends React.Component {
25 | render() {
26 | const { path } = this.props
27 | return (
28 |
29 |
30 |
31 |
44 |
45 |
46 |
47 |
48 | } />
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | )
58 | }
59 | }
60 |
61 | const mapStateToProps = ({ router }: { router: RouterState }): Props => ({
62 | path: router.location.pathname
63 | })
64 |
65 | export default connect(mapStateToProps)(App)
66 |
--------------------------------------------------------------------------------
/docs/basics/loading-state.md:
--------------------------------------------------------------------------------
1 | # Loading State
2 |
3 | Sometimes, you may want know if a service or flow is running. If so, you may display a loading spinner on your web page:
4 |
5 | 
6 |
7 | In reobservable, there is a built-in sub-state called `loading` in the root state:
8 |
9 | ```ts
10 | {
11 | loading: {
12 | services: {
13 | fetchUser: true
14 | },
15 | flows: {
16 | 'user/fetch': true
17 | }
18 | }
19 | }
20 | ```
21 |
22 | As you can see, there are two type of loading state in reobservable.
23 |
24 | ## Service Loading
25 |
26 | Service loading indicates if a service is running. You call a service inside a flow like:
27 |
28 | ```ts
29 | const model = {
30 | name: 'user',
31 | // ....
32 | flows: {
33 | fetch(flow$, action$, payload$, dependencies) {
34 | const { api } = dependencies.services
35 |
36 | return flow$.pipe(
37 | switchMap(action => {
38 | const [success$, error$] = api(
39 | // service name
40 | 'fetchUser',
41 | service.fetchUser(params)
42 | )
43 | // ...
44 | })
45 | )
46 | }
47 | }
48 | }
49 | ```
50 |
51 | Because we named the service `fetchUser`, then we can access it by:
52 |
53 | ```ts
54 | const mapStateToProps = (state: IState) => {
55 | const isUserFetching = state.loading.services['fetchUser']
56 | }
57 | ```
58 |
59 | ## Flow Loading
60 |
61 | Flow loading indicates if a flow is running. But in order to tell reobservable if a flow should be considered complete, you should use `end` or `endTo` operator to emit an end action:
62 |
63 | ```ts
64 | import { end } from '@reobservable/core'
65 |
66 | const model = {
67 | name: 'user',
68 | // ....
69 | flows: {
70 | changeName(flow$, action$, payload$, dependencies) {
71 | return flow$.pipe(
72 | end(action => ({
73 | type: 'user/changeName',
74 | payload: {
75 | name: action.payload.name
76 | }
77 | }))
78 | )
79 | }
80 | }
81 | }
82 | ```
83 |
84 | Then, you can access it by:
85 |
86 | ```ts
87 | const mapStateToProps = (state: IState) => {
88 | const isNameChanging = state.loading.flows['user/changeName']
89 | }
90 | ```
--------------------------------------------------------------------------------
/packages/core/es/reducers/loading.js:
--------------------------------------------------------------------------------
1 | /**
2 | * loading reducer
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 12:36:49
5 | */
6 | var __assign = (this && this.__assign) || function () {
7 | __assign = Object.assign || function(t) {
8 | for (var s, i = 1, n = arguments.length; i < n; i++) {
9 | s = arguments[i];
10 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
11 | t[p] = s[p];
12 | }
13 | return t;
14 | };
15 | return __assign.apply(this, arguments);
16 | };
17 | import { LOADING_START_ACTION, LOADING_END_ACTION, SERVICE_LOADING_START_ACTION, SERVICE_LOADING_END_ACTION, ERROR_SET_ACTION, SERVICE_ERROR_SET_ACTION } from '../constants/actionTypes';
18 | export default function loading(state, action) {
19 | if (state === void 0) { state = { services: {}, flows: {} }; }
20 | var _a, _b, _c, _d, _e, _f;
21 | var type = action.type;
22 | var payload = action.payload ? action.payload : {};
23 | switch (type) {
24 | case LOADING_START_ACTION: {
25 | return __assign({}, state, { flows: __assign({}, state.flows, (_a = {}, _a[payload.flow] = true, _a)) });
26 | }
27 | case LOADING_END_ACTION: {
28 | return __assign({}, state, { flows: __assign({}, state.flows, (_b = {}, _b[payload.flow] = false, _b)) });
29 | }
30 | case SERVICE_LOADING_START_ACTION: {
31 | return __assign({}, state, { services: __assign({}, state.services, (_c = {}, _c[payload.service] = true, _c)) });
32 | }
33 | case SERVICE_LOADING_END_ACTION: {
34 | return __assign({}, state, { services: __assign({}, state.services, (_d = {}, _d[payload.service] = false, _d)) });
35 | }
36 | // flow 出错时,停止 loading
37 | case ERROR_SET_ACTION: {
38 | return __assign({}, state, { flows: __assign({}, state.flows, (_e = {}, _e[payload.flow] = false, _e)) });
39 | }
40 | case SERVICE_ERROR_SET_ACTION: {
41 | return __assign({}, state, { services: __assign({}, state.services, (_f = {}, _f[payload.service] = false, _f)) });
42 | }
43 | default:
44 | return state;
45 | }
46 | }
47 | //# sourceMappingURL=loading.js.map
--------------------------------------------------------------------------------
/packages/core/lib/reducers/loading.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | /**
3 | * loading reducer
4 | * @author yoyoyohamapi
5 | * @ignore created 2018-08-03 12:36:49
6 | */
7 | var __assign = (this && this.__assign) || function () {
8 | __assign = Object.assign || function(t) {
9 | for (var s, i = 1, n = arguments.length; i < n; i++) {
10 | s = arguments[i];
11 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
12 | t[p] = s[p];
13 | }
14 | return t;
15 | };
16 | return __assign.apply(this, arguments);
17 | };
18 | Object.defineProperty(exports, "__esModule", { value: true });
19 | var actionTypes_1 = require("../constants/actionTypes");
20 | function loading(state, action) {
21 | if (state === void 0) { state = { services: {}, flows: {} }; }
22 | var _a, _b, _c, _d, _e, _f;
23 | var type = action.type;
24 | var payload = action.payload ? action.payload : {};
25 | switch (type) {
26 | case actionTypes_1.LOADING_START_ACTION: {
27 | return __assign({}, state, { flows: __assign({}, state.flows, (_a = {}, _a[payload.flow] = true, _a)) });
28 | }
29 | case actionTypes_1.LOADING_END_ACTION: {
30 | return __assign({}, state, { flows: __assign({}, state.flows, (_b = {}, _b[payload.flow] = false, _b)) });
31 | }
32 | case actionTypes_1.SERVICE_LOADING_START_ACTION: {
33 | return __assign({}, state, { services: __assign({}, state.services, (_c = {}, _c[payload.service] = true, _c)) });
34 | }
35 | case actionTypes_1.SERVICE_LOADING_END_ACTION: {
36 | return __assign({}, state, { services: __assign({}, state.services, (_d = {}, _d[payload.service] = false, _d)) });
37 | }
38 | // flow 出错时,停止 loading
39 | case actionTypes_1.ERROR_SET_ACTION: {
40 | return __assign({}, state, { flows: __assign({}, state.flows, (_e = {}, _e[payload.flow] = false, _e)) });
41 | }
42 | case actionTypes_1.SERVICE_ERROR_SET_ACTION: {
43 | return __assign({}, state, { services: __assign({}, state.services, (_f = {}, _f[payload.service] = false, _f)) });
44 | }
45 | default:
46 | return state;
47 | }
48 | }
49 | exports.default = loading;
50 | //# sourceMappingURL=loading.js.map
--------------------------------------------------------------------------------
/examples/github/src/modules/repo/ActionPanel.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * repo action panel
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-07-18 19:07:25
5 | */
6 | import * as React from 'react'
7 | import { connect, DispatchProp } from 'react-redux'
8 | import { Row, Col, Select, Input } from 'antd'
9 | import { LoadingState } from '@reobservable/core'
10 | import { RepoState } from '@models/repo'
11 |
12 | interface StateMapper {
13 | repo: RepoState
14 | loading: LoadingState
15 | }
16 |
17 | const Option = Select.Option
18 | const Search = Input.Search
19 |
20 | const ORDER_LIST = [
21 | {
22 | text: '按 star 数目排序',
23 | value: 'stars'
24 | },
25 | {
26 | text: '按 fork 数目排序',
27 | value: 'forks'
28 | }
29 | ]
30 |
31 | interface Props {
32 | sort: string
33 | query: string
34 | disabledSearch: boolean
35 | }
36 |
37 | const ActionPanel: React.SFC = props => {
38 | const { dispatch, sort, disabledSearch } = props
39 |
40 | const handleSearch = (query: string) => {
41 | if (query === props.query) {
42 | dispatch({ type: 'repo/fetch' })
43 | } else {
44 | dispatch({
45 | type: 'repo/change',
46 | payload: {
47 | query
48 | }
49 | })
50 | }
51 | dispatch({
52 | type: 'repo/patch',
53 | payload: {
54 | pagination: {
55 | page: 1
56 | }
57 | }
58 | })
59 | }
60 |
61 | const handleSortChange = (sort: string) => {
62 | dispatch({
63 | type: 'repo/change',
64 | payload: { sort }
65 | })
66 | }
67 |
68 | return (
69 |
76 |
77 |
84 |
85 |
86 |
93 |
94 |
95 | )
96 | }
97 |
98 | const mapStateToProps = ({ repo, loading }: StateMapper): Props => ({
99 | sort: repo.sort,
100 | query: repo.query,
101 | disabledSearch: loading.services['repo/fetch']
102 | })
103 |
104 | export default connect(mapStateToProps)(ActionPanel)
105 |
--------------------------------------------------------------------------------
/examples/github/src/modules/user/ActionPanel.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * user action panel
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-07-17 21:18:38
5 | */
6 | import * as React from 'react'
7 | import { connect, DispatchProp } from 'react-redux'
8 | import { Row, Col, Select, Input } from 'antd'
9 | import { LoadingState } from '@reobservable/core'
10 | import { UserState } from '@models/user'
11 |
12 | interface StateMapper {
13 | user: UserState
14 | loading: LoadingState
15 | }
16 |
17 | const Option = Select.Option
18 | const Search = Input.Search
19 |
20 | const ORDER_LIST = [
21 | {
22 | text: '按 followers 数目排序',
23 | value: 'followers'
24 | },
25 | {
26 | text: '按 repos 数目排序',
27 | value: 'repositories'
28 | }
29 | ]
30 |
31 | interface Props {
32 | sort: string
33 | query: string
34 | disabledSearch: boolean
35 | }
36 |
37 | const ActionPanel: React.SFC = props => {
38 | const { dispatch, sort, disabledSearch } = props
39 |
40 | const handleSearch = (query: string) => {
41 | dispatch({
42 | type: 'user/patch',
43 | payload: {
44 | pagination: {
45 | page: 1
46 | }
47 | }
48 | })
49 | if (query === props.query) {
50 | dispatch({ type: 'user/fetch' })
51 | } else {
52 | dispatch({
53 | type: 'user/change',
54 | payload: {
55 | query
56 | }
57 | })
58 | }
59 | }
60 |
61 | const handleSortChange = (sort: string) => {
62 | dispatch({
63 | type: 'user/change',
64 | payload: { sort }
65 | })
66 | }
67 |
68 | return (
69 |
76 |
77 |
84 |
85 |
86 |
93 |
94 |
95 | )
96 | }
97 |
98 | const mapStateToProps = ({ user, loading }: StateMapper): Props => ({
99 | sort: user.sort,
100 | query: user.query,
101 | disabledSearch: loading.services['user/fetch']
102 | })
103 |
104 | export default connect(mapStateToProps)(ActionPanel)
105 |
--------------------------------------------------------------------------------
/docs/basics/README.md:
--------------------------------------------------------------------------------
1 | # Model
2 |
3 | Just like dva, things interact with state are placed in a model. Previously, in plain reudx, we should manage our actions, action types and reducers in different places. But in dva, rematch or reobervable, boilerplate code was moved into the framework, we can focus on state management in a single model.
4 |
5 | ```js
6 | const model = {
7 | name: 'user',
8 | state: {
9 | list: [],
10 | total: 0,
11 | searchKey: '',
12 | error: null
13 | },
14 | reducers: {
15 | fetchSuccess(state, payload) {
16 | return {
17 | ...state,
18 | list: payload.list,
19 | total: payload.total
20 | }
21 | },
22 | fetchError(state, payload) {
23 | return { ...state, error: payload.error }
24 | }
25 | },
26 | flows: {
27 | fetch(flow$, action$, state$) {
28 | return flow$.pipe(
29 | withLatestFrom(state$, (action, state) => {
30 | return {
31 | searchKey: state.searchKey
32 | }
33 | }),
34 | switchMap(params => {
35 | return api.fetchUser(params).pipe(
36 | map(resp => ({
37 | type: 'user/fetchSuccess',
38 | payload: {
39 | list: resp.items,
40 | total: resp.totalCount
41 | }
42 | }))
43 | catchError(error => of({
44 | type: 'user/fetchError',
45 | payload: { error }
46 | }))
47 | )
48 | })
49 | )
50 | }
51 | }
52 | }
53 | ```
54 |
55 | As shown above, a model is composed of the following parts:
56 |
57 | - **name**
58 |
59 | The name of the model which should be universally **unique**. It determines how to locate then state, action and reducer of the model.
60 |
61 | - **state**
62 |
63 | The initial state of the model.
64 |
65 | - **reduers**
66 |
67 | The key of any reducer in model's reducers is its **name**. For example, as shown above, we defined a reducer named `fetch`, then if the application dispatches a an action with type `user/fetchSuccess`, the reducer will be called to reduce the next state.
68 |
69 | In addition, if we defined a reducer named `bar/fetchSuccess` in this model, it will be responsible for the `bar/fetchSuccess` action.
70 |
71 | Now, you'd like to know that **reducer is used to interact with synchronous action**.
72 |
73 | - **flows**
74 |
75 | Just like reducers, every flow in flows is assiociated with an action by its name. Compare with reducer, flow is designed to **interact with asynchronous action**.
76 |
77 | A flow is like an epic in redux-observable, the concept of them is same, but the function signature is a little difference. The first argument of a flow is not `action$`, but a `flow$` which means `action$.ofType(model/flow)`.
78 |
79 | As shown above, we defiened a flow named `fetch`, when the application dispatches an `user/fetch` action, `flow$` in `fetch` will emmit value.
80 |
--------------------------------------------------------------------------------
/docs/basics/change-and-patch.md:
--------------------------------------------------------------------------------
1 | # Change and Patch Action
2 |
3 | ## Defination
4 |
5 | There are two built-in sync action in reobservable:
6 |
7 | - `change` action: change the state declared in action payload
8 | - `patch` action: change the state **partly** declared in action payload.
9 |
10 | Correspondingly, every model in reobservable has two default reducer:
11 |
12 | - `change` reducer
13 | - `patch` reducer
14 |
15 | Change and patch reducer helps you quickly change the model state **without define any other reducers**.
16 |
17 | ## Difference
18 |
19 | Suppose we defined an user model:
20 |
21 | ```ts
22 | import { Model } from '@reobservable'
23 |
24 | const model: Model = {
25 | name: 'user',
26 | state: {
27 | list: [],
28 | pagination: {
29 | totolCount: 0,
30 | page: 0,
31 | pageSize: 10
32 | }
33 | },
34 | reducers: {
35 | // ...
36 | },
37 | flows: {
38 | // ...
39 | }
40 | }
41 | ```
42 |
43 | Initially, the state of the model is:
44 |
45 | ```ts
46 | {
47 | list: [],
48 | pagination: {
49 | totolCount: 0,
50 | page: 0,
51 | pageSize: 10
52 | }
53 | }
54 | ```
55 |
56 | if we dispatched an change action:
57 |
58 | ```ts
59 | {
60 | type: 'user/change',
61 | payload: {
62 | pagination: {
63 | totalCount: 1
64 | }
65 | }
66 | }
67 | ```
68 |
69 | then, new state will be:
70 |
71 | ```ts
72 | {
73 | list: [],
74 | pagination: {
75 | totalCount: 1
76 | }
77 | }
78 | ```
79 |
80 | As you can see, `page`、 `pageSize` state disappeared since **change** reducer will be overrided the state declared in action payload directly. In this example, **change** reducer overrided the `pagination` state.
81 |
82 | Instead, we dispatched an change action:
83 |
84 | ```ts
85 | {
86 | type: 'user/patch',
87 | payload: {
88 | pagination: {
89 | totalCount: 1
90 | }
91 | }
92 | }
93 | ```
94 |
95 | then, new state will be:
96 |
97 | ```ts
98 | {
99 | list: [],
100 | pagination: {
101 | totalCount: 1,
102 | page: 0,
103 | pageSize: 10
104 | }
105 | }
106 | ```
107 |
108 | Patch means **patch update** the state, in this example, **patch** reducer will only update the `totalCount` inside the `pagination`.
109 |
110 | ## Alias
111 |
112 | The abuse of change and patch action may cause your redux dev-tools looks unreadable:
113 |
114 | ```
115 | user/change
116 | user/change
117 | post/change
118 | ...
119 | ```
120 |
121 | To pursue a more clear action flow, we could provide a alias for change and patch action:
122 |
123 | ```ts
124 | import { Symbols } from '@reobservable/core'
125 |
126 | disaptch({
127 | type: 'user/change',
128 | [Symbols.ALIAS]: 'user/changePagination',
129 | payload: {
130 | pagination: {
131 | totalCount: 1,
132 | page: 0,
133 | pageSize: 10
134 | }
135 | }
136 | })
137 | ```
138 |
--------------------------------------------------------------------------------
/packages/core/lib/operators/createService.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"createService.js","sourceRoot":"","sources":["../../src/operators/createService.ts"],"names":[],"mappings":"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"}
--------------------------------------------------------------------------------
/packages/core/es/operators/createService.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"createService.js","sourceRoot":"","sources":["../../src/operators/createService.ts"],"names":[],"mappings":"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"}
--------------------------------------------------------------------------------
/docs/introduction/get-started.md:
--------------------------------------------------------------------------------
1 | # Get Started
2 |
3 | ## Installation
4 |
5 | ```bash
6 | npm install @reobservable/core
7 | ```
8 |
9 | Suppose we want to build a user management app which should display a user list in the web page, the following steps should be followed:
10 |
11 | ## Define Service
12 |
13 | At first, you should declare the service that in charge of the communication of the frontend and backend. In this example, suppose you prefre [axios](https://github.com/axios/axios) for api request:
14 |
15 | ```ts
16 | import { AxiosResponse, AxiosError } from 'axios'
17 | import { ServiceConfig, ServiceFunc } from '@reobservable/core'
18 |
19 | interface ApiService extends ServiceConfig, AxiosError> {}
20 |
21 | const api: ApiService = {
22 | templates: {
23 | success: (resp) => 'ok!',
24 | error: (error) => 'error!'
25 | }
26 | }
27 |
28 | export default api
29 | ```
30 |
31 | ## Define Models
32 |
33 | In this scenario, we should define a **user** model to aggregate state, actions, async actions & reducers.
34 |
35 | ```ts
36 | import { Model } from '@reobservable/core'
37 | import { merge } from 'rxjs'
38 | import { switchMap, withLatestFrom, map, mapTo } from 'rxjs/operators'
39 | import { IUserState, IState } from './types'
40 | import { fetchUsers } from './service'
41 |
42 | const user: Model = {
43 | name: 'user',
44 | state: {
45 | list: [],
46 | total: 0
47 | },
48 | reducers: {
49 | fetchSuccess(state, payload) {
50 | return {
51 | ...state,
52 | list: payload.list,
53 | total: payload.total
54 | }
55 | },
56 | fetchError(state, payload) {
57 | return {
58 | ...state,
59 | list: [],
60 | total: 0
61 | }
62 | }
63 | },
64 | flows: {
65 | fetch(flow$, action$, state$, dependencies) {
66 | const { api } = dependencies.services
67 | return flow$.pipe(
68 | withLatestFrom(state$, (action, state) => {
69 | const params = {
70 | // construct params from state or action payload
71 | }
72 | return params
73 | }),
74 | switchMap(params => {
75 | const [success$, error$] = api(
76 | '/api/fetchUsers',
77 | fetchUsers(params)
78 | )
79 |
80 | return merge(
81 | success$.pipe(
82 | map(resp => ({
83 | type: 'user/fetchSuccess',
84 | payload: {
85 | list: resp.list,
86 | total: resp.total
87 | }
88 | }))
89 | ),
90 | error$.pipe(
91 | mapTo({type: 'user/fetchError'})
92 | )
93 | )
94 | })
95 | )
96 | }
97 | }
98 | }
99 | ```
100 |
101 | ## Create Redux Store
102 |
103 | Finnaly, we could call `init(options)` to create redux store:
104 |
105 | ```tsx
106 | import * as React from 'react'
107 | import { render } from 'react-dom'
108 | import { Provider } from 'react-redux'
109 | import { init } from '@reobservable/core'
110 |
111 | import user from './models/user'
112 | import api from './services/api'
113 |
114 | const store = init({
115 | services: {
116 | api
117 | },
118 | models: {
119 | user
120 | }
121 | })
122 |
123 | render(
124 |
125 |
126 | ,
127 | document.getElementById('app')
128 | )
129 | ```
--------------------------------------------------------------------------------
/packages/core/test/redux.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * redux config test
3 | * @author yooyoyohamapi
4 | * @ignore created 2018-10-08 09:38:26
5 | */
6 |
7 | import * as sinon from 'sinon'
8 | import { expect } from 'chai'
9 | import { init } from '../src'
10 | import Model from '../src/types/Model'
11 | import { Middleware, combineReducers } from 'redux'
12 |
13 | describe('redux', () => {
14 | it('should support root reducer wrapper#1', () => {
15 | const model: Model<{ name: string }> = {
16 | name: 'model',
17 | state: {
18 | name: 'model'
19 | },
20 | reducers: {
21 | changeName(state, payload) {
22 | return { ...state, name: payload.name }
23 | },
24 | dangerouslyChangeName(state, payload) {
25 | return { ...state, name: payload.name }
26 | }
27 | }
28 | }
29 | const store = init({
30 | redux: {
31 | rootReducer(rootReducer) {
32 | return function(state, action) {
33 | if (action.type === 'model/dangerouslyChangeName') {
34 | return state
35 | } else {
36 | return rootReducer(state, action)
37 | }
38 | }
39 | }
40 | },
41 | models: { model }
42 | })
43 |
44 | store.dispatch({
45 | type: 'model/changeName',
46 | payload: { name: 'changed' }
47 | })
48 |
49 | store.dispatch({
50 | type: 'model/dangerouslyChangeName',
51 | payload: { name: 'dangerouslyChangedName' }
52 | })
53 |
54 | const { name } = store.getState().model
55 | expect(name).to.equal('changed')
56 | })
57 |
58 | it('should support root reducer wrapper#2', () => {
59 | const model: Model<{ name: string }> = {
60 | name: 'model',
61 | state: {
62 | name: 'model'
63 | },
64 | reducers: {
65 | changeName(state, payload) {
66 | return { ...state, name: payload.name }
67 | },
68 | dangerouslyChangeName(state, payload) {
69 | return { ...state, name: payload.name }
70 | }
71 | }
72 | }
73 | const store = init({
74 | redux: {
75 | rootReducer(rootReducer, reducers) {
76 | return combineReducers({
77 | ...reducers,
78 | count: (state = 0, action) => {
79 | if (action.type === 'count/incr') {
80 | return state + 1
81 | } else {
82 | return state
83 | }
84 | }
85 | })
86 | }
87 | },
88 | models: { model }
89 | })
90 |
91 | store.dispatch({
92 | type: 'model/changeName',
93 | payload: { name: 'changed' }
94 | })
95 |
96 | store.dispatch({
97 | type: 'count/incr'
98 | })
99 |
100 | const { name } = store.getState().model
101 | const count = store.getState().count
102 | expect(name).to.equal('changed')
103 | expect(count).to.equal(1)
104 | })
105 |
106 | it('should support single redux middleware', () => {
107 | const fake = sinon.fake()
108 | const middleware: Middleware = store => {
109 | return next => action => {
110 | return fake()
111 | }
112 | }
113 |
114 | const store = init({
115 | redux: {
116 | middleware
117 | }
118 | })
119 |
120 | store.dispatch({ type: 'fetch' })
121 |
122 | expect(fake.calledOnce).to.be.true
123 | })
124 |
125 | it('should support redux middleware list', () => {
126 | const fake = sinon.fake()
127 | const middleware: Middleware = store => {
128 | return next => action => {
129 | return fake()
130 | }
131 | }
132 |
133 | const store = init({
134 | redux: {
135 | middleware: [middleware]
136 | }
137 | })
138 |
139 | store.dispatch({ type: 'fetch' })
140 |
141 | expect(fake.calledOnce).to.be.true
142 | })
143 | })
144 |
--------------------------------------------------------------------------------
/docs/introduction/concepts.md:
--------------------------------------------------------------------------------
1 | # Concepts
2 |
3 | Before creating a reobservable app, there are only two concepts you should konw.
4 |
5 | ## Model
6 |
7 | Same as dva, model defines a state machine which mixined **initial state**,**sync actions**, **async actions(flows)**, **reducers** in one place.
8 |
9 | ```ts
10 | import { Model } from '@reobservable'
11 |
12 | const model: Model = {
13 | name: 'user',
14 | state: {
15 | list: [],
16 | pagination: {
17 | totolCount: 0,
18 | page: 0,
19 | pageSize: 10
20 | }
21 | },
22 | reducers: {},
23 | flows: {}
24 | }
25 |
26 | export default model
27 | ```
28 |
29 | As shown above, a model consists of five parts:
30 |
31 | **name**
32 |
33 | Model name, it indicates the scope of a model in redux store. In this example, we could get the user state by calling `store.getState().user`. Or in React component, we use react-redux:
34 |
35 | ```ts
36 | const mapStateToPros = (state: IState) => {
37 | return {
38 | totalCount: state.user.totalCount
39 | }
40 | }
41 | ```
42 |
43 | **state**
44 |
45 | Initial state of a model.
46 |
47 | **reducers**
48 |
49 | Redux reducers. When a synchronous action dispatched, depends on its type, it may hit a reducer to return a new state. For example, if we dispatched an action:
50 |
51 | ```ts
52 | {
53 | type: 'user/fetchSuccess',
54 | payload: {
55 | list: [{nick: 'john', age: 21}, {nick: 'tom', age: 32}],
56 | totalCount: 2
57 | }
58 | }
59 | ```
60 |
61 | then reobservable will lookup user model to determine if there was a `fetchSuccess` in the defined reducers. If user model has `fetchSuccess` reducer, it will be called, and then, we would get a new state of the user model.
62 |
63 | **flows**
64 |
65 | Async flows. Every flow defined in model flows have **four** parameters, and return an `ActionObservable`(An observable which emit action):
66 |
67 | ```ts
68 | const model = {
69 | name: 'user',
70 | // ....
71 | flows: {
72 | fetch(flow$, action$, payload$, dependencies) {
73 | return flow$.pipe(
74 | mapTo({
75 | type: 'user/fetchSuccess',
76 | payload: {}
77 | })
78 | )
79 | }
80 | }
81 | }
82 | ```
83 |
84 | It's signature is very similar to the **epic** in [redux-observable](https://redux-observable.js.org/). The difference is the first parameter - `flow$`, it is equivalent to `action$.ofType('model/flow')`.
85 |
86 | For example, if we dispatched an action:
87 |
88 | ```ts
89 | {
90 | type: 'user/fetch',
91 | payload: {}
92 | }
93 | ```
94 |
95 | `fetch` flow return in the user model will be responded.
96 |
97 | ## Service
98 |
99 | Service describe the communication between frontend and backend, in reobservable, you can define multiple services:
100 |
101 | ```ts
102 | import { AxiosResponse, AxiosError } from 'axios'
103 | import { ServiceConfig, ServiceFunc } from '@reobservable/core'
104 |
105 | interface ApiService extends ServiceConfig, AxiosError> {}
106 |
107 | const api: ApiService = {
108 | templates: {
109 | success: (resp) => 'ok!',
110 | error: (error) => 'error!'
111 | }
112 | }
113 |
114 | export default api
115 | ```
116 |
117 | In reobservable, service was a dependency injected into model flows, and could be used in frp way:
118 |
119 | ```ts
120 | const model = {
121 | // ....
122 | flows: {
123 | fetch(flow$, action$, payload$, dependencies) {
124 | const { api } = dependencies.services
125 | return flow$.pipe(
126 | switchMap(action => {
127 | const [success$, error$] = api(
128 | 'fetch',
129 | fetch(action.payload.id)
130 | )
131 |
132 | return merge(
133 | success$.pipe(
134 | // success stream
135 | ),
136 | error$.pipe(
137 | // error stream
138 | )
139 | )
140 | })
141 | )
142 | }
143 | }
144 | }
145 | ```
--------------------------------------------------------------------------------
/packages/core/test/flow.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * flow tests
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-09-21 15:10:10
5 | */
6 | import { expect } from 'chai'
7 | import { Store } from 'redux'
8 | import { map, delay } from 'rxjs/operators'
9 | import { init } from '../src'
10 | import Model from '../src/types/Model'
11 | import end from '../src/operators/end'
12 |
13 | interface UserState {
14 | list: User[]
15 | current: number
16 | }
17 |
18 | interface PostState {
19 | list: Post[]
20 | current: number
21 | }
22 |
23 | interface User {
24 | id: number
25 | name: string
26 | age: number
27 | }
28 |
29 | interface Post {
30 | id: number
31 | title: string
32 | content: string
33 | }
34 |
35 | const user: Model = {
36 | name: 'user',
37 | state: {
38 | list: [],
39 | current: null
40 | },
41 | flows: {
42 | throwError(flow$) {
43 | return flow$.pipe(
44 | map(({ payload }) => {
45 | if (payload.throw) {
46 | throw Error('error')
47 | } else {
48 | return {
49 | type: 'none'
50 | }
51 | }
52 | })
53 | )
54 | }
55 | }
56 | }
57 |
58 | const post: Model = {
59 | name: 'post',
60 | state: {
61 | list: [],
62 | current: null
63 | },
64 | reducers: {
65 | fetchSuccess(state, payload) {
66 | return {
67 | ...state,
68 | list: payload.list
69 | }
70 | }
71 | },
72 | flows: {
73 | fetch(flow$) {
74 | return flow$.pipe(
75 | delay(10),
76 | end(() => ({
77 | type: 'post/fetchSuccess',
78 | payload: {
79 | list: [
80 | {
81 | id: 1,
82 | title: '#1',
83 | content: '#1 content'
84 | },
85 | {
86 | id: 2,
87 | title: '#2',
88 | content: '#2 content'
89 | },
90 | {
91 | id: 3,
92 | title: '#3',
93 | content: '#3 content'
94 | }
95 | ]
96 | }
97 | }))
98 | )
99 | }
100 | }
101 | }
102 |
103 | describe('flow', () => {
104 | let store: Store
105 |
106 | const initStore = () => {
107 | store = init({
108 | models: { user, post }
109 | })
110 | }
111 |
112 | before(() => {
113 | // tslint:disable-next-line:no-empty
114 | console.error = function() {}
115 | })
116 |
117 | beforeEach(initStore)
118 |
119 | it('should set loading true when flow start', () => {
120 | store.dispatch({
121 | type: 'post/fetch'
122 | })
123 |
124 | const loading = store.getState().loading
125 | expect(loading.flows['post/fetch']).to.be.true
126 | })
127 |
128 | it('should set loading false when flow end', done => {
129 | store.dispatch({
130 | type: 'post/fetch'
131 | })
132 |
133 | setTimeout(() => {
134 | const loading = store.getState().loading
135 | expect(loading.flows['post/fetch']).to.be.false
136 | done()
137 | }, 20)
138 | })
139 |
140 | it('should catch flow error', () => {
141 | store.dispatch({
142 | type: 'user/throwError',
143 | payload: {
144 | throw: true
145 | }
146 | })
147 |
148 | const error = store.getState().error
149 | expect(error.flows['user/throwError'])
150 | .to.property('message')
151 | .be.equal('error')
152 | })
153 |
154 | it('should reset error when action retry', () => {
155 | store.dispatch({
156 | type: 'user/throwError',
157 | payload: {
158 | throw: true
159 | }
160 | })
161 |
162 | store.dispatch({
163 | type: 'user/throwError',
164 | payload: {
165 | throw: false
166 | }
167 | })
168 |
169 | const error = store.getState().error
170 | expect(error.flows['user/throwError']).to.be.null
171 | })
172 |
173 | it('should not stop current flow when other flow throw error', done => {
174 | store.dispatch({
175 | type: 'user/throwError'
176 | })
177 |
178 | store.dispatch({
179 | type: 'post/fetch'
180 | })
181 |
182 | setTimeout(() => {
183 | const state = store.getState()
184 | expect(state.post.list).to.not.empty
185 | done()
186 | }, 20)
187 | })
188 | })
189 |
--------------------------------------------------------------------------------
/examples/github/src/models/user.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * user model
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-07-17 15:11:14
5 | */
6 | import { isEqual } from 'lodash/fp'
7 | import { timer, Observable, merge } from 'rxjs'
8 | import {
9 | map,
10 | combineLatest,
11 | distinctUntilChanged,
12 | switchMap,
13 | startWith,
14 | takeUntil,
15 | mapTo
16 | } from 'rxjs/operators'
17 | import { createSelector } from 'reselect'
18 | import { PaginationProps } from 'antd/lib/pagination'
19 | import { Model, NOTIFICATION_LEVEL, getService } from '@reobservable/core'
20 | import { fetch } from '@services/user'
21 | import { Order, Pagination } from '@models/common'
22 | import { ApiServiceFunc } from '@services/types'
23 |
24 | export interface User {
25 | login: string
26 | id: number
27 | avatar_url: string
28 | url: string
29 | html_url: string
30 | }
31 |
32 | export interface UserState {
33 | list: User[]
34 | pagination: Pagination
35 | sort: string
36 | isSilentLoading: boolean
37 | query: string
38 | }
39 |
40 | export interface SearchParam {
41 | q: string
42 | language: string
43 | page: number
44 | per_page: number
45 | sort: string
46 | order: Order
47 | }
48 |
49 | export interface SearchResp {
50 | total_count: number
51 | items: User[]
52 | }
53 |
54 | const showTotal = total => `共 ${total} 条`
55 |
56 | const user: Model = {
57 | name: 'user',
58 | state: {
59 | list: [],
60 | pagination: {
61 | total: 0,
62 | page: 1,
63 | pageSize: 10
64 | },
65 | sort: 'followers',
66 | isSilentLoading: false,
67 | query: ''
68 | },
69 | selectors: {
70 | pagination: createSelector<
71 | { user: UserState },
72 | Pagination,
73 | PaginationProps
74 | >(
75 | ({ user }) => user.pagination,
76 | ({ total, page, pageSize }) => ({
77 | current: page,
78 | pageSize,
79 | total,
80 | showSizeChanger: true,
81 | showTotal
82 | })
83 | )
84 | },
85 | reducers: {
86 | startPolling(state) {
87 | return { ...state, isSilentLoading: false }
88 | },
89 | fetchSuccess(state, payload) {
90 | return {
91 | ...state,
92 | list: payload.list,
93 | pagination: {
94 | ...state.pagination,
95 | total: payload.total
96 | },
97 | isSilentLoading: true
98 | }
99 | },
100 | fetchError(state, payload) {
101 | return {
102 | ...state,
103 | list: [],
104 | pagination: {
105 | ...state.pagination,
106 | total: 0,
107 | page: 1
108 | }
109 | }
110 | }
111 | },
112 | flows: {
113 | fetch(flow$, action$, state$) {
114 | const service: ApiServiceFunc = getService('api')
115 | const stopPolling$ = action$.ofType(
116 | 'user/stopPolling',
117 | 'user/fetchError'
118 | )
119 | const params$: Observable = state$.pipe(
120 | map(({ user }: { user: UserState }) => {
121 | const { pagination, sort, query } = user
122 | return {
123 | q: `${query ? query + ' ' : ''}language:javascript`,
124 | language: 'javascript',
125 | page: pagination.page,
126 | per_page: pagination.pageSize,
127 | sort,
128 | order: Order.Desc
129 | }
130 | }),
131 | distinctUntilChanged(isEqual)
132 | )
133 |
134 | return flow$.pipe(
135 | combineLatest(params$, (action, params) => params),
136 | switchMap(params => {
137 | const polling$ = timer(0, 15 * 1000).pipe(
138 | takeUntil(stopPolling$),
139 | switchMap(() => {
140 | const [success$, error$] = service('user/fetch', fetch(params), {
141 | level: NOTIFICATION_LEVEL.all
142 | })
143 | return merge(
144 | success$.pipe(
145 | map(({ resp: { data } }) => ({
146 | type: 'user/fetchSuccess',
147 | payload: {
148 | total: data.total_count,
149 | list: data.items
150 | }
151 | }))
152 | ),
153 | error$.pipe(
154 | mapTo({
155 | type: 'user/fetchError',
156 | payload: {}
157 | })
158 | )
159 | )
160 | }),
161 | startWith({
162 | type: 'user/startPolling'
163 | })
164 | )
165 | return polling$
166 | })
167 | )
168 | }
169 | }
170 | }
171 |
172 | export default user
173 |
--------------------------------------------------------------------------------
/packages/core/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@reobservable/core",
3 | "version": "0.1.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "axios": {
8 | "version": "0.18.0",
9 | "resolved": "http://registry.npmjs.org/axios/-/axios-0.18.0.tgz",
10 | "integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=",
11 | "dev": true,
12 | "requires": {
13 | "follow-redirects": "^1.3.0",
14 | "is-buffer": "^1.1.5"
15 | }
16 | },
17 | "debug": {
18 | "version": "3.1.0",
19 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
20 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
21 | "dev": true,
22 | "requires": {
23 | "ms": "2.0.0"
24 | }
25 | },
26 | "follow-redirects": {
27 | "version": "1.5.8",
28 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.8.tgz",
29 | "integrity": "sha512-sy1mXPmv7kLAMKW/8XofG7o9T+6gAjzdZK4AJF6ryqQYUa/hnzgiypoeUecZ53x7XiqKNEpNqLtS97MshW2nxg==",
30 | "dev": true,
31 | "requires": {
32 | "debug": "=3.1.0"
33 | }
34 | },
35 | "invariant": {
36 | "version": "2.2.4",
37 | "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
38 | "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
39 | "requires": {
40 | "loose-envify": "^1.0.0"
41 | }
42 | },
43 | "is-buffer": {
44 | "version": "1.1.6",
45 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
46 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
47 | "dev": true
48 | },
49 | "js-tokens": {
50 | "version": "4.0.0",
51 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
52 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
53 | },
54 | "lodash.clonedeep": {
55 | "version": "4.5.0",
56 | "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
57 | "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8="
58 | },
59 | "lodash.merge": {
60 | "version": "4.6.1",
61 | "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.1.tgz",
62 | "integrity": "sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ=="
63 | },
64 | "lodash.mergewith": {
65 | "version": "4.6.1",
66 | "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz",
67 | "integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ=="
68 | },
69 | "loose-envify": {
70 | "version": "1.4.0",
71 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
72 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
73 | "requires": {
74 | "js-tokens": "^3.0.0 || ^4.0.0"
75 | }
76 | },
77 | "ms": {
78 | "version": "2.0.0",
79 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
80 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
81 | "dev": true
82 | },
83 | "redux": {
84 | "version": "4.0.0",
85 | "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.0.tgz",
86 | "integrity": "sha512-NnnHF0h0WVE/hXyrB6OlX67LYRuaf/rJcbWvnHHEPCF/Xa/AZpwhs/20WyqzQae5x4SD2F9nPObgBh2rxAgLiA==",
87 | "requires": {
88 | "loose-envify": "^1.1.0",
89 | "symbol-observable": "^1.2.0"
90 | }
91 | },
92 | "redux-devtools-extension": {
93 | "version": "2.13.5",
94 | "resolved": "https://registry.npmjs.org/redux-devtools-extension/-/redux-devtools-extension-2.13.5.tgz",
95 | "integrity": "sha512-QQ9BRy77oURHMdGys9rfQcCQDzXZ1T4oW+eUyE5Cg7DNVau69HJzc4YNDMOmpi0Dzpi1zOQgQ2rUpgJta4Lfqg=="
96 | },
97 | "redux-observable": {
98 | "version": "1.0.0",
99 | "resolved": "https://registry.npmjs.org/redux-observable/-/redux-observable-1.0.0.tgz",
100 | "integrity": "sha512-6bXnpqWTBeLaLQjXHyN1giXq4nLxCmv+SUkdmiwBgvmVxvDbdmydvL1Z7DGo0WItyzI/kqXQKiucUuTxnrPRkA=="
101 | },
102 | "rxjs": {
103 | "version": "6.3.2",
104 | "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.2.tgz",
105 | "integrity": "sha512-hV7criqbR0pe7EeL3O66UYVg92IR0XsA97+9y+BWTePK9SKmEI5Qd3Zj6uPnGkNzXsBywBQWTvujPl+1Kn9Zjw==",
106 | "requires": {
107 | "tslib": "^1.9.0"
108 | }
109 | },
110 | "symbol-observable": {
111 | "version": "1.2.0",
112 | "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
113 | "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="
114 | },
115 | "tslib": {
116 | "version": "1.9.3",
117 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
118 | "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ=="
119 | }
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/examples/github/src/models/repo.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * repo model
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-07-17 15:11:14
5 | */
6 | import * as moment from 'moment'
7 | import { isEqual } from 'lodash/fp'
8 | import { timer, merge, Observable } from 'rxjs'
9 | import {
10 | map,
11 | combineLatest,
12 | distinctUntilChanged,
13 | switchMap,
14 | startWith,
15 | takeUntil,
16 | mapTo
17 | } from 'rxjs/operators'
18 | import { createSelector } from 'reselect'
19 | import { PaginationProps } from 'antd/lib/pagination'
20 | import { Model, NOTIFICATION_LEVEL, getService } from '@reobservable/core'
21 | import { fetch } from '@services/repo'
22 | import { Order, Pagination } from '@models/common'
23 | import { ApiServiceFunc } from '@services/types'
24 |
25 | export interface Repo {
26 | id: number
27 | name: string
28 | full_name: string
29 | html_url: string
30 | url: string
31 | homepage: string
32 | updated_at: string
33 | }
34 |
35 | export interface RepoState {
36 | list: Repo[]
37 | pagination: Pagination
38 | sort: string
39 | isSilentLoading: boolean
40 | query: string
41 | }
42 |
43 | export interface SearchParam {
44 | q: string
45 | page: number
46 | per_page: number
47 | sort: string
48 | order: Order
49 | }
50 |
51 | export interface SearchResp {
52 | total_count: number
53 | items: Repo[]
54 | }
55 |
56 | const showTotal = total => `共 ${total} 条`
57 |
58 | const repo: Model = {
59 | name: 'repo',
60 | state: {
61 | list: [],
62 | pagination: {
63 | total: 0,
64 | page: 1,
65 | pageSize: 10
66 | },
67 | sort: 'stars',
68 | isSilentLoading: false,
69 | query: ''
70 | },
71 | reducers: {
72 | startPolling(state) {
73 | return { ...state, isSilentLoading: false }
74 | },
75 | fetchSuccess(state, payload) {
76 | return {
77 | ...state,
78 | list: payload.list,
79 | pagination: {
80 | ...state.pagination,
81 | total: payload.total
82 | },
83 | isSilentLoading: true
84 | }
85 | },
86 | fetchError(state, payload) {
87 | return {
88 | ...state,
89 | list: [],
90 | pagination: {
91 | ...state.pagination,
92 | total: 0,
93 | page: 1
94 | }
95 | }
96 | }
97 | },
98 | selectors: {
99 | pagination: createSelector<
100 | { repo: RepoState },
101 | Pagination,
102 | PaginationProps
103 | >(
104 | ({ repo }) => repo.pagination,
105 | ({ total, page, pageSize }) => ({
106 | current: page,
107 | pageSize,
108 | total,
109 | showSizeChanger: true,
110 | showTotal
111 | })
112 | )
113 | },
114 | flows: {
115 | fetch(flow$, action$, state$) {
116 | const service: ApiServiceFunc = getService('api')
117 | const stopPolling$ = action$.ofType(
118 | 'repo/stopPolling',
119 | 'repo/fetchError'
120 | )
121 | const params$: Observable = state$.pipe(
122 | map(state => {
123 | const { pagination, sort, query } = state.repo
124 | return {
125 | q: `${query ? query + '+' : ''}language:javascript`,
126 | page: pagination.page,
127 | per_page: pagination.pageSize,
128 | sort,
129 | order: Order.Desc
130 | }
131 | }),
132 | distinctUntilChanged(isEqual)
133 | )
134 |
135 | return flow$.pipe(
136 | combineLatest(params$, (action, params) => params),
137 | switchMap(params => {
138 | const polling$ = timer(0, 30 * 60 * 1000).pipe(
139 | takeUntil(stopPolling$),
140 | switchMap(() => {
141 | const [success$, error$] = service('repo/fetch', fetch(params), {
142 | level: NOTIFICATION_LEVEL.silent
143 | })
144 | return merge(
145 | success$.pipe(
146 | map(({ resp: { data } }) => ({
147 | type: 'rep/fetchSuccess',
148 | payload: {
149 | total: data.total_count,
150 | list: data.items.map((item: Repo) => ({
151 | ...item,
152 | displayUpdated: moment(item.updated_at).format(
153 | 'YYYY-MM-DD HH:mm'
154 | )
155 | }))
156 | }
157 | }))
158 | ),
159 | error$.pipe(
160 | mapTo({
161 | type: 'repo/fetchError',
162 | payload: {}
163 | })
164 | )
165 | )
166 | }),
167 | startWith({
168 | type: 'repo/startPolling',
169 | payload: {}
170 | })
171 | )
172 | return polling$
173 | })
174 | )
175 | }
176 | }
177 | }
178 |
179 | export default repo
180 |
--------------------------------------------------------------------------------
/packages/core/es/operators/createService.js:
--------------------------------------------------------------------------------
1 | /**
2 | * fromService creator
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-13 14:26:55
5 | */
6 | import { from, of, Observable } from 'rxjs';
7 | import { catchError, map, filter, tap, shareReplay, retryWhen, scan, delay } from 'rxjs/operators';
8 | import { LEVEL } from '../constants/notification';
9 | import { SERVICE_ERROR_SET_ACTION, SERVICE_LOADING_END_ACTION, SERVICE_LOADING_START_ACTION } from '../constants/actionTypes';
10 | import { noop } from '../utils/function';
11 | export function partition(source$, predicate) {
12 | return [
13 | source$.pipe(filter(predicate)),
14 | source$.pipe(filter(function (v, i) { return !predicate(v, i); })),
15 | ];
16 | }
17 | /**
18 | * 创建 fromService creator
19 | * @param {Notification} notification
20 | */
21 | export default function createFromService(notification, serviceConfig, store) {
22 | /**
23 | * fromServiceOperators
24 | * @param service
25 | * @param options
26 | */
27 | return function service(serviceName, service, options) {
28 | if (options === void 0) { options = {
29 | level: LEVEL.silent,
30 | retry: 0,
31 | retryDelay: 0,
32 | loadingDelay: 0
33 | }; }
34 | var _a = options.templates || serviceConfig.templates || {}, _b = _a.success, success = _b === void 0 ? noop : _b, _c = _a.error, error = _c === void 0 ? noop : _c;
35 | var level = options.level, _d = options.retry, retry = _d === void 0 ? 0 : _d, _e = options.retryDelay, retryDelay = _e === void 0 ? 0 : _e, _f = options.loadingDelay, loadingDelay = _f === void 0 ? 0 : _f;
36 | var timer = null;
37 | if (loadingDelay > 0) {
38 | timer = setTimeout(function () { return store.dispatch({
39 | type: SERVICE_LOADING_START_ACTION,
40 | payload: {
41 | service: serviceName
42 | }
43 | }); }, loadingDelay);
44 | }
45 | else {
46 | store.dispatch({
47 | type: SERVICE_LOADING_START_ACTION,
48 | payload: {
49 | service: serviceName
50 | }
51 | });
52 | }
53 | var successNotificate = function (resp) { return level >= LEVEL.all &&
54 | notification.success(success(resp)); };
55 | var errorNotificate = function (err) { return level >= LEVEL.error &&
56 | notification.error(error(err)); };
57 | var service$ = (service instanceof Observable)
58 | ? service.pipe(retryWhen(function (error$) { return error$.pipe(scan(function (count, err) {
59 | if (count >= retry) {
60 | throw err;
61 | }
62 | else {
63 | return count + 1;
64 | }
65 | }, 0), delay(retryDelay)); }))
66 | : from(service);
67 | var response$ = (typeof serviceConfig.isSuccess) === 'function'
68 | ? service$.pipe(map(function (resp) {
69 | if (serviceConfig.isSuccess(resp)) {
70 | return { resp: resp, success: true };
71 | }
72 | else {
73 | var error_1 = serviceConfig.errorSelector ? serviceConfig.errorSelector(resp) : 'error';
74 | return { error: error_1 };
75 | }
76 | }))
77 | : service$.pipe(map(function (resp) {
78 | return { resp: resp, success: true };
79 | }), catchError(function (error) {
80 | return of({
81 | error: serviceConfig.errorSelector ? serviceConfig.errorSelector(error) : error
82 | });
83 | }));
84 | var _g = partition(response$.pipe(tap(function () { return timer && clearTimeout(timer); }), shareReplay(1)), (function (_a) {
85 | var success = _a.success;
86 | return success;
87 | })), success$ = _g[0], error$ = _g[1];
88 | success$.subscribe({
89 | next: function (_a) {
90 | var resp = _a.resp;
91 | successNotificate(resp);
92 | store.dispatch({
93 | type: SERVICE_LOADING_END_ACTION,
94 | payload: {
95 | service: serviceName
96 | }
97 | });
98 | }
99 | });
100 | error$.subscribe({
101 | next: (function (_a) {
102 | var error = _a.error;
103 | errorNotificate(error);
104 | store.dispatch({
105 | type: SERVICE_ERROR_SET_ACTION,
106 | payload: {
107 | error: error,
108 | service: serviceName
109 | }
110 | });
111 | })
112 | });
113 | return [
114 | success$,
115 | error$
116 | ];
117 | };
118 | }
119 | //# sourceMappingURL=createService.js.map
--------------------------------------------------------------------------------
/packages/core/lib/operators/createService.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | /**
4 | * fromService creator
5 | * @author yoyoyohamapi
6 | * @ignore created 2018-08-13 14:26:55
7 | */
8 | var rxjs_1 = require("rxjs");
9 | var operators_1 = require("rxjs/operators");
10 | var notification_1 = require("../constants/notification");
11 | var actionTypes_1 = require("../constants/actionTypes");
12 | var function_1 = require("../utils/function");
13 | function partition(source$, predicate) {
14 | return [
15 | source$.pipe(operators_1.filter(predicate)),
16 | source$.pipe(operators_1.filter(function (v, i) { return !predicate(v, i); })),
17 | ];
18 | }
19 | exports.partition = partition;
20 | /**
21 | * 创建 fromService creator
22 | * @param {Notification} notification
23 | */
24 | function createFromService(notification, serviceConfig, store) {
25 | /**
26 | * fromServiceOperators
27 | * @param service
28 | * @param options
29 | */
30 | return function service(serviceName, service, options) {
31 | if (options === void 0) { options = {
32 | level: notification_1.LEVEL.silent,
33 | retry: 0,
34 | retryDelay: 0,
35 | loadingDelay: 0
36 | }; }
37 | var _a = options.templates || serviceConfig.templates || {}, _b = _a.success, success = _b === void 0 ? function_1.noop : _b, _c = _a.error, error = _c === void 0 ? function_1.noop : _c;
38 | var level = options.level, _d = options.retry, retry = _d === void 0 ? 0 : _d, _e = options.retryDelay, retryDelay = _e === void 0 ? 0 : _e, _f = options.loadingDelay, loadingDelay = _f === void 0 ? 0 : _f;
39 | var timer = null;
40 | if (loadingDelay > 0) {
41 | timer = setTimeout(function () { return store.dispatch({
42 | type: actionTypes_1.SERVICE_LOADING_START_ACTION,
43 | payload: {
44 | service: serviceName
45 | }
46 | }); }, loadingDelay);
47 | }
48 | else {
49 | store.dispatch({
50 | type: actionTypes_1.SERVICE_LOADING_START_ACTION,
51 | payload: {
52 | service: serviceName
53 | }
54 | });
55 | }
56 | var successNotificate = function (resp) { return level >= notification_1.LEVEL.all &&
57 | notification.success(success(resp)); };
58 | var errorNotificate = function (err) { return level >= notification_1.LEVEL.error &&
59 | notification.error(error(err)); };
60 | var service$ = (service instanceof rxjs_1.Observable)
61 | ? service.pipe(operators_1.retryWhen(function (error$) { return error$.pipe(operators_1.scan(function (count, err) {
62 | if (count >= retry) {
63 | throw err;
64 | }
65 | else {
66 | return count + 1;
67 | }
68 | }, 0), operators_1.delay(retryDelay)); }))
69 | : rxjs_1.from(service);
70 | var response$ = (typeof serviceConfig.isSuccess) === 'function'
71 | ? service$.pipe(operators_1.map(function (resp) {
72 | if (serviceConfig.isSuccess(resp)) {
73 | return { resp: resp, success: true };
74 | }
75 | else {
76 | var error_1 = serviceConfig.errorSelector ? serviceConfig.errorSelector(resp) : 'error';
77 | return { error: error_1 };
78 | }
79 | }))
80 | : service$.pipe(operators_1.map(function (resp) {
81 | return { resp: resp, success: true };
82 | }), operators_1.catchError(function (error) {
83 | return rxjs_1.of({
84 | error: serviceConfig.errorSelector ? serviceConfig.errorSelector(error) : error
85 | });
86 | }));
87 | var _g = partition(response$.pipe(operators_1.tap(function () { return timer && clearTimeout(timer); }), operators_1.shareReplay(1)), (function (_a) {
88 | var success = _a.success;
89 | return success;
90 | })), success$ = _g[0], error$ = _g[1];
91 | success$.subscribe({
92 | next: function (_a) {
93 | var resp = _a.resp;
94 | successNotificate(resp);
95 | store.dispatch({
96 | type: actionTypes_1.SERVICE_LOADING_END_ACTION,
97 | payload: {
98 | service: serviceName
99 | }
100 | });
101 | }
102 | });
103 | error$.subscribe({
104 | next: (function (_a) {
105 | var error = _a.error;
106 | errorNotificate(error);
107 | store.dispatch({
108 | type: actionTypes_1.SERVICE_ERROR_SET_ACTION,
109 | payload: {
110 | error: error,
111 | service: serviceName
112 | }
113 | });
114 | })
115 | });
116 | return [
117 | success$,
118 | error$
119 | ];
120 | };
121 | }
122 | exports.default = createFromService;
123 | //# sourceMappingURL=createService.js.map
--------------------------------------------------------------------------------
/packages/core/lib/index.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
--------------------------------------------------------------------------------
/docs/basics/custom-service.md:
--------------------------------------------------------------------------------
1 | # Custom Service
2 |
3 | In a modern web app, we should considering following parts when we dealing with the communication between the browser and the server:
4 |
5 | - How to **konw** if a service failed or not.
6 | - How to **konw** if a service is running or not.
7 | - How to **notify** the user of the service result.
8 |
9 | ## Define service & notification
10 |
11 | **Service** is an important concept in reobservable. You can custom a service through `init` api:
12 |
13 | ```ts
14 | import { AxiosResponse, AxiosError } from 'axios'
15 | import { ServiceConfig, ServiceFunc, init } from '@reobservable/core'
16 | import { message } from 'antd'
17 |
18 | interface IApiResp {
19 | data: {
20 | code: number,
21 | message: string,
22 | body: any
23 | }
24 | }
25 |
26 | interface ApiService extends ServiceConfig, AxiosError> {}
27 |
28 | const apiService: ApiService = {
29 | isSuccess: (resp) => resp.data.code === 0,
30 | errorSelector: (resp) => resp.data.message,
31 | templates: {
32 | success: (resp) => 'ok!',
33 | error: (error) => `Error: ${error}`
34 | }
35 | }
36 |
37 | const store = init({
38 | models: {
39 | // modesl
40 | },
41 | services: {
42 | api: apiService
43 | },
44 | notification: message
45 | })
46 | ```
47 |
48 | In the above example, we:
49 | - Use Axios as our **service lib**.
50 | - **Declare how to know a service failed or not by override `isSuccess` property**: If code inside the response is equal to `0`, then reobservble know that service is success.
51 | - **Declare how to select error message when service failed by override `errorSelector` property**. We select `message` property from the response as our error message.
52 | - **Define a notification template by override `templates` property**. It determines what message should be notified to user in reobservbale.
53 |
54 | We also defined the **notification** in reobservable with [**Message**](https://ant.design/components/message/) in ant design. It implements the methods defined in the `Notification` interface:
55 |
56 | ```ts
57 | interface Notification {
58 | info?: notificate,
59 | success?: notificate,
60 | warn?: notificate,
61 | error?: notificate
62 | }
63 | ```
64 |
65 | Then, when the service succeed, we will see:
66 |
67 | 
68 |
69 | and when the service failed with the following response:
70 |
71 | ```ts
72 | {
73 | code: -1,
74 | message: 'Invalid params'
75 | }
76 | ```
77 |
78 | we will see:
79 |
80 | 
81 |
82 | > We can also override **nothing** when we custom a service. If we did this, then the service library we used will be in charge of how a service failed or not, and the
83 |
84 | ## Use service
85 |
86 | Then, we can call service function in a flow's dependencies:
87 |
88 | ```ts
89 | import { Model, NOTIFICATION_LEVEL } from '@reobservable/core'
90 |
91 | const model: Model = {
92 | name: 'user',
93 | state: {
94 | list: []
95 | },
96 | reducers: {
97 | fetchSuccess(state, payload) {
98 | return { ...state, list: payload.list }
99 | },
100 | fetchError(state, payload) {
101 | return { ...state, list: [] }
102 | }
103 | },
104 | flows: {
105 | fetch(flow$, action$, payload$, dependencies) {
106 | const { api } = dependencies.services
107 |
108 | return flow$.pipe(
109 | switchMap(action => {
110 | const [success$, error$] = api(
111 | 'user/fetch',
112 | service.fetchUser(params),
113 | {
114 | level: NOTIFICATION_LEVEL.error,
115 | templates: {
116 | error: (error) => 'Fetch users error!'
117 | }
118 | }
119 | )
120 | return merge(
121 | success$.pipe(map(resp => ({type: 'user/fetchSuccess', payload: resp}))),
122 | error$.pipe(mapTo({type: 'user/fetchError'}))
123 | )
124 | })
125 | )
126 | }
127 | }
128 | }
129 | ```
130 |
131 | The signature of the service function is:
132 |
133 | ```ts
134 | export type ServiceFunc =
135 | (serviceName: string, service: Promise | Observable, options?: ServiceOptions) =>
136 | [Observable>, Observable>]
137 | ```
138 |
139 | It take 3 parameters:
140 |
141 | - `serviceName`: the service name, it helps store to know how to pick up the loading or error state of the service.
142 | - `service`: is a Promise or an Observable object that in charge of communicating with the server.
143 | - `options`: the service options. We will describe it next.
144 |
145 | then return two observable: `success$` and `error$`.
146 |
147 | ## Custom options
148 |
149 | In a service call, you can custom following service options:
150 |
151 | ```ts
152 | interface ServiceOptions {
153 | level: NotificationLevel,
154 | templates?: ServiceTemplates,
155 | retry?: number,
156 | retryDelay?: number
157 | }
158 | ```
159 |
160 | - `level`: the service notification level. It determines the timing of a service result notification, three different level supported currently:
161 | - `NOTIFICATION_LEVEL.silent`: don't notify.
162 | - `NOTIFICATION_LEVEL.error`: notify user only when service.
163 | - `NOTIFICATION_LEVEL.all`: notify user whether service is success or not.
164 | - `templates`: the same as the `templates` in service defination, but only act on this service call.
165 | - `retry`: max count of a service should retry. Default is `0`.
166 | - `retryDelay`: Delay ms of a service retry. Default is `0`.
167 |
168 | ## Loading & error
169 |
170 | We can pick up the loading and error state by service name:
171 |
172 | ```ts
173 | const mapStateToProps = (state: IState) => ({
174 | isUserFetching: state.loading.services['user/fetch'],
175 | userFetchError: state.error.services['user/fetch']
176 | })
177 | ```
178 |
179 |
--------------------------------------------------------------------------------
/packages/core/src/operators/createService.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * fromService creator
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-13 14:26:55
5 | */
6 | import { from, of, Observable } from 'rxjs'
7 | import {
8 | catchError,
9 | map,
10 | filter,
11 | tap,
12 | shareReplay,
13 | retryWhen,
14 | scan,
15 | delay
16 | } from 'rxjs/operators'
17 | import { Store } from 'redux'
18 | import { Notification, NotificationLevel } from '../types/Notification'
19 | import { LEVEL } from '../constants/notification'
20 | import {
21 | SERVICE_ERROR_SET_ACTION,
22 | SERVICE_LOADING_END_ACTION,
23 | SERVICE_LOADING_START_ACTION
24 | } from '../constants/actionTypes'
25 | import { noop } from '../utils/function'
26 |
27 | interface ServiceTemplates {
28 | success: (resp: T) => string
29 | error: (error: E) => string
30 | }
31 |
32 | export interface ServiceConfig {
33 | templates?: ServiceTemplates
34 | isSuccess?: (resp: T) => boolean
35 | errorSelector?: (error: any) => any
36 | }
37 |
38 | interface ServiceOptions {
39 | /** 等级 */
40 | level: NotificationLevel
41 | /** 消息模板 */
42 | templates?: ServiceTemplates
43 | /** 重试次数 */
44 | retry?: number
45 | /** 重试延迟 */
46 | retryDelay?: number
47 | /** 加载延迟 */
48 | loadingDelay?: number
49 | }
50 |
51 | interface Result {
52 | resp?: T | undefined
53 | error?: E | undefined
54 | success?: boolean | undefined
55 | }
56 |
57 | export type ServiceFunc = (
58 | serviceName: string,
59 | service: Promise | Observable,
60 | options?: ServiceOptions
61 | ) => [Observable>, Observable>]
62 |
63 | export function partition(
64 | source$: Observable,
65 | predicate: (value: T, index: number) => boolean
66 | ) {
67 | return [
68 | source$.pipe(filter(predicate)),
69 | source$.pipe(filter((v, i) => !predicate(v, i)))
70 | ]
71 | }
72 |
73 | /**
74 | * 创建 fromService creator
75 | * @param {Notification} notification
76 | */
77 | export default function createFromService(
78 | notification: Notification,
79 | serviceConfig: ServiceConfig,
80 | store: Store
81 | ): ServiceFunc {
82 | /**
83 | * fromServiceOperators
84 | * @param service
85 | * @param options
86 | */
87 | return function service(
88 | serviceName,
89 | service,
90 | options = {
91 | level: LEVEL.silent,
92 | retry: 0,
93 | retryDelay: 0,
94 | loadingDelay: 0
95 | }
96 | ) {
97 | const { success = noop, error = noop } =
98 | options.templates || serviceConfig.templates || {}
99 | const { level, retry = 0, retryDelay = 0, loadingDelay = 0 } = options
100 |
101 | let timer = null
102 |
103 | if (loadingDelay > 0) {
104 | timer = setTimeout(
105 | () =>
106 | store.dispatch({
107 | type: SERVICE_LOADING_START_ACTION,
108 | payload: {
109 | service: serviceName
110 | }
111 | }),
112 | loadingDelay
113 | )
114 | } else {
115 | store.dispatch({
116 | type: SERVICE_LOADING_START_ACTION,
117 | payload: {
118 | service: serviceName
119 | }
120 | })
121 | }
122 |
123 | const successNotificate = (resp: T) =>
124 | level >= LEVEL.all && notification.success(success(resp))
125 | const errorNotificate = (err: E) =>
126 | level >= LEVEL.error && notification.error(error(err))
127 |
128 | const service$ =
129 | service instanceof Observable
130 | ? service.pipe(
131 | retryWhen(error$ =>
132 | error$.pipe(
133 | scan((count, err) => {
134 | if (count >= retry) {
135 | throw err
136 | } else {
137 | return count + 1
138 | }
139 | }, 0),
140 | delay(retryDelay)
141 | )
142 | )
143 | )
144 | : from(service)
145 | const response$ =
146 | typeof serviceConfig.isSuccess === 'function'
147 | ? service$.pipe(
148 | map(resp => {
149 | if (serviceConfig.isSuccess(resp)) {
150 | return { resp, success: true }
151 | } else {
152 | const error: E = serviceConfig.errorSelector
153 | ? serviceConfig.errorSelector(resp)
154 | : 'error'
155 | return { error }
156 | }
157 | })
158 | )
159 | : service$.pipe(
160 | map(resp => {
161 | return { resp, success: true }
162 | }),
163 | catchError((error: E) => {
164 | return of({
165 | error: serviceConfig.errorSelector
166 | ? serviceConfig.errorSelector(error)
167 | : error
168 | })
169 | })
170 | )
171 |
172 | const [success$, error$]: Observable>[] = partition<
173 | Result
174 | >(
175 | response$.pipe(
176 | tap(() => timer && clearTimeout(timer)),
177 | shareReplay(1)
178 | ),
179 | ({ success }) => success
180 | )
181 |
182 | success$.subscribe({
183 | next: ({ resp }) => {
184 | successNotificate(resp)
185 | store.dispatch({
186 | type: SERVICE_LOADING_END_ACTION,
187 | payload: {
188 | service: serviceName
189 | }
190 | })
191 | }
192 | })
193 |
194 | error$.subscribe({
195 | next: ({ error }) => {
196 | errorNotificate(error)
197 | store.dispatch({
198 | type: SERVICE_ERROR_SET_ACTION,
199 | payload: {
200 | error,
201 | service: serviceName
202 | }
203 | })
204 | }
205 | })
206 |
207 | return [success$, error$]
208 | }
209 | }
210 |
--------------------------------------------------------------------------------
/packages/core/es/index.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
--------------------------------------------------------------------------------
/packages/core/test/model.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * model test
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-14 19:12:12
5 | */
6 | import { Store } from 'redux'
7 | import { mapTo } from 'rxjs/operators'
8 | import { expect } from 'chai'
9 | import * as sinon from 'sinon'
10 | import { init, getSelectors } from '../src'
11 | import Model from '../src/types/Model'
12 |
13 | interface User {
14 | id: number
15 | name: string
16 | age: number
17 | }
18 |
19 | interface Post {
20 | id: number
21 | title: string
22 | content: string
23 | }
24 |
25 | interface Pagination {
26 | page: number
27 | pageSize: number
28 | total: number
29 | }
30 |
31 | interface UserState {
32 | list: User[]
33 | pagination: Pagination
34 | current: number
35 | editingPost: number
36 | }
37 |
38 | interface PostState {
39 | list: Post[]
40 | pagination: Pagination
41 | current: string
42 | }
43 |
44 | const userInitialState = {
45 | list: [],
46 | pagination: {
47 | page: 1,
48 | pageSize: 10,
49 | total: 0
50 | },
51 | current: null,
52 | editingPost: null
53 | }
54 |
55 | const user: Model = {
56 | name: 'user',
57 | state: userInitialState,
58 | reducers: {
59 | fetchSuccess(state, payload) {
60 | const { list, total } = payload
61 | return { ...state, list, pagination: { ...state.pagination, total } }
62 | },
63 | 'post/create': function(state, payload) {
64 | const { id } = payload
65 | return { ...state, editingPost: id }
66 | },
67 | init(state, payload) {
68 | return state
69 | }
70 | },
71 | selectors: {
72 | youngsters: state => {
73 | const { list } = state.user
74 | return list.filter(({ age }) => age < 30)
75 | }
76 | },
77 | flows: {
78 | fetch(flow$) {
79 | return flow$.pipe(
80 | mapTo({
81 | type: 'user/fetchSuccess',
82 | payload: {
83 | list: [
84 | { id: 1, name: 'Messi', age: 31 },
85 | { id: 2, name: 'Neymar', age: 26 },
86 | { id: 3, name: 'Ronaldo', age: 33 }
87 | ],
88 | total: 20
89 | }
90 | })
91 | )
92 | }
93 | }
94 | }
95 |
96 | const post: Model = {
97 | name: 'post',
98 | state: {
99 | list: [],
100 | pagination: {
101 | page: 1,
102 | pageSize: 10,
103 | total: 0
104 | },
105 | current: null
106 | },
107 | reducers: {
108 | init(state, payload) {
109 | return state
110 | }
111 | },
112 | flows: {}
113 | }
114 |
115 | describe('model', () => {
116 | let store: Store
117 |
118 | const initStore = () => {
119 | store = init({
120 | models: { user, post }
121 | })
122 | }
123 |
124 | describe('#state', () => {
125 | beforeEach(initStore)
126 |
127 | it('should have model state', () => {
128 | const state = store.getState()
129 | expect(state).to.deep.include({
130 | user: {
131 | list: [],
132 | pagination: {
133 | page: 1,
134 | pageSize: 10,
135 | total: 0
136 | },
137 | current: null,
138 | editingPost: null
139 | },
140 | post: {
141 | list: [],
142 | pagination: {
143 | page: 1,
144 | pageSize: 10,
145 | total: 0
146 | },
147 | current: null
148 | }
149 | })
150 | })
151 |
152 | it('should have loading state', () => {
153 | const state = store.getState()
154 | expect(state).to.deep.include({
155 | loading: { flows: {}, services: {} }
156 | })
157 | })
158 |
159 | it('should have error state', () => {
160 | const state = store.getState()
161 | expect(state).to.deep.include({
162 | error: { flows: {}, services: {} }
163 | })
164 | })
165 | })
166 |
167 | describe('#reducers', () => {
168 | beforeEach(initStore)
169 |
170 | it('should have custom reducers', () => {
171 | store.dispatch({
172 | type: 'user/fetch'
173 | })
174 | const { list, pagination } = store.getState().user
175 | expect(list).to.have.deep.members([
176 | { id: 1, name: 'Messi', age: 31 },
177 | { id: 2, name: 'Neymar', age: 26 },
178 | { id: 3, name: 'Ronaldo', age: 33 }
179 | ])
180 | expect(pagination.total).to.equal(20)
181 | })
182 |
183 | it('should match model reducer correctly', () => {
184 | const spyUserInit = sinon.spy(user.reducers, 'init')
185 | const spyPostInit = sinon.spy(post.reducers, 'init')
186 | store.dispatch({
187 | type: 'user/init'
188 | })
189 | expect(spyUserInit.calledOnce).to.be.true
190 | expect(spyPostInit.notCalled).to.be.true
191 | })
192 |
193 | it('should support reactive the reducer of others', () => {
194 | store.dispatch({
195 | type: 'post/create',
196 | payload: {
197 | id: 1
198 | }
199 | })
200 | const { editingPost } = store.getState().user
201 | expect(editingPost).to.equal(1)
202 | })
203 |
204 | it('should have change reducer', () => {
205 | store.dispatch({
206 | type: 'user/change',
207 | payload: {
208 | current: 1
209 | }
210 | })
211 | const { current } = store.getState().user
212 | expect(current).to.equal(1)
213 | })
214 |
215 | it('should have patch reducer', () => {
216 | store.dispatch({
217 | type: 'user/patch',
218 | payload: {
219 | pagination: {
220 | total: 10
221 | },
222 | current: 4,
223 | list: [{ id: 1, name: 'Messi', age: 31 }]
224 | }
225 | })
226 | const { user } = store.getState()
227 | expect(user).to.deep.include({
228 | list: [{ id: 1, name: 'Messi', age: 31 }],
229 | pagination: {
230 | page: 1,
231 | pageSize: 10,
232 | total: 10
233 | },
234 | current: 4
235 | })
236 | })
237 |
238 | it('should have reset reducer', () => {
239 | store.dispatch({
240 | type: 'user/patch',
241 | payload: {
242 | pagination: { page: 2 },
243 | current: 4
244 | }
245 | })
246 |
247 | store.dispatch({
248 | type: 'user/reset'
249 | })
250 |
251 | const { user } = store.getState()
252 | expect(user).not.to.equal(userInitialState)
253 | expect(user).to.deep.equal(userInitialState)
254 | })
255 |
256 | it('should support reset partial state', () => {
257 | store.dispatch({
258 | type: 'user/patch',
259 | payload: {
260 | current: 4,
261 | pagination: { page: 2 },
262 | editingPost: 4
263 | }
264 | })
265 |
266 | store.dispatch({
267 | type: 'user/reset',
268 | payload: {
269 | states: ['pagination', 'current']
270 | }
271 | })
272 |
273 | const { user } = store.getState()
274 | expect(user).not.to.equal(userInitialState)
275 | expect(user).to.deep.equal({
276 | list: [],
277 | current: null,
278 | pagination: userInitialState.pagination,
279 | editingPost: 4
280 | })
281 | })
282 | })
283 |
284 | describe('#selectors', () => {
285 | beforeEach(initStore)
286 |
287 | it('should create selectors', () => {
288 | store.dispatch({
289 | type: 'user/fetch'
290 | })
291 | const youngsters = getSelectors('user').youngsters(store.getState())
292 | expect(youngsters).to.deep.equal([{ id: 2, name: 'Neymar', age: 26 }])
293 | })
294 | })
295 | })
296 |
--------------------------------------------------------------------------------
/packages/core/es/index.js:
--------------------------------------------------------------------------------
1 | var __assign = (this && this.__assign) || function () {
2 | __assign = Object.assign || function(t) {
3 | for (var s, i = 1, n = arguments.length; i < n; i++) {
4 | s = arguments[i];
5 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6 | t[p] = s[p];
7 | }
8 | return t;
9 | };
10 | return __assign.apply(this, arguments);
11 | };
12 | /**
13 | * reobservable core
14 | * @author yoyoyohamapi
15 | * @ignore created 2018-08-03 10:24:23
16 | */
17 | import { merge, of, throwError } from 'rxjs';
18 | import { mergeMap, mapTo, catchError, map } from 'rxjs/operators';
19 | import { createEpicMiddleware, ofType, combineEpics } from 'redux-observable';
20 | import { composeWithDevTools } from 'redux-devtools-extension';
21 | import { combineReducers, applyMiddleware, createStore } from 'redux';
22 | import * as mergeWith from 'lodash.mergewith';
23 | import * as cloneDeep from 'lodash.clonedeep';
24 | import * as invariant from 'invariant';
25 | import loadingReducer from './reducers/loading';
26 | import errorReducer from './reducers/error';
27 | import { getPayload, actionSanitizer } from './utils/action';
28 | import end from './operators/end';
29 | import endTo from './operators/endTo';
30 | import createService from './operators/createService';
31 | import { LOADING_START_ACTION, LOADING_END_ACTION, ERROR_SET_ACTION } from './constants/actionTypes';
32 | import { FLOW_END_INDICATOR } from './constants/meta';
33 | import { noop } from './utils/function';
34 | import { LEVEL as NOTIFICATION_LEVEL } from './constants/notification';
35 | import * as Symbols from './constants/symbols';
36 | import { isNil } from './utils/logic';
37 | var selectors = {};
38 | var services = {};
39 | var notification = {
40 | info: noop,
41 | success: noop,
42 | error: noop,
43 | warn: noop
44 | };
45 | var patchWith = function (objValue, srcValue) {
46 | if (Array.isArray(srcValue)) {
47 | return srcValue;
48 | }
49 | };
50 | var composeEnhancers = composeWithDevTools({
51 | actionSanitizer: actionSanitizer
52 | });
53 | export var init = function (config) {
54 | var _a = config.models, models = _a === void 0 ? {} : _a, _b = config.redux, redux = _b === void 0 ? {} : _b;
55 | notification = __assign({}, notification, (config.notification || {}));
56 | var epics = [];
57 | var reducers = {
58 | loading: loadingReducer,
59 | error: errorReducer
60 | };
61 | /**
62 | * 创建 model 的 reducer
63 | * @param {*} mode
64 | */
65 | var createReducer = function (model) {
66 | invariant(!reducers.hasOwnProperty(model.name), "model " + model.name + " has been defined");
67 | reducers[model.name] = function (state, action) {
68 | if (state === void 0) { state = model.state; }
69 | var type = action.type;
70 | var payload = getPayload(action);
71 | switch (type) {
72 | case model.name + "/change": {
73 | return __assign({}, state, payload);
74 | }
75 | case model.name + "/patch": {
76 | return mergeWith(cloneDeep(state), payload, patchWith);
77 | }
78 | case model.name + "/reset": {
79 | if (Array.isArray(payload.states) && payload.states.length) {
80 | return Object.keys(state).reduce(function (newState, key) {
81 | if (payload.states.indexOf(key) > -1) {
82 | newState[key] = cloneDeep(model.state[key]);
83 | }
84 | else {
85 | newState[key] = state[key];
86 | }
87 | return newState;
88 | }, {});
89 | }
90 | else {
91 | return __assign({}, model.state);
92 | }
93 | }
94 | default: {
95 | var _a = type.split('/'), modelName = _a[0], reducerName = _a[1];
96 | if (modelName === model.name && typeof model.reducers[reducerName] === 'function') {
97 | return model.reducers[reducerName](state, payload);
98 | }
99 | if (typeof model.reducers[type] === 'function') {
100 | return model.reducers[type](state, payload);
101 | }
102 | return state;
103 | }
104 | }
105 | };
106 | };
107 | /**
108 | * 创建 epic
109 | * @param actionType
110 | * @param flow
111 | */
112 | var createEpic = function (actionType, flow) {
113 | return function epic(action$, state$, dependencies) {
114 | var flow$ = flow(action$.pipe(ofType(actionType), map(function (action) { return (__assign({}, action, { payload: getPayload(action) })); })), action$, state$, dependencies);
115 | var loadingStart$ = action$.pipe(ofType(actionType), mapTo({
116 | type: LOADING_START_ACTION,
117 | payload: { flow: actionType }
118 | }));
119 | return merge(loadingStart$, flow$.pipe(mergeMap(function (action) {
120 | if (!action) {
121 | return throwError('inner error');
122 | }
123 | if (action[FLOW_END_INDICATOR]) {
124 | var hideAction = {
125 | type: LOADING_END_ACTION,
126 | payload: { flow: actionType }
127 | };
128 | return of(action, hideAction);
129 | }
130 | else {
131 | return of(action);
132 | }
133 | }), catchError(function (error) {
134 | console.error("@reobservable[flow " + actionType + " error]", error);
135 | return of({
136 | type: ERROR_SET_ACTION,
137 | payload: { flow: actionType, error: error }
138 | });
139 | })));
140 | };
141 | };
142 | /**
143 | * 创建 flows
144 | * @param {*} model
145 | */
146 | var createFlows = function (model) {
147 | Object.keys(model.flows).forEach(function (flowName) {
148 | var flow = model.flows[flowName];
149 | var actionType = model.name + "/" + flowName;
150 | epics.push(createEpic(actionType, flow));
151 | });
152 | };
153 | Object.keys(models).forEach(function (key) {
154 | var model = __assign({ reducers: {}, selectors: {}, flows: {}, state: {} }, models[key]);
155 | createReducer(model);
156 | createFlows(model);
157 | selectors[model.name] = model.selectors;
158 | });
159 | // 配置 redux
160 | var epicMiddleware = createEpicMiddleware({
161 | dependencies: {
162 | end: end,
163 | endTo: endTo,
164 | services: services
165 | }
166 | });
167 | var extraMiddleware = redux.middleware
168 | ? Array.isArray(redux.middleware) ? redux.middleware : [redux.middleware]
169 | : [];
170 | var middleware = applyMiddleware.apply(void 0, [epicMiddleware].concat(extraMiddleware));
171 | var rootReducer = combineReducers(reducers);
172 | if (redux.rootReducer) {
173 | rootReducer = redux.rootReducer(rootReducer, reducers);
174 | }
175 | var store = createStore(rootReducer, composeEnhancers(middleware));
176 | // 配置 services
177 | Object.keys(config.services || {}).forEach(function (key) {
178 | var service = config.services[key];
179 | services[key] = createService(notification, service, store);
180 | });
181 | epicMiddleware.run(combineEpics.apply(void 0, epics));
182 | return store;
183 | };
184 | export function getSelectors(model) {
185 | return selectors[model];
186 | }
187 | export function getService(service) {
188 | return services[service];
189 | }
190 | export function notificate(type, message) {
191 | if (!isNil(message)) {
192 | return (notification[type] || notification.info)(message);
193 | }
194 | }
195 | export { NOTIFICATION_LEVEL, Symbols };
196 | //# sourceMappingURL=index.js.map
--------------------------------------------------------------------------------
/packages/core/src/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * reobservable core
3 | * @author yoyoyohamapi
4 | * @ignore created 2018-08-03 10:24:23
5 | */
6 | import { merge, of, throwError } from 'rxjs'
7 | import { mergeMap, mapTo, catchError, map } from 'rxjs/operators'
8 | import {
9 | createEpicMiddleware,
10 | ofType,
11 | combineEpics,
12 | ActionsObservable,
13 | StateObservable
14 | } from 'redux-observable'
15 | import { composeWithDevTools } from 'redux-devtools-extension'
16 | import {
17 | combineReducers,
18 | applyMiddleware,
19 | createStore,
20 | Store,
21 | Middleware,
22 | Reducer
23 | } from 'redux'
24 | import * as mergeWith from 'lodash.mergewith'
25 | import * as cloneDeep from 'lodash.clonedeep'
26 | import * as invariant from 'invariant'
27 |
28 | import loadingReducer from './reducers/loading'
29 | import errorReducer from './reducers/error'
30 | import Model, { Flow, Selectors, Dependencies } from './types/Model'
31 | import { getPayload, actionSanitizer } from './utils/action'
32 | import { Action } from './types/action'
33 | import end from './operators/end'
34 | import endTo from './operators/endTo'
35 | import createService, {
36 | ServiceConfig,
37 | ServiceFunc
38 | } from './operators/createService'
39 | import {
40 | LOADING_START_ACTION,
41 | LOADING_END_ACTION,
42 | ERROR_SET_ACTION
43 | } from './constants/actionTypes'
44 | import { FLOW_END_INDICATOR } from './constants/meta'
45 | import { Notification } from './types/Notification'
46 | import { noop } from './utils/function'
47 | import { LEVEL as NOTIFICATION_LEVEL } from './constants/notification'
48 |
49 | import * as Symbols from './constants/symbols'
50 | import { isNil } from './utils/logic'
51 |
52 | interface Models {
53 | [modelName: string]: Model
54 | }
55 |
56 | interface ReduxConfig {
57 | middleware?: Middleware | [Middleware]
58 | rootReducer?: (
59 | reducer: Reducer,
60 | reducers: { [key: string]: Reducer }
61 | ) => Reducer
62 | }
63 |
64 | interface Config {
65 | models?: Models
66 | redux?: ReduxConfig
67 | notification?: Notification
68 | services?: {
69 | [serviceName: string]: ServiceConfig;
70 | }
71 | }
72 |
73 | export type InitFunc = (config: Config) => Store
74 |
75 | const selectors: { [selectorsName: string]: Selectors } = {}
76 |
77 | const services: {
78 | [stringName: string]: ServiceFunc;
79 | } = {}
80 |
81 | let notification: Notification = {
82 | info: noop,
83 | success: noop,
84 | error: noop,
85 | warn: noop
86 | }
87 |
88 | const patchWith = (objValue, srcValue) => {
89 | if (Array.isArray(srcValue)) {
90 | return srcValue
91 | }
92 | }
93 |
94 | const composeEnhancers = composeWithDevTools({
95 | actionSanitizer
96 | })
97 |
98 | export const init: InitFunc = config => {
99 | const { models = {}, redux = {} } = config
100 | notification = {
101 | ...notification,
102 | ...(config.notification || {})
103 | }
104 |
105 | const epics = []
106 |
107 | const reducers = {
108 | loading: loadingReducer,
109 | error: errorReducer
110 | }
111 |
112 | /**
113 | * 创建 model 的 reducer
114 | * @param {*} mode
115 | */
116 | const createReducer = (model: Model) => {
117 | invariant(
118 | !reducers.hasOwnProperty(model.name),
119 | `model ${model.name} has been defined`
120 | )
121 | reducers[model.name] = function(state = model.state, action: Action) {
122 | const { type } = action
123 | const payload = getPayload(action)
124 | switch (type) {
125 | case `${model.name}/change`: {
126 | return { ...state, ...payload }
127 | }
128 | case `${model.name}/patch`: {
129 | return mergeWith(cloneDeep(state), payload, patchWith)
130 | }
131 | case `${model.name}/reset`: {
132 | if (Array.isArray(payload.states) && payload.states.length) {
133 | return Object.keys(state).reduce((newState, key) => {
134 | if (payload.states.indexOf(key) > -1) {
135 | newState[key] = cloneDeep(model.state[key])
136 | } else {
137 | newState[key] = state[key]
138 | }
139 | return newState
140 | }, {})
141 | } else {
142 | return { ...model.state }
143 | }
144 | }
145 | default: {
146 | const [modelName, reducerName] = type.split('/')
147 |
148 | if (
149 | modelName === model.name &&
150 | typeof model.reducers[reducerName] === 'function'
151 | ) {
152 | return model.reducers[reducerName](state, payload)
153 | }
154 |
155 | if (typeof model.reducers[type] === 'function') {
156 | return model.reducers[type](state, payload)
157 | }
158 |
159 | return state
160 | }
161 | }
162 | }
163 | }
164 |
165 | /**
166 | * 创建 epic
167 | * @param actionType
168 | * @param flow
169 | */
170 | const createEpic = (actionType: string, flow: Flow) => {
171 | return function epic(
172 | action$: ActionsObservable,
173 | state$: StateObservable,
174 | dependencies: Dependencies
175 | ) {
176 | const flow$ = flow(
177 | action$.pipe(
178 | ofType(actionType),
179 | map(action => ({ ...action, payload: getPayload(action) }))
180 | ),
181 | action$,
182 | state$,
183 | dependencies
184 | )
185 |
186 | const loadingStart$ = action$.pipe(
187 | ofType(actionType),
188 | mapTo({
189 | type: LOADING_START_ACTION,
190 | payload: { flow: actionType }
191 | })
192 | )
193 |
194 | return merge(
195 | loadingStart$,
196 | flow$.pipe(
197 | mergeMap(action => {
198 | if (!action) {
199 | return throwError('inner error')
200 | }
201 | if (action[FLOW_END_INDICATOR]) {
202 | const hideAction = {
203 | type: LOADING_END_ACTION,
204 | payload: { flow: actionType }
205 | }
206 | return of(action, hideAction)
207 | } else {
208 | return of(action)
209 | }
210 | }),
211 | catchError(error => {
212 | console.error(`@reobservable[flow ${actionType} error]`, error)
213 | return of({
214 | type: ERROR_SET_ACTION,
215 | payload: { flow: actionType, error: error }
216 | })
217 | })
218 | )
219 | )
220 | }
221 | }
222 |
223 | /**
224 | * 创建 flows
225 | * @param {*} model
226 | */
227 | const createFlows = (model: Model) => {
228 | Object.keys(model.flows).forEach(flowName => {
229 | const flow = model.flows[flowName]
230 | const actionType = `${model.name}/${flowName}`
231 | epics.push(createEpic(actionType, flow))
232 | })
233 | }
234 |
235 | Object.keys(models).forEach(key => {
236 | const model = {
237 | reducers: {},
238 | selectors: {},
239 | flows: {},
240 | state: {},
241 | ...models[key]
242 | }
243 | createReducer(model)
244 | createFlows(model)
245 | selectors[model.name] = model.selectors
246 | })
247 |
248 | // 配置 redux
249 | const epicMiddleware = createEpicMiddleware({
250 | dependencies: {
251 | end,
252 | endTo,
253 | services
254 | }
255 | })
256 |
257 | const extraMiddleware = redux.middleware
258 | ? Array.isArray(redux.middleware)
259 | ? redux.middleware
260 | : [redux.middleware]
261 | : []
262 |
263 | const middleware = applyMiddleware(epicMiddleware, ...extraMiddleware)
264 |
265 | let rootReducer = combineReducers(reducers)
266 | if (redux.rootReducer) {
267 | rootReducer = redux.rootReducer(rootReducer, reducers)
268 | }
269 |
270 | const store = createStore(rootReducer, composeEnhancers(middleware))
271 |
272 | // 配置 services
273 | Object.keys(config.services || {}).forEach(key => {
274 | const service = config.services[key]
275 | services[key] = createService(notification, service, store)
276 | })
277 |
278 | epicMiddleware.run(combineEpics(...epics))
279 |
280 | return store
281 | }
282 |
283 | export function getSelectors(model: string) {
284 | return selectors[model]
285 | }
286 |
287 | export function getService(service: string) {
288 | return services[service]
289 | }
290 |
291 | export function notificate(type: string, message: any) {
292 | if (!isNil(message)) {
293 | return (notification[type] || notification.info)(message)
294 | }
295 | }
296 |
297 | export { NOTIFICATION_LEVEL, Symbols }
298 |
--------------------------------------------------------------------------------