Compare commits

..

5 Commits

Author SHA1 Message Date
Alessio Gravili
a8e2a835cc fix the issue 2025-08-12 23:52:59 -07:00
Alessio Gravili
b194ffc504 add tests 2025-08-12 23:27:03 -07:00
Alessio Gravili
d2ca782a3d Merge remote-tracking branch 'origin/main' into feat/$push 2025-08-12 23:17:42 -07:00
Alessio Gravili
f61e7d06b0 postgres support 2025-08-12 23:16:22 -07:00
Alessio Gravili
e84f43fca1 mongo, int tests 2025-08-11 16:25:21 -07:00
464 changed files with 1857 additions and 8000 deletions

View File

@@ -6,6 +6,7 @@ on:
- opened
- reopened
- synchronize
- labeled
push:
branches:
- main
@@ -370,7 +371,6 @@ jobs:
# report-tag: ${{ matrix.suite }}
# job-summary: true
# This is unused, keeping it here for reference and possibly enabling in the future
tests-e2e-turbo:
runs-on: ubuntu-24.04
needs: [changes, build]

2
.gitignore vendored
View File

@@ -331,7 +331,5 @@ test/databaseAdapter.js
test/.localstack
test/google-cloud-storage
test/azurestoragedata/
/media-without-delete-access
licenses.csv

View File

@@ -107,7 +107,6 @@ The following options are available:
| `suppressHydrationWarning` | If set to `true`, suppresses React hydration mismatch warnings during the hydration of the root `<html>` tag. Defaults to `false`. |
| `theme` | Restrict the Admin Panel theme to use only one of your choice. Default is `all`. |
| `timezones` | Configure the timezone settings for the admin panel. [More details](#timezones) |
| `toast` | Customize the handling of toast messages within the Admin Panel. [More details](#toasts) |
| `user` | The `slug` of the Collection that you want to allow to login to the Admin Panel. [More details](#the-admin-user-collection). |
<Banner type="success">
@@ -299,20 +298,3 @@ We validate the supported timezones array by checking the value against the list
`timezone: true`. See [Date Fields](../fields/overview#date) for more
information.
</Banner>
## Toast
The `admin.toast` configuration allows you to customize the handling of toast messages within the Admin Panel, such as increasing the duration they are displayed and limiting the number of visible toasts at once.
<Banner type="info">
**Note:** The Admin Panel currently uses the
[Sonner](https://sonner.emilkowal.ski) library for toast notifications.
</Banner>
The following options are available for the `admin.toast` configuration:
| Option | Description | Default |
| ---------- | ---------------------------------------------------------------------------------------------------------------- | ------- |
| `duration` | The length of time (in milliseconds) that a toast message is displayed. | `4000` |
| `expand` | If `true`, will expand the message stack so that all messages are shown simultaneously without user interaction. | `false` |
| `limit` | The maximum number of toasts that can be visible on the screen at once. | `5` |

View File

@@ -33,7 +33,7 @@ export const Users: CollectionConfig = {
}
```
![Authentication Admin Panel functionality](https://payloadcms.com/images/docs/auth-overview.jpg)
![Authentication Admin Panel functionality](https://payloadcms.com/images/docs/auth-admin.jpg)
_Admin Panel screenshot depicting an Admins Collection with Auth enabled_
## Config Options

View File

@@ -141,7 +141,7 @@ The following options are available:
| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). |
| `components` | Swap in your own React components to be used within this Collection. [More details](#custom-components). |
| `listSearchableFields` | Specify which fields should be searched in the List search view. [More details](#list-searchable-fields). |
| `pagination` | Set pagination-specific options for this Collection in the List View. [More details](#pagination). |
| `pagination` | Set pagination-specific options for this Collection. [More details](#pagination). |
| `baseFilter` | Defines a default base filter which will be applied to the List View (along with any other filters applied by the user) and internal links in Lexical Editor, |
<Banner type="warning">

View File

@@ -158,7 +158,7 @@ export function MyCustomView(props: AdminViewServerProps) {
<Banner type="success">
**Tip:** For consistent layout and navigation, you may want to wrap your
Custom View with one of the built-in [Templates](./overview#templates).
Custom View with one of the built-in [Template](./overview#templates).
</Banner>
### View Templates

View File

@@ -293,6 +293,7 @@ Here's an example of a custom `editMenuItems` component:
```tsx
import React from 'react'
import { PopupList } from '@payloadcms/ui'
import type { EditMenuItemsServerProps } from 'payload'
@@ -300,12 +301,12 @@ export const EditMenuItems = async (props: EditMenuItemsServerProps) => {
const href = `/custom-action?id=${props.id}`
return (
<>
<a href={href}>Custom Edit Menu Item</a>
<a href={href}>
<PopupList.ButtonGroup>
<PopupList.Button href={href}>Custom Edit Menu Item</PopupList.Button>
<PopupList.Button href={href}>
Another Custom Edit Menu Item - add as many as you need!
</a>
</>
</PopupList.Button>
</PopupList.ButtonGroup>
)
}
```

View File

@@ -63,22 +63,3 @@ export const MyCollection: CollectionConfig = {
],
}
```
## Localized fields and MongoDB indexes
When you set `index: true` or `unique: true` on a localized field, MongoDB creates one index **per locale path** (e.g., `slug.en`, `slug.da-dk`, etc.). With many locales and indexed fields, this can quickly approach MongoDB's per-collection index limit.
If you know you'll query specifically by a locale, index only those locale paths using the collection-level `indexes` option instead of setting `index: true` on the localized field. This approach gives you more control and helps avoid unnecessary indexes.
```ts
import type { CollectionConfig } from 'payload'
export const Pages: CollectionConfig = {
fields: [{ name: 'slug', type: 'text', localized: true }],
indexes: [
// Index English slug only (rather than all locales)
{ fields: ['slug.en'] },
// You could also make it unique:
// { fields: ['slug.en'], unique: true },
],
}
```

View File

@@ -60,21 +60,21 @@ You can access Mongoose models as follows:
## Using other MongoDB implementations
You can import the `compatibilityOptions` object to get the recommended settings for other MongoDB implementations. Since these databases aren't officially supported by payload, you may still encounter issues even with these settings (please create an issue or PR if you believe these options should be updated):
You can import the `compatabilityOptions` object to get the recommended settings for other MongoDB implementations. Since these databases aren't officially supported by payload, you may still encounter issues even with these settings (please create an issue or PR if you believe these options should be updated):
```ts
import { mongooseAdapter, compatibilityOptions } from '@payloadcms/db-mongodb'
import { mongooseAdapter, compatabilityOptions } from '@payloadcms/db-mongodb'
export default buildConfig({
db: mongooseAdapter({
url: process.env.DATABASE_URI,
// For example, if you're using firestore:
...compatibilityOptions.firestore,
...compatabilityOptions.firestore,
}),
})
```
We export compatibility options for [DocumentDB](https://aws.amazon.com/documentdb/), [Azure Cosmos DB](https://azure.microsoft.com/en-us/products/cosmos-db) and [Firestore](https://cloud.google.com/firestore/mongodb-compatibility/docs/overview). Known limitations:
We export compatability options for [DocumentDB](https://aws.amazon.com/documentdb/), [Azure Cosmos DB](https://azure.microsoft.com/en-us/products/cosmos-db) and [Firestore](https://cloud.google.com/firestore/mongodb-compatibility/docs/overview). Known limitations:
- Azure Cosmos DB does not support transactions that update two or more documents in different collections, which is a common case when using Payload (via hooks).
- Azure Cosmos DB the root config property `indexSortableFields` must be set to `true`.

View File

@@ -81,7 +81,7 @@ To install a Database Adapter, you can run **one** of the following commands:
#### 2. Copy Payload files into your Next.js app folder
Payload installs directly in your Next.js `/app` folder, and you'll need to place some files into that folder for Payload to run. You can copy these files from the [Blank Template](https://github.com/payloadcms/payload/tree/main/templates/blank/src/app/%28payload%29) on GitHub. Once you have the required Payload files in place in your `/app` folder, you should have something like this:
Payload installs directly in your Next.js `/app` folder, and you'll need to place some files into that folder for Payload to run. You can copy these files from the [Blank Template](<https://github.com/payloadcms/payload/tree/main/templates/blank/src/app/(payload)>) on GitHub. Once you have the required Payload files in place in your `/app` folder, you should have something like this:
```plaintext
app/

View File

@@ -162,11 +162,6 @@ const result = await payload.find({
})
```
<Banner type="info">
`pagination`, `page`, and `limit` are three related properties [documented
here](/docs/queries/pagination).
</Banner>
### Find by ID#collection-find-by-id
```js

View File

@@ -207,7 +207,7 @@ Everything mentioned above applies to local development as well, but there are a
### Enable Turbopack
<Banner type="warning">
**Note:** In the future this will be the default. Use at your own risk.
**Note:** In the future this will be the default. Use as your own risk.
</Banner>
Add `--turbo` to your dev script to significantly speed up your local development server start time.

View File

@@ -80,11 +80,6 @@ type MultiTenantPluginConfig<ConfigTypes = unknown> = {
* @default false
*/
isGlobal?: boolean
/**
* Opt out of adding the tenant field and place
* it manually using the `tenantField` export from the plugin
*/
customTenantField?: boolean
/**
* Overrides for the tenant field, will override the entire tenantField configuration
*/

View File

@@ -148,12 +148,6 @@ export const Pages: CollectionConfig<'pages'> = {
}
```
<VideoDrawer
id="Snqjng_w-QU"
label="Watch default populate in action"
drawerTitle="How to easily optimize Payload CMS requests with defaultPopulate"
/>
<Banner type="warning">
**Important:** When using `defaultPopulate` on a collection with
[Uploads](/docs/fields/upload) enabled and you want to select the `url` field,

View File

@@ -13,8 +13,8 @@ keywords: uploads, images, media, overview, documentation, Content Management Sy
</Banner>
<LightDarkImage
srcLight="https://payloadcms.com/images/docs/uploads-overview.jpg"
srcDark="https://payloadcms.com/images/docs/uploads-overview.jpg"
srcLight="https://payloadcms.com/images/docs/upload-admin.jpg"
srcDark="https://payloadcms.com/images/docs/upload-admin.jpg"
alt="Shows an Upload enabled collection in the Payload Admin Panel"
caption="Admin Panel screenshot depicting a Media Collection with Upload enabled"
/>

View File

@@ -12,7 +12,7 @@ Extending on Payload's [Draft](/docs/versions/drafts) functionality, you can con
Autosave relies on Versions and Drafts being enabled in order to function.
</Banner>
![Autosave Enabled](/images/docs/autosave-v3.jpg)
![Autosave Enabled](/images/docs/autosave-enabled.png)
_If Autosave is enabled, drafts will be created automatically as the document is modified and the Admin UI adds an indicator describing when the document was last saved to the top right of the sidebar._
## Options

View File

@@ -14,7 +14,7 @@ Payload's Draft functionality builds on top of the Versions functionality to all
By enabling Versions with Drafts, your collections and globals can maintain _newer_, and _unpublished_ versions of your documents. It's perfect for cases where you might want to work on a document, update it and save your progress, but not necessarily make it publicly published right away. Drafts are extremely helpful when building preview implementations.
![Drafts Enabled](/images/docs/autosave-drafts.jpg)
![Drafts Enabled](/images/docs/drafts-enabled.png)
_If Drafts are enabled, the typical Save button is replaced with new actions which allow you to either save a draft, or publish your changes._
## Options

View File

@@ -13,7 +13,7 @@ keywords: version history, revisions, audit log, draft, publish, restore, autosa
When enabled, Payload will automatically scaffold a new Collection in your database to store versions of your document(s) over time, and the Admin UI will be extended with additional views that allow you to browse document versions, view diffs in order to see exactly what has changed in your documents (and when they changed), and restore documents back to prior versions easily.
![Versions](/images/docs/versions-v3.jpg)
![Versions](/images/docs/versions.png)
_Comparing an old version to a newer version of a document_
**With Versions, you can:**

View File

@@ -1,6 +1,6 @@
{
"name": "payload-monorepo",
"version": "3.54.0",
"version": "3.50.0",
"private": true,
"type": "module",
"workspaces": [

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/admin-bar",
"version": "3.54.0",
"version": "3.50.0",
"description": "An admin bar for React apps using Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "create-payload-app",
"version": "3.54.0",
"version": "3.50.0",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-mongodb",
"version": "3.54.0",
"version": "3.50.0",
"description": "The officially supported MongoDB database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -35,12 +35,7 @@ export const connect: Connect = async function connect(
}
try {
if (!this.connection) {
this.connection = await mongoose.createConnection(urlToConnect, connectionOptions).asPromise()
}
await this.connection.openUri(urlToConnect, connectionOptions)
this.connection = (await mongoose.connect(urlToConnect, connectionOptions)).connection
if (this.useAlternativeDropDatabase) {
if (this.connection.db) {
// Firestore doesn't support dropDatabase, so we monkey patch
@@ -80,8 +75,7 @@ export const connect: Connect = async function connect(
if (!hotReload) {
if (process.env.PAYLOAD_DROP_DATABASE === 'true') {
this.payload.logger.info('---- DROPPING DATABASE ----')
await this.connection.dropDatabase()
await mongoose.connection.dropDatabase()
this.payload.logger.info('---- DROPPED DATABASE ----')
}
}

View File

@@ -17,16 +17,10 @@ export const create: Create = async function create(
const options: CreateOptions = {
session: await getSession(this, req),
// Timestamps are manually added by the write transform
timestamps: false,
}
let doc
if (!data.createdAt) {
data.createdAt = new Date().toISOString()
}
transform({
adapter: this,
data,

View File

@@ -14,10 +14,6 @@ export const createGlobal: CreateGlobal = async function createGlobal(
) {
const { globalConfig, Model } = getGlobal({ adapter: this, globalSlug })
if (!data.createdAt) {
;(data as any).createdAt = new Date().toISOString()
}
transform({
adapter: this,
data,
@@ -28,8 +24,6 @@ export const createGlobal: CreateGlobal = async function createGlobal(
const options: CreateOptions = {
session: await getSession(this, req),
// Timestamps are manually added by the write transform
timestamps: false,
}
let [result] = (await Model.create([data], options)) as any

View File

@@ -25,8 +25,6 @@ export const createGlobalVersion: CreateGlobalVersion = async function createGlo
const options = {
session: await getSession(this, req),
// Timestamps are manually added by the write transform
timestamps: false,
}
const data = {
@@ -39,9 +37,6 @@ export const createGlobalVersion: CreateGlobalVersion = async function createGlo
updatedAt,
version: versionData,
}
if (!data.createdAt) {
data.createdAt = new Date().toISOString()
}
const fields = buildVersionGlobalFields(this.payload.config, globalConfig)

View File

@@ -29,8 +29,6 @@ export const createVersion: CreateVersion = async function createVersion(
const options = {
session: await getSession(this, req),
// Timestamps are manually added by the write transform
timestamps: false,
}
const data = {
@@ -43,9 +41,6 @@ export const createVersion: CreateVersion = async function createVersion(
updatedAt,
version: versionData,
}
if (!data.createdAt) {
data.createdAt = new Date().toISOString()
}
const fields = buildVersionCollectionFields(this.payload.config, collectionConfig)

View File

@@ -1,11 +1,11 @@
import type { Destroy } from 'payload'
import mongoose from 'mongoose'
import type { MongooseAdapter } from './index.js'
export const destroy: Destroy = async function destroy(this: MongooseAdapter) {
await this.connection.close()
await mongoose.disconnect()
for (const name of Object.keys(this.connection.models)) {
this.connection.deleteModel(name)
}
Object.keys(mongoose.models).map((model) => mongoose.deleteModel(model))
}

View File

@@ -331,7 +331,7 @@ export function mongooseAdapter({
}
}
export { compatibilityOptions } from './utilities/compatibilityOptions.js'
export { compatabilityOptions } from './utilities/compatabilityOptions.js'
/**
* Attempt to find migrations directory.

View File

@@ -19,14 +19,11 @@ import { getBuildQueryPlugin } from './queries/getBuildQueryPlugin.js'
import { getDBName } from './utilities/getDBName.js'
export const init: Init = function init(this: MongooseAdapter) {
// Always create a scoped, **unopened** connection object
// (no URI here; models compile per-connection and do not require an open socket)
this.connection ??= mongoose.createConnection()
this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => {
const schemaOptions = this.collectionsSchemaOptions?.[collection.slug]
const schema = buildCollectionSchema(collection, this.payload, schemaOptions)
if (collection.versions) {
const versionModelName = getDBName({ config: collection, versions: true })
@@ -58,7 +55,7 @@ export const init: Init = function init(this: MongooseAdapter) {
const versionCollectionName =
this.autoPluralization === true && !collection.dbName ? undefined : versionModelName
this.versions[collection.slug] = this.connection.model(
this.versions[collection.slug] = mongoose.model(
versionModelName,
versionSchema,
versionCollectionName,
@@ -69,14 +66,14 @@ export const init: Init = function init(this: MongooseAdapter) {
const collectionName =
this.autoPluralization === true && !collection.dbName ? undefined : modelName
this.collections[collection.slug] = this.connection.model<any>(
this.collections[collection.slug] = mongoose.model<any>(
modelName,
schema,
collectionName,
) as CollectionModel
})
this.globals = buildGlobalModel(this) as GlobalModel
this.globals = buildGlobalModel(this.payload) as GlobalModel
this.payload.config.globals.forEach((global) => {
if (global.versions) {
@@ -104,7 +101,7 @@ export const init: Init = function init(this: MongooseAdapter) {
}),
)
this.versions[global.slug] = this.connection.model<any>(
this.versions[global.slug] = mongoose.model<any>(
versionModelName,
versionSchema,
versionModelName,

View File

@@ -1,13 +1,14 @@
import type { Payload } from 'payload'
import mongoose from 'mongoose'
import type { MongooseAdapter } from '../index.js'
import type { GlobalModel } from '../types.js'
import { getBuildQueryPlugin } from '../queries/getBuildQueryPlugin.js'
import { buildSchema } from './buildSchema.js'
export const buildGlobalModel = (adapter: MongooseAdapter): GlobalModel | null => {
if (adapter.payload.config.globals && adapter.payload.config.globals.length > 0) {
export const buildGlobalModel = (payload: Payload): GlobalModel | null => {
if (payload.config.globals && payload.config.globals.length > 0) {
const globalsSchema = new mongoose.Schema(
{},
{ discriminatorKey: 'globalType', minimize: false, timestamps: true },
@@ -15,13 +16,9 @@ export const buildGlobalModel = (adapter: MongooseAdapter): GlobalModel | null =
globalsSchema.plugin(getBuildQueryPlugin())
const Globals = adapter.connection.model(
'globals',
globalsSchema,
'globals',
) as unknown as GlobalModel
const Globals = mongoose.model('globals', globalsSchema, 'globals') as unknown as GlobalModel
Object.values(adapter.payload.config.globals).forEach((globalConfig) => {
Object.values(payload.config.globals).forEach((globalConfig) => {
const globalSchema = buildSchema({
buildSchemaOptions: {
options: {
@@ -29,7 +26,7 @@ export const buildGlobalModel = (adapter: MongooseAdapter): GlobalModel | null =
},
},
configFields: globalConfig.fields,
payload: adapter.payload,
payload,
})
Globals.discriminator(globalConfig.slug, globalSchema)
})

View File

@@ -63,10 +63,7 @@ const migrateModelWithBatching = async ({
},
},
})),
{
session, // Timestamps are manually added by the write transform
timestamps: false,
},
{ session },
)
skip += batchSize

View File

@@ -26,8 +26,6 @@ export const updateGlobal: UpdateGlobal = async function updateGlobal(
select,
}),
session: await getSession(this, req),
// Timestamps are manually added by the write transform
timestamps: false,
}
transform({ adapter: this, data, fields, globalSlug, operation: 'write' })

View File

@@ -39,8 +39,6 @@ export async function updateGlobalVersion<T extends TypeWithID>(
select,
}),
session: await getSession(this, req),
// Timestamps are manually added by the write transform
timestamps: false,
}
const query = await buildQuery({

View File

@@ -1,4 +1,4 @@
import type { MongooseUpdateQueryOptions, UpdateQuery } from 'mongoose'
import type { MongooseUpdateQueryOptions } from 'mongoose'
import type { Job, UpdateJobs, Where } from 'payload'
import type { MongooseAdapter } from './index.js'
@@ -14,13 +14,9 @@ export const updateJobs: UpdateJobs = async function updateMany(
this: MongooseAdapter,
{ id, data, limit, req, returning, sort: sortArg, where: whereArg },
) {
if (
!(data?.log as object[])?.length &&
!(data.log && typeof data.log === 'object' && '$push' in data.log)
) {
if (!(data?.log as object[])?.length) {
delete data.log
}
const where = id ? { id: { equals: id } } : (whereArg as Where)
const { collectionConfig, Model } = getCollection({
@@ -40,8 +36,6 @@ export const updateJobs: UpdateJobs = async function updateMany(
lean: true,
new: true,
session: await getSession(this, req),
// Timestamps are manually added by the write transform
timestamps: false,
}
let query = await buildQuery({
@@ -51,44 +45,17 @@ export const updateJobs: UpdateJobs = async function updateMany(
where,
})
let updateData: UpdateQuery<any> = data
const $inc: Record<string, number> = {}
const $push: Record<string, { $each: any[] } | any> = {}
transform({
$inc,
$push,
adapter: this,
data,
fields: collectionConfig.fields,
operation: 'write',
})
const updateOps: UpdateQuery<any> = {}
if (Object.keys($inc).length) {
updateOps.$inc = $inc
}
if (Object.keys($push).length) {
updateOps.$push = $push
}
if (Object.keys(updateOps).length) {
updateOps.$set = updateData
updateData = updateOps
}
transform({ adapter: this, data, fields: collectionConfig.fields, operation: 'write' })
let result: Job[] = []
try {
if (id) {
if (returning === false) {
await Model.updateOne(query, updateData, options)
transform({ adapter: this, data, fields: collectionConfig.fields, operation: 'read' })
await Model.updateOne(query, data, options)
return null
} else {
const doc = await Model.findOneAndUpdate(query, updateData, options)
const doc = await Model.findOneAndUpdate(query, data, options)
result = doc ? [doc] : []
}
} else {
@@ -105,7 +72,7 @@ export const updateJobs: UpdateJobs = async function updateMany(
query = { _id: { $in: documentsToUpdate.map((doc) => doc._id) } }
}
await Model.updateMany(query, updateData, options)
await Model.updateMany(query, data, options)
if (returning === false) {
return null

View File

@@ -58,8 +58,6 @@ export const updateMany: UpdateMany = async function updateMany(
select,
}),
session: await getSession(this, req),
// Timestamps are manually added by the write transform
timestamps: false,
}
let query = await buildQuery({

View File

@@ -38,8 +38,6 @@ export const updateOne: UpdateOne = async function updateOne(
select,
}),
session: await getSession(this, req),
// Timestamps are manually added by the write transform
timestamps: false,
}
const query = await buildQuery({
@@ -58,18 +56,11 @@ export const updateOne: UpdateOne = async function updateOne(
const $push: Record<string, { $each: any[] } | any> = {}
transform({ $inc, $push, adapter: this, data, fields, operation: 'write' })
const updateOps: UpdateQuery<any> = {}
if (Object.keys($inc).length) {
updateOps.$inc = $inc
updateData = { $inc, $set: updateData }
}
if (Object.keys($push).length) {
updateOps.$push = $push
}
if (Object.keys(updateOps).length) {
updateOps.$set = updateData
updateData = updateOps
updateData = { $push, $set: updateData }
}
try {

View File

@@ -45,8 +45,6 @@ export const updateVersion: UpdateVersion = async function updateVersion(
select,
}),
session: await getSession(this, req),
// Timestamps are manually added by the write transform
timestamps: false,
}
const query = await buildQuery({

View File

@@ -2,9 +2,9 @@ import type { Args } from '../index.js'
/**
* Each key is a mongo-compatible database and the value
* is the recommended `mongooseAdapter` settings for compatibility.
* is the recommended `mongooseAdapter` settings for compatability.
*/
export const compatibilityOptions = {
export const compatabilityOptions = {
cosmosdb: {
transactionOptions: false,
useJoinAggregations: false,
@@ -12,7 +12,6 @@ export const compatibilityOptions = {
},
documentdb: {
disableIndexHints: true,
useJoinAggregations: false,
},
firestore: {
disableIndexHints: true,

View File

@@ -395,10 +395,6 @@ describe('transform', () => {
data,
fields: config.collections[0].fields,
})
if ('updatedAt' in data) {
delete data.updatedAt
}
const flattenValuesAfter = Object.values(flattenRelationshipValues(data))
flattenValuesAfter.forEach((value, i) => {

View File

@@ -492,24 +492,11 @@ export const transform = ({
if (value && typeof value === 'object' && '$push' in value) {
const push = value.$push
if (config.localization && fieldShouldBeLocalized({ field, parentIsLocalized })) {
if (typeof push === 'object' && push !== null) {
Object.entries(push).forEach(([localeKey, localeData]) => {
if (Array.isArray(localeData)) {
$push[`${parentPath}${field.name}.${localeKey}`] = { $each: localeData }
} else if (typeof localeData === 'object') {
$push[`${parentPath}${field.name}.${localeKey}`] = localeData
}
})
}
} else {
if (Array.isArray(push)) {
$push[`${parentPath}${field.name}`] = { $each: push }
} else if (typeof push === 'object') {
$push[`${parentPath}${field.name}`] = push
}
if (Array.isArray(push)) {
$push[`${parentPath}${field.name}`] = { $each: push }
} else if (typeof push === 'object') {
$push[`${parentPath}${field.name}`] = push
}
delete ref[field.name]
}
}
@@ -592,15 +579,4 @@ export const transform = ({
parentIsLocalized,
ref: data,
})
if (operation === 'write') {
if (typeof data.updatedAt === 'undefined') {
// If data.updatedAt is explicitly set to `null` we should not set it - this means we don't want to change the value of updatedAt.
data.updatedAt = new Date().toISOString()
} else if (data.updatedAt === null) {
// `updatedAt` may be explicitly set to null to disable updating it - if that is the case, we need to delete the property. Keeping it as null will
// cause the database to think we want to set it to null, which we don't.
delete data.updatedAt
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-postgres",
"version": "3.54.0",
"version": "3.50.0",
"description": "The officially supported Postgres database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-sqlite",
"version": "3.54.0",
"version": "3.50.0",
"description": "The officially supported SQLite database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-vercel-postgres",
"version": "3.54.0",
"version": "3.50.0",
"description": "Vercel Postgres adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/drizzle",
"version": "3.54.0",
"version": "3.50.0",
"description": "A library of shared functions used by different payload database adapters",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -791,14 +791,9 @@ export const traverseFields = ({
} else {
shouldSelect = true
}
const tableName = fieldShouldBeLocalized({ field, parentIsLocalized })
? `${currentTableName}${adapter.localesSuffix}`
: currentTableName
if (shouldSelect) {
args.extras[name] = sql
.raw(`ST_AsGeoJSON("${adapter.tables[tableName][name].name}")::jsonb`)
.as(name)
args.extras[name] = sql.raw(`ST_AsGeoJSON(${toSnakeCase(name)})::jsonb`).as(name)
}
break
}

View File

@@ -129,23 +129,8 @@ export const traverseFields = ({
const arrayTableName = adapter.tableNameMap.get(`${parentTableName}_${columnName}`)
if (isLocalized) {
let value: {
[locale: string]: unknown[]
} = data[field.name] as any
let push = false
if (typeof value === 'object' && '$push' in value) {
value = value.$push as any
push = true
}
if (typeof value === 'object' && value !== null) {
Object.entries(value).forEach(([localeKey, _localeData]) => {
let localeData = _localeData
if (push && !Array.isArray(localeData)) {
localeData = [localeData]
}
if (typeof data[field.name] === 'object' && data[field.name] !== null) {
Object.entries(data[field.name]).forEach(([localeKey, localeData]) => {
if (Array.isArray(localeData)) {
const newRows = transformArray({
adapter,
@@ -167,25 +152,22 @@ export const traverseFields = ({
textsToDelete,
withinArrayOrBlockLocale: localeKey,
})
if (push) {
if (!arraysToPush[arrayTableName]) {
arraysToPush[arrayTableName] = []
}
arraysToPush[arrayTableName] = arraysToPush[arrayTableName].concat(newRows)
} else {
if (!arrays[arrayTableName]) {
arrays[arrayTableName] = []
}
arrays[arrayTableName] = arrays[arrayTableName].concat(newRows)
if (!arrays[arrayTableName]) {
arrays[arrayTableName] = []
}
arrays[arrayTableName] = arrays[arrayTableName].concat(newRows)
}
})
}
} else {
let value = data[field.name]
let push = false
if (typeof value === 'object' && '$push' in value) {
if (
// TODO do this for localized as well in DRY way
typeof value === 'object' &&
'$push' in value
) {
value = Array.isArray(value.$push) ? value.$push : [value.$push]
push = true
}
@@ -585,19 +567,6 @@ export const traverseFields = ({
valuesToTransform.forEach(({ localeKey, ref, value }) => {
let formattedValue = value
if (field.type === 'date') {
if (fieldName === 'updatedAt' && typeof formattedValue === 'undefined') {
// let the db handle this. If formattedValue is explicitly set to `null` we should not set it - this means we don't want to change the value of updatedAt.
formattedValue = new Date().toISOString()
} else {
if (typeof value === 'number' && !Number.isNaN(value)) {
formattedValue = new Date(value).toISOString()
} else if (value instanceof Date) {
formattedValue = value.toISOString()
}
}
}
if (typeof value !== 'undefined') {
if (value && field.type === 'point' && adapter.name !== 'sqlite') {
formattedValue = sql`ST_GeomFromGeoJSON(${JSON.stringify(value)})`
@@ -622,6 +591,19 @@ export const traverseFields = ({
formattedValue = sql.raw(`${columnName} + ${value.$inc}`)
}
if (field.type === 'date') {
if (typeof value === 'number' && !Number.isNaN(value)) {
formattedValue = new Date(value).toISOString()
} else if (value instanceof Date) {
formattedValue = value.toISOString()
}
}
}
if (field.type === 'date' && fieldName === 'updatedAt') {
// let the db handle this
formattedValue = new Date().toISOString()
}
if (typeof formattedValue !== 'undefined') {

View File

@@ -42,21 +42,15 @@ export const upsertRow = async <T extends Record<string, unknown> | TypeWithID>(
upsertTarget,
where,
}: Args): Promise<T> => {
if (operation === 'create' && !data.createdAt) {
data.createdAt = new Date().toISOString()
}
let insertedRow: Record<string, unknown> = { id }
if (id && shouldUseOptimizedUpsertRow({ data, fields })) {
const transformedForWrite = transformForWrite({
const { arraysToPush, row } = transformForWrite({
adapter,
data,
enableAtomicWrites: true,
fields,
tableName,
})
const { row } = transformedForWrite
const { arraysToPush } = transformedForWrite
const drizzle = db as LibSQLDatabase
@@ -72,19 +66,10 @@ export const upsertRow = async <T extends Record<string, unknown> | TypeWithID>(
})
}
// If row.updatedAt is not set, delete it to avoid triggering hasDataToUpdate. `updatedAt` may be explicitly set to null to
// disable triggering hasDataToUpdate.
if (typeof row.updatedAt === 'undefined' || row.updatedAt === null) {
delete row.updatedAt
}
const hasDataToUpdate = row && Object.keys(row)?.length
// Then, handle regular row update
if (ignoreResult) {
if (hasDataToUpdate) {
// Only update row if there is something to update.
// Example: if the data only consists of a single $push, calling insertArrays is enough - we don't need to update the row.
if (row && Object.keys(row).length) {
await drizzle
.update(adapter.tables[tableName])
.set(row)
@@ -105,7 +90,7 @@ export const upsertRow = async <T extends Record<string, unknown> | TypeWithID>(
const findManyKeysLength = Object.keys(findManyArgs).length
const hasOnlyColumns = Object.keys(findManyArgs.columns || {}).length > 0
if (!hasDataToUpdate) {
if (!row || !Object.keys(row).length) {
// Nothing to update => just fetch current row and return
findManyArgs.where = eq(adapter.tables[tableName].id, insertedRow.id)

View File

@@ -9,12 +9,7 @@ export const buildIndexName = ({
name: string
number?: number
}): string => {
let indexName = `${name}${number ? `_${number}` : ''}_idx`
if (indexName.length > 60) {
const suffix = `${number ? `_${number}` : ''}_idx`
indexName = `${name.slice(0, 60 - suffix.length)}${suffix}`
}
const indexName = `${name}${number ? `_${number}` : ''}_idx`
if (!adapter.indexes.has(indexName)) {
adapter.indexes.add(indexName)

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/email-nodemailer",
"version": "3.54.0",
"version": "3.50.0",
"description": "Payload Nodemailer Email Adapter",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/email-resend",
"version": "3.54.0",
"version": "3.50.0",
"description": "Payload Resend Email Adapter",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/graphql",
"version": "3.54.0",
"version": "3.50.0",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -22,7 +22,6 @@ export const formatName = (string: string): string => {
.replace(/\)/g, '_')
.replace(/'/g, '_')
.replace(/ /g, '')
.replace(/\[|\]/g, '_')
return formatted || '_'
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview-react",
"version": "3.54.0",
"version": "3.50.0",
"description": "The official React SDK for Payload Live Preview",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview-vue",
"version": "3.54.0",
"version": "3.50.0",
"description": "The official Vue SDK for Payload Live Preview",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview",
"version": "3.54.0",
"version": "3.50.0",
"description": "The official live preview JavaScript SDK for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/next",
"version": "3.54.0",
"version": "3.50.0",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -9,7 +9,7 @@ const handlerBuilder =
async (
request: Request,
args: {
params: Promise<{ slug?: string[] }>
params: Promise<{ slug: string[] }>
},
): Promise<Response> => {
const awaitedConfig = await config

View File

@@ -208,7 +208,7 @@ export const renderDocument = async ({
globalSlug,
locale: locale?.code,
operation,
readOnly: isTrashedDoc || isLocked,
readOnly: isTrashedDoc,
renderAllFields: true,
req,
schemaPath: collectionSlug || globalSlug,
@@ -333,7 +333,6 @@ export const renderDocument = async ({
}
const documentSlots = renderDocumentSlots({
id,
collectionConfig,
globalConfig,
hasSavePermission,

View File

@@ -1,5 +1,6 @@
import type {
BeforeDocumentControlsServerPropsOnly,
DefaultServerFunctionArgs,
DocumentSlots,
EditMenuItemsServerPropsOnly,
PayloadRequest,
@@ -26,11 +27,10 @@ export const renderDocumentSlots: (args: {
collectionConfig?: SanitizedCollectionConfig
globalConfig?: SanitizedGlobalConfig
hasSavePermission: boolean
id?: number | string
permissions: SanitizedDocumentPermissions
req: PayloadRequest
}) => DocumentSlots = (args) => {
const { id, collectionConfig, globalConfig, hasSavePermission, req } = args
const { collectionConfig, globalConfig, hasSavePermission, req } = args
const components: DocumentSlots = {} as DocumentSlots
@@ -39,7 +39,6 @@ export const renderDocumentSlots: (args: {
const isPreviewEnabled = collectionConfig?.admin?.preview || globalConfig?.admin?.preview
const serverProps: ServerProps = {
id,
i18n: req.i18n,
payload: req.payload,
user: req.user,
@@ -170,11 +169,10 @@ export const renderDocumentSlots: (args: {
return components
}
export const renderDocumentSlotsHandler: ServerFunction<{
collectionSlug: string
id?: number | string
}> = async (args) => {
const { id, collectionSlug, req } = args
export const renderDocumentSlotsHandler: ServerFunction<{ collectionSlug: string }> = async (
args,
) => {
const { collectionSlug, req } = args
const collectionConfig = req.payload.collections[collectionSlug]?.config
@@ -189,7 +187,6 @@ export const renderDocumentSlotsHandler: ServerFunction<{
})
return renderDocumentSlots({
id,
collectionConfig,
hasSavePermission,
permissions: docPermissions,

View File

@@ -15,16 +15,6 @@ import './index.scss'
const baseClass = 'logout'
/**
* This component should **just** be the inactivity route and do nothing with logging the user out.
*
* It currently handles too much, the auth provider should just log the user out and then
* we could remove the useEffect in this file. So instead of the logout button
* being an anchor link, it should be a button that calls `logOut` in the provider.
*
* This view is still useful if cookies attempt to refresh and fail, i.e. the user
* is logged out due to inactivity.
*/
export const LogoutClient: React.FC<{
adminRoute: string
inactivity?: boolean
@@ -36,11 +26,9 @@ export const LogoutClient: React.FC<{
const { startRouteTransition } = useRouteTransition()
const isLoggedIn = React.useMemo(() => {
return Boolean(user?.id)
}, [user?.id])
const [isLoggedOut, setIsLoggedOut] = React.useState<boolean>(!user)
const navigatingToLoginRef = React.useRef(false)
const logOutSuccessRef = React.useRef(false)
const [loginRoute] = React.useState(() =>
formatAdminURL({
@@ -57,25 +45,26 @@ export const LogoutClient: React.FC<{
const router = useRouter()
const handleLogOut = React.useCallback(async () => {
if (!inactivity && !navigatingToLoginRef.current) {
navigatingToLoginRef.current = true
await logOut()
const loggedOut = await logOut()
setIsLoggedOut(loggedOut)
if (!inactivity && loggedOut && !logOutSuccessRef.current) {
toast.success(t('authentication:loggedOutSuccessfully'))
logOutSuccessRef.current = true
startRouteTransition(() => router.push(loginRoute))
return
}
}, [inactivity, logOut, loginRoute, router, startRouteTransition, t])
useEffect(() => {
if (isLoggedIn) {
if (!isLoggedOut) {
void handleLogOut()
} else if (!navigatingToLoginRef.current) {
navigatingToLoginRef.current = true
} else {
startRouteTransition(() => router.push(loginRoute))
}
}, [handleLogOut, isLoggedIn, loginRoute, router, startRouteTransition])
}, [handleLogOut, isLoggedOut, loginRoute, router, startRouteTransition])
if (!isLoggedIn && inactivity) {
if (isLoggedOut && inactivity) {
return (
<div className={`${baseClass}__wrap`}>
<h2>{t('authentication:loggedOutInactivity')}</h2>

View File

@@ -90,7 +90,7 @@ export const SetStepNav: React.FC<{
}),
},
{
label: t('version:versions'),
label: 'Versions',
url: formatAdminURL({
adminRoute,
path: `${docBasePath}/versions`,
@@ -118,7 +118,7 @@ export const SetStepNav: React.FC<{
}),
},
{
label: t('version:versions'),
label: 'Versions',
url: formatAdminURL({
adminRoute,
path: `/globals/${globalSlug}/versions`,

View File

@@ -20,13 +20,13 @@ import {
import {
fieldIsID,
fieldShouldBeLocalized,
getFieldPaths,
getFieldPermissions,
getUniqueListBy,
tabHasName,
} from 'payload/shared'
import { diffComponents } from './fields/index.js'
import { getFieldPathsModified } from './utilities/getFieldPathsModified.js'
export type BuildVersionFieldsArgs = {
clientSchemaMap: ClientFieldSchemaMap
@@ -90,7 +90,7 @@ export const buildVersionFields = ({
continue
}
const { indexPath, path, schemaPath } = getFieldPaths({
const { indexPath, path, schemaPath } = getFieldPathsModified({
field,
index: fieldIndex,
parentIndexPath,
@@ -286,7 +286,7 @@ const buildVersionField = ({
indexPath: tabIndexPath,
path: tabPath,
schemaPath: tabSchemaPath,
} = getFieldPaths({
} = getFieldPathsModified({
field: tabAsField,
index: tabIndex,
parentIndexPath: indexPath,
@@ -322,18 +322,14 @@ const buildVersionField = ({
nestingLevel: nestingLevel + 1,
parentIndexPath: isNamedTab ? '' : tabIndexPath,
parentIsLocalized: parentIsLocalized || tab.localized,
parentPath: isNamedTab ? tabPath : 'name' in field ? path : parentPath,
parentSchemaPath: isNamedTab
? tabSchemaPath
: 'name' in field
? schemaPath
: parentSchemaPath,
parentPath: isNamedTab ? tabPath : path,
parentSchemaPath: isNamedTab ? tabSchemaPath : parentSchemaPath,
req,
selectedLocales,
versionFromSiblingData: 'name' in tab ? valueFrom?.[tab.name] : valueFrom,
versionToSiblingData: 'name' in tab ? valueTo?.[tab.name] : valueTo,
}).versionFields,
label: typeof tab.label === 'function' ? tab.label({ i18n, t: i18n.t }) : tab.label,
label: tab.label,
}
if (tabVersion?.fields?.length) {
baseVersionField.tabs.push(tabVersion)
@@ -374,8 +370,8 @@ const buildVersionField = ({
nestingLevel: nestingLevel + 1,
parentIndexPath: 'name' in field ? '' : indexPath,
parentIsLocalized: parentIsLocalized || field.localized,
parentPath: ('name' in field ? path : parentPath) + '.' + i,
parentSchemaPath: 'name' in field ? schemaPath : parentSchemaPath,
parentPath: path + '.' + i,
parentSchemaPath: schemaPath,
req,
selectedLocales,
versionFromSiblingData: fromRow,
@@ -473,8 +469,8 @@ const buildVersionField = ({
nestingLevel: nestingLevel + 1,
parentIndexPath: 'name' in field ? '' : indexPath,
parentIsLocalized: parentIsLocalized || ('localized' in field && field.localized),
parentPath: ('name' in field ? path : parentPath) + '.' + i,
parentSchemaPath: ('name' in field ? schemaPath : parentSchemaPath) + '.' + toBlock.slug,
parentPath: path + '.' + i,
parentSchemaPath: schemaPath + '.' + toBlock.slug,
req,
selectedLocales,
versionFromSiblingData: fromRow,

View File

@@ -25,7 +25,7 @@ export const Iterable: React.FC<FieldDiffClientProps> = ({
parentIsLocalized,
versionValue: valueTo,
}) => {
const { i18n, t } = useTranslation()
const { i18n } = useTranslation()
const { selectedLocales } = useSelectedLocales()
const { config } = useConfig()
@@ -73,9 +73,7 @@ export const Iterable: React.FC<FieldDiffClientProps> = ({
})
const rowNumber = String(i + 1).padStart(2, '0')
const rowLabel = fieldIsArrayType(field)
? `${t('general:item')} ${rowNumber}`
: `${t('fields:block')} ${rowNumber}`
const rowLabel = fieldIsArrayType(field) ? `Item ${rowNumber}` : `Block ${rowNumber}`
return (
<div className={`${baseClass}__row`} key={i}>

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/payload-cloud",
"version": "3.54.0",
"version": "3.50.0",
"description": "The official Payload Cloud plugin",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "payload",
"version": "3.54.0",
"version": "3.50.0",
"description": "Node, React, Headless CMS and Application Framework built on Next.js",
"keywords": [
"admin panel",

View File

@@ -25,7 +25,7 @@ type SelectFieldBaseClientProps = {
readonly onChange?: (e: string | string[]) => void
readonly path: string
readonly validate?: SelectFieldValidation
readonly value?: string | string[]
readonly value?: string
}
type SelectFieldBaseServerProps = Pick<FieldPaths, 'path'>

View File

@@ -11,7 +11,6 @@ export type Data = {
}
export type Row = {
addedByServer?: FieldState['addedByServer']
blockType?: string
collapsed?: boolean
customComponents?: {
@@ -57,12 +56,6 @@ export type FieldState = {
fieldSchema?: Field
filterOptions?: FilterOptionsResult
initialValue?: unknown
/**
* @experimental - Note: this property is experimental and may change in the future. Use at your own discretion.
* Every time a field is changed locally, this flag is set to true. Prevents form state from server from overwriting local changes.
* After merging server form state, this flag is reset.
*/
isModified?: boolean
/**
* The path of the field when its custom components were last rendered.
* This is used to denote if a field has been rendered, and if so,
@@ -121,11 +114,9 @@ export type BuildFormStateArgs = {
mockRSCs?: boolean
operation?: 'create' | 'update'
readOnly?: boolean
/**
* If true, will render field components within their state object.
* Performance optimization: Setting to `false` ensures that only fields that have changed paths will re-render, e.g. new array rows, etc.
* For example, you only need to render ALL fields on initial render, not on every onChange.
*/
/*
If true, will render field components within their state object
*/
renderAllFields?: boolean
req: PayloadRequest
returnLockStatus?: boolean

View File

@@ -73,9 +73,6 @@ export const logoutOperation = async (incomingArgs: Arguments): Promise<boolean>
userWithSessions.sessions = sessionsAfterLogout
}
// Ensure updatedAt date is always updated
;(userWithSessions as any).updatedAt = new Date().toISOString()
await req.payload.db.updateOne({
id: user.id,
collection: collectionConfig.slug,

View File

@@ -92,9 +92,6 @@ export const refreshOperation = async (incomingArgs: Arguments): Promise<Result>
const tokenExpInMs = collectionConfig.auth.tokenExpiration * 1000
existingSession.expiresAt = new Date(now.getTime() + tokenExpInMs)
// Ensure updatedAt date is always updated
user.updatedAt = new Date().toISOString()
await req.payload.db.updateOne({
id: user.id,
collection: collectionConfig.slug,

View File

@@ -131,9 +131,6 @@ export const resetPasswordOperation = async <TSlug extends CollectionSlug>(
// Update new password
// /////////////////////////////////////
// Ensure updatedAt date is always updated
user.updatedAt = new Date().toISOString()
const doc = await payload.db.updateOne({
id: user.id,
collection: collectionConfig.slug,

View File

@@ -46,9 +46,6 @@ export const verifyEmailOperation = async (args: Args): Promise<boolean> => {
throw new APIError('Verification token is invalid.', httpStatus.FORBIDDEN)
}
// Ensure updatedAt date is always updated
user.updatedAt = new Date().toISOString()
await req.payload.db.updateOne({
id: user.id,
collection: collection.config.slug,

View File

@@ -49,9 +49,6 @@ export const addSessionToUser = async ({
user.sessions.push(session)
}
// Ensure updatedAt date is always updated
user.updatedAt = new Date().toISOString()
await payload.db.updateOne({
id: user.id,
collection: collectionConfig.slug,

View File

@@ -142,9 +142,6 @@ export const incrementLoginAttempts = async ({
user.sessions = currentUser.sessions
// Ensure updatedAt date is always updated
user.updatedAt = new Date().toISOString()
await payload.db.updateOne({
id: user.id,
collection: collection.slug,

View File

@@ -32,7 +32,7 @@ export type Options<TSlug extends CollectionSlug> = {
locale?: TypedLocale
/**
* Skip access control.
* Set to `false` if you want to respect Access Control for the operation, for example when fetching data for the front-end.
* Set to `false` if you want to respect Access Control for the operation, for example when fetching data for the fron-end.
* @default true
*/
overrideAccess?: boolean

View File

@@ -32,7 +32,7 @@ export type Options<TSlug extends CollectionSlug> = {
locale?: TypedLocale
/**
* Skip access control.
* Set to `false` if you want to respect Access Control for the operation, for example when fetching data for the front-end.
* Set to `false` if you want to respect Access Control for the operation, for example when fetching data for the fron-end.
* @default true
*/
overrideAccess?: boolean

View File

@@ -81,7 +81,7 @@ export type Options<TSlug extends CollectionSlug, TSelect extends SelectType> =
locale?: TypedLocale
/**
* Skip access control.
* Set to `false` if you want to respect Access Control for the operation, for example when fetching data for the front-end.
* Set to `false` if you want to respect Access Control for the operation, for example when fetching data for the fron-end.
* @default true
*/
overrideAccess?: boolean

View File

@@ -46,7 +46,7 @@ export type BaseOptions<TSlug extends CollectionSlug, TSelect extends SelectType
locale?: TypedLocale
/**
* Skip access control.
* Set to `false` if you want to respect Access Control for the operation, for example when fetching data for the front-end.
* Set to `false` if you want to respect Access Control for the operation, for example when fetching data for the fron-end.
* @default true
*/
overrideAccess?: boolean

View File

@@ -62,7 +62,7 @@ export type Options<TSlug extends CollectionSlug, TSelect extends SelectType> =
locale?: TypedLocale
/**
* Skip access control.
* Set to `false` if you want to respect Access Control for the operation, for example when fetching data for the front-end.
* Set to `false` if you want to respect Access Control for the operation, for example when fetching data for the fron-end.
* @default true
*/
overrideAccess?: boolean

View File

@@ -76,7 +76,7 @@ export type Options<TSlug extends CollectionSlug, TSelect extends SelectType> =
locale?: 'all' | TypedLocale
/**
* Skip access control.
* Set to `false` if you want to respect Access Control for the operation, for example when fetching data for the front-end.
* Set to `false` if you want to respect Access Control for the operation, for example when fetching data for the fron-end.
* @default true
*/
overrideAccess?: boolean

View File

@@ -77,7 +77,7 @@ export type Options<
locale?: 'all' | TypedLocale
/**
* Skip access control.
* Set to `false` if you want to respect Access Control for the operation, for example when fetching data for the front-end.
* Set to `false` if you want to respect Access Control for the operation, for example when fetching data for the fron-end.
* @default true
*/
overrideAccess?: boolean

View File

@@ -54,7 +54,7 @@ export type Options<
locale?: 'all' | TypedLocale
/**
* Skip access control.
* Set to `false` if you want to respect Access Control for the operation, for example when fetching data for the front-end.
* Set to `false` if you want to respect Access Control for the operation, for example when fetching data for the fron-end.
* @default true
*/
overrideAccess?: boolean

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-restricted-exports */
import type { CollectionSlug, Payload, RequestContext, TypedLocale } from '../../../index.js'
import type { Document, PayloadRequest, PopulateType, SelectType } from '../../../types/index.js'
import type { CreateLocalReqOptions } from '../../../utilities/createLocalReq.js'
@@ -47,7 +48,7 @@ export type Options<TSlug extends CollectionSlug> = {
locale?: 'all' | TypedLocale
/**
* Skip access control.
* Set to `false` if you want to respect Access Control for the operation, for example when fetching data for the front-end.
* Set to `false` if you want to respect Access Control for the operation, for example when fetching data for the fron-end.
* @default true
*/
overrideAccess?: boolean

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-restricted-exports */
import type { PaginatedDocs } from '../../../database/types.js'
import type { CollectionSlug, Payload, RequestContext, TypedLocale } from '../../../index.js'
import type {
@@ -52,7 +53,7 @@ export type Options<TSlug extends CollectionSlug> = {
locale?: 'all' | TypedLocale
/**
* Skip access control.
* Set to `false` if you want to respect Access Control for the operation, for example when fetching data for the front-end.
* Set to `false` if you want to respect Access Control for the operation, for example when fetching data for the fron-end.
* @default true
*/
overrideAccess?: boolean

View File

@@ -41,7 +41,7 @@ export type Options<TSlug extends CollectionSlug> = {
locale?: TypedLocale
/**
* Skip access control.
* Set to `false` if you want to respect Access Control for the operation, for example when fetching data for the front-end.
* Set to `false` if you want to respect Access Control for the operation, for example when fetching data for the fron-end.
* @default true
*/
overrideAccess?: boolean

View File

@@ -76,7 +76,7 @@ export type BaseOptions<TSlug extends CollectionSlug, TSelect extends SelectType
locale?: TypedLocale
/**
* Skip access control.
* Set to `false` if you want to respect Access Control for the operation, for example when fetching data for the front-end.
* Set to `false` if you want to respect Access Control for the operation, for example when fetching data for the fron-end.
* @default true
*/
overrideAccess?: boolean

View File

@@ -258,10 +258,6 @@ export const restoreVersionOperation = async <TData extends TypeWithID = any>(
select: incomingSelect,
})
// Ensure updatedAt date is always updated
result.updatedAt = new Date().toISOString()
// Ensure status respects restoreAsDraft arg
result._status = draftArg ? 'draft' : result._status
result = await req.payload.db.updateOne({
id: parentDocID,
collection: collectionConfig.slug,

View File

@@ -293,8 +293,6 @@ export const updateDocument = async <
// /////////////////////////////////////
if (!shouldSaveDraft) {
// Ensure updatedAt date is always updated
dataToUpdate.updatedAt = new Date().toISOString()
result = await req.payload.db.updateOne({
id,
collection: collectionConfig.slug,

View File

@@ -116,7 +116,6 @@ export const createClientConfig = ({
routes: config.admin.routes,
theme: config.admin.theme,
timezones: config.admin.timezones,
toast: config.admin.toast,
user: config.admin.user,
}

View File

@@ -402,7 +402,6 @@ export type Params = { [key: string]: string | string[] | undefined }
export type ServerProps = {
readonly documentSubViewType?: DocumentSubViewTypes
readonly i18n: I18nClient
readonly id?: number | string
readonly locale?: Locale
readonly params?: Params
readonly payload: Payload
@@ -752,6 +751,7 @@ export type Config = {
username?: string
}
| false
/** Set account profile picture. Options: gravatar, default or a custom React component. */
avatar?:
| 'default'
@@ -759,7 +759,6 @@ export type Config = {
| {
Component: PayloadComponent
}
/**
* Add extra and/or replace built-in components with custom components
*
@@ -939,29 +938,6 @@ export type Config = {
* Configure timezone related settings for the admin panel.
*/
timezones?: TimezonesConfig
/**
* @experimental
* Configure toast message behavior and appearance in the admin panel.
* Currently using [Sonner](https://sonner.emilkowal.ski) for toast notifications.
*/
toast?: {
/**
* Time in milliseconds until the toast automatically closes.
* @default 4000
*/
duration?: number
/**
* If `true`, will expand the message stack so that all messages are shown simultaneously without user interaction.
* Otherwise only the latest notification can be read until the user hovers the stack.
* @default false
*/
expand?: boolean
/**
* The maximum number of toasts that can be visible on the screen at once.
* @default 5
*/
limit?: number
}
/** The slug of a Collection that you want to be used to log in to the Admin dashboard. */
user?: string
}

View File

@@ -162,12 +162,7 @@ export async function validateSearchParam({
if (versionFields) {
fieldAccess = policies[entityType]![entitySlug]!.fields
if (
segments[0] === 'parent' ||
segments[0] === 'version' ||
segments[0] === 'snapshot' ||
segments[0] === 'latest'
) {
if (segments[0] === 'parent' || segments[0] === 'version' || segments[0] === 'snapshot') {
segments.shift()
}
} else {

View File

@@ -1 +0,0 @@
export { is } from '@payloadcms/translations/languages/is'

View File

@@ -2,7 +2,8 @@ import ObjectIdImport from 'bson-objectid'
import type { TextField } from '../config/types.js'
const ObjectId = 'default' in ObjectIdImport ? ObjectIdImport.default : ObjectIdImport
const ObjectId = (ObjectIdImport.default ||
ObjectIdImport) as unknown as typeof ObjectIdImport.default
export const baseIDField: TextField = {
name: 'id',

View File

@@ -49,9 +49,6 @@ export function getFieldPaths({
}
}
/**
* @deprecated - will be removed in 4.0. Use `getFieldPaths` instead.
*/
export function getFieldPathsModified({
field,
index,

View File

@@ -1,7 +1,8 @@
import Ajv from 'ajv'
import ObjectIdImport from 'bson-objectid'
const ObjectId = 'default' in ObjectIdImport ? ObjectIdImport.default : ObjectIdImport
const ObjectId = (ObjectIdImport.default ||
ObjectIdImport) as unknown as typeof ObjectIdImport.default
import type { TFunction } from '@payloadcms/translations'
import type { JSONSchema4 } from 'json-schema'

View File

@@ -51,15 +51,6 @@ export async function buildFolderWhereConstraints({
equals: collectionConfig.slug,
},
})
// join queries need to omit trashed documents
if (collectionConfig.trash) {
constraints.push({
deletedAt: {
exists: false,
},
})
}
}
const filteredConstraints = constraints.filter(Boolean)

View File

@@ -32,7 +32,7 @@ export type CountGlobalVersionsOptions<TSlug extends GlobalSlug> = {
locale?: TypedLocale
/**
* Skip access control.
* Set to `false` if you want to respect Access Control for the operation, for example when fetching data for the front-end.
* Set to `false` if you want to respect Access Control for the operation, for example when fetching data for the fron-end.
* @default true
*/
overrideAccess?: boolean

View File

@@ -43,7 +43,7 @@ export type Options<TSlug extends GlobalSlug, TSelect extends SelectType> = {
locale?: 'all' | TypedLocale
/**
* Skip access control.
* Set to `false` if you want to respect Access Control for the operation, for example when fetching data for the front-end.
* Set to `false` if you want to respect Access Control for the operation, for example when fetching data for the fron-end.
* @default true
*/
overrideAccess?: boolean

View File

@@ -39,7 +39,7 @@ export type Options<TSlug extends GlobalSlug> = {
locale?: 'all' | TypedLocale
/**
* Skip access control.
* Set to `false` if you want to respect Access Control for the operation, for example when fetching data for the front-end.
* Set to `false` if you want to respect Access Control for the operation, for example when fetching data for the fron-end.
* @default true
*/
overrideAccess?: boolean

View File

@@ -44,7 +44,7 @@ export type Options<TSlug extends GlobalSlug> = {
locale?: 'all' | TypedLocale
/**
* Skip access control.
* Set to `false` if you want to respect Access Control for the operation, for example when fetching data for the front-end.
* Set to `false` if you want to respect Access Control for the operation, for example when fetching data for the fron-end.
* @default true
*/
overrideAccess?: boolean

Some files were not shown because too many files have changed in this diff Show More