"use strict" ; import { Dispatcher } from "./Dispatcher" ; import { UseCase } from "./UseCase" ; import { DispatcherPayloadMeta , DispatcherPayloadMetaImpl } from "./DispatcherPayloadMeta" ; import { UseCaseInstanceMap } from "./UseCaseInstanceMap" ; import { CompletedPayload } from "./payload/CompletedPayload" ; import { DidExecutedPayload } from "./payload/DidExecutedPayload" ; import { WillExecutedPayload } from "./payload/WillExecutedPayload" ; import { UseCaseLike } from "./UseCaseLike" ; import { Payload } from "./payload/Payload" ; import { WillNotExecutedPayload } from "./payload/WillNotExecutedPayload" ; import { assertOK } from "./util/assert" ; import { Events } from "./Events" ; type Arguments < F extends ( ... x : any [ ] ) => any > = F extends ( ... x : infer A ) => any ? A : never ; interface InvalidUsage { type : "InvalidUsage" ; error : Error ; } interface ShouldNotExecute { type : "ShouldNotExecute" ; } interface ErrorInExecute { type : "ErrorInExecute" ; error : Error ; } interface SuccessExecuteReturnValue { type : "SuccessExecuteReturnValue" ; value : any ; } interface SuccessExecuteNoReturnValue { type : "SuccessExecuteNoReturnValue" ; } type EXECUTING_RESULT = InvalidUsage | ShouldNotExecute | ErrorInExecute | SuccessExecuteReturnValue | SuccessExecuteNoReturnValue ; interface newProxifyUseCaseArgs { onWillNotExecute ( args ) : void ; onWillExecute ( args ) : void ; onDidExecute ( result ? ) : void ; onError ( error ) : void ; } /**
 * Create wrapper object of a UseCase.
 * This wrapper object only has `execute()` method.
 */ const proxifyUseCase = < T extends UseCaseLike > ( useCase , handlers ) : T => { let isExecuted = false ; const execute = ( ... args ) : EXECUTING_RESULT => { if ( process . env . NODE_ENV !== "production" ) { if ( isExecuted ) { console . error ( `Warning(UseCase): ${ useCase . name }#execute was called more than once.` ) ; } } isExecuted = true ; if ( typeof useCase . shouldExecute === "function" ) { const shouldExecute = useCase . shouldExecute ( args ) ; if ( typeof shouldExecute !== "boolean" ) { const error = new Error ( `<${ useCase . name }>#shouldExecute should return boolean value. Actual return value: ${ shouldExecute }` ) ; return { type : "InvalidUsage" , error } ; } if ( ! shouldExecute ) { handlers . onWillNotExecute ( args ) ; return { type : "ShouldNotExecute" } ; } } handlers . onWillExecute ( args ) ; try { const result = useCase . execute ( ... args ) ; handlers . onDidExecute ( result ) ; return { type : "SuccessExecuteReturnValue" , value : result } ; } catch ( error ) { handlers . onError ( error ) ; handlers . onDidExecute ( ) ; return { type : "ErrorInExecute" , error } ; } } ; if ( process . env . NODE_ENV !== "production" ) { ( execute as any ) . displayName = `Wrapped<${ useCase . name }>#execute` ; } return { execute : execute } as T ; } ; private const warningUseCaseIsAlreadyReleased = ( parentUseCase , useCase , payload , meta ) => { console . error ( `Warning(UseCase): ${ useCase . name }'s parent UseCase(${ parentUseCase . name }) is already released.
This UseCase(${ useCase . name }) will not work correctly.
https://almin.js.org/docs/warnings/usecase-is-already-released.html
` , payload , meta ) ; } ; export interface UseCaseExecutor < T extends UseCaseLike > extends Dispatcher { useCase : T ; execute < P extends Arguments < T [ "execute" ] > > ( ... args ) : Promise < void > ; executor ( executor ) : Promise < void > ; release ( ) : void ; onReleaseOnce ( handler ) : void ; } /**
 * `UseCaseExecutor` is a helper class for executing UseCase.
 *
 * You can not create the instance of UseCaseExecutor directory.
 * You can get the instance by `Context#useCase(useCase)`,
 *
 */ export class UseCaseExecutorImpl < T extends UseCaseLike > extends Dispatcher implements UseCaseExecutor < T > { /**
     * A executable useCase
     */ useCase : T ; /**
     * A parent useCase
     */ private _parentUseCase : UseCase | null ; /**
     * callable release handlers that are called in release()
     */ private _releaseHandlers : Array < ( ) => void > ; private releaseEvents : Events < void > ; param useCase param parent param dispatcher public constructor ( { useCase , parent } ) { super ( ) ; if ( process . env . NODE_ENV !== "production" ) { const useCaseName = useCase . name ; assertOK ( typeof useCaseName === "string" , `UseCase instance should have constructor.name ${ useCase }` ) ; assertOK ( typeof useCase . execute === "function" , `UseCase instance should have #execute function: ${ useCaseName }` ) ; } this . useCase = useCase ; this . _parentUseCase = parent ; this . _releaseHandlers = [ ] ; /**
         * ## Delegating Payload
         *
         * UseCase -> UseCaseExecutor -> UnitOfWork -> Dispatcher
         *
         */ const unListenUseCaseToDispatcherHandler = this . useCase . pipe ( this ) ; this . _releaseHandlers . push ( unListenUseCaseToDispatcherHandler ) ; if ( this . _parentUseCase ) { const unListenUseCaseExecutorToDispatcherHandler = this . pipe ( this . _parentUseCase ) ; this . _releaseHandlers . push ( unListenUseCaseExecutorToDispatcherHandler ) ; } this . releaseEvents = new Events < void > ( ) ; } private willNotExecuteUseCase ( args ? ) { const payload = new WillNotExecutedPayload ( { args } ) ; const meta = new DispatcherPayloadMetaImpl ( { useCase : this . useCase , parentUseCase : this . _parentUseCase , isTrusted : true , isUseCaseFinished : true } ) ; this . dispatch ( payload , meta ) ; } param args private willExecuteUseCase ( args ? ) { UseCaseInstanceMap . set ( this . useCase , this as UseCaseExecutor < T > ) ; const payload = new WillExecutedPayload ( { args } ) ; const meta = new DispatcherPayloadMetaImpl ( { useCase : this . useCase , parentUseCase : this . _parentUseCase , isTrusted : true , isUseCaseFinished : false } ) ; this . dispatch ( payload , meta ) ; if ( process . env . NODE_ENV !== "production" ) { if ( this . _parentUseCase && ! UseCaseInstanceMap . has ( this . _parentUseCase ) ) { warningUseCaseIsAlreadyReleased ( this . _parentUseCase , this . useCase , payload , meta ) ; } } } /**
     * dispatch did execute each UseCase
     */ private didExecuteUseCase ( value ? ) { const isResultPromise = typeof value === "object" && value !== null && typeof value . then == "function" ; const isUseCaseFinished = ! isResultPromise ; const payload = new DidExecutedPayload ( { value } ) ; const meta = new DispatcherPayloadMetaImpl ( { useCase : this . useCase , parentUseCase : this . _parentUseCase , isTrusted : true , isUseCaseFinished } ) ; this . dispatch ( payload , meta ) ; } param value private completeUseCase ( value ? ) { const payload = new CompletedPayload ( { value } ) ; const meta = new DispatcherPayloadMetaImpl ( { useCase : this . useCase , parentUseCase : this . _parentUseCase , isTrusted : true , isUseCaseFinished : true } ) ; this . dispatch ( payload , meta ) ; if ( process . env . NODE_ENV !== "production" ) { if ( this . _parentUseCase && ! UseCaseInstanceMap . has ( this . _parentUseCase ) ) { warningUseCaseIsAlreadyReleased ( this . _parentUseCase , this . useCase , payload , meta ) ; } } UseCaseInstanceMap . delete ( this . useCase ) ; } param handler onReleaseOnce ( handler ) { this . releaseEvents . addEventListenerOnce ( handler ) ; } deprecated executor ( executor ) { console . warn ( `Warning(UseCase): \`executor(useCase => useCase.execute(args))\` is deprecated. 
        
Use \`execute(args)\` insteadof it.        
See https://github.com/almin/almin/issues/356` ) ; if ( typeof executor !== "function" ) { console . error ( "Warning(UseCase): executor argument should be function. But this argument is not function: " , executor ) ; return Promise . reject ( new Error ( "executor(fn) arguments should be function" ) ) ; } const proxyfiedUseCase = proxifyUseCase < T > ( this . useCase , { onWillNotExecute : ( args ) => { this . willNotExecuteUseCase ( args ) ; } , onWillExecute : ( args ) => { this . willExecuteUseCase ( args ) ; } , onDidExecute : ( result ? ) => { this . didExecuteUseCase ( result ) ; } , onError : ( error ) => { this . useCase . throwError ( error ) ; } } ) ; const executorResult = executor ( proxyfiedUseCase ) ; const executedResult = executorResult !== undefined ? executorResult : { type : "SuccessExecuteNoReturnValue" } ; if ( executedResult . type === "ShouldNotExecute" ) { this . release ( ) ; return Promise . resolve ( ) ; } const startingExecutor = new Promise ( ( resolve , reject ) => { switch ( executedResult . type ) { case "InvalidUsage" : return reject ( executedResult . error ) ; case "ErrorInExecute" : return reject ( executedResult . error ) ; case "SuccessExecuteReturnValue" : { return Promise . resolve ( executedResult . value ) . then ( resolve , error => { this . useCase . throwError ( error ) ; return reject ( error ) ; } ) ; } case "SuccessExecuteNoReturnValue" : return resolve ( ) ; } } ) ; return startingExecutor . then ( result => { this . completeUseCase ( result ) ; this . release ( ) ; } ) . catch ( error => { this . completeUseCase ( ) ; this . release ( ) ; return Promise . reject ( error ) ; } ) ; } /**
     * Execute UseCase instance.
     * UseCase is a executable object that has `execute` method.
     *
     * This method invoke UseCase's `execute` method and return a promise<void>.
     * The promise will be resolved when the UseCase is completed finished.
     *
     * ### Why executor's result always to be undefined?
     *
     * `execute()` return a Promise that will resolve `undefined` by design.
     * In CQRS, the command always have returned void type.
     *
     * - http://cqrs.nu/Faq
     *
     * So, `execute()` only return command result that is success or failure.
     * You should not relay on the result value of the useCase#execute.
     *
     * ## Notes
     *
     * > Added: Almin 0.17.0+
     *
     * `execute()` support type check in Almin 0.17.0.
     * However, it has a limitation about argument lengths.
     * For more details, please see <https://github.com/almin/almin/issues/107#issuecomment-384993458>
     *
     * > Added: Almin 0.18.0+
     *
     * `execute()` support type check completely.
     * No more need to use `executor()` for typing.
     *
     */ execute < P extends Arguments < T [ "execute" ] > > ( ... args ) { const proxyfiedUseCase = proxifyUseCase < T > ( this . useCase , { onWillNotExecute : ( args ) => { this . willNotExecuteUseCase ( args ) ; } , onWillExecute : ( args ) => { this . willExecuteUseCase ( args ) ; } , onDidExecute : ( result ? ) => { this . didExecuteUseCase ( result ) ; } , onError : ( error ) => { this . useCase . throwError ( error ) ; } } ) ; const executorResult = proxyfiedUseCase . execute ( ... args ) ; const executedResult = executorResult !== undefined ? executorResult : { type : "SuccessExecuteNoReturnValue" } ; if ( executedResult . type === "ShouldNotExecute" ) { this . release ( ) ; return Promise . resolve ( ) ; } const startingExecutor = new Promise ( ( resolve , reject ) => { switch ( executedResult . type ) { case "InvalidUsage" : return reject ( executedResult . error ) ; case "ErrorInExecute" : return reject ( executedResult . error ) ; case "SuccessExecuteReturnValue" : { return Promise . resolve ( executedResult . value ) . then ( resolve , error => { this . useCase . throwError ( error ) ; return reject ( error ) ; } ) ; } case "SuccessExecuteNoReturnValue" : return resolve ( ) ; } } ) ; return startingExecutor . then ( result => { this . completeUseCase ( result ) ; this . release ( ) ; } ) . catch ( error => { this . completeUseCase ( ) ; this . release ( ) ; return Promise . reject ( error ) ; } ) ; } /**
     * release all events handler.
     * You can call this when no more call event handler
     *
     * **internal**
     */ release ( ) { this . _releaseHandlers . forEach ( releaseHandler => releaseHandler ( ) ) ; this . _releaseHandlers . length = 0 ; this . releaseEvents . emit ( undefined ) ; } }