Skip to content

Conversation

@CarlosGamero
Copy link
Collaborator

@CarlosGamero CarlosGamero commented Jan 13, 2026

Changes

Adds support for numeric cursors in pagination utilities and, since this is a major release, removes deprecated functionality.

Checklist

  • Apply one of following labels; major, minor, patch or skip-release
  • I've updated the documentation, or no changes were necessary
  • I've updated the tests, or no changes were necessary

Summary by CodeRabbit

  • New Features

    • Pagination now supports encoding numeric values as cursors
    • Added optional cursor key selection for customizable pagination responses
  • Refactor

    • Renamed pagination schemas to reflect encoded-cursor behavior
    • Updated pagination utilities and cursor encoding/decoding to handle numbers
    • Made pagination metadata hasMore required
  • Tests

    • Expanded tests to cover encoded cursors (objects and numbers) and updated expectations
  • Chores

    • Removed an unused runtime dependency

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 13, 2026

Note

.coderabbit.yaml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized key(s) in object: 'tools'
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Walkthrough

Cursor pagination was refactored to support numeric cursors and renamed from multiCursor* to encodedCursor*. Cursor codec accepts numbers; base64 helpers now require an explicit mode. Pagination utilities reorder parameters, accept optional cursorKeys, and remove legacy getPaginatedEntries. A runtime dependency was removed.

Changes

Cohort / File(s) Summary
Pagination Schema Renaming and Type Widening
packages/app/api-common/src/apiSchemas.ts, packages/app/api-common/src/apiSchemas.spec.ts
Renamed multiCursorMandatoryPaginationSchemaencodedCursorMandatoryPaginationSchema and multiCursorOptionalPaginationSchemaencodedCursorOptionalPaginationSchema. CursorType generic now allows number. zMeta.hasMore is required. Updated tests and snapshots for GUIDs and encoded number cursors.
Cursor Codec Expansion
packages/app/api-common/src/cursorCodec.ts, packages/app/api-common/src/cursorCodec.spec.ts
encodeCursor() now accepts `Record<string, unknown>
Pagination Utilities Refactoring
packages/app/api-common/src/paginationUtils.ts, packages/app/api-common/src/paginationUtils.spec.ts
Reordered getMetaForNextPage to (currentPageData, pageLimit, cursorKeys?). createPaginatedResponse() gains optional cursorKeys parameter and overloads; cursors can be derived from single or multiple keys and numeric values are encoded. Removed getPaginatedEntries export and related tests.
Dependency Management
packages/app/api-common/package.json
Removed runtime dependency @lokalise/universal-ts-utils from dependencies.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: api-common supporting numeric cursors' is concise and clearly describes the main change—adding numeric cursor support to pagination utilities.
Description check ✅ Passed The description includes the required 'Changes' section explaining the feature and all three checklist items are marked complete, meeting the template structure.

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

✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: Repository: lokalise/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8979bc9 and 23c9889.

📒 Files selected for processing (1)
  • packages/app/api-common/src/apiSchemas.spec.ts
🧰 Additional context used
🧬 Code graph analysis (1)
packages/app/api-common/src/apiSchemas.spec.ts (3)
packages/app/api-common/src/apiSchemas.ts (2)
  • encodedCursorMandatoryPaginationSchema (36-47)
  • encodedCursorOptionalPaginationSchema (48-52)
packages/app/api-common/src/index.ts (1)
  • encodeCursor (2-2)
packages/app/api-common/src/cursorCodec.ts (1)
  • encodeCursor (44-45)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build (22.x)
  • GitHub Check: build (24.x)
🔇 Additional comments (5)
packages/app/api-common/src/apiSchemas.spec.ts (5)

1-17: LGTM!

The imports and test setup correctly reflect the API surface rename from multiCursor* to encodedCursor*, and the schema update from z.uuid() to z.guid() is consistent with the expected error message changes later in the file.


37-48: LGTM!

Good coverage for the new numeric cursor support. The test properly validates the round-trip encoding and decoding of numeric cursors.


114-140: LGTM!

The error message expectations are correctly updated to reflect the GUID format validation. The inline snapshot accurately captures the expected error structure.


142-163: LGTM!

Excellent negative test case for numeric cursor validation. This properly ensures that cursor schema constraints (like .positive()) are enforced during parsing.


166-181: LGTM!

The schema rename is consistent with the API surface changes, and the test correctly validates that limit remains optional in the encodedCursorOptionalPaginationSchema.


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

@CarlosGamero CarlosGamero requested a review from drdaemos January 13, 2026 15:51
@CarlosGamero CarlosGamero changed the title feat/: api-common supporting numberic cursors feat: api-common supporting numberic cursors Jan 13, 2026
@CarlosGamero CarlosGamero marked this pull request as ready for review January 13, 2026 15:51
@CarlosGamero CarlosGamero requested review from a team, dariacm and kibertoad as code owners January 13, 2026 15:51
Comment on lines -43 to -45
},
"dependencies": {
"@lokalise/universal-ts-utils": "^4.2.3"
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

No longer needed

Comment on lines -30 to +32
code: z.ZodIssueCode.custom,
params: { message: result.error.message },
code: 'custom',
params: { message: result.error?.message },
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

z.ZodIssueCode is deprecated

export const zMeta = z.object({
count: z.number(),
cursor: z.string().optional().describe('Pagination cursor, a last item id from this result set'),
hasMore: z.boolean().optional().describe('Whether there are more items to fetch'),
Copy link
Collaborator Author

@CarlosGamero CarlosGamero Jan 13, 2026

Choose a reason for hiding this comment

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

Only a couple of endpoints are using meta without hasMore, adding it there should be pretty simple.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @packages/app/api-common/src/cursorCodec.ts:
- Around line 66-67: The type guard isObjectOrNumber incorrectly allows null
because typeof null === 'object'; update its implementation to explicitly
exclude null (e.g., check value !== null) so the predicate only returns true for
non-null objects or numbers; modify the function isObjectOrNumber to test value
!== null && (typeof value === 'object' || typeof value === 'number') so the type
narrowing is correct wherever the guard is used.

In @packages/app/api-common/src/paginationUtils.ts:
- Around line 42-48: The current branch handling a single cursor key unsafely
casts the extracted value to number; update the logic in paginationUtils.ts to
validate the runtime type of value (from lastElement[cursorKeys[0]]) before
encoding: if typeof value === 'string' use it as-is, else if typeof value ===
'number' call encodeCursor(value), otherwise fall back to the multi-key behavior
(encodeCursor(pick(lastElement, cursorKeys))) or throw/handle a clear error for
unsupported types; ensure you use the existing encodeCursor, cursorKeys,
lastElement and pick symbols and remove the unsafe cast.
🧹 Nitpick comments (2)
packages/app/api-common/src/cursorCodec.spec.ts (1)

23-26: Consider adding edge case tests for numeric cursors.

The test covers the basic case (42), but numeric cursors may include edge cases like 0, negative numbers, and floating-point values. Consider adding tests for these scenarios to ensure robustness.

💡 Suggested additional test cases
     it('encode and decode works for numbers', () => {
       const testValue = 42
       expect(decodeCursor(encodeCursor(testValue))).toEqual({ result: testValue })
     })
+
+    it('encode and decode works for zero', () => {
+      expect(decodeCursor(encodeCursor(0))).toEqual({ result: 0 })
+    })
+
+    it('encode and decode works for negative numbers', () => {
+      expect(decodeCursor(encodeCursor(-100))).toEqual({ result: -100 })
+    })
+
+    it('encode and decode works for floating-point numbers', () => {
+      expect(decodeCursor(encodeCursor(3.14))).toEqual({ result: 3.14 })
+    })
packages/app/api-common/src/paginationUtils.ts (1)

4-20: Consider using the shared pick utility.

A pick function exists in packages/app/universal-ts-utils/src/public/object/pick.ts. Unless there's a specific reason to avoid the dependency (e.g., bundle size), consider reusing it to reduce code duplication.

📜 Review details

Configuration used: Repository: lokalise/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1d5cd41 and 8979bc9.

📒 Files selected for processing (7)
  • packages/app/api-common/package.json
  • packages/app/api-common/src/apiSchemas.spec.ts
  • packages/app/api-common/src/apiSchemas.ts
  • packages/app/api-common/src/cursorCodec.spec.ts
  • packages/app/api-common/src/cursorCodec.ts
  • packages/app/api-common/src/paginationUtils.spec.ts
  • packages/app/api-common/src/paginationUtils.ts
💤 Files with no reviewable changes (1)
  • packages/app/api-common/package.json
🧰 Additional context used
🧬 Code graph analysis (4)
packages/app/api-common/src/cursorCodec.spec.ts (2)
packages/app/api-common/src/cursorCodec.ts (2)
  • decodeCursor (51-64)
  • encodeCursor (44-45)
packages/app/api-common/src/index.ts (2)
  • decodeCursor (2-2)
  • encodeCursor (2-2)
packages/app/api-common/src/apiSchemas.spec.ts (2)
packages/app/api-common/src/apiSchemas.ts (2)
  • encodedCursorMandatoryPaginationSchema (36-47)
  • encodedCursorOptionalPaginationSchema (48-52)
packages/app/api-common/src/cursorCodec.ts (1)
  • encodeCursor (44-45)
packages/app/api-common/src/paginationUtils.ts (3)
packages/app/api-common/src/apiSchemas.ts (2)
  • PaginationMeta (60-60)
  • PaginatedResponse (67-70)
packages/app/api-common/src/cursorCodec.ts (1)
  • encodeCursor (44-45)
packages/app/universal-ts-utils/src/public/object/pick.ts (1)
  • pick (42-58)
packages/app/api-common/src/apiSchemas.ts (2)
packages/app/api-common/src/cursorCodec.ts (1)
  • decodeCursor (51-64)
packages/app/api-common/src/index.ts (1)
  • decodeCursor (2-2)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build (22.x)
  • GitHub Check: build (24.x)
🔇 Additional comments (13)
packages/app/api-common/src/cursorCodec.spec.ts (1)

12-21: LGTM!

Test description appropriately updated to clarify the test is for object cursors.

packages/app/api-common/src/apiSchemas.spec.ts (3)

37-48: LGTM!

Good coverage for numeric cursor decoding. The test validates both the encoding and proper type transformation through the schema.


142-163: LGTM!

Excellent test for validating schema constraints on numeric cursors. This ensures that encoded numbers still go through proper Zod validation.


4-5: LGTM!

Test imports and references correctly updated to reflect the renamed encodedCursor* schema APIs.

Also applies to: 11-11, 19-20

packages/app/api-common/src/paginationUtils.spec.ts (2)

104-119: LGTM!

This test properly validates that single numeric cursor keys are encoded using encodeCursor, which aligns with the implementation in paginationUtils.ts (lines 44-45).


46-51: LGTM!

Good defensive test ensuring empty cursorKeys arrays throw an appropriate error.

packages/app/api-common/src/cursorCodec.ts (2)

44-45: LGTM!

Clean extension of encodeCursor to support numeric values while maintaining backward compatibility with objects.


51-64: LGTM!

The decodeCursor function correctly handles both object and number results with proper error handling.

packages/app/api-common/src/apiSchemas.ts (3)

57-57: Breaking change: hasMore is now required.

This is a breaking change making hasMore mandatory in the pagination meta. Ensure this is documented in the changelog for the major release.


22-34: LGTM!

The decodeCursorHook correctly handles decoding with proper error propagation through Zod's custom error mechanism.


36-47: Reconsider the generic constraint syntax for Zod v4.

In Zod v4, ZodSchema has two generic parameters: Output and Input. The constraint CursorType extends z.ZodSchema<unknown, Record<string, unknown> | number | undefined> sets the Output to unknown and constrains the Input type. This defeats type inference for the final output of the cursor field, which will always be typed as unknown regardless of what cursorType produces. The constraint appears inverted—you likely want to constrain the Output type instead to ensure proper type safety downstream.

packages/app/api-common/src/paginationUtils.ts (2)

57-72: LGTM!

Excellent JSDoc documentation clearly explaining the cursor formation behavior for different scenarios (no keys, single string key, single number key, multiple keys).


113-131: LGTM!

getPaginatedEntriesByHasMore correctly leverages the now-required hasMore field for pagination control.

@CarlosGamero CarlosGamero changed the title feat: api-common supporting numberic cursors feat: api-common supporting numeric cursors Jan 13, 2026
@CarlosGamero CarlosGamero requested a review from kjamrog January 14, 2026 09:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants