From 2de1013611bface9c30a994f52d68bacf41c9045 Mon Sep 17 00:00:00 2001 From: Sasha <64744993+r1tsuu@users.noreply.github.com> Date: Wed, 26 Nov 2025 16:44:18 +0200 Subject: [PATCH 1/3] fix: `select` in `findByID` with `draft: true` may return a wrong version --- .../src/collections/operations/findByID.ts | 14 ++++++++- .../payload/src/globals/operations/findOne.ts | 12 +++++++- test/select/getConfig.ts | 3 ++ test/select/int.spec.ts | 30 +++++++++++++++++++ test/select/payload-types.ts | 30 +++++++++++++++++++ 5 files changed, 87 insertions(+), 2 deletions(-) diff --git a/packages/payload/src/collections/operations/findByID.ts b/packages/payload/src/collections/operations/findByID.ts index 7ca512828ec..46155244e69 100644 --- a/packages/payload/src/collections/operations/findByID.ts +++ b/packages/payload/src/collections/operations/findByID.ts @@ -24,6 +24,7 @@ import { afterRead, type AfterReadArgs } from '../../fields/hooks/afterRead/inde import { validateQueryPaths } from '../../index.js' import { lockedDocumentsCollectionSlug } from '../../locked-documents/config.js' import { appendNonTrashedFilter } from '../../utilities/appendNonTrashedFilter.js' +import { getSelectMode } from '../../utilities/getSelectMode.js' import { killTransaction } from '../../utilities/killTransaction.js' import { sanitizeSelect } from '../../utilities/sanitizeSelect.js' import { replaceWithDraftIfAvailable } from '../../versions/drafts/replaceWithDraftIfAvailable.js' @@ -140,6 +141,17 @@ export const findByIDOperation = async < req, }) + let dbSelect = select + + if ( + collectionConfig.versions?.drafts && + draftEnabled && + select && + getSelectMode(select) === 'include' + ) { + dbSelect = { ...select, createdAt: true, updatedAt: true } + } + const findOneArgs: FindOneArgs = { collection: collectionConfig.slug, draftsEnabled: draftEnabled, @@ -148,7 +160,7 @@ export const findByIDOperation = async < req: { transactionID: req.transactionID, } as PayloadRequest, - select, + select: dbSelect, where: fullWhere, } diff --git a/packages/payload/src/globals/operations/findOne.ts b/packages/payload/src/globals/operations/findOne.ts index 26153106e5d..25f86a0c447 100644 --- a/packages/payload/src/globals/operations/findOne.ts +++ b/packages/payload/src/globals/operations/findOne.ts @@ -95,11 +95,21 @@ export const findOneOperation = async >( // Perform database operation // ///////////////////////////////////// + let dbSelect = select + + if ( + globalConfig.versions?.drafts && + draftEnabled && + select && + getSelectMode(select) === 'include' + ) { + dbSelect = { ...select, createdAt: true, updatedAt: true } + } const docFromDB = await req.payload.db.findGlobal({ slug, locale: locale!, req, - select, + select: dbSelect, where: overrideAccess ? undefined : (accessResult as Where), }) diff --git a/test/select/getConfig.ts b/test/select/getConfig.ts index dd11deece3b..38c82a546a1 100644 --- a/test/select/getConfig.ts +++ b/test/select/getConfig.ts @@ -72,6 +72,9 @@ export const getConfig: () => Partial = () => ({ globals: [ { slug: 'global-post', + versions: { + drafts: true, + }, fields: [ { name: 'text', diff --git a/test/select/int.spec.ts b/test/select/int.spec.ts index 5ae8d5c9637..8789020998b 100644 --- a/test/select/int.spec.ts +++ b/test/select/int.spec.ts @@ -1647,6 +1647,36 @@ describe('Select', () => { expect(doc.version.text).toBe(post.text) }) + it('should return a latest version with findByID and draft: true', async () => { + const doc = await payload.create({ + collection: 'versioned-posts', + data: { text: 'draft-post', _status: 'draft' }, + draft: true, + }) + + const res = await payload.findByID({ + collection: 'versioned-posts', + id: doc.id, + draft: true, + select: { text: true }, + }) + expect(res.text).toBe('draft-post') + await payload.update({ + collection: 'versioned-posts', + id: doc.id, + data: { text: 'published', _status: 'published' }, + }) + + const res_2 = await payload.findByID({ + collection: 'versioned-posts', + id: doc.id, + draft: true, + select: { text: true }, + }) + + expect(res_2.text).toBe('published') + }) + it('should create versions with complete data when updating with select', async () => { // First, update the post with select to only return the id field const updatedPost = await payload.update({ diff --git a/test/select/payload-types.ts b/test/select/payload-types.ts index faa0b1d0de9..0f0d8e70e6e 100644 --- a/test/select/payload-types.ts +++ b/test/select/payload-types.ts @@ -79,6 +79,7 @@ export interface Config { 'relationships-blocks': RelationshipsBlock; 'custom-ids': CustomId; users: User; + 'payload-kv': PayloadKv; 'payload-locked-documents': PayloadLockedDocument; 'payload-preferences': PayloadPreference; 'payload-migrations': PayloadMigration; @@ -97,6 +98,7 @@ export interface Config { 'relationships-blocks': RelationshipsBlocksSelect | RelationshipsBlocksSelect; 'custom-ids': CustomIdsSelect | CustomIdsSelect; users: UsersSelect | UsersSelect; + 'payload-kv': PayloadKvSelect | PayloadKvSelect; 'payload-locked-documents': PayloadLockedDocumentsSelect | PayloadLockedDocumentsSelect; 'payload-preferences': PayloadPreferencesSelect | PayloadPreferencesSelect; 'payload-migrations': PayloadMigrationsSelect | PayloadMigrationsSelect; @@ -104,6 +106,7 @@ export interface Config { db: { defaultIDType: string; }; + fallbackLocale: ('false' | 'none' | 'null') | false | null | ('en' | 'de') | ('en' | 'de')[]; globals: { 'global-post': GlobalPost; 'force-select-global': ForceSelectGlobal; @@ -524,6 +527,23 @@ export interface User { | null; password?: string | null; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "payload-kv". + */ +export interface PayloadKv { + id: string; + key: string; + data: + | { + [k: string]: unknown; + } + | unknown[] + | string + | number + | boolean + | null; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "payload-locked-documents". @@ -991,6 +1011,14 @@ export interface UsersSelect { expiresAt?: T; }; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "payload-kv_select". + */ +export interface PayloadKvSelect { + key?: T; + data?: T; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "payload-locked-documents_select". @@ -1031,6 +1059,7 @@ export interface GlobalPost { id: string; text?: string | null; number?: number | null; + _status?: ('draft' | 'published') | null; updatedAt?: string | null; createdAt?: string | null; } @@ -1058,6 +1087,7 @@ export interface ForceSelectGlobal { export interface GlobalPostSelect { text?: T; number?: T; + _status?: T; updatedAt?: T; createdAt?: T; globalType?: T; From 7f94fe465e4d19a56efcad2a3639fbfe2e8b9ed8 Mon Sep 17 00:00:00 2001 From: Sasha <64744993+r1tsuu@users.noreply.github.com> Date: Wed, 26 Nov 2025 16:48:24 +0200 Subject: [PATCH 2/3] confirm it doesnt return extra fields --- test/select/int.spec.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/select/int.spec.ts b/test/select/int.spec.ts index 8789020998b..bd1bd21a7d3 100644 --- a/test/select/int.spec.ts +++ b/test/select/int.spec.ts @@ -1674,7 +1674,10 @@ describe('Select', () => { select: { text: true }, }) - expect(res_2.text).toBe('published') + expect(res_2).toStrictEqual({ + text: 'published', + id: res_2.id, + }) }) it('should create versions with complete data when updating with select', async () => { From bce7a7b8026d134256fd1e255b0d3ebecde2ffcb Mon Sep 17 00:00:00 2001 From: Sasha <64744993+r1tsuu@users.noreply.github.com> Date: Wed, 26 Nov 2025 17:14:42 +0200 Subject: [PATCH 3/3] revert global config --- test/_community/payload-types.ts | 28 ++++++++++++++-------------- test/select/getConfig.ts | 3 --- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/test/_community/payload-types.ts b/test/_community/payload-types.ts index 39b48742beb..b38441fefa4 100644 --- a/test/_community/payload-types.ts +++ b/test/_community/payload-types.ts @@ -86,7 +86,7 @@ export interface Config { 'payload-migrations': PayloadMigrationsSelect | PayloadMigrationsSelect; }; db: { - defaultIDType: string; + defaultIDType: number; }; globals: { menu: Menu; @@ -126,7 +126,7 @@ export interface UserAuthOperations { * via the `definition` "posts". */ export interface Post { - id: string; + id: number; title?: string | null; content?: { root: { @@ -151,7 +151,7 @@ export interface Post { * via the `definition` "media". */ export interface Media { - id: string; + id: number; updatedAt: string; createdAt: string; url?: string | null; @@ -195,7 +195,7 @@ export interface Media { * via the `definition` "payload-kv". */ export interface PayloadKv { - id: string; + id: number; key: string; data: | { @@ -212,7 +212,7 @@ export interface PayloadKv { * via the `definition` "users". */ export interface User { - id: string; + id: number; updatedAt: string; createdAt: string; email: string; @@ -236,24 +236,24 @@ export interface User { * via the `definition` "payload-locked-documents". */ export interface PayloadLockedDocument { - id: string; + id: number; document?: | ({ relationTo: 'posts'; - value: string | Post; + value: number | Post; } | null) | ({ relationTo: 'media'; - value: string | Media; + value: number | Media; } | null) | ({ relationTo: 'users'; - value: string | User; + value: number | User; } | null); globalSlug?: string | null; user: { relationTo: 'users'; - value: string | User; + value: number | User; }; updatedAt: string; createdAt: string; @@ -263,10 +263,10 @@ export interface PayloadLockedDocument { * via the `definition` "payload-preferences". */ export interface PayloadPreference { - id: string; + id: number; user: { relationTo: 'users'; - value: string | User; + value: number | User; }; key?: string | null; value?: @@ -286,7 +286,7 @@ export interface PayloadPreference { * via the `definition` "payload-migrations". */ export interface PayloadMigration { - id: string; + id: number; name?: string | null; batch?: number | null; updatedAt: string; @@ -420,7 +420,7 @@ export interface PayloadMigrationsSelect { * via the `definition` "menu". */ export interface Menu { - id: string; + id: number; globalText?: string | null; updatedAt?: string | null; createdAt?: string | null; diff --git a/test/select/getConfig.ts b/test/select/getConfig.ts index 38c82a546a1..dd11deece3b 100644 --- a/test/select/getConfig.ts +++ b/test/select/getConfig.ts @@ -72,9 +72,6 @@ export const getConfig: () => Partial = () => ({ globals: [ { slug: 'global-post', - versions: { - drafts: true, - }, fields: [ { name: 'text',