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
5 changes: 5 additions & 0 deletions packages/sdk/src/collections/count.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ export type CountOptions<T extends PayloadTypesShape, TSlug extends CollectionSl
* Specify [locale](https://payloadcms.com/docs/configuration/localization) for any returned documents.
*/
locale?: 'all' | TypedLocale<T>
/**
* When `true`, the count includes trashed documents (same semantics as `find`). No effect unless the collection has `trash` enabled.
* @default false
*/
trash?: boolean
/**
* A filter [query](https://payloadcms.com/docs/queries/overview)
*/
Expand Down
5 changes: 5 additions & 0 deletions packages/sdk/src/collections/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ export type DeleteBaseOptions<
* Specify [select](https://payloadcms.com/docs/queries/select) to control which fields to include to the result.
*/
select?: TSelect
/**
* When the collection has `trash` enabled: use with the REST API semantics for permanent delete vs bulk operations on trashed docs.
* @default false
*/
trash?: boolean
}

export type DeleteByIDOptions<
Expand Down
5 changes: 5 additions & 0 deletions packages/sdk/src/collections/findByID.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ export type FindByIDOptions<
* Specify [populate](https://payloadcms.com/docs/queries/select#populate) to control which fields to include to the result from populated documents.
*/
populate?: PopulateType<T>
/**
* When `true`, returns the document even if it is trashed. No effect unless the collection has `trash` enabled.
* @default false
*/
trash?: boolean
} & Pick<FindOptions<TSlug, SelectType & TSelect>, 'select'>

export async function findByID<
Expand Down
5 changes: 5 additions & 0 deletions packages/sdk/src/collections/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ export type UpdateBaseOptions<
* Specify [select](https://payloadcms.com/docs/queries/select) to control which fields to include to the result.
*/
select?: TSelect
/**
* When `true`, the operation can target trashed (soft-deleted) documents. No effect unless the collection has `trash` enabled.
* @default false
*/
trash?: boolean
}

export type UpdateByIDOptions<
Expand Down
5 changes: 5 additions & 0 deletions packages/sdk/src/utilities/buildSearchParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type OperationArgs = {
populate?: Record<string, unknown>
select?: unknown
sort?: Sort
trash?: boolean
where?: Where
}

Expand All @@ -36,6 +37,10 @@ export const buildSearchParams = (args: OperationArgs): string => {
search.draft = String(args.draft)
}

if (typeof args.trash === 'boolean') {
search.trash = String(args.trash)
}

if (typeof args.pagination === 'boolean') {
search.pagination = String(args.pagination)
}
Expand Down
1 change: 1 addition & 0 deletions test/sdk/collections/Posts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,6 @@ export const PostsCollection: CollectionConfig = {
],
},
],
trash: true,
versions: true,
}
147 changes: 134 additions & 13 deletions test/sdk/int.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { emailsSlug } from './collections/Emails.js'

let payload: Payload
let post: Post
let postTrash: Post
let sdk: TypedPayloadSDK

const filename = fileURLToPath(import.meta.url)
Expand All @@ -30,6 +31,10 @@ describe('@payloadcms/sdk', () => {
;({ payload, sdk } = await initPayloadInt(dirname))

post = await payload.create({ collection: 'posts', data: { number: 1, number2: 3 } })
postTrash = await payload.create({
collection: 'posts',
data: { deletedAt: new Date().toISOString(), text: 'fixture-trash' },
})
await payload.create({
collection: 'users',
data: { ...testUserCredentials },
Expand Down Expand Up @@ -60,6 +65,15 @@ describe('@payloadcms/sdk', () => {
await payload.delete({ collection: 'posts', where: { id: { in: ids } } })
})

it('should execute find with trash', async () => {
const pairWhere = { id: { in: [post.id, postTrash.id] } }

expect((await sdk.find({ collection: 'posts', where: pairWhere })).docs).toHaveLength(1)
expect(
(await sdk.find({ collection: 'posts', trash: true, where: pairWhere })).docs,
).toHaveLength(2)
})

it('should execute findVersions', async () => {
const result = await sdk.findVersions({
collection: 'posts',
Expand All @@ -69,12 +83,30 @@ describe('@payloadcms/sdk', () => {
expect(result.docs[0].parent).toBe(post.id)
})

it('should execute findVersions with trash', async () => {
const pairWhere = { parent: { in: [post.id, postTrash.id] } }

expect((await sdk.findVersions({ collection: 'posts', where: pairWhere })).docs).toHaveLength(1)
expect(
(await sdk.findVersions({ collection: 'posts', trash: true, where: pairWhere })).docs,
).toHaveLength(2)
})

it('should execute findByID', async () => {
const result = await sdk.findByID({ collection: 'posts', id: post.id })

expect(result.id).toBe(post.id)
})

it('should execute findByID with trash', async () => {
expect(
await sdk.findByID({ collection: 'posts', id: postTrash.id, disableErrors: true }),
).toBeNull()
expect((await sdk.findByID({ collection: 'posts', id: postTrash.id, trash: true })).id).toEqual(
postTrash.id,
)
})

it('should execute findByID with disableErrors: true', async () => {
const result = await sdk.findByID({
disableErrors: true,
Expand All @@ -95,6 +127,23 @@ describe('@payloadcms/sdk', () => {
expect(result.id).toBe(version.id)
})

it('should execute findVersionByID with trash', async () => {
const {
docs: [trashVersion],
} = await payload.findVersions({
collection: 'posts',
trash: true,
where: { parent: { equals: postTrash.id } },
})

expect(
await sdk.findVersionByID({ collection: 'posts', id: trashVersion.id, disableErrors: true }),
).toBeNull()
expect(
(await sdk.findVersionByID({ collection: 'posts', id: trashVersion.id, trash: true })).id,
).toBe(trashVersion.id)
})

it('should execute create', async () => {
const result = await sdk.create({ collection: 'posts', data: { text: 'text' } })

Expand All @@ -115,6 +164,18 @@ describe('@payloadcms/sdk', () => {
expect(result.totalDocs).toBe(1)
})

it('should execute count with trash', async () => {
expect(
(
await sdk.count({
collection: 'posts',
trash: true,
where: { id: { in: [post.id, postTrash.id] } },
})
).totalDocs,
).toBe(2)
})

it('should execute update (by ID)', async () => {
const result = await sdk.update({
collection: 'posts',
Expand All @@ -125,34 +186,65 @@ describe('@payloadcms/sdk', () => {
expect(result.text).toBe('updated-text')
})

it('should execute update (by ID) with trash', async () => {
const result = await sdk.update({
collection: 'posts',
id: postTrash.id,
data: { text: 'updated-trash-by-id' },
trash: true,
})

expect(result.text).toBe('updated-trash-by-id')
})

it('should execute update (bulk)', async () => {
const result = await sdk.update({
collection: 'posts',
where: {
id: {
equals: post.id,
},
},
where: { id: { equals: post.id } },
data: { text: 'updated-text-bulk' },
})

expect(result.docs[0].text).toBe('updated-text-bulk')
})

it('should execute update (bulk) with trash', async () => {
const result = await sdk.update({
collection: 'posts',
where: { id: { equals: postTrash.id } },
data: { text: 'updated-trash-bulk' },
trash: true,
})

expect(result.docs[0].text).toBe('updated-trash-bulk')
})

it('should execute delete (by ID)', async () => {
const post = await payload.create({ collection: 'posts', data: {} })

const result = await sdk.delete({ id: post.id, collection: 'posts' })

expect(result.id).toBe(post.id)
expect(
await payload.findByID({ collection: 'posts', id: post.id, disableErrors: true }),
).toBeNull()
})

const resultLocal = await payload.findByID({
it('should execute delete (by ID) with trash', async () => {
const trashed = await payload.create({
collection: 'posts',
id: post.id,
disableErrors: true,
data: { deletedAt: new Date().toISOString() },
})

expect(resultLocal).toBeNull()
await sdk.delete({ collection: 'posts', id: trashed.id, trash: true })

expect(
await payload.findByID({
collection: 'posts',
disableErrors: true,
id: trashed.id,
trash: true,
}),
).toBeNull()
})

it('should execute delete (bulk)', async () => {
Expand All @@ -161,14 +253,43 @@ describe('@payloadcms/sdk', () => {
const result = await sdk.delete({ where: { id: { equals: post.id } }, collection: 'posts' })

expect(result.docs[0].id).toBe(post.id)
expect(
await payload.findByID({ collection: 'posts', id: post.id, disableErrors: true }),
).toBeNull()
})

const resultLocal = await payload.findByID({
it('should execute delete (bulk) with trash', async () => {
const trashedA = await payload.create({
collection: 'posts',
id: post.id,
disableErrors: true,
data: { deletedAt: new Date().toISOString(), text: 'bulk-perma-a' },
})
const trashedB = await payload.create({
collection: 'posts',
data: { deletedAt: new Date().toISOString(), text: 'bulk-perma-b' },
})

await sdk.delete({
collection: 'posts',
trash: true,
where: { id: { in: [trashedA.id, trashedB.id] } },
})

expect(resultLocal).toBeNull()
expect(
await payload.findByID({
collection: 'posts',
disableErrors: true,
id: trashedA.id,
trash: true,
}),
).toBeNull()
expect(
await payload.findByID({
collection: 'posts',
disableErrors: true,
id: trashedB.id,
trash: true,
}),
).toBeNull()
})

it('should execute restoreVersion', async () => {
Expand Down
Loading