From 096f041d010b1f11008ebf939eefb8a0a0cf0ffd Mon Sep 17 00:00:00 2001 From: Jake Fletcher Date: Wed, 23 Sep 2026 14:46:01 -0400 Subject: [PATCH 01/15] refactor!: replace createLocalReq with createPayloadReq --- docs/jobs-queue/jobs.mdx | 2 +- packages/drizzle/src/migrate.ts | 4 +- packages/drizzle/src/migrateDown.ts | 4 +- packages/drizzle/src/migrateFresh.ts | 4 +- packages/drizzle/src/migrateRefresh.ts | 4 +- packages/drizzle/src/migrateReset.ts | 4 +- packages/payload/src/admin/initReq.ts | 30 ++- .../payload/src/auth/operations/local/auth.ts | 4 +- .../auth/operations/local/forgotPassword.ts | 4 +- .../src/auth/operations/local/login.ts | 4 +- .../auth/operations/local/resetPassword.ts | 4 +- .../src/auth/operations/local/unlock.ts | 4 +- .../src/auth/operations/local/verifyEmail.ts | 4 +- .../payload/src/auth/serverFunctions/login.ts | 4 +- .../src/auth/serverFunctions/logout.ts | 4 +- .../src/auth/serverFunctions/refresh.ts | 4 +- .../commands/collections/createDocuments.ts | 4 +- .../commands/collections/duplicateDocument.ts | 4 +- .../collections/getCollectionSchema.ts | 4 +- .../commands/collections/updateDocument.ts | 4 +- .../cli/commands/globals/getGlobalSchema.ts | 4 +- .../src/cli/commands/globals/updateGlobal.ts | 4 +- .../src/collections/operations/local/count.ts | 6 +- .../operations/local/countVersions.ts | 6 +- .../collections/operations/local/create.ts | 9 +- .../collections/operations/local/delete.ts | 6 +- .../collections/operations/local/duplicate.ts | 6 +- .../src/collections/operations/local/find.ts | 6 +- .../collections/operations/local/findByID.ts | 6 +- .../operations/local/findDistinct.ts | 6 +- .../operations/local/findVersionByID.ts | 6 +- .../operations/local/findVersions.ts | 6 +- .../operations/local/restoreVersion.ts | 6 +- .../collections/operations/local/update.ts | 9 +- .../src/database/migrations/migrate.ts | 4 +- .../src/database/migrations/migrateDown.ts | 4 +- .../src/database/migrations/migrateRefresh.ts | 4 +- .../src/database/migrations/migrateReset.ts | 4 +- .../globals/operations/local/countVersions.ts | 6 +- .../src/globals/operations/local/findOne.ts | 6 +- .../operations/local/findVersionByID.ts | 6 +- .../globals/operations/local/findVersions.ts | 6 +- .../operations/local/restoreVersion.ts | 6 +- .../src/globals/operations/local/update.ts | 6 +- packages/payload/src/index.ts | 2 +- .../src/kv/adapters/DatabaseKVAdapter.ts | 2 +- .../src/query-presets/preventLockout.ts | 20 +- packages/payload/src/queues/localAPI.ts | 14 +- .../src/utilities/createLocalReq.spec.ts | 125 ----------- .../src/utilities/createPayloadReq.spec.ts | 210 ++++++++++++++++++ ...{createLocalReq.ts => createPayloadReq.ts} | 27 ++- .../utilities/isolateObjectProperty.spec.ts | 2 +- packages/plugin-mcp/src/mcp/buildMcpServer.ts | 2 +- .../src/mcp/builtin/collections/authTools.ts | 4 +- packages/plugin-mcp/src/stdio.ts | 4 +- .../converters/utilities/payloadPopulateFn.ts | 4 +- packages/ui/src/views/NotFound/page.tsx | 4 +- packages/ui/src/views/Root/index.tsx | 4 +- .../src/app/(app)/next/seed/route.ts | 4 +- .../src/app/(frontend)/next/seed/route.ts | 4 +- .../src/app/(frontend)/next/seed/route.ts | 4 +- test/access-control/int.spec.ts | 52 ++--- test/access-control/postgres-logs.int.spec.ts | 24 +- test/auth/int.spec.ts | 20 +- test/base-access/int.spec.ts | 16 +- test/dataloader/int.spec.ts | 4 +- test/field-access-context/int.spec.ts | 4 +- test/fields/int.spec.ts | 16 +- test/form-state/int.spec.ts | 24 +- test/localization/int.spec.ts | 20 +- test/queues/int.spec.ts | 22 +- test/queues/utilities.ts | 4 +- test/versions/int.spec.ts | 18 +- 73 files changed, 489 insertions(+), 413 deletions(-) delete mode 100644 packages/payload/src/utilities/createLocalReq.spec.ts create mode 100644 packages/payload/src/utilities/createPayloadReq.spec.ts rename packages/payload/src/utilities/{createLocalReq.ts => createPayloadReq.ts} (92%) diff --git a/docs/jobs-queue/jobs.mdx b/docs/jobs-queue/jobs.mdx index 1f541c82b89..43adc7cded1 100644 --- a/docs/jobs-queue/jobs.mdx +++ b/docs/jobs-queue/jobs.mdx @@ -289,7 +289,7 @@ These functions receive the current `req` and return a boolean. By default, they Omit `overrideAccess` or pass `overrideAccess: false` to run them from the Local API: ```ts -const req = await createLocalReq({ user }, payload) +const req = await createPayloadReq({ payload, user }) await payload.jobs.queue({ workflow: 'createPost', diff --git a/packages/drizzle/src/migrate.ts b/packages/drizzle/src/migrate.ts index 27c9018893c..a72a878b44b 100644 --- a/packages/drizzle/src/migrate.ts +++ b/packages/drizzle/src/migrate.ts @@ -2,7 +2,7 @@ import type { MigrationResult, Payload } from 'payload' import { commitTransaction, - createLocalReq, + createPayloadReq, initTransaction, killTransaction, readMigrationFiles, @@ -98,7 +98,7 @@ export const migrate: DrizzleAdapter['migrate'] = async function migrate( async function runMigrationFile(payload: Payload, migration: Migration, batch: number) { const start = Date.now() - const req = await createLocalReq({}, payload) + const req = await createPayloadReq({ payload }) payload.logger.info({ msg: `Migrating: ${migration.name}` }) diff --git a/packages/drizzle/src/migrateDown.ts b/packages/drizzle/src/migrateDown.ts index 1ad5554bed2..2c3ee85dc66 100644 --- a/packages/drizzle/src/migrateDown.ts +++ b/packages/drizzle/src/migrateDown.ts @@ -2,7 +2,7 @@ import type { MigrationResult } from 'payload' import { commitTransaction, - createLocalReq, + createPayloadReq, getMigrations, initTransaction, killTransaction, @@ -42,7 +42,7 @@ export async function migrateDown(this: DrizzleAdapter): Promise[0] + overrides?: Omit[0], 'payload'> requestURL?: string serverAdapter: ServerAdapter } @@ -121,22 +121,20 @@ export async function initReq({ const userOverride = hasOptionsUserOverride ? optionsOverrides.user : reqOverrides?.user const requestDefaults = getRequestDefaults({ requestURL }) - const req = await createLocalReq( - { - req: { - headers, - host: headers.get('host') ?? undefined, - i18n: i18n as I18n, - responseHeaders, - server: serverAdapter, - user, - ...requestDefaults, - ...(reqOverrides || {}), - }, - ...(optionsOverrides || {}), + const req = await createPayloadReq({ + req: { + headers, + host: headers.get('host') ?? undefined, + i18n: i18n as I18n, + responseHeaders, + server: serverAdapter, + user, + ...requestDefaults, + ...(reqOverrides || {}), }, + ...(optionsOverrides || {}), payload, - ) + }) if (hasUserOverride && userOverride == null) { req.user = null diff --git a/packages/payload/src/auth/operations/local/auth.ts b/packages/payload/src/auth/operations/local/auth.ts index 03d8b53f929..f5a5480c72a 100644 --- a/packages/payload/src/auth/operations/local/auth.ts +++ b/packages/payload/src/auth/operations/local/auth.ts @@ -1,7 +1,7 @@ import type { Payload } from '../../../index.js' import type { AuthArgs, AuthResult } from '../auth.js' -import { createLocalReq } from '../../../utilities/createLocalReq.js' +import { createPayloadReq } from '../../../utilities/createPayloadReq.js' import { auth as authOperation } from '../auth.js' export const authLocal = async (payload: Payload, options: AuthArgs): Promise => { @@ -10,6 +10,6 @@ export const authLocal = async (payload: Payload, options: AuthArgs): Promise = { @@ -46,6 +46,6 @@ export async function forgotPasswordLocal( disableEmail, expiration, overrideAccess, - req: await createLocalReq(options, payload), + req: await createPayloadReq({ ...options, payload }), }) as Promise } diff --git a/packages/payload/src/auth/operations/local/login.ts b/packages/payload/src/auth/operations/local/login.ts index 87c63cffbd6..192bbb22010 100644 --- a/packages/payload/src/auth/operations/local/login.ts +++ b/packages/payload/src/auth/operations/local/login.ts @@ -9,7 +9,7 @@ import type { SharedLocalAPIOptions } from '../../../types/operations.js' import type { LoginResult } from '../login.js' import { APIError } from '../../../errors/index.js' -import { createLocalReq } from '../../../utilities/createLocalReq.js' +import { createPayloadReq } from '../../../utilities/createPayloadReq.js' import { loginOperation } from '../login.js' export type Options = { @@ -49,7 +49,7 @@ export async function loginLocal( data, depth, overrideAccess, - req: await createLocalReq(options, payload), + req: await createPayloadReq({ ...options, payload }), showHiddenFields, } diff --git a/packages/payload/src/auth/operations/local/resetPassword.ts b/packages/payload/src/auth/operations/local/resetPassword.ts index 6ca95878b57..75bc4de9b95 100644 --- a/packages/payload/src/auth/operations/local/resetPassword.ts +++ b/packages/payload/src/auth/operations/local/resetPassword.ts @@ -3,7 +3,7 @@ import type { PayloadRequest } from '../../../types/index.js' import type { Result } from '../resetPassword.js' import { APIError } from '../../../errors/index.js' -import { createLocalReq } from '../../../utilities/createLocalReq.js' +import { createPayloadReq } from '../../../utilities/createPayloadReq.js' import { resetPasswordOperation } from '../resetPassword.js' export type Options = { @@ -37,7 +37,7 @@ export async function resetPasswordLocal( collection, data, overrideAccess, - req: await createLocalReq(options, payload), + req: await createPayloadReq({ ...options, payload }), }) if (collection.config.auth.removeTokenFromResponses) { diff --git a/packages/payload/src/auth/operations/local/unlock.ts b/packages/payload/src/auth/operations/local/unlock.ts index dfb211420f2..3bc0d517fa2 100644 --- a/packages/payload/src/auth/operations/local/unlock.ts +++ b/packages/payload/src/auth/operations/local/unlock.ts @@ -8,7 +8,7 @@ import type { PayloadRequest } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' import { APIError } from '../../../errors/index.js' -import { createLocalReq } from '../../../utilities/createLocalReq.js' +import { createPayloadReq } from '../../../utilities/createPayloadReq.js' import { unlockOperation } from '../unlock.js' export type Options = { @@ -36,6 +36,6 @@ export async function unlockLocal( collection, data, overrideAccess, - req: await createLocalReq(options, payload), + req: await createPayloadReq({ ...options, payload }), }) } diff --git a/packages/payload/src/auth/operations/local/verifyEmail.ts b/packages/payload/src/auth/operations/local/verifyEmail.ts index 80fa69eddc2..0d9794d49aa 100644 --- a/packages/payload/src/auth/operations/local/verifyEmail.ts +++ b/packages/payload/src/auth/operations/local/verifyEmail.ts @@ -2,7 +2,7 @@ import type { AuthCollectionSlug, Payload, RequestContext } from '../../../index import type { PayloadRequest } from '../../../types/index.js' import { APIError } from '../../../errors/index.js' -import { createLocalReq } from '../../../utilities/createLocalReq.js' +import { createPayloadReq } from '../../../utilities/createPayloadReq.js' import { verifyEmailOperation } from '../verifyEmail.js' export type Options = { @@ -28,7 +28,7 @@ export async function verifyEmailLocal( return verifyEmailOperation({ collection, - req: await createLocalReq(options, payload), + req: await createPayloadReq({ ...options, payload }), token, }) } diff --git a/packages/payload/src/auth/serverFunctions/login.ts b/packages/payload/src/auth/serverFunctions/login.ts index a24dc5379dc..ae238247482 100644 --- a/packages/payload/src/auth/serverFunctions/login.ts +++ b/packages/payload/src/auth/serverFunctions/login.ts @@ -5,7 +5,7 @@ import type { MaybePromise } from '../../types/index.js' import type { LoginResult } from '../operations/login.js' import { getPayload } from '../../index.js' -import { createLocalReq } from '../../utilities/createLocalReq.js' +import { createPayloadReq } from '../../utilities/createPayloadReq.js' import { loginWithCookie } from '../loginWithCookie.js' import { applyAuthCookie } from './cookies.js' @@ -80,7 +80,7 @@ export async function login({ collection: collectionConfig, data: loginData as Parameters[0]['data'], overrideAccess: true, - req: await createLocalReq({}, payload), + req: await createPayloadReq({ payload }), returnCookieAsObject: true, }) diff --git a/packages/payload/src/auth/serverFunctions/logout.ts b/packages/payload/src/auth/serverFunctions/logout.ts index c1a5dfa5944..8948d3b7a58 100644 --- a/packages/payload/src/auth/serverFunctions/logout.ts +++ b/packages/payload/src/auth/serverFunctions/logout.ts @@ -3,7 +3,7 @@ import type { SanitizedConfig } from '../../config/types.js' import type { MaybePromise } from '../../types/index.js' import { getPayload } from '../../index.js' -import { createLocalReq } from '../../utilities/createLocalReq.js' +import { createPayloadReq } from '../../utilities/createPayloadReq.js' import { logoutOperation } from '../operations/logout.js' import { clearAuthCookie } from './cookies.js' @@ -31,7 +31,7 @@ export async function logout({ } const { user } = authResult - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) const collection = payload.collections[user.collection] if (!collection) { diff --git a/packages/payload/src/auth/serverFunctions/refresh.ts b/packages/payload/src/auth/serverFunctions/refresh.ts index 0eef9cf1385..a7d3746898e 100644 --- a/packages/payload/src/auth/serverFunctions/refresh.ts +++ b/packages/payload/src/auth/serverFunctions/refresh.ts @@ -4,7 +4,7 @@ import type { CollectionSlug } from '../../index.js' import type { MaybePromise } from '../../types/index.js' import { getPayload } from '../../index.js' -import { createLocalReq } from '../../utilities/createLocalReq.js' +import { createPayloadReq } from '../../utilities/createPayloadReq.js' import { refreshOperation } from '../operations/refresh.js' import { getExistingAuthToken, setAuthCookie } from './cookies.js' @@ -46,7 +46,7 @@ export async function refresh({ throw new Error(`No auth config found for collection: ${collection}`) } - const req = await createLocalReq({ user: result.user }, payload) + const req = await createPayloadReq({ payload, user: result.user }) const refreshResult = await refreshOperation({ collection: collectionConfig, diff --git a/packages/payload/src/cli/commands/collections/createDocuments.ts b/packages/payload/src/cli/commands/collections/createDocuments.ts index a9b7e93d23c..c61f318390d 100644 --- a/packages/payload/src/cli/commands/collections/createDocuments.ts +++ b/packages/payload/src/cli/commands/collections/createDocuments.ts @@ -1,7 +1,7 @@ import type { EntityInputSchema } from '../../../utilities/entityInputSchema/types.js' import { createDocumentsLocalInputSchema } from '../../../collections/operations/inputSchemas.js' -import { createLocalReq } from '../../../utilities/createLocalReq.js' +import { createPayloadReq } from '../../../utilities/createPayloadReq.js' import { hasDraftValidationEnabled } from '../../../utilities/getVersionsConfig.js' import { defineCLICommand } from '../../defineCLICommand.js' import { @@ -40,7 +40,7 @@ export const createCreateDocumentsCommand = defineCLICommand({ const docs: Array<{ doc: unknown; index: number } | { id: number | string; index: number }> = [] const errors: Array<{ index: number; issues?: unknown[]; message: string }> = [] let schema: EntityInputSchema | undefined - const req = await createLocalReq({}, payload) + const req = await createPayloadReq({ payload }) const shouldUsePartialSchema = args.draft === true && collectionConfig !== undefined && diff --git a/packages/payload/src/cli/commands/collections/duplicateDocument.ts b/packages/payload/src/cli/commands/collections/duplicateDocument.ts index 457843cada4..39650ad3219 100644 --- a/packages/payload/src/cli/commands/collections/duplicateDocument.ts +++ b/packages/payload/src/cli/commands/collections/duplicateDocument.ts @@ -1,5 +1,5 @@ import { duplicateDocumentLocalInputSchema } from '../../../collections/operations/inputSchemas.js' -import { createLocalReq } from '../../../utilities/createLocalReq.js' +import { createPayloadReq } from '../../../utilities/createPayloadReq.js' import { parseDocumentID } from '../../../utilities/parseDocumentID.js' import { defineCLICommand } from '../../defineCLICommand.js' import { @@ -33,7 +33,7 @@ export const createDuplicateDocumentCommand = defineCLICommand({ const inputData = args.data ? stripCollectionVirtualFields({ collection, data: args.data, payload }) : undefined - const req = await createLocalReq({}, payload) + const req = await createPayloadReq({ payload }) let result try { diff --git a/packages/payload/src/cli/commands/collections/getCollectionSchema.ts b/packages/payload/src/cli/commands/collections/getCollectionSchema.ts index 9493485ced9..8da4f0b45fd 100644 --- a/packages/payload/src/cli/commands/collections/getCollectionSchema.ts +++ b/packages/payload/src/cli/commands/collections/getCollectionSchema.ts @@ -1,5 +1,5 @@ import { getCollectionSchemaInputSchema } from '../../../collections/operations/inputSchemas.js' -import { createLocalReq } from '../../../utilities/createLocalReq.js' +import { createPayloadReq } from '../../../utilities/createPayloadReq.js' import { getCollectionInputSchema } from '../../../utilities/entityInputSchema/getEntityInputSchema.js' import { defineCLICommand } from '../../defineCLICommand.js' import { printJSON } from '../data/utilities.js' @@ -9,7 +9,7 @@ export const createGetCollectionSchemaCommand = defineCLICommand({ handler: async ({ args, getPayload, isJSON }) => { const payload = await getPayload() const slug = args.slug - const req = await createLocalReq({}, payload) + const req = await createPayloadReq({ payload }) const schema = getCollectionInputSchema({ collectionSlug: slug, req }) if (!schema) { diff --git a/packages/payload/src/cli/commands/collections/updateDocument.ts b/packages/payload/src/cli/commands/collections/updateDocument.ts index f0c84d1a3cd..ac4fbfeb032 100644 --- a/packages/payload/src/cli/commands/collections/updateDocument.ts +++ b/packages/payload/src/cli/commands/collections/updateDocument.ts @@ -1,5 +1,5 @@ import { updateDocumentLocalInputSchema } from '../../../collections/operations/inputSchemas.js' -import { createLocalReq } from '../../../utilities/createLocalReq.js' +import { createPayloadReq } from '../../../utilities/createPayloadReq.js' import { parseDocumentID } from '../../../utilities/parseDocumentID.js' import { defineCLICommand } from '../../defineCLICommand.js' import { @@ -37,7 +37,7 @@ export const createUpdateDocumentCommand = defineCLICommand({ const payload = await getPayload() const collection = args.slug const inputData = stripCollectionVirtualFields({ collection, data: args.data, payload }) - const req = await createLocalReq({}, payload) + const req = await createPayloadReq({ payload }) try { validateCollectionData({ slug: collection, diff --git a/packages/payload/src/cli/commands/globals/getGlobalSchema.ts b/packages/payload/src/cli/commands/globals/getGlobalSchema.ts index 630caddf356..93a794c3dc7 100644 --- a/packages/payload/src/cli/commands/globals/getGlobalSchema.ts +++ b/packages/payload/src/cli/commands/globals/getGlobalSchema.ts @@ -1,5 +1,5 @@ import { getGlobalSchemaInputSchema } from '../../../globals/operations/inputSchemas.js' -import { createLocalReq } from '../../../utilities/createLocalReq.js' +import { createPayloadReq } from '../../../utilities/createPayloadReq.js' import { getGlobalInputSchema } from '../../../utilities/entityInputSchema/getEntityInputSchema.js' import { defineCLICommand } from '../../defineCLICommand.js' import { printJSON } from '../data/utilities.js' @@ -9,7 +9,7 @@ export const createGetGlobalSchemaCommand = defineCLICommand({ handler: async ({ args, getPayload, isJSON }) => { const payload = await getPayload() const slug = args.slug - const req = await createLocalReq({}, payload) + const req = await createPayloadReq({ payload }) const schema = getGlobalInputSchema({ globalSlug: slug, req }) if (!schema) { diff --git a/packages/payload/src/cli/commands/globals/updateGlobal.ts b/packages/payload/src/cli/commands/globals/updateGlobal.ts index c627a155b3e..fd04c5c08b4 100644 --- a/packages/payload/src/cli/commands/globals/updateGlobal.ts +++ b/packages/payload/src/cli/commands/globals/updateGlobal.ts @@ -1,5 +1,5 @@ import { updateGlobalLocalInputSchema } from '../../../globals/operations/inputSchemas.js' -import { createLocalReq } from '../../../utilities/createLocalReq.js' +import { createPayloadReq } from '../../../utilities/createPayloadReq.js' import { defineCLICommand } from '../../defineCLICommand.js' import { parseBoolean, parseFallbackLocale, parseJSON } from '../data/input.js' import { @@ -23,7 +23,7 @@ export const createUpdateGlobalCommand = defineCLICommand({ const payload = await getPayload() const slug = args.slug const inputData = stripGlobalVirtualFields({ slug, data: args.data, payload }) - const req = await createLocalReq({}, payload) + const req = await createPayloadReq({ payload }) let result try { diff --git a/packages/payload/src/collections/operations/local/count.ts b/packages/payload/src/collections/operations/local/count.ts index b603340bf76..2fb0cc41893 100644 --- a/packages/payload/src/collections/operations/local/count.ts +++ b/packages/payload/src/collections/operations/local/count.ts @@ -1,10 +1,10 @@ import type { CollectionSlug, Payload, RequestContext, TypedLocale, User } from '../../../index.js' import type { PayloadRequest, Where } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' -import type { CreateLocalReqOptions } from '../../../utilities/createLocalReq.js' +import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' import { APIError } from '../../../errors/index.js' -import { createLocalReq } from '../../../utilities/createLocalReq.js' +import { createPayloadReq } from '../../../utilities/createPayloadReq.js' import { countOperation } from '../count.js' export type CountOptions = { @@ -75,7 +75,7 @@ export async function countLocal( collection, disableErrors, overrideAccess, - req: await createLocalReq(options as CreateLocalReqOptions, payload), + req: await createPayloadReq({ ...(options as Omit), payload }), trash, where, }) diff --git a/packages/payload/src/collections/operations/local/countVersions.ts b/packages/payload/src/collections/operations/local/countVersions.ts index e91c737a1e0..b9fff8d814c 100644 --- a/packages/payload/src/collections/operations/local/countVersions.ts +++ b/packages/payload/src/collections/operations/local/countVersions.ts @@ -1,10 +1,10 @@ import type { CollectionSlug, Payload, RequestContext, TypedLocale, User } from '../../../index.js' import type { PayloadRequest, Where } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' -import type { CreateLocalReqOptions } from '../../../utilities/createLocalReq.js' +import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' import { APIError } from '../../../errors/index.js' -import { createLocalReq } from '../../../utilities/createLocalReq.js' +import { createPayloadReq } from '../../../utilities/createPayloadReq.js' import { countVersionsOperation } from '../countVersions.js' export type CountVersionsOptions = { @@ -60,7 +60,7 @@ export async function countVersionsLocal( collection, disableErrors, overrideAccess, - req: await createLocalReq(options as CreateLocalReqOptions, payload), + req: await createPayloadReq({ ...(options as Omit), payload }), where, }) } diff --git a/packages/payload/src/collections/operations/local/create.ts b/packages/payload/src/collections/operations/local/create.ts index fbc51f019b1..1806d826123 100644 --- a/packages/payload/src/collections/operations/local/create.ts +++ b/packages/payload/src/collections/operations/local/create.ts @@ -6,7 +6,7 @@ import type { } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' import type { File } from '../../../uploads/types.js' -import type { CreateLocalReqOptions } from '../../../utilities/createLocalReq.js' +import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' import type { CollectionsWithoutDrafts, DataFromCollectionSlug, @@ -27,7 +27,7 @@ import { type User, } from '../../../index.js' import { getFileByPath } from '../../../uploads/getFileByPath.js' -import { createLocalReq } from '../../../utilities/createLocalReq.js' +import { createPayloadReq } from '../../../utilities/createPayloadReq.js' import { createOperation } from '../create.js' type BaseOptions = { @@ -211,7 +211,10 @@ export async function createLocal< ) } - const req = await createLocalReq(options as CreateLocalReqOptions, payload) + const req = await createPayloadReq({ + ...(options as Omit), + payload, + }) req.file = file ?? (await getFileByPath(filePath!)) diff --git a/packages/payload/src/collections/operations/local/delete.ts b/packages/payload/src/collections/operations/local/delete.ts index 23388efee23..dfe6adaa136 100644 --- a/packages/payload/src/collections/operations/local/delete.ts +++ b/packages/payload/src/collections/operations/local/delete.ts @@ -14,11 +14,11 @@ import type { Where, } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' -import type { CreateLocalReqOptions } from '../../../utilities/createLocalReq.js' +import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' import type { BulkOperationResult, SelectFromCollectionSlug } from '../../config/types.js' import { APIError } from '../../../errors/index.js' -import { createLocalReq } from '../../../utilities/createLocalReq.js' +import { createPayloadReq } from '../../../utilities/createPayloadReq.js' import { deleteOperation } from '../delete.js' import { deleteByIDOperation } from '../deleteByID.js' @@ -176,7 +176,7 @@ async function deleteLocal< overrideAccess, overrideLock, populate, - req: await createLocalReq(options as CreateLocalReqOptions, payload), + req: await createPayloadReq({ ...(options as Omit), payload }), select, showHiddenFields, trash, diff --git a/packages/payload/src/collections/operations/local/duplicate.ts b/packages/payload/src/collections/operations/local/duplicate.ts index 422a1d8ff28..af0c5e1de62 100644 --- a/packages/payload/src/collections/operations/local/duplicate.ts +++ b/packages/payload/src/collections/operations/local/duplicate.ts @@ -9,7 +9,7 @@ import type { TransformCollectionWithSelect, } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' -import type { CreateLocalReqOptions } from '../../../utilities/createLocalReq.js' +import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' import type { DraftFlagFromCollectionSlug, RequiredDataFromCollectionSlug, @@ -17,7 +17,7 @@ import type { } from '../../config/types.js' import { APIError } from '../../../errors/index.js' -import { createLocalReq } from '../../../utilities/createLocalReq.js' +import { createPayloadReq } from '../../../utilities/createPayloadReq.js' import { duplicateOperation } from '../duplicate.js' type BaseOptions = { @@ -125,7 +125,7 @@ export async function duplicateLocal< ) } - const req = await createLocalReq(options as CreateLocalReqOptions, payload) + const req = await createPayloadReq({ ...(options as Omit), payload }) return duplicateOperation({ id, diff --git a/packages/payload/src/collections/operations/local/find.ts b/packages/payload/src/collections/operations/local/find.ts index 8772f2b5013..75cdad49a1f 100644 --- a/packages/payload/src/collections/operations/local/find.ts +++ b/packages/payload/src/collections/operations/local/find.ts @@ -19,11 +19,11 @@ import type { Where, } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' -import type { CreateLocalReqOptions } from '../../../utilities/createLocalReq.js' +import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' import type { DraftFlagFromCollectionSlug, SelectFromCollectionSlug } from '../../config/types.js' import { APIError } from '../../../errors/index.js' -import { createLocalReq } from '../../../utilities/createLocalReq.js' +import { createPayloadReq } from '../../../utilities/createPayloadReq.js' import { findOperation } from '../find.js' type BaseFindOptions = { @@ -239,7 +239,7 @@ export async function findLocal< page, pagination, populate, - req: await createLocalReq(options as CreateLocalReqOptions, payload), + req: await createPayloadReq({ ...(options as Omit), payload }), select, showHiddenFields, sort, diff --git a/packages/payload/src/collections/operations/local/findByID.ts b/packages/payload/src/collections/operations/local/findByID.ts index 6e65873c27c..16cc0d38606 100644 --- a/packages/payload/src/collections/operations/local/findByID.ts +++ b/packages/payload/src/collections/operations/local/findByID.ts @@ -16,11 +16,11 @@ import type { TransformCollectionWithSelect, } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' -import type { CreateLocalReqOptions } from '../../../utilities/createLocalReq.js' +import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' import type { DraftFlagFromCollectionSlug, SelectFromCollectionSlug } from '../../config/types.js' import { APIError } from '../../../errors/index.js' -import { createLocalReq } from '../../../utilities/createLocalReq.js' +import { createPayloadReq } from '../../../utilities/createPayloadReq.js' import { type FindByIDArgs, findByIDOperation } from '../findByID.js' type BaseFindByIDOptions< @@ -163,7 +163,7 @@ export async function findByIDLocal< joins, overrideAccess, populate, - req: await createLocalReq(options as CreateLocalReqOptions, payload), + req: await createPayloadReq({ ...(options as Omit), payload }), select, showHiddenFields, trash, diff --git a/packages/payload/src/collections/operations/local/findDistinct.ts b/packages/payload/src/collections/operations/local/findDistinct.ts index 1eae5cd1961..359ea1cd34d 100644 --- a/packages/payload/src/collections/operations/local/findDistinct.ts +++ b/packages/payload/src/collections/operations/local/findDistinct.ts @@ -12,9 +12,9 @@ import type { Where, } from '../../../index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' -import type { CreateLocalReqOptions } from '../../../utilities/createLocalReq.js' +import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' -import { APIError, createLocalReq } from '../../../index.js' +import { APIError, createPayloadReq } from '../../../index.js' import { findDistinctOperation } from '../findDistinct.js' export type Options< @@ -135,7 +135,7 @@ export async function findDistinct< overrideAccess, page, populate, - req: await createLocalReq(options as CreateLocalReqOptions, payload), + req: await createPayloadReq({ ...(options as Omit), payload }), showHiddenFields, sort, trash, diff --git a/packages/payload/src/collections/operations/local/findVersionByID.ts b/packages/payload/src/collections/operations/local/findVersionByID.ts index 5f791bd00b7..b2362621921 100644 --- a/packages/payload/src/collections/operations/local/findVersionByID.ts +++ b/packages/payload/src/collections/operations/local/findVersionByID.ts @@ -8,12 +8,12 @@ import type { } from '../../../index.js' import type { PayloadRequest, PopulateType, SelectType } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' -import type { CreateLocalReqOptions } from '../../../utilities/createLocalReq.js' +import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' import type { TypeWithVersion } from '../../../versions/types.js' import type { DataFromCollectionSlug, DraftFlagFromCollectionSlug } from '../../config/types.js' import { APIError } from '../../../errors/index.js' -import { createLocalReq } from '../../../utilities/createLocalReq.js' +import { createPayloadReq } from '../../../utilities/createPayloadReq.js' import { findVersionByIDOperation } from '../findVersionByID.js' type BaseOptions = { @@ -115,7 +115,7 @@ export async function findVersionByIDLocal( disableErrors, overrideAccess, populate, - req: await createLocalReq(options as CreateLocalReqOptions, payload), + req: await createPayloadReq({ ...(options as Omit), payload }), select, showHiddenFields, trash, diff --git a/packages/payload/src/collections/operations/local/findVersions.ts b/packages/payload/src/collections/operations/local/findVersions.ts index 7648ced98fc..7c8e300fd34 100644 --- a/packages/payload/src/collections/operations/local/findVersions.ts +++ b/packages/payload/src/collections/operations/local/findVersions.ts @@ -9,12 +9,12 @@ import type { } from '../../../index.js' import type { PayloadRequest, PopulateType, SelectType, Sort, Where } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' -import type { CreateLocalReqOptions } from '../../../utilities/createLocalReq.js' +import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' import type { TypeWithVersion } from '../../../versions/types.js' import type { DataFromCollectionSlug, DraftFlagFromCollectionSlug } from '../../config/types.js' import { APIError } from '../../../errors/index.js' -import { createLocalReq } from '../../../utilities/createLocalReq.js' +import { createPayloadReq } from '../../../utilities/createPayloadReq.js' import { findVersionsOperation } from '../findVersions.js' type BaseOptions = { @@ -135,7 +135,7 @@ export async function findVersionsLocal( page, pagination, populate, - req: await createLocalReq(options as CreateLocalReqOptions, payload), + req: await createPayloadReq({ ...(options as Omit), payload }), select, showHiddenFields, sort, diff --git a/packages/payload/src/collections/operations/local/restoreVersion.ts b/packages/payload/src/collections/operations/local/restoreVersion.ts index 93568cb19fb..e82c8e2a7cf 100644 --- a/packages/payload/src/collections/operations/local/restoreVersion.ts +++ b/packages/payload/src/collections/operations/local/restoreVersion.ts @@ -8,11 +8,11 @@ import type { } from '../../../index.js' import type { PayloadRequest, PopulateType, SelectType } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' -import type { CreateLocalReqOptions } from '../../../utilities/createLocalReq.js' +import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' import type { DataFromCollectionSlug, DraftFlagFromCollectionSlug } from '../../config/types.js' import { APIError } from '../../../errors/index.js' -import { createLocalReq } from '../../../utilities/createLocalReq.js' +import { createPayloadReq } from '../../../utilities/createPayloadReq.js' import { restoreVersionOperation } from '../restoreVersion.js' type BaseOptions = { @@ -98,7 +98,7 @@ export async function restoreVersionLocal( overrideAccess, payload, populate, - req: await createLocalReq(options as CreateLocalReqOptions, payload), + req: await createPayloadReq({ ...(options as Omit), payload }), select, showHiddenFields, } diff --git a/packages/payload/src/collections/operations/local/update.ts b/packages/payload/src/collections/operations/local/update.ts index 59f8a245cb9..a170e7c4185 100644 --- a/packages/payload/src/collections/operations/local/update.ts +++ b/packages/payload/src/collections/operations/local/update.ts @@ -18,7 +18,7 @@ import type { } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' import type { File } from '../../../uploads/types.js' -import type { CreateLocalReqOptions } from '../../../utilities/createLocalReq.js' +import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' import type { BulkOperationResult, DraftFlagFromCollectionSlug, @@ -28,7 +28,7 @@ import type { import { APIError } from '../../../errors/index.js' import { getFileByPath } from '../../../uploads/getFileByPath.js' -import { createLocalReq } from '../../../utilities/createLocalReq.js' +import { createPayloadReq } from '../../../utilities/createPayloadReq.js' import { updateOperation } from '../update.js' import { updateByIDOperation } from '../updateByID.js' @@ -245,7 +245,10 @@ async function updateLocal< ) } - const req = await createLocalReq(options as CreateLocalReqOptions, payload) + const req = await createPayloadReq({ + ...(options as Omit), + payload, + }) req.file = file ?? (await getFileByPath(filePath!)) const args = { diff --git a/packages/payload/src/database/migrations/migrate.ts b/packages/payload/src/database/migrations/migrate.ts index 22184f52c1f..b8f7bcc0a97 100644 --- a/packages/payload/src/database/migrations/migrate.ts +++ b/packages/payload/src/database/migrations/migrate.ts @@ -1,7 +1,7 @@ import type { BaseDatabaseAdapter } from '../types.js' import { commitTransaction } from '../../utilities/commitTransaction.js' -import { createLocalReq } from '../../utilities/createLocalReq.js' +import { createPayloadReq } from '../../utilities/createPayloadReq.js' import { initTransaction } from '../../utilities/initTransaction.js' import { killTransaction } from '../../utilities/killTransaction.js' import { getMigrations } from './getMigrations.js' @@ -30,7 +30,7 @@ export const migrate: BaseDatabaseAdapter['migrate'] = async function migrate( } const start = Date.now() - const req = await createLocalReq({}, payload) + const req = await createPayloadReq({ payload }) payload.logger.info({ msg: `Migrating: ${migration.name}` }) diff --git a/packages/payload/src/database/migrations/migrateDown.ts b/packages/payload/src/database/migrations/migrateDown.ts index ba5fe5d819d..6fe242d8bc7 100644 --- a/packages/payload/src/database/migrations/migrateDown.ts +++ b/packages/payload/src/database/migrations/migrateDown.ts @@ -1,7 +1,7 @@ import type { BaseDatabaseAdapter, MigrationResult } from '../types.js' import { commitTransaction } from '../../utilities/commitTransaction.js' -import { createLocalReq } from '../../utilities/createLocalReq.js' +import { createPayloadReq } from '../../utilities/createPayloadReq.js' import { initTransaction } from '../../utilities/initTransaction.js' import { killTransaction } from '../../utilities/killTransaction.js' import { getMigrations } from './getMigrations.js' @@ -34,7 +34,7 @@ export async function migrateDown(this: BaseDatabaseAdapter): Promise = { @@ -60,7 +60,7 @@ export async function countGlobalVersionsLocal( disableErrors, global, overrideAccess, - req: await createLocalReq(options as CreateLocalReqOptions, payload), + req: await createPayloadReq({ ...(options as Omit), payload }), where, }) } diff --git a/packages/payload/src/globals/operations/local/findOne.ts b/packages/payload/src/globals/operations/local/findOne.ts index a3d72dbec17..866a5dd698f 100644 --- a/packages/payload/src/globals/operations/local/findOne.ts +++ b/packages/payload/src/globals/operations/local/findOne.ts @@ -14,11 +14,11 @@ import type { TransformGlobalWithSelect, } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' -import type { CreateLocalReqOptions } from '../../../utilities/createLocalReq.js' +import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' import type { DraftFlagFromGlobalSlug, SelectFromGlobalSlug } from '../../config/types.js' import { APIError } from '../../../errors/index.js' -import { createLocalReq } from '../../../utilities/createLocalReq.js' +import { createPayloadReq } from '../../../utilities/createPayloadReq.js' import { findOneOperation, type GlobalFindOneArgs } from '../findOne.js' type BaseFindOneOptions = { @@ -128,7 +128,7 @@ export async function findOneGlobalLocal< includeLockStatus, overrideAccess, populate, - req: await createLocalReq(options as CreateLocalReqOptions, payload), + req: await createPayloadReq({ ...(options as Omit), payload }), select, showHiddenFields, }) diff --git a/packages/payload/src/globals/operations/local/findVersionByID.ts b/packages/payload/src/globals/operations/local/findVersionByID.ts index 77bc365116c..4f79f342bbc 100644 --- a/packages/payload/src/globals/operations/local/findVersionByID.ts +++ b/packages/payload/src/globals/operations/local/findVersionByID.ts @@ -8,12 +8,12 @@ import type { } from '../../../index.js' import type { PayloadRequest, PopulateType, SelectType } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' -import type { CreateLocalReqOptions } from '../../../utilities/createLocalReq.js' +import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' import type { TypeWithVersion } from '../../../versions/types.js' import type { DataFromGlobalSlug } from '../../config/types.js' import { APIError } from '../../../errors/index.js' -import { createLocalReq } from '../../../utilities/createLocalReq.js' +import { createPayloadReq } from '../../../utilities/createPayloadReq.js' import { findVersionByIDOperation } from '../findVersionByID.js' export type Options = { @@ -99,7 +99,7 @@ export async function findGlobalVersionByIDLocal( globalConfig, overrideAccess, populate, - req: await createLocalReq(options as CreateLocalReqOptions, payload), + req: await createPayloadReq({ ...(options as Omit), payload }), select, showHiddenFields, }) diff --git a/packages/payload/src/globals/operations/local/findVersions.ts b/packages/payload/src/globals/operations/local/findVersions.ts index af17fe5f874..2f3ec80d66e 100644 --- a/packages/payload/src/globals/operations/local/findVersions.ts +++ b/packages/payload/src/globals/operations/local/findVersions.ts @@ -9,12 +9,12 @@ import type { } from '../../../index.js' import type { PayloadRequest, PopulateType, SelectType, Sort, Where } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' -import type { CreateLocalReqOptions } from '../../../utilities/createLocalReq.js' +import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' import type { TypeWithVersion } from '../../../versions/types.js' import type { DataFromGlobalSlug } from '../../config/types.js' import { APIError } from '../../../errors/index.js' -import { createLocalReq } from '../../../utilities/createLocalReq.js' +import { createPayloadReq } from '../../../utilities/createPayloadReq.js' import { findVersionsOperation } from '../findVersions.js' export type Options = { @@ -120,7 +120,7 @@ export async function findGlobalVersionsLocal( page, pagination, populate, - req: await createLocalReq(options as CreateLocalReqOptions, payload), + req: await createPayloadReq({ ...(options as Omit), payload }), select, showHiddenFields, sort, diff --git a/packages/payload/src/globals/operations/local/restoreVersion.ts b/packages/payload/src/globals/operations/local/restoreVersion.ts index 0e97205984b..73ce2f5b4a0 100644 --- a/packages/payload/src/globals/operations/local/restoreVersion.ts +++ b/packages/payload/src/globals/operations/local/restoreVersion.ts @@ -1,11 +1,11 @@ import type { GlobalSlug, Payload, RequestContext, TypedLocale, User } from '../../../index.js' import type { PayloadRequest, PopulateType } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' -import type { CreateLocalReqOptions } from '../../../utilities/createLocalReq.js' +import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' import type { DataFromGlobalSlug } from '../../config/types.js' import { APIError } from '../../../errors/index.js' -import { createLocalReq } from '../../../utilities/createLocalReq.js' +import { createPayloadReq } from '../../../utilities/createPayloadReq.js' import { restoreVersionOperation } from '../restoreVersion.js' export type Options = { @@ -81,7 +81,7 @@ export async function restoreGlobalVersionLocal( globalConfig, overrideAccess, populate, - req: await createLocalReq(options as CreateLocalReqOptions, payload), + req: await createPayloadReq({ ...(options as Omit), payload }), showHiddenFields, }) } diff --git a/packages/payload/src/globals/operations/local/update.ts b/packages/payload/src/globals/operations/local/update.ts index 3697ecde804..15bf7b33de4 100644 --- a/packages/payload/src/globals/operations/local/update.ts +++ b/packages/payload/src/globals/operations/local/update.ts @@ -7,7 +7,7 @@ import type { TransformGlobalWithSelect, } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' -import type { CreateLocalReqOptions } from '../../../utilities/createLocalReq.js' +import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' import type { DataFromGlobalSlug, DraftFlagFromGlobalSlug, @@ -24,7 +24,7 @@ import { type TypedLocale, type User, } from '../../../index.js' -import { createLocalReq } from '../../../utilities/createLocalReq.js' +import { createPayloadReq } from '../../../utilities/createPayloadReq.js' import { updateOperation } from '../update.js' type BaseOptions = { @@ -137,7 +137,7 @@ export async function updateGlobalLocal< overrideLock, populate, publishAllLocales, - req: await createLocalReq(options as CreateLocalReqOptions, payload), + req: await createPayloadReq({ ...(options as Omit), payload }), select, showHiddenFields, unpublishAllLocales, diff --git a/packages/payload/src/index.ts b/packages/payload/src/index.ts index cc5dbf7a110..47d6181e6aa 100644 --- a/packages/payload/src/index.ts +++ b/packages/payload/src/index.ts @@ -2020,7 +2020,7 @@ export { withNullableJSONSchemaType, } from './utilities/configToJSONSchema.js' export { createArrayFromCommaDelineated } from './utilities/createArrayFromCommaDelineated.js' -export { createLocalReq } from './utilities/createLocalReq.js' +export { createPayloadReq, type CreatePayloadReqArgs } from './utilities/createPayloadReq.js' export { createPayloadRequest } from './utilities/createPayloadRequest.js' export { deepCopyObject, diff --git a/packages/payload/src/kv/adapters/DatabaseKVAdapter.ts b/packages/payload/src/kv/adapters/DatabaseKVAdapter.ts index c1ade3625a9..4841235e869 100644 --- a/packages/payload/src/kv/adapters/DatabaseKVAdapter.ts +++ b/packages/payload/src/kv/adapters/DatabaseKVAdapter.ts @@ -2,7 +2,7 @@ import type { CollectionConfig } from '../../index.js' import type { Payload, PayloadRequest } from '../../types/index.js' import type { KVAdapter, KVAdapterResult, KVStoreValue } from '../index.js' -/** Mocked `req`, we don't need to use transactions, neither we want `createLocalReq` overhead. */ +/** Mocked `req`, we don't need to use transactions, neither we want `createPayloadReq` overhead. */ const req = {} as PayloadRequest export class DatabaseKVAdapter implements KVAdapter { diff --git a/packages/payload/src/query-presets/preventLockout.ts b/packages/payload/src/query-presets/preventLockout.ts index cfe0810a672..01e78c4a640 100644 --- a/packages/payload/src/query-presets/preventLockout.ts +++ b/packages/payload/src/query-presets/preventLockout.ts @@ -1,7 +1,7 @@ import type { Validate } from '../fields/config/types.js' import { APIError } from '../errors/APIError.js' -import { createLocalReq } from '../utilities/createLocalReq.js' +import { createPayloadReq } from '../utilities/createPayloadReq.js' import { initTransaction } from '../utilities/initTransaction.js' import { killTransaction } from '../utilities/killTransaction.js' import { queryPresetsCollectionSlug } from './config.js' @@ -21,17 +21,15 @@ export const preventLockout: Validate = async ( ) => { // Use context to ensure an infinite loop doesn't occur if (!incomingReq.context._preventLockout && !overrideAccess) { - const req = await createLocalReq( - { - context: { - _preventLockout: true, - }, - req: { - user: incomingReq.user, - }, + const req = await createPayloadReq({ + payload: incomingReq.payload, + context: { + _preventLockout: true, }, - incomingReq.payload, - ) + req: { + user: incomingReq.user, + }, + }) // Might be `null` if no transactions are enabled const transaction = await initTransaction(req) diff --git a/packages/payload/src/queues/localAPI.ts b/packages/payload/src/queues/localAPI.ts index 359ff426d34..e4c41995f1c 100644 --- a/packages/payload/src/queues/localAPI.ts +++ b/packages/payload/src/queues/localAPI.ts @@ -2,7 +2,7 @@ import type { SharedLocalAPIOptions } from '../types/operations.js' import type { JobFromTask } from './config/types/workflowTypes.js' import { - createLocalReq, + createPayloadReq, Forbidden, type Job, type Payload, @@ -42,7 +42,7 @@ export const getJobsLocalAPI = (payload: Payload) => ({ queue?: string req?: PayloadRequest }): Promise => { - const newReq: PayloadRequest = args?.req ?? (await createLocalReq({}, payload)) + const newReq: PayloadRequest = args?.req ?? (await createPayloadReq({ payload })) return await handleSchedules({ allQueues: args?.allQueues, @@ -95,7 +95,7 @@ export const getJobsLocalAPI = (payload: Payload) => ({ : JobFromTask > => { const overrideAccess = args.overrideAccess ?? false - const req: PayloadRequest = args.req ?? (await createLocalReq({}, payload)) + const req: PayloadRequest = args.req ?? (await createPayloadReq({ payload })) if (!overrideAccess) { /** @@ -249,7 +249,7 @@ export const getJobsLocalAPI = (payload: Payload) => ({ where?: Where } & Pick, ): Promise> => { - const newReq: PayloadRequest = args?.req ?? (await createLocalReq({}, payload)) + const newReq: PayloadRequest = args?.req ?? (await createPayloadReq({ payload })) return await runJobs({ allQueues: args?.allQueues, @@ -279,7 +279,7 @@ export const getJobsLocalAPI = (payload: Payload) => ({ silent?: RunJobsSilent } & Pick, ): Promise> => { - const newReq: PayloadRequest = args.req ?? (await createLocalReq({}, payload)) + const newReq: PayloadRequest = args.req ?? (await createPayloadReq({ payload })) return await runJobs({ id: args.id, @@ -296,7 +296,7 @@ export const getJobsLocalAPI = (payload: Payload) => ({ where: Where } & Pick, ): Promise => { - const req: PayloadRequest = args.req ?? (await createLocalReq({}, payload)) + const req: PayloadRequest = args.req ?? (await createPayloadReq({ payload })) const overrideAccess = args.overrideAccess ?? false if (!overrideAccess) { @@ -354,7 +354,7 @@ export const getJobsLocalAPI = (payload: Payload) => ({ req?: PayloadRequest } & Pick, ): Promise => { - const req: PayloadRequest = args.req ?? (await createLocalReq({}, payload)) + const req: PayloadRequest = args.req ?? (await createPayloadReq({ payload })) const overrideAccess = args.overrideAccess ?? false if (!overrideAccess) { diff --git a/packages/payload/src/utilities/createLocalReq.spec.ts b/packages/payload/src/utilities/createLocalReq.spec.ts deleted file mode 100644 index 9ba39b015ce..00000000000 --- a/packages/payload/src/utilities/createLocalReq.spec.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' - -import type { Payload } from '../index.js' - -import { createLocalReq } from './createLocalReq.js' - -describe('createLocalReq - URL construction', () => { - const mockPayload = { - config: { - serverURL: undefined, - i18n: { - fallbackLanguage: 'en', - supportedLanguages: { en: {} }, - translations: {}, - }, - localization: undefined, - }, - logger: { - error: vi.fn(), - }, - } as unknown as Payload - - it('should use req.url when provided and serverURL is undefined', async () => { - const req = { - url: 'http://example.com/api/test', - } - - const result = await createLocalReq({ req }, mockPayload) - - expect(result.url).toBe('http://example.com/api/test') - expect(mockPayload.logger.error).not.toHaveBeenCalled() - }) - - it('should use serverURL when req.url is not provided', async () => { - const payloadWithServerURL = { - config: { - serverURL: 'http://configured-server.com', - i18n: { - fallbackLanguage: 'en', - supportedLanguages: { en: {} }, - translations: {}, - }, - localization: undefined, - }, - logger: { - error: vi.fn(), - }, - } as unknown as Payload - - const req = {} - - const result = await createLocalReq({ req, urlSuffix: '/api' }, payloadWithServerURL) - - expect(result.url).toContain('http://configured-server.com/api') - expect(payloadWithServerURL.logger.error).not.toHaveBeenCalled() - }) - - it('should prioritize req.url over serverURL', async () => { - const payloadWithServerURL = { - config: { - serverURL: 'http://configured-server.com', - i18n: { - fallbackLanguage: 'en', - supportedLanguages: { en: {} }, - translations: {}, - }, - localization: undefined, - }, - logger: { - error: vi.fn(), - }, - } as unknown as Payload - - const req = { - url: 'http://actual-request.com/api/test', - } - - const result = await createLocalReq({ req }, payloadWithServerURL) - - expect(result.url).toBe('http://actual-request.com/api/test') - expect(payloadWithServerURL.logger.error).not.toHaveBeenCalled() - }) - - it('should fall back to localhost when neither req.url nor serverURL provided', async () => { - const req = {} - - const result = await createLocalReq({ req }, mockPayload) - - expect(result.url).toBe('http://localhost/') - expect(mockPayload.logger.error).not.toHaveBeenCalled() - }) - - it('should append urlSuffix to serverURL when used', async () => { - const payloadWithServerURL = { - config: { - serverURL: 'http://configured-server.com', - i18n: { - fallbackLanguage: 'en', - supportedLanguages: { en: {} }, - translations: {}, - }, - localization: undefined, - }, - logger: { - error: vi.fn(), - }, - } as unknown as Payload - - const req = {} - - const result = await createLocalReq({ req, urlSuffix: '/api/preview' }, payloadWithServerURL) - - expect(result.url).toContain('/api/preview') - expect(payloadWithServerURL.logger.error).not.toHaveBeenCalled() - }) - - it('should append urlSuffix to fallback URL when neither req.url nor serverURL provided', async () => { - const req = {} - - const result = await createLocalReq({ req, urlSuffix: '/api/test' }, mockPayload) - - expect(result.url).toBe('http://localhost/api/test') - expect(mockPayload.logger.error).not.toHaveBeenCalled() - }) -}) diff --git a/packages/payload/src/utilities/createPayloadReq.spec.ts b/packages/payload/src/utilities/createPayloadReq.spec.ts new file mode 100644 index 00000000000..f50bbe15999 --- /dev/null +++ b/packages/payload/src/utilities/createPayloadReq.spec.ts @@ -0,0 +1,210 @@ +import { describe, expect, it, vi } from 'vitest' + +import type { Payload, PayloadRequest } from '../index.js' + +import { createPayloadReq } from './createPayloadReq.js' + +describe('createPayloadReq', () => { + const mockPayload = { + config: { + serverURL: undefined, + i18n: { + fallbackLanguage: 'en', + supportedLanguages: { en: {} }, + translations: {}, + }, + localization: undefined, + }, + logger: { + error: vi.fn(), + }, + } as unknown as Payload + + it('should use req.url when provided and serverURL is undefined', async () => { + const req = { + url: 'http://example.com/api/test', + } + + const result = await createPayloadReq({ payload: mockPayload, req }) + + expect(result.url).toBe('http://example.com/api/test') + expect(mockPayload.logger.error).not.toHaveBeenCalled() + }) + + it('should use serverURL when req.url is not provided', async () => { + const payloadWithServerURL = { + config: { + serverURL: 'http://configured-server.com', + i18n: { + fallbackLanguage: 'en', + supportedLanguages: { en: {} }, + translations: {}, + }, + localization: undefined, + }, + logger: { + error: vi.fn(), + }, + } as unknown as Payload + + const req = {} + + const result = await createPayloadReq({ + payload: payloadWithServerURL, + req, + urlSuffix: '/api', + }) + + expect(result.url).toContain('http://configured-server.com/api') + expect(payloadWithServerURL.logger.error).not.toHaveBeenCalled() + }) + + it('should prioritize req.url over serverURL', async () => { + const payloadWithServerURL = { + config: { + serverURL: 'http://configured-server.com', + i18n: { + fallbackLanguage: 'en', + supportedLanguages: { en: {} }, + translations: {}, + }, + localization: undefined, + }, + logger: { + error: vi.fn(), + }, + } as unknown as Payload + + const req = { + url: 'http://actual-request.com/api/test', + } + + const result = await createPayloadReq({ payload: payloadWithServerURL, req }) + + expect(result.url).toBe('http://actual-request.com/api/test') + expect(payloadWithServerURL.logger.error).not.toHaveBeenCalled() + }) + + it('should fall back to localhost when neither req.url nor serverURL provided', async () => { + const req = {} + + const result = await createPayloadReq({ payload: mockPayload, req }) + + expect(result.url).toBe('http://localhost/') + expect(mockPayload.logger.error).not.toHaveBeenCalled() + }) + + it('should append urlSuffix to serverURL when used', async () => { + const payloadWithServerURL = { + config: { + serverURL: 'http://configured-server.com', + i18n: { + fallbackLanguage: 'en', + supportedLanguages: { en: {} }, + translations: {}, + }, + localization: undefined, + }, + logger: { + error: vi.fn(), + }, + } as unknown as Payload + + const req = {} + + const result = await createPayloadReq({ + payload: payloadWithServerURL, + req, + urlSuffix: '/api/preview', + }) + + expect(result.url).toContain('/api/preview') + expect(payloadWithServerURL.logger.error).not.toHaveBeenCalled() + }) + + it('should append urlSuffix to fallback URL when neither req.url nor serverURL provided', async () => { + const req = {} + + const result = await createPayloadReq({ payload: mockPayload, req, urlSuffix: '/api/test' }) + + expect(result.url).toBe('http://localhost/api/test') + expect(mockPayload.logger.error).not.toHaveBeenCalled() + }) + + it('returns the supplied request and preserves request-owned state', async () => { + const headers = new Headers({ existing: 'true' }) + const payloadDataLoader = {} as PayloadRequest['payloadDataLoader'] + const req = { + context: { fromRequest: true }, + headers, + payloadAPI: 'REST' as const, + payloadDataLoader, + query: { existing: 'value' }, + routeParams: { id: '1' }, + user: null, + url: 'http://example.com/api/posts', + } as Partial + + const result = await createPayloadReq({ + context: { fromArgs: true }, + payload: mockPayload, + req, + }) + + expect(result).toBe(req) + expect(result.headers).toBe(headers) + expect(result.payloadAPI).toBe('REST') + expect(result.payloadDataLoader).toBe(payloadDataLoader) + expect(result.context).toEqual({ fromRequest: true, fromArgs: true }) + expect(result.query).toEqual({ existing: 'value' }) + expect(result.routeParams).toEqual({ id: '1' }) + }) + + describe('localization', () => { + const localizedPayload = { + ...mockPayload, + config: { + ...mockPayload.config, + localization: { + defaultLocale: 'en', + fallback: true, + localeCodes: ['en', 'de'], + locales: [{ code: 'en' }, { code: 'de' }], + }, + }, + } as unknown as Payload + + it("normalizes localized '*' requests to 'all'", async () => { + const result = await createPayloadReq({ locale: '*', payload: localizedPayload }) + + expect(result.locale).toBe('all') + }) + + it('uses the default locale for localized requests without a locale', async () => { + const result = await createPayloadReq({ payload: localizedPayload }) + + expect(result.locale).toBe('en') + }) + + it('sanitizes unsupported fallback locales for localized requests', async () => { + const result = await createPayloadReq({ + fallbackLocale: 'fr', + locale: 'en', + payload: localizedPayload, + }) + + expect(result.fallbackLocale).toBe(false) + }) + + it('does not assign locale state when localization is disabled', async () => { + const result = await createPayloadReq({ + fallbackLocale: 'de', + locale: 'en', + payload: mockPayload, + }) + + expect(result.locale).toBeUndefined() + expect(result.fallbackLocale).toBeUndefined() + }) + }) +}) diff --git a/packages/payload/src/utilities/createLocalReq.ts b/packages/payload/src/utilities/createPayloadReq.ts similarity index 92% rename from packages/payload/src/utilities/createLocalReq.ts rename to packages/payload/src/utilities/createPayloadReq.ts index 9f5c2b7925e..80c3736338c 100644 --- a/packages/payload/src/utilities/createLocalReq.ts +++ b/packages/payload/src/utilities/createPayloadReq.ts @@ -85,30 +85,29 @@ const attachFakeURLProperties = (req: Partial, urlSuffix?: strin } } -export type CreateLocalReqOptions = { +export type CreatePayloadReqArgs = { context?: RequestContext depth?: number fallbackLocale?: false | TypedLocale locale?: string + payload: Payload req?: Partial urlSuffix?: string user?: User } -type CreateLocalReq = (options: CreateLocalReqOptions, payload: Payload) => Promise - -export const createLocalReq: CreateLocalReq = async ( - { - context, - depth, - fallbackLocale, - locale: localeArg, - req = {} as PayloadRequest, - urlSuffix, - user, - }, +type CreatePayloadReq = (args: CreatePayloadReqArgs) => Promise + +export const createPayloadReq: CreatePayloadReq = async ({ + context, + depth, + fallbackLocale, + locale: localeArg, payload, -): Promise => { + req = {} as PayloadRequest, + urlSuffix, + user, +}): Promise => { const localization = payload.config?.localization if (localization) { diff --git a/packages/payload/src/utilities/isolateObjectProperty.spec.ts b/packages/payload/src/utilities/isolateObjectProperty.spec.ts index c100ba0e7b1..e024e6a44be 100644 --- a/packages/payload/src/utilities/isolateObjectProperty.spec.ts +++ b/packages/payload/src/utilities/isolateObjectProperty.spec.ts @@ -122,7 +122,7 @@ describe('isolateObjectProperty', () => { const request = new MockRequest() const proxy = isolateObjectProperty(request, 'nonExistentKey' as keyof typeof request) - // This simulates what happens in createLocalReq when checking req.headers + // This simulates what happens in createPayloadReq when checking req.headers expect(proxy.headers).toBeInstanceOf(Headers) expect(proxy.headers.get('content-type')).toBe('application/json') }) diff --git a/packages/plugin-mcp/src/mcp/buildMcpServer.ts b/packages/plugin-mcp/src/mcp/buildMcpServer.ts index d4242c47bb7..b2baa774505 100644 --- a/packages/plugin-mcp/src/mcp/buildMcpServer.ts +++ b/packages/plugin-mcp/src/mcp/buildMcpServer.ts @@ -25,7 +25,7 @@ import { toStandardSchema } from '../utils/toStandardSchema.js' * * `req` is the request context handlers see. For HTTP it's the live * `PayloadRequest` derived from the incoming HTTP request; for stdio it's a - * synthesized one built via `createLocalReq`. + * synthesized one built via `createPayloadReq`. */ export const buildMcpServer = ({ authorizedMCP, diff --git a/packages/plugin-mcp/src/mcp/builtin/collections/authTools.ts b/packages/plugin-mcp/src/mcp/builtin/collections/authTools.ts index 8eed4673fb6..11ea94c2818 100644 --- a/packages/plugin-mcp/src/mcp/builtin/collections/authTools.ts +++ b/packages/plugin-mcp/src/mcp/builtin/collections/authTools.ts @@ -1,4 +1,4 @@ -import { createLocalReq, strictObject, z } from 'payload' +import { createPayloadReq, strictObject, z } from 'payload' import type { MCPToolResponse } from '../../../types.js' @@ -53,7 +53,7 @@ export const authCollectionTool = defineCollectionTool({ if (input.headers) { authHeaders = new Headers(input.headers) } - const authReq = await createLocalReq({ req: { headers: authHeaders } }, req.payload) + const authReq = await createPayloadReq({ payload: req.payload, req: { headers: authHeaders } }) const result = await req.payload.auth({ headers: authHeaders, req: authReq }) if (result.user) { diff --git a/packages/plugin-mcp/src/stdio.ts b/packages/plugin-mcp/src/stdio.ts index 9c82daee318..448239b1065 100644 --- a/packages/plugin-mcp/src/stdio.ts +++ b/packages/plugin-mcp/src/stdio.ts @@ -3,7 +3,7 @@ import type { Config, Plugin, SanitizedConfig } from 'payload' /* eslint-disable no-console */ import { serveStdio } from '@modelcontextprotocol/server/stdio' import { fileURLToPath, pathToFileURL } from 'node:url' -import { createLocalReq, getPayload } from 'payload' +import { createPayloadReq, getPayload } from 'payload' import { findConfig, loadEnv } from 'payload/node' import type { SanitizedMCPPluginConfig } from './types.js' @@ -91,7 +91,7 @@ export const runMcpStdio = async (): Promise => { headers.set('Authorization', process.env.PAYLOAD_MCP_AUTHORIZATION) } - const req = await createLocalReq({ req: { headers } }, payload) + const req = await createPayloadReq({ payload, req: { headers } }) req.payloadAPI = 'MCP' as const const authorizedMCP = await getAuthorizedMCP({ overrideAccess, req }) diff --git a/packages/richtext-lexical/src/features/converters/utilities/payloadPopulateFn.ts b/packages/richtext-lexical/src/features/converters/utilities/payloadPopulateFn.ts index 98ca79d8fd2..df77929933c 100644 --- a/packages/richtext-lexical/src/features/converters/utilities/payloadPopulateFn.ts +++ b/packages/richtext-lexical/src/features/converters/utilities/payloadPopulateFn.ts @@ -1,4 +1,4 @@ -import { createLocalReq, type Payload, type PayloadRequest, type TypedLocale } from 'payload' +import { createPayloadReq, type Payload, type PayloadRequest, type TypedLocale } from 'payload' import type { HTMLPopulateFn } from '../lexicalToHtml/async/types.js' @@ -48,7 +48,7 @@ export const getPayloadPopulateFn: ( }) => { let reqToUse: PayloadRequest | undefined = req if (req === undefined && payload) { - reqToUse = await createLocalReq({}, payload) + reqToUse = await createPayloadReq({ payload }) } if (!reqToUse) { diff --git a/packages/ui/src/views/NotFound/page.tsx b/packages/ui/src/views/NotFound/page.tsx index 72b2f5f6911..85936659121 100644 --- a/packages/ui/src/views/NotFound/page.tsx +++ b/packages/ui/src/views/NotFound/page.tsx @@ -1,6 +1,6 @@ 'use server' -import type { createLocalReq, ImportMap, InitReqResult, SanitizedConfig } from 'payload' +import type { createPayloadReq, ImportMap, InitReqResult, SanitizedConfig } from 'payload' import { applyLocaleFiltering, formatAdminURL } from 'payload/shared' import * as qs from 'qs-esm' @@ -18,7 +18,7 @@ type InitReqFn = (args: { configPromise: Promise | SanitizedConfig importMap: ImportMap key: string - overrides?: Parameters[0] + overrides?: Omit[0], 'payload'> }) => Promise export type RenderNotFoundPageArgs = { diff --git a/packages/ui/src/views/Root/index.tsx b/packages/ui/src/views/Root/index.tsx index bbcc89ce4d6..33079457107 100644 --- a/packages/ui/src/views/Root/index.tsx +++ b/packages/ui/src/views/Root/index.tsx @@ -5,7 +5,7 @@ import type { AdminViewClientProps, AdminViewServerPropsOnly, CollectionPreferences, - createLocalReq, + createPayloadReq, ImportMap, InitReqResult, SanitizedCollectionConfig, @@ -36,7 +36,7 @@ type InitReqFn = (args: { configPromise: Promise | SanitizedConfig importMap: ImportMap key: string - overrides?: Parameters[0] + overrides?: Omit[0], 'payload'> }) => Promise export type RenderRootArgs = { diff --git a/templates/ecommerce/src/app/(app)/next/seed/route.ts b/templates/ecommerce/src/app/(app)/next/seed/route.ts index 8abaa1cec11..1c2165446bb 100644 --- a/templates/ecommerce/src/app/(app)/next/seed/route.ts +++ b/templates/ecommerce/src/app/(app)/next/seed/route.ts @@ -1,4 +1,4 @@ -import { createLocalReq, getPayload } from 'payload' +import { createPayloadReq, getPayload } from 'payload' import { seed } from '@/endpoints/seed' import config from '@payload-config' import { headers } from 'next/headers' @@ -21,7 +21,7 @@ export async function POST(): Promise { try { // Create a Payload request object to pass to the Local API for transactions // At this point you should pass in a user, locale, and any other context you need for the Local API - const payloadReq = await createLocalReq({ user }, payload) + const payloadReq = await createPayloadReq({ payload, user }) await seed({ payload, req: payloadReq }) diff --git a/templates/website/src/app/(frontend)/next/seed/route.ts b/templates/website/src/app/(frontend)/next/seed/route.ts index 326b677ccc3..48d313eb556 100644 --- a/templates/website/src/app/(frontend)/next/seed/route.ts +++ b/templates/website/src/app/(frontend)/next/seed/route.ts @@ -1,4 +1,4 @@ -import { createLocalReq, getPayload } from 'payload' +import { createPayloadReq, getPayload } from 'payload' import { seed } from '@/endpoints/seed' import config from '@payload-config' import { headers } from 'next/headers' @@ -19,7 +19,7 @@ export async function POST(): Promise { try { // Create a Payload request object to pass to the Local API for transactions // At this point you should pass in a user, locale, and any other context you need for the Local API - const payloadReq = await createLocalReq({ user }, payload) + const payloadReq = await createPayloadReq({ payload, user }) await seed({ payload, req: payloadReq }) diff --git a/templates/with-vercel-website/src/app/(frontend)/next/seed/route.ts b/templates/with-vercel-website/src/app/(frontend)/next/seed/route.ts index 326b677ccc3..48d313eb556 100644 --- a/templates/with-vercel-website/src/app/(frontend)/next/seed/route.ts +++ b/templates/with-vercel-website/src/app/(frontend)/next/seed/route.ts @@ -1,4 +1,4 @@ -import { createLocalReq, getPayload } from 'payload' +import { createPayloadReq, getPayload } from 'payload' import { seed } from '@/endpoints/seed' import config from '@payload-config' import { headers } from 'next/headers' @@ -19,7 +19,7 @@ export async function POST(): Promise { try { // Create a Payload request object to pass to the Local API for transactions // At this point you should pass in a user, locale, and any other context you need for the Local API - const payloadReq = await createLocalReq({ user }, payload) + const payloadReq = await createPayloadReq({ payload, user }) await seed({ payload, req: payloadReq }) diff --git a/test/access-control/int.spec.ts b/test/access-control/int.spec.ts index cd2a1271b67..c4634ed3569 100644 --- a/test/access-control/int.spec.ts +++ b/test/access-control/int.spec.ts @@ -7,7 +7,7 @@ import type { RequiredDataFromCollectionSlug, } from 'payload' -import { AuthenticationError, createLocalReq, Forbidden } from 'payload' +import { AuthenticationError, createPayloadReq, Forbidden } from 'payload' import { getEntityPermissions } from 'payload/internal' import { expect, vitest } from 'vitest' @@ -2017,7 +2017,7 @@ test.suite('Access Control', { config: './config.ts', resetBetweenTests: false } entityType: 'collection', fetchData: true, operations: ['read', 'readVersions'], - req: await createLocalReq({}, payload), + req: await createPayloadReq({ payload }), }) expect(permissions.read?.permission).toBe(false) @@ -2306,7 +2306,7 @@ test.suite('Access Control', { config: './config.ts', resetBetweenTests: false } }) test('should evaluate inherited global read version permissions against versions', async () => { - const req = await createLocalReq({}, payload) + const req = await createPayloadReq({ payload }) await payload.db.deleteVersions({ globalSlug: inheritedReadVersionsGlobalSlug, @@ -2398,7 +2398,7 @@ test.suite('Access Control', { config: './config.ts', resetBetweenTests: false } }) test('should sanitize virtual-field constraints from inherited global read access', async () => { - const req = await createLocalReq({}, payload) + const req = await createPayloadReq({ payload }) await payload.db.deleteVersions({ globalSlug: inheritedReadVersionsVirtualGlobalSlug, @@ -2543,19 +2543,17 @@ test.suite('Access Control', { config: './config.ts', resetBetweenTests: false } overrideAccess: true, }) - const req = await createLocalReq( - { - user: { - id: 123 as any, - collection: 'users', - createdAt: new Date().toISOString(), - email: 'test@test.com', - roles: ['admin'], - updatedAt: new Date().toISOString(), - }, - }, + const req = await createPayloadReq({ payload, - ) + user: { + id: 123 as any, + collection: 'users', + createdAt: new Date().toISOString(), + email: 'test@test.com', + roles: ['admin'], + updatedAt: new Date().toISOString(), + }, + }) // Get permissions with admin user (should have access) const permissions = await getEntityPermissions({ @@ -2613,19 +2611,17 @@ test.suite('Access Control', { config: './config.ts', resetBetweenTests: false } }) // Create non-admin user request - const nonAdminReq = await createLocalReq( - { - user: { - id: 456 as any, - collection: 'users', - createdAt: new Date().toISOString(), - email: 'user@test.com', - roles: ['user'], // Not admin - updatedAt: new Date().toISOString(), - }, - }, + const nonAdminReq = await createPayloadReq({ payload, - ) + user: { + id: 456 as any, + collection: 'users', + createdAt: new Date().toISOString(), + email: 'user@test.com', + roles: ['user'], // Not admin + updatedAt: new Date().toISOString(), + }, + }) const permissions = await getEntityPermissions({ id: doc.id, diff --git a/test/access-control/postgres-logs.int.spec.ts b/test/access-control/postgres-logs.int.spec.ts index 9b234bff2f6..d5444f72ac3 100644 --- a/test/access-control/postgres-logs.int.spec.ts +++ b/test/access-control/postgres-logs.int.spec.ts @@ -1,6 +1,6 @@ import type { CollectionPermission, PayloadRequest } from 'payload' -import { createLocalReq } from 'payload' +import { createPayloadReq } from 'payload' import { getEntityPermissions } from 'payload/internal' import { expect, vitest } from 'vitest' @@ -14,19 +14,17 @@ test.suite( { config: './config.postgreslogs.ts', db: (adapter) => adapter.startsWith('postgres') }, () => { test.beforeEach(async ({ payload }) => { - req = await createLocalReq( - { - user: { - id: 123 as any, - collection: 'users', - roles: ['admin'], - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - email: 'test@test.com', - }, - }, + req = await createPayloadReq({ payload, - ) + user: { + id: 123 as any, + collection: 'users', + roles: ['admin'], + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + email: 'test@test.com', + }, + }) }) test.describe('Tests', () => { diff --git a/test/auth/int.spec.ts b/test/auth/int.spec.ts index c8a5b0de2c8..03d1ad3327b 100644 --- a/test/auth/int.spec.ts +++ b/test/auth/int.spec.ts @@ -11,7 +11,7 @@ import type { import crypto from 'crypto' import { jwtDecode } from 'jwt-decode' import { - createLocalReq, + createPayloadReq, Forbidden, getFieldsToSign, refreshOperation, @@ -2814,7 +2814,7 @@ test.suite('Auth', { config: './config.ts', resetBetweenTests: false }, () => { await setLoginAttemptLock({ id: publicUser.id, collection: publicUsersSlug, payload }) - const req = await createLocalReq({ user: adminUser }, payload) + const req = await createPayloadReq({ payload, user: adminUser }) await payload.unlock({ collection: publicUsersSlug, @@ -2849,7 +2849,7 @@ test.suite('Auth', { config: './config.ts', resetBetweenTests: false }, () => { await setLoginAttemptLock({ id: selectedUser.id, collection: publicUsersSlug, payload }) - const req = await createLocalReq({ user: currentUser }, payload) + const req = await createPayloadReq({ payload, user: currentUser }) await expect( payload.unlock({ @@ -3095,15 +3095,13 @@ test.suite('Auth', { config: './config.ts', resetBetweenTests: false }, () => { Authorization: `JWT ${authenticated.token}`, }, }) - const req = await createLocalReq( - { - user: { - ...authenticated.user, - _sid: sid, - }, - }, + const req = await createPayloadReq({ payload, - ) + user: { + ...authenticated.user, + _sid: sid, + }, + }) expect(logoutResponse.status).toBe(200) await expect( diff --git a/test/base-access/int.spec.ts b/test/base-access/int.spec.ts index e2e0388f78a..68169e98f52 100644 --- a/test/base-access/int.spec.ts +++ b/test/base-access/int.spec.ts @@ -1,4 +1,4 @@ -import { createLocalReq, Forbidden, getAccessResults } from 'payload' +import { createPayloadReq, Forbidden, getAccessResults } from 'payload' import { expect } from 'vitest' import { test } from '../__helpers/int/vitest.js' @@ -9,16 +9,14 @@ const createRequest = async ({ payload, }: { headers: Record - payload: Parameters[1] + payload: Parameters[0]['payload'] }) => - createLocalReq( - { - req: { - headers: new Headers(headers), - }, - }, + createPayloadReq({ payload, - ) + req: { + headers: new Headers(headers), + }, + }) test.suite('baseAccess', { config: './config.ts' }, () => { test('should combine base and collection query constraints', async ({ payload }) => { diff --git a/test/dataloader/int.spec.ts b/test/dataloader/int.spec.ts index bf08dae8e44..ff02fba228c 100644 --- a/test/dataloader/int.spec.ts +++ b/test/dataloader/int.spec.ts @@ -1,7 +1,7 @@ import type { CollectionSlug } from 'payload' import { buildDefaultEditorState } from '@payloadcms/richtext-lexical' -import { createLocalReq } from 'payload' +import { createPayloadReq } from 'payload' import { fileURLToPath } from 'url' import { expect, vitest } from 'vitest' @@ -161,7 +161,7 @@ test.suite('dataloader', { config: './config.ts' }, () => { test.describe('find', () => { test('should call the same query only once in a request', async ({ payload }) => { - const req = await createLocalReq({}, payload) + const req = await createPayloadReq({ payload }) const spy = vitest.spyOn(payload, 'find') const findArgs = { diff --git a/test/field-access-context/int.spec.ts b/test/field-access-context/int.spec.ts index 9387bdf8bff..1d50da4c21a 100644 --- a/test/field-access-context/int.spec.ts +++ b/test/field-access-context/int.spec.ts @@ -1,4 +1,4 @@ -import { createLocalReq } from 'payload' +import { createPayloadReq } from 'payload' import { fileURLToPath } from 'url' import { expect } from 'vitest' @@ -269,7 +269,7 @@ test.suite('field access collection context', { config: './config.ts' }, () => { }) => { // payload.auth() calls getEntityPermissions for all registered collections, // which calls populateFieldPermissions → field.access[operation] for each field. - const req = await createLocalReq({}, payload) + const req = await createPayloadReq({ payload }) await payload.auth({ headers: new Headers(), diff --git a/test/fields/int.spec.ts b/test/fields/int.spec.ts index 3285d0a930b..7a041885ae8 100644 --- a/test/fields/int.spec.ts +++ b/test/fields/int.spec.ts @@ -3,7 +3,7 @@ import type { IndexDirection, IndexOptions } from 'mongoose' import type { ValidationError } from 'payload' import { slugifyHandler } from '@payloadcms/ui/utilities/slugify' -import { createLocalReq, reload } from 'payload' +import { createPayloadReq, reload } from 'payload' import { fileURLToPath } from 'url' import { expect } from 'vitest' @@ -520,7 +520,7 @@ test.suite('Fields', { config: './config.ts', resetBetweenTests: false }, () => const takenSlug = taken.localizedSlug as string expect(takenSlug).toMatch(/^slug-field-\d+$/) - const req = await createLocalReq({ user: user.user }, payload) + const req = await createPayloadReq({ payload, user: user.user }) const regenerated = await slugifyHandler({ collectionSlug: 'slug-fields', @@ -546,7 +546,7 @@ test.suite('Fields', { config: './config.ts', resetBetweenTests: false }, () => created.push(first.id) expect(first.slug).toBe('shared-regen') - const req = await createLocalReq({ user: user.user }, payload) + const req = await createPayloadReq({ payload, user: user.user }) // Regenerating from a source that slugifies to a taken slug must bump it, not hand back the // duplicate — otherwise the next save throws a uniqueness error. @@ -572,7 +572,7 @@ test.suite('Fields', { config: './config.ts', resetBetweenTests: false }, () => created.push(doc.id) expect(doc.slug).toBe('own-slug') - const req = await createLocalReq({ user: user.user }, payload) + const req = await createPayloadReq({ payload, user: user.user }) // Excluding the current doc means regenerating its own unchanged source reuses the value rather // than bumping past test. @@ -1004,7 +1004,7 @@ test.suite('Fields', { config: './config.ts', resetBetweenTests: false }, () => }) created.push(hidden.id) - const req = await createLocalReq({ user: user.user }, payload) + const req = await createPayloadReq({ payload, user: user.user }) const createdWithAccess = await payload.create({ collection, data: { title: 'Shared title' }, @@ -1031,7 +1031,7 @@ test.suite('Fields', { config: './config.ts', resetBetweenTests: false }, () => }) created.push(hidden.id, editable.id) - const req = await createLocalReq({ user: user.user }, payload) + const req = await createPayloadReq({ payload, user: user.user }) const updated = await payload.update({ collection, id: editable.id, @@ -1052,7 +1052,7 @@ test.suite('Fields', { config: './config.ts', resetBetweenTests: false }, () => }) created.push(hidden.id) - const req = await createLocalReq({ user: user.user }, payload) + const req = await createPayloadReq({ payload, user: user.user }) const createdWithAccess = await payload.create({ collection, data: { localizedTitle: 'Localized title', title: 'Visible' }, @@ -1089,7 +1089,7 @@ test.suite('Fields', { config: './config.ts', resetBetweenTests: false }, () => }) created.push(hidden.id) - const req = await createLocalReq({ user: user.user }, payload) + const req = await createPayloadReq({ payload, user: user.user }) const createdWithAccess = await payload.create({ collection, data: { title: 'Visible' }, diff --git a/test/form-state/int.spec.ts b/test/form-state/int.spec.ts index e1036f197c3..cdd8ae1ea6c 100644 --- a/test/form-state/int.spec.ts +++ b/test/form-state/int.spec.ts @@ -1,7 +1,7 @@ import type { FieldState, FormState, User } from 'payload' import { buildFormState } from '@payloadcms/ui/utilities/buildFormState' -import { createLocalReq, getAccessResults } from 'payload' +import { createPayloadReq, getAccessResults } from 'payload' import React from 'react' import { fileURLToPath } from 'url' import { expect, vi } from 'vitest' @@ -59,7 +59,7 @@ test.suite('Form State', { config: './config.ts', resetBetweenTests: false }, () overrideAccess: true, }) - const req = await createLocalReq({ user: editor }, payload) + const req = await createPayloadReq({ payload, user: editor }) const permissions = await getAccessResults({ req }) const restrictedValue = 'client supplied' const title = 'Access filtered autosave' @@ -98,7 +98,7 @@ test.suite('Form State', { config: './config.ts', resetBetweenTests: false }, () }) test('should build entire form state', async ({ payload }) => { - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) const postData = await payload.create({ collection: postsSlug, @@ -157,7 +157,7 @@ test.suite('Form State', { config: './config.ts', resetBetweenTests: false }, () test('should use `select` to build partial form state with only specified fields', async ({ payload, }) => { - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) const postData = await payload.create({ collection: postsSlug, @@ -205,7 +205,7 @@ test.suite('Form State', { config: './config.ts', resetBetweenTests: false }, () test('should not render custom components when `lastRenderedPath` exists', async ({ payload, }) => { - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) const { state: stateWithRow } = await buildFormState({ collectionSlug: postsSlug, @@ -290,7 +290,7 @@ test.suite('Form State', { config: './config.ts', resetBetweenTests: false }, () test('should not render custom Field components for fields hidden by admin.condition', async ({ payload, }) => { - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) const hiddenDoc = await payload.create({ collection: conditionsSlug, @@ -356,7 +356,7 @@ test.suite('Form State', { config: './config.ts', resetBetweenTests: false }, () test('should preserve values of fields nested inside a row hidden by admin.condition', async ({ payload, }) => { - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) const hiddenDoc = await payload.create({ collection: conditionsSlug, @@ -396,7 +396,7 @@ test.suite('Form State', { config: './config.ts', resetBetweenTests: false }, () test('should preserve values of fields nested inside a collapsible hidden by admin.condition', async ({ payload, }) => { - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) const hiddenDoc = await payload.create({ collection: conditionsSlug, @@ -433,7 +433,7 @@ test.suite('Form State', { config: './config.ts', resetBetweenTests: false }, () test('should render custom Field component when admin.condition flips from false to true via onChange', async ({ payload, }) => { - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) const doc = await payload.create({ collection: conditionsSlug, @@ -491,7 +491,7 @@ test.suite('Form State', { config: './config.ts', resetBetweenTests: false }, () test('should add `addedByServer` flag to fields that originate on the server', async ({ payload, }) => { - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) const postData = await payload.create({ collection: postsSlug, @@ -1370,7 +1370,7 @@ test.suite('Form State', { config: './config.ts', resetBetweenTests: false }, () }) test('should set rows to empty array for empty array fields', async ({ payload }) => { - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) // Create a document with an empty array const postData = await payload.create({ @@ -1412,7 +1412,7 @@ test.suite('Form State', { config: './config.ts', resetBetweenTests: false }, () test('should resolve a promise-returning `filterOptions` on a select field into `selectFilterOptions`', async ({ payload, }) => { - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) const postData = await payload.create({ collection: postsSlug, diff --git a/test/localization/int.spec.ts b/test/localization/int.spec.ts index 87c3ab2f97a..fb715f6e96a 100644 --- a/test/localization/int.spec.ts +++ b/test/localization/int.spec.ts @@ -1,6 +1,6 @@ import type { Payload, User, Where } from 'payload' -import { createLocalReq } from 'payload' +import { createPayloadReq } from 'payload' import { fileURLToPath } from 'url' import { expect } from 'vitest' @@ -3304,7 +3304,7 @@ test.suite('Localization', { config: './config.ts', resetBetweenTests: false }, overrideAccess: true, }) - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) const res = (await copyDataFromLocaleHandler({ collectionSlug: 'localized-posts', @@ -3343,7 +3343,7 @@ test.suite('Localization', { config: './config.ts', resetBetweenTests: false }, overrideAccess: true, }) - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) const res = (await copyDataFromLocaleHandler({ collectionSlug: 'blocks-fields', @@ -3374,7 +3374,7 @@ test.suite('Localization', { config: './config.ts', resetBetweenTests: false }, overrideAccess: true, }) - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) const res = (await copyDataFromLocaleHandler({ collectionSlug: 'blocks-fields', docID: doc.id, @@ -3401,7 +3401,7 @@ test.suite('Localization', { config: './config.ts', resetBetweenTests: false }, overrideAccess: true, }) - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) const res = (await copyDataFromLocaleHandler({ collectionSlug: 'nested', @@ -3440,7 +3440,7 @@ test.suite('Localization', { config: './config.ts', resetBetweenTests: false }, overrideAccess: true, }) - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) const res = (await copyDataFromLocaleHandler({ collectionSlug: 'nested', @@ -3483,7 +3483,7 @@ test.suite('Localization', { config: './config.ts', resetBetweenTests: false }, }) try { - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) const res = (await copyDataFromLocaleHandler({ collectionSlug: arrayCollectionSlug, @@ -3561,7 +3561,7 @@ test.suite('Localization', { config: './config.ts', resetBetweenTests: false }, expect(enDocBefore.content?.[0]?.text).toBe('English block text') // Copy data from en to es - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) await copyDataFromLocaleHandler({ collectionSlug: 'blocks-fields', @@ -3629,7 +3629,7 @@ test.suite('Localization', { config: './config.ts', resetBetweenTests: false }, expect(draftBefore.title).toBe('Draft English Title') // Copy draft data to another locale - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) await copyDataFromLocaleHandler({ collectionSlug: 'blocks-fields', @@ -3697,7 +3697,7 @@ test.suite('Localization', { config: './config.ts', resetBetweenTests: false }, expect(enDraftBefore.title).toBe('Draft EN') // Copy to another locale using the actual handler - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) await copyDataFromLocaleHandler({ collectionSlug: 'blocks-fields', diff --git a/test/queues/int.spec.ts b/test/queues/int.spec.ts index a52f55febb5..e59ff9c2246 100644 --- a/test/queues/int.spec.ts +++ b/test/queues/int.spec.ts @@ -3,7 +3,7 @@ import path from 'path' import { _internal_jobSystemGlobals, _internal_resetJobSystemGlobals, - createLocalReq, + createPayloadReq, Forbidden, type JobTaskStatus, type Payload, @@ -88,7 +88,7 @@ test.suite('Queues - Payload', { config: './config.ts' }, () => { test.describe('access control', () => { test('should deny raw job creation when access control is enabled', async ({ payload }) => { - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) await expect( payload.create({ @@ -106,7 +106,7 @@ test.suite('Queues - Payload', { config: './config.ts' }, () => { }) test('should deny raw job reads when access control is enabled', async ({ payload }) => { - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) const job = await payload.jobs.queue({ task: 'CreateSimple', input: { @@ -126,7 +126,7 @@ test.suite('Queues - Payload', { config: './config.ts' }, () => { }) test('should deny raw job updates when access control is enabled', async ({ payload }) => { - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) const job = await payload.jobs.queue({ task: 'CreateSimple', input: { @@ -159,7 +159,7 @@ test.suite('Queues - Payload', { config: './config.ts' }, () => { }) test('should deny raw job deletion when access control is enabled', async ({ payload }) => { - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) const job = await payload.jobs.queue({ task: 'CreateSimple', input: { @@ -218,7 +218,7 @@ test.suite('Queues - Payload', { config: './config.ts' }, () => { test('will pass access control on local api .queue when passing overrideAccess: false', async ({ payload, }) => { - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) const result = await payload.jobs.queue({ task: 'CreateSimple', input: { @@ -239,7 +239,7 @@ test.suite('Queues - Payload', { config: './config.ts' }, () => { test('will pass access control on local api .run when passing overrideAccess: false', async ({ payload, }) => { - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) const result = await payload.jobs.run({ overrideAccess: false, req, @@ -259,7 +259,7 @@ test.suite('Queues - Payload', { config: './config.ts' }, () => { test('will pass access control on local api .runByID when passing overrideAccess: false', async ({ payload, }) => { - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) // Queue a job first so we have a valid ID const job = await payload.jobs.queue({ @@ -319,7 +319,7 @@ test.suite('Queues - Payload', { config: './config.ts' }, () => { }) => { payload.config.jobs.deleteJobOnComplete = false - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) // Queue a job without running it const job = await payload.jobs.queue({ @@ -387,7 +387,7 @@ test.suite('Queues - Payload', { config: './config.ts' }, () => { }) => { payload.config.jobs.deleteJobOnComplete = false - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) // Queue a job without running it const job = await payload.jobs.queue({ @@ -1758,7 +1758,7 @@ test.suite('Queues - Payload', { config: './config.ts' }, () => { overrideAccess: true, }) - const _req = await createLocalReq({}, payload) + const _req = await createPayloadReq({ payload }) const t1Req = isolateObjectProperty(_req, 'transactionID') delete t1Req.transactionID diff --git a/test/queues/utilities.ts b/test/queues/utilities.ts index bda1e0ac837..9be7e627e89 100644 --- a/test/queues/utilities.ts +++ b/test/queues/utilities.ts @@ -1,7 +1,7 @@ import { _internal_jobSystemGlobals, countRunnableOrActiveJobsForQueue, - createLocalReq, + createPayloadReq, type Payload, } from 'payload' @@ -14,7 +14,7 @@ export async function waitUntilAutorunIsDone({ payload: Payload queue: string }): Promise { - const req = await createLocalReq({}, payload) + const req = await createPayloadReq({ payload }) return new Promise((resolve) => { const interval = setInterval(async () => { diff --git a/test/versions/int.spec.ts b/test/versions/int.spec.ts index 612134044ee..64d86f424d2 100644 --- a/test/versions/int.spec.ts +++ b/test/versions/int.spec.ts @@ -6,7 +6,7 @@ import { } from '@payloadcms/ui/utilities/schedulePublishHandler' import fs from 'fs' import path from 'path' -import { createLocalReq, Forbidden, getFileByPath, saveVersion, ValidationError } from 'payload' +import { createPayloadReq, Forbidden, getFileByPath, saveVersion, ValidationError } from 'payload' import { wait } from 'payload/shared' import * as qs from 'qs-esm' import { fileURLToPath } from 'url' @@ -4725,7 +4725,7 @@ test.suite('Versions', { config: './config.ts', resetBetweenTests: false }, () = overrideAccess: true, }) - const req = await createLocalReq({ user: secondaryAdminUser }, payload) + const req = await createPayloadReq({ payload, user: secondaryAdminUser }) const currentDate = new Date() await schedulePublishHandler({ @@ -4786,7 +4786,7 @@ test.suite('Versions', { config: './config.ts', resetBetweenTests: false }, () = overrideAccess: true, }) - const req = await createLocalReq({ user: secondaryAdminUser }, payload) + const req = await createPayloadReq({ payload, user: secondaryAdminUser }) const currentDate = new Date() await schedulePublishHandler({ @@ -5186,7 +5186,7 @@ test.suite('Versions', { config: './config.ts', resetBetweenTests: false }, () = // Create a request without a user (simulating unauthenticated request) // Access control on draftGlobalSlug requires published status when no user - const req = await createLocalReq({}, payload) + const req = await createPayloadReq({ payload }) req.user = null const result = await payload.findGlobal({ @@ -5227,7 +5227,7 @@ test.suite('Versions', { config: './config.ts', resetBetweenTests: false }, () = test('should create using schedule-publish', async ({ payload }) => { const currentDate = new Date() - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) // use server action to create the event await schedulePublishHandler({ @@ -5260,7 +5260,7 @@ test.suite('Versions', { config: './config.ts', resetBetweenTests: false }, () = test('should get upcoming scheduled publish events without reading the jobs collection', async ({ payload, }) => { - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) await schedulePublishHandler({ type: 'publish', @@ -5293,7 +5293,7 @@ test.suite('Versions', { config: './config.ts', resetBetweenTests: false }, () = test('should not get scheduled publish events without publish permission', async ({ payload, }) => { - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) await payload.update({ id: draftDoc.id, @@ -5316,7 +5316,7 @@ test.suite('Versions', { config: './config.ts', resetBetweenTests: false }, () = test('should delete using schedule-publish', async ({ payload }) => { const currentDate = new Date() - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) // use server action to create the event await schedulePublishHandler({ @@ -5373,7 +5373,7 @@ test.suite('Versions', { config: './config.ts', resetBetweenTests: false }, () = }) test('should not delete a job that is not a scheduled publish', async ({ payload }) => { - const req = await createLocalReq({ user }, payload) + const req = await createPayloadReq({ payload, user }) const unrelatedJob = await payload.db.create({ collection: 'payload-jobs', data: { From 80980a4a32dff88d131ac33b65ddfc5abfbe3a3d Mon Sep 17 00:00:00 2001 From: Jake Fletcher Date: Wed, 23 Sep 2026 15:03:29 -0400 Subject: [PATCH 02/15] refactor!: consolidate Web request creation --- packages/next/src/routes/graphql/handler.ts | 4 +- .../next/src/routes/graphql/playground.ts | 4 +- .../payload/reference/PLUGIN-DEVELOPMENT.md | 4 +- packages/payload/src/index.ts | 2 +- .../createPayloadReqFromWebRequest.spec.ts | 165 ++++++++++++++++++ ...t.ts => createPayloadReqFromWebRequest.ts} | 28 ++- .../payload/src/utilities/handleEndpoints.ts | 7 +- .../src/utilities/graphqlHandler.server.ts | 4 +- templates/plugin/dev/int.spec.ts | 4 +- test/auth/api-key/int.spec.ts | 10 +- test/auth/jwt/int.spec.ts | 6 +- test/uploads/int.spec.ts | 14 +- 12 files changed, 206 insertions(+), 46 deletions(-) create mode 100644 packages/payload/src/utilities/createPayloadReqFromWebRequest.spec.ts rename packages/payload/src/utilities/{createPayloadRequest.ts => createPayloadReqFromWebRequest.ts} (84%) diff --git a/packages/next/src/routes/graphql/handler.ts b/packages/next/src/routes/graphql/handler.ts index a97e7861ea1..d9638905460 100644 --- a/packages/next/src/routes/graphql/handler.ts +++ b/packages/next/src/routes/graphql/handler.ts @@ -7,7 +7,7 @@ import { status as httpStatus } from 'http-status' import { addDataAndFileToRequest, addLocalesToRequestFromData, - createPayloadRequest, + createPayloadReqFromWebRequest, headersWithCors, logError, mergeHeaders, @@ -98,7 +98,7 @@ export const getGraphql = async (config: Promise | SanitizedCon export const POST = (config: Promise | SanitizedConfig) => async (request: Request) => { const originalRequest = request.clone() - const req = await createPayloadRequest({ + const req = await createPayloadReqFromWebRequest({ canSetHeaders: true, config, request, diff --git a/packages/next/src/routes/graphql/playground.ts b/packages/next/src/routes/graphql/playground.ts index 1550befae18..e2657507a20 100644 --- a/packages/next/src/routes/graphql/playground.ts +++ b/packages/next/src/routes/graphql/playground.ts @@ -1,9 +1,9 @@ import { renderPlaygroundPage } from 'graphql-playground-html' -import { createPayloadRequest, type SanitizedConfig } from 'payload' +import { createPayloadReqFromWebRequest, type SanitizedConfig } from 'payload' import { formatAdminURL } from 'payload/shared' export const GET = (config: Promise) => async (request: Request) => { - const req = await createPayloadRequest({ + const req = await createPayloadReqFromWebRequest({ config, request, }) diff --git a/packages/payload/skills/payload/reference/PLUGIN-DEVELOPMENT.md b/packages/payload/skills/payload/reference/PLUGIN-DEVELOPMENT.md index c9e17b12e2b..54952ce23a6 100644 --- a/packages/payload/skills/payload/reference/PLUGIN-DEVELOPMENT.md +++ b/packages/payload/skills/payload/reference/PLUGIN-DEVELOPMENT.md @@ -1350,7 +1350,7 @@ Create `dev/int.spec.ts`: ```ts import type { Payload } from 'payload' import config from '@payload-config' -import { createPayloadRequest, getPayload } from 'payload' +import { createPayloadReqFromWebRequest, getPayload } from 'payload' import { afterAll, beforeAll, describe, expect, test } from 'vitest' import { customEndpointHandler } from '../src/endpoints/handler.js' @@ -1385,7 +1385,7 @@ describe('Plugin integration tests', () => { test('should query custom endpoint', async () => { const request = new Request('http://localhost:3000/api/my-endpoint') - const payloadRequest = await createPayloadRequest({ config, request }) + const payloadRequest = await createPayloadReqFromWebRequest({ config, request }) const response = await customEndpointHandler(payloadRequest) const data = await response.json() expect(data).toMatchObject({ message: 'Hello' }) diff --git a/packages/payload/src/index.ts b/packages/payload/src/index.ts index 47d6181e6aa..025a4e67832 100644 --- a/packages/payload/src/index.ts +++ b/packages/payload/src/index.ts @@ -2021,7 +2021,7 @@ export { } from './utilities/configToJSONSchema.js' export { createArrayFromCommaDelineated } from './utilities/createArrayFromCommaDelineated.js' export { createPayloadReq, type CreatePayloadReqArgs } from './utilities/createPayloadReq.js' -export { createPayloadRequest } from './utilities/createPayloadRequest.js' +export { createPayloadReqFromWebRequest } from './utilities/createPayloadReqFromWebRequest.js' export { deepCopyObject, deepCopyObjectComplex, diff --git a/packages/payload/src/utilities/createPayloadReqFromWebRequest.spec.ts b/packages/payload/src/utilities/createPayloadReqFromWebRequest.spec.ts new file mode 100644 index 00000000000..1e9c54dbbb6 --- /dev/null +++ b/packages/payload/src/utilities/createPayloadReqFromWebRequest.spec.ts @@ -0,0 +1,165 @@ +import type { I18nClient } from '@payloadcms/translations' + +import type { Payload, PayloadRequest, SanitizedConfig } from '../index.js' + +import { beforeEach, describe, expect, it, vi } from 'vitest' + +import { executeAuthStrategies } from '../auth/executeAuthStrategies.js' +import { getPayload } from '../index.js' +import { createPayloadReq } from './createPayloadReq.js' +import { createPayloadReqFromWebRequest } from './createPayloadReqFromWebRequest.js' + +const { initI18n } = vi.hoisted(() => ({ + initI18n: vi.fn(), +})) + +vi.mock('@payloadcms/translations', async (importOriginal) => { + const actual = await importOriginal() + + return { + ...actual, + initI18n, + } +}) + +vi.mock('../auth/executeAuthStrategies.js', () => ({ + executeAuthStrategies: vi.fn(), +})) + +vi.mock('../index.js', () => ({ + getPayload: vi.fn(), +})) + +vi.mock('./createPayloadReq.js', () => ({ + createPayloadReq: vi.fn(), +})) + +const config = { + cookiePrefix: 'payload', + graphQL: { + disable: false, + }, + i18n: { + fallbackLanguage: 'en', + supportedLanguages: { + en: {}, + }, + }, + localization: { + defaultLocale: 'en', + fallback: true, + localeCodes: ['en', 'de'], + locales: [ + { code: 'en', label: 'English' }, + { code: 'de', label: 'German' }, + ], + }, + routes: { + api: '/api', + graphQL: '/graphql', + }, +} as SanitizedConfig + +const i18n = { + language: 'en', + t: (key: string) => key, + translations: {}, +} as unknown as I18nClient + +const payload = { + config, +} as unknown as Payload + +describe('createPayloadReqFromWebRequest', () => { + beforeEach(() => { + vi.mocked(getPayload).mockReset().mockResolvedValue(payload) + vi.mocked(createPayloadReq) + .mockReset() + .mockImplementation(async ({ req }) => req as PayloadRequest) + vi.mocked(executeAuthStrategies).mockReset().mockResolvedValue({ + responseHeaders: undefined, + user: null, + }) + initI18n.mockReset().mockResolvedValue(i18n) + }) + + it('initializes the original Web Request through createPayloadReq before authentication', async () => { + const request = new Request('http://localhost/api/posts?locale=en&depth=2') + + const result = await createPayloadReqFromWebRequest({ config, request }) + + expect(createPayloadReq).toHaveBeenCalledWith( + expect.objectContaining({ + locale: 'en', + payload, + req: request, + }), + ) + expect((request as PayloadRequest).i18n).toBe(i18n) + expect((request as PayloadRequest).query).toEqual({ depth: '2', locale: 'en' }) + expect(executeAuthStrategies).toHaveBeenCalledWith( + expect.objectContaining({ req: request }), + ) + expect(vi.mocked(createPayloadReq).mock.invocationCallOrder[0]).toBeLessThan( + vi.mocked(executeAuthStrategies).mock.invocationCallOrder[0], + ) + expect(result).toBe(request) + }) + + it.each([ + ['http://localhost/api/graphql', 'GraphQL'], + ['http://localhost/api/posts', 'REST'], + ] as const)('assigns %s requests to the %s API', async (url, payloadAPI) => { + const request = new Request(url) + + const result = await createPayloadReqFromWebRequest({ config, request }) + + expect(result.payloadAPI).toBe(payloadAPI) + }) + + it('parses nested query values and assigns route params', async () => { + const request = new Request( + 'http://localhost/api/posts?where[title][equals]=Hello&tags[]=news&tags[]=featured', + ) + + const result = await createPayloadReqFromWebRequest({ + config, + params: { collection: 'posts' }, + request, + }) + + expect(result.query).toEqual({ + tags: ['news', 'featured'], + where: { title: { equals: 'Hello' } }, + }) + expect(result.routeParams).toEqual({ collection: 'posts' }) + }) + + it('assigns authentication response headers and user', async () => { + const request = new Request('http://localhost/api/posts') + const responseHeaders = new Headers({ 'x-auth': 'authenticated' }) + const user = { collection: 'users', id: 'user-id' } + + vi.mocked(executeAuthStrategies).mockResolvedValue({ responseHeaders, user }) + + const result = await createPayloadReqFromWebRequest({ config, request }) + + expect(result.responseHeaders).toBe(responseHeaders) + expect(result.user).toBe(user) + }) + + it('passes sanitized fallback locale state to common request initialization', async () => { + const request = new Request( + 'http://localhost/api/posts?locale=de&fallbackLocale=unsupported', + ) + + await createPayloadReqFromWebRequest({ config, request }) + + expect(createPayloadReq).toHaveBeenCalledWith( + expect.objectContaining({ + fallbackLocale: false, + locale: 'de', + }), + ) + }) +}) diff --git a/packages/payload/src/utilities/createPayloadRequest.ts b/packages/payload/src/utilities/createPayloadReqFromWebRequest.ts similarity index 84% rename from packages/payload/src/utilities/createPayloadRequest.ts rename to packages/payload/src/utilities/createPayloadReqFromWebRequest.ts index 8f025550a7b..0d768260f5c 100644 --- a/packages/payload/src/utilities/createPayloadRequest.ts +++ b/packages/payload/src/utilities/createPayloadReqFromWebRequest.ts @@ -2,13 +2,13 @@ import { initI18n } from '@payloadcms/translations' import * as qs from 'qs-esm' import type { SanitizedConfig } from '../config/types.js' -import type { TypedFallbackLocale } from '../index.js' -import type { CustomPayloadRequestProperties, PayloadRequest } from '../types/index.js' +import type { TypedFallbackLocale, TypedLocale } from '../index.js' +import type { PayloadRequest } from '../types/index.js' import { executeAuthStrategies } from '../auth/executeAuthStrategies.js' -import { getDataLoader } from '../collections/dataloader.js' import { getPayload } from '../index.js' import { sanitizeLocales } from './addLocalesToRequest.js' +import { createPayloadReq } from './createPayloadReq.js' import { formatAdminURL } from './formatAdminURL.js' import { getRequestLanguage } from './getRequestLanguage.js' import { parseCookies } from './parseCookies.js' @@ -23,7 +23,7 @@ type Args = { request: Request } -export const createPayloadRequest = async ({ +export const createPayloadReqFromWebRequest = async ({ canSetHeaders, config: configPromise, params, @@ -93,19 +93,14 @@ export const createPayloadRequest = async ({ locale = locales.locale! } - const customRequest: CustomPayloadRequestProperties = { - context: {}, - fallbackLocale: fallbackLocale!, + Object.assign(request, { hash: urlProperties.hash, host: urlProperties.host, href: urlProperties.href, i18n, - locale, origin: urlProperties.origin, pathname: urlProperties.pathname, - payload, payloadAPI: isGraphQL ? 'GraphQL' : 'REST', - payloadDataLoader: undefined!, payloadUploadSizes: {}, port: urlProperties.port, protocol: urlProperties.protocol, @@ -113,14 +108,15 @@ export const createPayloadRequest = async ({ routeParams: params || {}, search: urlProperties.search, searchParams: urlProperties.searchParams, - t: i18n.t, transactionID: undefined, - user: null, - } - - const req: PayloadRequest = Object.assign(request, customRequest) + }) - req.payloadDataLoader = getDataLoader(req) + const req = await createPayloadReq({ + fallbackLocale: fallbackLocale as false | TypedLocale | undefined, + locale: locale ?? undefined, + payload, + req: request, + }) const { responseHeaders, user } = await executeAuthStrategies({ canSetHeaders, diff --git a/packages/payload/src/utilities/handleEndpoints.ts b/packages/payload/src/utilities/handleEndpoints.ts index 57221876d01..ddb30a7d1e6 100644 --- a/packages/payload/src/utilities/handleEndpoints.ts +++ b/packages/payload/src/utilities/handleEndpoints.ts @@ -7,7 +7,7 @@ import type { APIError } from '../errors/APIError.js' import type { GlobalConfig } from '../globals/config/types.js' import type { PayloadRequest } from '../types/index.js' -import { createPayloadRequest } from './createPayloadRequest.js' +import { createPayloadReqFromWebRequest } from './createPayloadReqFromWebRequest.js' import { formatAdminURL, stripTrailingSlash } from './formatAdminURL.js' import { headersWithCors } from './headersWithCors.js' import { mergeHeaders } from './mergeHeaders.js' @@ -103,8 +103,7 @@ export const handleEndpoints = async ({ // May not be supported by every endpoint data = await request.json() - // locale and fallbackLocale is read by createPayloadRequest to populate req.locale and req.fallbackLocale - // => add to searchParams + // createPayloadReqFromWebRequest reads locale and fallbackLocale from searchParams. if (data?.locale) { url += `?locale=${data.locale}` } @@ -136,7 +135,7 @@ export const handleEndpoints = async ({ } try { - req = await createPayloadRequest({ + req = await createPayloadReqFromWebRequest({ canSetHeaders: true, config: incomingConfig, payloadInstanceCacheKey, diff --git a/packages/tanstack-start/src/utilities/graphqlHandler.server.ts b/packages/tanstack-start/src/utilities/graphqlHandler.server.ts index 15bbf2ce8ca..7625dde1bbf 100644 --- a/packages/tanstack-start/src/utilities/graphqlHandler.server.ts +++ b/packages/tanstack-start/src/utilities/graphqlHandler.server.ts @@ -3,7 +3,7 @@ import type { Payload, PayloadRequest, SanitizedConfig } from 'payload' import { addDataAndFileToRequest, addLocalesToRequestFromData, - createPayloadRequest, + createPayloadReqFromWebRequest, headersWithCors, logError, mergeHeaders, @@ -102,7 +102,7 @@ export const handleGraphQL = async ({ }): Promise => { const originalRequest = request.clone() - const req = await createPayloadRequest({ + const req = await createPayloadReqFromWebRequest({ canSetHeaders: true, config, request, diff --git a/templates/plugin/dev/int.spec.ts b/templates/plugin/dev/int.spec.ts index 9e3b74b993a..15a1100dc27 100644 --- a/templates/plugin/dev/int.spec.ts +++ b/templates/plugin/dev/int.spec.ts @@ -1,7 +1,7 @@ import type { Payload } from 'payload' import config from '@payload-config' -import { createPayloadRequest, getPayload } from 'payload' +import { createPayloadReqFromWebRequest, getPayload } from 'payload' import { afterAll, beforeAll, describe, expect, test } from 'vitest' import { customEndpointHandler } from '../src/endpoints/customEndpointHandler.js' @@ -22,7 +22,7 @@ describe('Plugin integration tests', () => { method: 'GET', }) - const payloadRequest = await createPayloadRequest({ config, request }) + const payloadRequest = await createPayloadReqFromWebRequest({ config, request }) const response = await customEndpointHandler(payloadRequest) expect(response.status).toBe(200) diff --git a/test/auth/api-key/int.spec.ts b/test/auth/api-key/int.spec.ts index e770afc8174..b06713895af 100644 --- a/test/auth/api-key/int.spec.ts +++ b/test/auth/api-key/int.spec.ts @@ -1,4 +1,4 @@ -import { createPayloadRequest } from 'payload' +import { createPayloadReqFromWebRequest } from 'payload' import { v4 as uuid } from 'uuid' import { expect } from 'vitest' @@ -89,7 +89,7 @@ test.suite('API key authentication', { config: './config.ts' }, () => { overrideAccess: true, }) - const req = await createPayloadRequest({ + const req = await createPayloadReqFromWebRequest({ config: payload.config, request: new Request( 'http://localhost/api/auth-read-hook?locale=fr&fallbackLocale=none&depth=2', @@ -100,7 +100,7 @@ test.suite('API key authentication', { config: './config.ts' }, () => { }, ), }) - const graphQLReq = await createPayloadRequest({ + const graphQLReq = await createPayloadReqFromWebRequest({ config: payload.config, request: new Request('http://localhost/api/graphql', { headers: { @@ -270,7 +270,7 @@ test.suite('API key authentication', { config: './config.ts' }, () => { overrideAccess: true, }) ).token! - const req = await createPayloadRequest({ + const req = await createPayloadReqFromWebRequest({ config: payload.config, request: new Request('http://localhost/api', { headers: { Authorization: `JWT ${token}` }, @@ -284,7 +284,7 @@ test.suite('API key authentication', { config: './config.ts' }, () => { expect('access' in apiKeyField && apiKeyField.access?.update).toBeTypeOf('function') expect(await apiKeyField.access!.update!({ id: target.id, req } as never)).toBe(true) - const unauthenticatedReq = await createPayloadRequest({ + const unauthenticatedReq = await createPayloadReqFromWebRequest({ config: payload.config, request: new Request('http://localhost/api'), }) diff --git a/test/auth/jwt/int.spec.ts b/test/auth/jwt/int.spec.ts index 05a2a71912a..6d2b57b2a0b 100644 --- a/test/auth/jwt/int.spec.ts +++ b/test/auth/jwt/int.spec.ts @@ -1,7 +1,7 @@ import type { CollectionRefreshHook, Payload } from 'payload' import { jwtDecode } from 'jwt-decode' -import { createPayloadRequest } from 'payload' +import { createPayloadReqFromWebRequest } from 'payload' import { v4 as uuid } from 'uuid' import { expect } from 'vitest' @@ -129,14 +129,14 @@ test.suite( overrideAccess: true, }) const headers = { Authorization: `JWT ${token}` } - const req = await createPayloadRequest({ + const req = await createPayloadReqFromWebRequest({ config: payload.config, request: new Request( 'http://localhost/api/auth-read-hook?locale=fr&fallbackLocale=none&depth=2', { headers }, ), }) - const graphQLReq = await createPayloadRequest({ + const graphQLReq = await createPayloadReqFromWebRequest({ config: payload.config, request: new Request('http://localhost/api/graphql', { headers }), }) diff --git a/test/uploads/int.spec.ts b/test/uploads/int.spec.ts index 0cfa08f4d84..0c3019e4079 100644 --- a/test/uploads/int.spec.ts +++ b/test/uploads/int.spec.ts @@ -6,7 +6,7 @@ import fs from 'fs' import { createServer } from 'http' import os from 'os' import path from 'path' -import { _internal_safeFetchGlobal, createPayloadRequest, getFileByPath } from 'payload' +import { _internal_safeFetchGlobal, createPayloadReqFromWebRequest, getFileByPath } from 'payload' import { fileURLToPath } from 'url' import { promisify } from 'util' import { expect, vitest } from 'vitest' @@ -2184,7 +2184,7 @@ test.suite('Collections - Uploads', { config: './config.ts', resetBetweenTests: const configuredOrigin = `http://localhost:${configuredPort}` const alternateOrigin = `http://localhost:${alternatePort}` - const req = await createPayloadRequest({ + const req = await createPayloadReqFromWebRequest({ config: payload.config, request: new Request(configuredOrigin, { headers: new Headers({ @@ -2229,7 +2229,7 @@ test.suite('Collections - Uploads', { config: './config.ts', resetBetweenTests: const port = (server.address() as AddressInfo).port const requestOrigin = `http://localhost:${port}` - const req = await createPayloadRequest({ + const req = await createPayloadReqFromWebRequest({ config: payload.config, request: new Request(requestOrigin, { headers: new Headers({ @@ -2306,7 +2306,7 @@ test.suite('Collections - Uploads', { config: './config.ts', resetBetweenTests: const configuredPort = (configuredServer.address() as AddressInfo).port const configuredOrigin = `http://localhost:${configuredPort}` - const req = await createPayloadRequest({ + const req = await createPayloadReqFromWebRequest({ config: payload.config, request: new Request(configuredOrigin, { headers: new Headers({ cookie: testCookies, origin: configuredOrigin }), @@ -2367,7 +2367,7 @@ test.suite('Collections - Uploads', { config: './config.ts', resetBetweenTests: status: 200, }), ) - const req = await createPayloadRequest({ + const req = await createPayloadReqFromWebRequest({ config: payload.config, request: new Request('https://app.example.com', { headers: new Headers({ cookie: 'payload-token=123; other-cookie=456' }), @@ -2398,7 +2398,7 @@ test.suite('Collections - Uploads', { config: './config.ts', resetBetweenTests: test.for(['http://[', 'ftp://files.example.com/image.png', 'data:text/plain,image'])( 'should reject unsupported file URL %s', async (url, { payload }) => { - const req = await createPayloadRequest({ + const req = await createPayloadReqFromWebRequest({ config: payload.config, request: new Request('https://app.example.com'), }) @@ -2465,7 +2465,7 @@ test.suite('Collections - Uploads', { config: './config.ts', resetBetweenTests: }) await new Promise((res) => configuredServer.listen(0, undefined, undefined, res)) const configuredOrigin = `http://localhost:${(configuredServer.address() as AddressInfo).port}` - const req = await createPayloadRequest({ + const req = await createPayloadReqFromWebRequest({ config: payload.config, request: new Request(configuredOrigin, { headers: new Headers({ cookie: 'payload-token=123; other-cookie=456' }), From 1b34d66a5f05c22869a18187710f9ee479954bbe Mon Sep 17 00:00:00 2001 From: Jake Fletcher Date: Wed, 23 Sep 2026 15:11:01 -0400 Subject: [PATCH 03/15] fix: preserve Web request locale state --- .../createPayloadReqFromWebRequest.spec.ts | 78 +++++++++++++++++-- .../createPayloadReqFromWebRequest.ts | 5 ++ 2 files changed, 77 insertions(+), 6 deletions(-) diff --git a/packages/payload/src/utilities/createPayloadReqFromWebRequest.spec.ts b/packages/payload/src/utilities/createPayloadReqFromWebRequest.spec.ts index 1e9c54dbbb6..a3de3498ee6 100644 --- a/packages/payload/src/utilities/createPayloadReqFromWebRequest.spec.ts +++ b/packages/payload/src/utilities/createPayloadReqFromWebRequest.spec.ts @@ -6,6 +6,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { executeAuthStrategies } from '../auth/executeAuthStrategies.js' import { getPayload } from '../index.js' +import { addLocalesToRequestFromData } from './addLocalesToRequest.js' import { createPayloadReq } from './createPayloadReq.js' import { createPayloadReqFromWebRequest } from './createPayloadReqFromWebRequest.js' @@ -70,6 +71,13 @@ const payload = { config, } as unknown as Payload +const useRealCreatePayloadReq = async () => { + const actual = + await vi.importActual('./createPayloadReq.js') + + vi.mocked(createPayloadReq).mockImplementation(actual.createPayloadReq) +} + describe('createPayloadReqFromWebRequest', () => { beforeEach(() => { vi.mocked(getPayload).mockReset().mockResolvedValue(payload) @@ -97,9 +105,7 @@ describe('createPayloadReqFromWebRequest', () => { ) expect((request as PayloadRequest).i18n).toBe(i18n) expect((request as PayloadRequest).query).toEqual({ depth: '2', locale: 'en' }) - expect(executeAuthStrategies).toHaveBeenCalledWith( - expect.objectContaining({ req: request }), - ) + expect(executeAuthStrategies).toHaveBeenCalledWith(expect.objectContaining({ req: request })) expect(vi.mocked(createPayloadReq).mock.invocationCallOrder[0]).toBeLessThan( vi.mocked(executeAuthStrategies).mock.invocationCallOrder[0], ) @@ -149,9 +155,7 @@ describe('createPayloadReqFromWebRequest', () => { }) it('passes sanitized fallback locale state to common request initialization', async () => { - const request = new Request( - 'http://localhost/api/posts?locale=de&fallbackLocale=unsupported', - ) + const request = new Request('http://localhost/api/posts?locale=de&fallbackLocale=unsupported') await createPayloadReqFromWebRequest({ config, request }) @@ -162,4 +166,66 @@ describe('createPayloadReqFromWebRequest', () => { }), ) }) + + it('preserves parsed locale fields when localization is disabled', async () => { + const unlocalizedConfig = { + ...config, + localization: false, + } as SanitizedConfig + const unlocalizedPayload = { + config: unlocalizedConfig, + } as unknown as Payload + + vi.mocked(getPayload).mockResolvedValue(unlocalizedPayload) + await useRealCreatePayloadReq() + + const result = await createPayloadReqFromWebRequest({ + config: unlocalizedConfig, + request: new Request('http://localhost/api/posts?locale=de&fallbackLocale=en'), + }) + + expect(result.locale).toBe('de') + expect(result.fallbackLocale).toBe('en') + }) + + it('allows a body locale when URL locale is absent and fallback is disabled', async () => { + const noFallbackConfig = { + ...config, + localization: { + ...config.localization, + fallback: false, + }, + } as SanitizedConfig + const noFallbackPayload = { + config: noFallbackConfig, + } as unknown as Payload + + vi.mocked(getPayload).mockResolvedValue(noFallbackPayload) + await useRealCreatePayloadReq() + + const result = await createPayloadReqFromWebRequest({ + config: noFallbackConfig, + request: new Request('http://localhost/api/posts'), + }) + + expect(result.locale).toBeNull() + + result.data = { locale: 'de' } + addLocalesToRequestFromData(result) + + expect(result.locale).toBe('de') + }) + + it('preserves fallback locale arrays through common request initialization', async () => { + await useRealCreatePayloadReq() + + const result = await createPayloadReqFromWebRequest({ + config, + request: new Request( + 'http://localhost/api/posts?locale=de&fallbackLocale[]=en&fallbackLocale[]=de', + ), + }) + + expect(result.fallbackLocale).toEqual(['en', 'de']) + }) }) diff --git a/packages/payload/src/utilities/createPayloadReqFromWebRequest.ts b/packages/payload/src/utilities/createPayloadReqFromWebRequest.ts index 0d768260f5c..1a4abe48c19 100644 --- a/packages/payload/src/utilities/createPayloadReqFromWebRequest.ts +++ b/packages/payload/src/utilities/createPayloadReqFromWebRequest.ts @@ -118,6 +118,11 @@ export const createPayloadReqFromWebRequest = async ({ req: request, }) + Object.assign(req, { + fallbackLocale, + locale, + }) + const { responseHeaders, user } = await executeAuthStrategies({ canSetHeaders, headers: req.headers, From 9ea08ace15995c3b8b48139aae29246db79b7d2f Mon Sep 17 00:00:00 2001 From: Jake Fletcher Date: Wed, 23 Sep 2026 15:24:25 -0400 Subject: [PATCH 04/15] refactor!: rename admin request setup to getAdminContext --- .../src/adapters/handleServerFunctions.ts | 6 +- packages/next/src/adapters/layout.tsx | 6 +- packages/next/src/adapters/views.tsx | 6 +- ...sc.spec.ts => getAdminContext.rsc.spec.ts} | 22 ++--- .../next/src/utilities/getAdminContext.ts | 33 +++++++ packages/next/src/utilities/initReq.ts | 33 ------- packages/payload/src/admin/functions/index.ts | 4 +- ...nitReq.spec.ts => getAdminContext.spec.ts} | 99 +++++++++++-------- .../admin/{initReq.ts => getAdminContext.ts} | 61 ++++++------ packages/payload/src/admin/types.ts | 2 +- packages/payload/src/admin/views/dashboard.ts | 4 +- packages/payload/src/exports/internal.ts | 10 +- .../src/layouts/Root/getLayoutData.ts | 4 +- .../src/utilities/devConfigReload.server.ts | 2 +- ...spec.ts => getAdminContext.server.spec.ts} | 20 ++-- .../src/utilities/getAdminContext.server.ts | 24 +++++ .../src/utilities/getRequestI18n.server.ts | 2 +- .../src/utilities/handleServerFunctions.ts | 4 +- .../src/utilities/initReq.server.ts | 21 ---- .../src/utilities/loadAdminPage.tsx | 18 ++-- packages/ui/src/layouts/Root/index.tsx | 18 ++-- .../ui/src/utilities/handleServerFunctions.ts | 23 +++-- packages/ui/src/views/NotFound/page.tsx | 19 ++-- packages/ui/src/views/Root/index.tsx | 12 +-- 24 files changed, 234 insertions(+), 219 deletions(-) rename packages/next/src/utilities/{initReq.rsc.spec.ts => getAdminContext.rsc.spec.ts} (83%) create mode 100644 packages/next/src/utilities/getAdminContext.ts delete mode 100644 packages/next/src/utilities/initReq.ts rename packages/payload/src/admin/{initReq.spec.ts => getAdminContext.spec.ts} (80%) rename packages/payload/src/admin/{initReq.ts => getAdminContext.ts} (75%) rename packages/tanstack-start/src/utilities/{initReq.server.spec.ts => getAdminContext.server.spec.ts} (69%) create mode 100644 packages/tanstack-start/src/utilities/getAdminContext.server.ts delete mode 100644 packages/tanstack-start/src/utilities/initReq.server.ts diff --git a/packages/next/src/adapters/handleServerFunctions.ts b/packages/next/src/adapters/handleServerFunctions.ts index 1cde77691b2..94defce1bcc 100644 --- a/packages/next/src/adapters/handleServerFunctions.ts +++ b/packages/next/src/adapters/handleServerFunctions.ts @@ -1,8 +1,8 @@ import { createServerFunctionHandler } from '@payloadcms/ui/utilities/handleServerFunctions' -import { initReq } from '../utilities/initReq.js' +import { getAdminContext } from '../utilities/getAdminContext.js' export const handleServerFunctions = createServerFunctionHandler({ - initReq: ({ configPromise, importMap }) => - initReq({ configPromise, importMap, key: 'RootLayout' }), + getAdminContext: ({ configPromise, importMap }) => + getAdminContext({ configPromise, importMap, key: 'RootLayout' }), }) diff --git a/packages/next/src/adapters/layout.tsx b/packages/next/src/adapters/layout.tsx index de9573605fd..fc6d4449a78 100644 --- a/packages/next/src/adapters/layout.tsx +++ b/packages/next/src/adapters/layout.tsx @@ -3,7 +3,7 @@ import { RootLayout as UIRootLayout } from '@payloadcms/ui/layouts' import { Inter, Roboto_Mono } from 'next/font/google' import React from 'react' -import { initReq } from '../utilities/initReq.js' +import { getAdminContext } from '../utilities/getAdminContext.js' import { NextRouterAdapter } from './router.js' // CSS import lives here (not in `@payloadcms/ui`) so the esbuild bundle that produces // `@payloadcms/next/css` (`dist/prod/styles.css`) can follow the CSS chain. esbuild @@ -34,7 +34,7 @@ export { metadata } from '@payloadcms/ui/layouts' type Props = Omit< React.ComponentProps, - 'additionalDependencyChecks' | 'fonts' | 'initReq' | 'RouterAdapter' + 'additionalDependencyChecks' | 'fonts' | 'getAdminContext' | 'RouterAdapter' > export const RootLayout = (props: Props) => ( @@ -45,7 +45,7 @@ export const RootLayout = (props: Props) => ( { className: inter.className, variable: inter.variable }, { className: robotoMono.className, variable: robotoMono.variable }, ]} - initReq={initReq} + getAdminContext={getAdminContext} RouterAdapter={NextRouterAdapter} /> ) diff --git a/packages/next/src/adapters/views.tsx b/packages/next/src/adapters/views.tsx index da837309a20..0a6ae788c02 100644 --- a/packages/next/src/adapters/views.tsx +++ b/packages/next/src/adapters/views.tsx @@ -11,7 +11,7 @@ import { renderRoot } from '@payloadcms/ui/views/Root' import { defaultAdminViews } from '@payloadcms/ui/views/Root/adminViews' import { notFound, redirect } from 'next/navigation.js' -import { initReq } from '../utilities/initReq.js' +import { getAdminContext } from '../utilities/getAdminContext.js' export const adminViews: AdminViewAdapter = defaultAdminViews @@ -27,6 +27,6 @@ type PageProps = { } export const RootPage = (props: PageProps) => - renderRoot({ ...props, adminViews, initReq, notFound, redirect }) + renderRoot({ ...props, adminViews, getAdminContext, notFound, redirect }) -export const NotFoundPage = (props: PageProps) => renderNotFoundPage({ ...props, initReq }) +export const NotFoundPage = (props: PageProps) => renderNotFoundPage({ ...props, getAdminContext }) diff --git a/packages/next/src/utilities/initReq.rsc.spec.ts b/packages/next/src/utilities/getAdminContext.rsc.spec.ts similarity index 83% rename from packages/next/src/utilities/initReq.rsc.spec.ts rename to packages/next/src/utilities/getAdminContext.rsc.spec.ts index 9e71d6091ff..b01ca4df59b 100644 --- a/packages/next/src/utilities/initReq.rsc.spec.ts +++ b/packages/next/src/utilities/getAdminContext.rsc.spec.ts @@ -5,21 +5,21 @@ import { createRequire } from 'node:module' import type React from 'react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { initReq } from './initReq.js' +import { getAdminContext } from './getAdminContext.js' -const { counters, initPayloadReq } = vi.hoisted(() => ({ +const { counters, getPayloadAdminContext } = vi.hoisted(() => ({ counters: { locale: 0, partial: 0, request: 0, }, - initPayloadReq: vi.fn(), + getPayloadAdminContext: vi.fn(), })) vi.mock('react', () => createRequire(import.meta.url)('react')) vi.mock('payload/internal', () => ({ - initReq: initPayloadReq, + getAdminContext: getPayloadAdminContext, })) vi.mock('../adapters/server.js', () => ({ @@ -46,9 +46,9 @@ const importMap = {} as ImportMap async function renderNavigation(): Promise { async function Navigation() { await Promise.all([ - initReq({ configPromise, importMap, key: 'RootLayout' }), - initReq({ configPromise, importMap, key: 'RootLayout' }), - initReq({ configPromise, importMap, key: 'initPage' }), + getAdminContext({ configPromise, importMap, key: 'RootLayout' }), + getAdminContext({ configPromise, importMap, key: 'RootLayout' }), + getAdminContext({ configPromise, importMap, key: 'initPage' }), ]) return null @@ -62,13 +62,13 @@ async function renderNavigation(): Promise { async function renderNavigationWithOverrides(): Promise { async function Navigation() { await Promise.all([ - initReq({ + getAdminContext({ configPromise, importMap, key: 'initPage', overrides: { context: { source: 'first' } }, }), - initReq({ + getAdminContext({ configPromise, importMap, key: 'initPage', @@ -84,12 +84,12 @@ async function renderNavigationWithOverrides(): Promise { await new Response(stream).arrayBuffer() } -describe('Next initReq RSC cache', () => { +describe('Next getAdminContext RSC cache', () => { beforeEach(() => { counters.locale = 0 counters.partial = 0 counters.request = 0 - initPayloadReq.mockReset().mockImplementation(async ({ cache, key, overrides }) => { + getPayloadAdminContext.mockReset().mockImplementation(async ({ cache, key, overrides }) => { await cache.getPartial(async () => { counters.partial += 1 return {} diff --git a/packages/next/src/utilities/getAdminContext.ts b/packages/next/src/utilities/getAdminContext.ts new file mode 100644 index 00000000000..44dbf983bf9 --- /dev/null +++ b/packages/next/src/utilities/getAdminContext.ts @@ -0,0 +1,33 @@ +import type { GetAdminContextResult } from 'payload' +import type { AdminContextCache, GetAdminContextArgs, PartialAdminContext } from 'payload/internal' + +import { getAdminContext as getPayloadAdminContext } from 'payload/internal' + +import { nextServerAdapter } from '../adapters/server.js' +import { selectiveCache } from './selectiveCache.js' + +const partialContextCache = selectiveCache('partialContext') +const localeCache = selectiveCache>('locale') +const adminContextCache = selectiveCache('adminContext') + +const cache: AdminContextCache = { + getLocale: (resolveLocale, ...key) => localeCache.get({ create: resolveLocale, key }), + getPartial: (createPartialContext) => + partialContextCache.get({ create: createPartialContext, key: ['global'] }), + getRequest: (createContext, key, ...cacheArgs) => + adminContextCache.get({ + create: createContext, + key: [key, ...cacheArgs], + }), +} + +type NextGetAdminContextArgs = { + key: string +} & Omit + +export const getAdminContext = (args: NextGetAdminContextArgs) => + getPayloadAdminContext({ + ...args, + cache, + serverAdapter: nextServerAdapter, + }) diff --git a/packages/next/src/utilities/initReq.ts b/packages/next/src/utilities/initReq.ts deleted file mode 100644 index 13b5bf7d8ea..00000000000 --- a/packages/next/src/utilities/initReq.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { InitReqResult } from 'payload' -import type { InitReqArgs, InitReqCache, InitReqPartialResult } from 'payload/internal' - -import { initReq as initPayloadReq } from 'payload/internal' - -import { nextServerAdapter } from '../adapters/server.js' -import { selectiveCache } from './selectiveCache.js' - -const partialResultCache = selectiveCache('partialResult') -const localeCache = selectiveCache>('locale') -const requestResultCache = selectiveCache('requestResult') - -const cache: InitReqCache = { - getLocale: (resolveLocale, ...key) => localeCache.get({ create: resolveLocale, key }), - getPartial: (createPartialResult) => - partialResultCache.get({ create: createPartialResult, key: ['global'] }), - getRequest: (createRequestResult, key, ...cacheArgs) => - requestResultCache.get({ - create: createRequestResult, - key: [key, ...cacheArgs], - }), -} - -type NextInitReqArgs = { - key: string -} & Omit - -export const initReq = (args: NextInitReqArgs) => - initPayloadReq({ - ...args, - cache, - serverAdapter: nextServerAdapter, - }) diff --git a/packages/payload/src/admin/functions/index.ts b/packages/payload/src/admin/functions/index.ts index 3d23dfd31bd..08c07b65a62 100644 --- a/packages/payload/src/admin/functions/index.ts +++ b/packages/payload/src/admin/functions/index.ts @@ -16,7 +16,7 @@ import type { PayloadRequest, Sort, Where } from '../../types/index.js' import type { ColumnsFromURL } from '../../utilities/transformColumnPreferences.js' import type { ComponentRenderer } from '../adapters/render.js' -export type InitReqResult = { +export type GetAdminContextResult = { cookies: Map // TODO: Remove in 4.0. Duplicative, already available in req.headers headers: Headers @@ -32,7 +32,7 @@ export type InitReqResult = { export type DefaultServerFunctionArgs = { importMap: ImportMap renderComponent?: ComponentRenderer -} & Pick +} & Pick export type ServerFunctionArgs = { args: Record diff --git a/packages/payload/src/admin/initReq.spec.ts b/packages/payload/src/admin/getAdminContext.spec.ts similarity index 80% rename from packages/payload/src/admin/initReq.spec.ts rename to packages/payload/src/admin/getAdminContext.spec.ts index e6b4fcf5f34..2159a87c083 100644 --- a/packages/payload/src/admin/initReq.spec.ts +++ b/packages/payload/src/admin/getAdminContext.spec.ts @@ -3,12 +3,12 @@ import type { ImportMap } from '../cli/commands/generateImportMap/generateImport import type { SanitizedConfig } from '../config/types.js' import type { Payload } from '../index.js' import type { ServerAdapter } from './adapters/server.js' -import type { InitReqCache, InitReqPartialResult } from './initReq.js' -import type { InitReqResult } from './functions/index.js' +import type { AdminContextCache, PartialAdminContext } from './getAdminContext.js' +import type { GetAdminContextResult } from './functions/index.js' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { initReq } from './initReq.js' +import { getAdminContext } from './getAdminContext.js' const { applyUserReadAccess, @@ -135,23 +135,23 @@ const serverAdapter = { unauthorized: vi.fn(), } as unknown as ServerAdapter -const createExecutingCache = (): InitReqCache => ({ - getPartial: vi.fn((createPartialResult: () => Promise) => - createPartialResult(), +const createExecutingCache = (): AdminContextCache => ({ + getPartial: vi.fn((createPartialContext: () => Promise) => + createPartialContext(), ), - getRequest: vi.fn((createRequestResult: () => Promise) => createRequestResult()), + getRequest: vi.fn((createContext: () => Promise) => createContext()), }) -const createReusingCache = (): InitReqCache => { +const createReusingCache = (): AdminContextCache => { const localeResults: Array<{ cacheArgs: unknown[] - result: Pick + result: Pick }> = [] - let partialResult: InitReqPartialResult | undefined - const requestResults: Array<{ + let partialContext: PartialAdminContext | undefined + const contexts: Array<{ cacheArgs: unknown[] key: string - result: InitReqResult + result: GetAdminContextResult }> = [] return { @@ -171,12 +171,12 @@ const createReusingCache = (): InitReqCache => { return result }), - getPartial: vi.fn(async (createPartialResult) => { - partialResult ??= await createPartialResult() - return partialResult + getPartial: vi.fn(async (createPartialContext) => { + partialContext ??= await createPartialContext() + return partialContext }), - getRequest: vi.fn(async (createRequestResult, key, ...cacheArgs) => { - const cached = requestResults.find( + getRequest: vi.fn(async (createContext, key, ...cacheArgs) => { + const cached = contexts.find( (entry) => entry.key === key && entry.cacheArgs.length === cacheArgs.length && @@ -187,15 +187,15 @@ const createReusingCache = (): InitReqCache => { return cached.result } - const result = await createRequestResult() - requestResults.push({ cacheArgs, key, result }) + const result = await createContext() + contexts.push({ cacheArgs, key, result }) return result }), } } -describe('initReq', () => { +describe('getAdminContext', () => { beforeEach(() => { applyUserReadAccess.mockReset().mockImplementation(async ({ user }) => ({ collection: user.collection, @@ -210,7 +210,7 @@ describe('initReq', () => { }) it('should derive the URL and a nested query from requestURL', async () => { - const result = await initReq({ + const result = await getAdminContext({ configPromise: config, importMap, requestURL: 'https://example.com/admin?locale=es&where%5Btitle%5D%5Bequals%5D=Hello', @@ -233,7 +233,7 @@ describe('initReq', () => { it('should persist an authenticated request locale', async () => { getPayload.mockResolvedValue({ ...payload, config: localizedConfig }) - const result = await initReq({ + const result = await getAdminContext({ configPromise: localizedConfig, importMap, requestURL: 'https://example.com/admin?locale=es', @@ -257,7 +257,7 @@ describe('initReq', () => { }) getPayload.mockResolvedValue({ ...payload, config: localizedConfig }) - const result = await initReq({ + const result = await getAdminContext({ configPromise: localizedConfig, importMap, requestURL: 'https://example.com/admin?locale=es', @@ -271,7 +271,7 @@ describe('initReq', () => { }) it('should prefer explicit request overrides over requestURL', async () => { - const result = await initReq({ + const result = await getAdminContext({ configPromise: config, importMap, overrides: { @@ -293,7 +293,7 @@ describe('initReq', () => { }) it('should ignore an invalid requestURL', async () => { - const result = await initReq({ + const result = await getAdminContext({ configPromise: config, importMap, requestURL: 'not a valid URL', @@ -305,8 +305,8 @@ describe('initReq', () => { }) it('should initialize each request directly when no cache is supplied', async () => { - await initReq({ configPromise: config, importMap, serverAdapter }) - await initReq({ configPromise: config, importMap, serverAdapter }) + await getAdminContext({ configPromise: config, importMap, serverAdapter }) + await getAdminContext({ configPromise: config, importMap, serverAdapter }) expect(authenticate).toHaveBeenCalledTimes(2) expect(getAccessResults).toHaveBeenCalledTimes(2) @@ -315,7 +315,7 @@ describe('initReq', () => { it('should use both cache stages when a cache is supplied', async () => { const cache = createExecutingCache() - await initReq({ + await getAdminContext({ cache, configPromise: config, importMap, @@ -327,19 +327,19 @@ describe('initReq', () => { expect(cache.getRequest).toHaveBeenCalledWith(expect.any(Function), 'initPage', undefined) }) - it('should reject a cache without a request key', async () => { + it('requires a key when cache is supplied', async () => { await expect( - initReq({ + getAdminContext({ cache: createExecutingCache(), configPromise: config, importMap, serverAdapter, }), - ).rejects.toThrow('initReq requires a key when cache is provided') + ).rejects.toThrow('getAdminContext requires a key when cache is provided') }) it('should enable cron and forward canSetHeaders to auth strategies', async () => { - await initReq({ + await getAdminContext({ canSetHeaders: true, configPromise: config, importMap, @@ -376,10 +376,10 @@ describe('initReq', () => { }, serverAdapter, } - const first = await initReq(args) + const first = await getAdminContext(args) first.req.context.source = 'mutated' - const second = await initReq(args) + const second = await getAdminContext(args) expect(first.req).not.toBe(second.req) expect(first.req.context).not.toBe(second.req.context) @@ -388,6 +388,21 @@ describe('initReq', () => { }) }) + it('should produce distinct full results for distinct cache keys', async () => { + const cache = createReusingCache() + const args = { + cache, + configPromise: config, + importMap, + serverAdapter, + } + + const rootLayoutResult = await getAdminContext({ ...args, key: 'RootLayout' }) + const pageResult = await getAdminContext({ ...args, key: 'initPage' }) + + expect(rootLayoutResult).not.toBe(pageResult) + }) + it('should keep the complete req.user and return the user with read access', async () => { authenticate.mockResolvedValueOnce({ responseHeaders: new Headers(), @@ -398,7 +413,7 @@ describe('initReq', () => { }, }) - const result = await initReq({ configPromise: config, importMap, serverAdapter }) + const result = await getAdminContext({ configPromise: config, importMap, serverAdapter }) expect(result.req.user).toHaveProperty('role', 'admin') expect(result.user).toEqual({ collection: 'users', id: 'user-id' }) @@ -420,11 +435,11 @@ describe('initReq', () => { serverAdapter, } - const first = await initReq({ + const first = await getAdminContext({ ...args, overrides: { context: { source: 'first' } }, }) - const second = await initReq({ + const second = await getAdminContext({ ...args, overrides: { context: { source: 'second' } }, }) @@ -446,8 +461,8 @@ describe('initReq', () => { serverAdapter, } - const rootLayoutResult = await initReq({ ...args, key: 'RootLayout' }) - const pageResult = await initReq({ ...args, key: 'initPage' }) + const rootLayoutResult = await getAdminContext({ ...args, key: 'RootLayout' }) + const pageResult = await getAdminContext({ ...args, key: 'initPage' }) expect(rootLayoutResult.locale).toMatchObject({ code: 'es' }) expect(pageResult.locale).toMatchObject({ code: 'es' }) @@ -457,7 +472,7 @@ describe('initReq', () => { it('should use anonymous access when user read access fails', async () => { applyUserReadAccess.mockRejectedValueOnce(new Error('read access failed')) - const result = await initReq({ configPromise: config, importMap, serverAdapter }) + const result = await getAdminContext({ configPromise: config, importMap, serverAdapter }) expect(result.req.user).toBeNull() expect(result.user).toBeNull() @@ -465,7 +480,7 @@ describe('initReq', () => { }) it('should preserve an explicit request user override', async () => { - const result = await initReq({ + const result = await getAdminContext({ configPromise: config, importMap, overrides: { @@ -485,7 +500,7 @@ describe('initReq', () => { }) it('should preserve an explicit null user override', async () => { - const result = await initReq({ + const result = await getAdminContext({ configPromise: config, importMap, overrides: { diff --git a/packages/payload/src/admin/initReq.ts b/packages/payload/src/admin/getAdminContext.ts similarity index 75% rename from packages/payload/src/admin/initReq.ts rename to packages/payload/src/admin/getAdminContext.ts index e3ef2fc9e75..635a3ea7978 100644 --- a/packages/payload/src/admin/initReq.ts +++ b/packages/payload/src/admin/getAdminContext.ts @@ -6,8 +6,9 @@ import * as qs from 'qs-esm' import type { ImportMap } from '../cli/commands/generateImportMap/generateImportMap.js' import type { SanitizedConfig } from '../config/types.js' import type { PayloadRequest } from '../types/index.js' +import type { CreatePayloadReqArgs } from '../utilities/createPayloadReq.js' import type { ServerAdapter } from './adapters/server.js' -import type { InitReqResult } from './functions/index.js' +import type { GetAdminContextResult } from './functions/index.js' import { applyUserReadAccess } from '../auth/applyUserReadAccess.js' import { executeAuthStrategies } from '../auth/executeAuthStrategies.js' @@ -18,50 +19,50 @@ import { getRequestLanguage } from '../utilities/getRequestLanguage.js' import { parseCookies } from '../utilities/parseCookies.js' import { getRequestLocale } from './getRequestLocale.js' -export type InitReqPartialResult = { +export type PartialAdminContext = { i18n: I18nClient -} & Pick & +} & Pick & Pick -/** Framework-provided request-scoped caching hooks used to deduplicate request initialization. */ -export type InitReqCache = { - /** Reuses locale preference resolution across request results. */ +/** Framework-provided request-scoped caching hooks used to deduplicate admin context creation. */ +export type AdminContextCache = { + /** Reuses locale preference resolution across admin contexts. */ getLocale?: ( - resolveLocale: () => Promise>, + resolveLocale: () => Promise>, ...cacheArgs: unknown[] - ) => Promise> + ) => Promise> /** Reuses Payload, i18n, and authentication state within the current request. */ getPartial: ( - createPartialResult: () => Promise, - ) => Promise - /** Reuses a complete initialized request for the supplied key and cache arguments. */ + createPartialContext: () => Promise, + ) => Promise + /** Reuses a complete admin context for the supplied key and cache arguments. */ getRequest: ( - createRequestResult: () => Promise, + createContext: () => Promise, key: string, ...cacheArgs: unknown[] - ) => Promise + ) => Promise } -export type InitReqArgs = { +export type GetAdminContextArgs = { /** * Optional framework-owned request-scoped cache. * Framework adapters control its lifetime to prevent request state from leaking between requests. */ - cache?: InitReqCache + cache?: AdminContextCache canSetHeaders?: boolean configPromise: Promise | SanitizedConfig importMap: ImportMap - /** Identifies the complete request result within `cache`; required when a cache is supplied. */ + /** Identifies the complete admin context within `cache`; required when a cache is supplied. */ key?: string - overrides?: Omit[0], 'payload'> + overrides?: Omit requestURL?: string serverAdapter: ServerAdapter } /** - * Initializes the request state used by framework adapters to render the admin panel. + * Gets the request context used by framework adapters to render the admin panel. */ -export async function initReq({ +export async function getAdminContext({ cache, canSetHeaders, configPromise, @@ -70,15 +71,15 @@ export async function initReq({ overrides, requestURL, serverAdapter, -}: InitReqArgs): Promise { +}: GetAdminContextArgs): Promise { if (cache && !key) { - throw new Error('initReq requires a key when cache is provided') + throw new Error('getAdminContext requires a key when cache is provided') } const headers = await serverAdapter.getHeaders() const cookies = parseCookies(headers) - const createPartialResult = async (): Promise => { + const createPartialContext = async (): Promise => { const config = await configPromise const payload = await getPayload({ config, cron: true, importMap }) const languageCode = getRequestLanguage({ @@ -108,12 +109,12 @@ export async function initReq({ } } - const partialResult = cache - ? await cache.getPartial(createPartialResult) - : await createPartialResult() + const partialContext = cache + ? await cache.getPartial(createPartialContext) + : await createPartialContext() - const createRequestResult = async (): Promise => { - const { i18n, languageCode, payload, responseHeaders, user } = partialResult + const createContext = async (): Promise => { + const { i18n, languageCode, payload, responseHeaders, user } = partialContext const { req: reqOverrides, ...optionsOverrides } = overrides || {} const hasOptionsUserOverride = Object.hasOwn(optionsOverrides, 'user') const hasReqUserOverride = Object.hasOwn(reqOverrides ?? {}, 'user') @@ -140,7 +141,7 @@ export async function initReq({ req.user = null } - const resolveLocale = async (): Promise> => ({ + const resolveLocale = async (): Promise> => ({ locale: await getRequestLocale({ req }), }) const { locale } = cache?.getLocale @@ -195,8 +196,8 @@ export async function initReq({ } const result = cache - ? await cache.getRequest(createRequestResult, key!, overrides) - : await createRequestResult() + ? await cache.getRequest(createContext, key!, overrides) + : await createContext() return { ...result, diff --git a/packages/payload/src/admin/types.ts b/packages/payload/src/admin/types.ts index 0f05339694c..a0e329bb977 100644 --- a/packages/payload/src/admin/types.ts +++ b/packages/payload/src/admin/types.ts @@ -591,7 +591,7 @@ export type DocumentSlots = { export type { BuildTableStateArgs, DefaultServerFunctionArgs, - InitReqResult, + GetAdminContextResult, ListQuery, ServerFunction, ServerFunctionArgs, diff --git a/packages/payload/src/admin/views/dashboard.ts b/packages/payload/src/admin/views/dashboard.ts index 49a1dc63a6d..fb46daac659 100644 --- a/packages/payload/src/admin/views/dashboard.ts +++ b/packages/payload/src/admin/views/dashboard.ts @@ -1,4 +1,4 @@ -import type { InitReqResult, TypedWidget, WidgetSlug } from '../../index.js' +import type { GetAdminContextResult, TypedWidget, WidgetSlug } from '../../index.js' import type { ComponentRenderer } from '../adapters/render.js' export enum EntityType { @@ -33,4 +33,4 @@ export type WidgetServerProps>] extends [never] ? string : WidgetSlugFromWidget<{ data?: unknown } & Exclude> -} & Pick +} & Pick diff --git a/packages/payload/src/exports/internal.ts b/packages/payload/src/exports/internal.ts index f859cef43b1..b4da8c7fbd8 100644 --- a/packages/payload/src/exports/internal.ts +++ b/packages/payload/src/exports/internal.ts @@ -3,11 +3,11 @@ */ export { - initReq, - type InitReqArgs, - type InitReqCache, - type InitReqPartialResult, -} from '../admin/initReq.js' + type AdminContextCache, + getAdminContext, + type GetAdminContextArgs, + type PartialAdminContext, +} from '../admin/getAdminContext.js' export { applyUserReadAccess } from '../auth/applyUserReadAccess.js' export { createCLI } from '../cli/index.js' export { getCommandInput } from '../cli/runtime/getCommandInput.js' diff --git a/packages/tanstack-start/src/layouts/Root/getLayoutData.ts b/packages/tanstack-start/src/layouts/Root/getLayoutData.ts index 9e34fe578c7..81fcb9405ba 100644 --- a/packages/tanstack-start/src/layouts/Root/getLayoutData.ts +++ b/packages/tanstack-start/src/layouts/Root/getLayoutData.ts @@ -19,7 +19,7 @@ import { Outlet } from '@tanstack/react-router' import { applyLocaleFiltering } from 'payload/shared' import { createElement } from 'react' -import { initReq } from '../../utilities/initReq.server.js' +import { getAdminContext } from '../../utilities/getAdminContext.server.js' export type RootLayoutData = { clientConfig: ClientConfig @@ -66,7 +66,7 @@ export async function getLayoutData({ payload: { config }, }, user, - } = await initReq({ configPromise, importMap }) + } = await getAdminContext({ configPromise, importMap }) const theme = getRequestTheme({ config, cookies, headers }) const isEmbedded = getRequestEmbed({ config, cookies }) diff --git a/packages/tanstack-start/src/utilities/devConfigReload.server.ts b/packages/tanstack-start/src/utilities/devConfigReload.server.ts index 509d34e59f6..f7f1ec5f6c1 100644 --- a/packages/tanstack-start/src/utilities/devConfigReload.server.ts +++ b/packages/tanstack-start/src/utilities/devConfigReload.server.ts @@ -6,7 +6,7 @@ import { PAYLOAD_CONFIG_CHANGED_EVENT } from './devConfigReloadEvent.js' * Subscribes the Payload instance to the config-changed event that the * `payload:dev-config-reload` Vite plugin broadcasts. * - * Imported for its side effect by `initReq.server.ts`, the adapter's only + * Imported for its side effect by `getAdminContext.server.ts`, the adapter's only * `getPayload` call site, so the strategy is registered before an instance can * be built. Registration is module-scoped on both ends: this runs inside the * app's server graph and so resolves the same copy of `payload` that diff --git a/packages/tanstack-start/src/utilities/initReq.server.spec.ts b/packages/tanstack-start/src/utilities/getAdminContext.server.spec.ts similarity index 69% rename from packages/tanstack-start/src/utilities/initReq.server.spec.ts rename to packages/tanstack-start/src/utilities/getAdminContext.server.spec.ts index 45f3d9ca1ef..42061995798 100644 --- a/packages/tanstack-start/src/utilities/initReq.server.spec.ts +++ b/packages/tanstack-start/src/utilities/getAdminContext.server.spec.ts @@ -1,8 +1,8 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' -const { getRequest, initPayloadReq, tanstackServerAdapter } = vi.hoisted(() => ({ +const { getRequest, getPayloadAdminContext, tanstackServerAdapter } = vi.hoisted(() => ({ getRequest: vi.fn(), - initPayloadReq: vi.fn(), + getPayloadAdminContext: vi.fn(), tanstackServerAdapter: { getHeaders: vi.fn(), }, @@ -13,7 +13,7 @@ vi.mock('@tanstack/react-start/server', () => ({ })) vi.mock('payload/internal', () => ({ - initReq: initPayloadReq, + getAdminContext: getPayloadAdminContext, })) vi.mock('./devConfigReload.server.js', () => ({})) @@ -22,12 +22,12 @@ vi.mock('./serverAdapter.server.js', () => ({ tanstackServerAdapter, })) -import { initReq } from './initReq.server.js' +import { getAdminContext } from './getAdminContext.server.js' -describe('initReq', () => { +describe('getAdminContext', () => { beforeEach(() => { getRequest.mockReset().mockReturnValue(new Request('http://localhost/admin?locale=es')) - initPayloadReq.mockReset().mockResolvedValue({}) + getPayloadAdminContext.mockReset().mockResolvedValue({}) }) it('should provide the active request URL and default server adapter', async () => { @@ -36,9 +36,9 @@ describe('initReq', () => { importMap: {}, } - await initReq(args) + await getAdminContext(args) - expect(initPayloadReq).toHaveBeenCalledWith({ + expect(getPayloadAdminContext).toHaveBeenCalledWith({ ...args, requestURL: 'http://localhost/admin?locale=es', serverAdapter: tanstackServerAdapter, @@ -50,13 +50,13 @@ describe('initReq', () => { getHeaders: vi.fn(), } - await initReq({ + await getAdminContext({ configPromise: Promise.resolve({} as never), importMap: {}, serverAdapter: serverAdapter as never, }) - expect(initPayloadReq).toHaveBeenCalledWith( + expect(getPayloadAdminContext).toHaveBeenCalledWith( expect.objectContaining({ serverAdapter, }), diff --git a/packages/tanstack-start/src/utilities/getAdminContext.server.ts b/packages/tanstack-start/src/utilities/getAdminContext.server.ts new file mode 100644 index 00000000000..6a1d452dfaf --- /dev/null +++ b/packages/tanstack-start/src/utilities/getAdminContext.server.ts @@ -0,0 +1,24 @@ +import type { ServerAdapter } from 'payload' +import type { GetAdminContextArgs } from 'payload/internal' + +import { getRequest } from '@tanstack/react-start/server' +import { getAdminContext as getPayloadAdminContext } from 'payload/internal' + +// Registers the dev reload strategy before `getPayloadAdminContext` can build an instance. +// Side-effect only, and a no-op outside of dev serve. +import './devConfigReload.server.js' +import { tanstackServerAdapter } from './serverAdapter.server.js' + +type TanStackGetAdminContextArgs = { + serverAdapter?: ServerAdapter +} & Omit + +export const getAdminContext = ({ + serverAdapter = tanstackServerAdapter, + ...args +}: TanStackGetAdminContextArgs) => + getPayloadAdminContext({ + ...args, + requestURL: getRequest().url, + serverAdapter, + }) diff --git a/packages/tanstack-start/src/utilities/getRequestI18n.server.ts b/packages/tanstack-start/src/utilities/getRequestI18n.server.ts index 5b061f610b3..bab89f9c8bd 100644 --- a/packages/tanstack-start/src/utilities/getRequestI18n.server.ts +++ b/packages/tanstack-start/src/utilities/getRequestI18n.server.ts @@ -7,7 +7,7 @@ import { getRequestLanguage, parseCookies } from 'payload' /** * Resolves the client `I18n` for the current TanStack Start request without a - * full `initReq` (no `getPayload`/auth/access). Mirrors the Next.js adapter's + * full `getAdminContext` (no `getPayload`/auth/access). Mirrors the Next.js adapter's * `getNextRequestI18n` and feeds the shared `generatePageMetadata`, which only * needs `config` + `i18n` to translate admin-page titles. */ diff --git a/packages/tanstack-start/src/utilities/handleServerFunctions.ts b/packages/tanstack-start/src/utilities/handleServerFunctions.ts index c36c06d4e4a..2218afa8ded 100644 --- a/packages/tanstack-start/src/utilities/handleServerFunctions.ts +++ b/packages/tanstack-start/src/utilities/handleServerFunctions.ts @@ -1,9 +1,9 @@ import { createServerFunctionHandler } from '@payloadcms/ui/utilities/handleServerFunctions' -import { initReq } from './initReq.server.js' +import { getAdminContext } from './getAdminContext.server.js' import { serializeForRsc } from './serializeForRsc.js' export const handleServerFunctions = createServerFunctionHandler({ - initReq, + getAdminContext, transformResult: serializeForRsc, }) diff --git a/packages/tanstack-start/src/utilities/initReq.server.ts b/packages/tanstack-start/src/utilities/initReq.server.ts deleted file mode 100644 index 8a3b4675bba..00000000000 --- a/packages/tanstack-start/src/utilities/initReq.server.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { ServerAdapter } from 'payload' -import type { InitReqArgs } from 'payload/internal' - -import { getRequest } from '@tanstack/react-start/server' -import { initReq as initPayloadReq } from 'payload/internal' - -// Registers the dev reload strategy before `initPayloadReq` can build an instance. -// Side-effect only, and a no-op outside of dev serve. -import './devConfigReload.server.js' -import { tanstackServerAdapter } from './serverAdapter.server.js' - -type TanStackInitReqArgs = { - serverAdapter?: ServerAdapter -} & Omit - -export const initReq = ({ serverAdapter = tanstackServerAdapter, ...args }: TanStackInitReqArgs) => - initPayloadReq({ - ...args, - requestURL: getRequest().url, - serverAdapter, - }) diff --git a/packages/tanstack-start/src/utilities/loadAdminPage.tsx b/packages/tanstack-start/src/utilities/loadAdminPage.tsx index a6d874f2863..34628bc7a34 100644 --- a/packages/tanstack-start/src/utilities/loadAdminPage.tsx +++ b/packages/tanstack-start/src/utilities/loadAdminPage.tsx @@ -6,8 +6,8 @@ import { renderServerComponent } from '@tanstack/react-start/rsc' import type { AdminPageMetadata } from './meta.js' +import { getAdminContext } from './getAdminContext.server.js' import { getRequestI18n } from './getRequestI18n.server.js' -import { initReq } from './initReq.server.js' import { createPageRenderServerAdapter } from './serverAdapter.server.js' export type LoadAdminPageArgs = { @@ -132,8 +132,8 @@ const toAdminPageMetadata = (meta: MetaConfig): AdminPageMetadata => { * result. The framework adapter wraps this in a `createServerFn` that supplies * the app's `config` and generated `importMap`. * - * 1. Initializes the Payload request via the shared `renderRoot` orchestrator - * from `@payloadcms/ui`, passing a TanStack-bound `initReq`. The injected + * 1. Gets the Payload admin context via the shared `renderRoot` orchestrator + * from `@payloadcms/ui`, passing a TanStack-bound `getAdminContext`. The injected * page-render `ServerAdapter` records navigation intent and throws the * framework-agnostic error contract. * 2. Pipes the resulting React server tree through `renderServerComponent` @@ -175,12 +175,14 @@ export async function loadAdminPage({ const pageServerAdapter = createPageRenderServerAdapter(nav) let userAgent: string | undefined - // `renderRoot` calls `initReq` itself with its own overrides (query + // `renderRoot` calls `getAdminContext` itself with its own overrides (query // re-nesting, `urlSuffix`, `fallbackLocale`). Forward them, injecting the // page-render `ServerAdapter` so `req.server.redirect()` / `.notFound()` // is recorded + thrown rather than escaping as raw TanStack nav. - const boundInitReq: Parameters[0]['initReq'] = async (args) => { - const result = await initReq({ + const boundGetAdminContext: Parameters[0]['getAdminContext'] = async ( + args, + ) => { + const result = await getAdminContext({ configPromise: args.configPromise, importMap: args.importMap, overrides: args.overrides, @@ -208,8 +210,8 @@ export async function loadAdminPage({ const notFoundNode = await renderNotFoundPage({ config: Promise.resolve(config), + getAdminContext, importMap, - initReq, params: Promise.resolve({ segments: splatSegments }), searchParams: Promise.resolve(searchParams), }) @@ -223,8 +225,8 @@ export async function loadAdminPage({ const node = await renderRoot({ adminViews: defaultAdminViews, config: Promise.resolve(config), + getAdminContext: boundGetAdminContext, importMap, - initReq: boundInitReq, key: splat ?? '', notFound: pageServerAdapter.notFound, // `segments` is intentionally `undefined` for the admin root (`/admin`), diff --git a/packages/ui/src/layouts/Root/index.tsx b/packages/ui/src/layouts/Root/index.tsx index cca7717082d..37f835e6017 100644 --- a/packages/ui/src/layouts/Root/index.tsx +++ b/packages/ui/src/layouts/Root/index.tsx @@ -1,16 +1,12 @@ -import type { - ImportMap, - InitReqResult, - LanguageOptions, - SanitizedConfig, - ServerFunctionClient, -} from 'payload' -import type { InitReqArgs } from 'payload/internal' +import type { ImportMap, LanguageOptions, SanitizedConfig, ServerFunctionClient } from 'payload' import { applyLocaleFiltering } from 'payload/shared' import React, { Suspense } from 'react' +import type { GetAdminContextFn } from '../../views/Root/index.js' + import { getNavPrefs } from '../../elements/Nav/getNavPrefs.js' + // eslint-disable-next-line payload/no-imports-from-exports-dir -- Server component must reference exports/client bundle for proper client boundary in prod builds import { ProgressBar, RootProvider } from '../../exports/client/index.js' import { checkDependencies, type CheckDependenciesArgs } from '../../utilities/checkDependencies.js' @@ -49,6 +45,7 @@ type RootLayoutProps = { * Framework-specific font loaders (e.g. `next/font`) are supplied by the caller. */ readonly fonts?: Font[] + readonly getAdminContext: GetAdminContextFn /** * Custom content to render inside the admin panel's `` element. * @@ -68,7 +65,6 @@ type RootLayoutProps = { readonly head?: React.ReactNode readonly htmlProps?: React.HtmlHTMLAttributes readonly importMap: ImportMap - readonly initReq: (args: Omit) => Promise /** * Client router adapter. Caller supplies a framework-specific provider * (for Next.js use the `NextRouterAdapter` exported from `@payloadcms/next`). @@ -93,10 +89,10 @@ const RootLayoutContent = async ({ children, config: configPromise, fonts = [], + getAdminContext, head: headFromProps, htmlProps = {}, importMap, - initReq, RouterAdapter, serverFunction, }: RootLayoutProps) => { @@ -110,7 +106,7 @@ const RootLayoutContent = async ({ payload: { config }, }, user, - } = await initReq({ configPromise, importMap, key: 'RootLayout' }) + } = await getAdminContext({ configPromise, importMap, key: 'RootLayout' }) const theme = getRequestTheme({ config, diff --git a/packages/ui/src/utilities/handleServerFunctions.ts b/packages/ui/src/utilities/handleServerFunctions.ts index d595c5fa755..cacc7fb1a31 100644 --- a/packages/ui/src/utilities/handleServerFunctions.ts +++ b/packages/ui/src/utilities/handleServerFunctions.ts @@ -1,7 +1,7 @@ import type { DefaultServerFunctionArgs, + GetAdminContextResult, ImportMap, - InitReqResult, SanitizedConfig, ServerFunction, ServerFunctionHandler, @@ -9,18 +9,18 @@ import type { import { sharedServerFunctions } from './serverFunctionRegistry.js' -type InitReq = (args: { +type GetAdminContextFn = (args: { configPromise: Promise | SanitizedConfig importMap: ImportMap -}) => Promise +}) => Promise type CreateServerFunctionHandlerArgs = { /** - * Framework-specific request initialization. The adapter closes over its own - * `ServerAdapter` / request source (Next.js: `initReq` + `nextServerAdapter`; - * TanStack Start: its `getRequest()`-based `initReq`). + * Framework-specific admin context creation. The adapter closes over its own + * `ServerAdapter` / request source (Next.js: `getAdminContext` + `nextServerAdapter`; + * TanStack Start: its `getRequest()`-based `getAdminContext`). */ - initReq: InitReq + getAdminContext: GetAdminContextFn /** * Additional handlers registered alongside `sharedServerFunctions`. Reserved * for adapter-specific functions; today both adapters run the shared set only. @@ -38,12 +38,12 @@ type CreateServerFunctionHandlerArgs = { * Factory for the framework-agnostic `handleServerFunctions` entry point. * * All adapters dispatch from the shared registry (`sharedServerFunctions`) and - * differ only in the two injected hooks below — `initReq` and `transformResult` + * differ only in the two injected hooks below — `getAdminContext` and `transformResult` * — so there is a single dispatch implementation and the handler sets cannot * drift between frameworks. */ export const createServerFunctionHandler = ({ - initReq, + getAdminContext, serverFunctions, transformResult, }: CreateServerFunctionHandlerArgs): ServerFunctionHandler => { @@ -60,7 +60,10 @@ export const createServerFunctionHandler = ({ serverFunctions: extraServerFunctions, } = args - const { cookies, locale, permissions, req, user } = await initReq({ configPromise, importMap }) + const { cookies, locale, permissions, req, user } = await getAdminContext({ + configPromise, + importMap, + }) const augmentedArgs: DefaultServerFunctionArgs = { ...fnArgs, diff --git a/packages/ui/src/views/NotFound/page.tsx b/packages/ui/src/views/NotFound/page.tsx index 85936659121..c4981effee3 100644 --- a/packages/ui/src/views/NotFound/page.tsx +++ b/packages/ui/src/views/NotFound/page.tsx @@ -1,38 +1,33 @@ 'use server' -import type { createPayloadReq, ImportMap, InitReqResult, SanitizedConfig } from 'payload' +import type { ImportMap, SanitizedConfig } from 'payload' import { applyLocaleFiltering, formatAdminURL } from 'payload/shared' import * as qs from 'qs-esm' import React from 'react' +import type { GetAdminContextFn } from '../Root/index.js' + /* eslint-disable payload/no-imports-from-exports-dir -- Server component must reference exports/client bundle for proper client boundary in prod builds */ import { NotFoundClient, PageConfigProvider } from '../../exports/client/index.js' + /* eslint-enable payload/no-imports-from-exports-dir */ import { DefaultTemplate } from '../../templates/Default/index.js' import { getClientConfig } from '../../utilities/getClientConfig.js' import { getVisibleEntities } from '../../utilities/getVisibleEntities.js' -type InitReqFn = (args: { - canSetHeaders?: boolean - configPromise: Promise | SanitizedConfig - importMap: ImportMap - key: string - overrides?: Omit[0], 'payload'> -}) => Promise - export type RenderNotFoundPageArgs = { config: Promise + getAdminContext: GetAdminContextFn importMap: ImportMap - initReq: InitReqFn params: Promise<{ segments: string[] }> searchParams: Promise<{ [key: string]: string | string[] }> } export const renderNotFoundPage = async ({ config: configPromise, + getAdminContext, importMap, - initReq, params: paramsPromise, searchParams: searchParamsPromise, }: RenderNotFoundPageArgs) => { @@ -48,7 +43,7 @@ export const renderNotFoundPage = async ({ req, req: { payload }, user, - } = await initReq({ + } = await getAdminContext({ configPromise: config, importMap, key: 'RootLayout', diff --git a/packages/ui/src/views/Root/index.tsx b/packages/ui/src/views/Root/index.tsx index 33079457107..14c51566e5c 100644 --- a/packages/ui/src/views/Root/index.tsx +++ b/packages/ui/src/views/Root/index.tsx @@ -6,8 +6,8 @@ import type { AdminViewServerPropsOnly, CollectionPreferences, createPayloadReq, + GetAdminContextResult, ImportMap, - InitReqResult, SanitizedCollectionConfig, SanitizedConfig, SanitizedGlobalConfig, @@ -31,19 +31,19 @@ import { isPublicAdminRoute } from '../../utilities/isPublicAdminRoute.js' import { getCustomViewByRoute } from './getCustomViewByRoute.js' import { getRouteData } from './getRouteData.js' -type InitReqFn = (args: { +export type GetAdminContextFn = (args: { canSetHeaders?: boolean configPromise: Promise | SanitizedConfig importMap: ImportMap key: string overrides?: Omit[0], 'payload'> -}) => Promise +}) => Promise export type RenderRootArgs = { adminViews: AdminViewAdapter config: Promise + getAdminContext: GetAdminContextFn importMap: ImportMap - initReq: InitReqFn /** * Optional React `key` applied to the rendered view (not the surrounding admin * template/nav). Adapters whose router reconciles a single RSC payload in place @@ -66,8 +66,8 @@ export type RenderRootArgs = { export const renderRoot = async ({ adminViews, config: configPromise, + getAdminContext, importMap, - initReq, key, notFound, params: paramsPromise, @@ -154,7 +154,7 @@ export const renderRoot = async ({ req, req: { payload }, user, - } = await initReq({ + } = await getAdminContext({ configPromise: config, importMap, key: 'initPage', From 8e66e95163754df9172f09242a94a2272363a58d Mon Sep 17 00:00:00 2001 From: Jake Fletcher Date: Wed, 23 Sep 2026 15:29:49 -0400 Subject: [PATCH 05/15] test: verify distinct admin context cache keys --- .../payload/src/admin/getAdminContext.spec.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/payload/src/admin/getAdminContext.spec.ts b/packages/payload/src/admin/getAdminContext.spec.ts index 2159a87c083..4c8c885c7fe 100644 --- a/packages/payload/src/admin/getAdminContext.spec.ts +++ b/packages/payload/src/admin/getAdminContext.spec.ts @@ -389,6 +389,7 @@ describe('getAdminContext', () => { }) it('should produce distinct full results for distinct cache keys', async () => { + getAccessResults.mockImplementation(async () => ({ ...permissions })) const cache = createReusingCache() const args = { cache, @@ -398,9 +399,19 @@ describe('getAdminContext', () => { } const rootLayoutResult = await getAdminContext({ ...args, key: 'RootLayout' }) - const pageResult = await getAdminContext({ ...args, key: 'initPage' }) + await getAdminContext({ ...args, key: 'initPage' }) + + expect(authenticate).toHaveBeenCalledOnce() + expect(applyUserReadAccess).toHaveBeenCalledTimes(2) + expect(getAccessResults).toHaveBeenCalledTimes(2) - expect(rootLayoutResult).not.toBe(pageResult) + const repeatedRootLayoutResult = await getAdminContext({ ...args, key: 'RootLayout' }) + + expect(authenticate).toHaveBeenCalledOnce() + expect(applyUserReadAccess).toHaveBeenCalledTimes(2) + expect(getAccessResults).toHaveBeenCalledTimes(2) + expect(repeatedRootLayoutResult.permissions).toBe(rootLayoutResult.permissions) + expect(repeatedRootLayoutResult.user).toBe(rootLayoutResult.user) }) it('should keep the complete req.user and return the user with read access', async () => { From f8ac6b548da67c66aa32e46fe8539d9e0452e717 Mon Sep 17 00:00:00 2001 From: Jake Fletcher Date: Wed, 23 Sep 2026 16:03:52 -0400 Subject: [PATCH 06/15] feat(codemod): migrate payload request creation APIs --- packages/codemod/README.md | 1 + packages/codemod/src/registry.ts | 2 + .../migrate-aliased-exports/index.spec.ts | 8 + .../migrate-aliased-exports/index.ts | 9 +- .../migrate-aliased-exports/request.input.ts | 5 + .../migrate-aliased-exports/request.output.ts | 5 + .../aliases.input.ts | 7 + .../aliases.output.ts | 7 + .../collision.input.ts | 8 + .../comments.input.ts | 8 + .../comments.output.ts | 8 + .../empty.input.ts | 5 + .../empty.output.ts | 5 + .../expression.input.ts | 5 + .../expression.output.ts | 5 + .../index.spec.ts | 121 +++++++++++++ .../migrate-payload-request-creation/index.ts | 170 ++++++++++++++++++ .../internal.input.ts | 7 + .../internal.output.ts | 7 + .../non-matching.input.ts | 6 + .../populated.input.ts | 4 + .../populated.output.ts | 4 + .../shadowed.input.ts | 6 + .../shadowed.output.ts | 6 + .../unsupported.input.ts | 8 + 25 files changed, 422 insertions(+), 5 deletions(-) create mode 100644 packages/codemod/src/transforms/migrate-aliased-exports/request.input.ts create mode 100644 packages/codemod/src/transforms/migrate-aliased-exports/request.output.ts create mode 100644 packages/codemod/src/transforms/migrate-payload-request-creation/aliases.input.ts create mode 100644 packages/codemod/src/transforms/migrate-payload-request-creation/aliases.output.ts create mode 100644 packages/codemod/src/transforms/migrate-payload-request-creation/collision.input.ts create mode 100644 packages/codemod/src/transforms/migrate-payload-request-creation/comments.input.ts create mode 100644 packages/codemod/src/transforms/migrate-payload-request-creation/comments.output.ts create mode 100644 packages/codemod/src/transforms/migrate-payload-request-creation/empty.input.ts create mode 100644 packages/codemod/src/transforms/migrate-payload-request-creation/empty.output.ts create mode 100644 packages/codemod/src/transforms/migrate-payload-request-creation/expression.input.ts create mode 100644 packages/codemod/src/transforms/migrate-payload-request-creation/expression.output.ts create mode 100644 packages/codemod/src/transforms/migrate-payload-request-creation/index.spec.ts create mode 100644 packages/codemod/src/transforms/migrate-payload-request-creation/index.ts create mode 100644 packages/codemod/src/transforms/migrate-payload-request-creation/internal.input.ts create mode 100644 packages/codemod/src/transforms/migrate-payload-request-creation/internal.output.ts create mode 100644 packages/codemod/src/transforms/migrate-payload-request-creation/non-matching.input.ts create mode 100644 packages/codemod/src/transforms/migrate-payload-request-creation/populated.input.ts create mode 100644 packages/codemod/src/transforms/migrate-payload-request-creation/populated.output.ts create mode 100644 packages/codemod/src/transforms/migrate-payload-request-creation/shadowed.input.ts create mode 100644 packages/codemod/src/transforms/migrate-payload-request-creation/shadowed.output.ts create mode 100644 packages/codemod/src/transforms/migrate-payload-request-creation/unsupported.input.ts diff --git a/packages/codemod/README.md b/packages/codemod/README.md index 437a50f1a1e..5f56698a356 100644 --- a/packages/codemod/README.md +++ b/packages/codemod/README.md @@ -88,6 +88,7 @@ The tool loads your project via [ts-morph](https://ts-morph.com/), using your `t - `migrate-block-references-to-blocks`: renames blocks field `blockReferences` configs to `blocks`, and removes a sibling `blocks: []` placeholder when present. If a non-empty `blocks` sibling already exists, the transform leaves the field untouched and surfaces a note for manual review. - `migrate-hide-api-url`: migrates `admin.hideAPIURL: true` to `admin.components.views.edit.api.tab.condition: () => false` on collection and global configs. - `migrate-aliased-exports`: rewrites imports of types and utilities that used to be re-exported from `@payloadcms/ui` and `@payloadcms/next/utilities` to their canonical sources in `payload` / `payload/shared`. +- `migrate-payload-request-creation`: renames request helpers and admin-context types from `payload` and `payload/internal`, and converts supported `createLocalReq(options, payload)` calls to `createPayloadReq(...)`; collisions and unsupported uses are left unchanged with notes for manual migration. - `migrate-document-title-context`: migrates `title` and `setDocumentTitle` destructured from `useDocumentInfo()` to `useDocumentTitle()`. They were removed from `DocumentInfoContext` in v4 and now live on `DocumentTitleContext`. - `migrate-storage-adapters-to-config`: moves storage adapter factory calls (`s3Storage`, `gcsStorage`, `azureStorage`, `r2Storage`, `vercelBlobStorage`) from `plugins` to the new top-level `storage` array. Removes `plugins` if it becomes empty after the move. **Limitations:** aliased imports (e.g. `import { s3Storage as myS3 }`) are not detected; rename any aliases to the canonical factory name before running, or migrate those calls manually. The transform preserves AST structure but does not re-format output. Run `prettier --write` (or your project's formatter) after applying. - `rename-storage-adapters-to-storage`: renames the top-level `storageAdapters` config property to `storage`. Skips any object that already has a `storage` property. Run this if you previously ran `migrate-storage-adapters-to-config` and need to update the property name. diff --git a/packages/codemod/src/registry.ts b/packages/codemod/src/registry.ts index 983321a7a53..6c366e04081 100644 --- a/packages/codemod/src/registry.ts +++ b/packages/codemod/src/registry.ts @@ -17,6 +17,7 @@ import { migrateImportExportHooks } from './transforms/migrate-import-export-hoo import { migrateListViewSelectAPI } from './transforms/migrate-list-view-select-api/index.js' import { migrateNextGenerateViewportExport } from './transforms/migrate-next-generate-viewport-export/index.js' import { migrateNextSubpathExports } from './transforms/migrate-next-subpath-exports/index.js' +import { migratePayloadRequestCreation } from './transforms/migrate-payload-request-creation/index.js' import { migrateSlugField } from './transforms/migrate-slug-field/index.js' import { migrateStorageAdaptersToConfig } from './transforms/migrate-storage-adapters-to-config/index.js' import { migrateVersionsDefault } from './transforms/migrate-versions-default/index.js' @@ -40,6 +41,7 @@ export const transforms: Transform[] = [ migrateDisabledFields, migrateForceSelect, migrateAliasedExports, + migratePayloadRequestCreation, migrateBlockReferencesToBlocks, migrateBuildScript, migrateDocumentTitleContext, diff --git a/packages/codemod/src/transforms/migrate-aliased-exports/index.spec.ts b/packages/codemod/src/transforms/migrate-aliased-exports/index.spec.ts index 97fcc552f3c..5fc573d6020 100644 --- a/packages/codemod/src/transforms/migrate-aliased-exports/index.spec.ts +++ b/packages/codemod/src/transforms/migrate-aliased-exports/index.spec.ts @@ -10,6 +10,14 @@ const here = dirname(fileURLToPath(import.meta.url)) const fixture = (name: string) => readFile(join(here, name), 'utf8') describe('migrate-aliased-exports', () => { + it('should migrate legacy request helpers directly to the canonical export while preserving local bindings', async () => { + const source = await fixture('request.input.ts') + const output = await fixture('request.output.ts') + + expect(await runTransform({ source, transform: migrateAliasedExports })).toBe(output) + expect(await runTransform({ source: output, transform: migrateAliasedExports })).toBe(output) + }) + it('moves pass-through re-exports to canonical sources', async () => { const input = await fixture('basic.input.ts') const output = await fixture('basic.output.ts') diff --git a/packages/codemod/src/transforms/migrate-aliased-exports/index.ts b/packages/codemod/src/transforms/migrate-aliased-exports/index.ts index b7544326c76..9f4f319b078 100644 --- a/packages/codemod/src/transforms/migrate-aliased-exports/index.ts +++ b/packages/codemod/src/transforms/migrate-aliased-exports/index.ts @@ -11,7 +11,7 @@ const SOURCE_MAP: Record> = { '@payloadcms/next/utilities': { addDataAndFileToRequest: { to: 'payload' }, addLocalesToRequestFromData: { to: 'payload' }, - createPayloadRequest: { to: 'payload' }, + createPayloadRequest: { rename: 'createPayloadReqFromWebRequest', to: 'payload' }, headersWithCors: { to: 'payload' }, mergeHeaders: { to: 'payload' }, sanitizeLocales: { to: 'payload' }, @@ -85,6 +85,7 @@ export const migrateAliasedExports: Transform = { const aliasNode = spec.getAliasNode() const localName = aliasNode?.getText() ?? spec.getName() const canonicalName = migration.rename ?? spec.getName() + const originalName = spec.getName() const alias = localName !== canonicalName ? localName : undefined spec.remove() @@ -97,9 +98,7 @@ export const migrateAliasedExports: Transform = { }) if (migration.rename) { - notes.push( - renameNote({ filePath: file.getFilePath(), migration, originalName: spec.getName() }), - ) + notes.push(renameNote({ filePath: file.getFilePath(), migration, originalName })) } } @@ -126,7 +125,7 @@ export const migrateAliasedExports: Transform = { } }, description: - 'Move imports of aliased re-exports from @payloadcms/ui and @payloadcms/next/utilities to their canonical sources in `payload` / `payload/shared`. Renamed names (`ListPreferences` → `CollectionPreferences`, `ListComponentClientProps` → `ListViewClientProps`, `ListComponentServerProps` → `ListViewServerProps`) are imported using an `as` alias so existing usages keep compiling.', + 'Move imports of aliased re-exports from @payloadcms/ui and @payloadcms/next/utilities to their canonical sources in `payload` / `payload/shared`. Renamed names (`createPayloadRequest` → `createPayloadReqFromWebRequest`, `ListPreferences` → `CollectionPreferences`, `ListComponentClientProps` → `ListViewClientProps`, `ListComponentServerProps` → `ListViewServerProps`) are imported using an `as` alias so existing usages keep compiling.', } type ApplyRenameArgs = { diff --git a/packages/codemod/src/transforms/migrate-aliased-exports/request.input.ts b/packages/codemod/src/transforms/migrate-aliased-exports/request.input.ts new file mode 100644 index 00000000000..6f60a7b019d --- /dev/null +++ b/packages/codemod/src/transforms/migrate-aliased-exports/request.input.ts @@ -0,0 +1,5 @@ +import { createPayloadRequest } from '@payloadcms/next/utilities' +import { createPayloadRequest as webRequest, unrelated } from '@payloadcms/next/utilities' + +const req = createPayloadRequest(args) +const aliased = webRequest(args) diff --git a/packages/codemod/src/transforms/migrate-aliased-exports/request.output.ts b/packages/codemod/src/transforms/migrate-aliased-exports/request.output.ts new file mode 100644 index 00000000000..ef1d6597283 --- /dev/null +++ b/packages/codemod/src/transforms/migrate-aliased-exports/request.output.ts @@ -0,0 +1,5 @@ +import { createPayloadReqFromWebRequest as createPayloadRequest, createPayloadReqFromWebRequest as webRequest } from 'payload' +import { unrelated } from '@payloadcms/next/utilities' + +const req = createPayloadRequest(args) +const aliased = webRequest(args) diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/aliases.input.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/aliases.input.ts new file mode 100644 index 00000000000..eff55b83433 --- /dev/null +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/aliases.input.ts @@ -0,0 +1,7 @@ +import type { CreateLocalReqOptions as Options, InitReqResult as Result } from 'payload' + +import { createLocalReq as local, createPayloadRequest as web } from 'payload' + +const req = local(options as Options, payload) +const request = web(input, config) +type Context = Result diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/aliases.output.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/aliases.output.ts new file mode 100644 index 00000000000..31960730ce7 --- /dev/null +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/aliases.output.ts @@ -0,0 +1,7 @@ +import type { CreatePayloadReqArgs as Options, GetAdminContextResult as Result } from 'payload' + +import { createPayloadReq as local, createPayloadReqFromWebRequest as web } from 'payload' + +const req = local({ ...options as Options, payload: payload }) +const request = web(input, config) +type Context = Result diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/collision.input.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/collision.input.ts new file mode 100644 index 00000000000..979c9476b5a --- /dev/null +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/collision.input.ts @@ -0,0 +1,8 @@ +import { createLocalReq } from 'payload' +import { createPayloadReq } from 'payload' +import { initReq } from 'payload/internal' + +const req = createLocalReq({}, payload) +function read(getAdminContext: Function) { + return initReq(args) +} diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/comments.input.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/comments.input.ts new file mode 100644 index 00000000000..2cac587a474 --- /dev/null +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/comments.input.ts @@ -0,0 +1,8 @@ +import { createLocalReq } from 'payload' + +const req = createLocalReq( + /* options */ { user } /* after options */, + // payload instance + payload /* after payload */, +) +const empty = createLocalReq({ /* empty options */ }, payload) diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/comments.output.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/comments.output.ts new file mode 100644 index 00000000000..f2029d151a8 --- /dev/null +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/comments.output.ts @@ -0,0 +1,8 @@ +import { createPayloadReq } from 'payload' + +const req = createPayloadReq({ + /* options */ ...{ user } /* after options */, + // payload instance + payload: payload /* after payload */, +}) +const empty = createPayloadReq({ /* empty options */ payload: payload }) diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/empty.input.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/empty.input.ts new file mode 100644 index 00000000000..83de7f2ee70 --- /dev/null +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/empty.input.ts @@ -0,0 +1,5 @@ +import { createLocalReq, createPayloadRequest, type InitReqResult } from 'payload' + +const req = await createLocalReq({}, payload) +const web = createPayloadRequest(request, config) +type Context = InitReqResult diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/empty.output.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/empty.output.ts new file mode 100644 index 00000000000..1b22125f4bd --- /dev/null +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/empty.output.ts @@ -0,0 +1,5 @@ +import { createPayloadReq, createPayloadReqFromWebRequest, type GetAdminContextResult } from 'payload' + +const req = await createPayloadReq({ payload: payload }) +const web = createPayloadReqFromWebRequest(request, config) +type Context = GetAdminContextResult diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/expression.input.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/expression.input.ts new file mode 100644 index 00000000000..af286af89b4 --- /dev/null +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/expression.input.ts @@ -0,0 +1,5 @@ +import { createLocalReq } from 'payload' + +const req = await createLocalReq(getOptions(), getPayload()) +const conditional = createLocalReq(enabled ? first : second, payload) +const nested = createLocalReq({ req: await createLocalReq({}, payload) }, payload) diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/expression.output.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/expression.output.ts new file mode 100644 index 00000000000..f72684ccf03 --- /dev/null +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/expression.output.ts @@ -0,0 +1,5 @@ +import { createPayloadReq } from 'payload' + +const req = await createPayloadReq({ ...getOptions(), payload: getPayload() }) +const conditional = createPayloadReq({ ...enabled ? first : second, payload: payload }) +const nested = createPayloadReq({ ...{ req: await createPayloadReq({ payload: payload }) }, payload: payload }) diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/index.spec.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/index.spec.ts new file mode 100644 index 00000000000..48085ec0d8c --- /dev/null +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/index.spec.ts @@ -0,0 +1,121 @@ +import { readFile } from 'node:fs/promises' +import { dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' +import { Project, SyntaxKind } from 'ts-morph' +import { describe, expect, it } from 'vitest' + +import { transforms } from '../../registry.js' +import { runTransform } from '../../utils/test-helpers.js' +import { migratePayloadRequestCreation } from './index.js' + +const here = dirname(fileURLToPath(import.meta.url)) +const fixture = (name: string) => readFile(join(here, name), 'utf8') + +describe('migrate-payload-request-creation', () => { + it.each(['empty', 'populated', 'expression', 'aliases', 'internal', 'comments', 'shadowed'])( + 'should migrate %s imports and calls', + async (name) => { + const source = await fixture(`${name}.input.ts`) + const output = await fixture(`${name}.output.ts`) + + expect(await runTransform({ source, transform: migratePayloadRequestCreation })).toBe(output) + expect(await runTransform({ source: output, transform: migratePayloadRequestCreation })).toBe( + output, + ) + }, + ) + + it.each(['collision', 'unsupported', 'non-matching'])( + 'should preserve %s source byte for byte and report manual work', + async (name) => { + const source = await fixture(`${name}.input.ts`) + const project = new Project({ useInMemoryFileSystem: true }) + const file = project.createSourceFile('/fixture.ts', source) + const result = await migratePayloadRequestCreation.apply({ packageJsons: [], project }) + + expect(file.getFullText()).toBe(source) + expect(result.filesChanged).toEqual([]) + if (name === 'non-matching') { + expect(result.notes).toBeUndefined() + } else { + expect(result.notes?.length).toBeGreaterThan(0) + expect(result.notes?.every((note) => note.includes('/fixture.ts'))).toBe(true) + expect(result.notes?.join('\n')).toContain( + name === 'collision' ? 'collision' : 'Unsupported', + ) + } + }, + ) + + it('should register the transform', () => { + expect(transforms).toContain(migratePayloadRequestCreation) + }) + + it('should isolate local bindings when dependency exports resolve', async () => { + const project = new Project({ useInMemoryFileSystem: true }) + const declaration = + 'export declare function createLocalReq(options: unknown, payload: unknown): unknown\nexport declare function createPayloadRequest(args: unknown): unknown' + const dependency = project.createSourceFile('/node_modules/payload/index.d.ts', declaration) + const matching = project.createSourceFile( + '/matching.ts', + `import { createLocalReq, createPayloadRequest } from 'payload'\nconst req = createLocalReq({}, payload)\nconst web = createPayloadRequest(args)`, + ) + const unsupportedSource = `import { createLocalReq } from 'payload'\nconst req = createLocalReq(options)` + const unsupported = project.createSourceFile('/unsupported.ts', unsupportedSource) + + const result = await migratePayloadRequestCreation.apply({ packageJsons: [], project }) + + expect(matching.getFullText()).toContain('createPayloadReq({ payload: payload })') + expect(matching.getFullText()).toContain('createPayloadReqFromWebRequest(args)') + expect(dependency.getFullText()).toBe(declaration) + expect(unsupported.getFullText()).toBe(unsupportedSource) + expect(result.filesChanged).toEqual(['/matching.ts']) + expect(result.notes).toHaveLength(1) + expect(result.notes?.[0]).toContain('/unsupported.ts:2: Unsupported use') + }) + + it('should report exactly the changed files and rewrite generic direct calls', async () => { + const project = new Project({ useInMemoryFileSystem: true }) + const file = project.createSourceFile( + '/matching.ts', + `import { createLocalReq } from 'payload'\nconst req = createLocalReq({}, payload)`, + ) + project.createSourceFile('/unrelated.ts', 'const value = 1') + + const result = await migratePayloadRequestCreation.apply({ packageJsons: [], project }) + + expect(result).toEqual({ filesChanged: ['/matching.ts'] }) + expect(file.getFullText()).toContain('createPayloadReq({ payload: payload })') + expect(file.getDescendantsOfKind(SyntaxKind.CallExpression)[0]?.getArguments()).toHaveLength(1) + }) + + it('should migrate a supported alias independently of an unsupported binding', async () => { + const source = `import { createLocalReq, createLocalReq as local } from 'payload'\nconst callback = createLocalReq\nconst req = local({}, payload)` + + const output = await runTransform({ source, transform: migratePayloadRequestCreation }) + + expect(output).toBe( + `import { createLocalReq, createPayloadReq as local } from 'payload'\nconst callback = createLocalReq\nconst req = local({ payload: payload })`, + ) + }) + + it.each([ + 'createLocalReq()', + 'createLocalReq({}, payload, extra)', + '(createLocalReq)({}, payload)', + 'new createLocalReq({}, payload)', + '({ createLocalReq })', + 'createLocalReq.bind(null)', + 'export { createLocalReq }', + ])('should leave unsupported use %s and its import unchanged', async (use) => { + const project = new Project({ useInMemoryFileSystem: true }) + const source = `import { createLocalReq } from 'payload'\n${use}` + const file = project.createSourceFile('/unsupported.ts', source) + + const result = await migratePayloadRequestCreation.apply({ packageJsons: [], project }) + + expect(file.getFullText()).toBe(source) + expect(result.filesChanged).toEqual([]) + expect(result.notes?.join('\n')).toContain('Unsupported use of `createLocalReq`') + }) +}) diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/index.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/index.ts new file mode 100644 index 00000000000..71c4b761899 --- /dev/null +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/index.ts @@ -0,0 +1,170 @@ +import type { CallExpression, ImportSpecifier } from 'ts-morph' + +import { Node, SymbolFlags, SyntaxKind } from 'ts-morph' + +import type { Transform } from '../../types.js' + +const RENAMES = { + payload: { + createLocalReq: 'createPayloadReq', + CreateLocalReqOptions: 'CreatePayloadReqArgs', + createPayloadRequest: 'createPayloadReqFromWebRequest', + InitReqResult: 'GetAdminContextResult', + }, + 'payload/internal': { + initReq: 'getAdminContext', + InitReqArgs: 'GetAdminContextArgs', + InitReqCache: 'AdminContextCache', + InitReqPartialResult: 'PartialAdminContext', + }, +} as const + +export const migratePayloadRequestCreation: Transform = { + name: 'migrate-payload-request-creation', + apply: ({ project }) => { + const originals = new Map(project.getSourceFiles().map((file) => [file, file.getFullText()])) + const notes: string[] = [] + + for (const file of project.getSourceFiles()) { + for (const declaration of file.getImportDeclarations()) { + const source = declaration.getModuleSpecifierValue() + if (!Object.hasOwn(RENAMES, source)) { + continue + } + + const renames: Record = RENAMES[source as keyof typeof RENAMES] + + for (const spec of declaration.getNamedImports()) { + const originalName = spec.getName() + + if (!Object.hasOwn(renames, originalName)) { + continue + } + + const newName = renames[originalName]! + const hadAlias = Boolean(spec.getAliasNode()) + + // An explicit alias limits ts-morph references and renames to this local binding. + // Renaming an unaliased import can also rename the dependency's export. + if (!hadAlias) { + spec.setAlias(originalName) + } + + const binding = spec.getAliasNode()! + const references = binding.findReferencesAsNodes().filter((node) => node !== binding) + const hasCollision = + !hadAlias && + [binding, ...references].some((node) => + node + .getSymbolsInScope(SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Alias) + .some((symbol) => symbol.getName() === newName), + ) + + if (hasCollision) { + notes.push( + `${file.getFilePath()}: Skipped \`${binding.getText()}\`: binding collision with \`${newName}\`; migrate manually.`, + ) + if (!hadAlias) { + spec.removeAlias() + } + continue + } + + const calls: CallExpression[] = [] + let hasUnsupportedUse = false + + if (source === 'payload' && originalName === 'createLocalReq') { + for (const reference of references) { + const parent = reference.getParent() + + if ( + Node.isCallExpression(parent) && + parent.getExpression() === reference && + !parent.getQuestionDotTokenNode() && + parent.getArguments().length === 2 && + !parent.getArguments().some(Node.isSpreadElement) + ) { + calls.push(parent) + } else { + hasUnsupportedUse = true + notes.push( + `${reference.getSourceFile().getFilePath()}:${reference.getStartLineNumber()}: Unsupported use of \`${binding.getText()}\`: \`${parent?.getText() ?? reference.getText()}\`. Left the import and all its uses unchanged; migrate manually.`, + ) + } + } + } + + if (hasUnsupportedUse) { + if (!hadAlias) { + spec.removeAlias() + } + continue + } + + renameImport({ hadAlias, newName, spec }) + + // Inner calls must be rewritten before replacing an enclosing call's text. + for (const call of calls.sort((a, b) => b.getStart() - a.getStart())) { + rewriteCall({ call }) + } + } + } + } + + return { + filesChanged: [...originals] + .filter(([file, original]) => file.getFullText() !== original) + .map(([file]) => file.getFilePath()), + ...(notes.length > 0 ? { notes } : {}), + } + }, + description: + 'Renames Payload request and admin-context imports and migrates direct createLocalReq(options, payload) calls to createPayloadReq({ ...options, payload }); reports collisions and unsupported uses for manual migration.', +} + +function renameImport({ + hadAlias, + newName, + spec, +}: { + hadAlias: boolean + newName: string + spec: ImportSpecifier +}): void { + if (hadAlias) { + spec.setName(newName) + } else { + spec.renameAlias(newName) + spec.setName(newName) + spec.removeAlias() + } +} + +function rewriteCall({ call }: { call: CallExpression }): void { + const [options, payload] = call.getArguments() + const open = call.getFirstChildByKindOrThrow(SyntaxKind.OpenParenToken) + const close = call.getLastChildByKindOrThrow(SyntaxKind.CloseParenToken) + const comma = options! + .getParentSyntaxListOrThrow() + .getChildren() + .find((node) => node.getKind() === SyntaxKind.CommaToken)! + const text = call.getSourceFile().getFullText() + const isEmpty = Node.isObjectLiteralExpression(options) && options.getProperties().length === 0 + const optionsText = isEmpty ? options.getText().slice(1, -1) : `...${options!.getText()}` + const leading = text.slice(open.getEnd(), options!.getStart()) || (isEmpty ? '' : ' ') + const trailing = text.slice(payload!.getEnd(), close.getStart()) || ' ' + const contents = [ + leading, + optionsText, + text.slice(options!.getEnd(), comma.getStart()), + isEmpty ? '' : ',', + text.slice(comma.getEnd(), payload!.getStart()), + 'payload: ', + payload!.getText(), + trailing, + ].join('') + + call.replaceWithText( + `${text.slice(call.getStart(), open.getEnd())}{${contents}}${text.slice(close.getStart(), call.getEnd())}`, + ) +} diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/internal.input.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/internal.input.ts new file mode 100644 index 00000000000..67d70199f7b --- /dev/null +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/internal.input.ts @@ -0,0 +1,7 @@ +import { initReq, type InitReqArgs, type InitReqCache, type InitReqPartialResult } from 'payload/internal' +import { initReq as initialize, type InitReqArgs as Args } from 'payload/internal' + +const context = initReq(args as InitReqArgs) +const aliased = initialize(args as Args) +type Cache = InitReqCache +type PartialContext = InitReqPartialResult diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/internal.output.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/internal.output.ts new file mode 100644 index 00000000000..7c429725d49 --- /dev/null +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/internal.output.ts @@ -0,0 +1,7 @@ +import { getAdminContext, type GetAdminContextArgs, type AdminContextCache, type PartialAdminContext } from 'payload/internal' +import { getAdminContext as initialize, type GetAdminContextArgs as Args } from 'payload/internal' + +const context = getAdminContext(args as GetAdminContextArgs) +const aliased = initialize(args as Args) +type Cache = AdminContextCache +type PartialContext = PartialAdminContext diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/non-matching.input.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/non-matching.input.ts new file mode 100644 index 00000000000..258c4680230 --- /dev/null +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/non-matching.input.ts @@ -0,0 +1,6 @@ +import { createLocalReq, initReq, type InitReqResult } from 'unrelated' +import { unrelated } from 'payload' + +const req=createLocalReq({},payload); +const context = initReq(args) +type Result = InitReqResult diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/populated.input.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/populated.input.ts new file mode 100644 index 00000000000..fa66b88063e --- /dev/null +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/populated.input.ts @@ -0,0 +1,4 @@ +import { createLocalReq, type CreateLocalReqOptions } from 'payload' + +const options: CreateLocalReqOptions = { user } +const req = await createLocalReq({ user, context: { source: 'test' } }, payload) diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/populated.output.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/populated.output.ts new file mode 100644 index 00000000000..7cac83141ce --- /dev/null +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/populated.output.ts @@ -0,0 +1,4 @@ +import { createPayloadReq, type CreatePayloadReqArgs } from 'payload' + +const options: CreatePayloadReqArgs = { user } +const req = await createPayloadReq({ ...{ user, context: { source: 'test' } }, payload: payload }) diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/shadowed.input.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/shadowed.input.ts new file mode 100644 index 00000000000..59512949b0d --- /dev/null +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/shadowed.input.ts @@ -0,0 +1,6 @@ +import { createLocalReq } from 'payload' + +const req = createLocalReq({}, payload) +function unrelated(createLocalReq: Function) { + return createLocalReq({}, payload) +} diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/shadowed.output.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/shadowed.output.ts new file mode 100644 index 00000000000..26fbcfaec1e --- /dev/null +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/shadowed.output.ts @@ -0,0 +1,6 @@ +import { createPayloadReq } from 'payload' + +const req = createPayloadReq({ payload: payload }) +function unrelated(createLocalReq: Function) { + return createLocalReq({}, payload) +} diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/unsupported.input.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/unsupported.input.ts new file mode 100644 index 00000000000..17d04f21426 --- /dev/null +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/unsupported.input.ts @@ -0,0 +1,8 @@ +import { createLocalReq, createLocalReq as optional, createLocalReq as spread, createLocalReq as indirect } from 'payload' + +const safe = createLocalReq({}, payload) +const wrong = createLocalReq(options) +const maybe = optional?.({}, payload) +const variadic = spread(...args, payload) +const callback = indirect +const viaCall = indirect.call(null, {}, payload) From 1df74518be379a1839f36e8687eab00e008eb8eb Mon Sep 17 00:00:00 2001 From: Jake Fletcher Date: Wed, 23 Sep 2026 16:11:23 -0400 Subject: [PATCH 07/15] fix(codemod): preserve request migration semantics --- packages/codemod/README.md | 2 +- .../evaluation-order.input.ts | 7 ++ .../expression.input.ts | 2 +- .../expression.output.ts | 2 +- .../index.spec.ts | 79 ++++++++++++++++--- .../migrate-payload-request-creation/index.ts | 13 ++- .../shorthand.input.ts | 8 ++ .../shorthand.output.ts | 8 ++ 8 files changed, 104 insertions(+), 17 deletions(-) create mode 100644 packages/codemod/src/transforms/migrate-payload-request-creation/evaluation-order.input.ts create mode 100644 packages/codemod/src/transforms/migrate-payload-request-creation/shorthand.input.ts create mode 100644 packages/codemod/src/transforms/migrate-payload-request-creation/shorthand.output.ts diff --git a/packages/codemod/README.md b/packages/codemod/README.md index 5f56698a356..2d17e2920ef 100644 --- a/packages/codemod/README.md +++ b/packages/codemod/README.md @@ -88,7 +88,7 @@ The tool loads your project via [ts-morph](https://ts-morph.com/), using your `t - `migrate-block-references-to-blocks`: renames blocks field `blockReferences` configs to `blocks`, and removes a sibling `blocks: []` placeholder when present. If a non-empty `blocks` sibling already exists, the transform leaves the field untouched and surfaces a note for manual review. - `migrate-hide-api-url`: migrates `admin.hideAPIURL: true` to `admin.components.views.edit.api.tab.condition: () => false` on collection and global configs. - `migrate-aliased-exports`: rewrites imports of types and utilities that used to be re-exported from `@payloadcms/ui` and `@payloadcms/next/utilities` to their canonical sources in `payload` / `payload/shared`. -- `migrate-payload-request-creation`: renames request helpers and admin-context types from `payload` and `payload/internal`, and converts supported `createLocalReq(options, payload)` calls to `createPayloadReq(...)`; collisions and unsupported uses are left unchanged with notes for manual migration. +- `migrate-payload-request-creation`: renames request helpers and admin-context types from `payload` and `payload/internal`, and converts `createLocalReq(options, payload)` when the payload argument is a plain identifier; collisions, other payload expressions, and unsupported uses are left unchanged with notes for manual migration. - `migrate-document-title-context`: migrates `title` and `setDocumentTitle` destructured from `useDocumentInfo()` to `useDocumentTitle()`. They were removed from `DocumentInfoContext` in v4 and now live on `DocumentTitleContext`. - `migrate-storage-adapters-to-config`: moves storage adapter factory calls (`s3Storage`, `gcsStorage`, `azureStorage`, `r2Storage`, `vercelBlobStorage`) from `plugins` to the new top-level `storage` array. Removes `plugins` if it becomes empty after the move. **Limitations:** aliased imports (e.g. `import { s3Storage as myS3 }`) are not detected; rename any aliases to the canonical factory name before running, or migrate those calls manually. The transform preserves AST structure but does not re-format output. Run `prettier --write` (or your project's formatter) after applying. - `rename-storage-adapters-to-storage`: renames the top-level `storageAdapters` config property to `storage`. Skips any object that already has a `storage` property. Run this if you previously ran `migrate-storage-adapters-to-config` and need to update the property name. diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/evaluation-order.input.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/evaluation-order.input.ts new file mode 100644 index 00000000000..5772b52ad4d --- /dev/null +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/evaluation-order.input.ts @@ -0,0 +1,7 @@ +import { createLocalReq } from 'payload' + +const options = { user: 'old' } +const newUser = 'new' +const payload = {} +const safe = createLocalReq({}, payload) +globalThis.observed = createLocalReq(options, (options.user = newUser, payload)).user diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/expression.input.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/expression.input.ts index af286af89b4..8bdcf949f1f 100644 --- a/packages/codemod/src/transforms/migrate-payload-request-creation/expression.input.ts +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/expression.input.ts @@ -1,5 +1,5 @@ import { createLocalReq } from 'payload' -const req = await createLocalReq(getOptions(), getPayload()) +const req = await createLocalReq(getOptions(), payload) const conditional = createLocalReq(enabled ? first : second, payload) const nested = createLocalReq({ req: await createLocalReq({}, payload) }, payload) diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/expression.output.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/expression.output.ts index f72684ccf03..cfd86601444 100644 --- a/packages/codemod/src/transforms/migrate-payload-request-creation/expression.output.ts +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/expression.output.ts @@ -1,5 +1,5 @@ import { createPayloadReq } from 'payload' -const req = await createPayloadReq({ ...getOptions(), payload: getPayload() }) +const req = await createPayloadReq({ ...getOptions(), payload: payload }) const conditional = createPayloadReq({ ...enabled ? first : second, payload: payload }) const nested = createPayloadReq({ ...{ req: await createPayloadReq({ payload: payload }) }, payload: payload }) diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/index.spec.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/index.spec.ts index 48085ec0d8c..460549c2dc7 100644 --- a/packages/codemod/src/transforms/migrate-payload-request-creation/index.spec.ts +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/index.spec.ts @@ -1,6 +1,7 @@ import { readFile } from 'node:fs/promises' import { dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' +import { runInNewContext } from 'node:vm' import { Project, SyntaxKind } from 'ts-morph' import { describe, expect, it } from 'vitest' @@ -12,18 +13,24 @@ const here = dirname(fileURLToPath(import.meta.url)) const fixture = (name: string) => readFile(join(here, name), 'utf8') describe('migrate-payload-request-creation', () => { - it.each(['empty', 'populated', 'expression', 'aliases', 'internal', 'comments', 'shadowed'])( - 'should migrate %s imports and calls', - async (name) => { - const source = await fixture(`${name}.input.ts`) - const output = await fixture(`${name}.output.ts`) - - expect(await runTransform({ source, transform: migratePayloadRequestCreation })).toBe(output) - expect(await runTransform({ source: output, transform: migratePayloadRequestCreation })).toBe( - output, - ) - }, - ) + it.each([ + 'empty', + 'populated', + 'expression', + 'aliases', + 'internal', + 'comments', + 'shadowed', + 'shorthand', + ])('should migrate %s imports and calls', async (name) => { + const source = await fixture(`${name}.input.ts`) + const output = await fixture(`${name}.output.ts`) + + expect(await runTransform({ source, transform: migratePayloadRequestCreation })).toBe(output) + expect(await runTransform({ source: output, transform: migratePayloadRequestCreation })).toBe( + output, + ) + }) it.each(['collision', 'unsupported', 'non-matching'])( 'should preserve %s source byte for byte and report manual work', @@ -51,6 +58,54 @@ describe('migrate-payload-request-creation', () => { expect(transforms).toContain(migratePayloadRequestCreation) }) + it('should preserve behavior when evaluating payload mutates the options', async () => { + const source = await fixture('evaluation-order.input.ts') + const project = new Project({ useInMemoryFileSystem: true }) + const file = project.createSourceFile('/evaluation-order.ts', source) + const result = await migratePayloadRequestCreation.apply({ packageJsons: [], project }) + const output = file.getFullText() + const evaluate = (code: string) => { + const context = { + createLocalReq: (options: { user: string }) => ({ ...options }), + createPayloadReq: (options: { user: string }) => options, + observed: undefined, + } + const executable = new Project({ useInMemoryFileSystem: true }).createSourceFile( + 'input.ts', + code, + ) + + executable.getImportDeclarations().forEach((declaration) => declaration.remove()) + runInNewContext(executable.getFullText(), context) + + return context.observed + } + + expect(evaluate(source)).toBe('new') + expect(evaluate(output)).toBe(evaluate(source)) + expect(output).toBe(source) + expect(result.filesChanged).toEqual([]) + expect(result.notes?.[0]).toContain('/evaluation-order.ts:') + expect(result.notes?.[0]).toContain('payload argument `(options.user = newUser, payload)`') + expect(result.notes?.[0]).toContain('evaluation order') + }) + + it.each(['getPayload()', 'holder.payload', 'await getPayload()', '(payload)', 'payload!'])( + 'should skip the entire binding for payload expression %s', + async (expression) => { + const project = new Project({ useInMemoryFileSystem: true }) + const source = `import { createLocalReq } from 'payload'\nconst safe = createLocalReq({}, payload)\nconst req = createLocalReq(options, ${expression})` + const file = project.createSourceFile('/evaluation-order.ts', source) + + const result = await migratePayloadRequestCreation.apply({ packageJsons: [], project }) + + expect(file.getFullText()).toBe(source) + expect(result.filesChanged).toEqual([]) + expect(result.notes?.[0]).toContain(`payload argument \`${expression}\``) + expect(result.notes?.[0]).toContain('evaluation order') + }, + ) + it('should isolate local bindings when dependency exports resolve', async () => { const project = new Project({ useInMemoryFileSystem: true }) const declaration = diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/index.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/index.ts index 71c4b761899..c24056a41c1 100644 --- a/packages/codemod/src/transforms/migrate-payload-request-creation/index.ts +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/index.ts @@ -84,7 +84,16 @@ export const migratePayloadRequestCreation: Transform = { parent.getArguments().length === 2 && !parent.getArguments().some(Node.isSpreadElement) ) { - calls.push(parent) + const payloadArgument = parent.getArguments()[1]! + + if (Node.isIdentifier(payloadArgument)) { + calls.push(parent) + } else { + hasUnsupportedUse = true + notes.push( + `${reference.getSourceFile().getFilePath()}:${reference.getStartLineNumber()}: Unsupported payload argument \`${payloadArgument.getText()}\` in \`${parent.getText()}\`: spreading options first could change evaluation order. Left the import and all its uses unchanged; migrate manually.`, + ) + } } else { hasUnsupportedUse = true notes.push( @@ -134,7 +143,7 @@ function renameImport({ if (hadAlias) { spec.setName(newName) } else { - spec.renameAlias(newName) + spec.getAliasNode()!.rename(newName, { usePrefixAndSuffixText: true }) spec.setName(newName) spec.removeAlias() } diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/shorthand.input.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/shorthand.input.ts new file mode 100644 index 00000000000..65698463833 --- /dev/null +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/shorthand.input.ts @@ -0,0 +1,8 @@ +import { createPayloadRequest } from 'payload' +import { initReq } from 'payload/internal' + +const handlers = { createPayloadRequest, initReq } +const requestKey: string = 'createPayloadRequest' +const contextKey: string = 'initReq' +handlers[requestKey](args) +handlers[contextKey](args) diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/shorthand.output.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/shorthand.output.ts new file mode 100644 index 00000000000..1184d5f145f --- /dev/null +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/shorthand.output.ts @@ -0,0 +1,8 @@ +import { createPayloadReqFromWebRequest } from 'payload' +import { getAdminContext } from 'payload/internal' + +const handlers = { createPayloadRequest: createPayloadReqFromWebRequest, initReq: getAdminContext } +const requestKey: string = 'createPayloadRequest' +const contextKey: string = 'initReq' +handlers[requestKey](args) +handlers[contextKey](args) From 4d815c565c0c4e616611c43ad916dd97aabed3ed Mon Sep 17 00:00:00 2001 From: Jake Fletcher Date: Wed, 23 Sep 2026 16:22:49 -0400 Subject: [PATCH 08/15] docs: add payload request creation migration --- docs/migration-guide/v4.mdx | 78 +++++++++++++++---- .../src/query-presets/preventLockout.ts | 2 +- 2 files changed, 64 insertions(+), 16 deletions(-) diff --git a/docs/migration-guide/v4.mdx b/docs/migration-guide/v4.mdx index 15141d8682b..20dd50ce98a 100644 --- a/docs/migration-guide/v4.mdx +++ b/docs/migration-guide/v4.mdx @@ -22,6 +22,54 @@ The codemod is idempotent and safe to run on a partially-migrated project. ## Breaking Changes +### Payload request creation is consolidated + +Payload request creation now uses `createPayloadReq` for local contexts and `createPayloadReqFromWebRequest` to convert a Web `Request`. Admin context setup is exposed as `getAdminContext`. + +| Old name | New name | +| ----------------------- | -------------------------------- | +| `createLocalReq` | `createPayloadReq` | +| `CreateLocalReqOptions` | `CreatePayloadReqArgs` | +| `createPayloadRequest` | `createPayloadReqFromWebRequest` | +| `initReq` | `getAdminContext` | +| `InitReqArgs` | `GetAdminContextArgs` | +| `InitReqResult` | `GetAdminContextResult` | +| `InitReqCache` | `AdminContextCache` | +| `InitReqPartialResult` | `PartialAdminContext` | + +`getAdminContext` and its cache types are primarily contracts for framework adapters. Custom Root layout adapters must rename their injected `initReq` callback to `getAdminContext`. + +Local request creation now takes a single object argument containing `payload`: + +```diff +-import { createLocalReq } from 'payload' ++import { createPayloadReq } from 'payload' + +-const req = await createLocalReq(options, payload) ++const req = await createPayloadReq({ ++ ...options, ++ payload, ++}) +``` + +Web request conversion uses its new role-specific name: + +```diff +-import { createPayloadRequest } from 'payload' ++import { createPayloadReqFromWebRequest } from 'payload' + +-const req = await createPayloadRequest({ config, request }) ++const req = await createPayloadReqFromWebRequest({ config, request }) +``` + +#### Codemod + +To migrate automatically, there's a codemod for this change available by running: + +```bash +npx @payloadcms/codemod --transform migrate-payload-request-creation +``` + ### JWT authentication is versioned without moving token data Payload 4 keeps the decoded JWT payload flat, so integrations can continue reading `id`, `collection`, `sid`, `email`, and fields selected by `saveToJWT` from the same paths as Payload 3. @@ -396,20 +444,20 @@ Several types and utilities that were re-exported from `@payloadcms/ui` and `@pa **Pass-through re-exports** — same name, new source: -| Symbol | Old source | New source | -| ----------------------------- | ---------------------------- | ---------------- | -| `Column` | `@payloadcms/ui` | `payload` | -| `ListViewSlots` | `@payloadcms/ui` | `payload` | -| `ListViewClientProps` | `@payloadcms/ui` | `payload` | -| `EntityType` | `@payloadcms/ui/shared` | `payload/shared` | -| `formatAdminURL` | `@payloadcms/ui/shared` | `payload/shared` | -| `mergeListSearchAndWhere` | `@payloadcms/ui/shared` | `payload/shared` | -| `mergeHeaders` | `@payloadcms/next/utilities` | `payload` | -| `headersWithCors` | `@payloadcms/next/utilities` | `payload` | -| `createPayloadRequest` | `@payloadcms/next/utilities` | `payload` | -| `addDataAndFileToRequest` | `@payloadcms/next/utilities` | `payload` | -| `sanitizeLocales` | `@payloadcms/next/utilities` | `payload` | -| `addLocalesToRequestFromData` | `@payloadcms/next/utilities` | `payload` | +| Symbol | Old source | New source | +| ----------------------------- | ---------------------------- | --------------------------------------------- | +| `Column` | `@payloadcms/ui` | `payload` | +| `ListViewSlots` | `@payloadcms/ui` | `payload` | +| `ListViewClientProps` | `@payloadcms/ui` | `payload` | +| `EntityType` | `@payloadcms/ui/shared` | `payload/shared` | +| `formatAdminURL` | `@payloadcms/ui/shared` | `payload/shared` | +| `mergeListSearchAndWhere` | `@payloadcms/ui/shared` | `payload/shared` | +| `mergeHeaders` | `@payloadcms/next/utilities` | `payload` | +| `headersWithCors` | `@payloadcms/next/utilities` | `payload` | +| `createPayloadRequest` | `@payloadcms/next/utilities` | `createPayloadReqFromWebRequest` in `payload` | +| `addDataAndFileToRequest` | `@payloadcms/next/utilities` | `payload` | +| `sanitizeLocales` | `@payloadcms/next/utilities` | `payload` | +| `addLocalesToRequestFromData` | `@payloadcms/next/utilities` | `payload` | **Renamed types** — use the new canonical name from `payload`: @@ -428,7 +476,7 @@ Several types and utilities that were re-exported from `@payloadcms/ui` and `@pa + import { headersWithCors, mergeHeaders } from 'payload' ``` -Run `npx @payloadcms/codemod --transform migrate-aliased-exports` to migrate automatically. Renamed types are imported using an `as` alias (e.g. `import type { CollectionPreferences as ListPreferences } from 'payload'`) so existing usages keep compiling — drop the alias and rename usages manually if you want to fully commit to the new name. +Running `npx @payloadcms/codemod --transform migrate-aliased-exports` preserves old local bindings by importing renamed symbols with an `as` alias (e.g. `import type { CollectionPreferences as ListPreferences } from 'payload'` or `import { createPayloadReqFromWebRequest as createPayloadRequest } from 'payload'`). Run `migrate-payload-request-creation` to complete the request helper's identifier and call-site migration, or drop the other aliases and rename their usages manually if you want to fully commit to the new names. ### `DefaultValue` function variant is now typed to allow returning a `Promise` ([#17522](https://github.com/payloadcms/payload/pull/17522)) diff --git a/packages/payload/src/query-presets/preventLockout.ts b/packages/payload/src/query-presets/preventLockout.ts index 01e78c4a640..0ea715a7c47 100644 --- a/packages/payload/src/query-presets/preventLockout.ts +++ b/packages/payload/src/query-presets/preventLockout.ts @@ -22,10 +22,10 @@ export const preventLockout: Validate = async ( // Use context to ensure an infinite loop doesn't occur if (!incomingReq.context._preventLockout && !overrideAccess) { const req = await createPayloadReq({ - payload: incomingReq.payload, context: { _preventLockout: true, }, + payload: incomingReq.payload, req: { user: incomingReq.user, }, From 08b875b923fc7dbd710db8b0b766b45ab815e488 Mon Sep 17 00:00:00 2001 From: Jake Fletcher Date: Wed, 23 Sep 2026 16:31:09 -0400 Subject: [PATCH 09/15] fix(codemod): complete aliased request migration --- .../index.spec.ts | 27 +++++++++++++++++++ .../migrate-payload-request-creation/index.ts | 19 ++++++++++--- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/index.spec.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/index.spec.ts index 460549c2dc7..b5796498683 100644 --- a/packages/codemod/src/transforms/migrate-payload-request-creation/index.spec.ts +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/index.spec.ts @@ -7,6 +7,7 @@ import { describe, expect, it } from 'vitest' import { transforms } from '../../registry.js' import { runTransform } from '../../utils/test-helpers.js' +import { migrateAliasedExports } from '../migrate-aliased-exports/index.js' import { migratePayloadRequestCreation } from './index.js' const here = dirname(fileURLToPath(import.meta.url)) @@ -58,6 +59,32 @@ describe('migrate-payload-request-creation', () => { expect(transforms).toContain(migratePayloadRequestCreation) }) + it('should complete the request rename after migrate-aliased-exports', async () => { + const source = `import { createPayloadRequest } from '@payloadcms/next/utilities'\nconst req = createPayloadRequest(args)` + const intermediate = await runTransform({ source, transform: migrateAliasedExports }) + + expect(intermediate).toBe( + `import { createPayloadReqFromWebRequest as createPayloadRequest } from 'payload'\nconst req = createPayloadRequest(args)`, + ) + + const output = await runTransform({ + source: intermediate, + transform: migratePayloadRequestCreation, + }) + const expected = `import { createPayloadReqFromWebRequest } from 'payload'\nconst req = createPayloadReqFromWebRequest(args)` + + expect(output).toBe(expected) + expect(await runTransform({ source: output, transform: migratePayloadRequestCreation })).toBe( + expected, + ) + }) + + it('should preserve user-authored aliases of createPayloadReqFromWebRequest', async () => { + const source = `import { createPayloadReqFromWebRequest as webRequest } from 'payload'\nconst req = webRequest(args)` + + expect(await runTransform({ source, transform: migratePayloadRequestCreation })).toBe(source) + }) + it('should preserve behavior when evaluating payload mutates the options', async () => { const source = await fixture('evaluation-order.input.ts') const project = new Project({ useInMemoryFileSystem: true }) diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/index.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/index.ts index c24056a41c1..1534f65cdaf 100644 --- a/packages/codemod/src/transforms/migrate-payload-request-creation/index.ts +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/index.ts @@ -35,7 +35,13 @@ export const migratePayloadRequestCreation: Transform = { const renames: Record = RENAMES[source as keyof typeof RENAMES] for (const spec of declaration.getNamedImports()) { - const originalName = spec.getName() + const importedName = spec.getName() + const aliasName = spec.getAliasNode()?.getText() + const isAliasedExportMigrationArtifact = + source === 'payload' && + importedName === 'createPayloadReqFromWebRequest' && + aliasName === 'createPayloadRequest' + const originalName = isAliasedExportMigrationArtifact ? aliasName : importedName if (!Object.hasOwn(renames, originalName)) { continue @@ -53,7 +59,7 @@ export const migratePayloadRequestCreation: Transform = { const binding = spec.getAliasNode()! const references = binding.findReferencesAsNodes().filter((node) => node !== binding) const hasCollision = - !hadAlias && + (!hadAlias || isAliasedExportMigrationArtifact) && [binding, ...references].some((node) => node .getSymbolsInScope(SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Alias) @@ -110,7 +116,7 @@ export const migratePayloadRequestCreation: Transform = { continue } - renameImport({ hadAlias, newName, spec }) + renameImport({ hadAlias, isAliasedExportMigrationArtifact, newName, spec }) // Inner calls must be rewritten before replacing an enclosing call's text. for (const call of calls.sort((a, b) => b.getStart() - a.getStart())) { @@ -133,14 +139,19 @@ export const migratePayloadRequestCreation: Transform = { function renameImport({ hadAlias, + isAliasedExportMigrationArtifact, newName, spec, }: { hadAlias: boolean + isAliasedExportMigrationArtifact: boolean newName: string spec: ImportSpecifier }): void { - if (hadAlias) { + if (isAliasedExportMigrationArtifact) { + spec.getAliasNode()!.rename(newName, { usePrefixAndSuffixText: true }) + spec.removeAlias() + } else if (hadAlias) { spec.setName(newName) } else { spec.getAliasNode()!.rename(newName, { usePrefixAndSuffixText: true }) From daa1f1a6b62ba9b51e3e07de60d53ad7af4d9a40 Mon Sep 17 00:00:00 2001 From: Jake Fletcher Date: Wed, 23 Sep 2026 16:55:13 -0400 Subject: [PATCH 10/15] fix(codemod): preserve request options semantics and types --- docs/migration-guide/v4.mdx | 33 ++- packages/codemod/README.md | 2 +- .../aliases.input.ts | 3 +- .../aliases.output.ts | 3 +- .../comments.input.ts | 3 +- .../comments.output.ts | 5 +- .../expression.output.ts | 8 +- .../getters.input.ts | 17 ++ .../index.spec.ts | 164 ++++++++++- .../migrate-payload-request-creation/index.ts | 275 ++++++++++++------ .../populated.input.ts | 2 +- .../populated.output.ts | 4 +- .../collections/operations/local/duplicate.ts | 5 +- 13 files changed, 409 insertions(+), 115 deletions(-) create mode 100644 packages/codemod/src/transforms/migrate-payload-request-creation/getters.input.ts diff --git a/docs/migration-guide/v4.mdx b/docs/migration-guide/v4.mdx index 20dd50ce98a..4a8c2762623 100644 --- a/docs/migration-guide/v4.mdx +++ b/docs/migration-guide/v4.mdx @@ -26,32 +26,31 @@ The codemod is idempotent and safe to run on a partially-migrated project. Payload request creation now uses `createPayloadReq` for local contexts and `createPayloadReqFromWebRequest` to convert a Web `Request`. Admin context setup is exposed as `getAdminContext`. -| Old name | New name | -| ----------------------- | -------------------------------- | -| `createLocalReq` | `createPayloadReq` | -| `CreateLocalReqOptions` | `CreatePayloadReqArgs` | -| `createPayloadRequest` | `createPayloadReqFromWebRequest` | -| `initReq` | `getAdminContext` | -| `InitReqArgs` | `GetAdminContextArgs` | -| `InitReqResult` | `GetAdminContextResult` | -| `InitReqCache` | `AdminContextCache` | -| `InitReqPartialResult` | `PartialAdminContext` | +| Old name | New name | +| --------------------------------------- | --------------------------------------- | +| `createLocalReq` | `createPayloadReq` | +| `CreateLocalReqOptions` (internal type) | `Omit` | +| `createPayloadRequest` | `createPayloadReqFromWebRequest` | +| `initReq` | `getAdminContext` | +| `InitReqArgs` | `GetAdminContextArgs` | +| `InitReqResult` | `GetAdminContextResult` | +| `InitReqCache` | `AdminContextCache` | +| `InitReqPartialResult` | `PartialAdminContext` | `getAdminContext` and its cache types are primarily contracts for framework adapters. Custom Root layout adapters must rename their injected `initReq` callback to `getAdminContext`. -Local request creation now takes a single object argument containing `payload`: +Local request creation now takes a single object argument containing `payload`. For a request without options: ```diff -import { createLocalReq } from 'payload' +import { createPayloadReq } from 'payload' --const req = await createLocalReq(options, payload) -+const req = await createPayloadReq({ -+ ...options, -+ payload, -+}) +-const req = await createLocalReq({}, payload) ++const req = await createPayloadReq({ payload }) ``` +`CreatePayloadReqArgs` is exported from `payload` and requires `payload`. Options-only values should use `Omit`. The old `CreateLocalReqOptions` type was declared in the internal request utility, not exported from the package root; relative/private imports of that type require manual migration. The codemod handles an existing named import of that type only when its source is `payload`. + Web request conversion uses its new role-specific name: ```diff @@ -70,6 +69,8 @@ To migrate automatically, there's a codemod for this change available by running npx @payloadcms/codemod --transform migrate-payload-request-creation ``` +Automatic call migration requires a plain identifier payload argument and either `{}` or an initialized local `const` options identifier. The options type must expose all seven old option properties, or be `any`. For that identifier form, the generated argument captures payload first and uses forwarding getters for `context`, `depth`, `fallbackLocale`, `locale`, `req`, `urlSuffix`, and `user`. This preserves inherited getters, their read order, and rejected promises from throwing getters without reading unrelated properties. Complex expressions, mutable/unresolved options bindings, narrow option types, collisions, and unsupported type usages are left unchanged with notes for manual migration. + ### JWT authentication is versioned without moving token data Payload 4 keeps the decoded JWT payload flat, so integrations can continue reading `id`, `collection`, `sid`, `email`, and fields selected by `saveToJWT` from the same paths as Payload 3. diff --git a/packages/codemod/README.md b/packages/codemod/README.md index 2d17e2920ef..b4bd961cdad 100644 --- a/packages/codemod/README.md +++ b/packages/codemod/README.md @@ -88,7 +88,7 @@ The tool loads your project via [ts-morph](https://ts-morph.com/), using your `t - `migrate-block-references-to-blocks`: renames blocks field `blockReferences` configs to `blocks`, and removes a sibling `blocks: []` placeholder when present. If a non-empty `blocks` sibling already exists, the transform leaves the field untouched and surfaces a note for manual review. - `migrate-hide-api-url`: migrates `admin.hideAPIURL: true` to `admin.components.views.edit.api.tab.condition: () => false` on collection and global configs. - `migrate-aliased-exports`: rewrites imports of types and utilities that used to be re-exported from `@payloadcms/ui` and `@payloadcms/next/utilities` to their canonical sources in `payload` / `payload/shared`. -- `migrate-payload-request-creation`: renames request helpers and admin-context types from `payload` and `payload/internal`, and converts `createLocalReq(options, payload)` when the payload argument is a plain identifier; collisions, other payload expressions, and unsupported uses are left unchanged with notes for manual migration. +- `migrate-payload-request-creation`: renames named request-helper and admin-context imports from `payload` and `payload/internal`. Calls migrate only with a plain identifier payload and either `{}` or an initialized local `const` options identifier whose type exposes all seven old option properties (or is `any`). The generated argument captures payload first and forwards only `context`, `depth`, `fallbackLocale`, `locale`, `req`, `urlSuffix`, and `user` through getters, preserving inherited getters, read order, and async rejection behavior. Existing `CreateLocalReqOptions` named imports from `payload` become options-only usages of `Omit`; relative/private imports of that internal type require manual migration. Unsupported shapes, type usages, and collisions leave the affected binding unchanged with notes. - `migrate-document-title-context`: migrates `title` and `setDocumentTitle` destructured from `useDocumentInfo()` to `useDocumentTitle()`. They were removed from `DocumentInfoContext` in v4 and now live on `DocumentTitleContext`. - `migrate-storage-adapters-to-config`: moves storage adapter factory calls (`s3Storage`, `gcsStorage`, `azureStorage`, `r2Storage`, `vercelBlobStorage`) from `plugins` to the new top-level `storage` array. Removes `plugins` if it becomes empty after the move. **Limitations:** aliased imports (e.g. `import { s3Storage as myS3 }`) are not detected; rename any aliases to the canonical factory name before running, or migrate those calls manually. The transform preserves AST structure but does not re-format output. Run `prettier --write` (or your project's formatter) after applying. - `rename-storage-adapters-to-storage`: renames the top-level `storageAdapters` config property to `storage`. Skips any object that already has a `storage` property. Run this if you previously ran `migrate-storage-adapters-to-config` and need to update the property name. diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/aliases.input.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/aliases.input.ts index eff55b83433..7a7319f5a40 100644 --- a/packages/codemod/src/transforms/migrate-payload-request-creation/aliases.input.ts +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/aliases.input.ts @@ -2,6 +2,7 @@ import type { CreateLocalReqOptions as Options, InitReqResult as Result } from ' import { createLocalReq as local, createPayloadRequest as web } from 'payload' -const req = local(options as Options, payload) +const options: Options = { user } +const req = local(options, payload) const request = web(input, config) type Context = Result diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/aliases.output.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/aliases.output.ts index 31960730ce7..bdc9f45008e 100644 --- a/packages/codemod/src/transforms/migrate-payload-request-creation/aliases.output.ts +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/aliases.output.ts @@ -2,6 +2,7 @@ import type { CreatePayloadReqArgs as Options, GetAdminContextResult as Result } import { createPayloadReq as local, createPayloadReqFromWebRequest as web } from 'payload' -const req = local({ ...options as Options, payload: payload }) +const options: Omit = { user } +const req = local({ payload: payload, get context() { return options.context }, get depth() { return options.depth }, get fallbackLocale() { return options.fallbackLocale }, get locale() { return options.locale }, get req() { return options.req }, get urlSuffix() { return options.urlSuffix }, get user() { return options.user } }) const request = web(input, config) type Context = Result diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/comments.input.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/comments.input.ts index 2cac587a474..150f41ca8f4 100644 --- a/packages/codemod/src/transforms/migrate-payload-request-creation/comments.input.ts +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/comments.input.ts @@ -1,7 +1,8 @@ import { createLocalReq } from 'payload' +const options: any = { user } const req = createLocalReq( - /* options */ { user } /* after options */, + /* options */ options /* after options */, // payload instance payload /* after payload */, ) diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/comments.output.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/comments.output.ts index f2029d151a8..ca5882fc00e 100644 --- a/packages/codemod/src/transforms/migrate-payload-request-creation/comments.output.ts +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/comments.output.ts @@ -1,8 +1,9 @@ import { createPayloadReq } from 'payload' +const options: any = { user } const req = createPayloadReq({ - /* options */ ...{ user } /* after options */, + /* options */ /* after options */ // payload instance - payload: payload /* after payload */, + payload: payload /* after payload */, get context() { return options.context }, get depth() { return options.depth }, get fallbackLocale() { return options.fallbackLocale }, get locale() { return options.locale }, get req() { return options.req }, get urlSuffix() { return options.urlSuffix }, get user() { return options.user }, }) const empty = createPayloadReq({ /* empty options */ payload: payload }) diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/expression.output.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/expression.output.ts index cfd86601444..8bdcf949f1f 100644 --- a/packages/codemod/src/transforms/migrate-payload-request-creation/expression.output.ts +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/expression.output.ts @@ -1,5 +1,5 @@ -import { createPayloadReq } from 'payload' +import { createLocalReq } from 'payload' -const req = await createPayloadReq({ ...getOptions(), payload: payload }) -const conditional = createPayloadReq({ ...enabled ? first : second, payload: payload }) -const nested = createPayloadReq({ ...{ req: await createPayloadReq({ payload: payload }) }, payload: payload }) +const req = await createLocalReq(getOptions(), payload) +const conditional = createLocalReq(enabled ? first : second, payload) +const nested = createLocalReq({ req: await createLocalReq({}, payload) }, payload) diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/getters.input.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/getters.input.ts new file mode 100644 index 00000000000..e04a20f6119 --- /dev/null +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/getters.input.ts @@ -0,0 +1,17 @@ +import { createLocalReq } from 'payload' + +const events = [] +let payload = { id: 'original' } +const inherited = { + get context() { events.push('context'); payload = { id: 'replacement' }; return 'context' }, + get depth() { events.push('depth'); return 1 }, + get fallbackLocale() { events.push('fallbackLocale'); return 'en' }, + get locale() { events.push('locale'); return 'fr' }, + get req() { events.push('req'); return {} }, + get urlSuffix() { events.push('urlSuffix'); return '/path' }, + get user() { events.push('user'); return 'inherited' }, +} +const options = Object.create(inherited, { + unrelated: { enumerable: true, get() { throw new Error('unrelated getter must not run') } }, +}) +globalThis.observed = { request: createLocalReq(options, payload), events } diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/index.spec.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/index.spec.ts index b5796498683..424188a1393 100644 --- a/packages/codemod/src/transforms/migrate-payload-request-creation/index.spec.ts +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/index.spec.ts @@ -2,7 +2,7 @@ import { readFile } from 'node:fs/promises' import { dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' import { runInNewContext } from 'node:vm' -import { Project, SyntaxKind } from 'ts-morph' +import { Project, SyntaxKind, ts } from 'ts-morph' import { describe, expect, it } from 'vitest' import { transforms } from '../../registry.js' @@ -59,6 +59,168 @@ describe('migrate-payload-request-creation', () => { expect(transforms).toContain(migratePayloadRequestCreation) }) + it('should preserve payload capture, inherited getters, getter order, and unrelated getters', async () => { + const source = await fixture('getters.input.ts') + const output = await runTransform({ source, transform: migratePayloadRequestCreation }) + const evaluate = (code: string) => { + const context = { + createLocalReq: ( + { context, depth, fallbackLocale, locale, req, urlSuffix, user }: Record, + payload: unknown, + ) => ({ context, depth, fallbackLocale, locale, req, urlSuffix, user, payload }), + createPayloadReq: ({ + context, + depth, + fallbackLocale, + locale, + payload, + req, + urlSuffix, + user, + }: Record) => ({ + context, + depth, + fallbackLocale, + locale, + req, + urlSuffix, + user, + payload, + }), + observed: undefined, + } + const executable = new Project({ useInMemoryFileSystem: true }).createSourceFile( + 'input.ts', + code, + ) + + executable.getImportDeclarations().forEach((declaration) => declaration.remove()) + runInNewContext(executable.getFullText(), context) + + return context.observed + } + + expect(output).toContain('createPayloadReq(') + expect(output).not.toContain('...options') + expect(evaluate(output)).toEqual(evaluate(source)) + expect(evaluate(output)).toEqual({ + request: { + payload: { id: 'original' }, + context: 'context', + depth: 1, + fallbackLocale: 'en', + locale: 'fr', + req: {}, + urlSuffix: '/path', + user: 'inherited', + }, + events: ['context', 'depth', 'fallbackLocale', 'locale', 'req', 'urlSuffix', 'user'], + }) + }) + + it('should keep throwing option getters inside the rejected-Promise boundary', async () => { + const source = `import { createLocalReq } from 'payload' +const options = Object.create(null, { context: { get() { throw new Error('getter') } } }) +const payload = {} +try { + globalThis.observed = createLocalReq(options, payload).then(() => 'resolved', error => 'rejected:' + error.message) +} catch (error) { + globalThis.observed = Promise.resolve('threw:' + error.message) +}` + const output = await runTransform({ source, transform: migratePayloadRequestCreation }) + const evaluate = async (code: string) => { + const context = { + createLocalReq: async ({ context }: Record) => context, + createPayloadReq: async ({ context }: Record) => context, + observed: undefined, + } + const executable = new Project({ useInMemoryFileSystem: true }).createSourceFile( + 'input.ts', + code, + ) + + executable.getImportDeclarations().forEach((declaration) => declaration.remove()) + runInNewContext(executable.getFullText(), context) + + return await context.observed + } + + expect(output).toContain('createPayloadReq(') + expect(await evaluate(source)).toBe('rejected:getter') + expect(await evaluate(output)).toBe('rejected:getter') + }) + + it.each(['CreateLocalReqOptions', 'CreateLocalReqOptions as Options'])( + 'should typecheck options-only annotations from %s without requiring payload', + async (imported) => { + const local = imported.includes(' as ') ? 'Options' : imported + const source = `import type { ${imported} } from 'payload'\nimport { createLocalReq } from 'payload'\nconst options: ${local} = { user: 'user' }\ntype Extra = ${local} & { extra?: boolean }\nconst payload = { id: 'payload' }\nconst request = createLocalReq(options, payload)` + const output = await runTransform({ source, transform: migratePayloadRequestCreation }) + const project = new Project({ + compilerOptions: { strict: true, noEmit: true }, + useInMemoryFileSystem: true, + }) + const actualHelper = ts.createSourceFile( + 'createPayloadReq.ts', + await readFile(join(here, '../../../../payload/src/utilities/createPayloadReq.ts'), 'utf8'), + ts.ScriptTarget.Latest, + true, + ) + const args = actualHelper.statements.find( + (node) => ts.isTypeAliasDeclaration(node) && node.name.text === 'CreatePayloadReqArgs', + )! + + project.createSourceFile( + '/node_modules/payload/index.d.ts', + `type Payload = { id: string }; type RequestContext = Record; type TypedLocale = string; type PayloadRequest = Record; type User = string;\n${args.getText(actualHelper)}\nexport declare function createPayloadReq(args: CreatePayloadReqArgs): unknown`, + ) + const file = project.createSourceFile('/output.ts', output) + + expect( + project.getPreEmitDiagnostics().map((diagnostic) => diagnostic.getMessageText()), + ).toEqual([]) + expect( + file.getVariableDeclarationOrThrow('options').getType().getProperty('payload'), + ).toBeUndefined() + expect(output).toContain( + `Omit<${local === 'Options' ? 'Options' : 'CreatePayloadReqArgs'}, 'payload'>`, + ) + }, + ) + + it.each([ + `const options = getOptions(); createLocalReq(getOptions(), payload)`, + `const options = {}; createLocalReq({ user }, payload)`, + `let options = {}; createLocalReq(options, payload)`, + `createLocalReq(options, payload)`, + `function build(options) { return createLocalReq(options, payload) }`, + `const options = { user: 'user' }; createLocalReq(options, payload)`, + ])('should skip unsafe options shapes and bindings: %s', async (body) => { + const project = new Project({ useInMemoryFileSystem: true }) + const source = `import { createLocalReq } from 'payload'\n${body}` + const file = project.createSourceFile('/unsafe.ts', source) + const result = await migratePayloadRequestCreation.apply({ packageJsons: [], project }) + + expect(file.getFullText()).toBe(source) + expect(result.filesChanged).toEqual([]) + expect(result.notes?.join('\n')).toContain('options') + }) + + it.each([ + `import type { CreateLocalReqOptions } from 'payload'\nexport { CreateLocalReqOptions }`, + `import type { CreateLocalReqOptions } from 'payload'\ntype Omit = T\nconst options: CreateLocalReqOptions = { user }`, + `import type { CreateLocalReqOptions } from '../utilities/createLocalReq.js'\nconst options: CreateLocalReqOptions = { user }`, + `import type { CreateLocalReqOptions } from 'payload/dist/utilities/createLocalReq.js'\nconst options: CreateLocalReqOptions = { user }`, + ])('should skip unsupported options-type usage or import source', async (source) => { + const project = new Project({ useInMemoryFileSystem: true }) + const file = project.createSourceFile('/types.ts', source) + const result = await migratePayloadRequestCreation.apply({ packageJsons: [], project }) + + expect(file.getFullText()).toBe(source) + expect(result.filesChanged).toEqual([]) + expect(result.notes?.join('\n')).toContain('CreateLocalReqOptions') + }) + it('should complete the request rename after migrate-aliased-exports', async () => { const source = `import { createPayloadRequest } from '@payloadcms/next/utilities'\nconst req = createPayloadRequest(args)` const intermediate = await runTransform({ source, transform: migrateAliasedExports }) diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/index.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/index.ts index 1534f65cdaf..e7a4d87e262 100644 --- a/packages/codemod/src/transforms/migrate-payload-request-creation/index.ts +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/index.ts @@ -1,6 +1,6 @@ -import type { CallExpression, ImportSpecifier } from 'ts-morph' +import type { CallExpression, ImportSpecifier, TypeReferenceNode } from 'ts-morph' -import { Node, SymbolFlags, SyntaxKind } from 'ts-morph' +import { Node, SymbolFlags, SyntaxKind, VariableDeclarationKind } from 'ts-morph' import type { Transform } from '../../types.js' @@ -19,6 +19,16 @@ const RENAMES = { }, } as const +const OPTION_NAMES = [ + 'context', + 'depth', + 'fallbackLocale', + 'locale', + 'req', + 'urlSuffix', + 'user', +] as const + export const migratePayloadRequestCreation: Transform = { name: 'migrate-payload-request-creation', apply: ({ project }) => { @@ -26,102 +36,158 @@ export const migratePayloadRequestCreation: Transform = { const notes: string[] = [] for (const file of project.getSourceFiles()) { - for (const declaration of file.getImportDeclarations()) { - const source = declaration.getModuleSpecifierValue() + // Check call options before replacing type imports that may only resolve after upgrading Payload. + const imports = file + .getImportDeclarations() + .flatMap((declaration) => + declaration + .getNamedImports() + .map((spec) => ({ source: declaration.getModuleSpecifierValue(), spec })), + ) + .sort( + (a, b) => + Number(a.spec.getName() === 'CreateLocalReqOptions') - + Number(b.spec.getName() === 'CreateLocalReqOptions'), + ) + + for (const { source, spec } of imports) { + if (source !== 'payload' && (source.startsWith('.') || source.startsWith('payload/'))) { + if (spec.getName() === 'CreateLocalReqOptions') { + notes.push( + `${file.getFilePath()}: Unsupported CreateLocalReqOptions import source \`${source}\`; migrate this private/relative type import manually to Omit from payload.`, + ) + } + } + if (!Object.hasOwn(RENAMES, source)) { continue } const renames: Record = RENAMES[source as keyof typeof RENAMES] - for (const spec of declaration.getNamedImports()) { - const importedName = spec.getName() - const aliasName = spec.getAliasNode()?.getText() - const isAliasedExportMigrationArtifact = - source === 'payload' && - importedName === 'createPayloadReqFromWebRequest' && - aliasName === 'createPayloadRequest' - const originalName = isAliasedExportMigrationArtifact ? aliasName : importedName - - if (!Object.hasOwn(renames, originalName)) { - continue - } + const importedName = spec.getName() + const aliasName = spec.getAliasNode()?.getText() + const isAliasedExportMigrationArtifact = + source === 'payload' && + importedName === 'createPayloadReqFromWebRequest' && + aliasName === 'createPayloadRequest' + const originalName = isAliasedExportMigrationArtifact ? aliasName : importedName + + if (!Object.hasOwn(renames, originalName)) { + continue + } + + const newName = renames[originalName]! + const hadAlias = Boolean(spec.getAliasNode()) + + // An explicit alias limits ts-morph references and renames to this local binding. + // Renaming an unaliased import can also rename the dependency's export. + if (!hadAlias) { + spec.setAlias(originalName) + } - const newName = renames[originalName]! - const hadAlias = Boolean(spec.getAliasNode()) + const binding = spec.getAliasNode()! + const references = binding.findReferencesAsNodes().filter((node) => node !== binding) + const hasCollision = + (!hadAlias || isAliasedExportMigrationArtifact) && + [binding, ...references].some((node) => + node + .getSymbolsInScope(SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Alias) + .some((symbol) => symbol.getName() === newName), + ) - // An explicit alias limits ts-morph references and renames to this local binding. - // Renaming an unaliased import can also rename the dependency's export. + if (hasCollision) { + notes.push( + `${file.getFilePath()}: Skipped \`${binding.getText()}\`: binding collision with \`${newName}\`; migrate manually.`, + ) if (!hadAlias) { - spec.setAlias(originalName) + spec.removeAlias() } + continue + } - const binding = spec.getAliasNode()! - const references = binding.findReferencesAsNodes().filter((node) => node !== binding) - const hasCollision = - (!hadAlias || isAliasedExportMigrationArtifact) && - [binding, ...references].some((node) => - node - .getSymbolsInScope(SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Alias) - .some((symbol) => symbol.getName() === newName), - ) + const calls: CallExpression[] = [] + const optionsTypes: TypeReferenceNode[] = [] + let hasUnsupportedUse = false - if (hasCollision) { - notes.push( - `${file.getFilePath()}: Skipped \`${binding.getText()}\`: binding collision with \`${newName}\`; migrate manually.`, - ) - if (!hadAlias) { - spec.removeAlias() + if (source === 'payload' && originalName === 'CreateLocalReqOptions') { + for (const reference of references) { + const parent = reference.getParent() + const omitSymbol = reference + .getSymbolsInScope(SymbolFlags.Type | SymbolFlags.Alias) + .find((symbol) => symbol.getName() === 'Omit') + const isOmitShadowed = omitSymbol + ?.getDeclarations() + .some((node) => node.getSourceFile().getBaseName() !== 'lib.es5.d.ts') + + if ( + Node.isTypeReference(parent) && + parent.getTypeName() === reference && + parent.getTypeArguments().length === 0 && + !isOmitShadowed + ) { + optionsTypes.push(parent) + } else { + hasUnsupportedUse = true + notes.push( + `${reference.getSourceFile().getFilePath()}:${reference.getStartLineNumber()}: Unsupported CreateLocalReqOptions usage \`${parent?.getText() ?? reference.getText()}\` or shadowed Omit type; left the import and all its uses unchanged. Migrate manually to an options-only type.`, + ) } - continue } + } - const calls: CallExpression[] = [] - let hasUnsupportedUse = false - - if (source === 'payload' && originalName === 'createLocalReq') { - for (const reference of references) { - const parent = reference.getParent() - - if ( - Node.isCallExpression(parent) && - parent.getExpression() === reference && - !parent.getQuestionDotTokenNode() && - parent.getArguments().length === 2 && - !parent.getArguments().some(Node.isSpreadElement) - ) { - const payloadArgument = parent.getArguments()[1]! - - if (Node.isIdentifier(payloadArgument)) { - calls.push(parent) - } else { - hasUnsupportedUse = true - notes.push( - `${reference.getSourceFile().getFilePath()}:${reference.getStartLineNumber()}: Unsupported payload argument \`${payloadArgument.getText()}\` in \`${parent.getText()}\`: spreading options first could change evaluation order. Left the import and all its uses unchanged; migrate manually.`, - ) - } - } else { + if (source === 'payload' && originalName === 'createLocalReq') { + for (const reference of references) { + const parent = reference.getParent() + + if ( + Node.isCallExpression(parent) && + parent.getExpression() === reference && + !parent.getQuestionDotTokenNode() && + parent.getArguments().length === 2 && + !parent.getArguments().some(Node.isSpreadElement) + ) { + const [optionsArgument, payloadArgument] = parent.getArguments() + const unsafeOptionsReason = getUnsafeOptionsReason({ options: optionsArgument! }) + + if (!Node.isIdentifier(payloadArgument)) { + hasUnsupportedUse = true + notes.push( + `${reference.getSourceFile().getFilePath()}:${reference.getStartLineNumber()}: Unsupported payload argument \`${payloadArgument!.getText()}\` in \`${parent.getText()}\`: automatic migration cannot preserve evaluation order. Left the import and all its uses unchanged; migrate manually.`, + ) + } else if (unsafeOptionsReason) { hasUnsupportedUse = true notes.push( - `${reference.getSourceFile().getFilePath()}:${reference.getStartLineNumber()}: Unsupported use of \`${binding.getText()}\`: \`${parent?.getText() ?? reference.getText()}\`. Left the import and all its uses unchanged; migrate manually.`, + `${reference.getSourceFile().getFilePath()}:${reference.getStartLineNumber()}: Unsupported options argument \`${optionsArgument!.getText()}\` in \`${parent.getText()}\`: ${unsafeOptionsReason}. Left the import and all its uses unchanged; migrate manually.`, ) + } else { + calls.push(parent) } + } else { + hasUnsupportedUse = true + notes.push( + `${reference.getSourceFile().getFilePath()}:${reference.getStartLineNumber()}: Unsupported use of \`${binding.getText()}\`: \`${parent?.getText() ?? reference.getText()}\`. Left the import and all its uses unchanged; migrate manually.`, + ) } } + } - if (hasUnsupportedUse) { - if (!hadAlias) { - spec.removeAlias() - } - continue + if (hasUnsupportedUse) { + if (!hadAlias) { + spec.removeAlias() } + continue + } - renameImport({ hadAlias, isAliasedExportMigrationArtifact, newName, spec }) + renameImport({ hadAlias, isAliasedExportMigrationArtifact, newName, spec }) - // Inner calls must be rewritten before replacing an enclosing call's text. - for (const call of calls.sort((a, b) => b.getStart() - a.getStart())) { - rewriteCall({ call }) - } + for (const optionsType of optionsTypes.sort((a, b) => b.getStart() - a.getStart())) { + optionsType.replaceWithText(`Omit<${optionsType.getText()}, 'payload'>`) + } + + // Inner calls must be rewritten before replacing an enclosing call's text. + for (const call of calls.sort((a, b) => b.getStart() - a.getStart())) { + rewriteCall({ call }) } } } @@ -134,7 +200,42 @@ export const migratePayloadRequestCreation: Transform = { } }, description: - 'Renames Payload request and admin-context imports and migrates direct createLocalReq(options, payload) calls to createPayloadReq({ ...options, payload }); reports collisions and unsupported uses for manual migration.', + 'Renames Payload request and admin-context imports, preserves options-only types, and migrates safe createLocalReq calls by capturing payload before projecting the seven old option properties; reports unsupported bindings for manual migration.', +} + +function getUnsafeOptionsReason({ options }: { options: Node }): string | undefined { + if (Node.isObjectLiteralExpression(options)) { + return options.getProperties().length === 0 + ? undefined + : 'only an empty inline options object can be migrated safely' + } + + if (!Node.isIdentifier(options)) { + return 'expected {} or a stable local const options identifier to preserve option reads and evaluation order' + } + + const declarations = options.getSymbol()?.getDeclarations() + const declaration = declarations?.length === 1 ? declarations[0] : undefined + + const isStable = + Node.isVariableDeclaration(declaration) && + declaration.getSourceFile() === options.getSourceFile() && + Node.isIdentifier(declaration.getNameNode()) && + Boolean(declaration.getInitializer()) && + declaration.getVariableStatement()?.getDeclarationKind() === VariableDeclarationKind.Const + + if (!isStable) { + return 'the options identifier must resolve to an initialized local const binding so getters cannot change the receiver between property reads' + } + + const type = options.getType() + const missing = type.isAny() ? [] : OPTION_NAMES.filter((name) => !type.getProperty(name)) + + if (missing.length > 0) { + return `the options type does not expose ${missing.join(', ')}; projecting these properties would be unsafe for typechecking` + } + + return undefined } function renameImport({ @@ -164,24 +265,30 @@ function rewriteCall({ call }: { call: CallExpression }): void { const [options, payload] = call.getArguments() const open = call.getFirstChildByKindOrThrow(SyntaxKind.OpenParenToken) const close = call.getLastChildByKindOrThrow(SyntaxKind.CloseParenToken) - const comma = options! + const commas = options! .getParentSyntaxListOrThrow() .getChildren() - .find((node) => node.getKind() === SyntaxKind.CommaToken)! + .filter((node) => node.getKind() === SyntaxKind.CommaToken) + const comma = commas[0]! + const trailingComma = commas[1] const text = call.getSourceFile().getFullText() const isEmpty = Node.isObjectLiteralExpression(options) && options.getProperties().length === 0 - const optionsText = isEmpty ? options.getText().slice(1, -1) : `...${options!.getText()}` - const leading = text.slice(open.getEnd(), options!.getStart()) || (isEmpty ? '' : ' ') - const trailing = text.slice(payload!.getEnd(), close.getStart()) || ' ' - const contents = [ - leading, - optionsText, + const prefix = [ + text.slice(open.getEnd(), options!.getStart()), + isEmpty ? options.getText().slice(1, -1) : '', text.slice(options!.getEnd(), comma.getStart()), - isEmpty ? '' : ',', text.slice(comma.getEnd(), payload!.getStart()), + ].join('') + // Forward reads into the async helper so throwing option getters still reject its promise. + const contents = [ + prefix.trim() ? prefix : ' ', 'payload: ', payload!.getText(), - trailing, + text.slice(payload!.getEnd(), trailingComma?.getStart() ?? close.getStart()), + isEmpty + ? '' + : `, ${OPTION_NAMES.map((name) => `get ${name}() { return ${options!.getText()}.${name} }`).join(', ')}`, + trailingComma ? text.slice(trailingComma.getStart(), close.getStart()) : ' ', ].join('') call.replaceWithText( diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/populated.input.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/populated.input.ts index fa66b88063e..34621c2ef40 100644 --- a/packages/codemod/src/transforms/migrate-payload-request-creation/populated.input.ts +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/populated.input.ts @@ -1,4 +1,4 @@ import { createLocalReq, type CreateLocalReqOptions } from 'payload' const options: CreateLocalReqOptions = { user } -const req = await createLocalReq({ user, context: { source: 'test' } }, payload) +const req = await createLocalReq(options, payload) diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/populated.output.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/populated.output.ts index 7cac83141ce..7b80b770a89 100644 --- a/packages/codemod/src/transforms/migrate-payload-request-creation/populated.output.ts +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/populated.output.ts @@ -1,4 +1,4 @@ import { createPayloadReq, type CreatePayloadReqArgs } from 'payload' -const options: CreatePayloadReqArgs = { user } -const req = await createPayloadReq({ ...{ user, context: { source: 'test' } }, payload: payload }) +const options: Omit = { user } +const req = await createPayloadReq({ payload: payload, get context() { return options.context }, get depth() { return options.depth }, get fallbackLocale() { return options.fallbackLocale }, get locale() { return options.locale }, get req() { return options.req }, get urlSuffix() { return options.urlSuffix }, get user() { return options.user } }) diff --git a/packages/payload/src/collections/operations/local/duplicate.ts b/packages/payload/src/collections/operations/local/duplicate.ts index af0c5e1de62..5c71134fceb 100644 --- a/packages/payload/src/collections/operations/local/duplicate.ts +++ b/packages/payload/src/collections/operations/local/duplicate.ts @@ -125,7 +125,10 @@ export async function duplicateLocal< ) } - const req = await createPayloadReq({ ...(options as Omit), payload }) + const req = await createPayloadReq({ + ...(options as Omit), + payload, + }) return duplicateOperation({ id, From a024fef617fd2b6c968734805e60ed204dfa616d Mon Sep 17 00:00:00 2001 From: Jake Fletcher Date: Thu, 24 Sep 2026 13:51:35 -0400 Subject: [PATCH 11/15] chore: clarify PayloadRequest creation names --- docs/jobs-queue/jobs.mdx | 2 +- docs/migration-guide/v4.mdx | 64 +++++++++---------- packages/codemod/README.md | 2 +- .../migrate-aliased-exports/index.ts | 4 +- .../migrate-aliased-exports/request.output.ts | 2 +- .../aliases.output.ts | 4 +- .../collision.input.ts | 5 +- .../comments.output.ts | 6 +- .../empty.output.ts | 8 +-- .../index.spec.ts | 39 +++++------ .../migrate-payload-request-creation/index.ts | 64 +++++++++++++++---- .../internal.output.ts | 6 +- .../populated.output.ts | 6 +- .../shadowed.output.ts | 4 +- .../shorthand.output.ts | 6 +- packages/drizzle/src/migrate.ts | 4 +- packages/drizzle/src/migrateDown.ts | 4 +- packages/drizzle/src/migrateFresh.ts | 4 +- packages/drizzle/src/migrateRefresh.ts | 4 +- packages/drizzle/src/migrateReset.ts | 4 +- .../src/adapters/handleServerFunctions.ts | 6 +- packages/next/src/adapters/layout.tsx | 6 +- packages/next/src/adapters/views.tsx | 7 +- packages/next/src/routes/graphql/handler.ts | 4 +- .../next/src/routes/graphql/playground.ts | 4 +- ...spec.ts => createAdminContext.rsc.spec.ts} | 22 +++---- ...tAdminContext.ts => createAdminContext.ts} | 18 +++--- .../payload/reference/PLUGIN-DEVELOPMENT.md | 4 +- ...ext.spec.ts => createAdminContext.spec.ts} | 62 +++++++++--------- ...tAdminContext.ts => createAdminContext.ts} | 34 +++++----- packages/payload/src/admin/functions/index.ts | 4 +- packages/payload/src/admin/types.ts | 2 +- packages/payload/src/admin/views/dashboard.ts | 4 +- .../payload/src/auth/operations/local/auth.ts | 4 +- .../auth/operations/local/forgotPassword.ts | 4 +- .../src/auth/operations/local/login.ts | 4 +- .../auth/operations/local/resetPassword.ts | 4 +- .../src/auth/operations/local/unlock.ts | 4 +- .../src/auth/operations/local/verifyEmail.ts | 4 +- .../payload/src/auth/serverFunctions/login.ts | 4 +- .../src/auth/serverFunctions/logout.ts | 4 +- .../src/auth/serverFunctions/refresh.ts | 4 +- .../commands/collections/createDocuments.ts | 4 +- .../commands/collections/duplicateDocument.ts | 4 +- .../collections/getCollectionSchema.ts | 4 +- .../commands/collections/updateDocument.ts | 4 +- .../cli/commands/globals/getGlobalSchema.ts | 4 +- .../src/cli/commands/globals/updateGlobal.ts | 4 +- .../src/collections/operations/local/count.ts | 9 ++- .../operations/local/countVersions.ts | 9 ++- .../collections/operations/local/create.ts | 8 +-- .../collections/operations/local/delete.ts | 9 ++- .../collections/operations/local/duplicate.ts | 8 +-- .../src/collections/operations/local/find.ts | 9 ++- .../collections/operations/local/findByID.ts | 9 ++- .../operations/local/findDistinct.ts | 9 ++- .../operations/local/findVersionByID.ts | 9 ++- .../operations/local/findVersions.ts | 9 ++- .../operations/local/restoreVersion.ts | 9 ++- .../collections/operations/local/update.ts | 8 +-- .../src/database/migrations/migrate.ts | 4 +- .../src/database/migrations/migrateDown.ts | 4 +- .../src/database/migrations/migrateRefresh.ts | 4 +- .../src/database/migrations/migrateReset.ts | 4 +- packages/payload/src/exports/internal.ts | 6 +- .../globals/operations/local/countVersions.ts | 9 ++- .../src/globals/operations/local/findOne.ts | 9 ++- .../operations/local/findVersionByID.ts | 9 ++- .../globals/operations/local/findVersions.ts | 9 ++- .../operations/local/restoreVersion.ts | 9 ++- .../src/globals/operations/local/update.ts | 9 ++- packages/payload/src/index.ts | 7 +- .../src/kv/adapters/DatabaseKVAdapter.ts | 2 +- .../src/query-presets/preventLockout.ts | 4 +- packages/payload/src/queues/localAPI.ts | 14 ++-- ...q.spec.ts => createPayloadRequest.spec.ts} | 26 ++++---- ...ePayloadReq.ts => createPayloadRequest.ts} | 6 +- ...reatePayloadRequestFromWebRequest.spec.ts} | 48 +++++++------- ... => createPayloadRequestFromWebRequest.ts} | 6 +- .../payload/src/utilities/handleEndpoints.ts | 6 +- .../utilities/isolateObjectProperty.spec.ts | 2 +- packages/plugin-mcp/src/mcp/buildMcpServer.ts | 2 +- .../src/mcp/builtin/collections/authTools.ts | 7 +- packages/plugin-mcp/src/stdio.ts | 4 +- .../converters/utilities/payloadPopulateFn.ts | 4 +- .../src/layouts/Root/getLayoutData.ts | 4 +- ...c.ts => createAdminContext.server.spec.ts} | 20 +++--- .../utilities/createAdminContext.server.ts | 24 +++++++ .../src/utilities/devConfigReload.server.ts | 2 +- .../src/utilities/getAdminContext.server.ts | 24 ------- .../src/utilities/getRequestI18n.server.ts | 2 +- .../src/utilities/graphqlHandler.server.ts | 4 +- .../src/utilities/handleServerFunctions.ts | 4 +- .../src/utilities/loadAdminPage.tsx | 14 ++-- packages/ui/src/layouts/Root/index.tsx | 8 +-- .../ui/src/utilities/handleServerFunctions.ts | 18 +++--- packages/ui/src/views/NotFound/page.tsx | 8 +-- packages/ui/src/views/Root/index.tsx | 16 ++--- .../src/app/(app)/next/seed/route.ts | 4 +- templates/plugin/dev/int.spec.ts | 4 +- .../src/app/(frontend)/next/seed/route.ts | 4 +- .../src/app/(frontend)/next/seed/route.ts | 4 +- test/access-control/int.spec.ts | 12 ++-- test/access-control/postgres-logs.int.spec.ts | 4 +- test/auth/api-key/int.spec.ts | 10 +-- test/auth/int.spec.ts | 8 +-- test/auth/jwt/int.spec.ts | 6 +- test/base-access/int.spec.ts | 6 +- test/dataloader/int.spec.ts | 4 +- test/field-access-context/int.spec.ts | 4 +- test/fields/int.spec.ts | 16 ++--- test/form-state/int.spec.ts | 24 +++---- test/localization/int.spec.ts | 20 +++--- test/queues/int.spec.ts | 22 +++---- test/queues/utilities.ts | 4 +- test/uploads/int.spec.ts | 18 ++++-- test/versions/int.spec.ts | 24 ++++--- 117 files changed, 624 insertions(+), 520 deletions(-) rename packages/next/src/utilities/{getAdminContext.rsc.spec.ts => createAdminContext.rsc.spec.ts} (82%) rename packages/next/src/utilities/{getAdminContext.ts => createAdminContext.ts} (52%) rename packages/payload/src/admin/{getAdminContext.spec.ts => createAdminContext.spec.ts} (87%) rename packages/payload/src/admin/{getAdminContext.ts => createAdminContext.ts} (84%) rename packages/payload/src/utilities/{createPayloadReq.spec.ts => createPayloadRequest.spec.ts} (85%) rename packages/payload/src/utilities/{createPayloadReq.ts => createPayloadRequest.ts} (95%) rename packages/payload/src/utilities/{createPayloadReqFromWebRequest.spec.ts => createPayloadRequestFromWebRequest.spec.ts} (78%) rename packages/payload/src/utilities/{createPayloadReqFromWebRequest.ts => createPayloadRequestFromWebRequest.ts} (95%) rename packages/tanstack-start/src/utilities/{getAdminContext.server.spec.ts => createAdminContext.server.spec.ts} (68%) create mode 100644 packages/tanstack-start/src/utilities/createAdminContext.server.ts delete mode 100644 packages/tanstack-start/src/utilities/getAdminContext.server.ts diff --git a/docs/jobs-queue/jobs.mdx b/docs/jobs-queue/jobs.mdx index 43adc7cded1..0fe1e483044 100644 --- a/docs/jobs-queue/jobs.mdx +++ b/docs/jobs-queue/jobs.mdx @@ -289,7 +289,7 @@ These functions receive the current `req` and return a boolean. By default, they Omit `overrideAccess` or pass `overrideAccess: false` to run them from the Local API: ```ts -const req = await createPayloadReq({ payload, user }) +const req = await createPayloadRequest({ payload, user }) await payload.jobs.queue({ workflow: 'createPost', diff --git a/docs/migration-guide/v4.mdx b/docs/migration-guide/v4.mdx index 4a8c2762623..fba10e26454 100644 --- a/docs/migration-guide/v4.mdx +++ b/docs/migration-guide/v4.mdx @@ -24,41 +24,41 @@ The codemod is idempotent and safe to run on a partially-migrated project. ### Payload request creation is consolidated -Payload request creation now uses `createPayloadReq` for local contexts and `createPayloadReqFromWebRequest` to convert a Web `Request`. Admin context setup is exposed as `getAdminContext`. +Payload request creation now uses `createPayloadRequest` for local contexts and `createPayloadRequestFromWebRequest` to convert a Web `Request`. Admin context setup is exposed as `createAdminContext`. -| Old name | New name | -| --------------------------------------- | --------------------------------------- | -| `createLocalReq` | `createPayloadReq` | -| `CreateLocalReqOptions` (internal type) | `Omit` | -| `createPayloadRequest` | `createPayloadReqFromWebRequest` | -| `initReq` | `getAdminContext` | -| `InitReqArgs` | `GetAdminContextArgs` | -| `InitReqResult` | `GetAdminContextResult` | -| `InitReqCache` | `AdminContextCache` | -| `InitReqPartialResult` | `PartialAdminContext` | +| Old name | New name | +| --------------------------------------- | ------------------------------------------- | +| `createLocalReq` | `createPayloadRequest` | +| `CreateLocalReqOptions` (internal type) | `Omit` | +| `createPayloadRequest` | `createPayloadRequestFromWebRequest` | +| `initReq` | `createAdminContext` | +| `InitReqArgs` | `CreateAdminContextArgs` | +| `InitReqResult` | `CreateAdminContextResult` | +| `InitReqCache` | `AdminContextCache` | +| `InitReqPartialResult` | `PartialAdminContext` | -`getAdminContext` and its cache types are primarily contracts for framework adapters. Custom Root layout adapters must rename their injected `initReq` callback to `getAdminContext`. +`createAdminContext` and its cache types are primarily contracts for framework adapters. Custom Root layout adapters must rename their injected `initReq` callback to `createAdminContext`. Local request creation now takes a single object argument containing `payload`. For a request without options: ```diff -import { createLocalReq } from 'payload' -+import { createPayloadReq } from 'payload' ++import { createPayloadRequest } from 'payload' -const req = await createLocalReq({}, payload) -+const req = await createPayloadReq({ payload }) ++const req = await createPayloadRequest({ payload }) ``` -`CreatePayloadReqArgs` is exported from `payload` and requires `payload`. Options-only values should use `Omit`. The old `CreateLocalReqOptions` type was declared in the internal request utility, not exported from the package root; relative/private imports of that type require manual migration. The codemod handles an existing named import of that type only when its source is `payload`. +`CreatePayloadRequestArgs` is exported from `payload` and requires `payload`. Options-only values should use `Omit`. The old `CreateLocalReqOptions` type was declared in the internal request utility, not exported from the package root; relative/private imports of that type require manual migration. The codemod handles an existing named import of that type only when its source is `payload`. Web request conversion uses its new role-specific name: ```diff -import { createPayloadRequest } from 'payload' -+import { createPayloadReqFromWebRequest } from 'payload' ++import { createPayloadRequestFromWebRequest } from 'payload' -const req = await createPayloadRequest({ config, request }) -+const req = await createPayloadReqFromWebRequest({ config, request }) ++const req = await createPayloadRequestFromWebRequest({ config, request }) ``` #### Codemod @@ -445,20 +445,20 @@ Several types and utilities that were re-exported from `@payloadcms/ui` and `@pa **Pass-through re-exports** — same name, new source: -| Symbol | Old source | New source | -| ----------------------------- | ---------------------------- | --------------------------------------------- | -| `Column` | `@payloadcms/ui` | `payload` | -| `ListViewSlots` | `@payloadcms/ui` | `payload` | -| `ListViewClientProps` | `@payloadcms/ui` | `payload` | -| `EntityType` | `@payloadcms/ui/shared` | `payload/shared` | -| `formatAdminURL` | `@payloadcms/ui/shared` | `payload/shared` | -| `mergeListSearchAndWhere` | `@payloadcms/ui/shared` | `payload/shared` | -| `mergeHeaders` | `@payloadcms/next/utilities` | `payload` | -| `headersWithCors` | `@payloadcms/next/utilities` | `payload` | -| `createPayloadRequest` | `@payloadcms/next/utilities` | `createPayloadReqFromWebRequest` in `payload` | -| `addDataAndFileToRequest` | `@payloadcms/next/utilities` | `payload` | -| `sanitizeLocales` | `@payloadcms/next/utilities` | `payload` | -| `addLocalesToRequestFromData` | `@payloadcms/next/utilities` | `payload` | +| Symbol | Old source | New source | +| ----------------------------- | ---------------------------- | ------------------------------------------------- | +| `Column` | `@payloadcms/ui` | `payload` | +| `ListViewSlots` | `@payloadcms/ui` | `payload` | +| `ListViewClientProps` | `@payloadcms/ui` | `payload` | +| `EntityType` | `@payloadcms/ui/shared` | `payload/shared` | +| `formatAdminURL` | `@payloadcms/ui/shared` | `payload/shared` | +| `mergeListSearchAndWhere` | `@payloadcms/ui/shared` | `payload/shared` | +| `mergeHeaders` | `@payloadcms/next/utilities` | `payload` | +| `headersWithCors` | `@payloadcms/next/utilities` | `payload` | +| `createPayloadRequest` | `@payloadcms/next/utilities` | `createPayloadRequestFromWebRequest` in `payload` | +| `addDataAndFileToRequest` | `@payloadcms/next/utilities` | `payload` | +| `sanitizeLocales` | `@payloadcms/next/utilities` | `payload` | +| `addLocalesToRequestFromData` | `@payloadcms/next/utilities` | `payload` | **Renamed types** — use the new canonical name from `payload`: @@ -477,7 +477,7 @@ Several types and utilities that were re-exported from `@payloadcms/ui` and `@pa + import { headersWithCors, mergeHeaders } from 'payload' ``` -Running `npx @payloadcms/codemod --transform migrate-aliased-exports` preserves old local bindings by importing renamed symbols with an `as` alias (e.g. `import type { CollectionPreferences as ListPreferences } from 'payload'` or `import { createPayloadReqFromWebRequest as createPayloadRequest } from 'payload'`). Run `migrate-payload-request-creation` to complete the request helper's identifier and call-site migration, or drop the other aliases and rename their usages manually if you want to fully commit to the new names. +Running `npx @payloadcms/codemod --transform migrate-aliased-exports` preserves old local bindings by importing renamed symbols with an `as` alias (e.g. `import type { CollectionPreferences as ListPreferences } from 'payload'` or `import { createPayloadRequestFromWebRequest as createPayloadRequest } from 'payload'`). Run `migrate-payload-request-creation` to complete the request helper's identifier and call-site migration, or drop the other aliases and rename their usages manually if you want to fully commit to the new names. ### `DefaultValue` function variant is now typed to allow returning a `Promise` ([#17522](https://github.com/payloadcms/payload/pull/17522)) diff --git a/packages/codemod/README.md b/packages/codemod/README.md index b4bd961cdad..00c945f6e90 100644 --- a/packages/codemod/README.md +++ b/packages/codemod/README.md @@ -88,7 +88,7 @@ The tool loads your project via [ts-morph](https://ts-morph.com/), using your `t - `migrate-block-references-to-blocks`: renames blocks field `blockReferences` configs to `blocks`, and removes a sibling `blocks: []` placeholder when present. If a non-empty `blocks` sibling already exists, the transform leaves the field untouched and surfaces a note for manual review. - `migrate-hide-api-url`: migrates `admin.hideAPIURL: true` to `admin.components.views.edit.api.tab.condition: () => false` on collection and global configs. - `migrate-aliased-exports`: rewrites imports of types and utilities that used to be re-exported from `@payloadcms/ui` and `@payloadcms/next/utilities` to their canonical sources in `payload` / `payload/shared`. -- `migrate-payload-request-creation`: renames named request-helper and admin-context imports from `payload` and `payload/internal`. Calls migrate only with a plain identifier payload and either `{}` or an initialized local `const` options identifier whose type exposes all seven old option properties (or is `any`). The generated argument captures payload first and forwards only `context`, `depth`, `fallbackLocale`, `locale`, `req`, `urlSuffix`, and `user` through getters, preserving inherited getters, read order, and async rejection behavior. Existing `CreateLocalReqOptions` named imports from `payload` become options-only usages of `Omit`; relative/private imports of that internal type require manual migration. Unsupported shapes, type usages, and collisions leave the affected binding unchanged with notes. +- `migrate-payload-request-creation`: renames named request-helper and admin-context imports from `payload` and `payload/internal`. Calls migrate only with a plain identifier payload and either `{}` or an initialized local `const` options identifier whose type exposes all seven old option properties (or is `any`). The generated argument captures payload first and forwards only `context`, `depth`, `fallbackLocale`, `locale`, `req`, `urlSuffix`, and `user` through getters, preserving inherited getters, read order, and async rejection behavior. Existing `CreateLocalReqOptions` named imports from `payload` become options-only usages of `Omit`; relative/private imports of that internal type require manual migration. Unsupported shapes, type usages, and collisions leave the affected binding unchanged with notes. - `migrate-document-title-context`: migrates `title` and `setDocumentTitle` destructured from `useDocumentInfo()` to `useDocumentTitle()`. They were removed from `DocumentInfoContext` in v4 and now live on `DocumentTitleContext`. - `migrate-storage-adapters-to-config`: moves storage adapter factory calls (`s3Storage`, `gcsStorage`, `azureStorage`, `r2Storage`, `vercelBlobStorage`) from `plugins` to the new top-level `storage` array. Removes `plugins` if it becomes empty after the move. **Limitations:** aliased imports (e.g. `import { s3Storage as myS3 }`) are not detected; rename any aliases to the canonical factory name before running, or migrate those calls manually. The transform preserves AST structure but does not re-format output. Run `prettier --write` (or your project's formatter) after applying. - `rename-storage-adapters-to-storage`: renames the top-level `storageAdapters` config property to `storage`. Skips any object that already has a `storage` property. Run this if you previously ran `migrate-storage-adapters-to-config` and need to update the property name. diff --git a/packages/codemod/src/transforms/migrate-aliased-exports/index.ts b/packages/codemod/src/transforms/migrate-aliased-exports/index.ts index 9f4f319b078..f072108d13b 100644 --- a/packages/codemod/src/transforms/migrate-aliased-exports/index.ts +++ b/packages/codemod/src/transforms/migrate-aliased-exports/index.ts @@ -11,7 +11,7 @@ const SOURCE_MAP: Record> = { '@payloadcms/next/utilities': { addDataAndFileToRequest: { to: 'payload' }, addLocalesToRequestFromData: { to: 'payload' }, - createPayloadRequest: { rename: 'createPayloadReqFromWebRequest', to: 'payload' }, + createPayloadRequest: { rename: 'createPayloadRequestFromWebRequest', to: 'payload' }, headersWithCors: { to: 'payload' }, mergeHeaders: { to: 'payload' }, sanitizeLocales: { to: 'payload' }, @@ -125,7 +125,7 @@ export const migrateAliasedExports: Transform = { } }, description: - 'Move imports of aliased re-exports from @payloadcms/ui and @payloadcms/next/utilities to their canonical sources in `payload` / `payload/shared`. Renamed names (`createPayloadRequest` → `createPayloadReqFromWebRequest`, `ListPreferences` → `CollectionPreferences`, `ListComponentClientProps` → `ListViewClientProps`, `ListComponentServerProps` → `ListViewServerProps`) are imported using an `as` alias so existing usages keep compiling.', + 'Move imports of aliased re-exports from @payloadcms/ui and @payloadcms/next/utilities to their canonical sources in `payload` / `payload/shared`. Renamed names (`createPayloadRequest` → `createPayloadRequestFromWebRequest`, `ListPreferences` → `CollectionPreferences`, `ListComponentClientProps` → `ListViewClientProps`, `ListComponentServerProps` → `ListViewServerProps`) are imported using an `as` alias so existing usages keep compiling.', } type ApplyRenameArgs = { diff --git a/packages/codemod/src/transforms/migrate-aliased-exports/request.output.ts b/packages/codemod/src/transforms/migrate-aliased-exports/request.output.ts index ef1d6597283..f713bdfaa94 100644 --- a/packages/codemod/src/transforms/migrate-aliased-exports/request.output.ts +++ b/packages/codemod/src/transforms/migrate-aliased-exports/request.output.ts @@ -1,4 +1,4 @@ -import { createPayloadReqFromWebRequest as createPayloadRequest, createPayloadReqFromWebRequest as webRequest } from 'payload' +import { createPayloadRequestFromWebRequest as createPayloadRequest, createPayloadRequestFromWebRequest as webRequest } from 'payload' import { unrelated } from '@payloadcms/next/utilities' const req = createPayloadRequest(args) diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/aliases.output.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/aliases.output.ts index bdc9f45008e..983aad88e01 100644 --- a/packages/codemod/src/transforms/migrate-payload-request-creation/aliases.output.ts +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/aliases.output.ts @@ -1,6 +1,6 @@ -import type { CreatePayloadReqArgs as Options, GetAdminContextResult as Result } from 'payload' +import type { CreatePayloadRequestArgs as Options, CreateAdminContextResult as Result } from 'payload' -import { createPayloadReq as local, createPayloadReqFromWebRequest as web } from 'payload' +import { createPayloadRequest as local, createPayloadRequestFromWebRequest as web } from 'payload' const options: Omit = { user } const req = local({ payload: payload, get context() { return options.context }, get depth() { return options.depth }, get fallbackLocale() { return options.fallbackLocale }, get locale() { return options.locale }, get req() { return options.req }, get urlSuffix() { return options.urlSuffix }, get user() { return options.user } }) diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/collision.input.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/collision.input.ts index 979c9476b5a..11f696c2397 100644 --- a/packages/codemod/src/transforms/migrate-payload-request-creation/collision.input.ts +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/collision.input.ts @@ -1,8 +1,9 @@ import { createLocalReq } from 'payload' -import { createPayloadReq } from 'payload' +import { createPayloadRequest } from 'payload' import { initReq } from 'payload/internal' +const createPayloadRequestFromWebRequest = other const req = createLocalReq({}, payload) -function read(getAdminContext: Function) { +function read(createAdminContext: Function) { return initReq(args) } diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/comments.output.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/comments.output.ts index ca5882fc00e..5ebc849b0e6 100644 --- a/packages/codemod/src/transforms/migrate-payload-request-creation/comments.output.ts +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/comments.output.ts @@ -1,9 +1,9 @@ -import { createPayloadReq } from 'payload' +import { createPayloadRequest } from 'payload' const options: any = { user } -const req = createPayloadReq({ +const req = createPayloadRequest({ /* options */ /* after options */ // payload instance payload: payload /* after payload */, get context() { return options.context }, get depth() { return options.depth }, get fallbackLocale() { return options.fallbackLocale }, get locale() { return options.locale }, get req() { return options.req }, get urlSuffix() { return options.urlSuffix }, get user() { return options.user }, }) -const empty = createPayloadReq({ /* empty options */ payload: payload }) +const empty = createPayloadRequest({ /* empty options */ payload: payload }) diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/empty.output.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/empty.output.ts index 1b22125f4bd..57fdce86784 100644 --- a/packages/codemod/src/transforms/migrate-payload-request-creation/empty.output.ts +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/empty.output.ts @@ -1,5 +1,5 @@ -import { createPayloadReq, createPayloadReqFromWebRequest, type GetAdminContextResult } from 'payload' +import { createPayloadRequest, createPayloadRequestFromWebRequest, type CreateAdminContextResult } from 'payload' -const req = await createPayloadReq({ payload: payload }) -const web = createPayloadReqFromWebRequest(request, config) -type Context = GetAdminContextResult +const req = await createPayloadRequest({ payload: payload }) +const web = createPayloadRequestFromWebRequest(request, config) +type Context = CreateAdminContextResult diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/index.spec.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/index.spec.ts index 424188a1393..e938fc0ca29 100644 --- a/packages/codemod/src/transforms/migrate-payload-request-creation/index.spec.ts +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/index.spec.ts @@ -68,7 +68,7 @@ describe('migrate-payload-request-creation', () => { { context, depth, fallbackLocale, locale, req, urlSuffix, user }: Record, payload: unknown, ) => ({ context, depth, fallbackLocale, locale, req, urlSuffix, user, payload }), - createPayloadReq: ({ + createPayloadRequest: ({ context, depth, fallbackLocale, @@ -100,7 +100,7 @@ describe('migrate-payload-request-creation', () => { return context.observed } - expect(output).toContain('createPayloadReq(') + expect(output).toContain('createPayloadRequest(') expect(output).not.toContain('...options') expect(evaluate(output)).toEqual(evaluate(source)) expect(evaluate(output)).toEqual({ @@ -131,7 +131,7 @@ try { const evaluate = async (code: string) => { const context = { createLocalReq: async ({ context }: Record) => context, - createPayloadReq: async ({ context }: Record) => context, + createPayloadRequest: async ({ context }: Record) => context, observed: undefined, } const executable = new Project({ useInMemoryFileSystem: true }).createSourceFile( @@ -145,7 +145,7 @@ try { return await context.observed } - expect(output).toContain('createPayloadReq(') + expect(output).toContain('createPayloadRequest(') expect(await evaluate(source)).toBe('rejected:getter') expect(await evaluate(output)).toBe('rejected:getter') }) @@ -161,18 +161,21 @@ try { useInMemoryFileSystem: true, }) const actualHelper = ts.createSourceFile( - 'createPayloadReq.ts', - await readFile(join(here, '../../../../payload/src/utilities/createPayloadReq.ts'), 'utf8'), + 'createPayloadRequest.ts', + await readFile( + join(here, '../../../../payload/src/utilities/createPayloadRequest.ts'), + 'utf8', + ), ts.ScriptTarget.Latest, true, ) const args = actualHelper.statements.find( - (node) => ts.isTypeAliasDeclaration(node) && node.name.text === 'CreatePayloadReqArgs', + (node) => ts.isTypeAliasDeclaration(node) && node.name.text === 'CreatePayloadRequestArgs', )! project.createSourceFile( '/node_modules/payload/index.d.ts', - `type Payload = { id: string }; type RequestContext = Record; type TypedLocale = string; type PayloadRequest = Record; type User = string;\n${args.getText(actualHelper)}\nexport declare function createPayloadReq(args: CreatePayloadReqArgs): unknown`, + `type Payload = { id: string }; type RequestContext = Record; type TypedLocale = string; type PayloadRequest = Record; type User = string;\n${args.getText(actualHelper)}\nexport declare function createPayloadRequest(args: CreatePayloadRequestArgs): unknown`, ) const file = project.createSourceFile('/output.ts', output) @@ -183,7 +186,7 @@ try { file.getVariableDeclarationOrThrow('options').getType().getProperty('payload'), ).toBeUndefined() expect(output).toContain( - `Omit<${local === 'Options' ? 'Options' : 'CreatePayloadReqArgs'}, 'payload'>`, + `Omit<${local === 'Options' ? 'Options' : 'CreatePayloadRequestArgs'}, 'payload'>`, ) }, ) @@ -226,14 +229,14 @@ try { const intermediate = await runTransform({ source, transform: migrateAliasedExports }) expect(intermediate).toBe( - `import { createPayloadReqFromWebRequest as createPayloadRequest } from 'payload'\nconst req = createPayloadRequest(args)`, + `import { createPayloadRequestFromWebRequest as createPayloadRequest } from 'payload'\nconst req = createPayloadRequest(args)`, ) const output = await runTransform({ source: intermediate, transform: migratePayloadRequestCreation, }) - const expected = `import { createPayloadReqFromWebRequest } from 'payload'\nconst req = createPayloadReqFromWebRequest(args)` + const expected = `import { createPayloadRequestFromWebRequest } from 'payload'\nconst req = createPayloadRequestFromWebRequest(args)` expect(output).toBe(expected) expect(await runTransform({ source: output, transform: migratePayloadRequestCreation })).toBe( @@ -241,8 +244,8 @@ try { ) }) - it('should preserve user-authored aliases of createPayloadReqFromWebRequest', async () => { - const source = `import { createPayloadReqFromWebRequest as webRequest } from 'payload'\nconst req = webRequest(args)` + it('should preserve user-authored aliases of createPayloadRequestFromWebRequest', async () => { + const source = `import { createPayloadRequestFromWebRequest as webRequest } from 'payload'\nconst req = webRequest(args)` expect(await runTransform({ source, transform: migratePayloadRequestCreation })).toBe(source) }) @@ -256,7 +259,7 @@ try { const evaluate = (code: string) => { const context = { createLocalReq: (options: { user: string }) => ({ ...options }), - createPayloadReq: (options: { user: string }) => options, + createPayloadRequest: (options: { user: string }) => options, observed: undefined, } const executable = new Project({ useInMemoryFileSystem: true }).createSourceFile( @@ -309,8 +312,8 @@ try { const result = await migratePayloadRequestCreation.apply({ packageJsons: [], project }) - expect(matching.getFullText()).toContain('createPayloadReq({ payload: payload })') - expect(matching.getFullText()).toContain('createPayloadReqFromWebRequest(args)') + expect(matching.getFullText()).toContain('createPayloadRequest({ payload: payload })') + expect(matching.getFullText()).toContain('createPayloadRequestFromWebRequest(args)') expect(dependency.getFullText()).toBe(declaration) expect(unsupported.getFullText()).toBe(unsupportedSource) expect(result.filesChanged).toEqual(['/matching.ts']) @@ -329,7 +332,7 @@ try { const result = await migratePayloadRequestCreation.apply({ packageJsons: [], project }) expect(result).toEqual({ filesChanged: ['/matching.ts'] }) - expect(file.getFullText()).toContain('createPayloadReq({ payload: payload })') + expect(file.getFullText()).toContain('createPayloadRequest({ payload: payload })') expect(file.getDescendantsOfKind(SyntaxKind.CallExpression)[0]?.getArguments()).toHaveLength(1) }) @@ -339,7 +342,7 @@ try { const output = await runTransform({ source, transform: migratePayloadRequestCreation }) expect(output).toBe( - `import { createLocalReq, createPayloadReq as local } from 'payload'\nconst callback = createLocalReq\nconst req = local({ payload: payload })`, + `import { createLocalReq, createPayloadRequest as local } from 'payload'\nconst callback = createLocalReq\nconst req = local({ payload: payload })`, ) }) diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/index.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/index.ts index e7a4d87e262..d9cb0f5e291 100644 --- a/packages/codemod/src/transforms/migrate-payload-request-creation/index.ts +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/index.ts @@ -6,14 +6,14 @@ import type { Transform } from '../../types.js' const RENAMES = { payload: { - createLocalReq: 'createPayloadReq', - CreateLocalReqOptions: 'CreatePayloadReqArgs', - createPayloadRequest: 'createPayloadReqFromWebRequest', - InitReqResult: 'GetAdminContextResult', + createLocalReq: 'createPayloadRequest', + CreateLocalReqOptions: 'CreatePayloadRequestArgs', + createPayloadRequest: 'createPayloadRequestFromWebRequest', + InitReqResult: 'CreateAdminContextResult', }, 'payload/internal': { - initReq: 'getAdminContext', - InitReqArgs: 'GetAdminContextArgs', + initReq: 'createAdminContext', + InitReqArgs: 'CreateAdminContextArgs', InitReqCache: 'AdminContextCache', InitReqPartialResult: 'PartialAdminContext', }, @@ -29,6 +29,18 @@ const OPTION_NAMES = [ 'user', ] as const +const getImportPriority = (name: string): number => { + if (name === 'createPayloadRequest') { + return 0 + } + + if (name === 'CreateLocalReqOptions') { + return 2 + } + + return 1 +} + export const migratePayloadRequestCreation: Transform = { name: 'migrate-payload-request-creation', apply: ({ project }) => { @@ -44,17 +56,13 @@ export const migratePayloadRequestCreation: Transform = { .getNamedImports() .map((spec) => ({ source: declaration.getModuleSpecifierValue(), spec })), ) - .sort( - (a, b) => - Number(a.spec.getName() === 'CreateLocalReqOptions') - - Number(b.spec.getName() === 'CreateLocalReqOptions'), - ) + .sort((a, b) => getImportPriority(a.spec.getName()) - getImportPriority(b.spec.getName())) for (const { source, spec } of imports) { if (source !== 'payload' && (source.startsWith('.') || source.startsWith('payload/'))) { if (spec.getName() === 'CreateLocalReqOptions') { notes.push( - `${file.getFilePath()}: Unsupported CreateLocalReqOptions import source \`${source}\`; migrate this private/relative type import manually to Omit from payload.`, + `${file.getFilePath()}: Unsupported CreateLocalReqOptions import source \`${source}\`; migrate this private/relative type import manually to Omit from payload.`, ) } } @@ -69,7 +77,7 @@ export const migratePayloadRequestCreation: Transform = { const aliasName = spec.getAliasNode()?.getText() const isAliasedExportMigrationArtifact = source === 'payload' && - importedName === 'createPayloadReqFromWebRequest' && + importedName === 'createPayloadRequestFromWebRequest' && aliasName === 'createPayloadRequest' const originalName = isAliasedExportMigrationArtifact ? aliasName : importedName @@ -88,6 +96,20 @@ export const migratePayloadRequestCreation: Transform = { const binding = spec.getAliasNode()! const references = binding.findReferencesAsNodes().filter((node) => node !== binding) + const isCurrentPayloadRequestCreator = + source === 'payload' && + importedName === 'createPayloadRequest' && + !isAliasedExportMigrationArtifact && + references.length > 0 && + references.every(isPayloadRequestCreationReference) + + if (isCurrentPayloadRequestCreator) { + if (!hadAlias) { + spec.removeAlias() + } + continue + } + const hasCollision = (!hadAlias || isAliasedExportMigrationArtifact) && [binding, ...references].some((node) => @@ -203,6 +225,22 @@ export const migratePayloadRequestCreation: Transform = { 'Renames Payload request and admin-context imports, preserves options-only types, and migrates safe createLocalReq calls by capturing payload before projecting the seven old option properties; reports unsupported bindings for manual migration.', } +function isPayloadRequestCreationReference(reference: Node): boolean { + const parent = reference.getParent() + + if ( + !Node.isCallExpression(parent) || + parent.getExpression() !== reference || + parent.getArguments().length !== 1 + ) { + return false + } + + const [argument] = parent.getArguments() + + return Node.isObjectLiteralExpression(argument) && Boolean(argument.getProperty('payload')) +} + function getUnsafeOptionsReason({ options }: { options: Node }): string | undefined { if (Node.isObjectLiteralExpression(options)) { return options.getProperties().length === 0 diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/internal.output.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/internal.output.ts index 7c429725d49..5249369a8db 100644 --- a/packages/codemod/src/transforms/migrate-payload-request-creation/internal.output.ts +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/internal.output.ts @@ -1,7 +1,7 @@ -import { getAdminContext, type GetAdminContextArgs, type AdminContextCache, type PartialAdminContext } from 'payload/internal' -import { getAdminContext as initialize, type GetAdminContextArgs as Args } from 'payload/internal' +import { createAdminContext, type CreateAdminContextArgs, type AdminContextCache, type PartialAdminContext } from 'payload/internal' +import { createAdminContext as initialize, type CreateAdminContextArgs as Args } from 'payload/internal' -const context = getAdminContext(args as GetAdminContextArgs) +const context = createAdminContext(args as CreateAdminContextArgs) const aliased = initialize(args as Args) type Cache = AdminContextCache type PartialContext = PartialAdminContext diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/populated.output.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/populated.output.ts index 7b80b770a89..3f24a787243 100644 --- a/packages/codemod/src/transforms/migrate-payload-request-creation/populated.output.ts +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/populated.output.ts @@ -1,4 +1,4 @@ -import { createPayloadReq, type CreatePayloadReqArgs } from 'payload' +import { createPayloadRequest, type CreatePayloadRequestArgs } from 'payload' -const options: Omit = { user } -const req = await createPayloadReq({ payload: payload, get context() { return options.context }, get depth() { return options.depth }, get fallbackLocale() { return options.fallbackLocale }, get locale() { return options.locale }, get req() { return options.req }, get urlSuffix() { return options.urlSuffix }, get user() { return options.user } }) +const options: Omit = { user } +const req = await createPayloadRequest({ payload: payload, get context() { return options.context }, get depth() { return options.depth }, get fallbackLocale() { return options.fallbackLocale }, get locale() { return options.locale }, get req() { return options.req }, get urlSuffix() { return options.urlSuffix }, get user() { return options.user } }) diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/shadowed.output.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/shadowed.output.ts index 26fbcfaec1e..c0d4b493177 100644 --- a/packages/codemod/src/transforms/migrate-payload-request-creation/shadowed.output.ts +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/shadowed.output.ts @@ -1,6 +1,6 @@ -import { createPayloadReq } from 'payload' +import { createPayloadRequest } from 'payload' -const req = createPayloadReq({ payload: payload }) +const req = createPayloadRequest({ payload: payload }) function unrelated(createLocalReq: Function) { return createLocalReq({}, payload) } diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/shorthand.output.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/shorthand.output.ts index 1184d5f145f..2005a858a47 100644 --- a/packages/codemod/src/transforms/migrate-payload-request-creation/shorthand.output.ts +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/shorthand.output.ts @@ -1,7 +1,7 @@ -import { createPayloadReqFromWebRequest } from 'payload' -import { getAdminContext } from 'payload/internal' +import { createPayloadRequestFromWebRequest } from 'payload' +import { createAdminContext } from 'payload/internal' -const handlers = { createPayloadRequest: createPayloadReqFromWebRequest, initReq: getAdminContext } +const handlers = { createPayloadRequest: createPayloadRequestFromWebRequest, initReq: createAdminContext } const requestKey: string = 'createPayloadRequest' const contextKey: string = 'initReq' handlers[requestKey](args) diff --git a/packages/drizzle/src/migrate.ts b/packages/drizzle/src/migrate.ts index a72a878b44b..c118048eb4c 100644 --- a/packages/drizzle/src/migrate.ts +++ b/packages/drizzle/src/migrate.ts @@ -2,7 +2,7 @@ import type { MigrationResult, Payload } from 'payload' import { commitTransaction, - createPayloadReq, + createPayloadRequest, initTransaction, killTransaction, readMigrationFiles, @@ -98,7 +98,7 @@ export const migrate: DrizzleAdapter['migrate'] = async function migrate( async function runMigrationFile(payload: Payload, migration: Migration, batch: number) { const start = Date.now() - const req = await createPayloadReq({ payload }) + const req = await createPayloadRequest({ payload }) payload.logger.info({ msg: `Migrating: ${migration.name}` }) diff --git a/packages/drizzle/src/migrateDown.ts b/packages/drizzle/src/migrateDown.ts index 2c3ee85dc66..b9ed51f5d27 100644 --- a/packages/drizzle/src/migrateDown.ts +++ b/packages/drizzle/src/migrateDown.ts @@ -2,7 +2,7 @@ import type { MigrationResult } from 'payload' import { commitTransaction, - createPayloadReq, + createPayloadRequest, getMigrations, initTransaction, killTransaction, @@ -42,7 +42,7 @@ export async function migrateDown(this: DrizzleAdapter): Promise - getAdminContext({ configPromise, importMap, key: 'RootLayout' }), + createAdminContext: ({ configPromise, importMap }) => + createAdminContext({ configPromise, importMap, key: 'RootLayout' }), }) diff --git a/packages/next/src/adapters/layout.tsx b/packages/next/src/adapters/layout.tsx index fc6d4449a78..171112e6918 100644 --- a/packages/next/src/adapters/layout.tsx +++ b/packages/next/src/adapters/layout.tsx @@ -3,7 +3,7 @@ import { RootLayout as UIRootLayout } from '@payloadcms/ui/layouts' import { Inter, Roboto_Mono } from 'next/font/google' import React from 'react' -import { getAdminContext } from '../utilities/getAdminContext.js' +import { createAdminContext } from '../utilities/createAdminContext.js' import { NextRouterAdapter } from './router.js' // CSS import lives here (not in `@payloadcms/ui`) so the esbuild bundle that produces // `@payloadcms/next/css` (`dist/prod/styles.css`) can follow the CSS chain. esbuild @@ -34,7 +34,7 @@ export { metadata } from '@payloadcms/ui/layouts' type Props = Omit< React.ComponentProps, - 'additionalDependencyChecks' | 'fonts' | 'getAdminContext' | 'RouterAdapter' + 'additionalDependencyChecks' | 'fonts' | 'createAdminContext' | 'RouterAdapter' > export const RootLayout = (props: Props) => ( @@ -45,7 +45,7 @@ export const RootLayout = (props: Props) => ( { className: inter.className, variable: inter.variable }, { className: robotoMono.className, variable: robotoMono.variable }, ]} - getAdminContext={getAdminContext} + createAdminContext={createAdminContext} RouterAdapter={NextRouterAdapter} /> ) diff --git a/packages/next/src/adapters/views.tsx b/packages/next/src/adapters/views.tsx index 0a6ae788c02..4027968f8ea 100644 --- a/packages/next/src/adapters/views.tsx +++ b/packages/next/src/adapters/views.tsx @@ -11,7 +11,7 @@ import { renderRoot } from '@payloadcms/ui/views/Root' import { defaultAdminViews } from '@payloadcms/ui/views/Root/adminViews' import { notFound, redirect } from 'next/navigation.js' -import { getAdminContext } from '../utilities/getAdminContext.js' +import { createAdminContext } from '../utilities/createAdminContext.js' export const adminViews: AdminViewAdapter = defaultAdminViews @@ -27,6 +27,7 @@ type PageProps = { } export const RootPage = (props: PageProps) => - renderRoot({ ...props, adminViews, getAdminContext, notFound, redirect }) + renderRoot({ ...props, adminViews, createAdminContext, notFound, redirect }) -export const NotFoundPage = (props: PageProps) => renderNotFoundPage({ ...props, getAdminContext }) +export const NotFoundPage = (props: PageProps) => + renderNotFoundPage({ ...props, createAdminContext }) diff --git a/packages/next/src/routes/graphql/handler.ts b/packages/next/src/routes/graphql/handler.ts index d9638905460..d4cebe62cfb 100644 --- a/packages/next/src/routes/graphql/handler.ts +++ b/packages/next/src/routes/graphql/handler.ts @@ -7,7 +7,7 @@ import { status as httpStatus } from 'http-status' import { addDataAndFileToRequest, addLocalesToRequestFromData, - createPayloadReqFromWebRequest, + createPayloadRequestFromWebRequest, headersWithCors, logError, mergeHeaders, @@ -98,7 +98,7 @@ export const getGraphql = async (config: Promise | SanitizedCon export const POST = (config: Promise | SanitizedConfig) => async (request: Request) => { const originalRequest = request.clone() - const req = await createPayloadReqFromWebRequest({ + const req = await createPayloadRequestFromWebRequest({ canSetHeaders: true, config, request, diff --git a/packages/next/src/routes/graphql/playground.ts b/packages/next/src/routes/graphql/playground.ts index e2657507a20..ae13b7b6669 100644 --- a/packages/next/src/routes/graphql/playground.ts +++ b/packages/next/src/routes/graphql/playground.ts @@ -1,9 +1,9 @@ import { renderPlaygroundPage } from 'graphql-playground-html' -import { createPayloadReqFromWebRequest, type SanitizedConfig } from 'payload' +import { createPayloadRequestFromWebRequest, type SanitizedConfig } from 'payload' import { formatAdminURL } from 'payload/shared' export const GET = (config: Promise) => async (request: Request) => { - const req = await createPayloadReqFromWebRequest({ + const req = await createPayloadRequestFromWebRequest({ config, request, }) diff --git a/packages/next/src/utilities/getAdminContext.rsc.spec.ts b/packages/next/src/utilities/createAdminContext.rsc.spec.ts similarity index 82% rename from packages/next/src/utilities/getAdminContext.rsc.spec.ts rename to packages/next/src/utilities/createAdminContext.rsc.spec.ts index b01ca4df59b..6ec0897b2b5 100644 --- a/packages/next/src/utilities/getAdminContext.rsc.spec.ts +++ b/packages/next/src/utilities/createAdminContext.rsc.spec.ts @@ -5,21 +5,21 @@ import { createRequire } from 'node:module' import type React from 'react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { getAdminContext } from './getAdminContext.js' +import { createAdminContext } from './createAdminContext.js' -const { counters, getPayloadAdminContext } = vi.hoisted(() => ({ +const { counters, createPayloadAdminContext } = vi.hoisted(() => ({ counters: { locale: 0, partial: 0, request: 0, }, - getPayloadAdminContext: vi.fn(), + createPayloadAdminContext: vi.fn(), })) vi.mock('react', () => createRequire(import.meta.url)('react')) vi.mock('payload/internal', () => ({ - getAdminContext: getPayloadAdminContext, + createAdminContext: createPayloadAdminContext, })) vi.mock('../adapters/server.js', () => ({ @@ -46,9 +46,9 @@ const importMap = {} as ImportMap async function renderNavigation(): Promise { async function Navigation() { await Promise.all([ - getAdminContext({ configPromise, importMap, key: 'RootLayout' }), - getAdminContext({ configPromise, importMap, key: 'RootLayout' }), - getAdminContext({ configPromise, importMap, key: 'initPage' }), + createAdminContext({ configPromise, importMap, key: 'RootLayout' }), + createAdminContext({ configPromise, importMap, key: 'RootLayout' }), + createAdminContext({ configPromise, importMap, key: 'initPage' }), ]) return null @@ -62,13 +62,13 @@ async function renderNavigation(): Promise { async function renderNavigationWithOverrides(): Promise { async function Navigation() { await Promise.all([ - getAdminContext({ + createAdminContext({ configPromise, importMap, key: 'initPage', overrides: { context: { source: 'first' } }, }), - getAdminContext({ + createAdminContext({ configPromise, importMap, key: 'initPage', @@ -84,12 +84,12 @@ async function renderNavigationWithOverrides(): Promise { await new Response(stream).arrayBuffer() } -describe('Next getAdminContext RSC cache', () => { +describe('Next createAdminContext RSC cache', () => { beforeEach(() => { counters.locale = 0 counters.partial = 0 counters.request = 0 - getPayloadAdminContext.mockReset().mockImplementation(async ({ cache, key, overrides }) => { + createPayloadAdminContext.mockReset().mockImplementation(async ({ cache, key, overrides }) => { await cache.getPartial(async () => { counters.partial += 1 return {} diff --git a/packages/next/src/utilities/getAdminContext.ts b/packages/next/src/utilities/createAdminContext.ts similarity index 52% rename from packages/next/src/utilities/getAdminContext.ts rename to packages/next/src/utilities/createAdminContext.ts index 44dbf983bf9..14ce6da3e16 100644 --- a/packages/next/src/utilities/getAdminContext.ts +++ b/packages/next/src/utilities/createAdminContext.ts @@ -1,14 +1,14 @@ -import type { GetAdminContextResult } from 'payload' -import type { AdminContextCache, GetAdminContextArgs, PartialAdminContext } from 'payload/internal' +import type { CreateAdminContextResult } from 'payload' +import type { AdminContextCache, CreateAdminContextArgs, PartialAdminContext } from 'payload/internal' -import { getAdminContext as getPayloadAdminContext } from 'payload/internal' +import { createAdminContext as createPayloadAdminContext } from 'payload/internal' import { nextServerAdapter } from '../adapters/server.js' import { selectiveCache } from './selectiveCache.js' const partialContextCache = selectiveCache('partialContext') -const localeCache = selectiveCache>('locale') -const adminContextCache = selectiveCache('adminContext') +const localeCache = selectiveCache>('locale') +const adminContextCache = selectiveCache('adminContext') const cache: AdminContextCache = { getLocale: (resolveLocale, ...key) => localeCache.get({ create: resolveLocale, key }), @@ -21,12 +21,12 @@ const cache: AdminContextCache = { }), } -type NextGetAdminContextArgs = { +type NextCreateAdminContextArgs = { key: string -} & Omit +} & Omit -export const getAdminContext = (args: NextGetAdminContextArgs) => - getPayloadAdminContext({ +export const createAdminContext = (args: NextCreateAdminContextArgs) => + createPayloadAdminContext({ ...args, cache, serverAdapter: nextServerAdapter, diff --git a/packages/payload/skills/payload/reference/PLUGIN-DEVELOPMENT.md b/packages/payload/skills/payload/reference/PLUGIN-DEVELOPMENT.md index 54952ce23a6..da70c325ba0 100644 --- a/packages/payload/skills/payload/reference/PLUGIN-DEVELOPMENT.md +++ b/packages/payload/skills/payload/reference/PLUGIN-DEVELOPMENT.md @@ -1350,7 +1350,7 @@ Create `dev/int.spec.ts`: ```ts import type { Payload } from 'payload' import config from '@payload-config' -import { createPayloadReqFromWebRequest, getPayload } from 'payload' +import { createPayloadRequestFromWebRequest, getPayload } from 'payload' import { afterAll, beforeAll, describe, expect, test } from 'vitest' import { customEndpointHandler } from '../src/endpoints/handler.js' @@ -1385,7 +1385,7 @@ describe('Plugin integration tests', () => { test('should query custom endpoint', async () => { const request = new Request('http://localhost:3000/api/my-endpoint') - const payloadRequest = await createPayloadReqFromWebRequest({ config, request }) + const payloadRequest = await createPayloadRequestFromWebRequest({ config, request }) const response = await customEndpointHandler(payloadRequest) const data = await response.json() expect(data).toMatchObject({ message: 'Hello' }) diff --git a/packages/payload/src/admin/getAdminContext.spec.ts b/packages/payload/src/admin/createAdminContext.spec.ts similarity index 87% rename from packages/payload/src/admin/getAdminContext.spec.ts rename to packages/payload/src/admin/createAdminContext.spec.ts index 4c8c885c7fe..38167a8e5ab 100644 --- a/packages/payload/src/admin/getAdminContext.spec.ts +++ b/packages/payload/src/admin/createAdminContext.spec.ts @@ -3,12 +3,12 @@ import type { ImportMap } from '../cli/commands/generateImportMap/generateImport import type { SanitizedConfig } from '../config/types.js' import type { Payload } from '../index.js' import type { ServerAdapter } from './adapters/server.js' -import type { AdminContextCache, PartialAdminContext } from './getAdminContext.js' -import type { GetAdminContextResult } from './functions/index.js' +import type { AdminContextCache, PartialAdminContext } from './createAdminContext.js' +import type { CreateAdminContextResult } from './functions/index.js' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { getAdminContext } from './getAdminContext.js' +import { createAdminContext } from './createAdminContext.js' const { applyUserReadAccess, @@ -139,19 +139,19 @@ const createExecutingCache = (): AdminContextCache => ({ getPartial: vi.fn((createPartialContext: () => Promise) => createPartialContext(), ), - getRequest: vi.fn((createContext: () => Promise) => createContext()), + getRequest: vi.fn((createContext: () => Promise) => createContext()), }) const createReusingCache = (): AdminContextCache => { const localeResults: Array<{ cacheArgs: unknown[] - result: Pick + result: Pick }> = [] let partialContext: PartialAdminContext | undefined const contexts: Array<{ cacheArgs: unknown[] key: string - result: GetAdminContextResult + result: CreateAdminContextResult }> = [] return { @@ -195,7 +195,7 @@ const createReusingCache = (): AdminContextCache => { } } -describe('getAdminContext', () => { +describe('createAdminContext', () => { beforeEach(() => { applyUserReadAccess.mockReset().mockImplementation(async ({ user }) => ({ collection: user.collection, @@ -210,7 +210,7 @@ describe('getAdminContext', () => { }) it('should derive the URL and a nested query from requestURL', async () => { - const result = await getAdminContext({ + const result = await createAdminContext({ configPromise: config, importMap, requestURL: 'https://example.com/admin?locale=es&where%5Btitle%5D%5Bequals%5D=Hello', @@ -233,7 +233,7 @@ describe('getAdminContext', () => { it('should persist an authenticated request locale', async () => { getPayload.mockResolvedValue({ ...payload, config: localizedConfig }) - const result = await getAdminContext({ + const result = await createAdminContext({ configPromise: localizedConfig, importMap, requestURL: 'https://example.com/admin?locale=es', @@ -257,7 +257,7 @@ describe('getAdminContext', () => { }) getPayload.mockResolvedValue({ ...payload, config: localizedConfig }) - const result = await getAdminContext({ + const result = await createAdminContext({ configPromise: localizedConfig, importMap, requestURL: 'https://example.com/admin?locale=es', @@ -271,7 +271,7 @@ describe('getAdminContext', () => { }) it('should prefer explicit request overrides over requestURL', async () => { - const result = await getAdminContext({ + const result = await createAdminContext({ configPromise: config, importMap, overrides: { @@ -293,7 +293,7 @@ describe('getAdminContext', () => { }) it('should ignore an invalid requestURL', async () => { - const result = await getAdminContext({ + const result = await createAdminContext({ configPromise: config, importMap, requestURL: 'not a valid URL', @@ -305,8 +305,8 @@ describe('getAdminContext', () => { }) it('should initialize each request directly when no cache is supplied', async () => { - await getAdminContext({ configPromise: config, importMap, serverAdapter }) - await getAdminContext({ configPromise: config, importMap, serverAdapter }) + await createAdminContext({ configPromise: config, importMap, serverAdapter }) + await createAdminContext({ configPromise: config, importMap, serverAdapter }) expect(authenticate).toHaveBeenCalledTimes(2) expect(getAccessResults).toHaveBeenCalledTimes(2) @@ -315,7 +315,7 @@ describe('getAdminContext', () => { it('should use both cache stages when a cache is supplied', async () => { const cache = createExecutingCache() - await getAdminContext({ + await createAdminContext({ cache, configPromise: config, importMap, @@ -329,17 +329,17 @@ describe('getAdminContext', () => { it('requires a key when cache is supplied', async () => { await expect( - getAdminContext({ + createAdminContext({ cache: createExecutingCache(), configPromise: config, importMap, serverAdapter, }), - ).rejects.toThrow('getAdminContext requires a key when cache is provided') + ).rejects.toThrow('createAdminContext requires a key when cache is provided') }) it('should enable cron and forward canSetHeaders to auth strategies', async () => { - await getAdminContext({ + await createAdminContext({ canSetHeaders: true, configPromise: config, importMap, @@ -376,10 +376,10 @@ describe('getAdminContext', () => { }, serverAdapter, } - const first = await getAdminContext(args) + const first = await createAdminContext(args) first.req.context.source = 'mutated' - const second = await getAdminContext(args) + const second = await createAdminContext(args) expect(first.req).not.toBe(second.req) expect(first.req.context).not.toBe(second.req.context) @@ -398,14 +398,14 @@ describe('getAdminContext', () => { serverAdapter, } - const rootLayoutResult = await getAdminContext({ ...args, key: 'RootLayout' }) - await getAdminContext({ ...args, key: 'initPage' }) + const rootLayoutResult = await createAdminContext({ ...args, key: 'RootLayout' }) + await createAdminContext({ ...args, key: 'initPage' }) expect(authenticate).toHaveBeenCalledOnce() expect(applyUserReadAccess).toHaveBeenCalledTimes(2) expect(getAccessResults).toHaveBeenCalledTimes(2) - const repeatedRootLayoutResult = await getAdminContext({ ...args, key: 'RootLayout' }) + const repeatedRootLayoutResult = await createAdminContext({ ...args, key: 'RootLayout' }) expect(authenticate).toHaveBeenCalledOnce() expect(applyUserReadAccess).toHaveBeenCalledTimes(2) @@ -424,7 +424,7 @@ describe('getAdminContext', () => { }, }) - const result = await getAdminContext({ configPromise: config, importMap, serverAdapter }) + const result = await createAdminContext({ configPromise: config, importMap, serverAdapter }) expect(result.req.user).toHaveProperty('role', 'admin') expect(result.user).toEqual({ collection: 'users', id: 'user-id' }) @@ -446,11 +446,11 @@ describe('getAdminContext', () => { serverAdapter, } - const first = await getAdminContext({ + const first = await createAdminContext({ ...args, overrides: { context: { source: 'first' } }, }) - const second = await getAdminContext({ + const second = await createAdminContext({ ...args, overrides: { context: { source: 'second' } }, }) @@ -472,8 +472,8 @@ describe('getAdminContext', () => { serverAdapter, } - const rootLayoutResult = await getAdminContext({ ...args, key: 'RootLayout' }) - const pageResult = await getAdminContext({ ...args, key: 'initPage' }) + const rootLayoutResult = await createAdminContext({ ...args, key: 'RootLayout' }) + const pageResult = await createAdminContext({ ...args, key: 'initPage' }) expect(rootLayoutResult.locale).toMatchObject({ code: 'es' }) expect(pageResult.locale).toMatchObject({ code: 'es' }) @@ -483,7 +483,7 @@ describe('getAdminContext', () => { it('should use anonymous access when user read access fails', async () => { applyUserReadAccess.mockRejectedValueOnce(new Error('read access failed')) - const result = await getAdminContext({ configPromise: config, importMap, serverAdapter }) + const result = await createAdminContext({ configPromise: config, importMap, serverAdapter }) expect(result.req.user).toBeNull() expect(result.user).toBeNull() @@ -491,7 +491,7 @@ describe('getAdminContext', () => { }) it('should preserve an explicit request user override', async () => { - const result = await getAdminContext({ + const result = await createAdminContext({ configPromise: config, importMap, overrides: { @@ -511,7 +511,7 @@ describe('getAdminContext', () => { }) it('should preserve an explicit null user override', async () => { - const result = await getAdminContext({ + const result = await createAdminContext({ configPromise: config, importMap, overrides: { diff --git a/packages/payload/src/admin/getAdminContext.ts b/packages/payload/src/admin/createAdminContext.ts similarity index 84% rename from packages/payload/src/admin/getAdminContext.ts rename to packages/payload/src/admin/createAdminContext.ts index 635a3ea7978..77382894d79 100644 --- a/packages/payload/src/admin/getAdminContext.ts +++ b/packages/payload/src/admin/createAdminContext.ts @@ -6,44 +6,44 @@ import * as qs from 'qs-esm' import type { ImportMap } from '../cli/commands/generateImportMap/generateImportMap.js' import type { SanitizedConfig } from '../config/types.js' import type { PayloadRequest } from '../types/index.js' -import type { CreatePayloadReqArgs } from '../utilities/createPayloadReq.js' +import type { CreatePayloadRequestArgs } from '../utilities/createPayloadRequest.js' import type { ServerAdapter } from './adapters/server.js' -import type { GetAdminContextResult } from './functions/index.js' +import type { CreateAdminContextResult } from './functions/index.js' import { applyUserReadAccess } from '../auth/applyUserReadAccess.js' import { executeAuthStrategies } from '../auth/executeAuthStrategies.js' import { getAccessResults } from '../auth/getAccessResults.js' import { getPayload } from '../index.js' -import { createPayloadReq } from '../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../utilities/createPayloadRequest.js' import { getRequestLanguage } from '../utilities/getRequestLanguage.js' import { parseCookies } from '../utilities/parseCookies.js' import { getRequestLocale } from './getRequestLocale.js' export type PartialAdminContext = { i18n: I18nClient -} & Pick & +} & Pick & Pick /** Framework-provided request-scoped caching hooks used to deduplicate admin context creation. */ export type AdminContextCache = { /** Reuses locale preference resolution across admin contexts. */ getLocale?: ( - resolveLocale: () => Promise>, + resolveLocale: () => Promise>, ...cacheArgs: unknown[] - ) => Promise> + ) => Promise> /** Reuses Payload, i18n, and authentication state within the current request. */ getPartial: ( createPartialContext: () => Promise, ) => Promise /** Reuses a complete admin context for the supplied key and cache arguments. */ getRequest: ( - createContext: () => Promise, + createContext: () => Promise, key: string, ...cacheArgs: unknown[] - ) => Promise + ) => Promise } -export type GetAdminContextArgs = { +export type CreateAdminContextArgs = { /** * Optional framework-owned request-scoped cache. * Framework adapters control its lifetime to prevent request state from leaking between requests. @@ -54,15 +54,15 @@ export type GetAdminContextArgs = { importMap: ImportMap /** Identifies the complete admin context within `cache`; required when a cache is supplied. */ key?: string - overrides?: Omit + overrides?: Omit requestURL?: string serverAdapter: ServerAdapter } /** - * Gets the request context used by framework adapters to render the admin panel. + * Creates the request context used by framework adapters to render the admin panel. */ -export async function getAdminContext({ +export async function createAdminContext({ cache, canSetHeaders, configPromise, @@ -71,9 +71,9 @@ export async function getAdminContext({ overrides, requestURL, serverAdapter, -}: GetAdminContextArgs): Promise { +}: CreateAdminContextArgs): Promise { if (cache && !key) { - throw new Error('getAdminContext requires a key when cache is provided') + throw new Error('createAdminContext requires a key when cache is provided') } const headers = await serverAdapter.getHeaders() @@ -113,7 +113,7 @@ export async function getAdminContext({ ? await cache.getPartial(createPartialContext) : await createPartialContext() - const createContext = async (): Promise => { + const createContext = async (): Promise => { const { i18n, languageCode, payload, responseHeaders, user } = partialContext const { req: reqOverrides, ...optionsOverrides } = overrides || {} const hasOptionsUserOverride = Object.hasOwn(optionsOverrides, 'user') @@ -122,7 +122,7 @@ export async function getAdminContext({ const userOverride = hasOptionsUserOverride ? optionsOverrides.user : reqOverrides?.user const requestDefaults = getRequestDefaults({ requestURL }) - const req = await createPayloadReq({ + const req = await createPayloadRequest({ req: { headers, host: headers.get('host') ?? undefined, @@ -141,7 +141,7 @@ export async function getAdminContext({ req.user = null } - const resolveLocale = async (): Promise> => ({ + const resolveLocale = async (): Promise> => ({ locale: await getRequestLocale({ req }), }) const { locale } = cache?.getLocale diff --git a/packages/payload/src/admin/functions/index.ts b/packages/payload/src/admin/functions/index.ts index 08c07b65a62..cfb813d883e 100644 --- a/packages/payload/src/admin/functions/index.ts +++ b/packages/payload/src/admin/functions/index.ts @@ -16,7 +16,7 @@ import type { PayloadRequest, Sort, Where } from '../../types/index.js' import type { ColumnsFromURL } from '../../utilities/transformColumnPreferences.js' import type { ComponentRenderer } from '../adapters/render.js' -export type GetAdminContextResult = { +export type CreateAdminContextResult = { cookies: Map // TODO: Remove in 4.0. Duplicative, already available in req.headers headers: Headers @@ -32,7 +32,7 @@ export type GetAdminContextResult = { export type DefaultServerFunctionArgs = { importMap: ImportMap renderComponent?: ComponentRenderer -} & Pick +} & Pick export type ServerFunctionArgs = { args: Record diff --git a/packages/payload/src/admin/types.ts b/packages/payload/src/admin/types.ts index a0e329bb977..88f6ad2ccb2 100644 --- a/packages/payload/src/admin/types.ts +++ b/packages/payload/src/admin/types.ts @@ -591,7 +591,7 @@ export type DocumentSlots = { export type { BuildTableStateArgs, DefaultServerFunctionArgs, - GetAdminContextResult, + CreateAdminContextResult, ListQuery, ServerFunction, ServerFunctionArgs, diff --git a/packages/payload/src/admin/views/dashboard.ts b/packages/payload/src/admin/views/dashboard.ts index fb46daac659..19908574e17 100644 --- a/packages/payload/src/admin/views/dashboard.ts +++ b/packages/payload/src/admin/views/dashboard.ts @@ -1,4 +1,4 @@ -import type { GetAdminContextResult, TypedWidget, WidgetSlug } from '../../index.js' +import type { CreateAdminContextResult, TypedWidget, WidgetSlug } from '../../index.js' import type { ComponentRenderer } from '../adapters/render.js' export enum EntityType { @@ -33,4 +33,4 @@ export type WidgetServerProps>] extends [never] ? string : WidgetSlugFromWidget<{ data?: unknown } & Exclude> -} & Pick +} & Pick diff --git a/packages/payload/src/auth/operations/local/auth.ts b/packages/payload/src/auth/operations/local/auth.ts index f5a5480c72a..9fb99b7cb6c 100644 --- a/packages/payload/src/auth/operations/local/auth.ts +++ b/packages/payload/src/auth/operations/local/auth.ts @@ -1,7 +1,7 @@ import type { Payload } from '../../../index.js' import type { AuthArgs, AuthResult } from '../auth.js' -import { createPayloadReq } from '../../../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../../../utilities/createPayloadRequest.js' import { auth as authOperation } from '../auth.js' export const authLocal = async (payload: Payload, options: AuthArgs): Promise => { @@ -10,6 +10,6 @@ export const authLocal = async (payload: Payload, options: AuthArgs): Promise = { @@ -46,6 +46,6 @@ export async function forgotPasswordLocal( disableEmail, expiration, overrideAccess, - req: await createPayloadReq({ ...options, payload }), + req: await createPayloadRequest({ ...options, payload }), }) as Promise } diff --git a/packages/payload/src/auth/operations/local/login.ts b/packages/payload/src/auth/operations/local/login.ts index 192bbb22010..0d83db7b192 100644 --- a/packages/payload/src/auth/operations/local/login.ts +++ b/packages/payload/src/auth/operations/local/login.ts @@ -9,7 +9,7 @@ import type { SharedLocalAPIOptions } from '../../../types/operations.js' import type { LoginResult } from '../login.js' import { APIError } from '../../../errors/index.js' -import { createPayloadReq } from '../../../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../../../utilities/createPayloadRequest.js' import { loginOperation } from '../login.js' export type Options = { @@ -49,7 +49,7 @@ export async function loginLocal( data, depth, overrideAccess, - req: await createPayloadReq({ ...options, payload }), + req: await createPayloadRequest({ ...options, payload }), showHiddenFields, } diff --git a/packages/payload/src/auth/operations/local/resetPassword.ts b/packages/payload/src/auth/operations/local/resetPassword.ts index 75bc4de9b95..84470505cc5 100644 --- a/packages/payload/src/auth/operations/local/resetPassword.ts +++ b/packages/payload/src/auth/operations/local/resetPassword.ts @@ -3,7 +3,7 @@ import type { PayloadRequest } from '../../../types/index.js' import type { Result } from '../resetPassword.js' import { APIError } from '../../../errors/index.js' -import { createPayloadReq } from '../../../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../../../utilities/createPayloadRequest.js' import { resetPasswordOperation } from '../resetPassword.js' export type Options = { @@ -37,7 +37,7 @@ export async function resetPasswordLocal( collection, data, overrideAccess, - req: await createPayloadReq({ ...options, payload }), + req: await createPayloadRequest({ ...options, payload }), }) if (collection.config.auth.removeTokenFromResponses) { diff --git a/packages/payload/src/auth/operations/local/unlock.ts b/packages/payload/src/auth/operations/local/unlock.ts index 3bc0d517fa2..ddb3dd719c2 100644 --- a/packages/payload/src/auth/operations/local/unlock.ts +++ b/packages/payload/src/auth/operations/local/unlock.ts @@ -8,7 +8,7 @@ import type { PayloadRequest } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' import { APIError } from '../../../errors/index.js' -import { createPayloadReq } from '../../../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../../../utilities/createPayloadRequest.js' import { unlockOperation } from '../unlock.js' export type Options = { @@ -36,6 +36,6 @@ export async function unlockLocal( collection, data, overrideAccess, - req: await createPayloadReq({ ...options, payload }), + req: await createPayloadRequest({ ...options, payload }), }) } diff --git a/packages/payload/src/auth/operations/local/verifyEmail.ts b/packages/payload/src/auth/operations/local/verifyEmail.ts index 0d9794d49aa..f4906f3fe74 100644 --- a/packages/payload/src/auth/operations/local/verifyEmail.ts +++ b/packages/payload/src/auth/operations/local/verifyEmail.ts @@ -2,7 +2,7 @@ import type { AuthCollectionSlug, Payload, RequestContext } from '../../../index import type { PayloadRequest } from '../../../types/index.js' import { APIError } from '../../../errors/index.js' -import { createPayloadReq } from '../../../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../../../utilities/createPayloadRequest.js' import { verifyEmailOperation } from '../verifyEmail.js' export type Options = { @@ -28,7 +28,7 @@ export async function verifyEmailLocal( return verifyEmailOperation({ collection, - req: await createPayloadReq({ ...options, payload }), + req: await createPayloadRequest({ ...options, payload }), token, }) } diff --git a/packages/payload/src/auth/serverFunctions/login.ts b/packages/payload/src/auth/serverFunctions/login.ts index ae238247482..d8591709345 100644 --- a/packages/payload/src/auth/serverFunctions/login.ts +++ b/packages/payload/src/auth/serverFunctions/login.ts @@ -5,7 +5,7 @@ import type { MaybePromise } from '../../types/index.js' import type { LoginResult } from '../operations/login.js' import { getPayload } from '../../index.js' -import { createPayloadReq } from '../../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../../utilities/createPayloadRequest.js' import { loginWithCookie } from '../loginWithCookie.js' import { applyAuthCookie } from './cookies.js' @@ -80,7 +80,7 @@ export async function login({ collection: collectionConfig, data: loginData as Parameters[0]['data'], overrideAccess: true, - req: await createPayloadReq({ payload }), + req: await createPayloadRequest({ payload }), returnCookieAsObject: true, }) diff --git a/packages/payload/src/auth/serverFunctions/logout.ts b/packages/payload/src/auth/serverFunctions/logout.ts index 8948d3b7a58..0e1af45e294 100644 --- a/packages/payload/src/auth/serverFunctions/logout.ts +++ b/packages/payload/src/auth/serverFunctions/logout.ts @@ -3,7 +3,7 @@ import type { SanitizedConfig } from '../../config/types.js' import type { MaybePromise } from '../../types/index.js' import { getPayload } from '../../index.js' -import { createPayloadReq } from '../../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../../utilities/createPayloadRequest.js' import { logoutOperation } from '../operations/logout.js' import { clearAuthCookie } from './cookies.js' @@ -31,7 +31,7 @@ export async function logout({ } const { user } = authResult - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) const collection = payload.collections[user.collection] if (!collection) { diff --git a/packages/payload/src/auth/serverFunctions/refresh.ts b/packages/payload/src/auth/serverFunctions/refresh.ts index a7d3746898e..a7ab5dc87ca 100644 --- a/packages/payload/src/auth/serverFunctions/refresh.ts +++ b/packages/payload/src/auth/serverFunctions/refresh.ts @@ -4,7 +4,7 @@ import type { CollectionSlug } from '../../index.js' import type { MaybePromise } from '../../types/index.js' import { getPayload } from '../../index.js' -import { createPayloadReq } from '../../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../../utilities/createPayloadRequest.js' import { refreshOperation } from '../operations/refresh.js' import { getExistingAuthToken, setAuthCookie } from './cookies.js' @@ -46,7 +46,7 @@ export async function refresh({ throw new Error(`No auth config found for collection: ${collection}`) } - const req = await createPayloadReq({ payload, user: result.user }) + const req = await createPayloadRequest({ payload, user: result.user }) const refreshResult = await refreshOperation({ collection: collectionConfig, diff --git a/packages/payload/src/cli/commands/collections/createDocuments.ts b/packages/payload/src/cli/commands/collections/createDocuments.ts index c61f318390d..0ae784cfebd 100644 --- a/packages/payload/src/cli/commands/collections/createDocuments.ts +++ b/packages/payload/src/cli/commands/collections/createDocuments.ts @@ -1,7 +1,7 @@ import type { EntityInputSchema } from '../../../utilities/entityInputSchema/types.js' import { createDocumentsLocalInputSchema } from '../../../collections/operations/inputSchemas.js' -import { createPayloadReq } from '../../../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../../../utilities/createPayloadRequest.js' import { hasDraftValidationEnabled } from '../../../utilities/getVersionsConfig.js' import { defineCLICommand } from '../../defineCLICommand.js' import { @@ -40,7 +40,7 @@ export const createCreateDocumentsCommand = defineCLICommand({ const docs: Array<{ doc: unknown; index: number } | { id: number | string; index: number }> = [] const errors: Array<{ index: number; issues?: unknown[]; message: string }> = [] let schema: EntityInputSchema | undefined - const req = await createPayloadReq({ payload }) + const req = await createPayloadRequest({ payload }) const shouldUsePartialSchema = args.draft === true && collectionConfig !== undefined && diff --git a/packages/payload/src/cli/commands/collections/duplicateDocument.ts b/packages/payload/src/cli/commands/collections/duplicateDocument.ts index 39650ad3219..6881a659068 100644 --- a/packages/payload/src/cli/commands/collections/duplicateDocument.ts +++ b/packages/payload/src/cli/commands/collections/duplicateDocument.ts @@ -1,5 +1,5 @@ import { duplicateDocumentLocalInputSchema } from '../../../collections/operations/inputSchemas.js' -import { createPayloadReq } from '../../../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../../../utilities/createPayloadRequest.js' import { parseDocumentID } from '../../../utilities/parseDocumentID.js' import { defineCLICommand } from '../../defineCLICommand.js' import { @@ -33,7 +33,7 @@ export const createDuplicateDocumentCommand = defineCLICommand({ const inputData = args.data ? stripCollectionVirtualFields({ collection, data: args.data, payload }) : undefined - const req = await createPayloadReq({ payload }) + const req = await createPayloadRequest({ payload }) let result try { diff --git a/packages/payload/src/cli/commands/collections/getCollectionSchema.ts b/packages/payload/src/cli/commands/collections/getCollectionSchema.ts index 8da4f0b45fd..a306c74e0c2 100644 --- a/packages/payload/src/cli/commands/collections/getCollectionSchema.ts +++ b/packages/payload/src/cli/commands/collections/getCollectionSchema.ts @@ -1,5 +1,5 @@ import { getCollectionSchemaInputSchema } from '../../../collections/operations/inputSchemas.js' -import { createPayloadReq } from '../../../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../../../utilities/createPayloadRequest.js' import { getCollectionInputSchema } from '../../../utilities/entityInputSchema/getEntityInputSchema.js' import { defineCLICommand } from '../../defineCLICommand.js' import { printJSON } from '../data/utilities.js' @@ -9,7 +9,7 @@ export const createGetCollectionSchemaCommand = defineCLICommand({ handler: async ({ args, getPayload, isJSON }) => { const payload = await getPayload() const slug = args.slug - const req = await createPayloadReq({ payload }) + const req = await createPayloadRequest({ payload }) const schema = getCollectionInputSchema({ collectionSlug: slug, req }) if (!schema) { diff --git a/packages/payload/src/cli/commands/collections/updateDocument.ts b/packages/payload/src/cli/commands/collections/updateDocument.ts index ac4fbfeb032..155e18bc3d6 100644 --- a/packages/payload/src/cli/commands/collections/updateDocument.ts +++ b/packages/payload/src/cli/commands/collections/updateDocument.ts @@ -1,5 +1,5 @@ import { updateDocumentLocalInputSchema } from '../../../collections/operations/inputSchemas.js' -import { createPayloadReq } from '../../../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../../../utilities/createPayloadRequest.js' import { parseDocumentID } from '../../../utilities/parseDocumentID.js' import { defineCLICommand } from '../../defineCLICommand.js' import { @@ -37,7 +37,7 @@ export const createUpdateDocumentCommand = defineCLICommand({ const payload = await getPayload() const collection = args.slug const inputData = stripCollectionVirtualFields({ collection, data: args.data, payload }) - const req = await createPayloadReq({ payload }) + const req = await createPayloadRequest({ payload }) try { validateCollectionData({ slug: collection, diff --git a/packages/payload/src/cli/commands/globals/getGlobalSchema.ts b/packages/payload/src/cli/commands/globals/getGlobalSchema.ts index 93a794c3dc7..e16290c1b74 100644 --- a/packages/payload/src/cli/commands/globals/getGlobalSchema.ts +++ b/packages/payload/src/cli/commands/globals/getGlobalSchema.ts @@ -1,5 +1,5 @@ import { getGlobalSchemaInputSchema } from '../../../globals/operations/inputSchemas.js' -import { createPayloadReq } from '../../../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../../../utilities/createPayloadRequest.js' import { getGlobalInputSchema } from '../../../utilities/entityInputSchema/getEntityInputSchema.js' import { defineCLICommand } from '../../defineCLICommand.js' import { printJSON } from '../data/utilities.js' @@ -9,7 +9,7 @@ export const createGetGlobalSchemaCommand = defineCLICommand({ handler: async ({ args, getPayload, isJSON }) => { const payload = await getPayload() const slug = args.slug - const req = await createPayloadReq({ payload }) + const req = await createPayloadRequest({ payload }) const schema = getGlobalInputSchema({ globalSlug: slug, req }) if (!schema) { diff --git a/packages/payload/src/cli/commands/globals/updateGlobal.ts b/packages/payload/src/cli/commands/globals/updateGlobal.ts index fd04c5c08b4..c2a712a9c30 100644 --- a/packages/payload/src/cli/commands/globals/updateGlobal.ts +++ b/packages/payload/src/cli/commands/globals/updateGlobal.ts @@ -1,5 +1,5 @@ import { updateGlobalLocalInputSchema } from '../../../globals/operations/inputSchemas.js' -import { createPayloadReq } from '../../../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../../../utilities/createPayloadRequest.js' import { defineCLICommand } from '../../defineCLICommand.js' import { parseBoolean, parseFallbackLocale, parseJSON } from '../data/input.js' import { @@ -23,7 +23,7 @@ export const createUpdateGlobalCommand = defineCLICommand({ const payload = await getPayload() const slug = args.slug const inputData = stripGlobalVirtualFields({ slug, data: args.data, payload }) - const req = await createPayloadReq({ payload }) + const req = await createPayloadRequest({ payload }) let result try { diff --git a/packages/payload/src/collections/operations/local/count.ts b/packages/payload/src/collections/operations/local/count.ts index 2fb0cc41893..6b7bc1095fb 100644 --- a/packages/payload/src/collections/operations/local/count.ts +++ b/packages/payload/src/collections/operations/local/count.ts @@ -1,10 +1,10 @@ import type { CollectionSlug, Payload, RequestContext, TypedLocale, User } from '../../../index.js' import type { PayloadRequest, Where } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' -import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' +import type { CreatePayloadRequestArgs } from '../../../utilities/createPayloadRequest.js' import { APIError } from '../../../errors/index.js' -import { createPayloadReq } from '../../../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../../../utilities/createPayloadRequest.js' import { countOperation } from '../count.js' export type CountOptions = { @@ -75,7 +75,10 @@ export async function countLocal( collection, disableErrors, overrideAccess, - req: await createPayloadReq({ ...(options as Omit), payload }), + req: await createPayloadRequest({ + ...(options as Omit), + payload, + }), trash, where, }) diff --git a/packages/payload/src/collections/operations/local/countVersions.ts b/packages/payload/src/collections/operations/local/countVersions.ts index b9fff8d814c..a48ffb18698 100644 --- a/packages/payload/src/collections/operations/local/countVersions.ts +++ b/packages/payload/src/collections/operations/local/countVersions.ts @@ -1,10 +1,10 @@ import type { CollectionSlug, Payload, RequestContext, TypedLocale, User } from '../../../index.js' import type { PayloadRequest, Where } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' -import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' +import type { CreatePayloadRequestArgs } from '../../../utilities/createPayloadRequest.js' import { APIError } from '../../../errors/index.js' -import { createPayloadReq } from '../../../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../../../utilities/createPayloadRequest.js' import { countVersionsOperation } from '../countVersions.js' export type CountVersionsOptions = { @@ -60,7 +60,10 @@ export async function countVersionsLocal( collection, disableErrors, overrideAccess, - req: await createPayloadReq({ ...(options as Omit), payload }), + req: await createPayloadRequest({ + ...(options as Omit), + payload, + }), where, }) } diff --git a/packages/payload/src/collections/operations/local/create.ts b/packages/payload/src/collections/operations/local/create.ts index 1806d826123..1f4f914eb39 100644 --- a/packages/payload/src/collections/operations/local/create.ts +++ b/packages/payload/src/collections/operations/local/create.ts @@ -6,7 +6,7 @@ import type { } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' import type { File } from '../../../uploads/types.js' -import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' +import type { CreatePayloadRequestArgs } from '../../../utilities/createPayloadRequest.js' import type { CollectionsWithoutDrafts, DataFromCollectionSlug, @@ -27,7 +27,7 @@ import { type User, } from '../../../index.js' import { getFileByPath } from '../../../uploads/getFileByPath.js' -import { createPayloadReq } from '../../../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../../../utilities/createPayloadRequest.js' import { createOperation } from '../create.js' type BaseOptions = { @@ -211,8 +211,8 @@ export async function createLocal< ) } - const req = await createPayloadReq({ - ...(options as Omit), + const req = await createPayloadRequest({ + ...(options as Omit), payload, }) diff --git a/packages/payload/src/collections/operations/local/delete.ts b/packages/payload/src/collections/operations/local/delete.ts index dfe6adaa136..d08e1fe6f34 100644 --- a/packages/payload/src/collections/operations/local/delete.ts +++ b/packages/payload/src/collections/operations/local/delete.ts @@ -14,11 +14,11 @@ import type { Where, } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' -import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' +import type { CreatePayloadRequestArgs } from '../../../utilities/createPayloadRequest.js' import type { BulkOperationResult, SelectFromCollectionSlug } from '../../config/types.js' import { APIError } from '../../../errors/index.js' -import { createPayloadReq } from '../../../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../../../utilities/createPayloadRequest.js' import { deleteOperation } from '../delete.js' import { deleteByIDOperation } from '../deleteByID.js' @@ -176,7 +176,10 @@ async function deleteLocal< overrideAccess, overrideLock, populate, - req: await createPayloadReq({ ...(options as Omit), payload }), + req: await createPayloadRequest({ + ...(options as Omit), + payload, + }), select, showHiddenFields, trash, diff --git a/packages/payload/src/collections/operations/local/duplicate.ts b/packages/payload/src/collections/operations/local/duplicate.ts index 5c71134fceb..940088f3bfb 100644 --- a/packages/payload/src/collections/operations/local/duplicate.ts +++ b/packages/payload/src/collections/operations/local/duplicate.ts @@ -9,7 +9,7 @@ import type { TransformCollectionWithSelect, } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' -import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' +import type { CreatePayloadRequestArgs } from '../../../utilities/createPayloadRequest.js' import type { DraftFlagFromCollectionSlug, RequiredDataFromCollectionSlug, @@ -17,7 +17,7 @@ import type { } from '../../config/types.js' import { APIError } from '../../../errors/index.js' -import { createPayloadReq } from '../../../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../../../utilities/createPayloadRequest.js' import { duplicateOperation } from '../duplicate.js' type BaseOptions = { @@ -125,8 +125,8 @@ export async function duplicateLocal< ) } - const req = await createPayloadReq({ - ...(options as Omit), + const req = await createPayloadRequest({ + ...(options as Omit), payload, }) diff --git a/packages/payload/src/collections/operations/local/find.ts b/packages/payload/src/collections/operations/local/find.ts index 75cdad49a1f..bb40ab4b2e5 100644 --- a/packages/payload/src/collections/operations/local/find.ts +++ b/packages/payload/src/collections/operations/local/find.ts @@ -19,11 +19,11 @@ import type { Where, } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' -import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' +import type { CreatePayloadRequestArgs } from '../../../utilities/createPayloadRequest.js' import type { DraftFlagFromCollectionSlug, SelectFromCollectionSlug } from '../../config/types.js' import { APIError } from '../../../errors/index.js' -import { createPayloadReq } from '../../../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../../../utilities/createPayloadRequest.js' import { findOperation } from '../find.js' type BaseFindOptions = { @@ -239,7 +239,10 @@ export async function findLocal< page, pagination, populate, - req: await createPayloadReq({ ...(options as Omit), payload }), + req: await createPayloadRequest({ + ...(options as Omit), + payload, + }), select, showHiddenFields, sort, diff --git a/packages/payload/src/collections/operations/local/findByID.ts b/packages/payload/src/collections/operations/local/findByID.ts index 16cc0d38606..93e894d27fb 100644 --- a/packages/payload/src/collections/operations/local/findByID.ts +++ b/packages/payload/src/collections/operations/local/findByID.ts @@ -16,11 +16,11 @@ import type { TransformCollectionWithSelect, } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' -import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' +import type { CreatePayloadRequestArgs } from '../../../utilities/createPayloadRequest.js' import type { DraftFlagFromCollectionSlug, SelectFromCollectionSlug } from '../../config/types.js' import { APIError } from '../../../errors/index.js' -import { createPayloadReq } from '../../../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../../../utilities/createPayloadRequest.js' import { type FindByIDArgs, findByIDOperation } from '../findByID.js' type BaseFindByIDOptions< @@ -163,7 +163,10 @@ export async function findByIDLocal< joins, overrideAccess, populate, - req: await createPayloadReq({ ...(options as Omit), payload }), + req: await createPayloadRequest({ + ...(options as Omit), + payload, + }), select, showHiddenFields, trash, diff --git a/packages/payload/src/collections/operations/local/findDistinct.ts b/packages/payload/src/collections/operations/local/findDistinct.ts index 359ea1cd34d..da9fd39e850 100644 --- a/packages/payload/src/collections/operations/local/findDistinct.ts +++ b/packages/payload/src/collections/operations/local/findDistinct.ts @@ -12,9 +12,9 @@ import type { Where, } from '../../../index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' -import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' +import type { CreatePayloadRequestArgs } from '../../../utilities/createPayloadRequest.js' -import { APIError, createPayloadReq } from '../../../index.js' +import { APIError, createPayloadRequest } from '../../../index.js' import { findDistinctOperation } from '../findDistinct.js' export type Options< @@ -135,7 +135,10 @@ export async function findDistinct< overrideAccess, page, populate, - req: await createPayloadReq({ ...(options as Omit), payload }), + req: await createPayloadRequest({ + ...(options as Omit), + payload, + }), showHiddenFields, sort, trash, diff --git a/packages/payload/src/collections/operations/local/findVersionByID.ts b/packages/payload/src/collections/operations/local/findVersionByID.ts index b2362621921..06de978febd 100644 --- a/packages/payload/src/collections/operations/local/findVersionByID.ts +++ b/packages/payload/src/collections/operations/local/findVersionByID.ts @@ -8,12 +8,12 @@ import type { } from '../../../index.js' import type { PayloadRequest, PopulateType, SelectType } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' -import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' +import type { CreatePayloadRequestArgs } from '../../../utilities/createPayloadRequest.js' import type { TypeWithVersion } from '../../../versions/types.js' import type { DataFromCollectionSlug, DraftFlagFromCollectionSlug } from '../../config/types.js' import { APIError } from '../../../errors/index.js' -import { createPayloadReq } from '../../../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../../../utilities/createPayloadRequest.js' import { findVersionByIDOperation } from '../findVersionByID.js' type BaseOptions = { @@ -115,7 +115,10 @@ export async function findVersionByIDLocal( disableErrors, overrideAccess, populate, - req: await createPayloadReq({ ...(options as Omit), payload }), + req: await createPayloadRequest({ + ...(options as Omit), + payload, + }), select, showHiddenFields, trash, diff --git a/packages/payload/src/collections/operations/local/findVersions.ts b/packages/payload/src/collections/operations/local/findVersions.ts index 7c8e300fd34..a3e9fc200e4 100644 --- a/packages/payload/src/collections/operations/local/findVersions.ts +++ b/packages/payload/src/collections/operations/local/findVersions.ts @@ -9,12 +9,12 @@ import type { } from '../../../index.js' import type { PayloadRequest, PopulateType, SelectType, Sort, Where } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' -import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' +import type { CreatePayloadRequestArgs } from '../../../utilities/createPayloadRequest.js' import type { TypeWithVersion } from '../../../versions/types.js' import type { DataFromCollectionSlug, DraftFlagFromCollectionSlug } from '../../config/types.js' import { APIError } from '../../../errors/index.js' -import { createPayloadReq } from '../../../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../../../utilities/createPayloadRequest.js' import { findVersionsOperation } from '../findVersions.js' type BaseOptions = { @@ -135,7 +135,10 @@ export async function findVersionsLocal( page, pagination, populate, - req: await createPayloadReq({ ...(options as Omit), payload }), + req: await createPayloadRequest({ + ...(options as Omit), + payload, + }), select, showHiddenFields, sort, diff --git a/packages/payload/src/collections/operations/local/restoreVersion.ts b/packages/payload/src/collections/operations/local/restoreVersion.ts index e82c8e2a7cf..8d9d3432cac 100644 --- a/packages/payload/src/collections/operations/local/restoreVersion.ts +++ b/packages/payload/src/collections/operations/local/restoreVersion.ts @@ -8,11 +8,11 @@ import type { } from '../../../index.js' import type { PayloadRequest, PopulateType, SelectType } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' -import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' +import type { CreatePayloadRequestArgs } from '../../../utilities/createPayloadRequest.js' import type { DataFromCollectionSlug, DraftFlagFromCollectionSlug } from '../../config/types.js' import { APIError } from '../../../errors/index.js' -import { createPayloadReq } from '../../../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../../../utilities/createPayloadRequest.js' import { restoreVersionOperation } from '../restoreVersion.js' type BaseOptions = { @@ -98,7 +98,10 @@ export async function restoreVersionLocal( overrideAccess, payload, populate, - req: await createPayloadReq({ ...(options as Omit), payload }), + req: await createPayloadRequest({ + ...(options as Omit), + payload, + }), select, showHiddenFields, } diff --git a/packages/payload/src/collections/operations/local/update.ts b/packages/payload/src/collections/operations/local/update.ts index a170e7c4185..856d178abc3 100644 --- a/packages/payload/src/collections/operations/local/update.ts +++ b/packages/payload/src/collections/operations/local/update.ts @@ -18,7 +18,7 @@ import type { } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' import type { File } from '../../../uploads/types.js' -import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' +import type { CreatePayloadRequestArgs } from '../../../utilities/createPayloadRequest.js' import type { BulkOperationResult, DraftFlagFromCollectionSlug, @@ -28,7 +28,7 @@ import type { import { APIError } from '../../../errors/index.js' import { getFileByPath } from '../../../uploads/getFileByPath.js' -import { createPayloadReq } from '../../../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../../../utilities/createPayloadRequest.js' import { updateOperation } from '../update.js' import { updateByIDOperation } from '../updateByID.js' @@ -245,8 +245,8 @@ async function updateLocal< ) } - const req = await createPayloadReq({ - ...(options as Omit), + const req = await createPayloadRequest({ + ...(options as Omit), payload, }) req.file = file ?? (await getFileByPath(filePath!)) diff --git a/packages/payload/src/database/migrations/migrate.ts b/packages/payload/src/database/migrations/migrate.ts index b8f7bcc0a97..f72ee680fb4 100644 --- a/packages/payload/src/database/migrations/migrate.ts +++ b/packages/payload/src/database/migrations/migrate.ts @@ -1,7 +1,7 @@ import type { BaseDatabaseAdapter } from '../types.js' import { commitTransaction } from '../../utilities/commitTransaction.js' -import { createPayloadReq } from '../../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../../utilities/createPayloadRequest.js' import { initTransaction } from '../../utilities/initTransaction.js' import { killTransaction } from '../../utilities/killTransaction.js' import { getMigrations } from './getMigrations.js' @@ -30,7 +30,7 @@ export const migrate: BaseDatabaseAdapter['migrate'] = async function migrate( } const start = Date.now() - const req = await createPayloadReq({ payload }) + const req = await createPayloadRequest({ payload }) payload.logger.info({ msg: `Migrating: ${migration.name}` }) diff --git a/packages/payload/src/database/migrations/migrateDown.ts b/packages/payload/src/database/migrations/migrateDown.ts index 6fe242d8bc7..6918a21370f 100644 --- a/packages/payload/src/database/migrations/migrateDown.ts +++ b/packages/payload/src/database/migrations/migrateDown.ts @@ -1,7 +1,7 @@ import type { BaseDatabaseAdapter, MigrationResult } from '../types.js' import { commitTransaction } from '../../utilities/commitTransaction.js' -import { createPayloadReq } from '../../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../../utilities/createPayloadRequest.js' import { initTransaction } from '../../utilities/initTransaction.js' import { killTransaction } from '../../utilities/killTransaction.js' import { getMigrations } from './getMigrations.js' @@ -34,7 +34,7 @@ export async function migrateDown(this: BaseDatabaseAdapter): Promise = { @@ -60,7 +60,10 @@ export async function countGlobalVersionsLocal( disableErrors, global, overrideAccess, - req: await createPayloadReq({ ...(options as Omit), payload }), + req: await createPayloadRequest({ + ...(options as Omit), + payload, + }), where, }) } diff --git a/packages/payload/src/globals/operations/local/findOne.ts b/packages/payload/src/globals/operations/local/findOne.ts index 866a5dd698f..93da5c55e1b 100644 --- a/packages/payload/src/globals/operations/local/findOne.ts +++ b/packages/payload/src/globals/operations/local/findOne.ts @@ -14,11 +14,11 @@ import type { TransformGlobalWithSelect, } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' -import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' +import type { CreatePayloadRequestArgs } from '../../../utilities/createPayloadRequest.js' import type { DraftFlagFromGlobalSlug, SelectFromGlobalSlug } from '../../config/types.js' import { APIError } from '../../../errors/index.js' -import { createPayloadReq } from '../../../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../../../utilities/createPayloadRequest.js' import { findOneOperation, type GlobalFindOneArgs } from '../findOne.js' type BaseFindOneOptions = { @@ -128,7 +128,10 @@ export async function findOneGlobalLocal< includeLockStatus, overrideAccess, populate, - req: await createPayloadReq({ ...(options as Omit), payload }), + req: await createPayloadRequest({ + ...(options as Omit), + payload, + }), select, showHiddenFields, }) diff --git a/packages/payload/src/globals/operations/local/findVersionByID.ts b/packages/payload/src/globals/operations/local/findVersionByID.ts index 4f79f342bbc..6376da8f942 100644 --- a/packages/payload/src/globals/operations/local/findVersionByID.ts +++ b/packages/payload/src/globals/operations/local/findVersionByID.ts @@ -8,12 +8,12 @@ import type { } from '../../../index.js' import type { PayloadRequest, PopulateType, SelectType } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' -import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' +import type { CreatePayloadRequestArgs } from '../../../utilities/createPayloadRequest.js' import type { TypeWithVersion } from '../../../versions/types.js' import type { DataFromGlobalSlug } from '../../config/types.js' import { APIError } from '../../../errors/index.js' -import { createPayloadReq } from '../../../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../../../utilities/createPayloadRequest.js' import { findVersionByIDOperation } from '../findVersionByID.js' export type Options = { @@ -99,7 +99,10 @@ export async function findGlobalVersionByIDLocal( globalConfig, overrideAccess, populate, - req: await createPayloadReq({ ...(options as Omit), payload }), + req: await createPayloadRequest({ + ...(options as Omit), + payload, + }), select, showHiddenFields, }) diff --git a/packages/payload/src/globals/operations/local/findVersions.ts b/packages/payload/src/globals/operations/local/findVersions.ts index 2f3ec80d66e..bb73edef263 100644 --- a/packages/payload/src/globals/operations/local/findVersions.ts +++ b/packages/payload/src/globals/operations/local/findVersions.ts @@ -9,12 +9,12 @@ import type { } from '../../../index.js' import type { PayloadRequest, PopulateType, SelectType, Sort, Where } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' -import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' +import type { CreatePayloadRequestArgs } from '../../../utilities/createPayloadRequest.js' import type { TypeWithVersion } from '../../../versions/types.js' import type { DataFromGlobalSlug } from '../../config/types.js' import { APIError } from '../../../errors/index.js' -import { createPayloadReq } from '../../../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../../../utilities/createPayloadRequest.js' import { findVersionsOperation } from '../findVersions.js' export type Options = { @@ -120,7 +120,10 @@ export async function findGlobalVersionsLocal( page, pagination, populate, - req: await createPayloadReq({ ...(options as Omit), payload }), + req: await createPayloadRequest({ + ...(options as Omit), + payload, + }), select, showHiddenFields, sort, diff --git a/packages/payload/src/globals/operations/local/restoreVersion.ts b/packages/payload/src/globals/operations/local/restoreVersion.ts index 73ce2f5b4a0..d841e470f00 100644 --- a/packages/payload/src/globals/operations/local/restoreVersion.ts +++ b/packages/payload/src/globals/operations/local/restoreVersion.ts @@ -1,11 +1,11 @@ import type { GlobalSlug, Payload, RequestContext, TypedLocale, User } from '../../../index.js' import type { PayloadRequest, PopulateType } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' -import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' +import type { CreatePayloadRequestArgs } from '../../../utilities/createPayloadRequest.js' import type { DataFromGlobalSlug } from '../../config/types.js' import { APIError } from '../../../errors/index.js' -import { createPayloadReq } from '../../../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../../../utilities/createPayloadRequest.js' import { restoreVersionOperation } from '../restoreVersion.js' export type Options = { @@ -81,7 +81,10 @@ export async function restoreGlobalVersionLocal( globalConfig, overrideAccess, populate, - req: await createPayloadReq({ ...(options as Omit), payload }), + req: await createPayloadRequest({ + ...(options as Omit), + payload, + }), showHiddenFields, }) } diff --git a/packages/payload/src/globals/operations/local/update.ts b/packages/payload/src/globals/operations/local/update.ts index 15bf7b33de4..121b62a1187 100644 --- a/packages/payload/src/globals/operations/local/update.ts +++ b/packages/payload/src/globals/operations/local/update.ts @@ -7,7 +7,7 @@ import type { TransformGlobalWithSelect, } from '../../../types/index.js' import type { SharedLocalAPIOptions } from '../../../types/operations.js' -import type { CreatePayloadReqArgs } from '../../../utilities/createPayloadReq.js' +import type { CreatePayloadRequestArgs } from '../../../utilities/createPayloadRequest.js' import type { DataFromGlobalSlug, DraftFlagFromGlobalSlug, @@ -24,7 +24,7 @@ import { type TypedLocale, type User, } from '../../../index.js' -import { createPayloadReq } from '../../../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../../../utilities/createPayloadRequest.js' import { updateOperation } from '../update.js' type BaseOptions = { @@ -137,7 +137,10 @@ export async function updateGlobalLocal< overrideLock, populate, publishAllLocales, - req: await createPayloadReq({ ...(options as Omit), payload }), + req: await createPayloadRequest({ + ...(options as Omit), + payload, + }), select, showHiddenFields, unpublishAllLocales, diff --git a/packages/payload/src/index.ts b/packages/payload/src/index.ts index 025a4e67832..23b19bdbc6f 100644 --- a/packages/payload/src/index.ts +++ b/packages/payload/src/index.ts @@ -2020,8 +2020,11 @@ export { withNullableJSONSchemaType, } from './utilities/configToJSONSchema.js' export { createArrayFromCommaDelineated } from './utilities/createArrayFromCommaDelineated.js' -export { createPayloadReq, type CreatePayloadReqArgs } from './utilities/createPayloadReq.js' -export { createPayloadReqFromWebRequest } from './utilities/createPayloadReqFromWebRequest.js' +export { + createPayloadRequest, + type CreatePayloadRequestArgs, +} from './utilities/createPayloadRequest.js' +export { createPayloadRequestFromWebRequest } from './utilities/createPayloadRequestFromWebRequest.js' export { deepCopyObject, deepCopyObjectComplex, diff --git a/packages/payload/src/kv/adapters/DatabaseKVAdapter.ts b/packages/payload/src/kv/adapters/DatabaseKVAdapter.ts index 4841235e869..b877bfb79eb 100644 --- a/packages/payload/src/kv/adapters/DatabaseKVAdapter.ts +++ b/packages/payload/src/kv/adapters/DatabaseKVAdapter.ts @@ -2,7 +2,7 @@ import type { CollectionConfig } from '../../index.js' import type { Payload, PayloadRequest } from '../../types/index.js' import type { KVAdapter, KVAdapterResult, KVStoreValue } from '../index.js' -/** Mocked `req`, we don't need to use transactions, neither we want `createPayloadReq` overhead. */ +/** Mocked `req`, we don't need to use transactions, neither we want `createPayloadRequest` overhead. */ const req = {} as PayloadRequest export class DatabaseKVAdapter implements KVAdapter { diff --git a/packages/payload/src/query-presets/preventLockout.ts b/packages/payload/src/query-presets/preventLockout.ts index 0ea715a7c47..34775ae19ab 100644 --- a/packages/payload/src/query-presets/preventLockout.ts +++ b/packages/payload/src/query-presets/preventLockout.ts @@ -1,7 +1,7 @@ import type { Validate } from '../fields/config/types.js' import { APIError } from '../errors/APIError.js' -import { createPayloadReq } from '../utilities/createPayloadReq.js' +import { createPayloadRequest } from '../utilities/createPayloadRequest.js' import { initTransaction } from '../utilities/initTransaction.js' import { killTransaction } from '../utilities/killTransaction.js' import { queryPresetsCollectionSlug } from './config.js' @@ -21,7 +21,7 @@ export const preventLockout: Validate = async ( ) => { // Use context to ensure an infinite loop doesn't occur if (!incomingReq.context._preventLockout && !overrideAccess) { - const req = await createPayloadReq({ + const req = await createPayloadRequest({ context: { _preventLockout: true, }, diff --git a/packages/payload/src/queues/localAPI.ts b/packages/payload/src/queues/localAPI.ts index e4c41995f1c..33b32650caa 100644 --- a/packages/payload/src/queues/localAPI.ts +++ b/packages/payload/src/queues/localAPI.ts @@ -2,7 +2,7 @@ import type { SharedLocalAPIOptions } from '../types/operations.js' import type { JobFromTask } from './config/types/workflowTypes.js' import { - createPayloadReq, + createPayloadRequest, Forbidden, type Job, type Payload, @@ -42,7 +42,7 @@ export const getJobsLocalAPI = (payload: Payload) => ({ queue?: string req?: PayloadRequest }): Promise => { - const newReq: PayloadRequest = args?.req ?? (await createPayloadReq({ payload })) + const newReq: PayloadRequest = args?.req ?? (await createPayloadRequest({ payload })) return await handleSchedules({ allQueues: args?.allQueues, @@ -95,7 +95,7 @@ export const getJobsLocalAPI = (payload: Payload) => ({ : JobFromTask > => { const overrideAccess = args.overrideAccess ?? false - const req: PayloadRequest = args.req ?? (await createPayloadReq({ payload })) + const req: PayloadRequest = args.req ?? (await createPayloadRequest({ payload })) if (!overrideAccess) { /** @@ -249,7 +249,7 @@ export const getJobsLocalAPI = (payload: Payload) => ({ where?: Where } & Pick, ): Promise> => { - const newReq: PayloadRequest = args?.req ?? (await createPayloadReq({ payload })) + const newReq: PayloadRequest = args?.req ?? (await createPayloadRequest({ payload })) return await runJobs({ allQueues: args?.allQueues, @@ -279,7 +279,7 @@ export const getJobsLocalAPI = (payload: Payload) => ({ silent?: RunJobsSilent } & Pick, ): Promise> => { - const newReq: PayloadRequest = args.req ?? (await createPayloadReq({ payload })) + const newReq: PayloadRequest = args.req ?? (await createPayloadRequest({ payload })) return await runJobs({ id: args.id, @@ -296,7 +296,7 @@ export const getJobsLocalAPI = (payload: Payload) => ({ where: Where } & Pick, ): Promise => { - const req: PayloadRequest = args.req ?? (await createPayloadReq({ payload })) + const req: PayloadRequest = args.req ?? (await createPayloadRequest({ payload })) const overrideAccess = args.overrideAccess ?? false if (!overrideAccess) { @@ -354,7 +354,7 @@ export const getJobsLocalAPI = (payload: Payload) => ({ req?: PayloadRequest } & Pick, ): Promise => { - const req: PayloadRequest = args.req ?? (await createPayloadReq({ payload })) + const req: PayloadRequest = args.req ?? (await createPayloadRequest({ payload })) const overrideAccess = args.overrideAccess ?? false if (!overrideAccess) { diff --git a/packages/payload/src/utilities/createPayloadReq.spec.ts b/packages/payload/src/utilities/createPayloadRequest.spec.ts similarity index 85% rename from packages/payload/src/utilities/createPayloadReq.spec.ts rename to packages/payload/src/utilities/createPayloadRequest.spec.ts index f50bbe15999..0cf21b4b0d0 100644 --- a/packages/payload/src/utilities/createPayloadReq.spec.ts +++ b/packages/payload/src/utilities/createPayloadRequest.spec.ts @@ -2,9 +2,9 @@ import { describe, expect, it, vi } from 'vitest' import type { Payload, PayloadRequest } from '../index.js' -import { createPayloadReq } from './createPayloadReq.js' +import { createPayloadRequest } from './createPayloadRequest.js' -describe('createPayloadReq', () => { +describe('createPayloadRequest', () => { const mockPayload = { config: { serverURL: undefined, @@ -25,7 +25,7 @@ describe('createPayloadReq', () => { url: 'http://example.com/api/test', } - const result = await createPayloadReq({ payload: mockPayload, req }) + const result = await createPayloadRequest({ payload: mockPayload, req }) expect(result.url).toBe('http://example.com/api/test') expect(mockPayload.logger.error).not.toHaveBeenCalled() @@ -49,7 +49,7 @@ describe('createPayloadReq', () => { const req = {} - const result = await createPayloadReq({ + const result = await createPayloadRequest({ payload: payloadWithServerURL, req, urlSuffix: '/api', @@ -79,7 +79,7 @@ describe('createPayloadReq', () => { url: 'http://actual-request.com/api/test', } - const result = await createPayloadReq({ payload: payloadWithServerURL, req }) + const result = await createPayloadRequest({ payload: payloadWithServerURL, req }) expect(result.url).toBe('http://actual-request.com/api/test') expect(payloadWithServerURL.logger.error).not.toHaveBeenCalled() @@ -88,7 +88,7 @@ describe('createPayloadReq', () => { it('should fall back to localhost when neither req.url nor serverURL provided', async () => { const req = {} - const result = await createPayloadReq({ payload: mockPayload, req }) + const result = await createPayloadRequest({ payload: mockPayload, req }) expect(result.url).toBe('http://localhost/') expect(mockPayload.logger.error).not.toHaveBeenCalled() @@ -112,7 +112,7 @@ describe('createPayloadReq', () => { const req = {} - const result = await createPayloadReq({ + const result = await createPayloadRequest({ payload: payloadWithServerURL, req, urlSuffix: '/api/preview', @@ -125,7 +125,7 @@ describe('createPayloadReq', () => { it('should append urlSuffix to fallback URL when neither req.url nor serverURL provided', async () => { const req = {} - const result = await createPayloadReq({ payload: mockPayload, req, urlSuffix: '/api/test' }) + const result = await createPayloadRequest({ payload: mockPayload, req, urlSuffix: '/api/test' }) expect(result.url).toBe('http://localhost/api/test') expect(mockPayload.logger.error).not.toHaveBeenCalled() @@ -145,7 +145,7 @@ describe('createPayloadReq', () => { url: 'http://example.com/api/posts', } as Partial - const result = await createPayloadReq({ + const result = await createPayloadRequest({ context: { fromArgs: true }, payload: mockPayload, req, @@ -175,19 +175,19 @@ describe('createPayloadReq', () => { } as unknown as Payload it("normalizes localized '*' requests to 'all'", async () => { - const result = await createPayloadReq({ locale: '*', payload: localizedPayload }) + const result = await createPayloadRequest({ locale: '*', payload: localizedPayload }) expect(result.locale).toBe('all') }) it('uses the default locale for localized requests without a locale', async () => { - const result = await createPayloadReq({ payload: localizedPayload }) + const result = await createPayloadRequest({ payload: localizedPayload }) expect(result.locale).toBe('en') }) it('sanitizes unsupported fallback locales for localized requests', async () => { - const result = await createPayloadReq({ + const result = await createPayloadRequest({ fallbackLocale: 'fr', locale: 'en', payload: localizedPayload, @@ -197,7 +197,7 @@ describe('createPayloadReq', () => { }) it('does not assign locale state when localization is disabled', async () => { - const result = await createPayloadReq({ + const result = await createPayloadRequest({ fallbackLocale: 'de', locale: 'en', payload: mockPayload, diff --git a/packages/payload/src/utilities/createPayloadReq.ts b/packages/payload/src/utilities/createPayloadRequest.ts similarity index 95% rename from packages/payload/src/utilities/createPayloadReq.ts rename to packages/payload/src/utilities/createPayloadRequest.ts index 80c3736338c..7b972434ce8 100644 --- a/packages/payload/src/utilities/createPayloadReq.ts +++ b/packages/payload/src/utilities/createPayloadRequest.ts @@ -85,7 +85,7 @@ const attachFakeURLProperties = (req: Partial, urlSuffix?: strin } } -export type CreatePayloadReqArgs = { +export type CreatePayloadRequestArgs = { context?: RequestContext depth?: number fallbackLocale?: false | TypedLocale @@ -96,9 +96,9 @@ export type CreatePayloadReqArgs = { user?: User } -type CreatePayloadReq = (args: CreatePayloadReqArgs) => Promise +type CreatePayloadRequest = (args: CreatePayloadRequestArgs) => Promise -export const createPayloadReq: CreatePayloadReq = async ({ +export const createPayloadRequest: CreatePayloadRequest = async ({ context, depth, fallbackLocale, diff --git a/packages/payload/src/utilities/createPayloadReqFromWebRequest.spec.ts b/packages/payload/src/utilities/createPayloadRequestFromWebRequest.spec.ts similarity index 78% rename from packages/payload/src/utilities/createPayloadReqFromWebRequest.spec.ts rename to packages/payload/src/utilities/createPayloadRequestFromWebRequest.spec.ts index a3de3498ee6..948011e09a6 100644 --- a/packages/payload/src/utilities/createPayloadReqFromWebRequest.spec.ts +++ b/packages/payload/src/utilities/createPayloadRequestFromWebRequest.spec.ts @@ -7,8 +7,8 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { executeAuthStrategies } from '../auth/executeAuthStrategies.js' import { getPayload } from '../index.js' import { addLocalesToRequestFromData } from './addLocalesToRequest.js' -import { createPayloadReq } from './createPayloadReq.js' -import { createPayloadReqFromWebRequest } from './createPayloadReqFromWebRequest.js' +import { createPayloadRequest } from './createPayloadRequest.js' +import { createPayloadRequestFromWebRequest } from './createPayloadRequestFromWebRequest.js' const { initI18n } = vi.hoisted(() => ({ initI18n: vi.fn(), @@ -31,8 +31,8 @@ vi.mock('../index.js', () => ({ getPayload: vi.fn(), })) -vi.mock('./createPayloadReq.js', () => ({ - createPayloadReq: vi.fn(), +vi.mock('./createPayloadRequest.js', () => ({ + createPayloadRequest: vi.fn(), })) const config = { @@ -71,17 +71,17 @@ const payload = { config, } as unknown as Payload -const useRealCreatePayloadReq = async () => { +const useRealCreatePayloadRequest = async () => { const actual = - await vi.importActual('./createPayloadReq.js') + await vi.importActual('./createPayloadRequest.js') - vi.mocked(createPayloadReq).mockImplementation(actual.createPayloadReq) + vi.mocked(createPayloadRequest).mockImplementation(actual.createPayloadRequest) } -describe('createPayloadReqFromWebRequest', () => { +describe('createPayloadRequestFromWebRequest', () => { beforeEach(() => { vi.mocked(getPayload).mockReset().mockResolvedValue(payload) - vi.mocked(createPayloadReq) + vi.mocked(createPayloadRequest) .mockReset() .mockImplementation(async ({ req }) => req as PayloadRequest) vi.mocked(executeAuthStrategies).mockReset().mockResolvedValue({ @@ -91,12 +91,12 @@ describe('createPayloadReqFromWebRequest', () => { initI18n.mockReset().mockResolvedValue(i18n) }) - it('initializes the original Web Request through createPayloadReq before authentication', async () => { + it('initializes the original Web Request through createPayloadRequest before authentication', async () => { const request = new Request('http://localhost/api/posts?locale=en&depth=2') - const result = await createPayloadReqFromWebRequest({ config, request }) + const result = await createPayloadRequestFromWebRequest({ config, request }) - expect(createPayloadReq).toHaveBeenCalledWith( + expect(createPayloadRequest).toHaveBeenCalledWith( expect.objectContaining({ locale: 'en', payload, @@ -106,7 +106,7 @@ describe('createPayloadReqFromWebRequest', () => { expect((request as PayloadRequest).i18n).toBe(i18n) expect((request as PayloadRequest).query).toEqual({ depth: '2', locale: 'en' }) expect(executeAuthStrategies).toHaveBeenCalledWith(expect.objectContaining({ req: request })) - expect(vi.mocked(createPayloadReq).mock.invocationCallOrder[0]).toBeLessThan( + expect(vi.mocked(createPayloadRequest).mock.invocationCallOrder[0]).toBeLessThan( vi.mocked(executeAuthStrategies).mock.invocationCallOrder[0], ) expect(result).toBe(request) @@ -118,7 +118,7 @@ describe('createPayloadReqFromWebRequest', () => { ] as const)('assigns %s requests to the %s API', async (url, payloadAPI) => { const request = new Request(url) - const result = await createPayloadReqFromWebRequest({ config, request }) + const result = await createPayloadRequestFromWebRequest({ config, request }) expect(result.payloadAPI).toBe(payloadAPI) }) @@ -128,7 +128,7 @@ describe('createPayloadReqFromWebRequest', () => { 'http://localhost/api/posts?where[title][equals]=Hello&tags[]=news&tags[]=featured', ) - const result = await createPayloadReqFromWebRequest({ + const result = await createPayloadRequestFromWebRequest({ config, params: { collection: 'posts' }, request, @@ -148,7 +148,7 @@ describe('createPayloadReqFromWebRequest', () => { vi.mocked(executeAuthStrategies).mockResolvedValue({ responseHeaders, user }) - const result = await createPayloadReqFromWebRequest({ config, request }) + const result = await createPayloadRequestFromWebRequest({ config, request }) expect(result.responseHeaders).toBe(responseHeaders) expect(result.user).toBe(user) @@ -157,9 +157,9 @@ describe('createPayloadReqFromWebRequest', () => { it('passes sanitized fallback locale state to common request initialization', async () => { const request = new Request('http://localhost/api/posts?locale=de&fallbackLocale=unsupported') - await createPayloadReqFromWebRequest({ config, request }) + await createPayloadRequestFromWebRequest({ config, request }) - expect(createPayloadReq).toHaveBeenCalledWith( + expect(createPayloadRequest).toHaveBeenCalledWith( expect.objectContaining({ fallbackLocale: false, locale: 'de', @@ -177,9 +177,9 @@ describe('createPayloadReqFromWebRequest', () => { } as unknown as Payload vi.mocked(getPayload).mockResolvedValue(unlocalizedPayload) - await useRealCreatePayloadReq() + await useRealCreatePayloadRequest() - const result = await createPayloadReqFromWebRequest({ + const result = await createPayloadRequestFromWebRequest({ config: unlocalizedConfig, request: new Request('http://localhost/api/posts?locale=de&fallbackLocale=en'), }) @@ -201,9 +201,9 @@ describe('createPayloadReqFromWebRequest', () => { } as unknown as Payload vi.mocked(getPayload).mockResolvedValue(noFallbackPayload) - await useRealCreatePayloadReq() + await useRealCreatePayloadRequest() - const result = await createPayloadReqFromWebRequest({ + const result = await createPayloadRequestFromWebRequest({ config: noFallbackConfig, request: new Request('http://localhost/api/posts'), }) @@ -217,9 +217,9 @@ describe('createPayloadReqFromWebRequest', () => { }) it('preserves fallback locale arrays through common request initialization', async () => { - await useRealCreatePayloadReq() + await useRealCreatePayloadRequest() - const result = await createPayloadReqFromWebRequest({ + const result = await createPayloadRequestFromWebRequest({ config, request: new Request( 'http://localhost/api/posts?locale=de&fallbackLocale[]=en&fallbackLocale[]=de', diff --git a/packages/payload/src/utilities/createPayloadReqFromWebRequest.ts b/packages/payload/src/utilities/createPayloadRequestFromWebRequest.ts similarity index 95% rename from packages/payload/src/utilities/createPayloadReqFromWebRequest.ts rename to packages/payload/src/utilities/createPayloadRequestFromWebRequest.ts index 1a4abe48c19..7c92250ebc2 100644 --- a/packages/payload/src/utilities/createPayloadReqFromWebRequest.ts +++ b/packages/payload/src/utilities/createPayloadRequestFromWebRequest.ts @@ -8,7 +8,7 @@ import type { PayloadRequest } from '../types/index.js' import { executeAuthStrategies } from '../auth/executeAuthStrategies.js' import { getPayload } from '../index.js' import { sanitizeLocales } from './addLocalesToRequest.js' -import { createPayloadReq } from './createPayloadReq.js' +import { createPayloadRequest } from './createPayloadRequest.js' import { formatAdminURL } from './formatAdminURL.js' import { getRequestLanguage } from './getRequestLanguage.js' import { parseCookies } from './parseCookies.js' @@ -23,7 +23,7 @@ type Args = { request: Request } -export const createPayloadReqFromWebRequest = async ({ +export const createPayloadRequestFromWebRequest = async ({ canSetHeaders, config: configPromise, params, @@ -111,7 +111,7 @@ export const createPayloadReqFromWebRequest = async ({ transactionID: undefined, }) - const req = await createPayloadReq({ + const req = await createPayloadRequest({ fallbackLocale: fallbackLocale as false | TypedLocale | undefined, locale: locale ?? undefined, payload, diff --git a/packages/payload/src/utilities/handleEndpoints.ts b/packages/payload/src/utilities/handleEndpoints.ts index ddb30a7d1e6..db78c983549 100644 --- a/packages/payload/src/utilities/handleEndpoints.ts +++ b/packages/payload/src/utilities/handleEndpoints.ts @@ -7,7 +7,7 @@ import type { APIError } from '../errors/APIError.js' import type { GlobalConfig } from '../globals/config/types.js' import type { PayloadRequest } from '../types/index.js' -import { createPayloadReqFromWebRequest } from './createPayloadReqFromWebRequest.js' +import { createPayloadRequestFromWebRequest } from './createPayloadRequestFromWebRequest.js' import { formatAdminURL, stripTrailingSlash } from './formatAdminURL.js' import { headersWithCors } from './headersWithCors.js' import { mergeHeaders } from './mergeHeaders.js' @@ -103,7 +103,7 @@ export const handleEndpoints = async ({ // May not be supported by every endpoint data = await request.json() - // createPayloadReqFromWebRequest reads locale and fallbackLocale from searchParams. + // createPayloadRequestFromWebRequest reads locale and fallbackLocale from searchParams. if (data?.locale) { url += `?locale=${data.locale}` } @@ -135,7 +135,7 @@ export const handleEndpoints = async ({ } try { - req = await createPayloadReqFromWebRequest({ + req = await createPayloadRequestFromWebRequest({ canSetHeaders: true, config: incomingConfig, payloadInstanceCacheKey, diff --git a/packages/payload/src/utilities/isolateObjectProperty.spec.ts b/packages/payload/src/utilities/isolateObjectProperty.spec.ts index e024e6a44be..6282a464378 100644 --- a/packages/payload/src/utilities/isolateObjectProperty.spec.ts +++ b/packages/payload/src/utilities/isolateObjectProperty.spec.ts @@ -122,7 +122,7 @@ describe('isolateObjectProperty', () => { const request = new MockRequest() const proxy = isolateObjectProperty(request, 'nonExistentKey' as keyof typeof request) - // This simulates what happens in createPayloadReq when checking req.headers + // This simulates what happens in createPayloadRequest when checking req.headers expect(proxy.headers).toBeInstanceOf(Headers) expect(proxy.headers.get('content-type')).toBe('application/json') }) diff --git a/packages/plugin-mcp/src/mcp/buildMcpServer.ts b/packages/plugin-mcp/src/mcp/buildMcpServer.ts index b2baa774505..3610952bfee 100644 --- a/packages/plugin-mcp/src/mcp/buildMcpServer.ts +++ b/packages/plugin-mcp/src/mcp/buildMcpServer.ts @@ -25,7 +25,7 @@ import { toStandardSchema } from '../utils/toStandardSchema.js' * * `req` is the request context handlers see. For HTTP it's the live * `PayloadRequest` derived from the incoming HTTP request; for stdio it's a - * synthesized one built via `createPayloadReq`. + * synthesized one built via `createPayloadRequest`. */ export const buildMcpServer = ({ authorizedMCP, diff --git a/packages/plugin-mcp/src/mcp/builtin/collections/authTools.ts b/packages/plugin-mcp/src/mcp/builtin/collections/authTools.ts index 11ea94c2818..b8863e7c2d5 100644 --- a/packages/plugin-mcp/src/mcp/builtin/collections/authTools.ts +++ b/packages/plugin-mcp/src/mcp/builtin/collections/authTools.ts @@ -1,4 +1,4 @@ -import { createPayloadReq, strictObject, z } from 'payload' +import { createPayloadRequest, strictObject, z } from 'payload' import type { MCPToolResponse } from '../../../types.js' @@ -53,7 +53,10 @@ export const authCollectionTool = defineCollectionTool({ if (input.headers) { authHeaders = new Headers(input.headers) } - const authReq = await createPayloadReq({ payload: req.payload, req: { headers: authHeaders } }) + const authReq = await createPayloadRequest({ + payload: req.payload, + req: { headers: authHeaders }, + }) const result = await req.payload.auth({ headers: authHeaders, req: authReq }) if (result.user) { diff --git a/packages/plugin-mcp/src/stdio.ts b/packages/plugin-mcp/src/stdio.ts index 448239b1065..0dabbbcdb54 100644 --- a/packages/plugin-mcp/src/stdio.ts +++ b/packages/plugin-mcp/src/stdio.ts @@ -3,7 +3,7 @@ import type { Config, Plugin, SanitizedConfig } from 'payload' /* eslint-disable no-console */ import { serveStdio } from '@modelcontextprotocol/server/stdio' import { fileURLToPath, pathToFileURL } from 'node:url' -import { createPayloadReq, getPayload } from 'payload' +import { createPayloadRequest, getPayload } from 'payload' import { findConfig, loadEnv } from 'payload/node' import type { SanitizedMCPPluginConfig } from './types.js' @@ -91,7 +91,7 @@ export const runMcpStdio = async (): Promise => { headers.set('Authorization', process.env.PAYLOAD_MCP_AUTHORIZATION) } - const req = await createPayloadReq({ payload, req: { headers } }) + const req = await createPayloadRequest({ payload, req: { headers } }) req.payloadAPI = 'MCP' as const const authorizedMCP = await getAuthorizedMCP({ overrideAccess, req }) diff --git a/packages/richtext-lexical/src/features/converters/utilities/payloadPopulateFn.ts b/packages/richtext-lexical/src/features/converters/utilities/payloadPopulateFn.ts index df77929933c..107d3bc1e3c 100644 --- a/packages/richtext-lexical/src/features/converters/utilities/payloadPopulateFn.ts +++ b/packages/richtext-lexical/src/features/converters/utilities/payloadPopulateFn.ts @@ -1,4 +1,4 @@ -import { createPayloadReq, type Payload, type PayloadRequest, type TypedLocale } from 'payload' +import { createPayloadRequest, type Payload, type PayloadRequest, type TypedLocale } from 'payload' import type { HTMLPopulateFn } from '../lexicalToHtml/async/types.js' @@ -48,7 +48,7 @@ export const getPayloadPopulateFn: ( }) => { let reqToUse: PayloadRequest | undefined = req if (req === undefined && payload) { - reqToUse = await createPayloadReq({ payload }) + reqToUse = await createPayloadRequest({ payload }) } if (!reqToUse) { diff --git a/packages/tanstack-start/src/layouts/Root/getLayoutData.ts b/packages/tanstack-start/src/layouts/Root/getLayoutData.ts index 81fcb9405ba..6182e9eec43 100644 --- a/packages/tanstack-start/src/layouts/Root/getLayoutData.ts +++ b/packages/tanstack-start/src/layouts/Root/getLayoutData.ts @@ -19,7 +19,7 @@ import { Outlet } from '@tanstack/react-router' import { applyLocaleFiltering } from 'payload/shared' import { createElement } from 'react' -import { getAdminContext } from '../../utilities/getAdminContext.server.js' +import { createAdminContext } from '../../utilities/createAdminContext.server.js' export type RootLayoutData = { clientConfig: ClientConfig @@ -66,7 +66,7 @@ export async function getLayoutData({ payload: { config }, }, user, - } = await getAdminContext({ configPromise, importMap }) + } = await createAdminContext({ configPromise, importMap }) const theme = getRequestTheme({ config, cookies, headers }) const isEmbedded = getRequestEmbed({ config, cookies }) diff --git a/packages/tanstack-start/src/utilities/getAdminContext.server.spec.ts b/packages/tanstack-start/src/utilities/createAdminContext.server.spec.ts similarity index 68% rename from packages/tanstack-start/src/utilities/getAdminContext.server.spec.ts rename to packages/tanstack-start/src/utilities/createAdminContext.server.spec.ts index 42061995798..1c5c6397668 100644 --- a/packages/tanstack-start/src/utilities/getAdminContext.server.spec.ts +++ b/packages/tanstack-start/src/utilities/createAdminContext.server.spec.ts @@ -1,8 +1,8 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' -const { getRequest, getPayloadAdminContext, tanstackServerAdapter } = vi.hoisted(() => ({ +const { getRequest, createPayloadAdminContext, tanstackServerAdapter } = vi.hoisted(() => ({ getRequest: vi.fn(), - getPayloadAdminContext: vi.fn(), + createPayloadAdminContext: vi.fn(), tanstackServerAdapter: { getHeaders: vi.fn(), }, @@ -13,7 +13,7 @@ vi.mock('@tanstack/react-start/server', () => ({ })) vi.mock('payload/internal', () => ({ - getAdminContext: getPayloadAdminContext, + createAdminContext: createPayloadAdminContext, })) vi.mock('./devConfigReload.server.js', () => ({})) @@ -22,12 +22,12 @@ vi.mock('./serverAdapter.server.js', () => ({ tanstackServerAdapter, })) -import { getAdminContext } from './getAdminContext.server.js' +import { createAdminContext } from './createAdminContext.server.js' -describe('getAdminContext', () => { +describe('createAdminContext', () => { beforeEach(() => { getRequest.mockReset().mockReturnValue(new Request('http://localhost/admin?locale=es')) - getPayloadAdminContext.mockReset().mockResolvedValue({}) + createPayloadAdminContext.mockReset().mockResolvedValue({}) }) it('should provide the active request URL and default server adapter', async () => { @@ -36,9 +36,9 @@ describe('getAdminContext', () => { importMap: {}, } - await getAdminContext(args) + await createAdminContext(args) - expect(getPayloadAdminContext).toHaveBeenCalledWith({ + expect(createPayloadAdminContext).toHaveBeenCalledWith({ ...args, requestURL: 'http://localhost/admin?locale=es', serverAdapter: tanstackServerAdapter, @@ -50,13 +50,13 @@ describe('getAdminContext', () => { getHeaders: vi.fn(), } - await getAdminContext({ + await createAdminContext({ configPromise: Promise.resolve({} as never), importMap: {}, serverAdapter: serverAdapter as never, }) - expect(getPayloadAdminContext).toHaveBeenCalledWith( + expect(createPayloadAdminContext).toHaveBeenCalledWith( expect.objectContaining({ serverAdapter, }), diff --git a/packages/tanstack-start/src/utilities/createAdminContext.server.ts b/packages/tanstack-start/src/utilities/createAdminContext.server.ts new file mode 100644 index 00000000000..5b98824ef65 --- /dev/null +++ b/packages/tanstack-start/src/utilities/createAdminContext.server.ts @@ -0,0 +1,24 @@ +import type { ServerAdapter } from 'payload' +import type { CreateAdminContextArgs } from 'payload/internal' + +import { getRequest } from '@tanstack/react-start/server' +import { createAdminContext as createPayloadAdminContext } from 'payload/internal' + +// Registers the dev reload strategy before `createPayloadAdminContext` can build an instance. +// Side-effect only, and a no-op outside of dev serve. +import './devConfigReload.server.js' +import { tanstackServerAdapter } from './serverAdapter.server.js' + +type TanStackCreateAdminContextArgs = { + serverAdapter?: ServerAdapter +} & Omit + +export const createAdminContext = ({ + serverAdapter = tanstackServerAdapter, + ...args +}: TanStackCreateAdminContextArgs) => + createPayloadAdminContext({ + ...args, + requestURL: getRequest().url, + serverAdapter, + }) diff --git a/packages/tanstack-start/src/utilities/devConfigReload.server.ts b/packages/tanstack-start/src/utilities/devConfigReload.server.ts index f7f1ec5f6c1..c375e39329a 100644 --- a/packages/tanstack-start/src/utilities/devConfigReload.server.ts +++ b/packages/tanstack-start/src/utilities/devConfigReload.server.ts @@ -6,7 +6,7 @@ import { PAYLOAD_CONFIG_CHANGED_EVENT } from './devConfigReloadEvent.js' * Subscribes the Payload instance to the config-changed event that the * `payload:dev-config-reload` Vite plugin broadcasts. * - * Imported for its side effect by `getAdminContext.server.ts`, the adapter's only + * Imported for its side effect by `createAdminContext.server.ts`, the adapter's only * `getPayload` call site, so the strategy is registered before an instance can * be built. Registration is module-scoped on both ends: this runs inside the * app's server graph and so resolves the same copy of `payload` that diff --git a/packages/tanstack-start/src/utilities/getAdminContext.server.ts b/packages/tanstack-start/src/utilities/getAdminContext.server.ts deleted file mode 100644 index 6a1d452dfaf..00000000000 --- a/packages/tanstack-start/src/utilities/getAdminContext.server.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { ServerAdapter } from 'payload' -import type { GetAdminContextArgs } from 'payload/internal' - -import { getRequest } from '@tanstack/react-start/server' -import { getAdminContext as getPayloadAdminContext } from 'payload/internal' - -// Registers the dev reload strategy before `getPayloadAdminContext` can build an instance. -// Side-effect only, and a no-op outside of dev serve. -import './devConfigReload.server.js' -import { tanstackServerAdapter } from './serverAdapter.server.js' - -type TanStackGetAdminContextArgs = { - serverAdapter?: ServerAdapter -} & Omit - -export const getAdminContext = ({ - serverAdapter = tanstackServerAdapter, - ...args -}: TanStackGetAdminContextArgs) => - getPayloadAdminContext({ - ...args, - requestURL: getRequest().url, - serverAdapter, - }) diff --git a/packages/tanstack-start/src/utilities/getRequestI18n.server.ts b/packages/tanstack-start/src/utilities/getRequestI18n.server.ts index bab89f9c8bd..c063793309b 100644 --- a/packages/tanstack-start/src/utilities/getRequestI18n.server.ts +++ b/packages/tanstack-start/src/utilities/getRequestI18n.server.ts @@ -7,7 +7,7 @@ import { getRequestLanguage, parseCookies } from 'payload' /** * Resolves the client `I18n` for the current TanStack Start request without a - * full `getAdminContext` (no `getPayload`/auth/access). Mirrors the Next.js adapter's + * full `createAdminContext` (no `getPayload`/auth/access). Mirrors the Next.js adapter's * `getNextRequestI18n` and feeds the shared `generatePageMetadata`, which only * needs `config` + `i18n` to translate admin-page titles. */ diff --git a/packages/tanstack-start/src/utilities/graphqlHandler.server.ts b/packages/tanstack-start/src/utilities/graphqlHandler.server.ts index 7625dde1bbf..6c97fdfc657 100644 --- a/packages/tanstack-start/src/utilities/graphqlHandler.server.ts +++ b/packages/tanstack-start/src/utilities/graphqlHandler.server.ts @@ -3,7 +3,7 @@ import type { Payload, PayloadRequest, SanitizedConfig } from 'payload' import { addDataAndFileToRequest, addLocalesToRequestFromData, - createPayloadReqFromWebRequest, + createPayloadRequestFromWebRequest, headersWithCors, logError, mergeHeaders, @@ -102,7 +102,7 @@ export const handleGraphQL = async ({ }): Promise => { const originalRequest = request.clone() - const req = await createPayloadReqFromWebRequest({ + const req = await createPayloadRequestFromWebRequest({ canSetHeaders: true, config, request, diff --git a/packages/tanstack-start/src/utilities/handleServerFunctions.ts b/packages/tanstack-start/src/utilities/handleServerFunctions.ts index 2218afa8ded..980acfa510f 100644 --- a/packages/tanstack-start/src/utilities/handleServerFunctions.ts +++ b/packages/tanstack-start/src/utilities/handleServerFunctions.ts @@ -1,9 +1,9 @@ import { createServerFunctionHandler } from '@payloadcms/ui/utilities/handleServerFunctions' -import { getAdminContext } from './getAdminContext.server.js' +import { createAdminContext } from './createAdminContext.server.js' import { serializeForRsc } from './serializeForRsc.js' export const handleServerFunctions = createServerFunctionHandler({ - getAdminContext, + createAdminContext, transformResult: serializeForRsc, }) diff --git a/packages/tanstack-start/src/utilities/loadAdminPage.tsx b/packages/tanstack-start/src/utilities/loadAdminPage.tsx index 34628bc7a34..54e683114c9 100644 --- a/packages/tanstack-start/src/utilities/loadAdminPage.tsx +++ b/packages/tanstack-start/src/utilities/loadAdminPage.tsx @@ -6,7 +6,7 @@ import { renderServerComponent } from '@tanstack/react-start/rsc' import type { AdminPageMetadata } from './meta.js' -import { getAdminContext } from './getAdminContext.server.js' +import { createAdminContext } from './createAdminContext.server.js' import { getRequestI18n } from './getRequestI18n.server.js' import { createPageRenderServerAdapter } from './serverAdapter.server.js' @@ -133,7 +133,7 @@ const toAdminPageMetadata = (meta: MetaConfig): AdminPageMetadata => { * the app's `config` and generated `importMap`. * * 1. Gets the Payload admin context via the shared `renderRoot` orchestrator - * from `@payloadcms/ui`, passing a TanStack-bound `getAdminContext`. The injected + * from `@payloadcms/ui`, passing a TanStack-bound `createAdminContext`. The injected * page-render `ServerAdapter` records navigation intent and throws the * framework-agnostic error contract. * 2. Pipes the resulting React server tree through `renderServerComponent` @@ -175,14 +175,14 @@ export async function loadAdminPage({ const pageServerAdapter = createPageRenderServerAdapter(nav) let userAgent: string | undefined - // `renderRoot` calls `getAdminContext` itself with its own overrides (query + // `renderRoot` calls `createAdminContext` itself with its own overrides (query // re-nesting, `urlSuffix`, `fallbackLocale`). Forward them, injecting the // page-render `ServerAdapter` so `req.server.redirect()` / `.notFound()` // is recorded + thrown rather than escaping as raw TanStack nav. - const boundGetAdminContext: Parameters[0]['getAdminContext'] = async ( + const boundCreateAdminContext: Parameters[0]['createAdminContext'] = async ( args, ) => { - const result = await getAdminContext({ + const result = await createAdminContext({ configPromise: args.configPromise, importMap: args.importMap, overrides: args.overrides, @@ -210,7 +210,7 @@ export async function loadAdminPage({ const notFoundNode = await renderNotFoundPage({ config: Promise.resolve(config), - getAdminContext, + createAdminContext, importMap, params: Promise.resolve({ segments: splatSegments }), searchParams: Promise.resolve(searchParams), @@ -225,7 +225,7 @@ export async function loadAdminPage({ const node = await renderRoot({ adminViews: defaultAdminViews, config: Promise.resolve(config), - getAdminContext: boundGetAdminContext, + createAdminContext: boundCreateAdminContext, importMap, key: splat ?? '', notFound: pageServerAdapter.notFound, diff --git a/packages/ui/src/layouts/Root/index.tsx b/packages/ui/src/layouts/Root/index.tsx index 37f835e6017..08469fdb12b 100644 --- a/packages/ui/src/layouts/Root/index.tsx +++ b/packages/ui/src/layouts/Root/index.tsx @@ -3,7 +3,7 @@ import type { ImportMap, LanguageOptions, SanitizedConfig, ServerFunctionClient import { applyLocaleFiltering } from 'payload/shared' import React, { Suspense } from 'react' -import type { GetAdminContextFn } from '../../views/Root/index.js' +import type { CreateAdminContextFn } from '../../views/Root/index.js' import { getNavPrefs } from '../../elements/Nav/getNavPrefs.js' @@ -45,7 +45,7 @@ type RootLayoutProps = { * Framework-specific font loaders (e.g. `next/font`) are supplied by the caller. */ readonly fonts?: Font[] - readonly getAdminContext: GetAdminContextFn + readonly createAdminContext: CreateAdminContextFn /** * Custom content to render inside the admin panel's `` element. * @@ -89,7 +89,7 @@ const RootLayoutContent = async ({ children, config: configPromise, fonts = [], - getAdminContext, + createAdminContext, head: headFromProps, htmlProps = {}, importMap, @@ -106,7 +106,7 @@ const RootLayoutContent = async ({ payload: { config }, }, user, - } = await getAdminContext({ configPromise, importMap, key: 'RootLayout' }) + } = await createAdminContext({ configPromise, importMap, key: 'RootLayout' }) const theme = getRequestTheme({ config, diff --git a/packages/ui/src/utilities/handleServerFunctions.ts b/packages/ui/src/utilities/handleServerFunctions.ts index cacc7fb1a31..32fd0d007d2 100644 --- a/packages/ui/src/utilities/handleServerFunctions.ts +++ b/packages/ui/src/utilities/handleServerFunctions.ts @@ -1,6 +1,6 @@ import type { DefaultServerFunctionArgs, - GetAdminContextResult, + CreateAdminContextResult, ImportMap, SanitizedConfig, ServerFunction, @@ -9,18 +9,18 @@ import type { import { sharedServerFunctions } from './serverFunctionRegistry.js' -type GetAdminContextFn = (args: { +type CreateAdminContextFn = (args: { configPromise: Promise | SanitizedConfig importMap: ImportMap -}) => Promise +}) => Promise type CreateServerFunctionHandlerArgs = { /** * Framework-specific admin context creation. The adapter closes over its own - * `ServerAdapter` / request source (Next.js: `getAdminContext` + `nextServerAdapter`; - * TanStack Start: its `getRequest()`-based `getAdminContext`). + * `ServerAdapter` / request source (Next.js: `createAdminContext` + `nextServerAdapter`; + * TanStack Start: its `getRequest()`-based `createAdminContext`). */ - getAdminContext: GetAdminContextFn + createAdminContext: CreateAdminContextFn /** * Additional handlers registered alongside `sharedServerFunctions`. Reserved * for adapter-specific functions; today both adapters run the shared set only. @@ -38,12 +38,12 @@ type CreateServerFunctionHandlerArgs = { * Factory for the framework-agnostic `handleServerFunctions` entry point. * * All adapters dispatch from the shared registry (`sharedServerFunctions`) and - * differ only in the two injected hooks below — `getAdminContext` and `transformResult` + * differ only in the two injected hooks below — `createAdminContext` and `transformResult` * — so there is a single dispatch implementation and the handler sets cannot * drift between frameworks. */ export const createServerFunctionHandler = ({ - getAdminContext, + createAdminContext, serverFunctions, transformResult, }: CreateServerFunctionHandlerArgs): ServerFunctionHandler => { @@ -60,7 +60,7 @@ export const createServerFunctionHandler = ({ serverFunctions: extraServerFunctions, } = args - const { cookies, locale, permissions, req, user } = await getAdminContext({ + const { cookies, locale, permissions, req, user } = await createAdminContext({ configPromise, importMap, }) diff --git a/packages/ui/src/views/NotFound/page.tsx b/packages/ui/src/views/NotFound/page.tsx index c4981effee3..890c84f05f0 100644 --- a/packages/ui/src/views/NotFound/page.tsx +++ b/packages/ui/src/views/NotFound/page.tsx @@ -6,7 +6,7 @@ import { applyLocaleFiltering, formatAdminURL } from 'payload/shared' import * as qs from 'qs-esm' import React from 'react' -import type { GetAdminContextFn } from '../Root/index.js' +import type { CreateAdminContextFn } from '../Root/index.js' /* eslint-disable payload/no-imports-from-exports-dir -- Server component must reference exports/client bundle for proper client boundary in prod builds */ import { NotFoundClient, PageConfigProvider } from '../../exports/client/index.js' @@ -18,7 +18,7 @@ import { getVisibleEntities } from '../../utilities/getVisibleEntities.js' export type RenderNotFoundPageArgs = { config: Promise - getAdminContext: GetAdminContextFn + createAdminContext: CreateAdminContextFn importMap: ImportMap params: Promise<{ segments: string[] }> searchParams: Promise<{ [key: string]: string | string[] }> @@ -26,7 +26,7 @@ export type RenderNotFoundPageArgs = { export const renderNotFoundPage = async ({ config: configPromise, - getAdminContext, + createAdminContext, importMap, params: paramsPromise, searchParams: searchParamsPromise, @@ -43,7 +43,7 @@ export const renderNotFoundPage = async ({ req, req: { payload }, user, - } = await getAdminContext({ + } = await createAdminContext({ configPromise: config, importMap, key: 'RootLayout', diff --git a/packages/ui/src/views/Root/index.tsx b/packages/ui/src/views/Root/index.tsx index 14c51566e5c..07265a7b548 100644 --- a/packages/ui/src/views/Root/index.tsx +++ b/packages/ui/src/views/Root/index.tsx @@ -5,8 +5,8 @@ import type { AdminViewClientProps, AdminViewServerPropsOnly, CollectionPreferences, - createPayloadReq, - GetAdminContextResult, + createPayloadRequest, + CreateAdminContextResult, ImportMap, SanitizedCollectionConfig, SanitizedConfig, @@ -31,18 +31,18 @@ import { isPublicAdminRoute } from '../../utilities/isPublicAdminRoute.js' import { getCustomViewByRoute } from './getCustomViewByRoute.js' import { getRouteData } from './getRouteData.js' -export type GetAdminContextFn = (args: { +export type CreateAdminContextFn = (args: { canSetHeaders?: boolean configPromise: Promise | SanitizedConfig importMap: ImportMap key: string - overrides?: Omit[0], 'payload'> -}) => Promise + overrides?: Omit[0], 'payload'> +}) => Promise export type RenderRootArgs = { adminViews: AdminViewAdapter config: Promise - getAdminContext: GetAdminContextFn + createAdminContext: CreateAdminContextFn importMap: ImportMap /** * Optional React `key` applied to the rendered view (not the surrounding admin @@ -66,7 +66,7 @@ export type RenderRootArgs = { export const renderRoot = async ({ adminViews, config: configPromise, - getAdminContext, + createAdminContext, importMap, key, notFound, @@ -154,7 +154,7 @@ export const renderRoot = async ({ req, req: { payload }, user, - } = await getAdminContext({ + } = await createAdminContext({ configPromise: config, importMap, key: 'initPage', diff --git a/templates/ecommerce/src/app/(app)/next/seed/route.ts b/templates/ecommerce/src/app/(app)/next/seed/route.ts index 1c2165446bb..fec6a777d2b 100644 --- a/templates/ecommerce/src/app/(app)/next/seed/route.ts +++ b/templates/ecommerce/src/app/(app)/next/seed/route.ts @@ -1,4 +1,4 @@ -import { createPayloadReq, getPayload } from 'payload' +import { createPayloadRequest, getPayload } from 'payload' import { seed } from '@/endpoints/seed' import config from '@payload-config' import { headers } from 'next/headers' @@ -21,7 +21,7 @@ export async function POST(): Promise { try { // Create a Payload request object to pass to the Local API for transactions // At this point you should pass in a user, locale, and any other context you need for the Local API - const payloadReq = await createPayloadReq({ payload, user }) + const payloadReq = await createPayloadRequest({ payload, user }) await seed({ payload, req: payloadReq }) diff --git a/templates/plugin/dev/int.spec.ts b/templates/plugin/dev/int.spec.ts index 15a1100dc27..8b3a13b1b57 100644 --- a/templates/plugin/dev/int.spec.ts +++ b/templates/plugin/dev/int.spec.ts @@ -1,7 +1,7 @@ import type { Payload } from 'payload' import config from '@payload-config' -import { createPayloadReqFromWebRequest, getPayload } from 'payload' +import { createPayloadRequestFromWebRequest, getPayload } from 'payload' import { afterAll, beforeAll, describe, expect, test } from 'vitest' import { customEndpointHandler } from '../src/endpoints/customEndpointHandler.js' @@ -22,7 +22,7 @@ describe('Plugin integration tests', () => { method: 'GET', }) - const payloadRequest = await createPayloadReqFromWebRequest({ config, request }) + const payloadRequest = await createPayloadRequestFromWebRequest({ config, request }) const response = await customEndpointHandler(payloadRequest) expect(response.status).toBe(200) diff --git a/templates/website/src/app/(frontend)/next/seed/route.ts b/templates/website/src/app/(frontend)/next/seed/route.ts index 48d313eb556..550671d9122 100644 --- a/templates/website/src/app/(frontend)/next/seed/route.ts +++ b/templates/website/src/app/(frontend)/next/seed/route.ts @@ -1,4 +1,4 @@ -import { createPayloadReq, getPayload } from 'payload' +import { createPayloadRequest, getPayload } from 'payload' import { seed } from '@/endpoints/seed' import config from '@payload-config' import { headers } from 'next/headers' @@ -19,7 +19,7 @@ export async function POST(): Promise { try { // Create a Payload request object to pass to the Local API for transactions // At this point you should pass in a user, locale, and any other context you need for the Local API - const payloadReq = await createPayloadReq({ payload, user }) + const payloadReq = await createPayloadRequest({ payload, user }) await seed({ payload, req: payloadReq }) diff --git a/templates/with-vercel-website/src/app/(frontend)/next/seed/route.ts b/templates/with-vercel-website/src/app/(frontend)/next/seed/route.ts index 48d313eb556..550671d9122 100644 --- a/templates/with-vercel-website/src/app/(frontend)/next/seed/route.ts +++ b/templates/with-vercel-website/src/app/(frontend)/next/seed/route.ts @@ -1,4 +1,4 @@ -import { createPayloadReq, getPayload } from 'payload' +import { createPayloadRequest, getPayload } from 'payload' import { seed } from '@/endpoints/seed' import config from '@payload-config' import { headers } from 'next/headers' @@ -19,7 +19,7 @@ export async function POST(): Promise { try { // Create a Payload request object to pass to the Local API for transactions // At this point you should pass in a user, locale, and any other context you need for the Local API - const payloadReq = await createPayloadReq({ payload, user }) + const payloadReq = await createPayloadRequest({ payload, user }) await seed({ payload, req: payloadReq }) diff --git a/test/access-control/int.spec.ts b/test/access-control/int.spec.ts index c4634ed3569..a4e6cb80b90 100644 --- a/test/access-control/int.spec.ts +++ b/test/access-control/int.spec.ts @@ -7,7 +7,7 @@ import type { RequiredDataFromCollectionSlug, } from 'payload' -import { AuthenticationError, createPayloadReq, Forbidden } from 'payload' +import { AuthenticationError, createPayloadRequest, Forbidden } from 'payload' import { getEntityPermissions } from 'payload/internal' import { expect, vitest } from 'vitest' @@ -2017,7 +2017,7 @@ test.suite('Access Control', { config: './config.ts', resetBetweenTests: false } entityType: 'collection', fetchData: true, operations: ['read', 'readVersions'], - req: await createPayloadReq({ payload }), + req: await createPayloadRequest({ payload }), }) expect(permissions.read?.permission).toBe(false) @@ -2306,7 +2306,7 @@ test.suite('Access Control', { config: './config.ts', resetBetweenTests: false } }) test('should evaluate inherited global read version permissions against versions', async () => { - const req = await createPayloadReq({ payload }) + const req = await createPayloadRequest({ payload }) await payload.db.deleteVersions({ globalSlug: inheritedReadVersionsGlobalSlug, @@ -2398,7 +2398,7 @@ test.suite('Access Control', { config: './config.ts', resetBetweenTests: false } }) test('should sanitize virtual-field constraints from inherited global read access', async () => { - const req = await createPayloadReq({ payload }) + const req = await createPayloadRequest({ payload }) await payload.db.deleteVersions({ globalSlug: inheritedReadVersionsVirtualGlobalSlug, @@ -2543,7 +2543,7 @@ test.suite('Access Control', { config: './config.ts', resetBetweenTests: false } overrideAccess: true, }) - const req = await createPayloadReq({ + const req = await createPayloadRequest({ payload, user: { id: 123 as any, @@ -2611,7 +2611,7 @@ test.suite('Access Control', { config: './config.ts', resetBetweenTests: false } }) // Create non-admin user request - const nonAdminReq = await createPayloadReq({ + const nonAdminReq = await createPayloadRequest({ payload, user: { id: 456 as any, diff --git a/test/access-control/postgres-logs.int.spec.ts b/test/access-control/postgres-logs.int.spec.ts index d5444f72ac3..cda292657e8 100644 --- a/test/access-control/postgres-logs.int.spec.ts +++ b/test/access-control/postgres-logs.int.spec.ts @@ -1,6 +1,6 @@ import type { CollectionPermission, PayloadRequest } from 'payload' -import { createPayloadReq } from 'payload' +import { createPayloadRequest } from 'payload' import { getEntityPermissions } from 'payload/internal' import { expect, vitest } from 'vitest' @@ -14,7 +14,7 @@ test.suite( { config: './config.postgreslogs.ts', db: (adapter) => adapter.startsWith('postgres') }, () => { test.beforeEach(async ({ payload }) => { - req = await createPayloadReq({ + req = await createPayloadRequest({ payload, user: { id: 123 as any, diff --git a/test/auth/api-key/int.spec.ts b/test/auth/api-key/int.spec.ts index b06713895af..f6d4d483d4d 100644 --- a/test/auth/api-key/int.spec.ts +++ b/test/auth/api-key/int.spec.ts @@ -1,4 +1,4 @@ -import { createPayloadReqFromWebRequest } from 'payload' +import { createPayloadRequestFromWebRequest } from 'payload' import { v4 as uuid } from 'uuid' import { expect } from 'vitest' @@ -89,7 +89,7 @@ test.suite('API key authentication', { config: './config.ts' }, () => { overrideAccess: true, }) - const req = await createPayloadReqFromWebRequest({ + const req = await createPayloadRequestFromWebRequest({ config: payload.config, request: new Request( 'http://localhost/api/auth-read-hook?locale=fr&fallbackLocale=none&depth=2', @@ -100,7 +100,7 @@ test.suite('API key authentication', { config: './config.ts' }, () => { }, ), }) - const graphQLReq = await createPayloadReqFromWebRequest({ + const graphQLReq = await createPayloadRequestFromWebRequest({ config: payload.config, request: new Request('http://localhost/api/graphql', { headers: { @@ -270,7 +270,7 @@ test.suite('API key authentication', { config: './config.ts' }, () => { overrideAccess: true, }) ).token! - const req = await createPayloadReqFromWebRequest({ + const req = await createPayloadRequestFromWebRequest({ config: payload.config, request: new Request('http://localhost/api', { headers: { Authorization: `JWT ${token}` }, @@ -284,7 +284,7 @@ test.suite('API key authentication', { config: './config.ts' }, () => { expect('access' in apiKeyField && apiKeyField.access?.update).toBeTypeOf('function') expect(await apiKeyField.access!.update!({ id: target.id, req } as never)).toBe(true) - const unauthenticatedReq = await createPayloadReqFromWebRequest({ + const unauthenticatedReq = await createPayloadRequestFromWebRequest({ config: payload.config, request: new Request('http://localhost/api'), }) diff --git a/test/auth/int.spec.ts b/test/auth/int.spec.ts index 03d1ad3327b..17bad5c6273 100644 --- a/test/auth/int.spec.ts +++ b/test/auth/int.spec.ts @@ -11,7 +11,7 @@ import type { import crypto from 'crypto' import { jwtDecode } from 'jwt-decode' import { - createPayloadReq, + createPayloadRequest, Forbidden, getFieldsToSign, refreshOperation, @@ -2814,7 +2814,7 @@ test.suite('Auth', { config: './config.ts', resetBetweenTests: false }, () => { await setLoginAttemptLock({ id: publicUser.id, collection: publicUsersSlug, payload }) - const req = await createPayloadReq({ payload, user: adminUser }) + const req = await createPayloadRequest({ payload, user: adminUser }) await payload.unlock({ collection: publicUsersSlug, @@ -2849,7 +2849,7 @@ test.suite('Auth', { config: './config.ts', resetBetweenTests: false }, () => { await setLoginAttemptLock({ id: selectedUser.id, collection: publicUsersSlug, payload }) - const req = await createPayloadReq({ payload, user: currentUser }) + const req = await createPayloadRequest({ payload, user: currentUser }) await expect( payload.unlock({ @@ -3095,7 +3095,7 @@ test.suite('Auth', { config: './config.ts', resetBetweenTests: false }, () => { Authorization: `JWT ${authenticated.token}`, }, }) - const req = await createPayloadReq({ + const req = await createPayloadRequest({ payload, user: { ...authenticated.user, diff --git a/test/auth/jwt/int.spec.ts b/test/auth/jwt/int.spec.ts index 6d2b57b2a0b..1c87098dd37 100644 --- a/test/auth/jwt/int.spec.ts +++ b/test/auth/jwt/int.spec.ts @@ -1,7 +1,7 @@ import type { CollectionRefreshHook, Payload } from 'payload' import { jwtDecode } from 'jwt-decode' -import { createPayloadReqFromWebRequest } from 'payload' +import { createPayloadRequestFromWebRequest } from 'payload' import { v4 as uuid } from 'uuid' import { expect } from 'vitest' @@ -129,14 +129,14 @@ test.suite( overrideAccess: true, }) const headers = { Authorization: `JWT ${token}` } - const req = await createPayloadReqFromWebRequest({ + const req = await createPayloadRequestFromWebRequest({ config: payload.config, request: new Request( 'http://localhost/api/auth-read-hook?locale=fr&fallbackLocale=none&depth=2', { headers }, ), }) - const graphQLReq = await createPayloadReqFromWebRequest({ + const graphQLReq = await createPayloadRequestFromWebRequest({ config: payload.config, request: new Request('http://localhost/api/graphql', { headers }), }) diff --git a/test/base-access/int.spec.ts b/test/base-access/int.spec.ts index 68169e98f52..e277fe6301a 100644 --- a/test/base-access/int.spec.ts +++ b/test/base-access/int.spec.ts @@ -1,4 +1,4 @@ -import { createPayloadReq, Forbidden, getAccessResults } from 'payload' +import { createPayloadRequest, Forbidden, getAccessResults } from 'payload' import { expect } from 'vitest' import { test } from '../__helpers/int/vitest.js' @@ -9,9 +9,9 @@ const createRequest = async ({ payload, }: { headers: Record - payload: Parameters[0]['payload'] + payload: Parameters[0]['payload'] }) => - createPayloadReq({ + createPayloadRequest({ payload, req: { headers: new Headers(headers), diff --git a/test/dataloader/int.spec.ts b/test/dataloader/int.spec.ts index ff02fba228c..e404d494bb6 100644 --- a/test/dataloader/int.spec.ts +++ b/test/dataloader/int.spec.ts @@ -1,7 +1,7 @@ import type { CollectionSlug } from 'payload' import { buildDefaultEditorState } from '@payloadcms/richtext-lexical' -import { createPayloadReq } from 'payload' +import { createPayloadRequest } from 'payload' import { fileURLToPath } from 'url' import { expect, vitest } from 'vitest' @@ -161,7 +161,7 @@ test.suite('dataloader', { config: './config.ts' }, () => { test.describe('find', () => { test('should call the same query only once in a request', async ({ payload }) => { - const req = await createPayloadReq({ payload }) + const req = await createPayloadRequest({ payload }) const spy = vitest.spyOn(payload, 'find') const findArgs = { diff --git a/test/field-access-context/int.spec.ts b/test/field-access-context/int.spec.ts index 1d50da4c21a..9126c03d1d5 100644 --- a/test/field-access-context/int.spec.ts +++ b/test/field-access-context/int.spec.ts @@ -1,4 +1,4 @@ -import { createPayloadReq } from 'payload' +import { createPayloadRequest } from 'payload' import { fileURLToPath } from 'url' import { expect } from 'vitest' @@ -269,7 +269,7 @@ test.suite('field access collection context', { config: './config.ts' }, () => { }) => { // payload.auth() calls getEntityPermissions for all registered collections, // which calls populateFieldPermissions → field.access[operation] for each field. - const req = await createPayloadReq({ payload }) + const req = await createPayloadRequest({ payload }) await payload.auth({ headers: new Headers(), diff --git a/test/fields/int.spec.ts b/test/fields/int.spec.ts index 7a041885ae8..0e24bbe1e09 100644 --- a/test/fields/int.spec.ts +++ b/test/fields/int.spec.ts @@ -3,7 +3,7 @@ import type { IndexDirection, IndexOptions } from 'mongoose' import type { ValidationError } from 'payload' import { slugifyHandler } from '@payloadcms/ui/utilities/slugify' -import { createPayloadReq, reload } from 'payload' +import { createPayloadRequest, reload } from 'payload' import { fileURLToPath } from 'url' import { expect } from 'vitest' @@ -520,7 +520,7 @@ test.suite('Fields', { config: './config.ts', resetBetweenTests: false }, () => const takenSlug = taken.localizedSlug as string expect(takenSlug).toMatch(/^slug-field-\d+$/) - const req = await createPayloadReq({ payload, user: user.user }) + const req = await createPayloadRequest({ payload, user: user.user }) const regenerated = await slugifyHandler({ collectionSlug: 'slug-fields', @@ -546,7 +546,7 @@ test.suite('Fields', { config: './config.ts', resetBetweenTests: false }, () => created.push(first.id) expect(first.slug).toBe('shared-regen') - const req = await createPayloadReq({ payload, user: user.user }) + const req = await createPayloadRequest({ payload, user: user.user }) // Regenerating from a source that slugifies to a taken slug must bump it, not hand back the // duplicate — otherwise the next save throws a uniqueness error. @@ -572,7 +572,7 @@ test.suite('Fields', { config: './config.ts', resetBetweenTests: false }, () => created.push(doc.id) expect(doc.slug).toBe('own-slug') - const req = await createPayloadReq({ payload, user: user.user }) + const req = await createPayloadRequest({ payload, user: user.user }) // Excluding the current doc means regenerating its own unchanged source reuses the value rather // than bumping past test. @@ -1004,7 +1004,7 @@ test.suite('Fields', { config: './config.ts', resetBetweenTests: false }, () => }) created.push(hidden.id) - const req = await createPayloadReq({ payload, user: user.user }) + const req = await createPayloadRequest({ payload, user: user.user }) const createdWithAccess = await payload.create({ collection, data: { title: 'Shared title' }, @@ -1031,7 +1031,7 @@ test.suite('Fields', { config: './config.ts', resetBetweenTests: false }, () => }) created.push(hidden.id, editable.id) - const req = await createPayloadReq({ payload, user: user.user }) + const req = await createPayloadRequest({ payload, user: user.user }) const updated = await payload.update({ collection, id: editable.id, @@ -1052,7 +1052,7 @@ test.suite('Fields', { config: './config.ts', resetBetweenTests: false }, () => }) created.push(hidden.id) - const req = await createPayloadReq({ payload, user: user.user }) + const req = await createPayloadRequest({ payload, user: user.user }) const createdWithAccess = await payload.create({ collection, data: { localizedTitle: 'Localized title', title: 'Visible' }, @@ -1089,7 +1089,7 @@ test.suite('Fields', { config: './config.ts', resetBetweenTests: false }, () => }) created.push(hidden.id) - const req = await createPayloadReq({ payload, user: user.user }) + const req = await createPayloadRequest({ payload, user: user.user }) const createdWithAccess = await payload.create({ collection, data: { title: 'Visible' }, diff --git a/test/form-state/int.spec.ts b/test/form-state/int.spec.ts index cdd8ae1ea6c..34b298eec6d 100644 --- a/test/form-state/int.spec.ts +++ b/test/form-state/int.spec.ts @@ -1,7 +1,7 @@ import type { FieldState, FormState, User } from 'payload' import { buildFormState } from '@payloadcms/ui/utilities/buildFormState' -import { createPayloadReq, getAccessResults } from 'payload' +import { createPayloadRequest, getAccessResults } from 'payload' import React from 'react' import { fileURLToPath } from 'url' import { expect, vi } from 'vitest' @@ -59,7 +59,7 @@ test.suite('Form State', { config: './config.ts', resetBetweenTests: false }, () overrideAccess: true, }) - const req = await createPayloadReq({ payload, user: editor }) + const req = await createPayloadRequest({ payload, user: editor }) const permissions = await getAccessResults({ req }) const restrictedValue = 'client supplied' const title = 'Access filtered autosave' @@ -98,7 +98,7 @@ test.suite('Form State', { config: './config.ts', resetBetweenTests: false }, () }) test('should build entire form state', async ({ payload }) => { - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) const postData = await payload.create({ collection: postsSlug, @@ -157,7 +157,7 @@ test.suite('Form State', { config: './config.ts', resetBetweenTests: false }, () test('should use `select` to build partial form state with only specified fields', async ({ payload, }) => { - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) const postData = await payload.create({ collection: postsSlug, @@ -205,7 +205,7 @@ test.suite('Form State', { config: './config.ts', resetBetweenTests: false }, () test('should not render custom components when `lastRenderedPath` exists', async ({ payload, }) => { - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) const { state: stateWithRow } = await buildFormState({ collectionSlug: postsSlug, @@ -290,7 +290,7 @@ test.suite('Form State', { config: './config.ts', resetBetweenTests: false }, () test('should not render custom Field components for fields hidden by admin.condition', async ({ payload, }) => { - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) const hiddenDoc = await payload.create({ collection: conditionsSlug, @@ -356,7 +356,7 @@ test.suite('Form State', { config: './config.ts', resetBetweenTests: false }, () test('should preserve values of fields nested inside a row hidden by admin.condition', async ({ payload, }) => { - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) const hiddenDoc = await payload.create({ collection: conditionsSlug, @@ -396,7 +396,7 @@ test.suite('Form State', { config: './config.ts', resetBetweenTests: false }, () test('should preserve values of fields nested inside a collapsible hidden by admin.condition', async ({ payload, }) => { - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) const hiddenDoc = await payload.create({ collection: conditionsSlug, @@ -433,7 +433,7 @@ test.suite('Form State', { config: './config.ts', resetBetweenTests: false }, () test('should render custom Field component when admin.condition flips from false to true via onChange', async ({ payload, }) => { - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) const doc = await payload.create({ collection: conditionsSlug, @@ -491,7 +491,7 @@ test.suite('Form State', { config: './config.ts', resetBetweenTests: false }, () test('should add `addedByServer` flag to fields that originate on the server', async ({ payload, }) => { - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) const postData = await payload.create({ collection: postsSlug, @@ -1370,7 +1370,7 @@ test.suite('Form State', { config: './config.ts', resetBetweenTests: false }, () }) test('should set rows to empty array for empty array fields', async ({ payload }) => { - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) // Create a document with an empty array const postData = await payload.create({ @@ -1412,7 +1412,7 @@ test.suite('Form State', { config: './config.ts', resetBetweenTests: false }, () test('should resolve a promise-returning `filterOptions` on a select field into `selectFilterOptions`', async ({ payload, }) => { - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) const postData = await payload.create({ collection: postsSlug, diff --git a/test/localization/int.spec.ts b/test/localization/int.spec.ts index fb715f6e96a..e6a03e41aa4 100644 --- a/test/localization/int.spec.ts +++ b/test/localization/int.spec.ts @@ -1,6 +1,6 @@ import type { Payload, User, Where } from 'payload' -import { createPayloadReq } from 'payload' +import { createPayloadRequest } from 'payload' import { fileURLToPath } from 'url' import { expect } from 'vitest' @@ -3304,7 +3304,7 @@ test.suite('Localization', { config: './config.ts', resetBetweenTests: false }, overrideAccess: true, }) - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) const res = (await copyDataFromLocaleHandler({ collectionSlug: 'localized-posts', @@ -3343,7 +3343,7 @@ test.suite('Localization', { config: './config.ts', resetBetweenTests: false }, overrideAccess: true, }) - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) const res = (await copyDataFromLocaleHandler({ collectionSlug: 'blocks-fields', @@ -3374,7 +3374,7 @@ test.suite('Localization', { config: './config.ts', resetBetweenTests: false }, overrideAccess: true, }) - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) const res = (await copyDataFromLocaleHandler({ collectionSlug: 'blocks-fields', docID: doc.id, @@ -3401,7 +3401,7 @@ test.suite('Localization', { config: './config.ts', resetBetweenTests: false }, overrideAccess: true, }) - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) const res = (await copyDataFromLocaleHandler({ collectionSlug: 'nested', @@ -3440,7 +3440,7 @@ test.suite('Localization', { config: './config.ts', resetBetweenTests: false }, overrideAccess: true, }) - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) const res = (await copyDataFromLocaleHandler({ collectionSlug: 'nested', @@ -3483,7 +3483,7 @@ test.suite('Localization', { config: './config.ts', resetBetweenTests: false }, }) try { - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) const res = (await copyDataFromLocaleHandler({ collectionSlug: arrayCollectionSlug, @@ -3561,7 +3561,7 @@ test.suite('Localization', { config: './config.ts', resetBetweenTests: false }, expect(enDocBefore.content?.[0]?.text).toBe('English block text') // Copy data from en to es - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) await copyDataFromLocaleHandler({ collectionSlug: 'blocks-fields', @@ -3629,7 +3629,7 @@ test.suite('Localization', { config: './config.ts', resetBetweenTests: false }, expect(draftBefore.title).toBe('Draft English Title') // Copy draft data to another locale - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) await copyDataFromLocaleHandler({ collectionSlug: 'blocks-fields', @@ -3697,7 +3697,7 @@ test.suite('Localization', { config: './config.ts', resetBetweenTests: false }, expect(enDraftBefore.title).toBe('Draft EN') // Copy to another locale using the actual handler - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) await copyDataFromLocaleHandler({ collectionSlug: 'blocks-fields', diff --git a/test/queues/int.spec.ts b/test/queues/int.spec.ts index e59ff9c2246..65813b22301 100644 --- a/test/queues/int.spec.ts +++ b/test/queues/int.spec.ts @@ -3,7 +3,7 @@ import path from 'path' import { _internal_jobSystemGlobals, _internal_resetJobSystemGlobals, - createPayloadReq, + createPayloadRequest, Forbidden, type JobTaskStatus, type Payload, @@ -88,7 +88,7 @@ test.suite('Queues - Payload', { config: './config.ts' }, () => { test.describe('access control', () => { test('should deny raw job creation when access control is enabled', async ({ payload }) => { - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) await expect( payload.create({ @@ -106,7 +106,7 @@ test.suite('Queues - Payload', { config: './config.ts' }, () => { }) test('should deny raw job reads when access control is enabled', async ({ payload }) => { - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) const job = await payload.jobs.queue({ task: 'CreateSimple', input: { @@ -126,7 +126,7 @@ test.suite('Queues - Payload', { config: './config.ts' }, () => { }) test('should deny raw job updates when access control is enabled', async ({ payload }) => { - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) const job = await payload.jobs.queue({ task: 'CreateSimple', input: { @@ -159,7 +159,7 @@ test.suite('Queues - Payload', { config: './config.ts' }, () => { }) test('should deny raw job deletion when access control is enabled', async ({ payload }) => { - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) const job = await payload.jobs.queue({ task: 'CreateSimple', input: { @@ -218,7 +218,7 @@ test.suite('Queues - Payload', { config: './config.ts' }, () => { test('will pass access control on local api .queue when passing overrideAccess: false', async ({ payload, }) => { - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) const result = await payload.jobs.queue({ task: 'CreateSimple', input: { @@ -239,7 +239,7 @@ test.suite('Queues - Payload', { config: './config.ts' }, () => { test('will pass access control on local api .run when passing overrideAccess: false', async ({ payload, }) => { - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) const result = await payload.jobs.run({ overrideAccess: false, req, @@ -259,7 +259,7 @@ test.suite('Queues - Payload', { config: './config.ts' }, () => { test('will pass access control on local api .runByID when passing overrideAccess: false', async ({ payload, }) => { - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) // Queue a job first so we have a valid ID const job = await payload.jobs.queue({ @@ -319,7 +319,7 @@ test.suite('Queues - Payload', { config: './config.ts' }, () => { }) => { payload.config.jobs.deleteJobOnComplete = false - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) // Queue a job without running it const job = await payload.jobs.queue({ @@ -387,7 +387,7 @@ test.suite('Queues - Payload', { config: './config.ts' }, () => { }) => { payload.config.jobs.deleteJobOnComplete = false - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) // Queue a job without running it const job = await payload.jobs.queue({ @@ -1758,7 +1758,7 @@ test.suite('Queues - Payload', { config: './config.ts' }, () => { overrideAccess: true, }) - const _req = await createPayloadReq({ payload }) + const _req = await createPayloadRequest({ payload }) const t1Req = isolateObjectProperty(_req, 'transactionID') delete t1Req.transactionID diff --git a/test/queues/utilities.ts b/test/queues/utilities.ts index 9be7e627e89..472a5663e44 100644 --- a/test/queues/utilities.ts +++ b/test/queues/utilities.ts @@ -1,7 +1,7 @@ import { _internal_jobSystemGlobals, countRunnableOrActiveJobsForQueue, - createPayloadReq, + createPayloadRequest, type Payload, } from 'payload' @@ -14,7 +14,7 @@ export async function waitUntilAutorunIsDone({ payload: Payload queue: string }): Promise { - const req = await createPayloadReq({ payload }) + const req = await createPayloadRequest({ payload }) return new Promise((resolve) => { const interval = setInterval(async () => { diff --git a/test/uploads/int.spec.ts b/test/uploads/int.spec.ts index 0c3019e4079..e9c8d5798b9 100644 --- a/test/uploads/int.spec.ts +++ b/test/uploads/int.spec.ts @@ -6,7 +6,11 @@ import fs from 'fs' import { createServer } from 'http' import os from 'os' import path from 'path' -import { _internal_safeFetchGlobal, createPayloadReqFromWebRequest, getFileByPath } from 'payload' +import { + _internal_safeFetchGlobal, + createPayloadRequestFromWebRequest, + getFileByPath, +} from 'payload' import { fileURLToPath } from 'url' import { promisify } from 'util' import { expect, vitest } from 'vitest' @@ -2184,7 +2188,7 @@ test.suite('Collections - Uploads', { config: './config.ts', resetBetweenTests: const configuredOrigin = `http://localhost:${configuredPort}` const alternateOrigin = `http://localhost:${alternatePort}` - const req = await createPayloadReqFromWebRequest({ + const req = await createPayloadRequestFromWebRequest({ config: payload.config, request: new Request(configuredOrigin, { headers: new Headers({ @@ -2229,7 +2233,7 @@ test.suite('Collections - Uploads', { config: './config.ts', resetBetweenTests: const port = (server.address() as AddressInfo).port const requestOrigin = `http://localhost:${port}` - const req = await createPayloadReqFromWebRequest({ + const req = await createPayloadRequestFromWebRequest({ config: payload.config, request: new Request(requestOrigin, { headers: new Headers({ @@ -2306,7 +2310,7 @@ test.suite('Collections - Uploads', { config: './config.ts', resetBetweenTests: const configuredPort = (configuredServer.address() as AddressInfo).port const configuredOrigin = `http://localhost:${configuredPort}` - const req = await createPayloadReqFromWebRequest({ + const req = await createPayloadRequestFromWebRequest({ config: payload.config, request: new Request(configuredOrigin, { headers: new Headers({ cookie: testCookies, origin: configuredOrigin }), @@ -2367,7 +2371,7 @@ test.suite('Collections - Uploads', { config: './config.ts', resetBetweenTests: status: 200, }), ) - const req = await createPayloadReqFromWebRequest({ + const req = await createPayloadRequestFromWebRequest({ config: payload.config, request: new Request('https://app.example.com', { headers: new Headers({ cookie: 'payload-token=123; other-cookie=456' }), @@ -2398,7 +2402,7 @@ test.suite('Collections - Uploads', { config: './config.ts', resetBetweenTests: test.for(['http://[', 'ftp://files.example.com/image.png', 'data:text/plain,image'])( 'should reject unsupported file URL %s', async (url, { payload }) => { - const req = await createPayloadReqFromWebRequest({ + const req = await createPayloadRequestFromWebRequest({ config: payload.config, request: new Request('https://app.example.com'), }) @@ -2465,7 +2469,7 @@ test.suite('Collections - Uploads', { config: './config.ts', resetBetweenTests: }) await new Promise((res) => configuredServer.listen(0, undefined, undefined, res)) const configuredOrigin = `http://localhost:${(configuredServer.address() as AddressInfo).port}` - const req = await createPayloadReqFromWebRequest({ + const req = await createPayloadRequestFromWebRequest({ config: payload.config, request: new Request(configuredOrigin, { headers: new Headers({ cookie: 'payload-token=123; other-cookie=456' }), diff --git a/test/versions/int.spec.ts b/test/versions/int.spec.ts index 64d86f424d2..fcc53fb518f 100644 --- a/test/versions/int.spec.ts +++ b/test/versions/int.spec.ts @@ -6,7 +6,13 @@ import { } from '@payloadcms/ui/utilities/schedulePublishHandler' import fs from 'fs' import path from 'path' -import { createPayloadReq, Forbidden, getFileByPath, saveVersion, ValidationError } from 'payload' +import { + createPayloadRequest, + Forbidden, + getFileByPath, + saveVersion, + ValidationError, +} from 'payload' import { wait } from 'payload/shared' import * as qs from 'qs-esm' import { fileURLToPath } from 'url' @@ -4725,7 +4731,7 @@ test.suite('Versions', { config: './config.ts', resetBetweenTests: false }, () = overrideAccess: true, }) - const req = await createPayloadReq({ payload, user: secondaryAdminUser }) + const req = await createPayloadRequest({ payload, user: secondaryAdminUser }) const currentDate = new Date() await schedulePublishHandler({ @@ -4786,7 +4792,7 @@ test.suite('Versions', { config: './config.ts', resetBetweenTests: false }, () = overrideAccess: true, }) - const req = await createPayloadReq({ payload, user: secondaryAdminUser }) + const req = await createPayloadRequest({ payload, user: secondaryAdminUser }) const currentDate = new Date() await schedulePublishHandler({ @@ -5186,7 +5192,7 @@ test.suite('Versions', { config: './config.ts', resetBetweenTests: false }, () = // Create a request without a user (simulating unauthenticated request) // Access control on draftGlobalSlug requires published status when no user - const req = await createPayloadReq({ payload }) + const req = await createPayloadRequest({ payload }) req.user = null const result = await payload.findGlobal({ @@ -5227,7 +5233,7 @@ test.suite('Versions', { config: './config.ts', resetBetweenTests: false }, () = test('should create using schedule-publish', async ({ payload }) => { const currentDate = new Date() - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) // use server action to create the event await schedulePublishHandler({ @@ -5260,7 +5266,7 @@ test.suite('Versions', { config: './config.ts', resetBetweenTests: false }, () = test('should get upcoming scheduled publish events without reading the jobs collection', async ({ payload, }) => { - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) await schedulePublishHandler({ type: 'publish', @@ -5293,7 +5299,7 @@ test.suite('Versions', { config: './config.ts', resetBetweenTests: false }, () = test('should not get scheduled publish events without publish permission', async ({ payload, }) => { - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) await payload.update({ id: draftDoc.id, @@ -5316,7 +5322,7 @@ test.suite('Versions', { config: './config.ts', resetBetweenTests: false }, () = test('should delete using schedule-publish', async ({ payload }) => { const currentDate = new Date() - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) // use server action to create the event await schedulePublishHandler({ @@ -5373,7 +5379,7 @@ test.suite('Versions', { config: './config.ts', resetBetweenTests: false }, () = }) test('should not delete a job that is not a scheduled publish', async ({ payload }) => { - const req = await createPayloadReq({ payload, user }) + const req = await createPayloadRequest({ payload, user }) const unrelatedJob = await payload.db.create({ collection: 'payload-jobs', data: { From 47b0a6f897d66c6dab3e56b8292750b94d216df0 Mon Sep 17 00:00:00 2001 From: Jake Fletcher Date: Thu, 24 Sep 2026 14:28:51 -0400 Subject: [PATCH 12/15] chore: sort renamed request symbols --- packages/next/src/adapters/layout.tsx | 4 ++-- packages/payload/src/admin/types.ts | 2 +- packages/ui/src/layouts/Root/index.tsx | 4 ++-- packages/ui/src/utilities/handleServerFunctions.ts | 2 +- packages/ui/src/views/Root/index.tsx | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/next/src/adapters/layout.tsx b/packages/next/src/adapters/layout.tsx index 171112e6918..c9a97b4b7f5 100644 --- a/packages/next/src/adapters/layout.tsx +++ b/packages/next/src/adapters/layout.tsx @@ -34,18 +34,18 @@ export { metadata } from '@payloadcms/ui/layouts' type Props = Omit< React.ComponentProps, - 'additionalDependencyChecks' | 'fonts' | 'createAdminContext' | 'RouterAdapter' + 'additionalDependencyChecks' | 'createAdminContext' | 'fonts' | 'RouterAdapter' > export const RootLayout = (props: Props) => ( ) diff --git a/packages/payload/src/admin/types.ts b/packages/payload/src/admin/types.ts index 88f6ad2ccb2..31a56717472 100644 --- a/packages/payload/src/admin/types.ts +++ b/packages/payload/src/admin/types.ts @@ -590,8 +590,8 @@ export type DocumentSlots = { export type { BuildTableStateArgs, - DefaultServerFunctionArgs, CreateAdminContextResult, + DefaultServerFunctionArgs, ListQuery, ServerFunction, ServerFunctionArgs, diff --git a/packages/ui/src/layouts/Root/index.tsx b/packages/ui/src/layouts/Root/index.tsx index 08469fdb12b..458570ceea7 100644 --- a/packages/ui/src/layouts/Root/index.tsx +++ b/packages/ui/src/layouts/Root/index.tsx @@ -39,13 +39,13 @@ type RootLayoutProps = { readonly additionalDependencyChecks?: CheckDependenciesArgs readonly children: React.ReactNode readonly config: Promise + readonly createAdminContext: CreateAdminContextFn /** * Fonts to apply to the admin `` element. Each entry's * `variable ?? className` is appended to the `` class list. * Framework-specific font loaders (e.g. `next/font`) are supplied by the caller. */ readonly fonts?: Font[] - readonly createAdminContext: CreateAdminContextFn /** * Custom content to render inside the admin panel's `` element. * @@ -88,8 +88,8 @@ export const RootLayout = (props: RootLayoutProps) => { const RootLayoutContent = async ({ children, config: configPromise, - fonts = [], createAdminContext, + fonts = [], head: headFromProps, htmlProps = {}, importMap, diff --git a/packages/ui/src/utilities/handleServerFunctions.ts b/packages/ui/src/utilities/handleServerFunctions.ts index 32fd0d007d2..686167a04e9 100644 --- a/packages/ui/src/utilities/handleServerFunctions.ts +++ b/packages/ui/src/utilities/handleServerFunctions.ts @@ -1,6 +1,6 @@ import type { - DefaultServerFunctionArgs, CreateAdminContextResult, + DefaultServerFunctionArgs, ImportMap, SanitizedConfig, ServerFunction, diff --git a/packages/ui/src/views/Root/index.tsx b/packages/ui/src/views/Root/index.tsx index 07265a7b548..b0dba24f3f5 100644 --- a/packages/ui/src/views/Root/index.tsx +++ b/packages/ui/src/views/Root/index.tsx @@ -5,8 +5,8 @@ import type { AdminViewClientProps, AdminViewServerPropsOnly, CollectionPreferences, - createPayloadRequest, CreateAdminContextResult, + createPayloadRequest, ImportMap, SanitizedCollectionConfig, SanitizedConfig, From 9e11c9cf72877ade6ae117aa45e9c68a23fd85b7 Mon Sep 17 00:00:00 2001 From: Jake Fletcher Date: Thu, 24 Sep 2026 16:46:27 -0400 Subject: [PATCH 13/15] chore: rename admin context initializer --- docs/migration-guide/v4.mdx | 10 +-- .../aliases.output.ts | 2 +- .../collision.input.ts | 2 +- .../empty.output.ts | 4 +- .../migrate-payload-request-creation/index.ts | 6 +- .../internal.output.ts | 6 +- .../shorthand.output.ts | 4 +- .../src/adapters/handleServerFunctions.ts | 6 +- packages/next/src/adapters/layout.tsx | 6 +- packages/next/src/adapters/views.tsx | 7 +-- ...c.spec.ts => initAdminContext.rsc.spec.ts} | 22 +++---- ...ateAdminContext.ts => initAdminContext.ts} | 18 +++--- packages/payload/src/admin/functions/index.ts | 4 +- ...ntext.spec.ts => initAdminContext.spec.ts} | 62 +++++++++---------- ...ateAdminContext.ts => initAdminContext.ts} | 24 +++---- packages/payload/src/admin/types.ts | 2 +- packages/payload/src/admin/views/dashboard.ts | 4 +- packages/payload/src/exports/internal.ts | 6 +- .../src/layouts/Root/getLayoutData.ts | 4 +- .../utilities/createAdminContext.server.ts | 24 ------- .../src/utilities/devConfigReload.server.ts | 2 +- .../src/utilities/getRequestI18n.server.ts | 2 +- .../src/utilities/handleServerFunctions.ts | 4 +- ...pec.ts => initAdminContext.server.spec.ts} | 20 +++--- .../src/utilities/initAdminContext.server.ts | 24 +++++++ .../src/utilities/loadAdminPage.tsx | 14 ++--- packages/ui/src/layouts/Root/index.tsx | 8 +-- .../ui/src/utilities/handleServerFunctions.ts | 18 +++--- packages/ui/src/views/NotFound/page.tsx | 8 +-- packages/ui/src/views/Root/index.tsx | 12 ++-- 30 files changed, 167 insertions(+), 168 deletions(-) rename packages/next/src/utilities/{createAdminContext.rsc.spec.ts => initAdminContext.rsc.spec.ts} (82%) rename packages/next/src/utilities/{createAdminContext.ts => initAdminContext.ts} (52%) rename packages/payload/src/admin/{createAdminContext.spec.ts => initAdminContext.spec.ts} (87%) rename packages/payload/src/admin/{createAdminContext.ts => initAdminContext.ts} (89%) delete mode 100644 packages/tanstack-start/src/utilities/createAdminContext.server.ts rename packages/tanstack-start/src/utilities/{createAdminContext.server.spec.ts => initAdminContext.server.spec.ts} (68%) create mode 100644 packages/tanstack-start/src/utilities/initAdminContext.server.ts diff --git a/docs/migration-guide/v4.mdx b/docs/migration-guide/v4.mdx index fba10e26454..afd6691d4f4 100644 --- a/docs/migration-guide/v4.mdx +++ b/docs/migration-guide/v4.mdx @@ -24,20 +24,20 @@ The codemod is idempotent and safe to run on a partially-migrated project. ### Payload request creation is consolidated -Payload request creation now uses `createPayloadRequest` for local contexts and `createPayloadRequestFromWebRequest` to convert a Web `Request`. Admin context setup is exposed as `createAdminContext`. +Payload request creation now uses `createPayloadRequest` for local contexts and `createPayloadRequestFromWebRequest` to convert a Web `Request`. Admin context setup is exposed as `initAdminContext`. | Old name | New name | | --------------------------------------- | ------------------------------------------- | | `createLocalReq` | `createPayloadRequest` | | `CreateLocalReqOptions` (internal type) | `Omit` | | `createPayloadRequest` | `createPayloadRequestFromWebRequest` | -| `initReq` | `createAdminContext` | -| `InitReqArgs` | `CreateAdminContextArgs` | -| `InitReqResult` | `CreateAdminContextResult` | +| `initReq` | `initAdminContext` | +| `InitReqArgs` | `InitAdminContextArgs` | +| `InitReqResult` | `AdminContext` | | `InitReqCache` | `AdminContextCache` | | `InitReqPartialResult` | `PartialAdminContext` | -`createAdminContext` and its cache types are primarily contracts for framework adapters. Custom Root layout adapters must rename their injected `initReq` callback to `createAdminContext`. +`initAdminContext` and its cache types are primarily contracts for framework adapters. Custom Root layout adapters must rename their injected `initReq` callback to `initAdminContext`. Local request creation now takes a single object argument containing `payload`. For a request without options: diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/aliases.output.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/aliases.output.ts index 983aad88e01..a2311312e0e 100644 --- a/packages/codemod/src/transforms/migrate-payload-request-creation/aliases.output.ts +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/aliases.output.ts @@ -1,4 +1,4 @@ -import type { CreatePayloadRequestArgs as Options, CreateAdminContextResult as Result } from 'payload' +import type { CreatePayloadRequestArgs as Options, AdminContext as Result } from 'payload' import { createPayloadRequest as local, createPayloadRequestFromWebRequest as web } from 'payload' diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/collision.input.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/collision.input.ts index 11f696c2397..f30014d624e 100644 --- a/packages/codemod/src/transforms/migrate-payload-request-creation/collision.input.ts +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/collision.input.ts @@ -4,6 +4,6 @@ import { initReq } from 'payload/internal' const createPayloadRequestFromWebRequest = other const req = createLocalReq({}, payload) -function read(createAdminContext: Function) { +function read(initAdminContext: Function) { return initReq(args) } diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/empty.output.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/empty.output.ts index 57fdce86784..a85ca5e387e 100644 --- a/packages/codemod/src/transforms/migrate-payload-request-creation/empty.output.ts +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/empty.output.ts @@ -1,5 +1,5 @@ -import { createPayloadRequest, createPayloadRequestFromWebRequest, type CreateAdminContextResult } from 'payload' +import { createPayloadRequest, createPayloadRequestFromWebRequest, type AdminContext } from 'payload' const req = await createPayloadRequest({ payload: payload }) const web = createPayloadRequestFromWebRequest(request, config) -type Context = CreateAdminContextResult +type Context = AdminContext diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/index.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/index.ts index d9cb0f5e291..0694eae7146 100644 --- a/packages/codemod/src/transforms/migrate-payload-request-creation/index.ts +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/index.ts @@ -9,11 +9,11 @@ const RENAMES = { createLocalReq: 'createPayloadRequest', CreateLocalReqOptions: 'CreatePayloadRequestArgs', createPayloadRequest: 'createPayloadRequestFromWebRequest', - InitReqResult: 'CreateAdminContextResult', + InitReqResult: 'AdminContext', }, 'payload/internal': { - initReq: 'createAdminContext', - InitReqArgs: 'CreateAdminContextArgs', + initReq: 'initAdminContext', + InitReqArgs: 'InitAdminContextArgs', InitReqCache: 'AdminContextCache', InitReqPartialResult: 'PartialAdminContext', }, diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/internal.output.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/internal.output.ts index 5249369a8db..63ae9289568 100644 --- a/packages/codemod/src/transforms/migrate-payload-request-creation/internal.output.ts +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/internal.output.ts @@ -1,7 +1,7 @@ -import { createAdminContext, type CreateAdminContextArgs, type AdminContextCache, type PartialAdminContext } from 'payload/internal' -import { createAdminContext as initialize, type CreateAdminContextArgs as Args } from 'payload/internal' +import { initAdminContext, type InitAdminContextArgs, type AdminContextCache, type PartialAdminContext } from 'payload/internal' +import { initAdminContext as initialize, type InitAdminContextArgs as Args } from 'payload/internal' -const context = createAdminContext(args as CreateAdminContextArgs) +const context = initAdminContext(args as InitAdminContextArgs) const aliased = initialize(args as Args) type Cache = AdminContextCache type PartialContext = PartialAdminContext diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/shorthand.output.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/shorthand.output.ts index 2005a858a47..2a4a27d12ee 100644 --- a/packages/codemod/src/transforms/migrate-payload-request-creation/shorthand.output.ts +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/shorthand.output.ts @@ -1,7 +1,7 @@ import { createPayloadRequestFromWebRequest } from 'payload' -import { createAdminContext } from 'payload/internal' +import { initAdminContext } from 'payload/internal' -const handlers = { createPayloadRequest: createPayloadRequestFromWebRequest, initReq: createAdminContext } +const handlers = { createPayloadRequest: createPayloadRequestFromWebRequest, initReq: initAdminContext } const requestKey: string = 'createPayloadRequest' const contextKey: string = 'initReq' handlers[requestKey](args) diff --git a/packages/next/src/adapters/handleServerFunctions.ts b/packages/next/src/adapters/handleServerFunctions.ts index be95bddbad1..f1ca9d31012 100644 --- a/packages/next/src/adapters/handleServerFunctions.ts +++ b/packages/next/src/adapters/handleServerFunctions.ts @@ -1,8 +1,8 @@ import { createServerFunctionHandler } from '@payloadcms/ui/utilities/handleServerFunctions' -import { createAdminContext } from '../utilities/createAdminContext.js' +import { initAdminContext } from '../utilities/initAdminContext.js' export const handleServerFunctions = createServerFunctionHandler({ - createAdminContext: ({ configPromise, importMap }) => - createAdminContext({ configPromise, importMap, key: 'RootLayout' }), + initAdminContext: ({ configPromise, importMap }) => + initAdminContext({ configPromise, importMap, key: 'RootLayout' }), }) diff --git a/packages/next/src/adapters/layout.tsx b/packages/next/src/adapters/layout.tsx index c9a97b4b7f5..ec4d8116a50 100644 --- a/packages/next/src/adapters/layout.tsx +++ b/packages/next/src/adapters/layout.tsx @@ -3,7 +3,7 @@ import { RootLayout as UIRootLayout } from '@payloadcms/ui/layouts' import { Inter, Roboto_Mono } from 'next/font/google' import React from 'react' -import { createAdminContext } from '../utilities/createAdminContext.js' +import { initAdminContext } from '../utilities/initAdminContext.js' import { NextRouterAdapter } from './router.js' // CSS import lives here (not in `@payloadcms/ui`) so the esbuild bundle that produces // `@payloadcms/next/css` (`dist/prod/styles.css`) can follow the CSS chain. esbuild @@ -34,18 +34,18 @@ export { metadata } from '@payloadcms/ui/layouts' type Props = Omit< React.ComponentProps, - 'additionalDependencyChecks' | 'createAdminContext' | 'fonts' | 'RouterAdapter' + 'additionalDependencyChecks' | 'fonts' | 'initAdminContext' | 'RouterAdapter' > export const RootLayout = (props: Props) => ( ) diff --git a/packages/next/src/adapters/views.tsx b/packages/next/src/adapters/views.tsx index 4027968f8ea..4431c1bd4b3 100644 --- a/packages/next/src/adapters/views.tsx +++ b/packages/next/src/adapters/views.tsx @@ -11,7 +11,7 @@ import { renderRoot } from '@payloadcms/ui/views/Root' import { defaultAdminViews } from '@payloadcms/ui/views/Root/adminViews' import { notFound, redirect } from 'next/navigation.js' -import { createAdminContext } from '../utilities/createAdminContext.js' +import { initAdminContext } from '../utilities/initAdminContext.js' export const adminViews: AdminViewAdapter = defaultAdminViews @@ -27,7 +27,6 @@ type PageProps = { } export const RootPage = (props: PageProps) => - renderRoot({ ...props, adminViews, createAdminContext, notFound, redirect }) + renderRoot({ ...props, adminViews, initAdminContext, notFound, redirect }) -export const NotFoundPage = (props: PageProps) => - renderNotFoundPage({ ...props, createAdminContext }) +export const NotFoundPage = (props: PageProps) => renderNotFoundPage({ ...props, initAdminContext }) diff --git a/packages/next/src/utilities/createAdminContext.rsc.spec.ts b/packages/next/src/utilities/initAdminContext.rsc.spec.ts similarity index 82% rename from packages/next/src/utilities/createAdminContext.rsc.spec.ts rename to packages/next/src/utilities/initAdminContext.rsc.spec.ts index 6ec0897b2b5..79774c5b415 100644 --- a/packages/next/src/utilities/createAdminContext.rsc.spec.ts +++ b/packages/next/src/utilities/initAdminContext.rsc.spec.ts @@ -5,21 +5,21 @@ import { createRequire } from 'node:module' import type React from 'react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { createAdminContext } from './createAdminContext.js' +import { initAdminContext } from './initAdminContext.js' -const { counters, createPayloadAdminContext } = vi.hoisted(() => ({ +const { counters, initPayloadAdminContext } = vi.hoisted(() => ({ counters: { locale: 0, partial: 0, request: 0, }, - createPayloadAdminContext: vi.fn(), + initPayloadAdminContext: vi.fn(), })) vi.mock('react', () => createRequire(import.meta.url)('react')) vi.mock('payload/internal', () => ({ - createAdminContext: createPayloadAdminContext, + initAdminContext: initPayloadAdminContext, })) vi.mock('../adapters/server.js', () => ({ @@ -46,9 +46,9 @@ const importMap = {} as ImportMap async function renderNavigation(): Promise { async function Navigation() { await Promise.all([ - createAdminContext({ configPromise, importMap, key: 'RootLayout' }), - createAdminContext({ configPromise, importMap, key: 'RootLayout' }), - createAdminContext({ configPromise, importMap, key: 'initPage' }), + initAdminContext({ configPromise, importMap, key: 'RootLayout' }), + initAdminContext({ configPromise, importMap, key: 'RootLayout' }), + initAdminContext({ configPromise, importMap, key: 'initPage' }), ]) return null @@ -62,13 +62,13 @@ async function renderNavigation(): Promise { async function renderNavigationWithOverrides(): Promise { async function Navigation() { await Promise.all([ - createAdminContext({ + initAdminContext({ configPromise, importMap, key: 'initPage', overrides: { context: { source: 'first' } }, }), - createAdminContext({ + initAdminContext({ configPromise, importMap, key: 'initPage', @@ -84,12 +84,12 @@ async function renderNavigationWithOverrides(): Promise { await new Response(stream).arrayBuffer() } -describe('Next createAdminContext RSC cache', () => { +describe('Next initAdminContext RSC cache', () => { beforeEach(() => { counters.locale = 0 counters.partial = 0 counters.request = 0 - createPayloadAdminContext.mockReset().mockImplementation(async ({ cache, key, overrides }) => { + initPayloadAdminContext.mockReset().mockImplementation(async ({ cache, key, overrides }) => { await cache.getPartial(async () => { counters.partial += 1 return {} diff --git a/packages/next/src/utilities/createAdminContext.ts b/packages/next/src/utilities/initAdminContext.ts similarity index 52% rename from packages/next/src/utilities/createAdminContext.ts rename to packages/next/src/utilities/initAdminContext.ts index 14ce6da3e16..2339e66cb60 100644 --- a/packages/next/src/utilities/createAdminContext.ts +++ b/packages/next/src/utilities/initAdminContext.ts @@ -1,14 +1,14 @@ -import type { CreateAdminContextResult } from 'payload' -import type { AdminContextCache, CreateAdminContextArgs, PartialAdminContext } from 'payload/internal' +import type { AdminContext } from 'payload' +import type { AdminContextCache, InitAdminContextArgs, PartialAdminContext } from 'payload/internal' -import { createAdminContext as createPayloadAdminContext } from 'payload/internal' +import { initAdminContext as initPayloadAdminContext } from 'payload/internal' import { nextServerAdapter } from '../adapters/server.js' import { selectiveCache } from './selectiveCache.js' const partialContextCache = selectiveCache('partialContext') -const localeCache = selectiveCache>('locale') -const adminContextCache = selectiveCache('adminContext') +const localeCache = selectiveCache>('locale') +const adminContextCache = selectiveCache('adminContext') const cache: AdminContextCache = { getLocale: (resolveLocale, ...key) => localeCache.get({ create: resolveLocale, key }), @@ -21,12 +21,12 @@ const cache: AdminContextCache = { }), } -type NextCreateAdminContextArgs = { +type NextInitAdminContextArgs = { key: string -} & Omit +} & Omit -export const createAdminContext = (args: NextCreateAdminContextArgs) => - createPayloadAdminContext({ +export const initAdminContext = (args: NextInitAdminContextArgs) => + initPayloadAdminContext({ ...args, cache, serverAdapter: nextServerAdapter, diff --git a/packages/payload/src/admin/functions/index.ts b/packages/payload/src/admin/functions/index.ts index cfb813d883e..855c074c30d 100644 --- a/packages/payload/src/admin/functions/index.ts +++ b/packages/payload/src/admin/functions/index.ts @@ -16,7 +16,7 @@ import type { PayloadRequest, Sort, Where } from '../../types/index.js' import type { ColumnsFromURL } from '../../utilities/transformColumnPreferences.js' import type { ComponentRenderer } from '../adapters/render.js' -export type CreateAdminContextResult = { +export type AdminContext = { cookies: Map // TODO: Remove in 4.0. Duplicative, already available in req.headers headers: Headers @@ -32,7 +32,7 @@ export type CreateAdminContextResult = { export type DefaultServerFunctionArgs = { importMap: ImportMap renderComponent?: ComponentRenderer -} & Pick +} & Pick export type ServerFunctionArgs = { args: Record diff --git a/packages/payload/src/admin/createAdminContext.spec.ts b/packages/payload/src/admin/initAdminContext.spec.ts similarity index 87% rename from packages/payload/src/admin/createAdminContext.spec.ts rename to packages/payload/src/admin/initAdminContext.spec.ts index 38167a8e5ab..aede1c59601 100644 --- a/packages/payload/src/admin/createAdminContext.spec.ts +++ b/packages/payload/src/admin/initAdminContext.spec.ts @@ -3,12 +3,12 @@ import type { ImportMap } from '../cli/commands/generateImportMap/generateImport import type { SanitizedConfig } from '../config/types.js' import type { Payload } from '../index.js' import type { ServerAdapter } from './adapters/server.js' -import type { AdminContextCache, PartialAdminContext } from './createAdminContext.js' -import type { CreateAdminContextResult } from './functions/index.js' +import type { AdminContextCache, PartialAdminContext } from './initAdminContext.js' +import type { AdminContext } from './functions/index.js' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { createAdminContext } from './createAdminContext.js' +import { initAdminContext } from './initAdminContext.js' const { applyUserReadAccess, @@ -139,19 +139,19 @@ const createExecutingCache = (): AdminContextCache => ({ getPartial: vi.fn((createPartialContext: () => Promise) => createPartialContext(), ), - getRequest: vi.fn((createContext: () => Promise) => createContext()), + getRequest: vi.fn((createContext: () => Promise) => createContext()), }) const createReusingCache = (): AdminContextCache => { const localeResults: Array<{ cacheArgs: unknown[] - result: Pick + result: Pick }> = [] let partialContext: PartialAdminContext | undefined const contexts: Array<{ cacheArgs: unknown[] key: string - result: CreateAdminContextResult + result: AdminContext }> = [] return { @@ -195,7 +195,7 @@ const createReusingCache = (): AdminContextCache => { } } -describe('createAdminContext', () => { +describe('initAdminContext', () => { beforeEach(() => { applyUserReadAccess.mockReset().mockImplementation(async ({ user }) => ({ collection: user.collection, @@ -210,7 +210,7 @@ describe('createAdminContext', () => { }) it('should derive the URL and a nested query from requestURL', async () => { - const result = await createAdminContext({ + const result = await initAdminContext({ configPromise: config, importMap, requestURL: 'https://example.com/admin?locale=es&where%5Btitle%5D%5Bequals%5D=Hello', @@ -233,7 +233,7 @@ describe('createAdminContext', () => { it('should persist an authenticated request locale', async () => { getPayload.mockResolvedValue({ ...payload, config: localizedConfig }) - const result = await createAdminContext({ + const result = await initAdminContext({ configPromise: localizedConfig, importMap, requestURL: 'https://example.com/admin?locale=es', @@ -257,7 +257,7 @@ describe('createAdminContext', () => { }) getPayload.mockResolvedValue({ ...payload, config: localizedConfig }) - const result = await createAdminContext({ + const result = await initAdminContext({ configPromise: localizedConfig, importMap, requestURL: 'https://example.com/admin?locale=es', @@ -271,7 +271,7 @@ describe('createAdminContext', () => { }) it('should prefer explicit request overrides over requestURL', async () => { - const result = await createAdminContext({ + const result = await initAdminContext({ configPromise: config, importMap, overrides: { @@ -293,7 +293,7 @@ describe('createAdminContext', () => { }) it('should ignore an invalid requestURL', async () => { - const result = await createAdminContext({ + const result = await initAdminContext({ configPromise: config, importMap, requestURL: 'not a valid URL', @@ -305,8 +305,8 @@ describe('createAdminContext', () => { }) it('should initialize each request directly when no cache is supplied', async () => { - await createAdminContext({ configPromise: config, importMap, serverAdapter }) - await createAdminContext({ configPromise: config, importMap, serverAdapter }) + await initAdminContext({ configPromise: config, importMap, serverAdapter }) + await initAdminContext({ configPromise: config, importMap, serverAdapter }) expect(authenticate).toHaveBeenCalledTimes(2) expect(getAccessResults).toHaveBeenCalledTimes(2) @@ -315,7 +315,7 @@ describe('createAdminContext', () => { it('should use both cache stages when a cache is supplied', async () => { const cache = createExecutingCache() - await createAdminContext({ + await initAdminContext({ cache, configPromise: config, importMap, @@ -329,17 +329,17 @@ describe('createAdminContext', () => { it('requires a key when cache is supplied', async () => { await expect( - createAdminContext({ + initAdminContext({ cache: createExecutingCache(), configPromise: config, importMap, serverAdapter, }), - ).rejects.toThrow('createAdminContext requires a key when cache is provided') + ).rejects.toThrow('initAdminContext requires a key when cache is provided') }) it('should enable cron and forward canSetHeaders to auth strategies', async () => { - await createAdminContext({ + await initAdminContext({ canSetHeaders: true, configPromise: config, importMap, @@ -376,10 +376,10 @@ describe('createAdminContext', () => { }, serverAdapter, } - const first = await createAdminContext(args) + const first = await initAdminContext(args) first.req.context.source = 'mutated' - const second = await createAdminContext(args) + const second = await initAdminContext(args) expect(first.req).not.toBe(second.req) expect(first.req.context).not.toBe(second.req.context) @@ -398,14 +398,14 @@ describe('createAdminContext', () => { serverAdapter, } - const rootLayoutResult = await createAdminContext({ ...args, key: 'RootLayout' }) - await createAdminContext({ ...args, key: 'initPage' }) + const rootLayoutResult = await initAdminContext({ ...args, key: 'RootLayout' }) + await initAdminContext({ ...args, key: 'initPage' }) expect(authenticate).toHaveBeenCalledOnce() expect(applyUserReadAccess).toHaveBeenCalledTimes(2) expect(getAccessResults).toHaveBeenCalledTimes(2) - const repeatedRootLayoutResult = await createAdminContext({ ...args, key: 'RootLayout' }) + const repeatedRootLayoutResult = await initAdminContext({ ...args, key: 'RootLayout' }) expect(authenticate).toHaveBeenCalledOnce() expect(applyUserReadAccess).toHaveBeenCalledTimes(2) @@ -424,7 +424,7 @@ describe('createAdminContext', () => { }, }) - const result = await createAdminContext({ configPromise: config, importMap, serverAdapter }) + const result = await initAdminContext({ configPromise: config, importMap, serverAdapter }) expect(result.req.user).toHaveProperty('role', 'admin') expect(result.user).toEqual({ collection: 'users', id: 'user-id' }) @@ -446,11 +446,11 @@ describe('createAdminContext', () => { serverAdapter, } - const first = await createAdminContext({ + const first = await initAdminContext({ ...args, overrides: { context: { source: 'first' } }, }) - const second = await createAdminContext({ + const second = await initAdminContext({ ...args, overrides: { context: { source: 'second' } }, }) @@ -472,8 +472,8 @@ describe('createAdminContext', () => { serverAdapter, } - const rootLayoutResult = await createAdminContext({ ...args, key: 'RootLayout' }) - const pageResult = await createAdminContext({ ...args, key: 'initPage' }) + const rootLayoutResult = await initAdminContext({ ...args, key: 'RootLayout' }) + const pageResult = await initAdminContext({ ...args, key: 'initPage' }) expect(rootLayoutResult.locale).toMatchObject({ code: 'es' }) expect(pageResult.locale).toMatchObject({ code: 'es' }) @@ -483,7 +483,7 @@ describe('createAdminContext', () => { it('should use anonymous access when user read access fails', async () => { applyUserReadAccess.mockRejectedValueOnce(new Error('read access failed')) - const result = await createAdminContext({ configPromise: config, importMap, serverAdapter }) + const result = await initAdminContext({ configPromise: config, importMap, serverAdapter }) expect(result.req.user).toBeNull() expect(result.user).toBeNull() @@ -491,7 +491,7 @@ describe('createAdminContext', () => { }) it('should preserve an explicit request user override', async () => { - const result = await createAdminContext({ + const result = await initAdminContext({ configPromise: config, importMap, overrides: { @@ -511,7 +511,7 @@ describe('createAdminContext', () => { }) it('should preserve an explicit null user override', async () => { - const result = await createAdminContext({ + const result = await initAdminContext({ configPromise: config, importMap, overrides: { diff --git a/packages/payload/src/admin/createAdminContext.ts b/packages/payload/src/admin/initAdminContext.ts similarity index 89% rename from packages/payload/src/admin/createAdminContext.ts rename to packages/payload/src/admin/initAdminContext.ts index 77382894d79..e3388f212c5 100644 --- a/packages/payload/src/admin/createAdminContext.ts +++ b/packages/payload/src/admin/initAdminContext.ts @@ -8,7 +8,7 @@ import type { SanitizedConfig } from '../config/types.js' import type { PayloadRequest } from '../types/index.js' import type { CreatePayloadRequestArgs } from '../utilities/createPayloadRequest.js' import type { ServerAdapter } from './adapters/server.js' -import type { CreateAdminContextResult } from './functions/index.js' +import type { AdminContext } from './functions/index.js' import { applyUserReadAccess } from '../auth/applyUserReadAccess.js' import { executeAuthStrategies } from '../auth/executeAuthStrategies.js' @@ -21,29 +21,29 @@ import { getRequestLocale } from './getRequestLocale.js' export type PartialAdminContext = { i18n: I18nClient -} & Pick & +} & Pick & Pick /** Framework-provided request-scoped caching hooks used to deduplicate admin context creation. */ export type AdminContextCache = { /** Reuses locale preference resolution across admin contexts. */ getLocale?: ( - resolveLocale: () => Promise>, + resolveLocale: () => Promise>, ...cacheArgs: unknown[] - ) => Promise> + ) => Promise> /** Reuses Payload, i18n, and authentication state within the current request. */ getPartial: ( createPartialContext: () => Promise, ) => Promise /** Reuses a complete admin context for the supplied key and cache arguments. */ getRequest: ( - createContext: () => Promise, + createContext: () => Promise, key: string, ...cacheArgs: unknown[] - ) => Promise + ) => Promise } -export type CreateAdminContextArgs = { +export type InitAdminContextArgs = { /** * Optional framework-owned request-scoped cache. * Framework adapters control its lifetime to prevent request state from leaking between requests. @@ -62,7 +62,7 @@ export type CreateAdminContextArgs = { /** * Creates the request context used by framework adapters to render the admin panel. */ -export async function createAdminContext({ +export async function initAdminContext({ cache, canSetHeaders, configPromise, @@ -71,9 +71,9 @@ export async function createAdminContext({ overrides, requestURL, serverAdapter, -}: CreateAdminContextArgs): Promise { +}: InitAdminContextArgs): Promise { if (cache && !key) { - throw new Error('createAdminContext requires a key when cache is provided') + throw new Error('initAdminContext requires a key when cache is provided') } const headers = await serverAdapter.getHeaders() @@ -113,7 +113,7 @@ export async function createAdminContext({ ? await cache.getPartial(createPartialContext) : await createPartialContext() - const createContext = async (): Promise => { + const createContext = async (): Promise => { const { i18n, languageCode, payload, responseHeaders, user } = partialContext const { req: reqOverrides, ...optionsOverrides } = overrides || {} const hasOptionsUserOverride = Object.hasOwn(optionsOverrides, 'user') @@ -141,7 +141,7 @@ export async function createAdminContext({ req.user = null } - const resolveLocale = async (): Promise> => ({ + const resolveLocale = async (): Promise> => ({ locale: await getRequestLocale({ req }), }) const { locale } = cache?.getLocale diff --git a/packages/payload/src/admin/types.ts b/packages/payload/src/admin/types.ts index 31a56717472..23a5256d94a 100644 --- a/packages/payload/src/admin/types.ts +++ b/packages/payload/src/admin/types.ts @@ -589,8 +589,8 @@ export type DocumentSlots = { } export type { + AdminContext, BuildTableStateArgs, - CreateAdminContextResult, DefaultServerFunctionArgs, ListQuery, ServerFunction, diff --git a/packages/payload/src/admin/views/dashboard.ts b/packages/payload/src/admin/views/dashboard.ts index 19908574e17..0873f95a992 100644 --- a/packages/payload/src/admin/views/dashboard.ts +++ b/packages/payload/src/admin/views/dashboard.ts @@ -1,4 +1,4 @@ -import type { CreateAdminContextResult, TypedWidget, WidgetSlug } from '../../index.js' +import type { AdminContext, TypedWidget, WidgetSlug } from '../../index.js' import type { ComponentRenderer } from '../adapters/render.js' export enum EntityType { @@ -33,4 +33,4 @@ export type WidgetServerProps>] extends [never] ? string : WidgetSlugFromWidget<{ data?: unknown } & Exclude> -} & Pick +} & Pick diff --git a/packages/payload/src/exports/internal.ts b/packages/payload/src/exports/internal.ts index 9bd49b37085..94581fc2710 100644 --- a/packages/payload/src/exports/internal.ts +++ b/packages/payload/src/exports/internal.ts @@ -4,10 +4,10 @@ export { type AdminContextCache, - createAdminContext, - type CreateAdminContextArgs, + initAdminContext, + type InitAdminContextArgs, type PartialAdminContext, -} from '../admin/createAdminContext.js' +} from '../admin/initAdminContext.js' export { applyUserReadAccess } from '../auth/applyUserReadAccess.js' export { createCLI } from '../cli/index.js' export { getCommandInput } from '../cli/runtime/getCommandInput.js' diff --git a/packages/tanstack-start/src/layouts/Root/getLayoutData.ts b/packages/tanstack-start/src/layouts/Root/getLayoutData.ts index 6182e9eec43..600bdb4a5dc 100644 --- a/packages/tanstack-start/src/layouts/Root/getLayoutData.ts +++ b/packages/tanstack-start/src/layouts/Root/getLayoutData.ts @@ -19,7 +19,7 @@ import { Outlet } from '@tanstack/react-router' import { applyLocaleFiltering } from 'payload/shared' import { createElement } from 'react' -import { createAdminContext } from '../../utilities/createAdminContext.server.js' +import { initAdminContext } from '../../utilities/initAdminContext.server.js' export type RootLayoutData = { clientConfig: ClientConfig @@ -66,7 +66,7 @@ export async function getLayoutData({ payload: { config }, }, user, - } = await createAdminContext({ configPromise, importMap }) + } = await initAdminContext({ configPromise, importMap }) const theme = getRequestTheme({ config, cookies, headers }) const isEmbedded = getRequestEmbed({ config, cookies }) diff --git a/packages/tanstack-start/src/utilities/createAdminContext.server.ts b/packages/tanstack-start/src/utilities/createAdminContext.server.ts deleted file mode 100644 index 5b98824ef65..00000000000 --- a/packages/tanstack-start/src/utilities/createAdminContext.server.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { ServerAdapter } from 'payload' -import type { CreateAdminContextArgs } from 'payload/internal' - -import { getRequest } from '@tanstack/react-start/server' -import { createAdminContext as createPayloadAdminContext } from 'payload/internal' - -// Registers the dev reload strategy before `createPayloadAdminContext` can build an instance. -// Side-effect only, and a no-op outside of dev serve. -import './devConfigReload.server.js' -import { tanstackServerAdapter } from './serverAdapter.server.js' - -type TanStackCreateAdminContextArgs = { - serverAdapter?: ServerAdapter -} & Omit - -export const createAdminContext = ({ - serverAdapter = tanstackServerAdapter, - ...args -}: TanStackCreateAdminContextArgs) => - createPayloadAdminContext({ - ...args, - requestURL: getRequest().url, - serverAdapter, - }) diff --git a/packages/tanstack-start/src/utilities/devConfigReload.server.ts b/packages/tanstack-start/src/utilities/devConfigReload.server.ts index c375e39329a..8b4468e95ce 100644 --- a/packages/tanstack-start/src/utilities/devConfigReload.server.ts +++ b/packages/tanstack-start/src/utilities/devConfigReload.server.ts @@ -6,7 +6,7 @@ import { PAYLOAD_CONFIG_CHANGED_EVENT } from './devConfigReloadEvent.js' * Subscribes the Payload instance to the config-changed event that the * `payload:dev-config-reload` Vite plugin broadcasts. * - * Imported for its side effect by `createAdminContext.server.ts`, the adapter's only + * Imported for its side effect by `initAdminContext.server.ts`, the adapter's only * `getPayload` call site, so the strategy is registered before an instance can * be built. Registration is module-scoped on both ends: this runs inside the * app's server graph and so resolves the same copy of `payload` that diff --git a/packages/tanstack-start/src/utilities/getRequestI18n.server.ts b/packages/tanstack-start/src/utilities/getRequestI18n.server.ts index c063793309b..9b8923efb18 100644 --- a/packages/tanstack-start/src/utilities/getRequestI18n.server.ts +++ b/packages/tanstack-start/src/utilities/getRequestI18n.server.ts @@ -7,7 +7,7 @@ import { getRequestLanguage, parseCookies } from 'payload' /** * Resolves the client `I18n` for the current TanStack Start request without a - * full `createAdminContext` (no `getPayload`/auth/access). Mirrors the Next.js adapter's + * full `initAdminContext` (no `getPayload`/auth/access). Mirrors the Next.js adapter's * `getNextRequestI18n` and feeds the shared `generatePageMetadata`, which only * needs `config` + `i18n` to translate admin-page titles. */ diff --git a/packages/tanstack-start/src/utilities/handleServerFunctions.ts b/packages/tanstack-start/src/utilities/handleServerFunctions.ts index 980acfa510f..75da756c0a9 100644 --- a/packages/tanstack-start/src/utilities/handleServerFunctions.ts +++ b/packages/tanstack-start/src/utilities/handleServerFunctions.ts @@ -1,9 +1,9 @@ import { createServerFunctionHandler } from '@payloadcms/ui/utilities/handleServerFunctions' -import { createAdminContext } from './createAdminContext.server.js' +import { initAdminContext } from './initAdminContext.server.js' import { serializeForRsc } from './serializeForRsc.js' export const handleServerFunctions = createServerFunctionHandler({ - createAdminContext, + initAdminContext, transformResult: serializeForRsc, }) diff --git a/packages/tanstack-start/src/utilities/createAdminContext.server.spec.ts b/packages/tanstack-start/src/utilities/initAdminContext.server.spec.ts similarity index 68% rename from packages/tanstack-start/src/utilities/createAdminContext.server.spec.ts rename to packages/tanstack-start/src/utilities/initAdminContext.server.spec.ts index 1c5c6397668..da7f332fe89 100644 --- a/packages/tanstack-start/src/utilities/createAdminContext.server.spec.ts +++ b/packages/tanstack-start/src/utilities/initAdminContext.server.spec.ts @@ -1,8 +1,8 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' -const { getRequest, createPayloadAdminContext, tanstackServerAdapter } = vi.hoisted(() => ({ +const { getRequest, initPayloadAdminContext, tanstackServerAdapter } = vi.hoisted(() => ({ getRequest: vi.fn(), - createPayloadAdminContext: vi.fn(), + initPayloadAdminContext: vi.fn(), tanstackServerAdapter: { getHeaders: vi.fn(), }, @@ -13,7 +13,7 @@ vi.mock('@tanstack/react-start/server', () => ({ })) vi.mock('payload/internal', () => ({ - createAdminContext: createPayloadAdminContext, + initAdminContext: initPayloadAdminContext, })) vi.mock('./devConfigReload.server.js', () => ({})) @@ -22,12 +22,12 @@ vi.mock('./serverAdapter.server.js', () => ({ tanstackServerAdapter, })) -import { createAdminContext } from './createAdminContext.server.js' +import { initAdminContext } from './initAdminContext.server.js' -describe('createAdminContext', () => { +describe('initAdminContext', () => { beforeEach(() => { getRequest.mockReset().mockReturnValue(new Request('http://localhost/admin?locale=es')) - createPayloadAdminContext.mockReset().mockResolvedValue({}) + initPayloadAdminContext.mockReset().mockResolvedValue({}) }) it('should provide the active request URL and default server adapter', async () => { @@ -36,9 +36,9 @@ describe('createAdminContext', () => { importMap: {}, } - await createAdminContext(args) + await initAdminContext(args) - expect(createPayloadAdminContext).toHaveBeenCalledWith({ + expect(initPayloadAdminContext).toHaveBeenCalledWith({ ...args, requestURL: 'http://localhost/admin?locale=es', serverAdapter: tanstackServerAdapter, @@ -50,13 +50,13 @@ describe('createAdminContext', () => { getHeaders: vi.fn(), } - await createAdminContext({ + await initAdminContext({ configPromise: Promise.resolve({} as never), importMap: {}, serverAdapter: serverAdapter as never, }) - expect(createPayloadAdminContext).toHaveBeenCalledWith( + expect(initPayloadAdminContext).toHaveBeenCalledWith( expect.objectContaining({ serverAdapter, }), diff --git a/packages/tanstack-start/src/utilities/initAdminContext.server.ts b/packages/tanstack-start/src/utilities/initAdminContext.server.ts new file mode 100644 index 00000000000..8a5daf4ae8e --- /dev/null +++ b/packages/tanstack-start/src/utilities/initAdminContext.server.ts @@ -0,0 +1,24 @@ +import type { ServerAdapter } from 'payload' +import type { InitAdminContextArgs } from 'payload/internal' + +import { getRequest } from '@tanstack/react-start/server' +import { initAdminContext as initPayloadAdminContext } from 'payload/internal' + +// Registers the dev reload strategy before `initPayloadAdminContext` can build an instance. +// Side-effect only, and a no-op outside of dev serve. +import './devConfigReload.server.js' +import { tanstackServerAdapter } from './serverAdapter.server.js' + +type TanStackInitAdminContextArgs = { + serverAdapter?: ServerAdapter +} & Omit + +export const initAdminContext = ({ + serverAdapter = tanstackServerAdapter, + ...args +}: TanStackInitAdminContextArgs) => + initPayloadAdminContext({ + ...args, + requestURL: getRequest().url, + serverAdapter, + }) diff --git a/packages/tanstack-start/src/utilities/loadAdminPage.tsx b/packages/tanstack-start/src/utilities/loadAdminPage.tsx index 54e683114c9..010b9432601 100644 --- a/packages/tanstack-start/src/utilities/loadAdminPage.tsx +++ b/packages/tanstack-start/src/utilities/loadAdminPage.tsx @@ -6,8 +6,8 @@ import { renderServerComponent } from '@tanstack/react-start/rsc' import type { AdminPageMetadata } from './meta.js' -import { createAdminContext } from './createAdminContext.server.js' import { getRequestI18n } from './getRequestI18n.server.js' +import { initAdminContext } from './initAdminContext.server.js' import { createPageRenderServerAdapter } from './serverAdapter.server.js' export type LoadAdminPageArgs = { @@ -133,7 +133,7 @@ const toAdminPageMetadata = (meta: MetaConfig): AdminPageMetadata => { * the app's `config` and generated `importMap`. * * 1. Gets the Payload admin context via the shared `renderRoot` orchestrator - * from `@payloadcms/ui`, passing a TanStack-bound `createAdminContext`. The injected + * from `@payloadcms/ui`, passing a TanStack-bound `initAdminContext`. The injected * page-render `ServerAdapter` records navigation intent and throws the * framework-agnostic error contract. * 2. Pipes the resulting React server tree through `renderServerComponent` @@ -175,14 +175,14 @@ export async function loadAdminPage({ const pageServerAdapter = createPageRenderServerAdapter(nav) let userAgent: string | undefined - // `renderRoot` calls `createAdminContext` itself with its own overrides (query + // `renderRoot` calls `initAdminContext` itself with its own overrides (query // re-nesting, `urlSuffix`, `fallbackLocale`). Forward them, injecting the // page-render `ServerAdapter` so `req.server.redirect()` / `.notFound()` // is recorded + thrown rather than escaping as raw TanStack nav. - const boundCreateAdminContext: Parameters[0]['createAdminContext'] = async ( + const boundInitAdminContext: Parameters[0]['initAdminContext'] = async ( args, ) => { - const result = await createAdminContext({ + const result = await initAdminContext({ configPromise: args.configPromise, importMap: args.importMap, overrides: args.overrides, @@ -210,8 +210,8 @@ export async function loadAdminPage({ const notFoundNode = await renderNotFoundPage({ config: Promise.resolve(config), - createAdminContext, importMap, + initAdminContext, params: Promise.resolve({ segments: splatSegments }), searchParams: Promise.resolve(searchParams), }) @@ -225,8 +225,8 @@ export async function loadAdminPage({ const node = await renderRoot({ adminViews: defaultAdminViews, config: Promise.resolve(config), - createAdminContext: boundCreateAdminContext, importMap, + initAdminContext: boundInitAdminContext, key: splat ?? '', notFound: pageServerAdapter.notFound, // `segments` is intentionally `undefined` for the admin root (`/admin`), diff --git a/packages/ui/src/layouts/Root/index.tsx b/packages/ui/src/layouts/Root/index.tsx index 458570ceea7..49a4ce820f2 100644 --- a/packages/ui/src/layouts/Root/index.tsx +++ b/packages/ui/src/layouts/Root/index.tsx @@ -3,7 +3,7 @@ import type { ImportMap, LanguageOptions, SanitizedConfig, ServerFunctionClient import { applyLocaleFiltering } from 'payload/shared' import React, { Suspense } from 'react' -import type { CreateAdminContextFn } from '../../views/Root/index.js' +import type { InitAdminContextFn } from '../../views/Root/index.js' import { getNavPrefs } from '../../elements/Nav/getNavPrefs.js' @@ -39,7 +39,6 @@ type RootLayoutProps = { readonly additionalDependencyChecks?: CheckDependenciesArgs readonly children: React.ReactNode readonly config: Promise - readonly createAdminContext: CreateAdminContextFn /** * Fonts to apply to the admin `` element. Each entry's * `variable ?? className` is appended to the `` class list. @@ -65,6 +64,7 @@ type RootLayoutProps = { readonly head?: React.ReactNode readonly htmlProps?: React.HtmlHTMLAttributes readonly importMap: ImportMap + readonly initAdminContext: InitAdminContextFn /** * Client router adapter. Caller supplies a framework-specific provider * (for Next.js use the `NextRouterAdapter` exported from `@payloadcms/next`). @@ -88,11 +88,11 @@ export const RootLayout = (props: RootLayoutProps) => { const RootLayoutContent = async ({ children, config: configPromise, - createAdminContext, fonts = [], head: headFromProps, htmlProps = {}, importMap, + initAdminContext, RouterAdapter, serverFunction, }: RootLayoutProps) => { @@ -106,7 +106,7 @@ const RootLayoutContent = async ({ payload: { config }, }, user, - } = await createAdminContext({ configPromise, importMap, key: 'RootLayout' }) + } = await initAdminContext({ configPromise, importMap, key: 'RootLayout' }) const theme = getRequestTheme({ config, diff --git a/packages/ui/src/utilities/handleServerFunctions.ts b/packages/ui/src/utilities/handleServerFunctions.ts index 686167a04e9..24f848e2d09 100644 --- a/packages/ui/src/utilities/handleServerFunctions.ts +++ b/packages/ui/src/utilities/handleServerFunctions.ts @@ -1,5 +1,5 @@ import type { - CreateAdminContextResult, + AdminContext, DefaultServerFunctionArgs, ImportMap, SanitizedConfig, @@ -9,18 +9,18 @@ import type { import { sharedServerFunctions } from './serverFunctionRegistry.js' -type CreateAdminContextFn = (args: { +type InitAdminContextFn = (args: { configPromise: Promise | SanitizedConfig importMap: ImportMap -}) => Promise +}) => Promise type CreateServerFunctionHandlerArgs = { /** * Framework-specific admin context creation. The adapter closes over its own - * `ServerAdapter` / request source (Next.js: `createAdminContext` + `nextServerAdapter`; - * TanStack Start: its `getRequest()`-based `createAdminContext`). + * `ServerAdapter` / request source (Next.js: `initAdminContext` + `nextServerAdapter`; + * TanStack Start: its `getRequest()`-based `initAdminContext`). */ - createAdminContext: CreateAdminContextFn + initAdminContext: InitAdminContextFn /** * Additional handlers registered alongside `sharedServerFunctions`. Reserved * for adapter-specific functions; today both adapters run the shared set only. @@ -38,12 +38,12 @@ type CreateServerFunctionHandlerArgs = { * Factory for the framework-agnostic `handleServerFunctions` entry point. * * All adapters dispatch from the shared registry (`sharedServerFunctions`) and - * differ only in the two injected hooks below — `createAdminContext` and `transformResult` + * differ only in the two injected hooks below — `initAdminContext` and `transformResult` * — so there is a single dispatch implementation and the handler sets cannot * drift between frameworks. */ export const createServerFunctionHandler = ({ - createAdminContext, + initAdminContext, serverFunctions, transformResult, }: CreateServerFunctionHandlerArgs): ServerFunctionHandler => { @@ -60,7 +60,7 @@ export const createServerFunctionHandler = ({ serverFunctions: extraServerFunctions, } = args - const { cookies, locale, permissions, req, user } = await createAdminContext({ + const { cookies, locale, permissions, req, user } = await initAdminContext({ configPromise, importMap, }) diff --git a/packages/ui/src/views/NotFound/page.tsx b/packages/ui/src/views/NotFound/page.tsx index 890c84f05f0..c99d56cedbe 100644 --- a/packages/ui/src/views/NotFound/page.tsx +++ b/packages/ui/src/views/NotFound/page.tsx @@ -6,7 +6,7 @@ import { applyLocaleFiltering, formatAdminURL } from 'payload/shared' import * as qs from 'qs-esm' import React from 'react' -import type { CreateAdminContextFn } from '../Root/index.js' +import type { InitAdminContextFn } from '../Root/index.js' /* eslint-disable payload/no-imports-from-exports-dir -- Server component must reference exports/client bundle for proper client boundary in prod builds */ import { NotFoundClient, PageConfigProvider } from '../../exports/client/index.js' @@ -18,16 +18,16 @@ import { getVisibleEntities } from '../../utilities/getVisibleEntities.js' export type RenderNotFoundPageArgs = { config: Promise - createAdminContext: CreateAdminContextFn importMap: ImportMap + initAdminContext: InitAdminContextFn params: Promise<{ segments: string[] }> searchParams: Promise<{ [key: string]: string | string[] }> } export const renderNotFoundPage = async ({ config: configPromise, - createAdminContext, importMap, + initAdminContext, params: paramsPromise, searchParams: searchParamsPromise, }: RenderNotFoundPageArgs) => { @@ -43,7 +43,7 @@ export const renderNotFoundPage = async ({ req, req: { payload }, user, - } = await createAdminContext({ + } = await initAdminContext({ configPromise: config, importMap, key: 'RootLayout', diff --git a/packages/ui/src/views/Root/index.tsx b/packages/ui/src/views/Root/index.tsx index b0dba24f3f5..27f5e929e6a 100644 --- a/packages/ui/src/views/Root/index.tsx +++ b/packages/ui/src/views/Root/index.tsx @@ -1,11 +1,11 @@ 'use server' import type { + AdminContext, AdminViewAdapter, AdminViewClientProps, AdminViewServerPropsOnly, CollectionPreferences, - CreateAdminContextResult, createPayloadRequest, ImportMap, SanitizedCollectionConfig, @@ -31,19 +31,19 @@ import { isPublicAdminRoute } from '../../utilities/isPublicAdminRoute.js' import { getCustomViewByRoute } from './getCustomViewByRoute.js' import { getRouteData } from './getRouteData.js' -export type CreateAdminContextFn = (args: { +export type InitAdminContextFn = (args: { canSetHeaders?: boolean configPromise: Promise | SanitizedConfig importMap: ImportMap key: string overrides?: Omit[0], 'payload'> -}) => Promise +}) => Promise export type RenderRootArgs = { adminViews: AdminViewAdapter config: Promise - createAdminContext: CreateAdminContextFn importMap: ImportMap + initAdminContext: InitAdminContextFn /** * Optional React `key` applied to the rendered view (not the surrounding admin * template/nav). Adapters whose router reconciles a single RSC payload in place @@ -66,8 +66,8 @@ export type RenderRootArgs = { export const renderRoot = async ({ adminViews, config: configPromise, - createAdminContext, importMap, + initAdminContext, key, notFound, params: paramsPromise, @@ -154,7 +154,7 @@ export const renderRoot = async ({ req, req: { payload }, user, - } = await createAdminContext({ + } = await initAdminContext({ configPromise: config, importMap, key: 'initPage', From fb9b75eefa929e5b692c0d63b38bf519900e1531 Mon Sep 17 00:00:00 2001 From: Jake Fletcher Date: Thu, 24 Sep 2026 17:18:32 -0400 Subject: [PATCH 14/15] chore: remove added request unit tests --- .../utilities/createPayloadRequest.spec.ts | 81 +----- ...createPayloadRequestFromWebRequest.spec.ts | 231 ------------------ 2 files changed, 2 insertions(+), 310 deletions(-) delete mode 100644 packages/payload/src/utilities/createPayloadRequestFromWebRequest.spec.ts diff --git a/packages/payload/src/utilities/createPayloadRequest.spec.ts b/packages/payload/src/utilities/createPayloadRequest.spec.ts index 0cf21b4b0d0..3fa7934b55c 100644 --- a/packages/payload/src/utilities/createPayloadRequest.spec.ts +++ b/packages/payload/src/utilities/createPayloadRequest.spec.ts @@ -1,10 +1,10 @@ import { describe, expect, it, vi } from 'vitest' -import type { Payload, PayloadRequest } from '../index.js' +import type { Payload } from '../index.js' import { createPayloadRequest } from './createPayloadRequest.js' -describe('createPayloadRequest', () => { +describe('createPayloadRequest - URL construction', () => { const mockPayload = { config: { serverURL: undefined, @@ -130,81 +130,4 @@ describe('createPayloadRequest', () => { expect(result.url).toBe('http://localhost/api/test') expect(mockPayload.logger.error).not.toHaveBeenCalled() }) - - it('returns the supplied request and preserves request-owned state', async () => { - const headers = new Headers({ existing: 'true' }) - const payloadDataLoader = {} as PayloadRequest['payloadDataLoader'] - const req = { - context: { fromRequest: true }, - headers, - payloadAPI: 'REST' as const, - payloadDataLoader, - query: { existing: 'value' }, - routeParams: { id: '1' }, - user: null, - url: 'http://example.com/api/posts', - } as Partial - - const result = await createPayloadRequest({ - context: { fromArgs: true }, - payload: mockPayload, - req, - }) - - expect(result).toBe(req) - expect(result.headers).toBe(headers) - expect(result.payloadAPI).toBe('REST') - expect(result.payloadDataLoader).toBe(payloadDataLoader) - expect(result.context).toEqual({ fromRequest: true, fromArgs: true }) - expect(result.query).toEqual({ existing: 'value' }) - expect(result.routeParams).toEqual({ id: '1' }) - }) - - describe('localization', () => { - const localizedPayload = { - ...mockPayload, - config: { - ...mockPayload.config, - localization: { - defaultLocale: 'en', - fallback: true, - localeCodes: ['en', 'de'], - locales: [{ code: 'en' }, { code: 'de' }], - }, - }, - } as unknown as Payload - - it("normalizes localized '*' requests to 'all'", async () => { - const result = await createPayloadRequest({ locale: '*', payload: localizedPayload }) - - expect(result.locale).toBe('all') - }) - - it('uses the default locale for localized requests without a locale', async () => { - const result = await createPayloadRequest({ payload: localizedPayload }) - - expect(result.locale).toBe('en') - }) - - it('sanitizes unsupported fallback locales for localized requests', async () => { - const result = await createPayloadRequest({ - fallbackLocale: 'fr', - locale: 'en', - payload: localizedPayload, - }) - - expect(result.fallbackLocale).toBe(false) - }) - - it('does not assign locale state when localization is disabled', async () => { - const result = await createPayloadRequest({ - fallbackLocale: 'de', - locale: 'en', - payload: mockPayload, - }) - - expect(result.locale).toBeUndefined() - expect(result.fallbackLocale).toBeUndefined() - }) - }) }) diff --git a/packages/payload/src/utilities/createPayloadRequestFromWebRequest.spec.ts b/packages/payload/src/utilities/createPayloadRequestFromWebRequest.spec.ts deleted file mode 100644 index 948011e09a6..00000000000 --- a/packages/payload/src/utilities/createPayloadRequestFromWebRequest.spec.ts +++ /dev/null @@ -1,231 +0,0 @@ -import type { I18nClient } from '@payloadcms/translations' - -import type { Payload, PayloadRequest, SanitizedConfig } from '../index.js' - -import { beforeEach, describe, expect, it, vi } from 'vitest' - -import { executeAuthStrategies } from '../auth/executeAuthStrategies.js' -import { getPayload } from '../index.js' -import { addLocalesToRequestFromData } from './addLocalesToRequest.js' -import { createPayloadRequest } from './createPayloadRequest.js' -import { createPayloadRequestFromWebRequest } from './createPayloadRequestFromWebRequest.js' - -const { initI18n } = vi.hoisted(() => ({ - initI18n: vi.fn(), -})) - -vi.mock('@payloadcms/translations', async (importOriginal) => { - const actual = await importOriginal() - - return { - ...actual, - initI18n, - } -}) - -vi.mock('../auth/executeAuthStrategies.js', () => ({ - executeAuthStrategies: vi.fn(), -})) - -vi.mock('../index.js', () => ({ - getPayload: vi.fn(), -})) - -vi.mock('./createPayloadRequest.js', () => ({ - createPayloadRequest: vi.fn(), -})) - -const config = { - cookiePrefix: 'payload', - graphQL: { - disable: false, - }, - i18n: { - fallbackLanguage: 'en', - supportedLanguages: { - en: {}, - }, - }, - localization: { - defaultLocale: 'en', - fallback: true, - localeCodes: ['en', 'de'], - locales: [ - { code: 'en', label: 'English' }, - { code: 'de', label: 'German' }, - ], - }, - routes: { - api: '/api', - graphQL: '/graphql', - }, -} as SanitizedConfig - -const i18n = { - language: 'en', - t: (key: string) => key, - translations: {}, -} as unknown as I18nClient - -const payload = { - config, -} as unknown as Payload - -const useRealCreatePayloadRequest = async () => { - const actual = - await vi.importActual('./createPayloadRequest.js') - - vi.mocked(createPayloadRequest).mockImplementation(actual.createPayloadRequest) -} - -describe('createPayloadRequestFromWebRequest', () => { - beforeEach(() => { - vi.mocked(getPayload).mockReset().mockResolvedValue(payload) - vi.mocked(createPayloadRequest) - .mockReset() - .mockImplementation(async ({ req }) => req as PayloadRequest) - vi.mocked(executeAuthStrategies).mockReset().mockResolvedValue({ - responseHeaders: undefined, - user: null, - }) - initI18n.mockReset().mockResolvedValue(i18n) - }) - - it('initializes the original Web Request through createPayloadRequest before authentication', async () => { - const request = new Request('http://localhost/api/posts?locale=en&depth=2') - - const result = await createPayloadRequestFromWebRequest({ config, request }) - - expect(createPayloadRequest).toHaveBeenCalledWith( - expect.objectContaining({ - locale: 'en', - payload, - req: request, - }), - ) - expect((request as PayloadRequest).i18n).toBe(i18n) - expect((request as PayloadRequest).query).toEqual({ depth: '2', locale: 'en' }) - expect(executeAuthStrategies).toHaveBeenCalledWith(expect.objectContaining({ req: request })) - expect(vi.mocked(createPayloadRequest).mock.invocationCallOrder[0]).toBeLessThan( - vi.mocked(executeAuthStrategies).mock.invocationCallOrder[0], - ) - expect(result).toBe(request) - }) - - it.each([ - ['http://localhost/api/graphql', 'GraphQL'], - ['http://localhost/api/posts', 'REST'], - ] as const)('assigns %s requests to the %s API', async (url, payloadAPI) => { - const request = new Request(url) - - const result = await createPayloadRequestFromWebRequest({ config, request }) - - expect(result.payloadAPI).toBe(payloadAPI) - }) - - it('parses nested query values and assigns route params', async () => { - const request = new Request( - 'http://localhost/api/posts?where[title][equals]=Hello&tags[]=news&tags[]=featured', - ) - - const result = await createPayloadRequestFromWebRequest({ - config, - params: { collection: 'posts' }, - request, - }) - - expect(result.query).toEqual({ - tags: ['news', 'featured'], - where: { title: { equals: 'Hello' } }, - }) - expect(result.routeParams).toEqual({ collection: 'posts' }) - }) - - it('assigns authentication response headers and user', async () => { - const request = new Request('http://localhost/api/posts') - const responseHeaders = new Headers({ 'x-auth': 'authenticated' }) - const user = { collection: 'users', id: 'user-id' } - - vi.mocked(executeAuthStrategies).mockResolvedValue({ responseHeaders, user }) - - const result = await createPayloadRequestFromWebRequest({ config, request }) - - expect(result.responseHeaders).toBe(responseHeaders) - expect(result.user).toBe(user) - }) - - it('passes sanitized fallback locale state to common request initialization', async () => { - const request = new Request('http://localhost/api/posts?locale=de&fallbackLocale=unsupported') - - await createPayloadRequestFromWebRequest({ config, request }) - - expect(createPayloadRequest).toHaveBeenCalledWith( - expect.objectContaining({ - fallbackLocale: false, - locale: 'de', - }), - ) - }) - - it('preserves parsed locale fields when localization is disabled', async () => { - const unlocalizedConfig = { - ...config, - localization: false, - } as SanitizedConfig - const unlocalizedPayload = { - config: unlocalizedConfig, - } as unknown as Payload - - vi.mocked(getPayload).mockResolvedValue(unlocalizedPayload) - await useRealCreatePayloadRequest() - - const result = await createPayloadRequestFromWebRequest({ - config: unlocalizedConfig, - request: new Request('http://localhost/api/posts?locale=de&fallbackLocale=en'), - }) - - expect(result.locale).toBe('de') - expect(result.fallbackLocale).toBe('en') - }) - - it('allows a body locale when URL locale is absent and fallback is disabled', async () => { - const noFallbackConfig = { - ...config, - localization: { - ...config.localization, - fallback: false, - }, - } as SanitizedConfig - const noFallbackPayload = { - config: noFallbackConfig, - } as unknown as Payload - - vi.mocked(getPayload).mockResolvedValue(noFallbackPayload) - await useRealCreatePayloadRequest() - - const result = await createPayloadRequestFromWebRequest({ - config: noFallbackConfig, - request: new Request('http://localhost/api/posts'), - }) - - expect(result.locale).toBeNull() - - result.data = { locale: 'de' } - addLocalesToRequestFromData(result) - - expect(result.locale).toBe('de') - }) - - it('preserves fallback locale arrays through common request initialization', async () => { - await useRealCreatePayloadRequest() - - const result = await createPayloadRequestFromWebRequest({ - config, - request: new Request( - 'http://localhost/api/posts?locale=de&fallbackLocale[]=en&fallbackLocale[]=de', - ), - }) - - expect(result.fallbackLocale).toEqual(['en', 'de']) - }) -}) From 1e5a481336cba8d3b1edcc5f6b970966c526b186 Mon Sep 17 00:00:00 2001 From: Jake Fletcher Date: Thu, 24 Sep 2026 17:45:09 -0400 Subject: [PATCH 15/15] fix(codemod): preserve migrated request helper bindings --- .../index.spec.ts | 37 +++++++++++++++++++ .../migrate-payload-request-creation/index.ts | 8 ++-- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/index.spec.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/index.spec.ts index e938fc0ca29..c8e22d65a1d 100644 --- a/packages/codemod/src/transforms/migrate-payload-request-creation/index.spec.ts +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/index.spec.ts @@ -55,6 +55,43 @@ describe('migrate-payload-request-creation', () => { }, ) + it.each([ + `const args = { payload, user }; const req = createPayloadRequest(args)`, + `const args = { payload }; const req = createPayloadRequest({ ...args })`, + `export const makeRequest = createPayloadRequest; const req = createPayloadRequest({ payload })`, + `function build(args: { payload: unknown }) { return createPayloadRequest(args) }`, + ])('should preserve current request creation: %s', async (body) => { + const source = `import { createPayloadRequest } from 'payload'\n${body}` + const project = new Project({ useInMemoryFileSystem: true }) + const file = project.createSourceFile('/current.ts', source) + + const result = await migratePayloadRequestCreation.apply({ packageJsons: [], project }) + + expect(file.getFullText()).toBe(source) + expect(result.filesChanged).toEqual([]) + expect(await runTransform({ source, transform: migratePayloadRequestCreation })).toBe(source) + }) + + it('should preserve an aliased current request creator with variable arguments', async () => { + const source = `import { createPayloadRequest as local } from 'payload' +const args = { payload, user } +const req = local(args)` + + expect(await runTransform({ source, transform: migratePayloadRequestCreation })).toBe(source) + }) + + it('should migrate legacy web request creation with variable arguments', async () => { + const source = `import { createPayloadRequest } from 'payload' +const args = { config, request } +const req = createPayloadRequest(args)` + + expect(await runTransform({ source, transform: migratePayloadRequestCreation })).toBe( + `import { createPayloadRequestFromWebRequest } from 'payload' +const args = { config, request } +const req = createPayloadRequestFromWebRequest(args)`, + ) + }) + it('should register the transform', () => { expect(transforms).toContain(migratePayloadRequestCreation) }) diff --git a/packages/codemod/src/transforms/migrate-payload-request-creation/index.ts b/packages/codemod/src/transforms/migrate-payload-request-creation/index.ts index 0694eae7146..e7b3ff9deaa 100644 --- a/packages/codemod/src/transforms/migrate-payload-request-creation/index.ts +++ b/packages/codemod/src/transforms/migrate-payload-request-creation/index.ts @@ -100,8 +100,7 @@ export const migratePayloadRequestCreation: Transform = { source === 'payload' && importedName === 'createPayloadRequest' && !isAliasedExportMigrationArtifact && - references.length > 0 && - references.every(isPayloadRequestCreationReference) + references.some(isPayloadRequestCreationReference) if (isCurrentPayloadRequestCreator) { if (!hadAlias) { @@ -238,7 +237,10 @@ function isPayloadRequestCreationReference(reference: Node): boolean { const [argument] = parent.getArguments() - return Node.isObjectLiteralExpression(argument) && Boolean(argument.getProperty('payload')) + return ( + (Node.isObjectLiteralExpression(argument) && Boolean(argument.getProperty('payload'))) || + Boolean(argument?.getType().getProperty('payload')) + ) } function getUnsafeOptionsReason({ options }: { options: Node }): string | undefined {