Skip to content

Commit

Permalink
## v3.2.0 2021-03-11
Browse files Browse the repository at this point in the history
* [bug] 修复 api useMiddleWare 复用了 api createAgentReducer 的 MiddleWare 的问题。
* [design] 在过去, applyMiddleWares 使用的是方法级统一的缓存 cache ,
这使得 MiddleWare 的缓存之间可能存在缓存数据共享问题,自当前版本开始
 applyMiddleWares 会为每个 MiddleWare 开辟了独立的缓存空间 cache ,
 各个 MiddleWare 之间将不再有缓存共享问题。
* [design]  `MiddleWares.takeNone()`作为当前版本新加入的 MiddleWare 可以阻止任何 state 改变。
* [feature] `MiddleWare`的覆盖优先级已经变更为:
`useMiddleWare` -> `middleWare` -> `createAgentReducer`。
* [feature] 使用相同实例模型的`Agent`代理对象之间数据更新同步。
  • Loading branch information
wangyi committed Mar 11, 2021
1 parent 01a5cd7 commit 7b61ec7
Show file tree
Hide file tree
Showing 54 changed files with 1,316 additions and 687 deletions.
6 changes: 6 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.eslintrc.js
test/
jest.config.js
babel.config.js
index.js
webpack.config.js
43 changes: 43 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
module.exports = {
env: {
"browser": true,
"es6": true
},
extends: [
'airbnb-base',
'plugin:@typescript-eslint/recommended',
// 'plugin:@typescript-eslint/recommended-requiring-type-checking'
],
globals: {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly",
"self": "readonly"
},
parser: "@typescript-eslint/parser",
parserOptions: {
"ecmaVersion": 2019,
"sourceType": 'module',
tsconfigRootDir: '.',
project: ['./tsconfig.json'],
},
plugins: ['@typescript-eslint'],

settings: {
'import/parsers': {
'@typescript-eslint/parser': ['.ts']
},
'import/resolver': {
'node': {
"extensions": [".js", ".jsx", ".ts", ".tsx"]
}

}
},
rules: {
"import/extensions": "off",
"no-param-reassign": "off",
"no-use-before-define": ["error", { "functions": false, "classes": true }],
"@typescript-eslint/no-explicit-any":"off",
"@typescript-eslint/no-unused-vars": ["off"]
}
};
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules/
/dist
/libs
/package-lock.json
/index.d.ts
/.idea/
/coverage/
26 changes: 15 additions & 11 deletions CHANGE_LOG.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
## v3.1.1 2020-12-17
## v3.1.2 2021-02-28

* [bug] 已修复 reducer.update 方法中当 state 为 undefined 时,
外部 store state 与 agent state 不同步的问题。
* [bug] fix `MiddleWares.takeDebounce` problem.
* [bug] fix `globalConfig` problem. If a global object can not be found, `globalConfig` should not work.
* [feature] add `nextExperience` in `env` config for experience next version features.
`MiddleWare` override order in next version: `useMiddleWare` -> `middleWare` -> `createAgentReducer`.
* [document] update document structure.
* [document] increase english language support.
* [unit test] update unit test structure.

## v3.1.2 2021-02-28
## v3.2.0 2021-03-11

* [bug] 使用常规 debounce 代码修复原`MiddleWares.takeDebounce`因事件堆积导致的不稳定问题。
* [bug] 修复了`globalConfig`在无法获取到`window,global,self`情况下报错的问题。
* [feature] 新增`nextExperience`下一版本体验特性,并在`env`中增加了相应开启配置项。
`MiddleWare`的覆盖优先级将在`nextExperience`环境中变更为:`useMiddleWare` -> `middleWare` -> `createAgentReducer`
* [document] 更改了文档结构以方便分步阅读。
* [document] 新增了英文文档。
* [unit test] 根据文档变更,新增了中英文分离的单元测试案例。
* [bug] fix `useMiddleWare` reuse the MiddleWare from `createAgentReducer` problem.
* [design] `applyMiddleWares` reassign `runtime.cache` for each `MiddleWare`,
in this version, `runtime.cache` used in MiddleWare is independent.
* [design] `MiddleWares.takeNone()` is a new MiddleWare, this MiddleWare can stop any state change.
* [feature] `MiddleWare` override order in current version: `useMiddleWare` -> `middleWare` -> `createAgentReducer`.
* [feature] `Agent` objects based on the same object mode model updates state synchronously.
26 changes: 26 additions & 0 deletions CHANGE_LOG_ZH.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
## v3.1.1 2020-12-17

* [bug] 已修复 reducer.update 方法中当 state 为 undefined 时,
外部 store state 与 agent state 不同步的问题。

## v3.1.2 2021-02-28

* [bug] 使用常规 debounce 代码修复原`MiddleWares.takeDebounce`因事件堆积导致的不稳定问题。
* [bug] 修复了`globalConfig`在无法获取到`window,global,self`情况下报错的问题。
* [feature] 新增`nextExperience`下一版本体验特性,并在`env`中增加了相应开启配置项。
`MiddleWare`的覆盖优先级将在`nextExperience`环境中变更为:`useMiddleWare` -> `middleWare` -> `createAgentReducer`
* [document] 更改了文档结构以方便分步阅读。
* [document] 新增了英文文档。
* [unit test] 根据文档变更,新增了中英文分离的单元测试案例。

## v3.2.0 2021-03-11

* [bug] 修复 api useMiddleWare 复用了 api createAgentReducer 的 MiddleWare 的问题。
* [design] 在过去, applyMiddleWares 使用的是方法级统一的缓存 cache ,
这使得 MiddleWare 的缓存之间可能存在缓存数据共享问题,自当前版本开始
applyMiddleWares 会为每个 MiddleWare 开辟了独立的缓存空间 cache ,
各个 MiddleWare 之间将不再有缓存共享问题。
* [design] `MiddleWares.takeNone()`作为当前版本新加入的 MiddleWare 可以阻止任何 state 改变。
* [feature] `MiddleWare`的覆盖优先级已经变更为:
`useMiddleWare` -> `middleWare` -> `createAgentReducer`
* [feature] 使用相同实例模型的`Agent`代理对象之间数据更新同步。
10 changes: 10 additions & 0 deletions documents/en/api/middle_wares.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

MiddleWares is a class which contains some static MiddleWare properties. It is a set of often using MiddleWare.

## MiddleWares.takeNone()

This MiddleWare can stop any state change.

```typescript
class MiddleWares{
static takeNone(): MiddleWare
}
```

## MiddleWares.takePromiseResolve()

This MiddleWare helps for waiting a promise object which is returned by an `Agent` method or passed from a previous MiddleWare, and passes the promise resolved data to next process. This is a very common MiddleWare, and it just process a promise object, if the object passed in is not a promise, it will be passed to a next process immediately.
Expand Down
2 changes: 1 addition & 1 deletion documents/en/api/not_recommend.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ The state processing MiddleWares can not work with `MiddleActions`, but method c

2 . useMiddleActions(middleActions, ...otherParams)

This function can build an `Agent` use helper. It returns a `MiddleActions` instance which is a proxy object. You can use `method control MiddleWares` on this proxy object.
This function can build an `Agent` using helper. It returns a `MiddleActions` instance which is a proxy object. You can use `method control MiddleWares` on this proxy object.

```typescript
type MiddleActionsInterface<T>={agent:T,[key:string]:any};
Expand Down
2 changes: 1 addition & 1 deletion documents/en/guides/about_env.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,4 @@ You can check out the code about how to set env, [here](https://github.com/filef

Be careful in `env.strict`, you'd better not set it to be `false`. For it will make the state difference between `Agent` and your reducer tool.

Go to [next section](https://github.com/filefoxper/agent-reducer/blob/master/documents/en/guides/not_recommend.md), and know some bad design in `agent-reducer` which are not recommend to use.
Go to [next section](https://github.com/filefoxper/agent-reducer/blob/master/documents/en/guides/model_agent_relationship.md) to learn relationship of `Agent` and `OriginAgent`.
16 changes: 3 additions & 13 deletions documents/en/guides/about_middle_ware_override.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
There are three api functions for you to set MiddleWares.

1. `createAgentReducer`, it is a basic api in `agent-reducer`, you can set MiddleWares like: `createAgentReducer( OriginAgent, MiddleWare )`.
2. `useMiddleWare`, this api will copy an `Agent` object, and override MiddleWares of `Agent` object on the copy one. It is used like: `useMiddleWare( Agent, MiddleWare )`.
3. `middleWare`, it can add MiddleWare directly on an `OriginAgent` method, and when the `Agent` method is called, it can uses this MiddleWare on its origin one. These MiddleWares will override MiddleWares which are added by `createAgentReducer` or `useMiddleWare`.

Attention: MiddleWares added by api `middleWare` has a highest running prior level in this version, but it is not reasonable, and we will change the running prior level between `useMiddleWare` and `middleWare` at version 3.2.0. You can set `env.nextExperience` to be `true` for experience.
2. `middleWare`, it can add MiddleWare directly on an `OriginAgent` method, and when the `Agent` method is called, it can uses this MiddleWare on its origin one. These MiddleWares will override MiddleWares which are added by `createAgentReducer`.
3. `useMiddleWare`, this api will copy an `Agent` object, and override MiddleWares of `Agent` object on the copy one. It is used like: `useMiddleWare( Agent, MiddleWare )`. The MiddleWare additions from this api has a highest running prior level in this version. So, in the copied `Agent` object, its MiddleWare can override MiddleWares which are added by `createAgentReducer` and `middleWare`.

You can check code in [middleWare.override.spec.ts](https://github.com/filefoxper/agent-reducer/blob/master/test/en/guides/middleWare.override.spec.ts).

Expand Down Expand Up @@ -85,18 +83,10 @@ describe('use different middleWare api', () => {
expect(agent.state).toEqual({id: 0, name: 'name'});
});

it("MiddleWare from api 'middleWare' will override MiddleWare from api 'useMiddleWare' in current version", async () => {
it("MiddleWare from api 'useMiddleWare' will override MiddleWare from api 'middleWare' in current version", async () => {
const {agent} = createAgentReducer(MiddleWareOverrideModel, MiddleWares.takePromiseResolve());
const branch = useMiddleWare(agent, MiddleWarePresets.takePromiseResolveAssignable());
await branch.changeByPromiseResolve('name');
expect(agent.state).toEqual({name: 'name'});
expect(agent.state.id).toBeUndefined();
});

it("MiddleWare from api 'useMiddleWare' will override MiddleWare from api 'middleWare' in next version: 3.2.0", async () => {
const {agent} = createAgentReducer(MiddleWareOverrideModel, MiddleWares.takePromiseResolve(), {nextExperience: true});
const branch = useMiddleWare(agent, MiddleWarePresets.takePromiseResolveAssignable());
await branch.changeByPromiseResolve('name');
expect(agent.state).toEqual({id: 0, name: 'name'});
});

Expand Down
81 changes: 81 additions & 0 deletions documents/en/guides/model_agent_relationship.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Relationship of Agent and OriginAgent

`OriginAgent` is an action model, it provides state and methods for simulating a reducer function. `Agent` is a proxy object of `OriginAgent`, it can be generated from `OriginAgent` by using API createAgentReducer. The model instance and its `Agent` object shares a same state.

Before `agent-reducer@3.2.0`, state updating between `Agent` objects from the same model instance is not synchronous, that may cause problems if you are sharing one model instance between some different `Agent` objects. Now, the state updating between `Agent` objects from same model instance is synchronous. If you call a method from one `Agent` object, and change its state, other `Agent` objects from the same model instance will update state too.

You can check code in [state.sync.spec.ts](https://github.com/filefoxper/agent-reducer/blob/master/test/en/guides/state.sync.spec.ts).

```typescript
import {Action, createAgentReducer, OriginAgent, Reducer} from "agent-reducer";

describe("state updating between different Agents with same model instance", () => {

// simulate a simple redux
function createStore<S>(reducer: Reducer<S, Action>, initialState: S) {
let listener: undefined | (() => any) = undefined;
let state = initialState;
return {
dispatch(action: Action) {
state = reducer(state, action);
if (listener) {
listener();
}
},
getState(): S {
return state;
},
subscribe(l: () => any) {
listener = l;
l();
return () => {
listener = undefined;
};
},
};
}

class Model implements OriginAgent<number> {

state = 0;

increase() {
return this.state + 1;
}

}

const model = new Model();

it("Agents from same model instance update state synchronously", () => {
const reducer1 = createAgentReducer(model, {updateBy: 'manual'});
const {agent: agent1, update: update1} = reducer1;
// create first store
const store1 = createStore(reducer1, agent1.state);
store1.subscribe(() => {
update1(store1.getState(), store1.dispatch);
});

const reducer2 = createAgentReducer(model, {updateBy: 'manual'});
const {agent: agent2, update: update2} = reducer2;
// create second store
const store2 = createStore(reducer2, agent2.state);
store2.subscribe(() => {
update2(store2.getState(), store2.dispatch);
});

// just invoke first agent
agent1.increase();
expect(agent1.state).toBe(store1.getState());
// agent1 and agent2 shares state,
// for they have a same model instance
expect(agent2.state).toBe(agent1.state);
// though we did not dispatch any thing to store2,
// but agent2 can listen its brothers changing,
// and dispatch the changing state to store2
expect(agent2.state).toBe(store2.getState());
});

});
```
Go to [next section](https://github.com/filefoxper/agent-reducer/blob/master/documents/en/guides/not_recommend.md), and know some bad design in `agent-reducer` which are not recommend to use.
1 change: 1 addition & 0 deletions documents/en/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
* [about runtime](https://github.com/filefoxper/agent-reducer/blob/master/documents/en/guides/about_runtime.md)
* [about middleWare override](https://github.com/filefoxper/agent-reducer/blob/master/documents/en/guides/about_middle_ware_override.md)
* [about env](https://github.com/filefoxper/agent-reducer/blob/master/documents/en/guides/about_env.md)
* [relationship of Agent and OriginAgent](https://github.com/filefoxper/agent-reducer/blob/master/documents/en/guides/model_agent_relationship.md)
* [not recommend](https://github.com/filefoxper/agent-reducer/blob/master/documents/en/guides/not_recommend.md)

## API Reference
Expand Down
4 changes: 4 additions & 0 deletions documents/en/introduction/concept.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
2. `method` is a function for producing a `next state`.

for example:

```typescript
import {OriginAgent} from 'agent-reducer';

Expand Down Expand Up @@ -84,6 +85,7 @@ const agent=reducer.agent;
6. `recordStateChanges`: this function is designed for unit test. It records state change histories when you used it in your unit test.

using AgentReducer properties example:

```typescript
import {OriginAgent,createAgentReducer} from 'agent-reducer';

Expand Down Expand Up @@ -158,6 +160,7 @@ It is a little different with redux MiddleWare, the MiddleWare ecosystem in `age
MiddleWares are functions, they can be chained together. When MiddleWare accept a data from its previous one, it process this data, and pass a processed result to its next one.

MiddleWare structure looks like:

```typescript
const MiddleWare = <T>(runtime: Runtime<T>):NextProcess | void =>{
// this function is called before current method calls.
Expand Down Expand Up @@ -190,6 +193,7 @@ const MiddleWare = <T>(runtime: Runtime<T>):NextProcess | void =>{

};
```

If you want to know how to chain `MiddleWares` together, and how the chained `MiddleWare` work with system, [see the guides about middle ware](https://github.com/filefoxper/agent-reducer/blob/master/documents/en/guides/about_middle_ware.md).

[next to installation](https://github.com/filefoxper/agent-reducer/blob/master/documents/en/introduction/installation.md)
15 changes: 13 additions & 2 deletions documents/en/introduction/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ npm i use-redux-agent
OriginAgent is a basic model for generating `reducer function` and `agent` instance. It can be an object or a class.

object model:

```typescript
import {OriginAgent} from 'agent-reducer';
import {OriginAgent,createAgentReducer} from 'agent-reducer';

interface State{
name?:string,
Expand All @@ -52,9 +53,11 @@ const model:Model={

const agentReducer = createAgentReducer(model);
```

class model:

```typescript
import {OriginAgent} from 'agent-reducer';
import {OriginAgent,createAgentReducer} from 'agent-reducer';

interface State{
name?:string,
Expand All @@ -78,13 +81,15 @@ class Model implements OriginAgent<State>{

const agentReducer = createAgentReducer(new Model());
```

We recommend using class model.

## AgentReducer

`AgentReducer` is a reducer function generated by API createAgentReducer. `AgentReducer` has some useful properties, and the most important property `agent` is widely used in `agent-reducer` system. It is a proxy object for your `OriginAgent model`, and only call the methods from `agent` directly can change state.

create an `AgentReducer`:

``` typescript
import {OriginAgent,createAgentReducer} from 'agent-reducer';

Expand Down Expand Up @@ -124,6 +129,7 @@ const name = agent.state.name; // 'primary.secondary'
MiddleWare system makes `agent` more flexible. You can use MiddleWare to reproduce the `next state` when it is returned by a agent method, or control `agent` lifecycle (disable an `agent` changing state action).

promise MiddleWare:

``` typescript
import {OriginAgent,createAgentReducer,MiddleWarePresets} from 'agent-reducer';

Expand Down Expand Up @@ -161,7 +167,9 @@ await agent.fetchName();

const name = agent.state.name; // 'primary.secondary'
```

If you are using `Babel decorator plugin`, you can use `MiddleWarePresets.takePromiseResolve()` more simple.

``` typescript
import {OriginAgent,createAgentReducer,middleWare,MiddleWarePresets} from 'agent-reducer';

Expand Down Expand Up @@ -201,7 +209,9 @@ await agent.fetchName();

const name = agent.state.name; // 'primary.secondary'
```

If you want to process a promise resolved data and merge it with `this.state`, you can use `MiddleWarePresets.takePromiseResolveAssignable`.

``` typescript
import {OriginAgent,createAgentReducer,middleWare,MiddleWarePresets} from 'agent-reducer';

Expand Down Expand Up @@ -238,6 +248,7 @@ await agent.fetchName();

const name = agent.state.name; // 'primary.secondary'
```

One MiddleWare represent one feature, and chain together, their features will be token one by one, such as `MiddleWarePresets.takePromiseResolveAssignable()`, it is chained by `MiddleWares.takePromiseResolve()`,`MiddleWares.takeAssignable()`. We will learn how to chain them together and how to write a `MiddleWare` later.

[next to tutorial](https://github.com/filefoxper/agent-reducer/blob/master/documents/en/tutorial/intro.md)
10 changes: 10 additions & 0 deletions documents/zh/api/middle_wares.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

MiddleWares 是一个用于存储多个常用 MiddleWare 的 class 集合。

## MiddleWares.takeNone()

这个 MiddleWare 可以拦截所有的 state 变化。

```typescript
class MiddleWares{
static takeNone(): MiddleWare
}
```

## MiddleWares.takePromiseResolve()

当前 MiddleWare 用于处理 promise 返回值,它将 promise resolve 的数据传递给下一个 MiddleWare 数据处理器。如果待处理数据并非 promise ,该 MiddleWare 会跳过处理,直接将数据透传给下一个 MiddleWare。
Expand Down
Loading

0 comments on commit 7b61ec7

Please sign in to comment.