Skip to content

Commit

Permalink
# 4.3.1 2022-04-12
Browse files Browse the repository at this point in the history
* [design] add [avatar](/experience?id=avatar-experience-1) API.
  • Loading branch information
wangyi committed Apr 12, 2022
1 parent 5d070d2 commit bc38e47
Show file tree
Hide file tree
Showing 14 changed files with 729 additions and 16 deletions.
6 changes: 5 additions & 1 deletion docs/changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,4 +180,8 @@ in this version, `runtime.cache` used in MiddleWare is independent.
# 4.3.0 2022-04-06

* [design] add [flow](/experience?id=flow-experience) and [effect](/experience?id=effect-decorator-experience).
* [design] add experience, set `process.env.AGENT_REDUCER_EXPERIENCE` to `OPEN`, can use the experience features.
* [design] add experience, set `process.env.AGENT_REDUCER_EXPERIENCE` to `OPEN`, can use the experience features.

# 4.3.1 2022-04-12

* [design] add [avatar](/experience?id=avatar-experience-1) API.
235 changes: 234 additions & 1 deletion docs/experience.md
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,217 @@ describe('subscribe error',()=>{

```

### avatar (experience)

Flow can do many thing to compose requests and state change methods together as a work flow. Sometimes, we even want to use the interface functions from platform, but, use these interfaces directly in model is not a good idea. That make model difficult to move to other platforms.

Now, we add a new API `avatar` to resolve this problem. `Avatar` use the `Algebraic Effects` mode to fix it. You can describe a simple `interfaces object` to replace the functions from platform, and implements these functions before using in the platform codes.

When the method of `avatar(interfaces).current` is used, it always finds the if the function is implemented, and use the implement one as possible as it can, if the function is not exist in the implement one, it use the relative function from `interfaces object` for a replace.

How to use a global avatar?

```typescript
import {
Flows,
flow,
create,
effect,
experience,
avatar,
Model
} from "agent-reducer";

describe('how to use global avatar', () => {

type User = {
id: number,
name: string
};

type UserListState = {
source: User[] | null,
loading: boolean,
}

const dataSource: User[] = [
{id: 1, name: 'Jimmy'},
{id: 2, name: 'Jacky'},
{id: 3, name: 'Lucy'},
{id: 4, name: 'Lily'},
{id: 5, name: 'Nike'},
];

const prompt = avatar({
success:(info:string)=>undefined,
error:(e:any)=>undefined
});

class UserListModel implements Model<UserListState> {

state: UserListState = {
source: [],
loading: false,
};

private load() {
return {...this.state, loading: true};
}

private changeSource(source: User[] | null) {
return {...this.state, source};
}

private unload() {
return {...this.state, loading: false};
}

@flow(Flows.latest())
async loadSource() {
this.load();
try {
const source: User[] = await new Promise((resolve) => {
resolve([...dataSource]);
});
this.changeSource(source);
// use prompt.current.success to popup a message `fetch success`
prompt.current.success('fetch success!');
} catch (e) {
// use prompt.current.error to popup a error message
prompt.current.error(e);
}finally {
this.unload();
}
}

}

test('if you want to call outside effect function in model, you can use API `avatar`', async () => {
const success = jest.fn().mockImplementation((info:string)=>console.log(info));
// implement the interfaces of prompt avatar
const destroy = prompt.implement({
success,
});
const {agent, connect, disconnect} = create(UserListModel);
connect();
await agent.loadSource();
expect(success).toBeCalledTimes(1);
disconnect();
// if you do not need this avatar,
// please destroy it finally
destroy();
});

});
```

How to use avatar for different model instances.

```typescript
import {
Flows,
flow,
create,
effect,
experience,
avatar,
Model
} from "agent-reducer";

describe('how to use model avatar', () => {

type User = {
id: number,
name: string
};

type UserListState = {
source: User[] | null,
list: User[],
filterName: string,
loading: boolean,
}

const dataSource: User[] = [
{id: 1, name: 'Jimmy'},
{id: 2, name: 'Jacky'},
{id: 3, name: 'Lucy'},
{id: 4, name: 'Lily'},
{id: 5, name: 'Nike'},
];

class UserListModel implements Model<UserListState> {

state: UserListState = {
source: [],
list: [],
filterName: '',
loading: false,
};

prompt = avatar({
success:(info:string)=>undefined,
error:(e:any)=>undefined
});

private load() {
return {...this.state, loading: true};
}

private changeSource(source: User[] | null) {
return {...this.state,source}
}

private unload() {
return {...this.state, loading: false};
}

@flow(Flows.latest())
async loadSource() {
this.load();
try {
const source: User[] = await new Promise((resolve) => {
resolve([...dataSource]);
});
this.changeSource(source);
// use prompt.current.success to popup a message `fetch success`
this.prompt.current.success('fetch success!');
} catch (e) {
// use prompt.current.error to popup a error message
this.prompt.current.error(e);
}finally {
this.unload();
}
}

}

test('If you want to use `avatar` in model, please build avatar inside model', async () => {
const success = jest.fn().mockImplementation((info:string)=>console.log(info));

const {agent, connect, disconnect} = create(UserListModel);
const {agent:another, connect:anotherConnect, disconnect:anotherDisconnect} = create(UserListModel);
// implement avatar for different models
const destroy = agent.prompt.implement({
success,
});
connect();
anotherConnect();
await agent.loadSource();
await another.loadSource();
// the agent.prompt is implemented with avatar,
// the another one is not.
expect(success).toBeCalledTimes(1);
disconnect();
anotherDisconnect();
// if you do not need this avatar,
// please destroy it finally
destroy();
});

});
```

### Effect decorator (experience)

If you want to add effect inside model, and start it after this model is connected, you can use api [effect](/experience?id=effect-experience) to decorate a model method to be a effect callback. If you pass `*` into [effect](/experience?id=effect-experience) decorator, it will take all the methods of current model instance as the listening target. If you pass a callback which returns a method of current model into [effect](/experience?id=effect-experience) decorator as a param, it will only listen to the state changes leaded by this specific `method`.
Expand Down Expand Up @@ -746,4 +957,26 @@ export declare function effect<S=any, T extends Model<S>=Model>(
):MethodDecoratorCaller
```

* method - optional, a callback which returns `Class.prototype.method` as the target method for state change listening.
* method - optional, a callback which returns `Class.prototype.method` as the target method for state change listening.

### avatar (experience)

Create an avatar object for outside interfaces.

```typescript
export type Avatar<T extends Record<string, any>> = {
current:T,
implement:(impl:Partial<T>)=>()=>void;
};
export declare function avatar<
T extends Record<string, unknown>
>(interfaces:T):Avatar<T>;
```

* interfaces - provide a default simulate `interfaces object`, if the used function from implements is not exist, it will provide the right function for a replace.

return Avatar object:

* current - the current working interfaces object. If the used function from implements is not exist, it will take the right function from `interfaces object` for a replace.
* implement - callback, accept a implement object for `interfaces object`.
6 changes: 5 additions & 1 deletion docs/zh/changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,4 +188,8 @@
# 4.3.0 2022-04-06

* [design] 添加 [flow](/zh/experience?id=工作流-体验)[effect decorator](/zh/experience?id=副作用-decorator-装饰器用法-体验).
* [design] 添加 experience 体验模式,设置 `process.env.AGENT_REDUCER_EXPERIENCE``OPEN`, 可体验最新特性和 API.
* [design] 添加 experience 体验模式,设置 `process.env.AGENT_REDUCER_EXPERIENCE``OPEN`, 可体验最新特性和 API.

# 4.3.1 2022-04-12

* [design] 添加 [avatar](/zh/experience?id=avatar-体验) API.

0 comments on commit bc38e47

Please sign in to comment.