import produce , { isDraft } from "immer" ; import cloneDeep = require ( "lodash.clonedeep" ) ; import * as React from "react" ; import { ProdoProvider } from "." ; import { completeEvent , startEvent } from "./events" ; import { ProdoPlugin } from "./plugins" ; import { BaseStore , Origin , PluginDispatch , Provider , WatchTree , } from "./types" ; import { syncIfPossible } from "./utils" ; const initPlugins = ( universe , config , plugins , store ) : any => produce ( universe , u => { plugins . forEach ( p => { if ( p . _internals . init != null ) { p . _internals . init ( config , u , { dispatch : ( ... args ) => store . createDispatch ( p . name ) ( ... args ) , exposedUniverseVars : store . exposedUniverseVars , } ) ; } } ) ; } ) ; const createProvider = < State > ( store , plugins ) : Provider => plugins . reduce ( ( next , plugin ) => plugin . _internals . Provider ? ( { children } ) => React . createElement ( plugin . _internals . Provider ! , { children : React . createElement ( next , { children } ) , } ) : next , ( ( { children } ) => React . createElement ( ProdoProvider , { value : store , children , } ) ) as Provider ) ; export const createStore = < State > ( config , plugins ) : { store : BaseStore < State > ; Provider : React . ComponentType < { children : React . ReactNode ; } > ; } => { const initStore = { createDispatch : ( ) => ( ) => { throw new Error ( "Cannot use the store until all plugins have finished initialising." ) ; } , exposedUniverseVars : plugins . reduce ( ( acc , plugin ) => acc . concat ( plugin . _internals . exposedUniverseVars || [ ] ) , [ ] as string [ ] ) , } ; const universe = initPlugins ( { state : config . initState } , config , plugins , initStore ) ; const watchTree = { subs : new Set ( ) , esubs : new Set ( ) , children : { } , } ; const store = { config , history : [ ] , universe , watchTree , trackHistory : true , plugins , exec : null as any , dispatch : null as any , } ; const createRootDispatch = ( name ) : PluginDispatch < any > => < A extends any [ ] > ( func ) => ( ... args ) => store . exec ( { id : name , parentId : null } , func as any , ... args ) ; store . exec = < A extends any [ ] > ( origin , func , ... args ) => { const event = startEvent ( store , ( func as any ) . __name || "(unnamed)" , ( func as any ) . __pluginName || "(user)" , args , origin ) ; syncIfPossible ( ( ) => produce ( store . universe , u => { const ctx = { state : u . state , dispatch : < A extends any [ ] > ( func ) => ( ... args ) => { event . nextActions . push ( { func : func as any , name : ( func as any ) . __name || "(unnamed)" , args : args . map ( arg => ( isDraft ( arg ) ? cloneDeep ( arg ) : arg ) ) , origin : { parentId : event . id , id : `${ event . id }/${ event . nextActions . length }` , } , } ) ; } , } ; plugins . forEach ( p => { if ( p . _internals . actionCtx ) { ( ctx as any ) . rootDispatch = createRootDispatch ( p . name ) ; p . _internals . actionCtx ( { ctx , universe : u , event , } , config ) ; } } ) ; return syncIfPossible ( ( ) => ( func as any ) ( ctx ) ( ... args ) , ( ) => { } ) ( ) ; } , p => { event . patches = p ; } ) , ( ) => { completeEvent ( event , store ) ; plugins . forEach ( p => { if ( p . _internals . onCompleteEvent ) { p . _internals . onCompleteEvent ( { event , rootDispatch : createRootDispatch ( p . name ) } , config ) ; } } ) ; } ) ( ) ; } ; store . dispatch = < A extends any [ ] > ( func ) => async ( ... args ) => { let actionsCompleted ; if ( store . watchForComplete != null ) { const previous = store . watchForComplete ; actionsCompleted = new Promise ( async ( r ) => { store . watchForComplete = { count : previous . count + 1 , cb : ( ) => { previous . cb ( ) ; r ( ) ; } , } ; } ) ; } else { actionsCompleted = new Promise ( async ( r ) => { store . watchForComplete = { count : 1 , cb : r , } ; } ) ; } await store . exec ( { id : "dispatch" , parentId : null , } , func , ... args ) ; await actionsCompleted ; store . watchForComplete = undefined ; return store . universe ; } ; initStore . createDispatch = createRootDispatch ; const Provider = createProvider ( store , plugins ) ; return { store , Provider , } ; } ;