async function* eachNodeInTree({
  moduleSpecifier,
  importHook,
  shouldImport = () => true,
  visitedSpecifiers = new Set(),
}) {
  // walk next record
  const moduleRecord = await importHook(moduleSpecifier)
  if (moduleRecord === undefined) {
    return
  }
  yield moduleRecord

  // walk children specified in importMap and policyOverride
  const importMapChildren = Object.values(moduleRecord.importMap)
  for (const childSpecifier of importMapChildren) {
    // skip children that are set to null (resolution was skipped)
    if (childSpecifier === null) {
      continue
    }
    // skip modules we're told not to import
    if (!shouldImport(childSpecifier, moduleSpecifier)) {
      continue
    }
    // dont revisit specifiers
    if (visitedSpecifiers.has(childSpecifier)) {
      continue
    }
    visitedSpecifiers.add(childSpecifier)
    // continue walking child
    yield* eachNodeInTree({
      moduleSpecifier: childSpecifier,
      importHook,
      shouldImport,
      visitedSpecifiers,
    })
  }
}
@param {object} options
@param {string} options.moduleSpecifier
@param {import('./parseForPolicy').ImportHookFn} options.importHook
@param {import('./parseForPolicy').ShouldImportFn} [options.shouldImport]
@param {Set<string>} [options.visitedSpecifiers]
@returns {AsyncIterableIterator<
  import('./moduleRecord').LavamoatModuleRecord
>}