Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,12 +200,12 @@ 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') }), {
data(user) {
user.name = user.name.toUpperCase(),
user.name = user.name.toUpperCase()
},
orderBy: {
name: 'asc',
Expand All @@ -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.
Expand Down
20 changes: 15 additions & 5 deletions src/sort.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
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'

export interface SortOptions<Schema extends StandardSchemaV1> {
orderBy?: OrderBy<Schema>
}

type OrderBy<
type OrderBy<Schema extends StandardSchemaV1> =
| OrderByCriteria<Schema>
| Array<OrderByCriteria<Schema>>

type OrderByCriteria<
Schema extends StandardSchemaV1,
T = StandardSchemaV1.InferOutput<Schema>,
> =
NonNullable<T> extends Array<infer V>
? OrderBy<Schema, V>
? OrderByCriteria<Schema, V>
: NonNullable<T> extends Record<any, any>
? {
[K in keyof T]?: OrderBy<Schema, T[K]>
[K in keyof T]?: OrderByCriteria<Schema, T[K]>
}
: SortDirection

Expand All @@ -28,7 +32,13 @@ export function sortResults<Schema extends StandardSchemaV1>(
return
}

const criteria = toDeepEntries<SortDirection>(sortOptions.orderBy as any)
const criteria: Array<[PropertyPath, SortDirection]> = Array.isArray(
sortOptions.orderBy,
)
? sortOptions.orderBy.flatMap((entry) => {
return toDeepEntries<SortDirection>(entry as any)
})
: toDeepEntries<SortDirection>(sortOptions.orderBy as any)

data.sort((left, right) => {
for (const [path, sortDirection] of criteria) {
Expand Down
94 changes: 90 additions & 4 deletions tests/sort.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' })
Expand All @@ -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' })
Expand All @@ -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' })
Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -121,3 +121,89 @@ 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 },
])
})

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 },
])
})
5 changes: 5 additions & 0 deletions tests/types/delete-many.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ it('supports sorting the results', () => {
name?: SortDirection
nested?: { key?: SortDirection }
}
| Array<{
id?: SortDirection
name?: SortDirection
nested?: { key?: SortDirection }
}>
| undefined
>()
})
5 changes: 5 additions & 0 deletions tests/types/find-many.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
>()
})
5 changes: 5 additions & 0 deletions tests/types/update-many.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ it('supports sorting the results', () => {
name?: SortDirection
nested?: { key?: SortDirection }
}
| Array<{
id?: SortDirection
name?: SortDirection
nested?: { key?: SortDirection }
}>
| undefined
>()
})
Loading