import { default as createSagaMiddleware } from 'redux-saga' ; import { all , call } from 'redux-saga/effects' ; import produce from "immer" ; import { App } from "./index" ; import { AppStore } from "./store" ; import { configureStore } from "@reduxjs/toolkit" ; import taskSlice from "./task/task_slice" ; import documentationSlice from "./task/documentation_slice" ; import log from "loglevel" ; export interface Linker { scope : App ; actionTypes : { [ key ] : string ; } ; store : AppStore ; finalize : Function ; start : Function ; } export function link ( rootBuilder , globalScope ) { const typeMap = new Map ( ) ; const nameForActionType = new Map ( ) ; const typeForActionName = { } ; const useQueue = [ ] ; function declareActionType ( name ) { if ( nameForActionType . has ( name ) ) { throw new Error ( `action type conflict: ${ name }` ) ; } nameForActionType . set ( name , name ) ; typeForActionName [ name ] = name ; } function declareUse ( target , names ) { useQueue . push ( [ target , names ] ) ; } function publish ( type , name , value ) { if ( name in globalScope ) { throw new Error ( `linker conflict on ${ name }` ) ; } typeMap . set ( name , type ) ; globalScope [ name ] = value ; } function lookup ( name ) { return globalScope [ name ] ; } function inject ( typeFilter , locals , name ) { if ( typeFilter ) { const type = typeMap . get ( name ) ; if ( - 1 === typeFilter . indexOf ( type ) ) { return ; } } if ( name in globalScope ) { locals [ name ] = globalScope [ name ] ; } else { throw new Error ( `undefined dependency: ${ name }` ) ; } } function injectAll ( typeFilter ) { for ( let i = 0 ; i < useQueue . length ; i += 1 ) { const dir = useQueue [ i ] , locals = dir [ 0 ] , arg = dir [ 1 ] ; if ( typeof arg === 'string' ) { inject ( typeFilter , locals , arg ) ; } else if ( Array . isArray ( arg ) ) { for ( let name of arg ) { inject ( typeFilter , locals , name ) ; } } else { throw new Error ( 'invalid use' ) ; } } } const linker = { publish , declareUse , declareActionType , lookup } ; const rootBundle = new Bundle ( linker , rootBuilder ) ; rootBuilder ( rootBundle , rootBundle . locals ) ; injectAll ( [ 'action' , 'value' ] ) ; const actionMap = new Map ( ) ; rootBundle . _buildActionMap ( actionMap ) ; const actionReducer = function ( state , action ) { if ( actionMap . has ( action . type ) ) { state = actionMap . get ( action . type ) ( state , action ) ; } return state ; } ; const rootReducer = [ rootBundle . _earlyReducer ( ) , actionReducer , rootBundle . _lateReducer ( ) ] . reduce ( reverseCompose , null ) ; const sagaMiddleware = createSagaMiddleware ( { onError : ( error ) => { console . error ( error ) ; setImmediate ( ( ) => { throw error ; } ) ; } } ) ; const userTimingMiddleware = ( ) => ( next ) => ( action ) => { if ( performance . mark === undefined ) { return next ( action ) ; } performance . mark ( `${ action . type }_start` ) ; const result = next ( action ) ; performance . mark ( `${ action . type }_end` ) ; performance . measure ( `${ action . type }` , `${ action . type }_start` , `${ action . type }_end` ) ; return result ; } ; const immerRootReducer = produce ( rootReducer ) ; const rootReducerWithSlices = ( state , action ) => { log . getLogger ( 'performance' ) . debug ( 'Root reducer' , action ) ; let start = window . performance . now ( ) ; let newState = immerRootReducer ( state , action ) ; let end = window . performance . now ( ) ; log . getLogger ( 'performance' ) . debug ( `Execution time: ${ end - start } ms` ) ; let start2 = window . performance . now ( ) ; const newNewState = customCombineReducers ( { [ taskSlice . name ] : taskSlice . reducer , [ documentationSlice . name ] : documentationSlice . reducer , } ) ( newState , action ) ; let end2 = window . performance . now ( ) ; log . getLogger ( 'performance' ) . debug ( `Execution time2: ${ end2 - start2 } ms` ) ; return newNewState ; } ; const store = configureStore ( { reducer : rootReducerWithSlices , preloadedState : { } , middleware : [ sagaMiddleware , userTimingMiddleware ] , devTools : true , } ) ; function finalize ( ... args ) { console . log ( { actionMap } ) ; rootBundle . _runDefers ( ... args ) ; } function start ( ... args ) { const rootSaga = rootBundle . _saga ( ) ; console . log ( 'run saga middleware' , rootSaga ) ; return sagaMiddleware . run ( rootSaga , args ) ; } globalScope . dispatch = store . dispatch ; return { scope : globalScope as App , actionTypes : typeForActionName , store : store as AppStore , finalize , start , } ; } function undeclaredDependencyError ( target , property ) { throw new Error ( `use of undeclared dependency ${ property }` ) ; } export class Bundle { builder : any ; locals : any ; _ : any ; constructor ( linker , builder ) { this . builder = builder ; this . locals = makeSafeProxy ( { } , undeclaredDependencyError ) ; this . _ = { linker : linker , bundles : [ ] , earlyReducers : [ ] , actionReducers : [ ] , lateReducers : [ ] , defers : [ ] , sagas : [ ] , sealed : false } ; } include ( builder ) { this . _assertNotSealed ( ) ; const bundle = new Bundle ( this . _ . linker , builder ) ; this . _ . bundles . push ( bundle ) ; builder ( bundle , bundle . locals ) ; return bundle . locals ; } use ( ... names ) { this . _assertNotSealed ( ) ; this . _ . linker . declareUse ( this . locals , names ) ; } pack ( ... names ) { this . _assertNotSealed ( ) ; const target = makeSafeProxy ( { } , undeclaredDependencyError ) ; this . _ . linker . declareUse ( target , names ) ; return target ; } defineValue ( name , value ) { this . _assertNotSealed ( ) ; this . _ . linker . publish ( 'value' , name , value ) ; this . use ( name ) ; } defineAction ( name ) { this . _assertNotSealed ( ) ; this . _ . linker . declareActionType ( name ) ; this . _ . linker . publish ( 'action' , name , name ) ; this . use ( name ) ; } addReducer ( name , reducer ) { this . _assertNotSealed ( ) ; if ( reducer === undefined ) { this . _ . lateReducers . push ( name ) ; } else { this . use ( name ) ; this . _ . actionReducers . push ( { name , reducer : reducer } ) ; } } addEarlyReducer ( reducer ) { this . _assertNotSealed ( ) ; this . _ . earlyReducers . push ( reducer ) ; } addLateReducer ( reducer ) { this . _assertNotSealed ( ) ; this . _ . lateReducers . push ( reducer ) ; } addSaga ( saga ) { this . _assertNotSealed ( ) ; this . _ . sagas . push ( saga ) ; } ; defer ( callback ) { this . _assertNotSealed ( ) ; this . _ . defers . push ( callback ) ; } lookup ( name ) { return this . _ . linker . lookup ( name ) ; } _assertNotSealed ( ) { if ( this . _ . sealed ) { throw new Error ( 'Dynamically calling epic-linker directives is not supported.' ) ; } } _runDefers ( ... args ) { for ( let i = 0 ; i < this . _ . defers . length ; i += 1 ) { this . _ . defers [ i ] . call ( null , ... args ) ; } for ( let i = 0 ; i < this . _ . bundles . length ; i += 1 ) { this . _ . bundles [ i ] . _runDefers ( ... args ) ; } } _earlyReducer ( ) { const reducers = this . _ . earlyReducers . concat ( this . _ . bundles . map ( bundle => bundle . _earlyReducer ( ) ) ) ; return reducers . reduce ( reverseCompose , null ) ; } _buildActionMap ( actionMap ) { for ( let i = 0 ; i < this . _ . actionReducers . length ; i += 1 ) { const { name , reducer } = this . _ . actionReducers [ i ] ; const actionType = this . locals [ name ] ; const prevReducer = actionMap . get ( actionType ) ; actionMap . set ( actionType , reverseCompose ( prevReducer , reducer ) ) ; } for ( let i = 0 ; i < this . _ . bundles . length ; i += 1 ) { this . _ . bundles [ i ] . _buildActionMap ( actionMap ) ; } } _lateReducer ( ) { const reducers = this . _ . lateReducers . concat ( this . _ . bundles . map ( bundle => bundle . _lateReducer ( ) ) ) ; return reducers . reduce ( directCompose , null ) ; } _saga ( ) { const { sagas , bundles } = this . _ ; return function * ( args ) { const effects = [ ] ; for ( let saga of sagas ) { effects . push ( call ( saga , ... args ) ) ; } for ( let bundle of bundles ) { effects . push ( call ( bundle . _saga ( ) , args ) ) ; } yield all ( effects ) ; } ; } _seal ( ) { this . _ . sealed = true ; for ( let i = 0 ; i < this . _ . bundles . length ; i += 1 ) { this . _ . bundles [ i ] . _seal ( ) ; } } } function makeSafeProxy ( obj , onError ) { if ( typeof Proxy !== 'function' ) { return obj ; } const safeGet = function ( target , property ) { if ( property in target ) { return target [ property ] ; } else { return onError ( target , property ) ; } } ; return new Proxy ( obj , { get : safeGet } ) ; } function directCompose ( secondReducer , firstReducer ) { if ( ! firstReducer ) { return secondReducer ; } if ( ! secondReducer ) { return firstReducer ; } return ( state , action ) => { firstReducer ( state , action ) ; secondReducer ( state , action ) ; } ; } function reverseCompose ( firstReducer , secondReducer ) { if ( ! firstReducer ) { return secondReducer ; } if ( ! secondReducer ) { return firstReducer ; } return ( state , action ) => { firstReducer ( state , action ) ; secondReducer ( state , action ) ; } ; } function customCombineReducers ( reducers ) { let reducerKeys = Object . keys ( reducers ) ; let finalReducers = { } ; for ( let i = 0 ; i < reducerKeys . length ; i ++ ) { let key = reducerKeys [ i ] ; if ( typeof reducers [ key ] === 'function' ) { finalReducers [ key ] = reducers [ key ] ; } } let finalReducerKeys = Object . keys ( finalReducers ) ; return function combination ( state , action ) { if ( state === void 0 ) { state = { } ; } let hasChanged = false ; let nextState = { ... state } ; for ( let _i = 0 ; _i < finalReducerKeys . length ; _i ++ ) { let _key = finalReducerKeys [ _i ] ; let reducer = finalReducers [ _key ] ; let previousStateForKey = state [ _key ] ; let nextStateForKey = reducer ( previousStateForKey , action ) ; nextState [ _key ] = nextStateForKey ; hasChanged = hasChanged || nextStateForKey !== previousStateForKey ; } hasChanged = hasChanged || finalReducerKeys . length !== Object . keys ( state ) . length ; return hasChanged ? nextState : state ; } ; }