UNPKG

@azure/cosmos

Version:
182 lines 7.66 kB
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. import { parseBaseContinuationToken, } from "../../documents/ContinuationToken/CompositeQueryContinuationToken.js"; import { PartitionRangeManager, isPartitionExhausted } from "../PartitionRangeManager.js"; /** * Base abstract class for continuation token management. * Provides common functionality shared between parallel and ORDER BY query token managers. * @internal */ export class BaseContinuationTokenManager { ranges = []; partitionRangeManager = new PartitionRangeManager(); collectionLink; offset; limit; constructor(collectionLink, initialContinuationToken) { this.collectionLink = collectionLink; if (initialContinuationToken) { const token = parseBaseContinuationToken(initialContinuationToken); if (token?.rangeMappings) { this.ranges.push(...token.rangeMappings); } this.offset = token?.offset; this.limit = token?.limit; } } /** * Provides controlled access to partition range manager for subclasses. * This is the only protected access point needed. */ get partitionManager() { return this.partitionRangeManager; } /** * Provides controlled access to ranges for subclasses. * Made protected to allow subclass range management. */ get rangeList() { return this.ranges; } /** * Processes query results and generates continuation tokens for pagination. * Handles response data processing, range management, and token generation. * * @param pageSize - Maximum number of items to return in this page * @param isResponseEmpty - Whether the current response contains no data * @param responseResult - Optional response data containing partition mappings and query-specific data * @returns Object containing the end index for slicing results and optional continuation token for next page */ paginateResults(pageSize, isResponseEmpty, responseResult) { if (responseResult) { this.processResponseResult(responseResult); } this.removeExhaustedRangesFromRanges(); const result = this.processRangesForPagination(pageSize, isResponseEmpty); const tokenString = this.generateContinuationTokenString(); // Clean up processed ranges this.trimProcessedData(result.processedRanges, result.endIndex); return { endIndex: result.endIndex, continuationToken: tokenString, }; } /** * Generates continuation token string using the appropriate serialization function. * This provides a common implementation that delegates to query-specific logic. */ generateContinuationTokenString() { const token = this.getCurrentContinuationToken(); if (!token) { return undefined; } const serializeFunction = this.getSerializationFunction(); return serializeFunction(token); } /** * Cleans up processed data after a page has been returned. * Handles both common and query-specific cleanup. */ trimProcessedData(processedRanges, endIndex) { processedRanges.forEach((rangeId) => { this.partitionRangeManager.removePartitionRangeMapping(rangeId); }); // Delegate query-specific cleanup to subclass this.performQuerySpecificDataTrim(processedRanges, endIndex); } addPartitionKeyRangeMap(partitionKeyRangeMap) { this.partitionRangeManager.addPartitionKeyRangeMap(partitionKeyRangeMap); } /** * Processes the entire response result and updates the continuation token manager state. * This encapsulates all response handling logic in one place. */ processResponseResult(responseResult) { // Handle partition key range map if (responseResult.partitionKeyRangeMap) { this.addPartitionKeyRangeMap(responseResult.partitionKeyRangeMap); } // Handle partition range updates if (responseResult.updatedContinuationRanges) { this.handlePartitionRangeChanges(responseResult.updatedContinuationRanges); } this.processQuerySpecificResponse(responseResult); } /** * Compacts the ranges array in place by keeping only items that satisfy the predicate. * This preserves the same array reference while removing unwanted entries. */ compactRangesInPlace(shouldKeep) { let writeIndex = 0; for (let i = 0; i < this.ranges.length; i++) { const mapping = this.ranges[i]; if (!shouldKeep(mapping)) { continue; } this.ranges[writeIndex++] = mapping; } this.ranges.length = writeIndex; } removeExhaustedRangesFromRanges() { if (!this.ranges || !Array.isArray(this.ranges)) { return; } this.compactRangesInPlace((mapping) => !!mapping && !isPartitionExhausted(mapping.continuationToken)); } handlePartitionRangeChanges(updatedContinuationRanges) { if (updatedContinuationRanges && Object.keys(updatedContinuationRanges).length === 0) { return; } Object.entries(updatedContinuationRanges).forEach(([rangeKey, rangeChange]) => { this.processRangeChange(rangeKey, rangeChange); }); } processRangeChange(_rangeKey, rangeChange) { const { oldRange, newRanges, continuationToken } = rangeChange; if (newRanges.length === 1) { this.handleRangeMerge(oldRange, newRanges[0], continuationToken); } else { this.handleRangeSplit(oldRange, newRanges, continuationToken); } } handleRangeMerge(oldRange, newRange, continuationToken) { // Find existing range mapping to update in the common ranges array const existingMappingIndex = this.ranges.findIndex((mapping) => mapping.queryRange.min === oldRange.min && mapping.queryRange.max === oldRange.max); if (existingMappingIndex < 0) { return; } // Update existing mapping with new range properties const existingMapping = this.ranges[existingMappingIndex]; // Create new simplified QueryRange with updated boundaries const updatedQueryRange = { min: newRange.min, max: newRange.max, }; // Update the mapping existingMapping.queryRange = updatedQueryRange; existingMapping.continuationToken = continuationToken; } handleRangeSplit(oldRange, newRanges, continuationToken) { // Remove the old range mapping from the common ranges array (in-place, no reinitialization) this.compactRangesInPlace((mapping) => !(mapping.queryRange.min === oldRange.min && mapping.queryRange.max === oldRange.max)); // Add new range mappings for each split range newRanges.forEach((newRange) => { this.createNewRangeMapping(newRange, continuationToken); }); } createNewRangeMapping(partitionKeyRange, continuationToken) { // Create new simplified QueryRange const queryRange = { min: partitionKeyRange.min, max: partitionKeyRange.max, }; // Create new QueryRangeWithContinuationToken const newRangeWithToken = { queryRange: queryRange, continuationToken: continuationToken, }; this.ranges.push(newRangeWithToken); } } //# sourceMappingURL=BaseContinuationTokenManager.js.map