Skip to content

TML-2596: add OrderByItem.reverse() for backward cursor pagination#671

Open
tensordreams wants to merge 3 commits into
mainfrom
tml-2596-sql-orm-expose-a-way-to-reverse-an-orderbyitem-backward
Open

TML-2596: add OrderByItem.reverse() for backward cursor pagination#671
tensordreams wants to merge 3 commits into
mainfrom
tml-2596-sql-orm-expose-a-way-to-reverse-an-orderbyitem-backward

Conversation

@tensordreams
Copy link
Copy Markdown
Contributor

@tensordreams tensordreams commented Jun 1, 2026

Linked issue

Refs TML-2596

At a glance

// Forward page (newest first).
const firstPage = await db.Post
  .orderBy((post) => post.createdAt.desc())
  .take(10)
  .all();

// Backward page: same sort, flipped — no value-level OrderByItem import needed.
const lastPage = await db.Post
  .orderBy((post) => post.createdAt.desc().reverse())
  .take(10)
  .all();

Before this PR, OrderByItem exposed no inversion path. An integration that owns pagination (Relay backward cursors, REST list endpoints) could only new OrderByItem(item.expr, item.dir === 'asc' ? 'desc' : 'asc') against the internal @prisma-next/sql-relational-core/ast package — or abandon the chain API entirely, which is what the Pothos and Drizzle plugins ended up doing.

Decision

Add an instance method OrderByItem.prototype.reverse() that returns a new frozen item with the direction flipped (ascdesc) and expr unchanged. Direction is 'asc' | 'desc' only (no NULLS FIRST/LAST yet), so a single-axis flip is unambiguous.

That's the whole API. Because .reverse() is an instance method, an integration flips a sort by calling it on the item the orderBy selector already returns (selector(post).reverse()) — it never has to name or import OrderByItem at the value level. So this PR deliberately does not re-export OrderByItem from @prisma-next/sql-orm-client (see Alternatives).

How it fits together

  1. Add reverse() to the OrderByItem class in relational-core/src/ast/types.ts. It reuses the frozen-class constructor, so the returned item carries the same immutability guarantees as .asc() / .desc().
  2. Document the backward-pagination pattern in the ORM README, reversing inside the orderBy selector (the form the chain API accepts) — no AST import in the example.

Behavior changes & evidence

  • OrderByItem.reverse() returns a new frozen item with the opposite direction and the same expr referencetypes.ts. Covered by the round-trip + frozen-identity runtime cases in order.test.ts, with the public type shape (reverse(): OrderByItem, readable dir/expr) locked by order.types.test-d.ts.

Reviewer notes

  • This started out re-exporting OrderByItem (per the ticket's original proposal). On review we dropped that: it would single out two AST types (OrderByItem, Direction) from the whole alphabet that otherwise lives only in sql-relational-core/ast, and .reverse() as an instance method removes the need for a value-level import anyway. The type test therefore lives in relational-core next to the runtime test, not in sql-orm-client.
  • This is public API for extension authors, so it's covered by both a runtime unit test and a type-level test.

Compatibility / migration / risk

Purely additive — one new method. No existing signatures change; no migration needed.

Verification

  • pnpm --filter @prisma-next/sql-relational-core test — 289 passed
  • pnpm --filter @prisma-next/sql-relational-core typecheck — clean (includes the new .test-d.ts)
  • pnpm --filter @prisma-next/sql-orm-client test — 505 passed, 3 skipped; type tests: no errors

Alternatives considered

  • Re-exporting OrderByItem / Direction from @prisma-next/sql-orm-client (the ticket's original step 2). Rejected: it exposes a two-type slice of the AST alphabet from the ORM surface while every other AST type stays in sql-relational-core/ast — an inconsistent boundary. .reverse() makes the re-export unnecessary for the runtime path; consumers that want the type for annotations can still name it from sql-relational-core/ast.
  • A static OrderByItem.invert(item) helper instead of an instance method. The instance method composes naturally with the chain API and matches the existing .asc() / .desc() ergonomics, so it's the more discoverable surface.
  • Also accepting a user-supplied option-shape orderBy: [...] (what Hayes built in his plugin). A separate design question tracked as a follow-up; this PR only fills the immediate gap in the chain API.

Checklist

  • All commits are signed off (git commit -s) per the DCO.
  • I read CONTRIBUTING.md and the change is scoped to one logical concern.
  • Tests are updated.
  • The PR title is in TML-NNNN: <sentence-case title> form.
  • The Skill update section is addressed (n/a — see below).

Skill update

n/a — this adds a public method covered by README and tests; no CLI/config/error-code surface changed that the agent skills track.

Summary by CodeRabbit

  • New Features

    • Added reverse() method to invert sort direction (asc ↔ desc) for sort order definitions.
  • Documentation

    • Updated pagination guide with examples demonstrating sort direction reversal for cursor-based navigation scenarios.
  • Tests

    • Added test coverage validating sort reversal behavior and type correctness.

…ation

Add an OrderByItem.reverse() instance method that returns a new frozen
item with the sort direction flipped and expr unchanged, and re-export
OrderByItem from the public ORM client surface so integrations that own
pagination (Relay backward cursors, REST list endpoints) can invert a
user sort order without reaching into @prisma-next/sql-relational-core/ast.

TML-2596

Signed-off-by: Alexey Orlenko <orlenko@prisma.io>
…-export Direction

Address code review: the README backward-pagination snippet now reverses
inside the orderBy selector (the form the public chain API actually
accepts) instead of mapping a bare OrderByItem[] that no public method
consumes. Also re-export Direction so consumers can name the type that
.dir / .reverse() flips between, without reaching into sql-relational-core.

TML-2596

Signed-off-by: Alexey Orlenko <orlenko@prisma.io>
@tensordreams tensordreams requested a review from a team as a code owner June 1, 2026 15:20
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 718c8dc8-4bde-482e-a5ad-9f6a9716e8e5

📥 Commits

Reviewing files that changed from the base of the PR and between e1b6dc2 and 93663e6.

📒 Files selected for processing (2)
  • packages/2-sql/4-lanes/relational-core/test/ast/order.types.test-d.ts
  • packages/3-extensions/sql-orm-client/README.md
✅ Files skipped from review due to trivial changes (1)
  • packages/3-extensions/sql-orm-client/README.md

📝 Walkthrough

Walkthrough

This PR adds a reverse() method to OrderByItem that flips sort direction while preserving the expression, along with comprehensive runtime and type-level tests, plus pagination documentation showing cursor pagination use cases.

Changes

OrderByItem.reverse() Implementation and Documentation

Layer / File(s) Summary
Core reverse() implementation and runtime tests
packages/2-sql/4-lanes/relational-core/src/ast/types.ts, packages/2-sql/4-lanes/relational-core/test/ast/order.test.ts
OrderByItem.reverse() flips dir between asc and desc while preserving expr, returning a distinct frozen instance without mutating the original. Runtime tests verify direction flipping, immutability, reference identity preservation, and involution (double-reverse round-trips).
Type validation and pagination documentation
packages/2-sql/4-lanes/relational-core/test/ast/order.types.test-d.ts, packages/3-extensions/sql-orm-client/README.md
Type-level tests confirm reverse() returns OrderByItem and validate property types. README documents pagination patterns using reverse() to invert sort direction for forward and backward cursor pagination.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 A rabbit hops through sorted lists today,
Reversing orders in a clever way—
Direction flips, expression stays the same,
Frozen instances without a frame of blame!
Pagination dances forward, backward too,
Thanks to reverse() working clean and true. 🌟

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main addition: a new reverse() method on OrderByItem for backward cursor pagination support.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tml-2596-sql-orm-expose-a-way-to-reverse-an-orderbyitem-backward

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.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 1, 2026

size-limit report 📦

Path Size
postgres / no-emit 135.96 KB (+0.02% 🔺)
postgres / emit 125.61 KB (+0.02% 🔺)
mongo / no-emit 75.69 KB (0%)
mongo / emit 70.68 KB (0%)

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Jun 1, 2026

Open in StackBlitz

@prisma-next/extension-author-tools

npm i https://pkg.pr.new/@prisma-next/extension-author-tools@671

@prisma-next/mongo-runtime

npm i https://pkg.pr.new/@prisma-next/mongo-runtime@671

@prisma-next/family-mongo

npm i https://pkg.pr.new/@prisma-next/family-mongo@671

@prisma-next/sql-runtime

npm i https://pkg.pr.new/@prisma-next/sql-runtime@671

@prisma-next/family-sql

npm i https://pkg.pr.new/@prisma-next/family-sql@671

@prisma-next/extension-arktype-json

npm i https://pkg.pr.new/@prisma-next/extension-arktype-json@671

@prisma-next/middleware-cache

npm i https://pkg.pr.new/@prisma-next/middleware-cache@671

@prisma-next/mongo

npm i https://pkg.pr.new/@prisma-next/mongo@671

@prisma-next/extension-paradedb

npm i https://pkg.pr.new/@prisma-next/extension-paradedb@671

@prisma-next/extension-pgvector

npm i https://pkg.pr.new/@prisma-next/extension-pgvector@671

@prisma-next/extension-postgis

npm i https://pkg.pr.new/@prisma-next/extension-postgis@671

@prisma-next/postgres

npm i https://pkg.pr.new/@prisma-next/postgres@671

@prisma-next/sql-orm-client

npm i https://pkg.pr.new/@prisma-next/sql-orm-client@671

@prisma-next/sqlite

npm i https://pkg.pr.new/@prisma-next/sqlite@671

@prisma-next/target-mongo

npm i https://pkg.pr.new/@prisma-next/target-mongo@671

@prisma-next/adapter-mongo

npm i https://pkg.pr.new/@prisma-next/adapter-mongo@671

@prisma-next/driver-mongo

npm i https://pkg.pr.new/@prisma-next/driver-mongo@671

@prisma-next/contract

npm i https://pkg.pr.new/@prisma-next/contract@671

@prisma-next/utils

npm i https://pkg.pr.new/@prisma-next/utils@671

@prisma-next/config

npm i https://pkg.pr.new/@prisma-next/config@671

@prisma-next/errors

npm i https://pkg.pr.new/@prisma-next/errors@671

@prisma-next/framework-components

npm i https://pkg.pr.new/@prisma-next/framework-components@671

@prisma-next/operations

npm i https://pkg.pr.new/@prisma-next/operations@671

@prisma-next/ts-render

npm i https://pkg.pr.new/@prisma-next/ts-render@671

@prisma-next/contract-authoring

npm i https://pkg.pr.new/@prisma-next/contract-authoring@671

@prisma-next/ids

npm i https://pkg.pr.new/@prisma-next/ids@671

@prisma-next/psl-parser

npm i https://pkg.pr.new/@prisma-next/psl-parser@671

@prisma-next/psl-printer

npm i https://pkg.pr.new/@prisma-next/psl-printer@671

@prisma-next/cli

npm i https://pkg.pr.new/@prisma-next/cli@671

@prisma-next/cli-telemetry

npm i https://pkg.pr.new/@prisma-next/cli-telemetry@671

@prisma-next/emitter

npm i https://pkg.pr.new/@prisma-next/emitter@671

@prisma-next/migration-tools

npm i https://pkg.pr.new/@prisma-next/migration-tools@671

prisma-next

npm i https://pkg.pr.new/prisma-next@671

@prisma-next/vite-plugin-contract-emit

npm i https://pkg.pr.new/@prisma-next/vite-plugin-contract-emit@671

@prisma-next/mongo-codec

npm i https://pkg.pr.new/@prisma-next/mongo-codec@671

@prisma-next/mongo-contract

npm i https://pkg.pr.new/@prisma-next/mongo-contract@671

@prisma-next/mongo-value

npm i https://pkg.pr.new/@prisma-next/mongo-value@671

@prisma-next/mongo-contract-psl

npm i https://pkg.pr.new/@prisma-next/mongo-contract-psl@671

@prisma-next/mongo-contract-ts

npm i https://pkg.pr.new/@prisma-next/mongo-contract-ts@671

@prisma-next/mongo-emitter

npm i https://pkg.pr.new/@prisma-next/mongo-emitter@671

@prisma-next/mongo-schema-ir

npm i https://pkg.pr.new/@prisma-next/mongo-schema-ir@671

@prisma-next/mongo-query-ast

npm i https://pkg.pr.new/@prisma-next/mongo-query-ast@671

@prisma-next/mongo-orm

npm i https://pkg.pr.new/@prisma-next/mongo-orm@671

@prisma-next/mongo-query-builder

npm i https://pkg.pr.new/@prisma-next/mongo-query-builder@671

@prisma-next/mongo-lowering

npm i https://pkg.pr.new/@prisma-next/mongo-lowering@671

@prisma-next/mongo-wire

npm i https://pkg.pr.new/@prisma-next/mongo-wire@671

@prisma-next/sql-contract

npm i https://pkg.pr.new/@prisma-next/sql-contract@671

@prisma-next/sql-errors

npm i https://pkg.pr.new/@prisma-next/sql-errors@671

@prisma-next/sql-operations

npm i https://pkg.pr.new/@prisma-next/sql-operations@671

@prisma-next/sql-schema-ir

npm i https://pkg.pr.new/@prisma-next/sql-schema-ir@671

@prisma-next/sql-contract-psl

npm i https://pkg.pr.new/@prisma-next/sql-contract-psl@671

@prisma-next/sql-contract-ts

npm i https://pkg.pr.new/@prisma-next/sql-contract-ts@671

@prisma-next/sql-contract-emitter

npm i https://pkg.pr.new/@prisma-next/sql-contract-emitter@671

@prisma-next/sql-lane-query-builder

npm i https://pkg.pr.new/@prisma-next/sql-lane-query-builder@671

@prisma-next/sql-relational-core

npm i https://pkg.pr.new/@prisma-next/sql-relational-core@671

@prisma-next/sql-builder

npm i https://pkg.pr.new/@prisma-next/sql-builder@671

@prisma-next/target-postgres

npm i https://pkg.pr.new/@prisma-next/target-postgres@671

@prisma-next/target-sqlite

npm i https://pkg.pr.new/@prisma-next/target-sqlite@671

@prisma-next/adapter-postgres

npm i https://pkg.pr.new/@prisma-next/adapter-postgres@671

@prisma-next/adapter-sqlite

npm i https://pkg.pr.new/@prisma-next/adapter-sqlite@671

@prisma-next/driver-postgres

npm i https://pkg.pr.new/@prisma-next/driver-postgres@671

@prisma-next/driver-sqlite

npm i https://pkg.pr.new/@prisma-next/driver-sqlite@671

commit: 93663e6

…as the API

Re-exporting OrderByItem (and Direction) from the ORM client singled out
two AST types out of the whole alphabet that otherwise lives only in
@prisma-next/sql-relational-core/ast. .reverse() being an instance method
is enough: integrations call it on the item the orderBy selector already
returns, so no value-level import of OrderByItem is needed.

Move the public-shape type test to relational-core (OrderByItem.reverse()
returns OrderByItem; readable dir/expr) next to the runtime round-trip
test; drop the now-redundant sql-orm-client tests; reword the README
pagination note to use .reverse() inline.

TML-2596

Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
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