import { AppState, epics as coreEpics, reducers, IContentProvider } from "@nteract/core"; import { compose, Store, AnyAction, Middleware, Dispatch, MiddlewareAPI } from "redux"; import { Epic } from "redux-observable"; import { allEpics } from "./epics"; import { coreReducer, cdbReducer } from "./reducers"; import { catchError } from "rxjs/operators"; import { Observable } from "rxjs"; import { configuration } from "@nteract/mythic-configuration"; import { makeConfigureStore } from "@nteract/myths"; import { CdbAppState } from "./types"; const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; export default function configureStore( initialState: Partial, contentProvider: IContentProvider, onTraceFailure: (title: string, message: string) => void, customMiddlewares?: Middleware<{}, any, Dispatch>[], autoStartKernelOnNotebookOpen?: boolean ): Store { /** * Catches errors in reducers */ const catchErrorMiddleware: Middleware = , S extends AppState>({ dispatch, getState, }: MiddlewareAPI) => (next: Dispatch) => (action: A): any => { try { next(action); } catch (error) { traceFailure("Reducer failure", error); } }; const protect = (epic: Epic) => { return (action$: Observable, state$: any, dependencies: any) => epic(action$, state$, dependencies).pipe( catchError((error, caught) => { traceFailure("Epic failure", error); return caught; }) ); }; const traceFailure = (title: string, error: any) => { if (error instanceof Error) { onTraceFailure(title, `${error.message} ${JSON.stringify(error.stack)}`); console.error(error); } else { onTraceFailure(title, error.message); } }; const protectEpics = (epics: Epic[]): Epic[] => { return epics.map((epic) => protect(epic)); }; const filteredCoreEpics = getCoreEpics(autoStartKernelOnNotebookOpen); const mythConfigureStore = makeConfigureStore()({ packages: [configuration], reducers: { app: reducers.app, core: coreReducer as any, cdb: cdbReducer, }, epics: protectEpics([...filteredCoreEpics, ...allEpics]), epicDependencies: { contentProvider }, epicMiddleware: customMiddlewares.concat(catchErrorMiddleware), enhancer: composeEnhancers, }); const store = mythConfigureStore(initialState as any); // TODO Fix typing issue here: createStore() output type doesn't quite match AppState // return store as Store; return store as any; } export const getCoreEpics = (autoStartKernelOnNotebookOpen: boolean): Epic[] => { // This list needs to be consistent and in sync with core.allEpics until we figure // out how to safely filter out the ones we are overriding here. const filteredCoreEpics = [ coreEpics.autoSaveCurrentContentEpic, coreEpics.executeCellEpic, coreEpics.executeFocusedCellEpic, coreEpics.executeCellAfterKernelLaunchEpic, coreEpics.sendExecuteRequestEpic, coreEpics.updateDisplayEpic, coreEpics.executeAllCellsEpic, coreEpics.commListenEpic, coreEpics.interruptKernelEpic, coreEpics.lazyLaunchKernelEpic, coreEpics.killKernelEpic, coreEpics.watchExecutionStateEpic, coreEpics.restartKernelEpic, coreEpics.fetchKernelspecsEpic, coreEpics.fetchContentEpic, coreEpics.updateContentEpic, coreEpics.saveContentEpic, coreEpics.publishToBookstore, coreEpics.publishToBookstoreAfterSave, coreEpics.sendInputReplyEpic, ]; if (autoStartKernelOnNotebookOpen) { filteredCoreEpics.push(coreEpics.launchKernelWhenNotebookSetEpic); } return filteredCoreEpics; };