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 {pipe} from 'fp-ts/pipeable';
|
||||
import {objectEntries} from './objectEntries';
|
||||
|
||||
type PromiseApi = Record<string, (...args: any[]) => Promise<unknown>>;
|
||||
|
||||
type StreamApi<T extends PromiseApi> = {
|
||||
[K in keyof T]: (...params: Parameters<T[K]>) => (
|
||||
T[K] extends (...args: any[]) => Promise<infer R> ? Stream<R> : never
|
||||
);
|
||||
type StreamApi<T extends PromiseApi, E> = {
|
||||
[K in keyof T]: (
|
||||
...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 {
|
||||
...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 {useEffect, useState} from 'react';
|
||||
import {newDefaultScheduler} from '@most/scheduler';
|
||||
import {pending, RemoteData} from '@devexperts/remote-data-ts';
|
||||
|
||||
export function useStream<T extends Array<unknown>, R>(
|
||||
piping: () => Stream<R>,
|
||||
@ -31,3 +32,32 @@ export function useStream<T extends Array<unknown>, R>(
|
||||
|
||||
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 as combineRD, map} from '@devexperts/remote-data-ts';
|
||||
import {pipe} from 'fp-ts/lib/function';
|
||||
import React, {Fragment, memo} from 'react';
|
||||
import {isNotEmpty} from '_referers/common';
|
||||
import {makeTreeList} from '_utils/makeTreeList';
|
||||
|
||||
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';
|
||||
|
||||
const stream$ = combine((taskList, folderList) => {
|
||||
const stream$ = combine(
|
||||
(taskListRD, folderListRD) => {
|
||||
return pipe(
|
||||
combineRD(taskListRD, folderListRD),
|
||||
map(([taskList, folderList]) => {
|
||||
return makeTreeList(folderList, taskList);
|
||||
}, commonApi.taskList.getAll(), commonApi.folderList.getAll());
|
||||
})
|
||||
);
|
||||
},
|
||||
commonApi.taskList.getAll(),
|
||||
commonApi.folderList.getAll()
|
||||
);
|
||||
|
||||
const Page: React.FC = () => {
|
||||
const tree = useStream(() => stream$, []);
|
||||
const treeRD = useStreamRD(() => stream$, []);
|
||||
return (
|
||||
<Fragment>
|
||||
{isNotEmpty(tree) && (
|
||||
{renderAsyncData(treeRD, tree => (
|
||||
<InfoList list={tree} space={1} />
|
||||
)}
|
||||
))}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,25 +1,27 @@
|
||||
import React, {Fragment, memo} from 'react';
|
||||
import {renderAsyncData} from '_utils/asyncDataUtils';
|
||||
|
||||
import {usersApi} from '_api/usersTestApi';
|
||||
import {useStream} from '_utils/useStream';
|
||||
import {useStreamRD} from '_utils/useStream';
|
||||
|
||||
import UserComponent from './User';
|
||||
|
||||
const Page: React.FC = () => {
|
||||
const users = useStream(() => usersApi.request(), []);
|
||||
const users = useStreamRD(() => usersApi.request(), []);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div>
|
||||
tags
|
||||
{users?.map(user => (
|
||||
<UserComponent userId={user.id} key={user.id}/>
|
||||
))}
|
||||
{renderAsyncData(users, successData =>
|
||||
successData?.map(user => <UserComponent userId={user.id} key={user.id} />)
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
tags
|
||||
{users?.map(user => (
|
||||
<UserComponent userId={user.id} key={user.id}/>
|
||||
))}
|
||||
{renderAsyncData(users, successData =>
|
||||
successData?.map(user => <UserComponent userId={user.id} key={user.id} />)
|
||||
)}
|
||||
</div>
|
||||
</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 {Link} from 'react-router-dom';
|
||||
|
||||
import {usersApi} from '_api/usersTestApi';
|
||||
import {renderAsyncData} from '_utils/asyncDataUtils';
|
||||
import {useStream} from '_utils/useStream';
|
||||
import {useStreamRD} from '_utils/useStream';
|
||||
import {userEntityStore} from './utils';
|
||||
|
||||
type Props = {
|
||||
@ -15,16 +12,13 @@ type Props = {
|
||||
|
||||
const User: FC<Props> = ({userId}) => {
|
||||
const data =
|
||||
useStream(() => {
|
||||
useStreamRD(() => {
|
||||
const userStringId = userId.toString();
|
||||
|
||||
return userEntityStore.get(userStringId, () =>
|
||||
pipe(
|
||||
usersApi.findById(userStringId),
|
||||
map(val => success(val))
|
||||
)
|
||||
usersApi.findById(userStringId)
|
||||
);
|
||||
}, [userId]) ?? pending;
|
||||
}, [userId]);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
|
||||
@ -1,38 +1,44 @@
|
||||
import React, {FC, memo} from 'react';
|
||||
import {pipe} from 'fp-ts/es6/pipeable';
|
||||
import {success} from '@devexperts/remote-data-ts';
|
||||
import {at, chain, periodic, map} from '@most/core';
|
||||
|
||||
import {useStream} from '_utils/useStream';
|
||||
import {usersApi} from '_api/usersTestApi';
|
||||
import {pipe} from 'fp-ts/es6/pipeable';
|
||||
import React, {FC, memo} from 'react';
|
||||
import {useParams} from 'react-router-dom';
|
||||
|
||||
import {usersApi} from '_api/usersTestApi';
|
||||
import {chainRD, renderAsyncData} from '_utils/asyncDataUtils';
|
||||
import {useStreamRD} from '_utils/useStream';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
}
|
||||
};
|
||||
|
||||
const User: FC<Props> = () => {
|
||||
const {id} = useParams<Props>();
|
||||
|
||||
const user = useStream(() => {
|
||||
const userRD = useStreamRD(() => {
|
||||
let i = 0;
|
||||
return pipe(
|
||||
at(3000, undefined),
|
||||
chain(() => usersApi.findById(id)),
|
||||
chain(data => {
|
||||
return pipe(periodic(1000), map(() => {
|
||||
chainRD(data => {
|
||||
const res = pipe(
|
||||
periodic(1000),
|
||||
map(() => {
|
||||
i = i + 1;
|
||||
return {
|
||||
return success({
|
||||
...data,
|
||||
chainableNumber: i
|
||||
};
|
||||
}));
|
||||
});
|
||||
})
|
||||
);
|
||||
return res;
|
||||
})
|
||||
);
|
||||
}, [id]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{user ? (
|
||||
{renderAsyncData(userRD, user => (
|
||||
<div>
|
||||
<div>{user.avatar}</div>
|
||||
<div>{user.email}</div>
|
||||
@ -41,9 +47,7 @@ const User: FC<Props> = () => {
|
||||
<div>{user.last_name}</div>
|
||||
<div>{user.chainableNumber}</div>
|
||||
</div>
|
||||
) : (
|
||||
'Loading...'
|
||||
)}
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user