"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = exports.RequestGraph = void 0;
exports.getWatcherOptions = getWatcherOptions;
exports.requestTypes = exports.requestGraphEdgeTypes = void 0;
function _assert() {
  const data = _interopRequireWildcard(require("assert"));
  _assert = function () {
    return data;
  };
  return data;
}
function _path2() {
  const data = _interopRequireDefault(require("path"));
  _path2 = function () {
    return data;
  };
  return data;
}
function _graph() {
  const data = require("@parcel/graph");
  _graph = function () {
    return data;
  };
  return data;
}
function _logger() {
  const data = _interopRequireDefault(require("@parcel/logger"));
  _logger = function () {
    return data;
  };
  return data;
}
function _rust() {
  const data = require("@parcel/rust");
  _rust = function () {
    return data;
  };
  return data;
}
function _utils() {
  const data = require("@parcel/utils");
  _utils = function () {
    return data;
  };
  return data;
}
function _nullthrows() {
  const data = _interopRequireDefault(require("nullthrows"));
  _nullthrows = function () {
    return data;
  };
  return data;
}
var _constants = require("./constants");
var _projectPath = require("./projectPath");
var _ConfigRequest = require("./requests/ConfigRequest");
var _serializer = require("./serializer");
var _utils2 = require("./utils");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
const requestGraphEdgeTypes = exports.requestGraphEdgeTypes = {
  subrequest: 2,
  invalidated_by_update: 3,
  invalidated_by_delete: 4,
  invalidated_by_create: 5,
  invalidated_by_create_above: 6,
  dirname: 7
};
class FSBailoutError extends Error {
  name = 'FSBailoutError';
}
const FILE = 0;
const REQUEST = 1;
const FILE_NAME = 2;
const ENV = 3;
const OPTION = 4;
const GLOB = 5;
const CONFIG_KEY = 6;
const requestTypes = exports.requestTypes = {
  parcel_build_request: 1,
  bundle_graph_request: 2,
  asset_graph_request: 3,
  entry_request: 4,
  target_request: 5,
  parcel_config_request: 6,
  path_request: 7,
  dev_dep_request: 8,
  asset_request: 9,
  config_request: 10,
  write_bundles_request: 11,
  package_request: 12,
  write_bundle_request: 13,
  validation_request: 14
};
const nodeFromFilePath = filePath => ({
  id: (0, _projectPath.fromProjectPathRelative)(filePath),
  type: FILE
});
const nodeFromGlob = glob => ({
  id: (0, _projectPath.fromProjectPathRelative)(glob),
  type: GLOB,
  value: glob
});
const nodeFromFileName = fileName => ({
  id: 'file_name:' + fileName,
  type: FILE_NAME
});
const nodeFromRequest = request => ({
  id: request.id,
  type: REQUEST,
  requestType: request.requestType,
  invalidateReason: _constants.INITIAL_BUILD
});
const nodeFromEnv = (env, value) => ({
  id: 'env:' + env,
  type: ENV,
  value
});
const nodeFromOption = (option, value) => ({
  id: 'option:' + option,
  type: OPTION,
  hash: (0, _utils2.hashFromOption)(value)
});
const nodeFromConfigKey = (fileName, configKey, contentHash) => ({
  id: `config_key:${(0, _projectPath.fromProjectPathRelative)(fileName)}:${configKey}`,
  type: CONFIG_KEY,
  configKey,
  contentHash
});
const keyFromEnvContentKey = contentKey => contentKey.slice('env:'.length);
const keyFromOptionContentKey = contentKey => contentKey.slice('option:'.length);
class RequestGraph extends _graph().ContentGraph {
  invalidNodeIds = new Set();
  incompleteNodeIds = new Set();
  incompleteNodePromises = new Map();
  globNodeIds = new Set();
  envNodeIds = new Set();
  optionNodeIds = new Set();
  // Unpredictable nodes are requests that cannot be predicted whether they should rerun based on
  // filesystem changes alone. They should rerun on each startup of Parcel.
  unpredicatableNodeIds = new Set();
  invalidateOnBuildNodeIds = new Set();
  configKeyNodes = new Map();

  // $FlowFixMe[prop-missing]
  static deserialize(opts) {
    // $FlowFixMe[prop-missing]
    let deserialized = new RequestGraph(opts);
    deserialized.invalidNodeIds = opts.invalidNodeIds;
    deserialized.incompleteNodeIds = opts.incompleteNodeIds;
    deserialized.globNodeIds = opts.globNodeIds;
    deserialized.envNodeIds = opts.envNodeIds;
    deserialized.optionNodeIds = opts.optionNodeIds;
    deserialized.unpredicatableNodeIds = opts.unpredicatableNodeIds;
    deserialized.invalidateOnBuildNodeIds = opts.invalidateOnBuildNodeIds;
    deserialized.configKeyNodes = opts.configKeyNodes;
    return deserialized;
  }

  // $FlowFixMe[prop-missing]
  serialize() {
    return {
      ...super.serialize(),
      invalidNodeIds: this.invalidNodeIds,
      incompleteNodeIds: this.incompleteNodeIds,
      globNodeIds: this.globNodeIds,
      envNodeIds: this.envNodeIds,
      optionNodeIds: this.optionNodeIds,
      unpredicatableNodeIds: this.unpredicatableNodeIds,
      invalidateOnBuildNodeIds: this.invalidateOnBuildNodeIds,
      configKeyNodes: this.configKeyNodes
    };
  }

  // addNode for RequestGraph should not override the value if added multiple times
  addNode(node) {
    let nodeId = this._contentKeyToNodeId.get(node.id);
    if (nodeId != null) {
      return nodeId;
    }
    nodeId = super.addNodeByContentKey(node.id, node);
    if (node.type === GLOB) {
      this.globNodeIds.add(nodeId);
    } else if (node.type === ENV) {
      this.envNodeIds.add(nodeId);
    } else if (node.type === OPTION) {
      this.optionNodeIds.add(nodeId);
    }
    return nodeId;
  }
  removeNode(nodeId) {
    this.invalidNodeIds.delete(nodeId);
    this.incompleteNodeIds.delete(nodeId);
    this.incompleteNodePromises.delete(nodeId);
    this.unpredicatableNodeIds.delete(nodeId);
    this.invalidateOnBuildNodeIds.delete(nodeId);
    let node = (0, _nullthrows().default)(this.getNode(nodeId));
    if (node.type === GLOB) {
      this.globNodeIds.delete(nodeId);
    } else if (node.type === ENV) {
      this.envNodeIds.delete(nodeId);
    } else if (node.type === OPTION) {
      this.optionNodeIds.delete(nodeId);
    } else if (node.type === CONFIG_KEY) {
      for (let configKeyNodes of this.configKeyNodes.values()) {
        configKeyNodes.delete(nodeId);
      }
    }
    return super.removeNode(nodeId);
  }
  getRequestNode(nodeId) {
    let node = (0, _nullthrows().default)(this.getNode(nodeId));
    if (node.type === REQUEST) {
      return node;
    }
    throw new (_assert().AssertionError)({
      message: `Expected a request node: ${node.type} (${typeof node.type}) does not equal ${REQUEST} (${typeof REQUEST}).`,
      expected: REQUEST,
      actual: node.type
    });
  }
  replaceSubrequests(requestNodeId, subrequestContentKeys) {
    let subrequestNodeIds = [];
    for (let key of subrequestContentKeys) {
      if (this.hasContentKey(key)) {
        subrequestNodeIds.push(this.getNodeIdByContentKey(key));
      }
    }
    this.replaceNodeIdsConnectedTo(requestNodeId, subrequestNodeIds, null, requestGraphEdgeTypes.subrequest);
  }
  invalidateNode(nodeId, reason) {
    let node = (0, _nullthrows().default)(this.getNode(nodeId));
    (0, _assert().default)(node.type === REQUEST);
    node.invalidateReason |= reason;
    this.invalidNodeIds.add(nodeId);
    let parentNodes = this.getNodeIdsConnectedTo(nodeId, requestGraphEdgeTypes.subrequest);
    for (let parentNode of parentNodes) {
      this.invalidateNode(parentNode, reason);
    }
  }
  invalidateUnpredictableNodes() {
    for (let nodeId of this.unpredicatableNodeIds) {
      let node = (0, _nullthrows().default)(this.getNode(nodeId));
      (0, _assert().default)(node.type !== FILE && node.type !== GLOB);
      this.invalidateNode(nodeId, _constants.STARTUP);
    }
  }
  invalidateOnBuildNodes() {
    for (let nodeId of this.invalidateOnBuildNodeIds) {
      let node = (0, _nullthrows().default)(this.getNode(nodeId));
      (0, _assert().default)(node.type !== FILE && node.type !== GLOB);
      this.invalidateNode(nodeId, _constants.STARTUP);
    }
  }
  invalidateEnvNodes(env) {
    for (let nodeId of this.envNodeIds) {
      let node = (0, _nullthrows().default)(this.getNode(nodeId));
      (0, _assert().default)(node.type === ENV);
      if (env[keyFromEnvContentKey(node.id)] !== node.value) {
        let parentNodes = this.getNodeIdsConnectedTo(nodeId, requestGraphEdgeTypes.invalidated_by_update);
        for (let parentNode of parentNodes) {
          this.invalidateNode(parentNode, _constants.ENV_CHANGE);
        }
      }
    }
  }
  invalidateOptionNodes(options) {
    for (let nodeId of this.optionNodeIds) {
      let node = (0, _nullthrows().default)(this.getNode(nodeId));
      (0, _assert().default)(node.type === OPTION);
      if ((0, _utils2.hashFromOption)(options[keyFromOptionContentKey(node.id)]) !== node.hash) {
        let parentNodes = this.getNodeIdsConnectedTo(nodeId, requestGraphEdgeTypes.invalidated_by_update);
        for (let parentNode of parentNodes) {
          this.invalidateNode(parentNode, _constants.OPTION_CHANGE);
        }
      }
    }
  }
  invalidateOnConfigKeyChange(requestNodeId, filePath, configKey, contentHash) {
    let configKeyNodeId = this.addNode(nodeFromConfigKey(filePath, configKey, contentHash));
    let nodes = this.configKeyNodes.get(filePath);
    if (!nodes) {
      nodes = new Set();
      this.configKeyNodes.set(filePath, nodes);
    }
    nodes.add(configKeyNodeId);
    if (!this.hasEdge(requestNodeId, configKeyNodeId, requestGraphEdgeTypes.invalidated_by_update)) {
      this.addEdge(requestNodeId, configKeyNodeId,
      // Store as an update edge, but file deletes are handled too
      requestGraphEdgeTypes.invalidated_by_update);
    }
  }
  invalidateOnFileUpdate(requestNodeId, filePath) {
    let fileNodeId = this.addNode(nodeFromFilePath(filePath));
    if (!this.hasEdge(requestNodeId, fileNodeId, requestGraphEdgeTypes.invalidated_by_update)) {
      this.addEdge(requestNodeId, fileNodeId, requestGraphEdgeTypes.invalidated_by_update);
    }
  }
  invalidateOnFileDelete(requestNodeId, filePath) {
    let fileNodeId = this.addNode(nodeFromFilePath(filePath));
    if (!this.hasEdge(requestNodeId, fileNodeId, requestGraphEdgeTypes.invalidated_by_delete)) {
      this.addEdge(requestNodeId, fileNodeId, requestGraphEdgeTypes.invalidated_by_delete);
    }
  }
  invalidateOnFileCreate(requestNodeId, input) {
    let node;
    if (input.glob != null) {
      node = nodeFromGlob(input.glob);
    } else if (input.fileName != null && input.aboveFilePath != null) {
      let aboveFilePath = input.aboveFilePath;

      // Create nodes and edges for each part of the filename pattern.
      // For example, 'node_modules/foo' would create two nodes and one edge.
      // This creates a sort of trie structure within the graph that can be
      // quickly matched by following the edges. This is also memory efficient
      // since common sub-paths (e.g. 'node_modules') are deduplicated.
      let parts = input.fileName.split('/').reverse();
      let lastNodeId;
      for (let part of parts) {
        let fileNameNode = nodeFromFileName(part);
        let fileNameNodeId = this.addNode(fileNameNode);
        if (lastNodeId != null && !this.hasEdge(lastNodeId, fileNameNodeId, requestGraphEdgeTypes.dirname)) {
          this.addEdge(lastNodeId, fileNameNodeId, requestGraphEdgeTypes.dirname);
        }
        lastNodeId = fileNameNodeId;
      }

      // The `aboveFilePath` condition asserts that requests are only invalidated
      // if the file being created is "above" it in the filesystem (e.g. the file
      // is created in a parent directory). There is likely to already be a node
      // for this file in the graph (e.g. the source file) that we can reuse for this.
      node = nodeFromFilePath(aboveFilePath);
      let nodeId = this.addNode(node);

      // Now create an edge from the `aboveFilePath` node to the first file_name node
      // in the chain created above, and an edge from the last node in the chain back to
      // the `aboveFilePath` node. When matching, we will start from the first node in
      // the chain, and continue following it to parent directories until there is an
      // edge pointing an `aboveFilePath` node that also points to the start of the chain.
      // This indicates a complete match, and any requests attached to the `aboveFilePath`
      // node will be invalidated.
      let firstId = 'file_name:' + parts[0];
      let firstNodeId = this.getNodeIdByContentKey(firstId);
      if (!this.hasEdge(nodeId, firstNodeId, requestGraphEdgeTypes.invalidated_by_create_above)) {
        this.addEdge(nodeId, firstNodeId, requestGraphEdgeTypes.invalidated_by_create_above);
      }
      (0, _assert().default)(lastNodeId != null);
      if (!this.hasEdge(lastNodeId, nodeId, requestGraphEdgeTypes.invalidated_by_create_above)) {
        this.addEdge(lastNodeId, nodeId, requestGraphEdgeTypes.invalidated_by_create_above);
      }
    } else if (input.filePath != null) {
      node = nodeFromFilePath(input.filePath);
    } else {
      throw new Error('Invalid invalidation');
    }
    let nodeId = this.addNode(node);
    if (!this.hasEdge(requestNodeId, nodeId, requestGraphEdgeTypes.invalidated_by_create)) {
      this.addEdge(requestNodeId, nodeId, requestGraphEdgeTypes.invalidated_by_create);
    }
  }
  invalidateOnStartup(requestNodeId) {
    this.getRequestNode(requestNodeId);
    this.unpredicatableNodeIds.add(requestNodeId);
  }
  invalidateOnBuild(requestNodeId) {
    this.getRequestNode(requestNodeId);
    this.invalidateOnBuildNodeIds.add(requestNodeId);
  }
  invalidateOnEnvChange(requestNodeId, env, value) {
    let envNode = nodeFromEnv(env, value);
    let envNodeId = this.addNode(envNode);
    if (!this.hasEdge(requestNodeId, envNodeId, requestGraphEdgeTypes.invalidated_by_update)) {
      this.addEdge(requestNodeId, envNodeId, requestGraphEdgeTypes.invalidated_by_update);
    }
  }
  invalidateOnOptionChange(requestNodeId, option, value) {
    let optionNode = nodeFromOption(option, value);
    let optionNodeId = this.addNode(optionNode);
    if (!this.hasEdge(requestNodeId, optionNodeId, requestGraphEdgeTypes.invalidated_by_update)) {
      this.addEdge(requestNodeId, optionNodeId, requestGraphEdgeTypes.invalidated_by_update);
    }
  }
  clearInvalidations(nodeId) {
    this.unpredicatableNodeIds.delete(nodeId);
    this.invalidateOnBuildNodeIds.delete(nodeId);
    this.replaceNodeIdsConnectedTo(nodeId, [], null, requestGraphEdgeTypes.invalidated_by_update);
    this.replaceNodeIdsConnectedTo(nodeId, [], null, requestGraphEdgeTypes.invalidated_by_delete);
    this.replaceNodeIdsConnectedTo(nodeId, [], null, requestGraphEdgeTypes.invalidated_by_create);
  }
  getInvalidations(requestNodeId) {
    if (!this.hasNode(requestNodeId)) {
      return [];
    }

    // For now just handling updates. Could add creates/deletes later if needed.
    let invalidations = this.getNodeIdsConnectedFrom(requestNodeId, requestGraphEdgeTypes.invalidated_by_update);
    return invalidations.map(nodeId => {
      let node = (0, _nullthrows().default)(this.getNode(nodeId));
      switch (node.type) {
        case FILE:
          return {
            type: 'file',
            filePath: (0, _projectPath.toProjectPathUnsafe)(node.id)
          };
        case ENV:
          return {
            type: 'env',
            key: keyFromEnvContentKey(node.id)
          };
        case OPTION:
          return {
            type: 'option',
            key: keyFromOptionContentKey(node.id)
          };
      }
    }).filter(Boolean);
  }
  getSubRequests(requestNodeId) {
    if (!this.hasNode(requestNodeId)) {
      return [];
    }
    let subRequests = this.getNodeIdsConnectedFrom(requestNodeId, requestGraphEdgeTypes.subrequest);
    return subRequests.map(nodeId => {
      let node = (0, _nullthrows().default)(this.getNode(nodeId));
      (0, _assert().default)(node.type === REQUEST);
      return node;
    });
  }
  getInvalidSubRequests(requestNodeId) {
    if (!this.hasNode(requestNodeId)) {
      return [];
    }
    let subRequests = this.getNodeIdsConnectedFrom(requestNodeId, requestGraphEdgeTypes.subrequest);
    return subRequests.filter(id => this.invalidNodeIds.has(id)).map(nodeId => {
      let node = (0, _nullthrows().default)(this.getNode(nodeId));
      (0, _assert().default)(node.type === REQUEST);
      return node;
    });
  }
  invalidateFileNameNode(node, filePath, matchNodes) {
    // If there is an edge between this file_name node and one of the original file nodes pointed to
    // by the original file_name node, and the matched node is inside the current directory, invalidate
    // all connected requests pointed to by the file node.
    let dirname = _path2().default.dirname((0, _projectPath.fromProjectPathRelative)(filePath));
    let nodeId = this.getNodeIdByContentKey(node.id);
    for (let matchNode of matchNodes) {
      let matchNodeId = this.getNodeIdByContentKey(matchNode.id);
      if (this.hasEdge(nodeId, matchNodeId, requestGraphEdgeTypes.invalidated_by_create_above) && (0, _utils().isDirectoryInside)((0, _projectPath.fromProjectPathRelative)((0, _projectPath.toProjectPathUnsafe)(matchNode.id)), dirname)) {
        let connectedNodes = this.getNodeIdsConnectedTo(matchNodeId, requestGraphEdgeTypes.invalidated_by_create);
        for (let connectedNode of connectedNodes) {
          this.invalidateNode(connectedNode, _constants.FILE_CREATE);
        }
      }
    }

    // Find the `file_name` node for the parent directory and
    // recursively invalidate connected requests as described above.
    let basename = _path2().default.basename(dirname);
    let contentKey = 'file_name:' + basename;
    if (this.hasContentKey(contentKey)) {
      if (this.hasEdge(nodeId, this.getNodeIdByContentKey(contentKey), requestGraphEdgeTypes.dirname)) {
        let parent = (0, _nullthrows().default)(this.getNodeByContentKey(contentKey));
        (0, _assert().default)(parent.type === FILE_NAME);
        this.invalidateFileNameNode(parent, (0, _projectPath.toProjectPathUnsafe)(dirname), matchNodes);
      }
    }
  }
  async respondToFSEvents(events, options, threshold) {
    let didInvalidate = false;
    let count = 0;
    let predictedTime = 0;
    let startTime = Date.now();
    for (let {
      path: _path,
      type
    } of events) {
      if (++count === 256) {
        let duration = Date.now() - startTime;
        predictedTime = duration * (events.length >> 8);
        if (predictedTime > threshold) {
          _logger().default.warn({
            origin: '@parcel/core',
            message: 'Building with clean cache. Cache invalidation took too long.',
            meta: {
              trackableEvent: 'cache_invalidation_timeout',
              watcherEventCount: events.length,
              predictedTime
            }
          });
          throw new FSBailoutError('Responding to file system events exceeded threshold, start with empty cache.');
        }
      }
      let _filePath = (0, _projectPath.toProjectPath)(options.projectRoot, _path);
      let filePath = (0, _projectPath.fromProjectPathRelative)(_filePath);
      let hasFileRequest = this.hasContentKey(filePath);

      // If we see a 'create' event for the project root itself,
      // this means the project root was moved and we need to
      // re-run all requests.
      if (type === 'create' && filePath === '') {
        _logger().default.verbose({
          origin: '@parcel/core',
          message: 'Watcher reported project root create event. Invalidate all nodes.',
          meta: {
            trackableEvent: 'project_root_create'
          }
        });
        for (let [id, node] of this.nodes.entries()) {
          if ((node === null || node === void 0 ? void 0 : node.type) === REQUEST) {
            this.invalidNodeIds.add(id);
          }
        }
        return true;
      }

      // sometimes mac os reports update events as create events.
      // if it was a create event, but the file already exists in the graph,
      // then also invalidate nodes connected by invalidated_by_update edges.
      if (hasFileRequest && (type === 'create' || type === 'update')) {
        let nodeId = this.getNodeIdByContentKey(filePath);
        let nodes = this.getNodeIdsConnectedTo(nodeId, requestGraphEdgeTypes.invalidated_by_update);
        for (let connectedNode of nodes) {
          didInvalidate = true;
          this.invalidateNode(connectedNode, _constants.FILE_UPDATE);
        }
        if (type === 'create') {
          let nodes = this.getNodeIdsConnectedTo(nodeId, requestGraphEdgeTypes.invalidated_by_create);
          for (let connectedNode of nodes) {
            didInvalidate = true;
            this.invalidateNode(connectedNode, _constants.FILE_CREATE);
          }
        }
      } else if (type === 'create') {
        let basename = _path2().default.basename(filePath);
        let fileNameNode = this.getNodeByContentKey('file_name:' + basename);
        if (fileNameNode != null && fileNameNode.type === FILE_NAME) {
          let fileNameNodeId = this.getNodeIdByContentKey('file_name:' + basename);

          // Find potential file nodes to be invalidated if this file name pattern matches
          let above = [];
          for (const nodeId of this.getNodeIdsConnectedTo(fileNameNodeId, requestGraphEdgeTypes.invalidated_by_create_above)) {
            let node = (0, _nullthrows().default)(this.getNode(nodeId));
            // these might also be `glob` nodes which get handled below, we only care about files here.
            if (node.type === FILE) {
              above.push(node);
            }
          }
          if (above.length > 0) {
            didInvalidate = true;
            this.invalidateFileNameNode(fileNameNode, _filePath, above);
          }
        }
        for (let globeNodeId of this.globNodeIds) {
          let globNode = this.getNode(globeNodeId);
          (0, _assert().default)(globNode && globNode.type === GLOB);
          if ((0, _utils().isGlobMatch)(filePath, (0, _projectPath.fromProjectPathRelative)(globNode.value))) {
            let connectedNodes = this.getNodeIdsConnectedTo(globeNodeId, requestGraphEdgeTypes.invalidated_by_create);
            for (let connectedNode of connectedNodes) {
              didInvalidate = true;
              this.invalidateNode(connectedNode, _constants.FILE_CREATE);
            }
          }
        }
      } else if (hasFileRequest && type === 'delete') {
        let nodeId = this.getNodeIdByContentKey(filePath);
        for (let connectedNode of this.getNodeIdsConnectedTo(nodeId, requestGraphEdgeTypes.invalidated_by_delete)) {
          didInvalidate = true;
          this.invalidateNode(connectedNode, _constants.FILE_DELETE);
        }

        // Delete the file node since it doesn't exist anymore.
        // This ensures that files that don't exist aren't sent
        // to requests as invalidations for future requests.
        this.removeNode(nodeId);
      }
      let configKeyNodes = this.configKeyNodes.get(_filePath);
      if (configKeyNodes && (type === 'delete' || type === 'update')) {
        for (let nodeId of configKeyNodes) {
          let isInvalid = type === 'delete';
          if (type === 'update') {
            let node = this.getNode(nodeId);
            (0, _assert().default)(node && node.type === CONFIG_KEY);
            let contentHash = await (0, _ConfigRequest.getConfigKeyContentHash)(_filePath, node.configKey, options);
            isInvalid = node.contentHash !== contentHash;
          }
          if (isInvalid) {
            for (let connectedNode of this.getNodeIdsConnectedTo(nodeId, requestGraphEdgeTypes.invalidated_by_update)) {
              this.invalidateNode(connectedNode, type === 'delete' ? _constants.FILE_DELETE : _constants.FILE_UPDATE);
            }
            didInvalidate = true;
            this.removeNode(nodeId);
          }
        }
      }
    }
    let duration = Date.now() - startTime;
    _logger().default.verbose({
      origin: '@parcel/core',
      message: `RequestGraph.respondToFSEvents duration: ${duration}`,
      meta: {
        trackableEvent: 'fsevent_response_time',
        duration,
        predictedTime
      }
    });
    return didInvalidate && this.invalidNodeIds.size > 0;
  }
}
exports.RequestGraph = RequestGraph;
class RequestTracker {
  stats = new Map();
  constructor({
    graph,
    farm,
    options
  }) {
    this.graph = graph || new RequestGraph();
    this.farm = farm;
    this.options = options;
  }

  // TODO: refactor (abortcontroller should be created by RequestTracker)
  setSignal(signal) {
    this.signal = signal;
  }
  startRequest(request) {
    let didPreviouslyExist = this.graph.hasContentKey(request.id);
    let requestNodeId;
    if (didPreviouslyExist) {
      requestNodeId = this.graph.getNodeIdByContentKey(request.id);
      // Clear existing invalidations for the request so that the new
      // invalidations created during the request replace the existing ones.
      this.graph.clearInvalidations(requestNodeId);
    } else {
      requestNodeId = this.graph.addNode(nodeFromRequest(request));
    }
    this.graph.incompleteNodeIds.add(requestNodeId);
    this.graph.invalidNodeIds.delete(requestNodeId);
    let {
      promise,
      deferred
    } = (0, _utils().makeDeferredWithPromise)();
    this.graph.incompleteNodePromises.set(requestNodeId, promise);
    return {
      requestNodeId,
      deferred
    };
  }

  // If a cache key is provided, the result will be removed from the node and stored in a separate cache entry
  storeResult(nodeId, result, cacheKey) {
    let node = this.graph.getNode(nodeId);
    if (node && node.type === REQUEST) {
      node.result = result;
      node.resultCacheKey = cacheKey;
    }
  }
  hasValidResult(nodeId) {
    return this.graph.hasNode(nodeId) && !this.graph.invalidNodeIds.has(nodeId) && !this.graph.incompleteNodeIds.has(nodeId);
  }
  async getRequestResult(contentKey, ifMatch) {
    let node = (0, _nullthrows().default)(this.graph.getNodeByContentKey(contentKey));
    (0, _assert().default)(node.type === REQUEST);
    if (ifMatch != null && node.resultCacheKey !== ifMatch) {
      return null;
    }
    if (node.result != undefined) {
      // $FlowFixMe
      let result = node.result;
      return result;
    } else if (node.resultCacheKey != null && ifMatch == null) {
      let key = node.resultCacheKey;
      (0, _assert().default)(this.options.cache.hasLargeBlob(key));
      let cachedResult = (0, _serializer.deserialize)(await this.options.cache.getLargeBlob(key));
      node.result = cachedResult;
      return cachedResult;
    }
  }
  completeRequest(nodeId) {
    this.graph.invalidNodeIds.delete(nodeId);
    this.graph.incompleteNodeIds.delete(nodeId);
    this.graph.incompleteNodePromises.delete(nodeId);
    let node = this.graph.getNode(nodeId);
    if (node && node.type === REQUEST) {
      node.invalidateReason = _constants.VALID;
    }
  }
  rejectRequest(nodeId) {
    this.graph.incompleteNodeIds.delete(nodeId);
    this.graph.incompleteNodePromises.delete(nodeId);
    let node = this.graph.getNode(nodeId);
    if ((node === null || node === void 0 ? void 0 : node.type) === REQUEST) {
      this.graph.invalidateNode(nodeId, _constants.ERROR);
    }
  }
  respondToFSEvents(events, threshold) {
    return this.graph.respondToFSEvents(events, this.options, threshold);
  }
  hasInvalidRequests() {
    return this.graph.invalidNodeIds.size > 0;
  }
  getInvalidRequests() {
    let invalidRequests = [];
    for (let id of this.graph.invalidNodeIds) {
      let node = (0, _nullthrows().default)(this.graph.getNode(id));
      (0, _assert().default)(node.type === REQUEST);
      invalidRequests.push(node);
    }
    return invalidRequests;
  }
  replaceSubrequests(requestNodeId, subrequestContextKeys) {
    this.graph.replaceSubrequests(requestNodeId, subrequestContextKeys);
  }
  async runRequest(request, opts) {
    let hasKey = this.graph.hasContentKey(request.id);
    let requestId = hasKey ? this.graph.getNodeIdByContentKey(request.id) : undefined;
    let hasValidResult = requestId != null && this.hasValidResult(requestId);
    if (!(opts !== null && opts !== void 0 && opts.force) && hasValidResult) {
      // $FlowFixMe[incompatible-type]
      return this.getRequestResult(request.id);
    }
    if (requestId != null) {
      let incompletePromise = this.graph.incompleteNodePromises.get(requestId);
      if (incompletePromise != null) {
        // There is a another instance of this request already running, wait for its completion and reuse its result
        try {
          if (await incompletePromise) {
            // $FlowFixMe[incompatible-type]
            return this.getRequestResult(request.id);
          }
        } catch (e) {
          // Rerun this request
        }
      }
    }
    let previousInvalidations = requestId != null ? this.graph.getInvalidations(requestId) : [];
    let {
      requestNodeId,
      deferred
    } = this.startRequest({
      id: request.id,
      type: REQUEST,
      requestType: request.type,
      invalidateReason: _constants.INITIAL_BUILD
    });
    let {
      api,
      subRequestContentKeys
    } = this.createAPI(requestNodeId, previousInvalidations);
    try {
      let node = this.graph.getRequestNode(requestNodeId);
      this.stats.set(request.type, (this.stats.get(request.type) ?? 0) + 1);
      let result = await request.run({
        input: request.input,
        api,
        farm: this.farm,
        invalidateReason: node.invalidateReason,
        options: this.options
      });
      (0, _utils2.assertSignalNotAborted)(this.signal);
      this.completeRequest(requestNodeId);
      deferred.resolve(true);
      return result;
    } catch (err) {
      if (!(err instanceof _utils2.BuildAbortError) && request.type === requestTypes.dev_dep_request) {
        _logger().default.verbose({
          origin: '@parcel/core',
          message: `Failed DevDepRequest`,
          meta: {
            trackableEvent: 'failed_dev_dep_request',
            hasKey,
            hasValidResult
          }
        });
      }
      this.rejectRequest(requestNodeId);
      deferred.resolve(false);
      throw err;
    } finally {
      this.graph.replaceSubrequests(requestNodeId, [...subRequestContentKeys]);
    }
  }
  flushStats() {
    let requestTypeEntries = {};
    for (let key of Object.keys(requestTypes)) {
      requestTypeEntries[requestTypes[key]] = key;
    }
    let formattedStats = {};
    for (let [requestType, count] of this.stats.entries()) {
      let requestTypeName = requestTypeEntries[requestType];
      formattedStats[requestTypeName] = count;
    }
    this.stats = new Map();
    return formattedStats;
  }
  createAPI(requestId, previousInvalidations) {
    let subRequestContentKeys = new Set();
    return {
      api: {
        invalidateOnFileCreate: input => this.graph.invalidateOnFileCreate(requestId, input),
        invalidateOnConfigKeyChange: (filePath, configKey, contentHash) => this.graph.invalidateOnConfigKeyChange(requestId, filePath, configKey, contentHash),
        invalidateOnFileDelete: filePath => this.graph.invalidateOnFileDelete(requestId, filePath),
        invalidateOnFileUpdate: filePath => this.graph.invalidateOnFileUpdate(requestId, filePath),
        invalidateOnStartup: () => this.graph.invalidateOnStartup(requestId),
        invalidateOnBuild: () => this.graph.invalidateOnBuild(requestId),
        invalidateOnEnvChange: env => this.graph.invalidateOnEnvChange(requestId, env, this.options.env[env]),
        invalidateOnOptionChange: option => this.graph.invalidateOnOptionChange(requestId, option, this.options[option]),
        getInvalidations: () => previousInvalidations,
        storeResult: (result, cacheKey) => {
          this.storeResult(requestId, result, cacheKey);
        },
        getSubRequests: () => this.graph.getSubRequests(requestId),
        getInvalidSubRequests: () => this.graph.getInvalidSubRequests(requestId),
        getPreviousResult: ifMatch => {
          var _this$graph$getNode;
          let contentKey = (0, _nullthrows().default)((_this$graph$getNode = this.graph.getNode(requestId)) === null || _this$graph$getNode === void 0 ? void 0 : _this$graph$getNode.id);
          return this.getRequestResult(contentKey, ifMatch);
        },
        getRequestResult: id => this.getRequestResult(id),
        canSkipSubrequest: contentKey => {
          if (this.graph.hasContentKey(contentKey) && this.hasValidResult(this.graph.getNodeIdByContentKey(contentKey))) {
            subRequestContentKeys.add(contentKey);
            return true;
          }
          return false;
        },
        runRequest: (subRequest, opts) => {
          subRequestContentKeys.add(subRequest.id);
          return this.runRequest(subRequest, opts);
        }
      },
      subRequestContentKeys
    };
  }
  async writeToCache(signal) {
    let cacheKey = getCacheKey(this.options);
    let requestGraphKey = `${cacheKey}-RequestGraph`;
    if (this.options.shouldDisableCache) {
      return;
    }
    let keys = [requestGraphKey];
    let promises = [];
    for (let node of this.graph.nodes) {
      if (!node || node.type !== REQUEST) {
        continue;
      }
      let resultCacheKey = node.resultCacheKey;
      if (resultCacheKey != null && node.result != null) {
        keys.push(resultCacheKey);
        promises.push(this.options.cache.setLargeBlob(resultCacheKey, (0, _serializer.serialize)(node.result), {
          signal
        }));
        delete node.result;
      }
    }
    promises.push(this.options.cache.setLargeBlob(requestGraphKey, (0, _serializer.serialize)(this.graph), {
      signal
    }));
    let opts = getWatcherOptions(this.options);
    let snapshotPath = _path2().default.join(this.options.cacheDir, `snapshot-${cacheKey}` + '.txt');
    promises.push(this.options.inputFS.writeSnapshot(this.options.watchDir, snapshotPath, opts));
    try {
      await Promise.all(promises);
    } catch (err) {
      if (signal !== null && signal !== void 0 && signal.aborted) {
        // If writing to the cache was aborted, delete all of the keys to avoid inconsistent states.
        for (let key of keys) {
          try {
            await this.options.cache.deleteLargeBlob(key);
          } catch (err) {
            // ignore.
          }
        }
      } else {
        throw err;
      }
    }
  }
  static async init({
    farm,
    options
  }) {
    let graph = await loadRequestGraph(options);
    return new RequestTracker({
      farm,
      graph,
      options
    });
  }
}
exports.default = RequestTracker;
function getWatcherOptions({
  watchIgnore = [],
  cacheDir,
  watchDir,
  watchBackend
}) {
  const uniqueDirs = [...new Set([...watchIgnore, ...['.git', '.hg'], cacheDir])];
  const ignore = uniqueDirs.map(dir => _path2().default.resolve(watchDir, dir));
  return {
    ignore,
    backend: watchBackend
  };
}
function getCacheKey(options) {
  return (0, _rust().hashString)(`${_constants.PARCEL_VERSION}:${JSON.stringify(options.entries)}:${options.mode}:${options.shouldBuildLazily ? 'lazy' : 'eager'}:${options.watchBackend ?? ''}`);
}
async function loadRequestGraph(options) {
  if (options.shouldDisableCache) {
    return new RequestGraph();
  }
  let cacheKey = getCacheKey(options);
  let requestGraphKey = `${cacheKey}-RequestGraph`;
  const snapshotPath = _path2().default.join(options.cacheDir, `snapshot-${cacheKey}` + '.txt');
  if (await options.cache.hasLargeBlob(requestGraphKey)) {
    try {
      let requestGraph = (0, _serializer.deserialize)(await options.cache.getLargeBlob(requestGraphKey));
      let opts = getWatcherOptions(options);
      let events = await options.inputFS.getEventsSince(options.watchDir, snapshotPath, opts);
      requestGraph.invalidateUnpredictableNodes();
      requestGraph.invalidateOnBuildNodes();
      requestGraph.invalidateEnvNodes(options.env);
      requestGraph.invalidateOptionNodes(options);
      await requestGraph.respondToFSEvents(options.unstableFileInvalidations || events, options, 10000);
      return requestGraph;
    } catch (e) {
      // Prevent logging fs events took too long warning
      logErrorOnBailout(options, snapshotPath, e);
      // This error means respondToFSEvents timed out handling the invalidation events
      // In this case we'll return a fresh RequestGraph
      return new RequestGraph();
    }
  }
  return new RequestGraph();
}
function logErrorOnBailout(options, snapshotPath, e) {
  if (e.message && e.message.includes('invalid clockspec')) {
    const snapshotContents = options.inputFS.readFileSync(snapshotPath, 'utf-8');
    _logger().default.warn({
      origin: '@parcel/core',
      message: `Error reading clockspec from snapshot, building with clean cache.`,
      meta: {
        snapshotContents: snapshotContents,
        trackableEvent: 'invalid_clockspec_error'
      }
    });
  } else if (!(e instanceof FSBailoutError)) {
    _logger().default.warn({
      origin: '@parcel/core',
      message: `Unexpected error loading cache from disk, building with clean cache.`,
      meta: {
        errorMessage: e.message,
        errorStack: e.stack,
        trackableEvent: 'cache_load_error'
      }
    });
  }
}