Skip to content

Fix Cosmos DB pagination for backwards compatibility#1594

Open
MarcAstr0 wants to merge 4 commits intoboostercloud:mainfrom
Optum:fix/cosmos-pagination
Open

Fix Cosmos DB pagination for backwards compatibility#1594
MarcAstr0 wants to merge 4 commits intoboostercloud:mainfrom
Optum:fix/cosmos-pagination

Conversation

@MarcAstr0
Copy link
Collaborator

@MarcAstr0 MarcAstr0 commented Mar 2, 2026

Description

v3.4.2 (see #1587) introduced continuation-token-based pagination for the Azure provider. That change replaced the numeric id in the response cursor with a continuationToken. For backwards compatibility, OFFSET-based pagination could still be forced by sending a numeric id in the afterCursor property of the GraphQL request (e.g., '0' for the first page). However, this broke existing frontends that didn't force OFFSET-based pagination and expected a numeric id in the cursor. This PR restores backwards compatibility by always returning a numeric cursor id (indicating the cursor's position) alongside the optional continuationToken.

Changes

  • Refactor query-helper.ts to always return a numeric id in the cursor for backwards compatibility

Checks

  • Project Builds
  • Project passes tests and checks
  • Updated documentation accordingly

@what-the-diff
Copy link

what-the-diff bot commented Mar 2, 2026

PR Summary

  • Addition of a new JSON file
    A new file named fix-cosmos-pagination_2026-02-27-21-10.json was added, which addresses issues with database pagination. This was classified as a minor fix for the core package of our application.

  • Modifications to query-helper.ts

    • Introduction of a new numerical value: Establishes a default page size of 100 entries.
    • Reworked pagination mechanics: Now, our database interactions have been improved to better manage data pagination, especially in terms of consistency and continuity.
    • Adjusted legacy querying system: Older methods of querying data are now more accommodating of how much data is shown on each page while retaining their former functionality.
    • Updated how cursor IDs are calculated: Cursor ID, which is used in moving from one page of data to another, is now based on an effective limit to ensure consistent navigation across different pages of data.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Restores backward compatibility for Azure/CosmosDB pagination by ensuring GraphQL cursors always include a numeric id while still supporting continuation-token pagination.

Changes:

  • Add a default page size and revise Cosmos feed options to better support continuation tokens.
  • Always include a numeric cursor.id (and optionally cursor.continuationToken) in paginated results.
  • Update the monorepo lockfile and add a Rush change file for a patch release.

Reviewed changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated 5 comments.

File Description
packages/framework-provider-azure/src/helpers/query-helper.ts Adjusts pagination logic to return a numeric cursor id for backward compatibility and refines continuation-token/legacy OFFSET behavior.
common/config/rush/pnpm-lock.yaml Updates workspace specifiers and dependency resolutions in the pnpm lockfile.
common/changes/@boostercloud/framework-core/fix-cosmos-pagination_2026-02-27-21-10.json Adds a Rush change file to drive release/versioning metadata for the change.
Files not reviewed (1)
  • common/config/rush/pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +109 to +112
// cursor.id advances by the page size (limit) to maintain consistent page-based offsets
// that frontends rely on (e.g., limit=5 produces cursors 5, 10 ,15, ...)
const previousOffset = afterCursor?.id ? parseInt(afterCursor.id) : 0
const effectiveLimit = limit ?? DEFAULT_PAGE_SIZE
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

previousOffset is computed with parseInt(afterCursor.id) without guarding for non-numeric values. If a client sends an unexpected/non-numeric id, this will become NaN and you’ll end up returning cursor.id: "NaN" (and also advance offsets incorrectly). Consider reusing the numeric check you already do for hasLegacyCursor (or fallback to 0 when Number.isNaN(parsed)), so cursor IDs are always valid numeric strings.

Copilot uses AI. Check for mistakes.
Comment on lines +79 to +82
// Always set maxItemCount when limit is provided or when using continuation token
if (limit || afterCursor?.continuationToken) {
feedOptions.maxItemCount = limit ?? DEFAULT_PAGE_SIZE
}
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When paginatedVersion is true and limit is omitted on the first page, effectiveLimit defaults to DEFAULT_PAGE_SIZE for the numeric cursor, but feedOptions.maxItemCount is not set (because there is no limit and no afterCursor.continuationToken). Cosmos may then return an arbitrary page size, making the returned cursor.id inconsistent with the number of items actually returned and potentially breaking the “id indicates position/page” assumption. Consider always setting feedOptions.maxItemCount to limit ?? DEFAULT_PAGE_SIZE whenever the continuation-token path is used (not only once a continuation token exists).

Suggested change
// Always set maxItemCount when limit is provided or when using continuation token
if (limit || afterCursor?.continuationToken) {
feedOptions.maxItemCount = limit ?? DEFAULT_PAGE_SIZE
}
// Always set maxItemCount for Cosmos pagination (defaulting when limit is omitted)
feedOptions.maxItemCount = limit ?? DEFAULT_PAGE_SIZE

Copilot uses AI. Check for mistakes.
Comment on lines 114 to +118
let cursor: Record<string, string> | undefined
if (continuationToken) {
cursor = { continuationToken }
cursor = { continuationToken, id: (previousOffset + effectiveLimit).toString() }
} else if (finalResources.length > 0) {
const currentOffset = afterCursor?.id && !isNaN(parseInt(afterCursor.id)) ? parseInt(afterCursor.id) : 0
cursor = { id: (currentOffset + finalResources.length).toString() } // Use the length of the results to calculate the next id
cursor = { id: (previousOffset + effectiveLimit).toString() }
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The continuation-token pagination branch now returns a composite cursor ({ continuationToken, id }) and computes id using effectiveLimit. There are existing unit tests for search() in this package, but none appear to cover the continuation-token path (e.g., asserting maxItemCount/continuationToken usage and the returned cursor shape). Please add/update tests to cover: (1) first page continuation-token pagination (no afterCursor), (2) subsequent page with { continuationToken, id }, and (3) behavior when limit is omitted (default page size).

Copilot uses AI. Check for mistakes.
Comment on lines +3 to +7
{
"packageName": "@boostercloud/framework-core",
"comment": "Fix pagination issues with Cosmos DB",
"type": "patch"
}
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change file declares a patch for @boostercloud/framework-core, but the functional change in this PR is in @boostercloud/framework-provider-azure (Cosmos pagination logic). With Rush change files, this likely means the Azure provider won’t get a version bump/release containing the fix. Consider generating the change file for @boostercloud/framework-provider-azure (or whichever package actually ships query-helper.ts).

Copilot uses AI. Check for mistakes.
Comment on lines +5170 to 5177
[email protected]:
resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==}
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting [email protected]
hasBin: true

[email protected]:
resolution: {integrity: sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==}
engines: {node: 20 || >=22}
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lockfile introduces [email protected], which is marked as deprecated due to “widely publicized security vulnerabilities” in its own metadata. If possible, consider updating the dependency chain so a supported glob version is used (or confirm why this downgrade is required), since this can affect build tooling security posture.

Suggested change
[email protected]:
resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==}
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting [email protected]
hasBin: true
[email protected]:
resolution: {integrity: sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==}
engines: {node: 20 || >=22}
[email protected]:
resolution: {integrity: sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==}
engines: {node: 20 || >=22}
hasBin: true

Copilot uses AI. Check for mistakes.
@MarcAstr0
Copy link
Collaborator Author

/integration sha=04d10e1

@github-actions
Copy link
Contributor

github-actions bot commented Mar 2, 2026

⌛ Integration tests are running...

Check their status here 👈

@github-actions
Copy link
Contributor

github-actions bot commented Mar 2, 2026

❌ Oh no! Integration tests have failed

@github-actions
Copy link
Contributor

github-actions bot commented Mar 2, 2026

⌛ Integration tests are running...

Check their status here 👈

@github-actions
Copy link
Contributor

github-actions bot commented Mar 2, 2026

❌ Oh no! Integration tests have failed

@github-actions
Copy link
Contributor

github-actions bot commented Mar 2, 2026

⌛ Integration tests are running...

Check their status here 👈

@github-actions
Copy link
Contributor

github-actions bot commented Mar 2, 2026

❌ Oh no! Integration tests have failed

@github-actions
Copy link
Contributor

github-actions bot commented Mar 3, 2026

⌛ Integration tests are running...

Check their status here 👈

@github-actions
Copy link
Contributor

github-actions bot commented Mar 3, 2026

❌ Oh no! Integration tests have failed

@github-actions
Copy link
Contributor

github-actions bot commented Mar 3, 2026

⌛ Integration tests are running...

Check their status here 👈

@github-actions
Copy link
Contributor

github-actions bot commented Mar 3, 2026

❌ Oh no! Integration tests have failed

@MarcAstr0
Copy link
Collaborator Author

/integration sha=589add3

@github-actions
Copy link
Contributor

github-actions bot commented Mar 3, 2026

⌛ Integration tests are running...

Check their status here 👈

@github-actions
Copy link
Contributor

github-actions bot commented Mar 3, 2026

✅ Integration tests have finished successfully!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants