feat(nx): infer state type, add optional action type parameter

This commit is contained in:
Manduro 2017-10-18 14:56:34 +02:00 committed by Victor Savkin
parent 7219c8af71
commit 6fc7145602
2 changed files with 71 additions and 62 deletions

View File

@ -92,7 +92,7 @@ describe('DataPersistence', () => {
class TodoEffects {
@Effect()
loadTodo = this.s.navigation(TodoComponent, {
run: (a: ActivatedRouteSnapshot, state: TodosState) => {
run: (a, state) => {
return {
type: 'TODO_LOADED',
payload: { id: a.params['id'], user: state.user }
@ -100,7 +100,7 @@ describe('DataPersistence', () => {
},
onError: () => null
});
constructor(private s: DataPersistence<any>) {}
constructor(private s: DataPersistence<TodosState>) {}
}
beforeEach(() => {
@ -131,7 +131,7 @@ describe('DataPersistence', () => {
class TodoEffects {
@Effect()
loadTodo = this.s.navigation(TodoComponent, {
run: (a: ActivatedRouteSnapshot, state: TodosState) => {
run: (a, state) => {
if (a.params['id'] === '123') {
throw new Error('boom');
} else {
@ -143,7 +143,7 @@ describe('DataPersistence', () => {
},
onError: (a, e) => ({ type: 'ERROR', payload: { error: e } })
});
constructor(private s: DataPersistence<any>) {}
constructor(private s: DataPersistence<TodosState>) {}
}
beforeEach(() => {
@ -183,7 +183,7 @@ describe('DataPersistence', () => {
class TodoEffects {
@Effect()
loadTodo = this.s.navigation(TodoComponent, {
run: (a: ActivatedRouteSnapshot, state: TodosState) => {
run: (a, state) => {
if (a.params['id'] === '123') {
return _throw('boom');
} else {
@ -195,7 +195,7 @@ describe('DataPersistence', () => {
},
onError: (a, e) => ({ type: 'ERROR', payload: { error: e } })
});
constructor(private s: DataPersistence<any>) {}
constructor(private s: DataPersistence<TodosState>) {}
}
beforeEach(() => {
@ -236,11 +236,15 @@ describe('DataPersistence', () => {
});
describe('no id', () => {
type GetTodos = {
type: 'GET_TODOS';
};
@Injectable()
class TodoEffects {
@Effect()
loadTodos = this.s.fetch('GET_TODOS', {
run: (a: any, state: TodosState) => {
loadTodos = this.s.fetch<GetTodos>('GET_TODOS', {
run: (a, state) => {
// we need to introduce the delay to "enable" switchMap
return of({
type: 'TODOS',
@ -248,10 +252,10 @@ describe('DataPersistence', () => {
}).delay(1);
},
onError: (a: UpdateTodo, e: any) => null
onError: (a, e: any) => null
});
constructor(private s: DataPersistence<any>) {}
constructor(private s: DataPersistence<TodosState>) {}
}
function userReducer() {
@ -281,16 +285,21 @@ describe('DataPersistence', () => {
});
describe('id', () => {
type GetTodo = {
type: 'GET_TODO';
payload: { id: string };
};
@Injectable()
class TodoEffects {
@Effect()
loadTodo = this.s.fetch('GET_TODO', {
id: (a: any, state: TodosState) => a.payload.id,
run: (a: any, state: TodosState) => of({ type: 'TODO', payload: a.payload }).delay(1),
onError: (a: UpdateTodo, e: any) => null
loadTodo = this.s.fetch<GetTodo>('GET_TODO', {
id: (a, state) => a.payload.id,
run: (a, state) => of({ type: 'TODO', payload: a.payload }).delay(1),
onError: (a, e: any) => null
});
constructor(private s: DataPersistence<any>) {}
constructor(private s: DataPersistence<TodosState>) {}
}
function userReducer() {
@ -333,15 +342,15 @@ describe('DataPersistence', () => {
@Injectable()
class TodoEffects {
@Effect()
loadTodo = this.s.pessimisticUpdate('UPDATE_TODO', {
run: (a: UpdateTodo, state: TodosState) => ({
loadTodo = this.s.pessimisticUpdate<UpdateTodo>('UPDATE_TODO', {
run: (a, state) => ({
type: 'TODO_UPDATED',
payload: { user: state.user, newTitle: a.payload.newTitle }
}),
onError: (a: UpdateTodo, e: any) => null
onError: (a, e: any) => null
});
constructor(private s: DataPersistence<any>) {}
constructor(private s: DataPersistence<TodosState>) {}
}
function userReducer() {
@ -379,18 +388,18 @@ describe('DataPersistence', () => {
@Injectable()
class TodoEffects {
@Effect()
loadTodo = this.s.pessimisticUpdate('UPDATE_TODO', {
run: (a: UpdateTodo, state: TodosState) => {
loadTodo = this.s.pessimisticUpdate<UpdateTodo>('UPDATE_TODO', {
run: (a, state) => {
throw new Error('boom');
},
onError: (a: UpdateTodo, e: any) => ({
onError: (a, e: any) => ({
type: 'ERROR',
payload: { error: e }
})
});
constructor(private s: DataPersistence<any>) {}
constructor(private s: DataPersistence<TodosState>) {}
}
function userReducer() {
@ -426,18 +435,18 @@ describe('DataPersistence', () => {
@Injectable()
class TodoEffects {
@Effect()
loadTodo = this.s.pessimisticUpdate('UPDATE_TODO', {
run: (a: UpdateTodo, state: TodosState) => {
loadTodo = this.s.pessimisticUpdate<UpdateTodo>('UPDATE_TODO', {
run: (a, state) => {
return _throw('boom');
},
onError: (a: UpdateTodo, e: any) => ({
onError: (a, e: any) => ({
type: 'ERROR',
payload: { error: e }
})
});
constructor(private s: DataPersistence<any>) {}
constructor(private s: DataPersistence<TodosState>) {}
}
function userReducer() {
@ -479,18 +488,18 @@ describe('DataPersistence', () => {
@Injectable()
class TodoEffects {
@Effect()
loadTodo = this.s.optimisticUpdate('UPDATE_TODO', {
run: (a: UpdateTodo, state: TodosState) => {
loadTodo = this.s.optimisticUpdate<UpdateTodo>('UPDATE_TODO', {
run: (a, state) => {
throw new Error('boom');
},
undoAction: (a: UpdateTodo, e: any) => ({
undoAction: (a, e: any) => ({
type: 'UNDO_UPDATE_TODO',
payload: a.payload
})
});
constructor(private s: DataPersistence<any>) {}
constructor(private s: DataPersistence<TodosState>) {}
}
function userReducer() {

View File

@ -17,32 +17,32 @@ import { withLatestFrom } from 'rxjs/operator/withLatestFrom';
/**
* See {@link DataPersistence.pessimisticUpdate} for more information.
*/
export interface PessimisticUpdateOpts {
run(a: Action, state?: any): Observable<Action> | Action | void;
onError(a: Action, e: any): Observable<any> | any;
export interface PessimisticUpdateOpts<T, A> {
run(a: A, state?: T): Observable<Action> | Action | void;
onError(a: A, e: any): Observable<any> | any;
}
/**
* See {@link DataPersistence.pessimisticUpdate} for more information.
*/
export interface OptimisticUpdateOpts {
run(a: Action, state?: any): Observable<any> | any;
undoAction(a: Action, e: any): Observable<Action> | Action;
export interface OptimisticUpdateOpts<T, A> {
run(a: A, state?: T): Observable<Action> | Action | void;
undoAction(a: A, e: any): Observable<Action> | Action;
}
/**
* See {@link DataPersistence.navigation} for more information.
*/
export interface FetchOpts {
id?(a: Action, state?: any): any;
run(a: Action, state?: any): Observable<Action> | Action | void;
onError?(a: Action, e: any): Observable<any> | any;
export interface FetchOpts<T, A> {
id?(a: A, state?: T): any;
run(a: A, state?: T): Observable<Action> | Action | void;
onError?(a: A, e: any): Observable<any> | any;
}
/**
* See {@link DataPersistence.navigation} for more information.
*/
export interface HandleNavigationOpts {
run(a: ActivatedRouteSnapshot, state?: any): Observable<Action> | Action | void;
export interface HandleNavigationOpts<T> {
run(a: ActivatedRouteSnapshot, state?: T): Observable<Action> | Action | void;
onError?(a: ActivatedRouteSnapshot, e: any): Observable<any> | any;
}
@ -69,9 +69,9 @@ export class DataPersistence<T> {
* ```typescript
* @Injectable()
* class TodoEffects {
* @Effect() updateTodo = this.s.pessimisticUpdate('UPDATE_TODO', {
* @Effect() updateTodo = this.s.pessimisticUpdate<UpdateTodo>('UPDATE_TODO', {
* // provides an action and the current state of the store
* run(a: UpdateTodo, state: TodosState) {
* run(a, state) {
* // update the backend first, and then dispatch an action that will
* // update the client side
* return this.backend(state.user, a.payload).map(updated => ({
@ -80,7 +80,7 @@ export class DataPersistence<T> {
* }));
* },
*
* onError(a: UpdateTodo, e: any) {
* onError(a, e: any) {
* // we don't need to undo the changes on the client side.
* // we can dispatch an error, or simply log the error here and return `null`
* return null;
@ -91,7 +91,7 @@ export class DataPersistence<T> {
* }
* ```
*/
pessimisticUpdate(actionType: string, opts: PessimisticUpdateOpts): Observable<any> {
pessimisticUpdate<A = Action>(actionType: string, opts: PessimisticUpdateOpts<T, A>): Observable<any> {
const nav = this.actions.ofType(actionType);
const pairs = withLatestFrom.call(nav, this.store);
return concatMap.call(pairs, this.runWithErrorHandling(opts.run, opts.onError));
@ -114,13 +114,13 @@ export class DataPersistence<T> {
* ```typescript
* @Injectable()
* class TodoEffects {
* @Effect() updateTodo = this.s.optimisticUpdate('UPDATE_TODO', {
* @Effect() updateTodo = this.s.optimisticUpdate<UpdateTodo>('UPDATE_TODO', {
* // provides an action and the current state of the store
* run: (a: UpdateTodo, state: TodosState) => {
* run: (a, state) => {
* return this.backend(state.user, a.payload);
* },
*
* undoAction: (a: UpdateTodo, e: any) => {
* undoAction: (a, e: any) => {
* // dispatch an undo action to undo the changes in the client state
* return ({
* type: 'UNDO_UPDATE_TODO',
@ -133,7 +133,7 @@ export class DataPersistence<T> {
* }
* ```
*/
optimisticUpdate(actionType: string, opts: OptimisticUpdateOpts): Observable<any> {
optimisticUpdate<A = Action>(actionType: string, opts: OptimisticUpdateOpts<T, A>): Observable<any> {
const nav = this.actions.ofType(actionType);
const pairs = withLatestFrom.call(nav, this.store);
return concatMap.call(pairs, this.runWithErrorHandling(opts.run, opts.undoAction));
@ -153,16 +153,16 @@ export class DataPersistence<T> {
* ```typescript
* @Injectable()
* class TodoEffects {
* @Effect() loadTodos = this.s.fetch('GET_TODOS', {
* @Effect() loadTodos = this.s.fetch<GetTodos>('GET_TODOS', {
* // provides an action and the current state of the store
* run: (a: GetTodos, state: TodosState) => {
* run: (a, state) => {
* return this.backend(state.user, a.payload).map(r => ({
* type: 'TODOS',
* payload: r
* });
* },
*
* onError: (a: GetTodos, e: any) => {
* onError: (a, e: any) => {
* // dispatch an undo action to undo the changes in the client state
* }
* });
@ -178,20 +178,20 @@ export class DataPersistence<T> {
* ```typescript
* @Injectable()
* class TodoEffects {
* @Effect() loadTodo = this.s.fetch('GET_TODO', {
* id: (a: GetTodo, state: TodosState) => {
* @Effect() loadTodo = this.s.fetch<GetTodo>('GET_TODO', {
* id: (a, state) => {
* return a.payload.id;
* }
*
* // provides an action and the current state of the store
* run: (a: GetTodo, state: TodosState) => {
* run: (a, state) => {
* return this.backend(state.user, a.payload).map(r => ({
* type: 'TODO',
* payload: r
* });
* },
*
* onError: (a: GetTodo, e: any) => {
* onError: (a, e: any) => {
* // dispatch an undo action to undo the changes in the client state
* return null;
* }
@ -206,7 +206,7 @@ export class DataPersistence<T> {
* In addition, if DataPersistence notices that there are multiple requests for Todo 1 scheduled,
* it will only run the last one.
*/
fetch(actionType: string, opts: FetchOpts): Observable<any> {
fetch<A = Action>(actionType: string, opts: FetchOpts<T, A>): Observable<any> {
const nav = this.actions.ofType(actionType);
const allPairs = withLatestFrom.call(nav, this.store);
@ -237,13 +237,13 @@ export class DataPersistence<T> {
* @Injectable()
* class TodoEffects {
* @Effect() loadTodo = this.s.navigation(TodoComponent, {
* run: (a: ActivatedRouteSnapshot, state: TodosState) => {
* run: (a, state) => {
* return this.backend.fetchTodo(a.params['id']).map(todo => ({
* type: 'TODO_LOADED',
* payload: todo
* }));
* },
* onError: (a: ActivatedRouteSnapshot, e: any) => {
* onError: (a, e: any) => {
* // we can log and error here and return null
* // we can also navigate back
* return null;
@ -254,7 +254,7 @@ export class DataPersistence<T> {
* ```
*
*/
navigation(component: Type<any>, opts: HandleNavigationOpts): Observable<any> {
navigation(component: Type<any>, opts: HandleNavigationOpts<T>): Observable<any> {
const nav = filter.call(
map.call(this.actions.ofType(ROUTER_NAVIGATION), (a: RouterNavigationAction<RouterStateSnapshot>) =>
findSnapshot(component, a.payload.routerState.root)