From b08b6da0d7fc5b37ab6c68970a69864acb30e45e Mon Sep 17 00:00:00 2001 From: Artem Zakharchenko Date: Mon, 20 Apr 2026 11:25:14 +0200 Subject: [PATCH 1/4] fix: support array of `orderBy` options --- README.md | 10 +++++++++- src/sort.ts | 20 +++++++++++++++----- tests/sort.test.ts | 34 ++++++++++++++++++++++++++++++---- 3 files changed, 54 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 1283acc..1285293 100644 --- a/README.md +++ b/README.md @@ -200,7 +200,7 @@ users.findMany((q) => q.where({ name: (name) => name.startsWith('J') }), { }) ``` -You can sort by multiple criteria by providing them in the `orderBy` object: +You can sort by multiple keys by listing them in the `orderBy` object: ```ts users.updateMany((q) => q.where({ name: (name) => name.startsWith('J') }), { @@ -214,6 +214,14 @@ users.updateMany((q) => q.where({ name: (name) => name.startsWith('J') }), { }) ``` +You can sort by an ordered list of criteria by passing an array to `orderBy`. Each entry is applied in sequence: the first entry determines the primary sort, and each subsequent entry breaks ties among records that compare equal under the preceding criteria. + +```ts +users.findMany(undefined, { + orderBy: [{ age: 'asc' }, { name: 'desc' }] +}) +``` + ## Relations You can define relations by calling the `.defineRelations()` method on the collection. diff --git a/src/sort.ts b/src/sort.ts index d3ae1fc..c12eef7 100644 --- a/src/sort.ts +++ b/src/sort.ts @@ -1,6 +1,6 @@ import type { StandardSchemaV1 } from '@standard-schema/spec' import { get } from 'es-toolkit/compat' -import { toDeepEntries } from '#/src/utils.js' +import { toDeepEntries, type PropertyPath } from '#/src/utils.js' export type SortDirection = 'asc' | 'desc' @@ -8,15 +8,19 @@ export interface SortOptions { orderBy?: OrderBy } -type OrderBy< +type OrderBy = + | OrderByCriteria + | Array> + +type OrderByCriteria< Schema extends StandardSchemaV1, T = StandardSchemaV1.InferOutput, > = NonNullable extends Array - ? OrderBy + ? OrderByCriteria : NonNullable extends Record ? { - [K in keyof T]?: OrderBy + [K in keyof T]?: OrderByCriteria } : SortDirection @@ -28,7 +32,13 @@ export function sortResults( return } - const criteria = toDeepEntries(sortOptions.orderBy as any) + const criteria: Array<[PropertyPath, SortDirection]> = Array.isArray( + sortOptions.orderBy, + ) + ? sortOptions.orderBy.flatMap((entry) => { + return toDeepEntries(entry as any) + }) + : toDeepEntries(sortOptions.orderBy as any) data.sort((left, right) => { for (const [path, sortDirection] of criteria) { diff --git a/tests/sort.test.ts b/tests/sort.test.ts index ede2ed6..ed1e014 100644 --- a/tests/sort.test.ts +++ b/tests/sort.test.ts @@ -6,7 +6,7 @@ const schema = z.object({ name: z.string(), }) -it('sorts the find results by a single key (asc)', async () => { +it('sorts the results by a single key (asc)', async () => { const users = new Collection({ schema }) await users.create({ id: 1, name: 'John' }) @@ -33,7 +33,7 @@ it('sorts the find results by a single key (asc)', async () => { ]) }) -it('sorts the find results by a single key (desc)', async () => { +it('sorts the results by a single key (desc)', async () => { const users = new Collection({ schema }) await users.create({ id: 1, name: 'John' }) @@ -60,7 +60,7 @@ it('sorts the find results by a single key (desc)', async () => { ]) }) -it('sorts the find results by multiple keys (mixed)', async () => { +it('sorts the results by multiple keys (mixed)', async () => { const users = new Collection({ schema }) await users.create({ id: 1, name: 'John' }) await users.create({ id: 2, name: 'Alice' }) @@ -89,7 +89,7 @@ it('sorts the find results by multiple keys (mixed)', async () => { ]) }) -it('sorts the find results by a nested key', async () => { +it('sorts the results by a nested key', async () => { const users = new Collection({ schema: schema.extend({ address: z.object({ @@ -121,3 +121,29 @@ it('sorts the find results by a nested key', async () => { { id: 1, name: 'John', address: { street: 'C' } }, ]) }) + +it('sorts the results by a list of sort criteria', async () => { + const schema = z.object({ + id: z.number(), + name: z.string(), + age: z.number(), + }) + + const users = new Collection({ schema }) + + await users.create({ id: 1, name: 'John', age: 32 }) + await users.create({ id: 2, name: 'Alice', age: 24 }) + await users.create({ id: 3, name: 'Bob', age: 41 }) + await users.create({ id: 4, name: 'Alice', age: 41 }) + + expect( + users.findMany(undefined, { + orderBy: [{ age: 'asc' }, { name: 'desc' }], + }), + ).toEqual([ + { id: 2, name: 'Alice', age: 24 }, + { id: 1, name: 'John', age: 32 }, + { id: 3, name: 'Bob', age: 41 }, + { id: 4, name: 'Alice', age: 41 }, + ]) +}) From 911ae71c74b220ff25a871d9ce350224d6bcccc7 Mon Sep 17 00:00:00 2001 From: Artem Zakharchenko Date: Mon, 20 Apr 2026 11:33:02 +0200 Subject: [PATCH 2/4] test: add sorting by relations test case --- tests/sort.test.ts | 60 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/tests/sort.test.ts b/tests/sort.test.ts index ed1e014..a90fb62 100644 --- a/tests/sort.test.ts +++ b/tests/sort.test.ts @@ -147,3 +147,63 @@ it('sorts the results by a list of sort criteria', async () => { { id: 4, name: 'Alice', age: 41 }, ]) }) + +it('sorts by a relational property', async () => { + const userSchema = z.object({ + id: z.number(), + name: z.string(), + get posts() { + return z.array(postSchema) + }, + }) + const postSchema = z.object({ + id: z.number(), + title: z.string(), + get author() { + return userSchema.optional() + }, + }) + + const users = new Collection({ schema: userSchema }) + const posts = new Collection({ schema: postSchema }) + + users.defineRelations(({ many }) => ({ + posts: many(posts), + })) + posts.defineRelations(({ one }) => ({ + author: one(users, { unique: true }), + })) + + const john = await users.create({ + id: 1, + name: 'John', + posts: await posts.createMany(2, (index) => ({ + id: index + 1, + title: `Post ${index + 1}`, + })), + }) + + const alice = await users.create({ + id: 2, + name: 'Alice', + posts: await posts.createMany(2, (index) => ({ + id: index + 3, + title: `Post ${index + 3}`, + })), + }) + + expect( + posts.findMany(undefined, { + orderBy: { + author: { + name: 'asc', + }, + }, + }), + ).toEqual([ + { id: 3, title: 'Post 3', author: alice }, + { id: 4, title: 'Post 4', author: alice }, + { id: 1, title: 'Post 1', author: john }, + { id: 2, title: 'Post 2', author: john }, + ]) +}) From 835f3d18c02543535fe97015bd13527583010aa8 Mon Sep 17 00:00:00 2001 From: Artem Zakharchenko Date: Mon, 20 Apr 2026 11:34:14 +0200 Subject: [PATCH 3/4] docs: fix typo in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1285293..c2abd96 100644 --- a/README.md +++ b/README.md @@ -205,7 +205,7 @@ You can sort by multiple keys by listing them in the `orderBy` object: ```ts users.updateMany((q) => q.where({ name: (name) => name.startsWith('J') }), { data(user) { - user.name = user.name.toUpperCase(), + user.name = user.name.toUpperCase() }, orderBy: { name: 'asc', From 213490ba1866da1dcc2792d458f2c591d9dd97e2 Mon Sep 17 00:00:00 2001 From: Artem Zakharchenko Date: Mon, 20 Apr 2026 11:37:36 +0200 Subject: [PATCH 4/4] test: refactor type tests for `orderBy` --- tests/types/delete-many.test-d.ts | 5 +++++ tests/types/find-many.test-d.ts | 5 +++++ tests/types/update-many.test-d.ts | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/tests/types/delete-many.test-d.ts b/tests/types/delete-many.test-d.ts index d04ae74..a09a740 100644 --- a/tests/types/delete-many.test-d.ts +++ b/tests/types/delete-many.test-d.ts @@ -66,6 +66,11 @@ it('supports sorting the results', () => { name?: SortDirection nested?: { key?: SortDirection } } + | Array<{ + id?: SortDirection + name?: SortDirection + nested?: { key?: SortDirection } + }> | undefined >() }) diff --git a/tests/types/find-many.test-d.ts b/tests/types/find-many.test-d.ts index 00d0f61..d5ad7bd 100644 --- a/tests/types/find-many.test-d.ts +++ b/tests/types/find-many.test-d.ts @@ -117,6 +117,11 @@ it('supports sorting the results', () => { name?: SortDirection | undefined nested?: { key?: SortDirection | undefined } } + | Array<{ + id?: SortDirection | undefined + name?: SortDirection | undefined + nested?: { key?: SortDirection | undefined } + }> | undefined >() }) diff --git a/tests/types/update-many.test-d.ts b/tests/types/update-many.test-d.ts index d05099c..09a864d 100644 --- a/tests/types/update-many.test-d.ts +++ b/tests/types/update-many.test-d.ts @@ -109,6 +109,11 @@ it('supports sorting the results', () => { name?: SortDirection nested?: { key?: SortDirection } } + | Array<{ + id?: SortDirection + name?: SortDirection + nested?: { key?: SortDirection } + }> | undefined >() })