Last active
May 25, 2018 12:35
-
-
Save tlaitinen/b33101de59c698b9997af3875d1c1c4c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import {createAction} from 'typesafe-actions'; | |
| import {SagaIterator} from 'redux-saga'; | |
| import {all, call, put, select} from 'redux-saga/effects'; | |
| export interface Results { | |
| results: string[]; | |
| loading: boolean; | |
| error?: string; | |
| } | |
| const defaultResults:Results = { | |
| results: [], | |
| loading: false | |
| }; | |
| export interface EntityStatus { | |
| busy: boolean; | |
| error?: string; | |
| } | |
| export interface State<E,Q> { | |
| queries: {[queryName:string]: Q | undefined}; | |
| results: {[queryName:string]: Results | undefined}; | |
| entities: {[entityId:string]: E | undefined}; | |
| entityStatus: {[entityId:string]: EntityStatus | undefined}; | |
| postError?: string; | |
| } | |
| export function mkDefState<E,Q>():State<E,Q> { | |
| return { | |
| queries: {}, | |
| results: {}, | |
| entities: {}, | |
| entityStatus: {} | |
| }; | |
| } | |
| export function mkSelectors<E,Q,RS>(getState:(rs:RS) => State<E,Q>) { | |
| return { | |
| querySelector: (s:RS, queryName:string):Q | undefined => getState(s).queries[queryName], | |
| resultsSelector: (s:RS, queryName:string):Results | undefined => getState(s).results[queryName], | |
| entitiesSelector: (s:RS):{[entityId:string]: E | undefined} => getState(s).entities, | |
| entitySelector: (s:RS, entityId:string): E | undefined => getState(s).entities[entityId], | |
| statusSelector: (s:RS): {[entityId:string]:EntityStatus | undefined} => getState(s).entityStatus, | |
| postErrorSelector: (s:RS): string | undefined => getState(s).postError | |
| }; | |
| } | |
| export interface SetQueryPayload<C,Q> { | |
| crud: C; | |
| queryName: string; | |
| query: Q; | |
| } | |
| export interface SetQueryAction<C, Q> { | |
| type: 'CRUD_SET_QUERY'; | |
| payload: SetQueryPayload<C,Q>; | |
| } | |
| export interface SetResultsPayload<C> { | |
| crud: C; | |
| queryName: string; | |
| results: Results | |
| } | |
| export interface SetResultsAction<C> { | |
| type: 'CRUD_SET_RESULTS'; | |
| payload: SetResultsPayload<C>; | |
| } | |
| export interface SetEntitiesPayload<C,E> { | |
| crud: C; | |
| entities: {[entityId:string]:E} | |
| } | |
| export interface SetEntitiesAction<C,E> { | |
| type: 'CRUD_SET_ENTITIES'; | |
| payload: SetEntitiesPayload<C,E>; | |
| } | |
| export interface SetEntityStatusPayload<C> { | |
| crud: C; | |
| entityId: string; | |
| entityStatus: EntityStatus; | |
| } | |
| export interface SetEntityStatusAction<C> { | |
| type: 'CRUD_SET_ENTITY_STATUS'; | |
| payload: SetEntityStatusPayload<C>; | |
| } | |
| export interface SetPostErrorPayload<C> { | |
| crud: C; | |
| postError: string | undefined; | |
| } | |
| export interface SetPostErrorAction<C> { | |
| type: 'CRUD_SET_POST_ERROR'; | |
| payload: SetPostErrorPayload<C>; | |
| } | |
| export type Action<C,E,Q> = | |
| SetQueryAction<C,Q> | |
| | SetResultsAction<C> | |
| | SetEntitiesAction<C,E> | |
| | SetEntityStatusAction<C> | |
| | SetPostErrorAction<C> | |
| export function mkActions<C,E,Q,RS,EI=E>({crud,getId,getState,fetchFunc,putFunc,postFunc}:{ | |
| crud:C; | |
| getId: (entity:E) => string; | |
| getState: (rs:RS) => State<E,Q>; | |
| fetchFunc: (query:Q) => Promise<E[]>; | |
| putFunc: (entityId:string, entity:EI) => Promise<E>; | |
| postFunc: (entity:EI) => Promise<E> | |
| }) { | |
| const actions = { | |
| setQuery: createAction('CRUD_SET_QUERY', resolve => (queryName: string, query:Q) => resolve({ | |
| crud, | |
| queryName, | |
| query | |
| } as SetQueryPayload<C,Q>)), | |
| setResults: createAction('CRUD_SET_RESULTS', resolve => (queryName: string, results:Results) => resolve({ | |
| crud, | |
| queryName, | |
| results | |
| } as SetResultsPayload<C>)), | |
| setEntities: createAction('CRUD_SET_ENTITIES', resolve => (entities: {[entityId:string]:E}) => resolve({ | |
| crud, | |
| entities | |
| } as SetEntitiesPayload<C,E>)), | |
| setStatus: createAction('CRUD_SET_ENTITY_STATUS', resolve => (entityId: string, entityStatus:EntityStatus) => resolve({ | |
| crud, | |
| entityId, | |
| entityStatus | |
| } as SetEntityStatusPayload<C>)), | |
| setPostError: createAction('CRUD_SET_POST_ERROR', resolve => (postError:string | undefined) => resolve({ | |
| crud, | |
| postError | |
| })) | |
| }; | |
| function* fetchResults(queryName: string, query: Q):SagaIterator { | |
| yield put(actions.setResults(queryName, { | |
| results: [], | |
| loading: true | |
| })); | |
| yield put(actions.setQuery(queryName, query)); | |
| try { | |
| const r:E[] = yield call(fetchFunc, query); | |
| const entities:{[entityId:string]:E} = {}; | |
| r.forEach(e => entities[getId(e)] = e); | |
| yield put(actions.setEntities(entities)); | |
| yield put(actions.setResults(queryName, { | |
| results: r.map(e => getId(e)), | |
| loading: false | |
| })); | |
| } catch (e) { | |
| yield put(actions.setResults(queryName, { | |
| results: [], | |
| loading: false, | |
| error: e.message | |
| })); | |
| } | |
| } | |
| function* putEntity(entityId: string, entity:EI):SagaIterator { | |
| yield put(actions.setStatus(entityId, {busy:true})); | |
| try { | |
| const r = yield call(putFunc, entityId, entity); | |
| yield put(actions.setEntities({[getId(r)]:r})); | |
| yield put(actions.setStatus(entityId, {busy:false})); | |
| } catch (e) { | |
| yield put(actions.setStatus(entityId, {busy:false, error: e.message})); | |
| } | |
| } | |
| function* postEntity(entity:EI) { | |
| const state = yield select(getState); | |
| const queryNames = Object.keys(state.queries).sort(); | |
| try { | |
| for (let i = 0; i < queryNames.length; i++) { | |
| const qn = queryNames[i]; | |
| if (state.queries[qn]) { | |
| yield put(actions.setResults(qn, { | |
| ...defaultResults, | |
| ...state.results[qn], | |
| loading:true | |
| })); | |
| } | |
| } | |
| const r = yield call(postFunc, entity); | |
| yield put(actions.setEntities({[getId(r)]:r})); | |
| yield put(actions.setPostError(undefined)); | |
| } catch (e) { | |
| yield put(actions.setPostError(e.message)); | |
| } finally { | |
| yield all(queryNames.map(qn => fetchResults(qn, state.queries[qn]))); | |
| } | |
| } | |
| return { | |
| ...actions, | |
| fetchResults, | |
| put: putEntity, | |
| post: postEntity | |
| } | |
| } | |
| export function mkReducer<C,E,Q>(crud:C) { | |
| const defState = mkDefState<E,Q>(); | |
| return function(state:State<E,Q> = defState, action:Action<C,E,Q>) { | |
| if (!action.payload || action.payload.crud !== crud) { | |
| return state; | |
| } | |
| switch(action.type) { | |
| case 'CRUD_SET_QUERY': | |
| return { | |
| ...state, | |
| queries: { | |
| ...state.queries, | |
| [action.payload.queryName]: action.payload.query | |
| } | |
| }; | |
| case 'CRUD_SET_RESULTS': | |
| return { | |
| ...state, | |
| results: { | |
| ...state.queries, | |
| [action.payload.queryName]: action.payload.results | |
| } | |
| }; | |
| case 'CRUD_SET_ENTITIES': | |
| return { | |
| ...state, | |
| entities: { | |
| ...state.entities, | |
| ...action.payload.entities | |
| } | |
| }; | |
| case 'CRUD_SET_ENTITY_STATUS': | |
| return { | |
| ...state, | |
| entityStatus: { | |
| ...state.entityStatus, | |
| [action.payload.entityId]: action.payload.entityStatus | |
| } | |
| }; | |
| case 'CRUD_SET_POST_ERROR': | |
| return { | |
| ...state, | |
| postError: action.payload.postError | |
| }; | |
| default: | |
| return state; | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment