public async Task <TTValues[]> GetAsyncInternal <TTValues>(DateTime?from = null, DateTime?to = null, int start = 0, int pageSize = int.MaxValue, CancellationToken token = default) where TTValues : TimeSeriesEntry
        {
            TimeSeriesRangeResult <TTValues> rangeResult;

            from = from?.EnsureUtc();
            to   = to?.EnsureUtc();

            if (pageSize == 0)
            {
                return(Array.Empty <TTValues>());
            }

            if (Session.TimeSeriesByDocId.TryGetValue(DocId, out var cache) &&
                cache.TryGetValue(Name, out var ranges) &&
                ranges.Count > 0)
            {
                if (ranges[0].From > to || ranges[ranges.Count - 1].To < from)
                {
                    // the entire range [from, to] is out of cache bounds

                    // e.g. if cache is : [[2,3], [4,6], [8,9]]
                    // and requested range is : [12, 15]
                    // then ranges[ranges.Count - 1].To < from
                    // so we need to get [12,15] from server and place it
                    // at the end of the cache list

                    Session.IncrementRequestCount();

                    rangeResult = await Session.Operations.SendAsync(
                        new GetTimeSeriesOperation <TTValues>(DocId, Name, from, to, start, pageSize), Session._sessionInfo, token : token)
                                  .ConfigureAwait(false);

                    if (rangeResult == null)
                    {
                        return(null);
                    }

                    if (Session.NoTracking == false)
                    {
                        var index = ranges[0].From > to ? 0 : ranges.Count;
                        ranges.Insert(index, rangeResult);
                    }

                    return(rangeResult.Entries);
                }

                var(servedFromCache, resultToUser, mergedValues, fromRangeIndex, toRangeIndex) =
                    await ServeFromCacheOrGetMissingPartsFromServerAndMerge(from ?? DateTime.MinValue, to ?? DateTime.MaxValue, ranges, start, pageSize, token)
                    .ConfigureAwait(false);

                if (servedFromCache == false && Session.NoTracking == false)
                {
                    InMemoryDocumentSessionOperations.AddToCache(Name, from ?? DateTime.MinValue, to ?? DateTime.MaxValue, fromRangeIndex, toRangeIndex, ranges, cache, mergedValues);
                }

                return(resultToUser?.Take(pageSize).Cast <TTValues>().ToArray());
            }

            if (Session.DocumentsById.TryGetValue(DocId, out var document) &&
                document.Metadata.TryGet(Constants.Documents.Metadata.TimeSeries, out BlittableJsonReaderArray metadataTimeSeries) &&
                metadataTimeSeries.BinarySearch(Name, StringComparison.OrdinalIgnoreCase) < 0)
            {
                // the document is loaded in the session, but the metadata says that there is no such timeseries
                return(Array.Empty <TTValues>());
            }

            Session.IncrementRequestCount();

            rangeResult = await Session.Operations.SendAsync(
                new GetTimeSeriesOperation <TTValues>(DocId, Name, from, to, start, pageSize), Session._sessionInfo, token : token)
                          .ConfigureAwait(false);

            if (rangeResult == null)
            {
                return(null);
            }

            if (Session.NoTracking == false)
            {
                if (Session.TimeSeriesByDocId.TryGetValue(DocId, out cache) == false)
                {
                    Session.TimeSeriesByDocId[DocId] = cache = new Dictionary <string, List <TimeSeriesRangeResult> >(StringComparer.OrdinalIgnoreCase);
                }

                cache[Name] = new List <TimeSeriesRangeResult>
                {
                    rangeResult
                };
            }

            return(rangeResult.Entries);
        }
        ServeFromCache(
            DateTime from,
            DateTime to,
            int start,
            int pageSize,
            Action <ITimeSeriesIncludeBuilder> includes,
            CancellationToken token)
        {
            var cache  = Session.TimeSeriesByDocId[DocId];
            var ranges = cache[Name];

            // try to find a range in cache that contains [from, to]
            // if found, chop just the relevant part from it and return to the user.

            // otherwise, try to find two ranges (fromRange, toRange),
            // such that 'fromRange' is the last occurence for which range.From <= from
            // and 'toRange' is the first occurence for which range.To >= to.
            // At the same time, figure out the missing partial ranges that we need to get from the server.

            int toRangeIndex;
            var fromRangeIndex = -1;

            List <TimeSeriesRange> rangesToGetFromServer = default;

            for (toRangeIndex = 0; toRangeIndex < ranges.Count; toRangeIndex++)
            {
                if (ranges[toRangeIndex].From <= from)
                {
                    if ((ranges[toRangeIndex].To >= to) || (ranges[toRangeIndex].Entries.Length - start >= pageSize))
                    {
                        // we have the entire range in cache
                        // we have all the range we need
                        // or that we have all the results we need in smaller range

                        return(ChopRelevantRange(ranges[toRangeIndex], from, to, start, pageSize));
                    }

                    fromRangeIndex = toRangeIndex;
                    continue;
                }

                // can't get the entire range from cache

                rangesToGetFromServer ??= new List <TimeSeriesRange>();

                // add the missing part [f, t] between current range start (or 'from')
                // and previous range end (or 'to') to the list of ranges we need to get from server

                rangesToGetFromServer.Add(new TimeSeriesRange
                {
                    Name = Name,
                    From = toRangeIndex == 0 || ranges[toRangeIndex - 1].To < from
                        ? from
                        : ranges[toRangeIndex - 1].To,
                    To = ranges[toRangeIndex].From <= to
                        ? ranges[toRangeIndex].From
                        : to
                });

                if (ranges[toRangeIndex].To >= to)
                {
                    break;
                }
            }

            if (toRangeIndex == ranges.Count)
            {
                // requested range [from, to] ends after all ranges in cache
                // add the missing part between the last range end and 'to'
                // to the list of ranges we need to get from server

                rangesToGetFromServer ??= new List <TimeSeriesRange>();
                rangesToGetFromServer.Add(new TimeSeriesRange
                {
                    Name = Name,
                    From = ranges[ranges.Count - 1].To,
                    To   = to
                });
            }

            // get all the missing parts from server

            Session.IncrementRequestCount();

            var details = await Session.Operations.SendAsync(
                new GetMultipleTimeSeriesOperation(DocId, rangesToGetFromServer, start, pageSize, includes), Session._sessionInfo, token : token)
                          .ConfigureAwait(false);

            if (includes != null)
            {
                RegisterIncludes(details);
            }

            // merge all the missing parts we got from server
            // with all the ranges in cache that are between 'fromRange' and 'toRange'

            var mergedValues = MergeRangesWithResults(from, to, ranges, fromRangeIndex, toRangeIndex,
                                                      resultFromServer: details.Values[Name], out var resultToUser);

            if (Session.NoTracking == false)
            {
                from = details.Values[Name].Min(ts => ts.From);
                to   = details.Values[Name].Max(ts => ts.To);
                InMemoryDocumentSessionOperations.AddToCache(Name, from, to, fromRangeIndex, toRangeIndex, ranges, cache, mergedValues);
            }

            return(resultToUser);
        }