UNPKG

@azure/cosmos

Version:
174 lines 7.29 kB
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. import { parseBaseContinuationToken, } from "../../documents/ContinuationToken/CompositeQueryContinuationToken.js"; import { PartitionRangeManager } 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(); constructor(initialContinuationToken) { if (initialContinuationToken) { const token = parseBaseContinuationToken(initialContinuationToken); if (token?.rangeMappings) { this.ranges = token.rangeMappings; } } } /** * 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) { // Process response data 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); } isPartitionExhausted(continuationToken) { return (!continuationToken || continuationToken === "" || continuationToken === "null" || continuationToken.toLowerCase() === "null"); } removeExhaustedRangesFromRanges() { if (!this.ranges || !Array.isArray(this.ranges)) { return; } this.ranges = this.ranges.filter((mapping) => { if (!mapping) { return false; } const isExhausted = this.isPartitionExhausted(mapping.continuationToken); return !isExhausted; }); } 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 this.ranges = this.ranges.filter((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