function* chooseModuleDescriptor(
  {
    candidates,
    compartmentDescriptor,
    compartmentDescriptors,
    compartments,
    computeSha512,
    moduleDescriptors,
    moduleSpecifier,
    packageLocation,
    packageSources,
    readPowers,
    archiveOnly,
    sourceMapHook,
    strictlyRequiredForCompartment,
  },
  { maybeRead, parse, shouldDeferError = () => false },
) {
  const { sourceDirname } = compartmentDescriptor;
  for (const candidateSpecifier of candidates) {
    const candidateModuleDescriptor = moduleDescriptors[candidateSpecifier];
    if (candidateModuleDescriptor !== undefined) {
      candidateModuleDescriptor.retained = true;
      const { compartment: candidateCompartmentName = packageLocation } =
        candidateModuleDescriptor;
      const candidateCompartment = compartments[candidateCompartmentName];
      if (candidateCompartment === undefined) {
        throw Error(
          `compartment missing for candidate ${candidateSpecifier} in ${candidateCompartmentName}`,
        );
      }
      // modify compartmentMap to include this redirect
      const candidateCompartmentDescriptor =
        compartmentDescriptors[candidateCompartmentName];
      if (candidateCompartmentDescriptor === undefined) {
        throw Error(
          `compartmentDescriptor missing for candidate ${candidateSpecifier} in ${candidateCompartmentName}`,
        );
      }
      candidateCompartmentDescriptor.modules[moduleSpecifier] =
        candidateModuleDescriptor;
      // return a redirect
      /** @type {RedirectStaticModuleInterface} */
      const record = {
        specifier: candidateSpecifier,
        compartment: candidateCompartment,
      };
      return record;
    }

    // Using a specifier as a location.
    // This is not always valid.
    // But, for Node.js, when the specifier is relative and not a directory
    // name, they are usable as URL's.
    const moduleLocation = resolveLocation(candidateSpecifier, packageLocation);

    // "next" values must have type assertions for narrowing because we have
    // multiple yielded types
    const moduleBytes = /** @type {Uint8Array|undefined} */ (
      yield maybeRead(moduleLocation)
    );

    if (moduleBytes !== undefined) {
      /** @type {string | undefined} */
      let sourceMap;
      // must be narrowed
      const envelope = /** @type {ParseResult} */ (
        yield parse(
          moduleBytes,
          candidateSpecifier,
          moduleLocation,
          packageLocation,
          {
            readPowers,
            archiveOnly,
            sourceMapHook:
              sourceMapHook &&
              (nextSourceMapObject => {
                sourceMap = JSON.stringify(nextSourceMapObject);
              }),
            compartmentDescriptor,
          },
        )
      );
      const {
        parser,
        bytes: transformedBytes,
        record: concreteRecord,
      } = envelope;

      // Facilitate a redirect if the returned record has a different
      // module specifier than the requested one.
      if (candidateSpecifier !== moduleSpecifier) {
        moduleDescriptors[moduleSpecifier] = {
          retained: true,
          module: candidateSpecifier,
          compartment: packageLocation,
        };
      }
      /** @type {StaticModuleType} */
      const record = {
        record: concreteRecord,
        specifier: candidateSpecifier,
        importMeta: { url: moduleLocation },
      };

      let sha512;
      if (computeSha512 !== undefined) {
        sha512 = computeSha512(transformedBytes);

        if (sourceMapHook !== undefined && sourceMap !== undefined) {
          sourceMapHook(sourceMap, {
            compartment: packageLocation,
            module: candidateSpecifier,
            location: moduleLocation,
            sha512,
          });
        }
      }

      const packageRelativeLocation = moduleLocation.slice(
        packageLocation.length,
      );
      packageSources[candidateSpecifier] = {
        location: packageRelativeLocation,
        sourceLocation: moduleLocation,
        sourceDirname,
        parser,
        bytes: transformedBytes,
        record: concreteRecord,
        sha512,
      };
      if (!shouldDeferError(parser)) {
        for (const importSpecifier of getImportsFromRecord(record)) {
          strictlyRequiredForCompartment(packageLocation).add(
            resolve(importSpecifier, moduleSpecifier),
          );
        }
      }

      return record;
    }
  }
  return undefined;
}
Returns a generator which applies {@link ChooseModuleDescriptorOperators} in
`operators` using the options in options to ultimately result in a
{@link StaticModuleType} for a particular {@link CompartmentDescriptor} (or
`undefined`).

Supports both {@link SyncChooseModuleDescriptorOperators sync} and
{@link AsyncChooseModuleDescriptorOperators async} operators.

Used by both {@link makeImportNowHookMaker} and {@link makeImportHookMaker}.

@template {ChooseModuleDescriptorOperators} Operators Type of operators (sync
or async)
@param {ChooseModuleDescriptorParams} options Options/context
@param {Operators} operators Operators
@returns {Generator<ChooseModuleDescriptorYieldables,
StaticModuleType|undefined, Awaited<ChooseModuleDescriptorYieldables>>}
Generator