import { action as mobxAction } from "mobx" ; import { getStateTreeNode , fail , argsToArray , IDisposer , getRoot , Hook , IAnyStateTreeNode , warnError , AnyObjectNode , devMode , IActionContext } from "../internal" ; export type IMiddlewareEventType = "action" | "flow_spawn" | "flow_resume" | "flow_resume_error" | "flow_return" | "flow_throw" ; export interface IMiddlewareEvent extends IActionContext { /** Event type */ readonly type : IMiddlewareEventType ; /** Parent event unique id */ readonly parentId : number ; /** Parent event object */ readonly parentEvent : IMiddlewareEvent | undefined ; /** Root event unique id */ readonly rootId : number ; /** Id of all events, from root until current (excluding current) */ readonly allParentIds : number [ ] ; } internal hidden export type IMiddleware = { handler : IMiddlewareHandler ; includeHooks : boolean ; } ; export type IMiddlewareHandler = ( actionCall : IMiddlewareEvent , next : ( actionCall : IMiddlewareEvent , callback ? : ( value : any ) => any ) => void , abort : ( value : any ) => void ) => any ; let nextActionId = 1 ; let currentActionContext ; internal hidden export function getCurrentActionContext ( ) { return currentActionContext ; } internal hidden export function getNextActionId ( ) { return nextActionId ++ ; } internal hidden export function runWithActionContext ( context , fn ) { const node = getStateTreeNode ( context . context ) ; if ( context . type === "action" ) { node . assertAlive ( { actionContext : context } ) ; } const baseIsRunningAction = node . _isRunningAction ; node . _isRunningAction = true ; const previousContext = currentActionContext ; currentActionContext = context ; try { return runMiddleWares ( node , context , fn ) ; } finally { currentActionContext = previousContext ; node . _isRunningAction = baseIsRunningAction ; } } internal hidden export function getParentActionContext ( parentContext ) { if ( ! parentContext ) return undefined ; if ( parentContext . type === "action" ) return parentContext ; return parentContext . parentActionEvent ; } internal hidden export function createActionInvoker < T extends Function > ( target , name , fn ) { const res = function ( ) { const id = getNextActionId ( ) ; const parentContext = currentActionContext ; const parentActionContext = getParentActionContext ( parentContext ) ; return runWithActionContext ( { type : "action" , name , id , args : argsToArray ( arguments ) , context : target , tree : getRoot ( target ) , rootId : parentContext ? parentContext . rootId : id , parentId : parentContext ? parentContext . id : 0 , allParentIds : parentContext ? [ ... parentContext . allParentIds , parentContext . id ] : [ ] , parentEvent : parentContext , parentActionEvent : parentActionContext } , fn ) ; } ; ( res as any ) . _isMSTAction = true ; return res ; } param target param middleware returns export function addMiddleware ( target , handler , includeHooks = true ) { const node = getStateTreeNode ( target ) ; if ( devMode ( ) ) { if ( ! node . isProtectionEnabled ) { warnError ( "It is recommended to protect the state tree before attaching action middleware, as otherwise it cannot be guaranteed that all changes are passed through middleware. See `protect`" ) ; } } return node . addMiddleWare ( handler , includeHooks ) ; } param handler param fn param includeHooks returns export function decorate < T extends Function > ( handler , fn , includeHooks = true ) { const middleware = { handler , includeHooks } ; ( fn as any ) . $mst_middleware = ( fn as any ) . $mst_middleware || [ ] ; ( fn as any ) . $mst_middleware . push ( middleware ) ; return fn ; } class CollectedMiddlewares { private arrayIndex = 0 ; private inArrayIndex = 0 ; private middlewares : IMiddleware [ ] [ ] = [ ] ; constructor ( node , fn ) { if ( ( fn as any ) . $mst_middleware ) { this . middlewares . push ( ( fn as any ) . $mst_middleware ) ; } let n = node ; while ( n ) { if ( n . middlewares ) this . middlewares . push ( n . middlewares ) ; n = n . parent ; } } get isEmpty ( ) { return this . middlewares . length <= 0 ; } getNextMiddleware ( ) { const array = this . middlewares [ this . arrayIndex ] ; if ( ! array ) return undefined ; const item = array [ this . inArrayIndex ++ ] ; if ( ! item ) { this . arrayIndex ++ ; this . inArrayIndex = 0 ; return this . getNextMiddleware ( ) ; } return item ; } } function runMiddleWares ( node , baseCall , originalFn ) { const middlewares = new CollectedMiddlewares ( node , originalFn ) ; if ( middlewares . isEmpty ) return mobxAction ( originalFn ) . apply ( null , baseCall . args ) ; let result = null ; function runNextMiddleware ( call ) { const middleware = middlewares . getNextMiddleware ( ) ; const handler = middleware && middleware . handler ; if ( ! handler ) { return mobxAction ( originalFn ) . apply ( null , call . args ) ; } if ( ! middleware ! . includeHooks && ( Hook as any ) [ call . name ] ) { return runNextMiddleware ( call ) ; } let nextInvoked = false ; function next ( call2 , callback ? ) { nextInvoked = true ; result = runNextMiddleware ( call2 ) ; if ( callback ) { result = callback ( result ) ; } } let abortInvoked = false ; function abort ( value ) { abortInvoked = true ; result = value ; } handler ( call , next , abort ) ; if ( devMode ( ) ) { if ( ! nextInvoked && ! abortInvoked ) { const node2 = getStateTreeNode ( call . tree ) ; throw fail ( `Neither the next() nor the abort() callback within the middleware ${ handler . name } for the action: "${ call . name }" on the node: ${ node2 . type . name } was invoked.` ) ; } else if ( nextInvoked && abortInvoked ) { const node2 = getStateTreeNode ( call . tree ) ; throw fail ( `The next() and abort() callback within the middleware ${ handler . name } for the action: "${ call . name }" on the node: ${ node2 . type . name } were invoked.` ) ; } } return result ; } return runNextMiddleware ( baseCall ) ; }