Repository: gatsbyjs/gatsby
File: packages/gatsby/src/redux/nodes.ts (TypeScript)

Code before:

    // TODO: there's probably a more efficient algorithm to do set
    //       subtraction in such a way that we dont have to resort here
    return [...set].sort((A, B) => (A.id < B.id ? -1 : A.id === B.id ? 0 : 1))
  }

  if (op === `$ne`) {
    const set = new Set(filterCache.meta.nodesUnordered)

    if (filterValue === null) {
      // Edge case: $ne with `null` returns only the nodes that contain the full
      // path and that don't resolve to null, so drop `undefined` as well.
      let cache = filterCache.byValue.get(undefined)
      if (cache) cache.forEach(node => set.delete(node))
      cache = filterCache.byValue.get(null)
      if (cache) cache.forEach(node => set.delete(node))
    } else {
      // Not excluding null so it should include undefined leafs or leafs where
      // only the partial path exists for whatever reason.
      const cache = filterCache.byValue.get(filterValue)
      if (cache) cache.forEach(node => set.delete(node))
    }

    // TODO: there's probably a more efficient algorithm to do set
    //       subtraction in such a way that we dont have to resort here
    return [...set].sort((A, B) => (A.id < B.id ? -1 : A.id === B.id ? 0 : 1))
  }

  if (op === `$regex`) {
    // Note: $glob is converted to $regex so $glob filters go through here, too
    // Aside from the input pattern format, further behavior is exactly the same.

    // The input to the filter must be a string (including leading/trailing slash and regex flags)
    // By the time the filter reaches this point, the filterValue has to be a regex.

    if (!(filterValue instanceof RegExp)) {
      throw new Error(
        `The value for the $regex comparator must be an instance of RegExp`
      )
    }
    const regex = filterValue

    const result: Array<IGatsbyNode> = []
    filterCache.byValue.forEach((nodes, value) => {
      // TODO: does the value have to be a string for $regex? Can we auto-ignore any non-strings? Or does it coerce.
      // Note: partial paths should also be included for regex (matching Sift behavior)
      if (value !== undefined && regex.test(String(value))) {
        nodes.forEach(node => result.push(node))
      }
    })


Review comment (refactor):
Looks like `nin` and `ne` have a similar logic for diffing lists of nodes. Maybe we can extract it to a separate utility like `diffNodes` similar to `intersectNodes`?

Code after:
  }

  if (op === `$ne`) {
    const set = new Set(filterCache.meta.nodesUnordered)

    removeBucketFromSet(filterValue, filterCache, set)

    // TODO: there's probably a more efficient algorithm to do set
    //       subtraction in such a way that we dont have to resort here
    return [...set].sort((A, B) => A.internal.counter - B.internal.counter)
  }

  if (op === `$regex`) {
    // Note: $glob is converted to $regex so $glob filters go through here, too
    // Aside from the input pattern format, further behavior is exactly the same.

    // The input to the filter must be a string (including leading/trailing slash and regex flags)
    // By the time the filter reaches this point, the filterValue has to be a regex.

    if (!(filterValue instanceof RegExp)) {
      throw new Error(
        `The value for the $regex comparator must be an instance of RegExp`
      )
    }
    const regex = filterValue

    const result: Array<IGatsbyNode> = []
    filterCache.byValue.forEach((nodes, value) => {
      // TODO: does the value have to be a string for $regex? Can we auto-ignore any non-strings? Or does it coerce.
      // Note: partial paths should also be included for regex (matching Sift behavior)
      if (value !== undefined && regex.test(String(value))) {
        nodes.forEach(node => result.push(node))
      }
    })

    // TODO: we _can_ cache this list as well. Might make sense if it turns out that $regex is mostly used with literals
    // TODO: it may make sense to first collect all buckets and then to .concat them, or merge sort them
    return result.sort((A, B) => A.internal.counter - B.internal.counter)
  }

  if (filterValue == null) {
    if (op === `$lt` || op === `$gt`) {
      // Nothing is lt/gt null
      return undefined
    }

    // This is an edge case and this value should be directly indexed
    // For `lte`/`gte` this should only return nodes for `null`, not a "range"
    return filterCache.byValue.get(filterValue)
  }