Skip to content

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

Merged
aqrln merged 6 commits into
mainfrom
tml-2596-sql-orm-expose-a-way-to-reverse-an-orderbyitem-backward
Jun 2, 2026
Merged

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

Conversation

@tensordreams

@tensordreams tensordreams commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

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

  • Documentation
    • Added upgrade instructions metadata for version transition support.

@tensordreams tensordreams requested a review from a team as a code owner June 1, 2026 15:20
@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@tensordreams, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 24 minutes and 36 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 7f2d5b35-02c2-4ef4-9bf5-6b95f50df08d

📥 Commits

Reviewing files that changed from the base of the PR and between a293064 and 77cf1ee.

📒 Files selected for processing (4)
  • packages/2-sql/4-lanes/relational-core/src/ast/types.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/order.test.ts
  • packages/3-extensions/sql-orm-client/README.md
  • skills/extension-author/prisma-next-extension-upgrade/upgrades/0.12-to-0.13/instructions.md
📝 Walkthrough

Walkthrough

A single instructions file is updated to include upgrade metadata marking the version transition from 0.12 to 0.13, with from, to, and an empty changes array.

Changes

Upgrade Metadata for 0.12 to 0.13 Transition

Layer / File(s) Summary
Upgrade metadata for 0.12 to 0.13 transition
skills/extension-author/prisma-next-extension-upgrade/upgrades/0.12-to-0.13/instructions.md
Metadata fields from, to, and changes are added to the instructions file, specifying the upgrade path from version 0.12 to 0.13 with no changes listed.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~2 minutes

Poem

A rabbit hops through version chains,
From 0.12 to 0.13 plains,
Metadata blooms where upgrades grow,
A simple step, a gentle flow! 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title mentions adding OrderByItem.reverse() for backward cursor pagination, but the actual changeset only modifies an upgrade instructions file with metadata for a version transition from 0.12 to 0.13, which is unrelated to the stated objective. Verify the correct files are included in this PR. The title describes AST modifications and test updates in relational-core packages, but only an instructions metadata file appears in the changeset. Either update the title to match the actual changes or ensure all intended files are included.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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 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

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown

size-limit report 📦

Path Size
postgres / no-emit 136.06 KB (+0.03% 🔺)
postgres / emit 108.35 KB (+0.03% 🔺)
mongo / no-emit 75.76 KB (0%)
mongo / emit 70.75 KB (0%)
cf-worker / no-emit 164.74 KB (+0.02% 🔺)
cf-worker / emit 133.79 KB (+0.02% 🔺)

@pkg-pr-new

pkg-pr-new Bot commented Jun 1, 2026

Copy link
Copy Markdown

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: a6ed2f2

@tensordreams tensordreams force-pushed the tml-2596-sql-orm-expose-a-way-to-reverse-an-orderbyitem-backward branch from 93663e6 to b02c6f7 Compare June 2, 2026 11:46
Comment thread packages/2-sql/4-lanes/relational-core/test/ast/order.types.test-d.ts Outdated
Comment thread packages/3-extensions/sql-orm-client/README.md Outdated

@aqrln aqrln left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Lint failure on CI:

check-upgrade-coverage: 1 violation(s) (0.12 → 0.12)
  [coverage] diff in packages/3-extensions/ requires an upgrade-instructions directory at
              skills/extension-author/prisma-next-extension-upgrade/upgrades/0.12-to-0.13/instructions.md
              sample paths from the diff:
                packages/3-extensions/sql-orm-client/README.md

See the in-repo `record-upgrade-instructions` skill for the authoring workflow:
  .agents/skills/record-upgrade-instructions/SKILL.md

@tensordreams tensordreams force-pushed the tml-2596-sql-orm-expose-a-way-to-reverse-an-orderbyitem-backward branch 2 times, most recently from a293064 to 77cf1ee Compare June 2, 2026 12:17
@aqrln aqrln enabled auto-merge (rebase) June 2, 2026 12:19
…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's AI Agent <robot@aqrln.net>
…-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's AI Agent <robot@aqrln.net>
…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>
The type assertions just restated the class definition (reverse(): OrderByItem,
dir: Direction, expr: AnyExpression) without exercising any type-level logic.
The runtime round-trip + frozen-identity coverage in order.test.ts is the
meaningful test. Per PR review.

TML-2596

Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
.reverse() is for extension/integration authors, not ORM-client users, so the
backward-cursor example and its prose do not belong in this README. Keep the
forward orderBy example only. Per PR review.

TML-2596

Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
This PR's only packages/3-extensions/ diff is a doc-only trim of the
sql-orm-client README pagination section — no consumer-side action. Record
the in-flight transition with changes: [] to satisfy the upgrade-coverage
gate, per the record-upgrade-instructions skill.

TML-2596

Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
@tensordreams tensordreams force-pushed the tml-2596-sql-orm-expose-a-way-to-reverse-an-orderbyitem-backward branch from 77cf1ee to a6ed2f2 Compare June 2, 2026 12:34
@aqrln aqrln merged commit 81f1517 into main Jun 2, 2026
21 checks passed
@aqrln aqrln deleted the tml-2596-sql-orm-expose-a-way-to-reverse-an-orderbyitem-backward branch June 2, 2026 12:43
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