src/index.js
function processNode({rawNode, parsedNode, parentId = resourceId, cursor, refChain, prop}) {

      logger.info(`processNode ${cursor}${prop ? '(' + prop + ')' : ''} with refChain ${refChain}`)

      const currentId = options.urlBaseKey && rawNode[options.urlBaseKey]
      ? parseRef(rawNode[options.urlBaseKey], parentId).fullUrl
      : parentId

      const props = !!prop ? [prop] : Object.getOwnPropertyNames(rawNode)

      let propIndex = -1, nodeChanged = 0

      return new Promise((accept, reject) => {
        nextProp()

        function nextProp() {
          propIndex++

          if(propIndex >= props.length) {
            if (!!prop) {
              accept(parsedNode[prop])
            } else {
              accept(nodeChanged ? parsedNode : rawNode)
            }

          } else {
            const prop = props[propIndex]
            const sourceValue = rawNode[prop]
            const propCursor = `${cursor}/${prop}`

            // If prop is already defined in parsedNode assume complete and skip it
            if(parsedNode.hasOwnProperty(prop)){
              nodeChanged |= parsedNode[prop] !== sourceValue
              nextProp()
            }
            // Is scalar, just set same and continue
            else if (typeof sourceValue !== 'object' || sourceValue === null) {
              parsedNode[prop] = sourceValue
              nextProp()
            }
            // prop is a reference
            else if(!!sourceValue.$ref) {

              const {$ref, ...params} = sourceValue
              const branchRefChain = [...refChain, propCursor]
              const {url, pointer, isLocalRef, isCircular} = parseRef($ref, currentId, branchRefChain) //, options.vars)
              if(!url) throw new Error(`Invalid $ref ${$ref}`)

              if(isCircular && options.skipCircular || isLocalRef && externalOnly) {
              // if(isLocalRef && externalOnly) {
                parsedNode[prop] = sourceValue
                nextProp()
              } else {
                Promise.resolve(isLocalRef
                  ? solvePointer(pointer, branchRefChain, currentId)
                  : externalOnly && pointer
                   ? processResource(url, pointer, branchRefChain, false)
                   : processResource(url, pointer, branchRefChain)
                )
                .then(newValue => {
                  nodeChanged = 1
                  parsedNode[prop] = newValue
                  nextProp()
                })
                .catch(err => {
                  const log = `Error derefing ${cursor}/${prop}`
                  if (options.failOnMissing) {
                    reject(log)
                  } else {
                    logger.info(log)
                    parsedNode[prop] = sourceValue
                    nextProp()
                  }
                })

              }

            } else {
              const placeholder = parsedNode[prop] = Array.isArray(sourceValue) ? [] : {}
              processNode({
                rawNode: sourceValue,
                parsedNode: placeholder,
                parentId: currentId,
                cursor: propCursor,
                refChain
              })
              .then(newValue => {
                nodeChanged |= newValue !== sourceValue
                nextProp()
              })
              .catch(reject)
            }
          }
        }
      })
    }
function processNode({rawNode, parsedNode, parentId = resourceId, cursor, refChain, prop}) {

      logger.info(`processNode ${cursor}${prop ? '(' + prop + ')' : ''} with refChain ${refChain}`)

      const currentId = options.urlBaseKey && rawNode[options.urlBaseKey]
      ? parseRef(rawNode[options.urlBaseKey], parentId).fullUrl
      : parentId

      const props = !!prop ? [prop] : Object.getOwnPropertyNames(rawNode)

      let propIndex = -1, nodeChanged = 0

      return new Promise((accept, reject) => {
        nextProp()

        function nextProp() {
          propIndex++

          if(propIndex >= props.length) {
            if (!!prop) {
              accept(parsedNode[prop])
            } else {
              accept(nodeChanged ? parsedNode : rawNode)
            }

          } else {
            const prop = props[propIndex]
            const sourceValue = rawNode[prop]
            const propCursor = `${cursor}/${prop}`

            // If prop is already defined in parsedNode assume complete and skip it
            if(parsedNode.hasOwnProperty(prop)){
              nodeChanged |= parsedNode[prop] !== sourceValue
              nextProp()
            }
            // Is scalar, just set same and continue
            else if (typeof sourceValue !== 'object' || sourceValue === null) {
              parsedNode[prop] = sourceValue
              nextProp()
            }
            // prop is a reference
            else if(!!sourceValue.$ref) {

              const {$ref, ...params} = sourceValue
              const branchRefChain = [...refChain, propCursor]
              const {url, pointer, isLocalRef, isCircular} = parseRef($ref, currentId, branchRefChain) //, options.vars)
              if(!url) throw new Error(`Invalid $ref ${$ref}`)

              if(isCircular && options.skipCircular || isLocalRef && externalOnly) {
              // if(isLocalRef && externalOnly) {
                parsedNode[prop] = sourceValue
                nextProp()
              } else {
                Promise.resolve(isLocalRef
                  ? solvePointer(pointer, branchRefChain, currentId)
                  : externalOnly && pointer
                   ? processResource(url, pointer, branchRefChain, false)
                   : processResource(url, pointer, branchRefChain)
                )
                .then(newValue => {
                  nodeChanged = 1
                  parsedNode[prop] = newValue
                  nextProp()
                })
                .catch(err => {
                  const log = `Error derefing ${cursor}/${prop}`
                  if (options.failOnMissing) {
                    reject(log)
                  } else {
                    logger.info(log)
                    parsedNode[prop] = sourceValue
                    nextProp()
                  }
                })

              }

            } else {
              const placeholder = parsedNode[prop] = Array.isArray(sourceValue) ? [] : {}
              processNode({
                rawNode: sourceValue,
                parsedNode: placeholder,
                parentId: currentId,
                cursor: propCursor,
                refChain
              })
              .then(newValue => {
                nodeChanged |= newValue !== sourceValue
                nextProp()
              })
              .catch(reject)
            }
          }
        }
      })
    }
function processNode ( { rawNode , parsedNode , parentId = resourceId , cursor , refChain , prop } ) { logger . info ( ` ${ cursor } ${ prop ? '(' + prop + ')' : '' } ${ refChain } ` ) const currentId = options . urlBaseKey && rawNode [ options . urlBaseKey ] ? parseRef ( rawNode [ options . urlBaseKey ] , parentId ) . fullUrl : parentId const props = ! ! prop ? [ prop ] : Object . getOwnPropertyNames ( rawNode ) let propIndex = - 1 , nodeChanged = 0 return new Promise ( ( accept , reject ) => { nextProp ( ) function nextProp ( ) { propIndex ++ if ( propIndex >= props . length ) { if ( ! ! prop ) { accept ( parsedNode [ prop ] ) } else { accept ( nodeChanged ? parsedNode : rawNode ) } } else { const prop = props [ propIndex ] const sourceValue = rawNode [ prop ] const propCursor = ` ${ cursor } ${ prop } ` // If prop is already defined in parsedNode assume complete and skip it if ( parsedNode . hasOwnProperty ( prop ) ) { nodeChanged |= parsedNode [ prop ] !== sourceValue nextProp ( ) } // Is scalar, just set same and continue else if ( typeof sourceValue !== 'object' || sourceValue === null ) { parsedNode [ prop ] = sourceValue nextProp ( ) } // prop is a reference else if ( ! ! sourceValue . $ref ) { const { $ref , ... params } = sourceValue const branchRefChain = [ ... refChain , propCursor ] const { url , pointer , isLocalRef , isCircular } = parseRef ( $ref , currentId , branchRefChain ) //, options.vars) if ( ! url ) throw new Error ( ` ${ $ref } ` ) if ( isCircular && options . skipCircular || isLocalRef && externalOnly ) { // if(isLocalRef && externalOnly) { parsedNode [ prop ] = sourceValue nextProp ( ) } else { Promise . resolve ( isLocalRef ? solvePointer ( pointer , branchRefChain , currentId ) : externalOnly && pointer ? processResource ( url , pointer , branchRefChain , false ) : processResource ( url , pointer , branchRefChain ) ) . then ( newValue => { nodeChanged = 1 parsedNode [ prop ] = newValue nextProp ( ) } ) . catch ( err => { const log = ` ${ cursor } ${ prop } ` if ( options . failOnMissing ) { reject ( log ) } else { logger . info ( log ) parsedNode [ prop ] = sourceValue nextProp ( ) } } ) } } else { const placeholder = parsedNode [ prop ] = Array . isArray ( sourceValue ) ? [ ] : { } processNode ( { rawNode : sourceValue , parsedNode : placeholder , parentId : currentId , cursor : propCursor , refChain } ) . then ( newValue => { nodeChanged |= newValue !== sourceValue nextProp ( ) } ) . catch ( reject ) } } } } ) }
Main recursive traverse function

@param {Object} rawNode - The source node to processed
@param {Object} parsedNode - Object to contain the processed properties
@param {string} cursor - Path of the current node. Adds to refChain...do I need this?
@param {string[]} [refChain=[]] - Parent pointers used to check circular references
@param {string} [prop] - If provided returns an object just that prop parsed
Main recursive traverse function