DocumentSource::GetNextResult DocumentSourceCheckInvalidate::getNext() {
    pExpCtx->checkForInterrupt();

    invariant(!pExpCtx->inMongos);

    if (_queuedInvalidate) {
        const auto res = DocumentSource::GetNextResult(std::move(_queuedInvalidate.get()));
        _queuedInvalidate.reset();
        return res;
    }

    auto nextInput = pSource->getNext();
    if (!nextInput.isAdvanced())
        return nextInput;

    auto doc = nextInput.getDocument();
    const auto& kOperationTypeField = DSCS::kOperationTypeField;
    DSCS::checkValueType(doc[kOperationTypeField], kOperationTypeField, BSONType::String);
    auto operationType = doc[kOperationTypeField].getString();

    // If this command should invalidate the stream, generate an invalidate entry and queue it up
    // to be returned after the notification of this command. The new entry will have a nearly
    // identical resume token to the notification for the command, except with an extra flag
    // indicating that the token is from an invalidate. This flag is necessary to disambiguate
    // the two tokens, and thus preserve a total ordering on the stream.

    // As a special case, if a client receives an invalidate like this one and then wants to
    // start a new stream after the invalidate, they can use the "startAfter" option, in which
    // case '_ignoreFirstInvalidate' will be set, and we should ignore (AKA not generate) the
    // very first invalidation.
    if (isInvalidatingCommand(pExpCtx, operationType) && !_ignoreFirstInvalidate) {
        auto resumeTokenData = ResumeToken::parse(doc[DSCS::kIdField].getDocument()).getData();
        resumeTokenData.fromInvalidate = ResumeTokenData::FromInvalidate::kFromInvalidate;

        MutableDocument result(Document{{DSCS::kIdField, ResumeToken(resumeTokenData).toDocument()},
                                        {DSCS::kOperationTypeField, DSCS::kInvalidateOpType},
                                        {DSCS::kClusterTimeField, doc[DSCS::kClusterTimeField]}});

        // If we're in a sharded environment, we'll need to merge the results by their sort key, so
        // add that as metadata.
        result.copyMetaDataFrom(doc);

        _queuedInvalidate = result.freeze();
    }

    // Regardless of whether the first document we see is an invalidating command, we only skip the
    // first invalidate for streams with the 'startAfter' option, so we should not skip any
    // invalidates that come after the first one.
    _ignoreFirstInvalidate = false;

    return nextInput;
}
DocumentSource::GetNextResult DocumentSourceCloseCursor::getNext() {
    pExpCtx->checkForInterrupt();

    // Close cursor if we have returned an invalidate entry.
    if (_shouldCloseCursor) {
        uasserted(ErrorCodes::CloseChangeStream, "Change stream has been invalidated");
    }

    if (_queuedInvalidate) {
        _shouldCloseCursor = true;
        return DocumentSource::GetNextResult(std::move(_queuedInvalidate.get()));
    }

    auto nextInput = pSource->getNext();
    if (!nextInput.isAdvanced())
        return nextInput;

    auto doc = nextInput.getDocument();
    const auto& kOperationTypeField = DocumentSourceChangeStream::kOperationTypeField;
    DocumentSourceChangeStream::checkValueType(
        doc[kOperationTypeField], kOperationTypeField, BSONType::String);
    auto operationType = doc[kOperationTypeField].getString();
    if (operationType == DocumentSourceChangeStream::kInvalidateOpType) {
        // Pass the invalidation forward, so that it can be included in the results, or
        // filtered/transformed by further stages in the pipeline, then throw an exception
        // to close the cursor on the next call to getNext().
        _shouldCloseCursor = true;
    }

    // Check if this is an invalidating command and the next entry should be an "invalidate".
    if (isInvalidatingCommand(pExpCtx, operationType)) {
        _queuedInvalidate = Document{
            {DocumentSourceChangeStream::kIdField, doc[DocumentSourceChangeStream::kIdField]},
            {DocumentSourceChangeStream::kClusterTimeField,
             doc[DocumentSourceChangeStream::kClusterTimeField]},
            {DocumentSourceChangeStream::kOperationTypeField, "invalidate"_sd}};
    }

    return nextInput;
}