import { Event , StateValue , ActionType , Action , EventObject , PropertyMapper , Mapper , EventType , HistoryValue , AssignAction , Condition , Subscribable , StateMachine , ConditionPredicate , SCXML , StateLike , EventData , TransitionConfig , TransitionConfigTarget , NullEvent , SingleOrArray , Guard , GuardMeta , InvokeSourceDefinition , Observer , Behavior } from './types' ; import { STATE_DELIMITER , DEFAULT_GUARD_TYPE , TARGETLESS_KEY } from './constants' ; import { IS_PRODUCTION } from './environment' ; import { StateNode } from './StateNode' ; import { State } from './State' ; import { Actor } from './Actor' ; export function keys < T extends object > ( value ) { return Object . keys ( value ) as Array < keyof T & string > ; } export function matchesState ( parentStateId , childStateId , delimiter = STATE_DELIMITER ) { const parentStateValue = toStateValue ( parentStateId , delimiter ) ; const childStateValue = toStateValue ( childStateId , delimiter ) ; if ( isString ( childStateValue ) ) { if ( isString ( parentStateValue ) ) { return childStateValue === parentStateValue ; } return false ; } if ( isString ( parentStateValue ) ) { return parentStateValue in childStateValue ; } return keys ( parentStateValue ) . every ( ( key ) => { if ( ! ( key in childStateValue ) ) { return false ; } return matchesState ( parentStateValue [ key ] , childStateValue [ key ] ) ; } ) ; } export function getEventType < TEvent extends EventObject = EventObject > ( event ) { try { return isString ( event ) || typeof event === 'number' ? `${ event }` : ( event as TEvent ) . type ; } catch ( e ) { throw new Error ( 'Events must be strings or objects with a string event.type property.' ) ; } } export function getActionType ( action ) { try { return isString ( action ) || typeof action === 'number' ? `${ action }` : isFunction ( action ) ? action . name : action . type ; } catch ( e ) { throw new Error ( 'Actions must be strings or objects with a string action.type property.' ) ; } } export function toStatePath ( stateId , delimiter ) { try { if ( isArray ( stateId ) ) { return stateId ; } return stateId . toString ( ) . split ( delimiter ) ; } catch ( e ) { throw new Error ( `'${ stateId }' is not a valid state path.` ) ; } } export function isStateLike ( state ) { return ( typeof state === 'object' && 'value' in state && 'context' in state && 'event' in state && '_event' in state ) ; } export function toStateValue ( stateValue , delimiter ) { if ( isStateLike ( stateValue ) ) { return stateValue . value ; } if ( isArray ( stateValue ) ) { return pathToStateValue ( stateValue ) ; } if ( typeof stateValue !== 'string' ) { return stateValue as StateValue ; } const statePath = toStatePath ( stateValue as string , delimiter ) ; return pathToStateValue ( statePath ) ; } export function pathToStateValue ( statePath ) { if ( statePath . length === 1 ) { return statePath [ 0 ] ; } const value = { } ; let marker = value ; for ( let i = 0 ; i < statePath . length - 1 ; i ++ ) { if ( i === statePath . length - 2 ) { marker [ statePath [ i ] ] = statePath [ i + 1 ] ; } else { marker [ statePath [ i ] ] = { } ; marker = marker [ statePath [ i ] ] ; } } return value ; } export function mapValues < T , P , O extends { [ key ] : T ; } > ( collection , iteratee ) { const result = { } ; const collectionKeys = keys ( collection ) ; for ( let i = 0 ; i < collectionKeys . length ; i ++ ) { const key = collectionKeys [ i ] ; result [ key ] = iteratee ( collection [ key ] , key , collection , i ) ; } return result as { [ key in keyof O ] : P ; } ; } export function mapFilterValues < T , P > ( collection , iteratee , predicate ) { const result = { } ; for ( const key of keys ( collection ) ) { const item = collection [ key ] ; if ( ! predicate ( item ) ) { continue ; } result [ key ] = iteratee ( item , key , collection ) ; } return result ; } param props export const path = < T extends Record < string , any > > ( props ) : any => ( object ) : any => { let result = object ; for ( const prop of props ) { result = result [ prop as keyof typeof result ] ; } return result ; } ; param props export function nestedPath < T extends Record < string , any > > ( props , accessorProp ) { return ( object ) => { let result = object ; for ( const prop of props ) { result = result [ accessorProp ] [ prop ] ; } return result ; } ; } export function toStatePaths ( stateValue ) { if ( ! stateValue ) { return [ [ ] ] ; } if ( isString ( stateValue ) ) { return [ [ stateValue ] ] ; } const result = flatten ( keys ( stateValue ) . map ( ( key ) => { const subStateValue = stateValue [ key ] ; if ( typeof subStateValue !== 'string' && ( ! subStateValue || ! Object . keys ( subStateValue ) . length ) ) { return [ [ key ] ] ; } return toStatePaths ( stateValue [ key ] ) . map ( ( subPath ) => { return [ key ] . concat ( subPath ) ; } ) ; } ) ) ; return result ; } export function pathsToStateValue ( paths ) { const result = { } ; if ( paths && paths . length === 1 && paths [ 0 ] . length === 1 ) { return paths [ 0 ] [ 0 ] ; } for ( const currentPath of paths ) { let marker = result ; for ( let i = 0 ; i < currentPath . length ; i ++ ) { const subPath = currentPath [ i ] ; if ( i === currentPath . length - 2 ) { marker [ subPath ] = currentPath [ i + 1 ] ; break ; } marker [ subPath ] = marker [ subPath ] || { } ; marker = marker [ subPath ] as { } ; } } return result ; } export function flatten < T > ( array ) { return ( [ ] as T [ ] ) . concat ( ... array ) ; } export function toArrayStrict < T > ( value ) { if ( isArray ( value ) ) { return value ; } return [ value ] ; } export function toArray < T > ( value ) { if ( value === undefined ) { return [ ] ; } return toArrayStrict ( value ) ; } export function mapContext < TContext , TEvent extends EventObject > ( mapper , context , _event ) { if ( isFunction ( mapper ) ) { return mapper ( context , _event . data ) ; } const result = { } as any ; for ( const key of Object . keys ( mapper ) ) { const subMapper = mapper [ key ] ; if ( isFunction ( subMapper ) ) { result [ key ] = subMapper ( context , _event . data ) ; } else { result [ key ] = subMapper ; } } return result ; } export function isBuiltInEvent ( eventType ) { return /^(done|error)\./ . test ( eventType ) ; } export function isPromiseLike ( value ) { if ( value instanceof Promise ) { return true ; } if ( value !== null && ( isFunction ( value ) || typeof value === 'object' ) && isFunction ( value . then ) ) { return true ; } return false ; } export function isBehavior ( value ) { return ( value !== null && typeof value === 'object' && 'transition' in value && typeof value . transition === 'function' ) ; } export function partition < T , A extends T , B extends T > ( items , predicate ) { const [ truthy , falsy ] = [ [ ] , [ ] ] as [ A [ ] , B [ ] ] ; for ( const item of items ) { if ( predicate ( item ) ) { truthy . push ( item ) ; } else { falsy . push ( item as B ) ; } } return [ truthy , falsy ] ; } export function updateHistoryStates ( hist , stateValue ) { return mapValues ( hist . states , ( subHist , key ) => { if ( ! subHist ) { return undefined ; } const subStateValue = ( isString ( stateValue ) ? undefined : stateValue [ key ] ) || ( subHist ? subHist . current : undefined ) ; if ( ! subStateValue ) { return undefined ; } return { current : subStateValue , states : updateHistoryStates ( subHist , subStateValue ) } ; } ) ; } export function updateHistoryValue ( hist , stateValue ) { return { current : stateValue , states : updateHistoryStates ( hist , stateValue ) } ; } export function updateContext < TContext , TEvent extends EventObject > ( context , _event , assignActions , state ? ) { if ( ! IS_PRODUCTION ) { warn ( ! ! context , 'Attempting to update undefined context' ) ; } const updatedContext = context ? assignActions . reduce ( ( acc , assignAction ) => { const { assignment } = assignAction as AssignAction < TContext , TEvent > ; const meta = { state , action : assignAction , _event } ; let partialUpdate = { } ; if ( isFunction ( assignment ) ) { partialUpdate = assignment ( acc , _event . data , meta ) ; } else { for ( const key of keys ( assignment ) ) { const propAssignment = assignment [ key ] ; partialUpdate [ key ] = isFunction ( propAssignment ) ? propAssignment ( acc , _event . data , meta ) : propAssignment ; } } return Object . assign ( { } , acc , partialUpdate ) ; } , context ) : context ; return updatedContext ; } let warn = ( ) => { } ; if ( ! IS_PRODUCTION ) { warn = ( condition , message ) => { const error = condition instanceof Error ? condition : undefined ; if ( ! error && condition ) { return ; } if ( console !== undefined ) { const args = [ `Warning: ${ message }` ] ; if ( error ) { args . push ( error ) ; } console . warn . apply ( console , args ) ; } } ; } export { warn } ; export function isArray ( value ) { return Array . isArray ( value ) ; } export function isFunction ( value ) { return typeof value === 'function' ; } export function isString ( value ) { return typeof value === 'string' ; } export function toGuard < TContext , TEvent extends EventObject > ( condition ? , guardMap ? ) { if ( ! condition ) { return undefined ; } if ( isString ( condition ) ) { return { type : DEFAULT_GUARD_TYPE , name : condition , predicate : guardMap ? guardMap [ condition ] : undefined } ; } if ( isFunction ( condition ) ) { return { type : DEFAULT_GUARD_TYPE , name : condition . name , predicate : condition } ; } return condition ; } export function isObservable < T > ( value ) { try { return 'subscribe' in value && isFunction ( value . subscribe ) ; } catch ( e ) { return false ; } } export const symbolObservable = ( ( ) => ( typeof Symbol === 'function' && Symbol . observable ) || '@@observable' ) ( ) ; export const interopSymbols = { [ symbolObservable ] : function ( ) { return this ; } , [ Symbol . observable ] : function ( ) { return this ; } } ; export function isMachine ( value ) { try { return '__xstatenode' in value ; } catch ( e ) { return false ; } } export function isActor ( value ) { return ! ! value && typeof value . send === 'function' ; } export const uniqueId = ( ( ) => { let currentId = 0 ; return ( ) => { currentId ++ ; return currentId . toString ( 16 ) ; } ; } ) ( ) ; export function toEventObject < TEvent extends EventObject > ( event , payload ? ) { if ( isString ( event ) || typeof event === 'number' ) { return { type : event , ... payload } as TEvent ; } return event ; } export function toSCXMLEvent < TEvent extends EventObject > ( event , scxmlEvent ? ) { if ( ! isString ( event ) && '$$type' in event && event . $$type === 'scxml' ) { return event as SCXML . Event < TEvent > ; } const eventObject = toEventObject ( event as Event < TEvent > ) ; return { name : eventObject . type , data : eventObject , $$type : 'scxml' , type : 'external' , ... scxmlEvent } ; } export function toTransitionConfigArray < TContext , TEvent extends EventObject > ( event , configLike ) { const transitions = toArrayStrict ( configLike ) . map ( ( transitionLike ) => { if ( typeof transitionLike === 'undefined' || typeof transitionLike === 'string' || isMachine ( transitionLike ) ) { return { target : transitionLike , event } ; } return { ... transitionLike , event } ; } ) as Array < TransitionConfig < TContext , TEvent > & { event : TEvent [ 'type' ] | NullEvent [ 'type' ] | '*' ; } > ; return transitions ; } export function normalizeTarget < TContext , TEvent extends EventObject > ( target ) { if ( target === undefined || target === TARGETLESS_KEY ) { return undefined ; } return toArray ( target ) ; } export function reportUnhandledExceptionOnInvocation ( originalError , currentError , id ) { if ( ! IS_PRODUCTION ) { const originalStackTrace = originalError . stack ? ` Stacktrace was '${ originalError . stack }'` : '' ; if ( originalError === currentError ) { console . error ( `Missing onError handler for invocation '${ id }', error was '${ originalError }'.${ originalStackTrace }` ) ; } else { const stackTrace = currentError . stack ? ` Stacktrace was '${ currentError . stack }'` : '' ; console . error ( `Missing onError handler and/or unhandled exception/promise rejection for invocation '${ id }'. ` + `Original error: '${ originalError }'. ${ originalStackTrace } Current error is '${ currentError }'.${ stackTrace }` ) ; } } } export function evaluateGuard < TContext , TEvent extends EventObject > ( machine , guard , context , _event , state ) { const { guards } = machine . options ; const guardMeta = { state , cond : guard , _event } ; if ( guard . type === DEFAULT_GUARD_TYPE ) { return ( guards ?. [ guard . name ] || guard . predicate ) ( context , _event . data , guardMeta ) ; } const condFn = guards [ guard . type ] ; if ( ! condFn ) { throw new Error ( `Guard '${ guard . type }' is not implemented on machine '${ machine . id }'.` ) ; } return condFn ( context , _event . data , guardMeta ) ; } export function toInvokeSource ( src ) { if ( typeof src === 'string' ) { return { type : src } ; } return src ; } export function toObserver < T > ( nextHandler , errorHandler ? , completionHandler ? ) { if ( typeof nextHandler === 'object' ) { return nextHandler ; } const noop = ( ) => void 0 ; return { next : nextHandler , error : errorHandler || noop , complete : completionHandler || noop } ; } export function createInvokeId ( stateNodeId , index ) { return `${ stateNodeId }:invocation[${ index }]` ; }