Skip to content

feat(query-graphql): add offset field to cursor pagination#452

Open
jtomaszewski wants to merge 1 commit into
TriPSs:masterfrom
fullstackhouse:feat/cursor-pagination-offset
Open

feat(query-graphql): add offset field to cursor pagination#452
jtomaszewski wants to merge 1 commit into
TriPSs:masterfrom
fullstackhouse:feat/cursor-pagination-offset

Conversation

@jtomaszewski

@jtomaszewski jtomaszewski commented Mar 21, 2026

Copy link
Copy Markdown
Contributor

Currently a collection can either have cursor-based or offset-based pagination. What if I liked both, depending on the client usage? (e.g. offset in superadmin apps; cursor in consumer apps)

This allows for this.

Summary

  • Add offset field to cursor pagination, allowing it to be combined with cursor fields (first/after, last/before) for additional skipping
  • This enables use cases like: skip N records after a cursor position

Changes

  • Add offset field to CursorPagingType interface
  • Add offset GraphQL field with @IsInt and @Min(0) validation
  • Update LimitOffsetPagerStrategy.getOffset() to include additional offset
  • Add documentation for "Using Offset with Cursors"

Example Usage

# Skip 3 more records after cursor position, then return 2 items
{
  todoItems(paging: { first: 2, after: "YXJyYXljb25uZWN0aW9uOjE=", offset: 3 }) {
    edges {
      node {
        id
        title
      }
    }
  }
}

Test plan

  • Added validation tests for first + offset and first + after + offset combinations
  • Added test for rejecting negative offset values
  • Added connection type tests verifying correct offset calculation
  • All 497 existing tests pass
  • Updated snapshots for new GraphQL schema field

🤖 Generated with Claude Code

Allow combining offset with cursor pagination fields (first/after,
last/before) for additional skipping within cursor-based pagination.

- Add offset field to CursorPagingType interface
- Add offset GraphQL field with validation (@ISINT, @min(0))
- Update limit-offset pager strategy to add offset to cursor position

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Mar 21, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR adds support for combining an offset parameter with cursor-based GraphQL pagination. The implementation introduces a new optional offset field to cursor paging types, updates offset calculation logic to incorporate this value, and includes comprehensive test coverage and documentation for the new functionality.

Changes

Cohort / File(s) Summary
Documentation
documentation/docs/graphql/queries/paging.mdx
Added new "Using Offset with Cursors" section explaining how offset can be combined with cursor-based paging with a GraphQL example and behavior description.
Paging Type Definitions
packages/query-graphql/src/types/query/paging/cursor-paging.type.ts, packages/query-graphql/src/types/query/paging/interfaces.ts
Added optional offset?: number field to CursorPagingType interface and GraphQLCursorPagingImpl input type with validation rules (non-negative integer).
Pager Strategy Logic
packages/query-graphql/src/types/connection/cursor/pager/strategies/limit-offset.pager-strategy.ts
Updated getOffset() method to incorporate cursor.offset into computed offset, handling both forward and backward paging scenarios with enableFetchAllWithNegative.
Type Specification Tests
packages/query-graphql/__tests__/types/connection/cursor-connection.type.spec.ts
Added test cases verifying correct cursor offset computation when offset is combined with first parameter and when after cursor is also present.
Query Arguments Validation Tests
packages/query-graphql/__tests__/types/query/cursor-query-args.type.spec.ts
Added validation tests confirming offset can be combined with first and after cursors, and that negative offset values produce validation errors.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • feat: Bump dependencies #401: Modifies the same limit-offset.pager-strategy.ts file regarding offset handling behavior when combining with cursor-based paging.

Poem

🐰 A cursor hops, offset in tow,
Skipping records row by row,
First and after, now with grace,
Hop beyond to find your place! 🌟

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title 'feat(query-graphql): add offset field to cursor pagination' directly and clearly describes the main change: adding an offset field to cursor pagination functionality.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

Migrating from UI to YAML configuration.

Use the @coderabbitai configuration command in a PR comment to get a dump of all your UI settings in YAML format. You can then edit this YAML file and upload it to the root of your repository to configure CodeRabbit programmatically.

@nx-cloud

nx-cloud Bot commented Mar 21, 2026

Copy link
Copy Markdown

🤖 Nx Cloud AI Fix Eligible

An automatically generated fix could have helped fix failing tasks for this run, but Self-healing CI is disabled for this workspace. Visit workspace settings to enable it and get automatic fixes in future runs.

To disable these notifications, a workspace admin can disable them in workspace settings.


View your CI Pipeline Execution ↗ for commit 2e8c859

Command Status Duration Result
nx run-many --target=lint --all ❌ Failed 15s View ↗
nx run-many --target=e2e --all ✅ Succeeded 1m 53s View ↗
nx run-many --target=test --all ✅ Succeeded 42s View ↗
nx run-many --target=build --all ✅ Succeeded 1s View ↗
nx run workspace:version ✅ Succeeded <1s View ↗

☁️ Nx Cloud last updated this comment at 2026-03-21 21:21:27 UTC

@pkg-pr-new

pkg-pr-new Bot commented Mar 21, 2026

Copy link
Copy Markdown

Open in StackBlitz

@ptc-org/nestjs-query-core

npm i https://pkg.pr.new/@ptc-org/nestjs-query-core@452

@ptc-org/nestjs-query-graphql

npm i https://pkg.pr.new/@ptc-org/nestjs-query-graphql@452

@ptc-org/nestjs-query-mikro-orm

npm i https://pkg.pr.new/@ptc-org/nestjs-query-mikro-orm@452

@ptc-org/nestjs-query-mongoose

npm i https://pkg.pr.new/@ptc-org/nestjs-query-mongoose@452

@ptc-org/nestjs-query-sequelize

npm i https://pkg.pr.new/@ptc-org/nestjs-query-sequelize@452

@ptc-org/nestjs-query-typegoose

npm i https://pkg.pr.new/@ptc-org/nestjs-query-typegoose@452

@ptc-org/nestjs-query-typeorm

npm i https://pkg.pr.new/@ptc-org/nestjs-query-typeorm@452

commit: 2e8c859

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
documentation/docs/graphql/queries/paging.mdx (1)

389-407: Good documentation addition for the new offset feature.

The example and explanation clearly demonstrate the primary use case of combining offset with cursor-based pagination.

Consider expanding the documentation to cover:

  1. Using offset with just first (without after) - e.g., { first: 2, offset: 5 } to skip the first 5 records
  2. Clarifying whether offset works with backward paging (last/before) and what behavior to expect
  3. A brief note on validation (non-negative integers only)

This would provide users with a complete picture of the feature's capabilities.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@documentation/docs/graphql/queries/paging.mdx` around lines 389 - 407, Update
the paging docs for the new offset feature by adding (1) an example using offset
with only forward paging (e.g., todoItems(paging: { first: 2, offset: 5 })) and
a short sentence explaining it skips the first N records then returns the next
first items, (2) a clarification about backward paging behavior with last/before
(whether offset is applied after moving to the before cursor or applied before
backward slicing — document the chosen semantics for todoItems' paging args:
last, before, offset), and (3) a brief validation note stating offset accepts
only non-negative integers and how invalid values are handled; reference the
todoItems(paging: { first/last, after/before, offset }) argument names so the
maintainer can place these additions near the existing cursor+offset example.
packages/query-graphql/__tests__/types/query/cursor-query-args.type.spec.ts (1)

110-144: Good test coverage for the new offset validation scenarios.

The tests appropriately verify:

  • first + offset combination passes validation
  • first + after + offset combination passes validation
  • Negative offset values are rejected with a validation error

The test patterns align with existing tests in this file.

Consider adding test coverage for backward paging with offset (last + before + offset) to verify validation behavior is consistent across both paging directions. This would match the implementation which allows offset with backward paging.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/query-graphql/__tests__/types/query/cursor-query-args.type.spec.ts`
around lines 110 - 144, Add a unit test that mirrors the forward-paging cases
but for backward paging: create a TestCursorQuery instance with paging { last:
<number>, before: '<cursor>', offset: <number> } (use
plainToClass(TestCursorQuery, ...)) and assert validateSync(queryInstance)
returns an empty array; also add a case that uses last+offset without before if
desired. Target the paging.offset validation paths by referencing
TestCursorQuery, paging, offset, last, before, plainToClass and validateSync so
the behavior allowing offset with backward paging is covered.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@documentation/docs/graphql/queries/paging.mdx`:
- Around line 389-407: Update the paging docs for the new offset feature by
adding (1) an example using offset with only forward paging (e.g.,
todoItems(paging: { first: 2, offset: 5 })) and a short sentence explaining it
skips the first N records then returns the next first items, (2) a clarification
about backward paging behavior with last/before (whether offset is applied after
moving to the before cursor or applied before backward slicing — document the
chosen semantics for todoItems' paging args: last, before, offset), and (3) a
brief validation note stating offset accepts only non-negative integers and how
invalid values are handled; reference the todoItems(paging: { first/last,
after/before, offset }) argument names so the maintainer can place these
additions near the existing cursor+offset example.

In `@packages/query-graphql/__tests__/types/query/cursor-query-args.type.spec.ts`:
- Around line 110-144: Add a unit test that mirrors the forward-paging cases but
for backward paging: create a TestCursorQuery instance with paging { last:
<number>, before: '<cursor>', offset: <number> } (use
plainToClass(TestCursorQuery, ...)) and assert validateSync(queryInstance)
returns an empty array; also add a case that uses last+offset without before if
desired. Target the paging.offset validation paths by referencing
TestCursorQuery, paging, offset, last, before, plainToClass and validateSync so
the behavior allowing offset with backward paging is covered.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a9368cc8-d3f7-4cd2-8305-293dc2c7fa29

📥 Commits

Reviewing files that changed from the base of the PR and between e21819f and 2e8c859.

⛔ Files ignored due to path filters (10)
  • packages/query-graphql/__tests__/resolvers/__snapshots__/read.resolver.spec.ts.snap is excluded by !**/*.snap
  • packages/query-graphql/__tests__/resolvers/federation/__snapshots__/federation.resolver.spec.ts.snap is excluded by !**/*.snap
  • packages/query-graphql/__tests__/resolvers/relations/read-relation/many/__snapshots__/1.spec.ts.snap is excluded by !**/*.snap
  • packages/query-graphql/__tests__/resolvers/relations/read-relation/many/__snapshots__/2.spec.ts.snap is excluded by !**/*.snap
  • packages/query-graphql/__tests__/resolvers/relations/read-relation/many/__snapshots__/3.spec.ts.snap is excluded by !**/*.snap
  • packages/query-graphql/__tests__/resolvers/relations/read-relation/many/__snapshots__/4.spec.ts.snap is excluded by !**/*.snap
  • packages/query-graphql/__tests__/resolvers/relations/read-relation/many/__snapshots__/5.spec.ts.snap is excluded by !**/*.snap
  • packages/query-graphql/__tests__/types/query/__snapshots__/cursor-query-args-with-decorator.spec.ts.snap is excluded by !**/*.snap
  • packages/query-graphql/__tests__/types/query/__snapshots__/cursor-query-args.type.spec.ts.snap is excluded by !**/*.snap
  • packages/query-graphql/__tests__/types/query/__snapshots__/paging.type.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (6)
  • documentation/docs/graphql/queries/paging.mdx
  • packages/query-graphql/__tests__/types/connection/cursor-connection.type.spec.ts
  • packages/query-graphql/__tests__/types/query/cursor-query-args.type.spec.ts
  • packages/query-graphql/src/types/connection/cursor/pager/strategies/limit-offset.pager-strategy.ts
  • packages/query-graphql/src/types/query/paging/cursor-paging.type.ts
  • packages/query-graphql/src/types/query/paging/interfaces.ts

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.

1 participant