feat: add new logic for stream api (#76)
This commit is contained in:
@ -1,20 +1,33 @@
|
|||||||
import {fromPromise} from '@most/core';
|
import {failure, pending, RemoteData, success} from '@devexperts/remote-data-ts';
|
||||||
|
import {fromPromise, startWith, recoverWith, map, now} from '@most/core';
|
||||||
import {Stream} from '@most/types';
|
import {Stream} from '@most/types';
|
||||||
|
import {pipe} from 'fp-ts/pipeable';
|
||||||
import {objectEntries} from './objectEntries';
|
import {objectEntries} from './objectEntries';
|
||||||
|
|
||||||
type PromiseApi = Record<string, (...args: any[]) => Promise<unknown>>;
|
type PromiseApi = Record<string, (...args: any[]) => Promise<unknown>>;
|
||||||
|
|
||||||
type StreamApi<T extends PromiseApi> = {
|
type StreamApi<T extends PromiseApi, E> = {
|
||||||
[K in keyof T]: (...params: Parameters<T[K]>) => (
|
[K in keyof T]: (
|
||||||
T[K] extends (...args: any[]) => Promise<infer R> ? Stream<R> : never
|
...params: Parameters<T[K]>
|
||||||
);
|
) => T[K] extends (...args: any[]) => Promise<infer R> ? Stream<RemoteData<E, R>> : never;
|
||||||
};
|
};
|
||||||
|
const pipeApiStream = <T>(stream$: Stream<T>) =>
|
||||||
|
pipe(
|
||||||
|
stream$,
|
||||||
|
map(val => success(val)),
|
||||||
|
startWith(pending),
|
||||||
|
recoverWith(err => now(failure(err)))
|
||||||
|
);
|
||||||
|
|
||||||
export const makeApi = <T extends PromiseApi>(apiObj: T) => {
|
export const makeApi = <T extends PromiseApi, E = Error>(apiObj: T) => {
|
||||||
return objectEntries(apiObj).reduce((streamObj, [apiKey, apiMethod]) => {
|
return objectEntries(apiObj).reduce((streamObj, [apiKey, apiMethod]) => {
|
||||||
return {
|
return {
|
||||||
...streamObj,
|
...streamObj,
|
||||||
[apiKey]: (...args: Parameters<typeof apiMethod>) => fromPromise(apiMethod(...args)),
|
[apiKey]: (...args: Parameters<typeof apiMethod>) => {
|
||||||
|
const res = fromPromise(apiMethod(...args));
|
||||||
|
|
||||||
|
return pipeApiStream(res);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}, {} as StreamApi<T>);
|
}, {} as StreamApi<T, E>);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import {Sink, Stream} from '@most/types';
|
|||||||
import {noop} from 'lodash';
|
import {noop} from 'lodash';
|
||||||
import {useEffect, useState} from 'react';
|
import {useEffect, useState} from 'react';
|
||||||
import {newDefaultScheduler} from '@most/scheduler';
|
import {newDefaultScheduler} from '@most/scheduler';
|
||||||
|
import {pending, RemoteData} from '@devexperts/remote-data-ts';
|
||||||
|
|
||||||
export function useStream<T extends Array<unknown>, R>(
|
export function useStream<T extends Array<unknown>, R>(
|
||||||
piping: () => Stream<R>,
|
piping: () => Stream<R>,
|
||||||
@ -31,3 +32,32 @@ export function useStream<T extends Array<unknown>, R>(
|
|||||||
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useStreamRD<T extends Array<unknown>, R, E = Error>(
|
||||||
|
piping: () => Stream<RemoteData<E, R>>,
|
||||||
|
props: T,
|
||||||
|
) {
|
||||||
|
const [state, setState] = useState<RemoteData<E, R>>(pending);
|
||||||
|
useEffect(() => {
|
||||||
|
setState(pending);
|
||||||
|
// eslint-disable-next-line
|
||||||
|
}, props);
|
||||||
|
useEffect(() => {
|
||||||
|
const effect$ = piping();
|
||||||
|
const sink: Sink<RemoteData<E, R>> = {
|
||||||
|
event: (_, val) => {
|
||||||
|
setState(val);
|
||||||
|
},
|
||||||
|
end: noop,
|
||||||
|
error: noop
|
||||||
|
};
|
||||||
|
const unsub = effect$.run(sink, newDefaultScheduler());
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unsub.dispose();
|
||||||
|
};
|
||||||
|
// eslint-disable-next-line
|
||||||
|
}, props);
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,22 +1,35 @@
|
|||||||
import {combine} from '@most/core';
|
import {combine} from '@most/core';
|
||||||
|
import {combine as combineRD, map} from '@devexperts/remote-data-ts';
|
||||||
|
import {pipe} from 'fp-ts/lib/function';
|
||||||
import React, {Fragment, memo} from 'react';
|
import React, {Fragment, memo} from 'react';
|
||||||
import {isNotEmpty} from '_referers/common';
|
|
||||||
import {makeTreeList} from '_utils/makeTreeList';
|
|
||||||
import {commonApi} from '_api/commonApi';
|
import {commonApi} from '_api/commonApi';
|
||||||
import {useStream} from '_utils/useStream';
|
import {renderAsyncData} from '_utils/asyncDataUtils';
|
||||||
|
import {makeTreeList} from '_utils/makeTreeList';
|
||||||
|
import {useStreamRD} from '_utils/useStream';
|
||||||
|
|
||||||
import InfoList from '../info-list';
|
import InfoList from '../info-list';
|
||||||
|
|
||||||
const stream$ = combine((taskList, folderList) => {
|
const stream$ = combine(
|
||||||
return makeTreeList(folderList, taskList);
|
(taskListRD, folderListRD) => {
|
||||||
}, commonApi.taskList.getAll(), commonApi.folderList.getAll());
|
return pipe(
|
||||||
|
combineRD(taskListRD, folderListRD),
|
||||||
|
map(([taskList, folderList]) => {
|
||||||
|
return makeTreeList(folderList, taskList);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
commonApi.taskList.getAll(),
|
||||||
|
commonApi.folderList.getAll()
|
||||||
|
);
|
||||||
|
|
||||||
const Page: React.FC = () => {
|
const Page: React.FC = () => {
|
||||||
const tree = useStream(() => stream$, []);
|
const treeRD = useStreamRD(() => stream$, []);
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
{isNotEmpty(tree) && (
|
{renderAsyncData(treeRD, tree => (
|
||||||
<InfoList list={tree} space={1} />
|
<InfoList list={tree} space={1} />
|
||||||
)}
|
))}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,25 +1,27 @@
|
|||||||
import React, {Fragment, memo} from 'react';
|
import React, {Fragment, memo} from 'react';
|
||||||
|
import {renderAsyncData} from '_utils/asyncDataUtils';
|
||||||
|
|
||||||
import {usersApi} from '_api/usersTestApi';
|
import {usersApi} from '_api/usersTestApi';
|
||||||
import {useStream} from '_utils/useStream';
|
import {useStreamRD} from '_utils/useStream';
|
||||||
|
|
||||||
import UserComponent from './User';
|
import UserComponent from './User';
|
||||||
|
|
||||||
const Page: React.FC = () => {
|
const Page: React.FC = () => {
|
||||||
const users = useStream(() => usersApi.request(), []);
|
const users = useStreamRD(() => usersApi.request(), []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div>
|
<div>
|
||||||
tags
|
tags
|
||||||
{users?.map(user => (
|
{renderAsyncData(users, successData =>
|
||||||
<UserComponent userId={user.id} key={user.id}/>
|
successData?.map(user => <UserComponent userId={user.id} key={user.id} />)
|
||||||
))}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
tags
|
tags
|
||||||
{users?.map(user => (
|
{renderAsyncData(users, successData =>
|
||||||
<UserComponent userId={user.id} key={user.id}/>
|
successData?.map(user => <UserComponent userId={user.id} key={user.id} />)
|
||||||
))}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,12 +1,9 @@
|
|||||||
import {pending, success} from '@devexperts/remote-data-ts';
|
|
||||||
import {map} from '@most/core';
|
|
||||||
import {pipe} from 'fp-ts/lib/function';
|
|
||||||
import React, {FC, Fragment, memo} from 'react';
|
import React, {FC, Fragment, memo} from 'react';
|
||||||
import {Link} from 'react-router-dom';
|
import {Link} from 'react-router-dom';
|
||||||
|
|
||||||
import {usersApi} from '_api/usersTestApi';
|
import {usersApi} from '_api/usersTestApi';
|
||||||
import {renderAsyncData} from '_utils/asyncDataUtils';
|
import {renderAsyncData} from '_utils/asyncDataUtils';
|
||||||
import {useStream} from '_utils/useStream';
|
import {useStreamRD} from '_utils/useStream';
|
||||||
import {userEntityStore} from './utils';
|
import {userEntityStore} from './utils';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -15,16 +12,13 @@ type Props = {
|
|||||||
|
|
||||||
const User: FC<Props> = ({userId}) => {
|
const User: FC<Props> = ({userId}) => {
|
||||||
const data =
|
const data =
|
||||||
useStream(() => {
|
useStreamRD(() => {
|
||||||
const userStringId = userId.toString();
|
const userStringId = userId.toString();
|
||||||
|
|
||||||
return userEntityStore.get(userStringId, () =>
|
return userEntityStore.get(userStringId, () =>
|
||||||
pipe(
|
usersApi.findById(userStringId)
|
||||||
usersApi.findById(userStringId),
|
|
||||||
map(val => success(val))
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}, [userId]) ?? pending;
|
}, [userId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
|
|||||||
@ -1,38 +1,44 @@
|
|||||||
import React, {FC, memo} from 'react';
|
import {success} from '@devexperts/remote-data-ts';
|
||||||
import {pipe} from 'fp-ts/es6/pipeable';
|
|
||||||
import {at, chain, periodic, map} from '@most/core';
|
import {at, chain, periodic, map} from '@most/core';
|
||||||
|
import {pipe} from 'fp-ts/es6/pipeable';
|
||||||
import {useStream} from '_utils/useStream';
|
import React, {FC, memo} from 'react';
|
||||||
import {usersApi} from '_api/usersTestApi';
|
|
||||||
import {useParams} from 'react-router-dom';
|
import {useParams} from 'react-router-dom';
|
||||||
|
|
||||||
|
import {usersApi} from '_api/usersTestApi';
|
||||||
|
import {chainRD, renderAsyncData} from '_utils/asyncDataUtils';
|
||||||
|
import {useStreamRD} from '_utils/useStream';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
id: string;
|
id: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
const User: FC<Props> = () => {
|
const User: FC<Props> = () => {
|
||||||
const {id} = useParams<Props>();
|
const {id} = useParams<Props>();
|
||||||
|
|
||||||
const user = useStream(() => {
|
const userRD = useStreamRD(() => {
|
||||||
let i = 0;
|
let i = 0;
|
||||||
return pipe(
|
return pipe(
|
||||||
at(3000, undefined),
|
at(3000, undefined),
|
||||||
chain(() => usersApi.findById(id)),
|
chain(() => usersApi.findById(id)),
|
||||||
chain(data => {
|
chainRD(data => {
|
||||||
return pipe(periodic(1000), map(() => {
|
const res = pipe(
|
||||||
i = i + 1;
|
periodic(1000),
|
||||||
return {
|
map(() => {
|
||||||
...data,
|
i = i + 1;
|
||||||
chainableNumber: i
|
return success({
|
||||||
};
|
...data,
|
||||||
}));
|
chainableNumber: i
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return res;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{user ? (
|
{renderAsyncData(userRD, user => (
|
||||||
<div>
|
<div>
|
||||||
<div>{user.avatar}</div>
|
<div>{user.avatar}</div>
|
||||||
<div>{user.email}</div>
|
<div>{user.email}</div>
|
||||||
@ -41,9 +47,7 @@ const User: FC<Props> = () => {
|
|||||||
<div>{user.last_name}</div>
|
<div>{user.last_name}</div>
|
||||||
<div>{user.chainableNumber}</div>
|
<div>{user.chainableNumber}</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
))}
|
||||||
'Loading...'
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user