Sorting
$sort orders rows by fields of the entity, JSON paths inside them, or a joined relation’s fields, in the order you write the keys:
const posts = await pool.findMany(Post, { $select: { title: true, publishedAt: true }, $sort: { publishedAt: 'desc', title: 'asc' },});ORDER BY "publishedAt" DESC, "title"A direction is 'asc' or 1, 'desc' or -1, or a null placement.
Where nulls land
Section titled “Where nulls land”Engines disagree on where nulls go, and a plain direction keeps each engine’s answer:
| Engine | Plain asc puts nulls |
|---|---|
| PostgreSQL, Neon, PGlite, Bun SQL on Postgres | last |
| CockroachDB, SQLite, libSQL, Turso, D1, MySQL, MariaDB, SQL Server | first |
| MongoDB | first (a missing field counts as null) |
Name a placement and the order is the same everywhere:
const posts = await pool.findMany(Post, { $sort: { publishedAt: 'descNullsLast' },});The placements are ascNullsFirst, ascNullsLast, descNullsFirst and descNullsLast. They work wherever a direction does, including a populated relation’s $sort and an aggregate’s. Engines without NULLS LAST get an equivalent:
ORDER BY "publishedAt" DESC NULLS LASTORDER BY `publishedAt` IS NULL, `publishedAt` DESCORDER BY CASE WHEN "publishedAt" IS NULL THEN 1 ELSE 0 END, "publishedAt" DESCOn MongoDB, UQL sorts by a null flag first and strips it from the results.
In generic code
Section titled “In generic code”Where the entity is a type parameter, a key held in one cannot build the map: TypeScript widens { [key]: 'asc' } to an index signature, which no entity’s $sort accepts. Take the member as a selector, read its name with keyOf, and build the sort with sortOf, which takes [key, direction] pairs in the order they sort:
import { type FieldKeyOf, type KeyMap, keyOf, sortOf, type Type,} from 'uql-orm';
function oldestFirst<E extends object, K extends FieldKeyOf<E, Date>>( entity: Type<E>, since: (keys: KeyMap<E>) => K,) { const key = keyOf(since); return pool.findMany(entity, { $sort: sortOf<E>([[key, 'asc']]), $limit: 20, });}
const oldest = await oldestFirst(Post, (post) => post.publishedAt);The selector follows a rename of publishedAt, where a string key would be left behind, and FieldKeyOf<E, Date> refuses a member that is not a date.
Next steps
Section titled “Next steps”- Relations: sorting by a joined field, and a populated collection’s own
$sort. - Counting: ordering rows by how many related rows they have.
- Cursor pagination: paging through a sorted result.
- Full-text search and semantic search: ranking by relevance or vector distance.