import { O } from 'ts-toolbelt' ; import { isFullArray , isFullString , isPromise } from 'is-what' ; import { handleAction } from './handleAction' ; import { getEventNameFnsMap } from '../types/events' ; import { ActionConfig , MagnetarFetchAction , MagnetarWriteAction , MagnetarDeleteAction , MagnetarDeletePropAction , MagnetarInsertAction , ActionName , FetchPromises , } from '../types/actions' ; import { ActionType , ActionTernary } from '../types/actionsInternal' ; import { FetchResponse , isDoOnFetch , isFetchResponse , DoOnFetch , SyncBatch } from '../types/plugins' ; import { getModifyPayloadFnsMap } from '../types/modifyPayload' ; import { OnAddedFn , getModifyReadResponseFnsMap } from '../types/modifyReadResponse' ; import { executeOnFns } from '../helpers/executeOnFns' ; import { throwIfNoFnsToExecute } from '../helpers/throwFns' ; import { ModuleConfig , GlobalConfig } from '../types/config' ; import { CollectionFn , DocFn , WriteLock } from '../Magnetar' ; import { getPluginModuleConfig } from '../helpers/moduleHelpers' ; import { getCollectionWriteLocks } from '../helpers/pathHelpers' ; export type HandleActionSharedParams = { collectionPath : string ; _docId : string | undefined ; moduleConfig : ModuleConfig ; globalConfig : O . Compulsory < GlobalConfig > ; fetchPromises : FetchPromises ; writeLockMap : Map < string , WriteLock > ; docFn : DocFn ; collectionFn ? : CollectionFn ; } ; export function handleActionPerStore < TActionName extends Exclude < ActionName , 'stream' > > ( sharedParams , actionName , actionType ) ; export function handleActionPerStore ( sharedParams , actionName , actionType ) { const { collectionPath , _docId , moduleConfig , globalConfig , fetchPromises , writeLockMap , docFn , collectionFn } = sharedParams ; return function ( payload ? , actionConfig = { } ) : Promise < any > { const fetchPromiseKey = JSON . stringify ( payload ) ; const foundFetchPromise = fetchPromises . get ( fetchPromiseKey ) ; if ( actionName === 'fetch' && isPromise ( foundFetchPromise ) ) return foundFetchPromise ; const writeLock = _docId ? writeLockMap . get ( `${ collectionPath }/${ _docId }` ) ! : writeLockMap . get ( collectionPath ) ! ; if ( actionName !== 'fetch' ) { if ( writeLock . promise === null ) { writeLock . promise = new Promise ( ( resolve ) => { writeLock . resolve = ( ) => { resolve ( ) ; writeLock . resolve = ( ) => { } ; writeLock . promise = null ; if ( writeLock . countdown !== null ) { clearTimeout ( writeLock . countdown ) ; writeLock . countdown = null ; } } ; } ) ; } if ( writeLock . promise !== null && writeLock . countdown !== null ) { clearTimeout ( writeLock . countdown ) ; } } const actionPromise = new Promise < any > ( async ( resolve , reject ) => { if ( actionName === 'fetch' ) { await writeLock . promise ; if ( ! _docId ) { const collectionWriteMaps = getCollectionWriteLocks ( collectionPath , writeLockMap ) ; await Promise . allSettled ( collectionWriteMaps . map ( ( w ) => w . promise ) ) ; } } try { let docId = _docId ; let modulePath = [ collectionPath , docId ] . filter ( Boolean ) . join ( '/' ) ; const onError = actionConfig . onError || moduleConfig . onError || globalConfig . onError ; const modifyPayloadFnsMap = getModifyPayloadFnsMap ( globalConfig . modifyPayloadOn , moduleConfig . modifyPayloadOn , actionConfig . modifyPayloadOn ) ; const modifyReadResponseMap = getModifyReadResponseFnsMap ( globalConfig . modifyReadResponseOn , moduleConfig . modifyReadResponseOn , actionConfig . modifyReadResponseOn ) ; const eventNameFnsMap = getEventNameFnsMap ( globalConfig . on , moduleConfig . on , actionConfig . on ) ; const storesToExecute = actionConfig . executionOrder || ( moduleConfig . executionOrder || { } ) [ actionName ] || ( moduleConfig . executionOrder || { } ) [ actionType ] || ( globalConfig . executionOrder || { } ) [ actionName ] || ( globalConfig . executionOrder || { } ) [ actionType ] || [ ] ; throwIfNoFnsToExecute ( storesToExecute ) ; for ( const modifyFn of modifyPayloadFnsMap [ actionName ] ) { payload = modifyFn ( payload , docId ) ; } type StopExecution = boolean | 'revert' ; let stopExecution = false as StopExecution ; /**
                 * The abort mechanism for the entire store chain. When executed in handleAction() it won't go to the next store in executionOrder.
                 */ function stopExecutionAfterAction ( trueOrRevert = true ) { stopExecution = trueOrRevert ; } /**
                 * each each time a store returns a `FetchResponse` then all `doOnFetchFns` need to be executed
                 */ const doOnFetchFns = modifyReadResponseMap . added ; /**
                 * All possible results from the plugins.
                 * `unknown` in case an error was thrown
                 */ let resultFromPlugin ; for ( const [ i , storeName ] of storesToExecute . entries ( ) ) { if ( stopExecution === true ) break ; const pluginAction = globalConfig . stores [ storeName ] . actions [ actionName ] ; const pluginModuleConfig = getPluginModuleConfig ( moduleConfig , storeName ) ; resultFromPlugin = ! pluginAction ? resultFromPlugin : await handleAction ( { collectionPath , docId , modulePath , pluginModuleConfig , pluginAction , payload , actionConfig , eventNameFnsMap , onError , actionName , stopExecutionAfterAction , storeName , } ) ; if ( stopExecution === 'revert' ) { const storesToRevert = storesToExecute . slice ( 0 , i ) ; storesToRevert . reverse ( ) ; for ( const storeToRevert of storesToRevert ) { const pluginRevertAction = globalConfig . stores [ storeToRevert ] . revert ; const pluginModuleConfig = getPluginModuleConfig ( moduleConfig , storeToRevert ) ; await pluginRevertAction ( { payload , actionConfig , collectionPath , docId , pluginModuleConfig , actionName , error : resultFromPlugin , } ) ; for ( const fn of eventNameFnsMap . revert ) { await fn ( { payload , result : resultFromPlugin , actionName , storeName , collectionPath , docId , path : modulePath , pluginModuleConfig } ) ; } } throw resultFromPlugin ; } if ( actionName === 'insert' ) { if ( ! docId ) { if ( isFullString ( resultFromPlugin ) ) { docId = resultFromPlugin ; } if ( isFullArray ( resultFromPlugin ) && isFullString ( resultFromPlugin [ 0 ] ) ) { docId = resultFromPlugin [ 0 ] ; } modulePath = [ collectionPath , docId ] . filter ( Boolean ) . join ( '/' ) ; } } if ( actionName === 'fetch' ) { const optimisticFetch = ! payload || ! Object . hasOwnProperty . call ( payload || { } , 'force' ) || payload ?. force === false ; if ( optimisticFetch ) { if ( storeName === globalConfig . localStoreName && isFetchResponse ( resultFromPlugin ) ) { stopExecutionAfterAction ( true ) ; } } if ( isDoOnFetch ( resultFromPlugin ) ) { doOnFetchFns . push ( resultFromPlugin ) ; } if ( isFetchResponse ( resultFromPlugin ) ) { for ( const docRetrieved of resultFromPlugin . docs ) { executeOnFns ( doOnFetchFns , docRetrieved . data , [ docRetrieved ] ) ; } } } } if ( actionName !== 'fetch' ) { writeLock . countdown = setTimeout ( writeLock . resolve , 5000 ) ; } if ( actionName === 'insert' && docId ) { resolve ( docFn ( modulePath , moduleConfig ) ) ; return ; } if ( docId || ! collectionFn ) { resolve ( docFn ( modulePath , moduleConfig ) . data ) ; if ( actionName === 'fetch' ) fetchPromises . delete ( fetchPromiseKey ) ; return ; } resolve ( collectionFn ( modulePath , moduleConfig ) . data ) ; if ( actionName === 'fetch' ) fetchPromises . delete ( fetchPromiseKey ) ; } catch ( error ) { reject ( error ) ; if ( actionName === 'fetch' ) fetchPromises . delete ( fetchPromiseKey ) ; } } ) ; if ( actionName === 'fetch' ) { fetchPromises . set ( fetchPromiseKey , actionPromise ) ; } return actionPromise ; } ; }