Files
payloadcms/packages/payload/src/globals/operations/local/findOne.ts
Alessio Gravili b34e5eadf4 fix(live-preview): client-side live preview failed to populate localized fields (#13794)
Fixes #13756 

The findByID endpoint, by default, expects the data for localized fields
to be an object, values mapped to the locale. This is not the case for
client-side live preview, as we send already-flattened data to the
findByID endpoint.

For localized fields where the value is an object (richText/json/group),
the afterRead hook handler would attempt to flatten the field value,
even though it was already flattened.

## Solution

The solution is to expose a `flattenLocales` arg to the findByID
endpoint (default: true) and pass `flattenLocales: false` from the
client-side live preview request handler.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211334752795627
2025-09-15 12:16:52 -04:00

124 lines
4.3 KiB
TypeScript

import type { GlobalSlug, Payload, RequestContext, TypedLocale } from '../../../index.js'
import type {
Document,
PayloadRequest,
PopulateType,
SelectType,
TransformGlobalWithSelect,
} from '../../../types/index.js'
import type { CreateLocalReqOptions } from '../../../utilities/createLocalReq.js'
import type { SelectFromGlobalSlug } from '../../config/types.js'
import { APIError } from '../../../errors/index.js'
import { createLocalReq } from '../../../utilities/createLocalReq.js'
import { findOneOperation, type GlobalFindOneArgs } from '../findOne.js'
export type Options<TSlug extends GlobalSlug, TSelect extends SelectType> = {
/**
* [Context](https://payloadcms.com/docs/hooks/context), which will then be passed to `context` and `req.context`,
* which can be read by hooks. Useful if you want to pass additional information to the hooks which
* shouldn't be necessarily part of the document, for example a `triggerBeforeChange` option which can be read by the BeforeChange hook
* to determine if it should run or not.
*/
context?: RequestContext
/**
* You may pass the document data directly which will skip the `db.findOne` database query.
* This is useful if you want to use this endpoint solely for running hooks and populating data.
*/
data?: Record<string, unknown>
/**
* [Control auto-population](https://payloadcms.com/docs/queries/depth) of nested relationship and upload fields.
*/
depth?: number
/**
* Whether the document should be queried from the versions table/collection or not. [More](https://payloadcms.com/docs/versions/drafts#draft-api)
*/
draft?: boolean
/**
* Specify a [fallback locale](https://payloadcms.com/docs/configuration/localization) to use for any returned documents.
*/
fallbackLocale?: false | TypedLocale
/**
* Include info about the lock status to the result with fields: `_isLocked` and `_userEditing`
*/
includeLockStatus?: boolean
/**
* Specify [locale](https://payloadcms.com/docs/configuration/localization) for any returned documents.
*/
locale?: 'all' | TypedLocale
/**
* Skip access control.
* Set to `false` if you want to respect Access Control for the operation, for example when fetching data for the front-end.
* @default true
*/
overrideAccess?: boolean
/**
* Specify [populate](https://payloadcms.com/docs/queries/select#populate) to control which fields to include to the result from populated documents.
*/
populate?: PopulateType
/**
* The `PayloadRequest` object. You can pass it to thread the current [transaction](https://payloadcms.com/docs/database/transactions), user and locale to the operation.
* Recommended to pass when using the Local API from hooks, as usually you want to execute the operation within the current transaction.
*/
req?: Partial<PayloadRequest>
/**
* Specify [select](https://payloadcms.com/docs/queries/select) to control which fields to include to the result.
*/
select?: TSelect
/**
* Opt-in to receiving hidden fields. By default, they are hidden from returned documents in accordance to your config.
* @default false
*/
showHiddenFields?: boolean
/**
* the Global slug to operate against.
*/
slug: TSlug
/**
* If you set `overrideAccess` to `false`, you can pass a user to use against the access control checks.
*/
user?: Document
} & Pick<GlobalFindOneArgs, 'flattenLocales'>
export async function findOneGlobalLocal<
TSlug extends GlobalSlug,
TSelect extends SelectFromGlobalSlug<TSlug>,
>(
payload: Payload,
options: Options<TSlug, TSelect>,
): Promise<TransformGlobalWithSelect<TSlug, TSelect>> {
const {
slug: globalSlug,
data,
depth,
draft = false,
flattenLocales,
includeLockStatus,
overrideAccess = true,
populate,
select,
showHiddenFields,
} = options
const globalConfig = payload.globals.config.find((config) => config.slug === globalSlug)
if (!globalConfig) {
throw new APIError(`The global with slug ${String(globalSlug)} can't be found.`)
}
return findOneOperation({
slug: globalSlug as string,
data,
depth,
draft,
flattenLocales,
globalConfig,
includeLockStatus,
overrideAccess,
populate,
req: await createLocalReq(options as CreateLocalReqOptions, payload),
select,
showHiddenFields,
})
}