feat: sort support for payload.update operation (#11769)

Continuation of https://github.com/payloadcms/payload/pull/11768. This
adds support for `sort` in `payload.update`.

## Example

```ts
const { docs } = await payload.update({
  collection: 'posts',
  data: {
    title: 'updated',
  },
  limit: 5,
  sort: '-numberField', // <= new
  where: {
    id: {
      exists: true,
    },
  },
})
```
This commit is contained in:
Alessio Gravili
2025-03-19 11:22:13 -06:00
committed by GitHub
parent e96d3c87e2
commit 20e975b7c6
4 changed files with 163 additions and 2 deletions

View File

@@ -14,13 +14,14 @@ import { updateOperation } from '../operations/update.js'
export const updateHandler: PayloadHandler = async (req) => { export const updateHandler: PayloadHandler = async (req) => {
const collection = getRequestCollection(req) const collection = getRequestCollection(req)
const { depth, draft, limit, overrideLock, populate, select, where } = req.query as { const { depth, draft, limit, overrideLock, populate, select, sort, where } = req.query as {
depth?: string depth?: string
draft?: string draft?: string
limit?: string limit?: string
overrideLock?: string overrideLock?: string
populate?: Record<string, unknown> populate?: Record<string, unknown>
select?: Record<string, unknown> select?: Record<string, unknown>
sort?: string
where?: Where where?: Where
} }
@@ -34,6 +35,7 @@ export const updateHandler: PayloadHandler = async (req) => {
populate: sanitizePopulateParam(populate), populate: sanitizePopulateParam(populate),
req, req,
select: sanitizeSelectParam(select), select: sanitizeSelectParam(select),
sort: typeof sort === 'string' ? sort.split(',') : undefined,
where, where,
}) })

View File

@@ -7,6 +7,7 @@ import type {
PayloadRequest, PayloadRequest,
PopulateType, PopulateType,
SelectType, SelectType,
Sort,
TransformCollectionWithSelect, TransformCollectionWithSelect,
Where, Where,
} from '../../../types/index.js' } from '../../../types/index.js'
@@ -130,6 +131,12 @@ export type ByIDOptions<
* Limit documents to update * Limit documents to update
*/ */
limit?: never limit?: never
/**
* Sort the documents, can be a string or an array of strings
* @example '-createdAt' // Sort DESC by createdAt
* @example ['group', '-createdAt'] // sort by 2 fields, ASC group and DESC createdAt
*/
sort?: never
/** /**
* A filter [query](https://payloadcms.com/docs/queries/overview) * A filter [query](https://payloadcms.com/docs/queries/overview)
*/ */
@@ -148,6 +155,12 @@ export type ManyOptions<
* Limit documents to update * Limit documents to update
*/ */
limit?: number limit?: number
/**
* Sort the documents, can be a string or an array of strings
* @example '-createdAt' // Sort DESC by createdAt
* @example ['group', '-createdAt'] // sort by 2 fields, ASC group and DESC createdAt
*/
sort?: Sort
/** /**
* A filter [query](https://payloadcms.com/docs/queries/overview) * A filter [query](https://payloadcms.com/docs/queries/overview)
*/ */
@@ -205,6 +218,7 @@ async function updateLocal<
publishSpecificLocale, publishSpecificLocale,
select, select,
showHiddenFields, showHiddenFields,
sort,
where, where,
} = options } = options
@@ -237,6 +251,7 @@ async function updateLocal<
req, req,
select, select,
showHiddenFields, showHiddenFields,
sort,
where, where,
} }

View File

@@ -4,7 +4,7 @@ import type { DeepPartial } from 'ts-essentials'
import { status as httpStatus } from 'http-status' import { status as httpStatus } from 'http-status'
import type { AccessResult } from '../../config/types.js' import type { AccessResult } from '../../config/types.js'
import type { PayloadRequest, PopulateType, SelectType, Where } from '../../types/index.js' import type { PayloadRequest, PopulateType, SelectType, Sort, Where } from '../../types/index.js'
import type { import type {
BulkOperationResult, BulkOperationResult,
Collection, Collection,
@@ -26,6 +26,7 @@ import { killTransaction } from '../../utilities/killTransaction.js'
import { sanitizeSelect } from '../../utilities/sanitizeSelect.js' import { sanitizeSelect } from '../../utilities/sanitizeSelect.js'
import { buildVersionCollectionFields } from '../../versions/buildCollectionFields.js' import { buildVersionCollectionFields } from '../../versions/buildCollectionFields.js'
import { appendVersionToQueryKey } from '../../versions/drafts/appendVersionToQueryKey.js' import { appendVersionToQueryKey } from '../../versions/drafts/appendVersionToQueryKey.js'
import { getQueryDraftsSort } from '../../versions/drafts/getQueryDraftsSort.js'
import { updateDocument } from './utilities/update.js' import { updateDocument } from './utilities/update.js'
import { buildAfterOperation } from './utils.js' import { buildAfterOperation } from './utils.js'
@@ -45,6 +46,12 @@ export type Arguments<TSlug extends CollectionSlug> = {
req: PayloadRequest req: PayloadRequest
select?: SelectType select?: SelectType
showHiddenFields?: boolean showHiddenFields?: boolean
/**
* Sort the documents, can be a string or an array of strings
* @example '-createdAt' // Sort DESC by createdAt
* @example ['group', '-createdAt'] // sort by 2 fields, ASC group and DESC createdAt
*/
sort?: Sort
where: Where where: Where
} }
@@ -96,6 +103,7 @@ export const updateOperation = async <
req, req,
select: incomingSelect, select: incomingSelect,
showHiddenFields, showHiddenFields,
sort,
where, where,
} = args } = args
@@ -147,6 +155,7 @@ export const updateOperation = async <
locale, locale,
pagination: false, pagination: false,
req, req,
sort: getQueryDraftsSort({ collectionConfig, sort }),
where: versionsWhere, where: versionsWhere,
}) })
@@ -158,6 +167,7 @@ export const updateOperation = async <
locale, locale,
pagination: false, pagination: false,
req, req,
sort,
where: fullWhere, where: fullWhere,
}) })

View File

@@ -1235,6 +1235,73 @@ describe('database', () => {
} }
}) })
it('ensure payload.update operation respects limit and sort', async () => {
await payload.db.deleteMany({
collection: postsSlug,
where: {
id: {
exists: true,
},
},
})
const numbers = Array.from({ length: 11 }, (_, i) => i)
// shuffle the numbers
numbers.sort(() => Math.random() - 0.5)
// create 11 documents numbered 0-10, but in random order
for (const i of numbers) {
await payload.create({
collection: postsSlug,
data: {
title: 'not updated',
number: i,
},
})
}
const result = await payload.update({
collection: postsSlug,
data: {
title: 'updated',
},
limit: 5,
sort: 'number',
where: {
id: {
exists: true,
},
},
})
expect(result?.docs.length).toBe(5)
for (let i = 0; i < 5; i++) {
expect(result?.docs?.[i]?.number).toBe(i)
expect(result?.docs?.[i]?.title).toBe('updated')
}
// Ensure all posts minus the one we don't want updated are updated
const { docs } = await payload.find({
collection: postsSlug,
depth: 0,
pagination: false,
sort: 'number',
where: {
title: {
equals: 'updated',
},
},
})
expect(docs).toHaveLength(5)
for (let i = 0; i < 5; i++) {
expect(docs?.[i]?.number).toBe(i)
expect(docs?.[i]?.title).toBe('updated')
}
})
it('ensure updateMany respects limit and negative sort', async () => { it('ensure updateMany respects limit and negative sort', async () => {
await payload.db.deleteMany({ await payload.db.deleteMany({
collection: postsSlug, collection: postsSlug,
@@ -1302,6 +1369,73 @@ describe('database', () => {
} }
}) })
it('ensure payload.update operation respects limit and negative sort', async () => {
await payload.db.deleteMany({
collection: postsSlug,
where: {
id: {
exists: true,
},
},
})
const numbers = Array.from({ length: 11 }, (_, i) => i)
// shuffle the numbers
numbers.sort(() => Math.random() - 0.5)
// create 11 documents numbered 0-10, but in random order
for (const i of numbers) {
await payload.create({
collection: postsSlug,
data: {
title: 'not updated',
number: i,
},
})
}
const result = await payload.update({
collection: postsSlug,
data: {
title: 'updated',
},
limit: 5,
sort: '-number',
where: {
id: {
exists: true,
},
},
})
expect(result?.docs?.length).toBe(5)
for (let i = 10; i > 5; i--) {
expect(result?.docs?.[-i + 10]?.number).toBe(i)
expect(result?.docs?.[-i + 10]?.title).toBe('updated')
}
// Ensure all posts minus the one we don't want updated are updated
const { docs } = await payload.find({
collection: postsSlug,
depth: 0,
pagination: false,
sort: '-number',
where: {
title: {
equals: 'updated',
},
},
})
expect(docs).toHaveLength(5)
for (let i = 10; i > 5; i--) {
expect(docs?.[-i + 10]?.number).toBe(i)
expect(docs?.[-i + 10]?.title).toBe('updated')
}
})
it('ensure updateMany correctly handles 0 limit', async () => { it('ensure updateMany correctly handles 0 limit', async () => {
await payload.db.deleteMany({ await payload.db.deleteMany({
collection: postsSlug, collection: postsSlug,