@azure/cosmos
Version:
Microsoft Azure Cosmos DB Service Node.js SDK for NOSQL API
174 lines • 7.29 kB
JavaScript
// 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