Compare commits
1 Commits
feat/lexic
...
feat/multi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c9d0d1ffcc |
2
.github/actions/triage/action.yml
vendored
2
.github/actions/triage/action.yml
vendored
@@ -26,7 +26,7 @@ runs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
- name: Run action
|
||||
run: node ${{ github.action_path }}/dist/index.js
|
||||
shell: sh
|
||||
|
||||
4
.github/workflows/audit-dependencies.yml
vendored
4
.github/workflows/audit-dependencies.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
|
||||
- name: Slack notification on failure
|
||||
if: failure()
|
||||
uses: slackapi/slack-github-action@v2.1.1
|
||||
uses: slackapi/slack-github-action@v2.1.0
|
||||
with:
|
||||
webhook: ${{ inputs.debug == 'true' && secrets.SLACK_TEST_WEBHOOK_URL || secrets.SLACK_WEBHOOK_URL }}
|
||||
webhook-type: incoming-webhook
|
||||
|
||||
2
.github/workflows/dispatch-event.yml
vendored
2
.github/workflows/dispatch-event.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
name: Repository dispatch
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Dispatch event
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
|
||||
24
.github/workflows/main.yml
vendored
24
.github/workflows/main.yml
vendored
@@ -6,6 +6,7 @@ on:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
- labeled
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
@@ -33,7 +34,7 @@ jobs:
|
||||
- name: tune linux network
|
||||
run: sudo ethtool -K eth0 tx off rx off
|
||||
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dorny/paths-filter@v3
|
||||
id: filter
|
||||
with:
|
||||
@@ -62,7 +63,7 @@ jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -78,7 +79,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Node setup
|
||||
uses: ./.github/actions/setup
|
||||
@@ -98,7 +99,7 @@ jobs:
|
||||
needs: [changes, build]
|
||||
if: ${{ needs.changes.outputs.needs_tests == 'true' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Node setup
|
||||
uses: ./.github/actions/setup
|
||||
@@ -122,7 +123,7 @@ jobs:
|
||||
needs: [changes, build]
|
||||
if: ${{ needs.changes.outputs.needs_tests == 'true' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Node setup
|
||||
uses: ./.github/actions/setup
|
||||
@@ -184,7 +185,7 @@ jobs:
|
||||
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Node setup
|
||||
uses: ./.github/actions/setup
|
||||
@@ -309,7 +310,7 @@ jobs:
|
||||
env:
|
||||
SUITE_NAME: ${{ matrix.suite }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Node setup
|
||||
uses: ./.github/actions/setup
|
||||
@@ -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]
|
||||
@@ -447,7 +447,7 @@ jobs:
|
||||
env:
|
||||
SUITE_NAME: ${{ matrix.suite }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Node setup
|
||||
uses: ./.github/actions/setup
|
||||
@@ -550,7 +550,7 @@ jobs:
|
||||
MONGODB_VERSION: 6.0
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Node setup
|
||||
uses: ./.github/actions/setup
|
||||
@@ -647,7 +647,7 @@ jobs:
|
||||
needs: [changes, build]
|
||||
if: ${{ needs.changes.outputs.needs_tests == 'true' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Node setup
|
||||
uses: ./.github/actions/setup
|
||||
@@ -706,7 +706,7 @@ jobs:
|
||||
actions: read # for fetching base branch bundle stats
|
||||
pull-requests: write # for comments
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Node setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
4
.github/workflows/post-release-templates.yml
vendored
4
.github/workflows/post-release-templates.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
release_tag: ${{ steps.determine_tag.outputs.release_tag }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
sparse-checkout: .github/workflows
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
POSTGRES_DB: payloadtests
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
9
.github/workflows/post-release.yml
vendored
9
.github/workflows/post-release.yml
vendored
@@ -17,13 +17,10 @@ env:
|
||||
|
||||
jobs:
|
||||
post_release:
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-24.04
|
||||
if: ${{ github.event_name != 'workflow_dispatch' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/release-commenter
|
||||
continue-on-error: true
|
||||
env:
|
||||
@@ -43,9 +40,9 @@ jobs:
|
||||
if: ${{ github.event_name != 'workflow_dispatch' }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
- name: Github Releases To Discord
|
||||
uses: SethCohen/github-releases-to-discord@v1.19.0
|
||||
uses: SethCohen/github-releases-to-discord@v1.16.2
|
||||
with:
|
||||
webhook_url: ${{ secrets.DISCORD_RELEASES_WEBHOOK_URL }}
|
||||
color: '16777215'
|
||||
|
||||
2
.github/workflows/pr-title.yml
vendored
2
.github/workflows/pr-title.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
name: lint-pr-title
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: amannn/action-semantic-pull-request@v6
|
||||
- uses: amannn/action-semantic-pull-request@v5
|
||||
id: lint_pr_title
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
2
.github/workflows/publish-prerelease.yml
vendored
2
.github/workflows/publish-prerelease.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
- name: Load npm token
|
||||
|
||||
2
.github/workflows/triage.yml
vendored
2
.github/workflows/triage.yml
vendored
@@ -90,7 +90,7 @@ jobs:
|
||||
if: github.event_name == 'issues'
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.base.ref }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -331,7 +331,5 @@ test/databaseAdapter.js
|
||||
test/.localstack
|
||||
test/google-cloud-storage
|
||||
test/azurestoragedata/
|
||||
/media-without-delete-access
|
||||
|
||||
|
||||
licenses.csv
|
||||
|
||||
@@ -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` |
|
||||
|
||||
@@ -739,7 +739,7 @@ The `useDocumentInfo` hook provides information about the current document being
|
||||
| **`lastUpdateTime`** | Timestamp of the last update to the document. |
|
||||
| **`mostRecentVersionIsAutosaved`** | Whether the most recent version is an autosaved version. |
|
||||
| **`preferencesKey`** | The `preferences` key to use when interacting with document-level user preferences. [More details](./preferences). |
|
||||
| **`data`** | The saved data of the document. |
|
||||
| **`savedDocumentData`** | The saved data of the document. |
|
||||
| **`setDocFieldPreferences`** | Method to set preferences for a specific field. [More details](./preferences). |
|
||||
| **`setDocumentTitle`** | Method to set the document title. |
|
||||
| **`setHasPublishedDoc`** | Method to update whether the document has been published. |
|
||||
|
||||
@@ -33,7 +33,7 @@ export const Users: CollectionConfig = {
|
||||
}
|
||||
```
|
||||
|
||||

|
||||

|
||||
_Admin Panel screenshot depicting an Admins Collection with Auth enabled_
|
||||
|
||||
## Config Options
|
||||
|
||||
@@ -141,8 +141,8 @@ 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). |
|
||||
| `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, |
|
||||
| `pagination` | Set pagination-specific options for this Collection. [More details](#pagination). |
|
||||
| `baseListFilter` | You can define a default base filter for this collection's List view, which will be merged into any filters that the user performs. |
|
||||
|
||||
<Banner type="warning">
|
||||
**Note:** If you set `useAsTitle` to a relationship or join field, it will use
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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 },
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -296,16 +296,11 @@ query {
|
||||
sort: "createdAt"
|
||||
limit: 5
|
||||
where: { author: { equals: "66e3431a3f23e684075aaeb9" } }
|
||||
"""
|
||||
Optionally pass count: true if you want to retrieve totalDocs
|
||||
"""
|
||||
count: true -- s
|
||||
) {
|
||||
docs {
|
||||
title
|
||||
}
|
||||
hasNextPage
|
||||
totalDocs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -79,7 +79,6 @@ formBuilderPlugin({
|
||||
text: true,
|
||||
textarea: true,
|
||||
select: true,
|
||||
radio: true,
|
||||
email: true,
|
||||
state: true,
|
||||
country: true,
|
||||
@@ -294,46 +293,14 @@ Maps to a `textarea` input on your front-end. Used to collect a multi-line strin
|
||||
|
||||
Maps to a `select` input on your front-end. Used to display a list of options.
|
||||
|
||||
| Property | Type | Description |
|
||||
| -------------- | -------- | ------------------------------------------------------------------------------- |
|
||||
| `name` | string | The name of the field. |
|
||||
| `label` | string | The label of the field. |
|
||||
| `defaultValue` | string | The default value of the field. |
|
||||
| `placeholder` | string | The placeholder text for the field. |
|
||||
| `width` | string | The width of the field on the front-end. |
|
||||
| `required` | checkbox | Whether or not the field is required when submitted. |
|
||||
| `options` | array | An array of objects that define the select options. See below for more details. |
|
||||
|
||||
#### Select Options
|
||||
|
||||
Each option in the `options` array defines a selectable choice for the select field.
|
||||
|
||||
| Property | Type | Description |
|
||||
| -------- | ------ | ----------------------------------- |
|
||||
| `label` | string | The display text for the option. |
|
||||
| `value` | string | The value submitted for the option. |
|
||||
|
||||
### Radio
|
||||
|
||||
Maps to radio button inputs on your front-end. Used to allow users to select a single option from a list of choices.
|
||||
|
||||
| Property | Type | Description |
|
||||
| -------------- | -------- | ------------------------------------------------------------------------------ |
|
||||
| `name` | string | The name of the field. |
|
||||
| `label` | string | The label of the field. |
|
||||
| `defaultValue` | string | The default value of the field. |
|
||||
| `width` | string | The width of the field on the front-end. |
|
||||
| `required` | checkbox | Whether or not the field is required when submitted. |
|
||||
| `options` | array | An array of objects that define the radio options. See below for more details. |
|
||||
|
||||
#### Radio Options
|
||||
|
||||
Each option in the `options` array defines a selectable choice for the radio field.
|
||||
|
||||
| Property | Type | Description |
|
||||
| -------- | ------ | ----------------------------------- |
|
||||
| `label` | string | The display text for the option. |
|
||||
| `value` | string | The value submitted for the option. |
|
||||
| Property | Type | Description |
|
||||
| -------------- | -------- | -------------------------------------------------------- |
|
||||
| `name` | string | The name of the field. |
|
||||
| `label` | string | The label of the field. |
|
||||
| `defaultValue` | string | The default value of the field. |
|
||||
| `width` | string | The width of the field on the front-end. |
|
||||
| `required` | checkbox | Whether or not the field is required when submitted. |
|
||||
| `options` | array | An array of objects with `label` and `value` properties. |
|
||||
|
||||
### Email (field)
|
||||
|
||||
|
||||
@@ -54,15 +54,8 @@ The plugin accepts an object with the following properties:
|
||||
```ts
|
||||
type MultiTenantPluginConfig<ConfigTypes = unknown> = {
|
||||
/**
|
||||
* Base path for your application
|
||||
*
|
||||
* https://nextjs.org/docs/app/api-reference/config/next-config-js/basePath
|
||||
*
|
||||
* @default undefined
|
||||
*/
|
||||
basePath?: string
|
||||
/**
|
||||
* After a tenant is deleted, the plugin will attempt to clean up related documents
|
||||
* After a tenant is deleted, the plugin will attempt
|
||||
* to clean up related documents
|
||||
* - removing documents with the tenant ID
|
||||
* - removing the tenant from users
|
||||
*
|
||||
@@ -75,41 +68,22 @@ type MultiTenantPluginConfig<ConfigTypes = unknown> = {
|
||||
collections: {
|
||||
[key in CollectionSlug]?: {
|
||||
/**
|
||||
* Set to `true` if you want the collection to behave as a global
|
||||
* Set to `true` if you want the collection to
|
||||
* behave as a global
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
tenantFieldOverrides?: CollectionTenantFieldConfigOverrides
|
||||
/**
|
||||
* Set to `false` if you want to manually apply the baseListFilter
|
||||
* Set to `false` if you want to manually apply the baseFilter
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
useBaseFilter?: boolean
|
||||
/**
|
||||
* @deprecated Use `useBaseFilter` instead. If both are defined,
|
||||
* `useBaseFilter` will take precedence. This property remains only
|
||||
* for backward compatibility and may be removed in a future version.
|
||||
*
|
||||
* Originally, `baseListFilter` was intended to filter only the List View
|
||||
* in the admin panel. However, base filtering is often required in other areas
|
||||
* such as internal link relationships in the Lexical editor.
|
||||
* Set to `false` if you want to manually apply
|
||||
* the baseListFilter
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
useBaseListFilter?: boolean
|
||||
/**
|
||||
* Set to `false` if you want to handle collection access manually without the multi-tenant constraints applied
|
||||
* Set to `false` if you want to handle collection access
|
||||
* manually without the multi-tenant constraints applied
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
@@ -118,7 +92,8 @@ type MultiTenantPluginConfig<ConfigTypes = unknown> = {
|
||||
}
|
||||
/**
|
||||
* Enables debug mode
|
||||
* - Makes the tenant field visible in the admin UI within applicable collections
|
||||
* - Makes the tenant field visible in the
|
||||
* admin UI within applicable collections
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
@@ -130,41 +105,27 @@ type MultiTenantPluginConfig<ConfigTypes = unknown> = {
|
||||
*/
|
||||
enabled?: boolean
|
||||
/**
|
||||
* Localization for the plugin
|
||||
* Field configuration for the field added
|
||||
* to all tenant enabled collections
|
||||
*/
|
||||
i18n?: {
|
||||
translations: {
|
||||
[key in AcceptedLanguages]?: {
|
||||
/**
|
||||
* @default 'You are about to change ownership from <0>{{fromTenant}}</0> to <0>{{toTenant}}</0>'
|
||||
*/
|
||||
'confirm-modal-tenant-switch--body'?: string
|
||||
/**
|
||||
* `tenantLabel` defaults to the value of the `nav-tenantSelector-label` translation
|
||||
*
|
||||
* @default 'Confirm {{tenantLabel}} change'
|
||||
*/
|
||||
'confirm-modal-tenant-switch--heading'?: string
|
||||
/**
|
||||
* @default 'Assigned Tenant'
|
||||
*/
|
||||
'field-assignedTenant-label'?: string
|
||||
/**
|
||||
* @default 'Tenant'
|
||||
*/
|
||||
'nav-tenantSelector-label'?: string
|
||||
}
|
||||
}
|
||||
tenantField?: {
|
||||
access?: RelationshipField['access']
|
||||
/**
|
||||
* The name of the field added to all tenant
|
||||
* enabled collections
|
||||
*
|
||||
* @default 'tenant'
|
||||
*/
|
||||
name?: string
|
||||
}
|
||||
/**
|
||||
* Field configuration for the field added to all tenant enabled collections
|
||||
*/
|
||||
tenantField?: RootTenantFieldConfigOverrides
|
||||
/**
|
||||
* Field configuration for the field added to the users collection
|
||||
* Field configuration for the field added
|
||||
* to the users collection
|
||||
*
|
||||
* If `includeDefaultField` is `false`, you must include the field on your users collection manually
|
||||
* This is useful if you want to customize the field or place the field in a specific location
|
||||
* If `includeDefaultField` is `false`, you must
|
||||
* include the field on your users collection manually
|
||||
* This is useful if you want to customize the field
|
||||
* or place the field in a specific location
|
||||
*/
|
||||
tenantsArrayField?:
|
||||
| {
|
||||
@@ -185,7 +146,8 @@ type MultiTenantPluginConfig<ConfigTypes = unknown> = {
|
||||
*/
|
||||
arrayTenantFieldName?: string
|
||||
/**
|
||||
* When `includeDefaultField` is `true`, the field will be added to the users collection automatically
|
||||
* When `includeDefaultField` is `true`, the field will
|
||||
* be added to the users collection automatically
|
||||
*/
|
||||
includeDefaultField?: true
|
||||
/**
|
||||
@@ -202,7 +164,8 @@ type MultiTenantPluginConfig<ConfigTypes = unknown> = {
|
||||
arrayFieldName?: string
|
||||
arrayTenantFieldName?: string
|
||||
/**
|
||||
* When `includeDefaultField` is `false`, you must include the field on your users collection manually
|
||||
* When `includeDefaultField` is `false`, you must
|
||||
* include the field on your users collection manually
|
||||
*/
|
||||
includeDefaultField?: false
|
||||
rowFields?: never
|
||||
@@ -211,9 +174,8 @@ type MultiTenantPluginConfig<ConfigTypes = unknown> = {
|
||||
/**
|
||||
* Customize tenant selector label
|
||||
*
|
||||
* Either a string or an object where the keys are i18n codes and the values are the string labels
|
||||
*
|
||||
* @deprecated Use `i18n.translations` instead.
|
||||
* Either a string or an object where the keys are i18n
|
||||
* codes and the values are the string labels
|
||||
*/
|
||||
tenantSelectorLabel?:
|
||||
| Partial<{
|
||||
@@ -227,25 +189,27 @@ type MultiTenantPluginConfig<ConfigTypes = unknown> = {
|
||||
*/
|
||||
tenantsSlug?: string
|
||||
/**
|
||||
* Function that determines if a user has access to _all_ tenants
|
||||
* Function that determines if a user has access
|
||||
* to _all_ tenants
|
||||
*
|
||||
* Useful for super-admin type users
|
||||
*/
|
||||
userHasAccessToAllTenants?: (
|
||||
user: ConfigTypes extends { user: unknown }
|
||||
? ConfigTypes['user']
|
||||
: TypedUser,
|
||||
user: ConfigTypes extends { user: unknown } ? ConfigTypes['user'] : User,
|
||||
) => boolean
|
||||
/**
|
||||
* Opt out of adding access constraints to the tenants collection
|
||||
* Opt out of adding access constraints to
|
||||
* the tenants collection
|
||||
*/
|
||||
useTenantsCollectionAccess?: boolean
|
||||
/**
|
||||
* Opt out including the baseListFilter to filter tenants by selected tenant
|
||||
* Opt out including the baseListFilter to filter
|
||||
* tenants by selected tenant
|
||||
*/
|
||||
useTenantsListFilter?: boolean
|
||||
/**
|
||||
* Opt out including the baseListFilter to filter users by selected tenant
|
||||
* Opt out including the baseListFilter to filter
|
||||
* users by selected tenant
|
||||
*/
|
||||
useUsersTenantFilter?: boolean
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -269,13 +269,11 @@ Lexical does not generate accurate type definitions for your richText fields for
|
||||
|
||||
The Rich Text Field editor configuration has an `admin` property with the following options:
|
||||
|
||||
| Property | Description |
|
||||
| ------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
||||
| **`placeholder`** | Set this property to define a placeholder string for the field. |
|
||||
| **`hideGutter`** | Set this property to `true` to hide this field's gutter within the Admin Panel. |
|
||||
| **`hideInsertParagraphAtEnd`** | Set this property to `true` to hide the "+" button that appears at the end of the editor. |
|
||||
| **`hideDraggableBlockElement`** | Set this property to `true` to hide the draggable element that appears when you hover a node in the editor. |
|
||||
| **`hideAddBlockButton`** | Set this property to `true` to hide the "+" button that appears when you hover a node in the editor. |
|
||||
| Property | Description |
|
||||
| ------------------------------ | ---------------------------------------------------------------------------------------- |
|
||||
| **`placeholder`** | Set this property to define a placeholder string for the field. |
|
||||
| **`hideGutter`** | Set this property to `true` to hide this field's gutter within the Admin Panel. |
|
||||
| **`hideInsertParagraphAtEnd`** | Set this property to `true` to hide the "+" button that appears at the end of the editor |
|
||||
|
||||
### Disable the gutter
|
||||
|
||||
|
||||
@@ -6,112 +6,9 @@ desc: Troubleshooting Common Issues in Payload
|
||||
keywords: admin, components, custom, customize, documentation, Content Management System, cms, headless, javascript, node, react, nextjs, troubleshooting
|
||||
---
|
||||
|
||||
## Dependency mismatches
|
||||
## Common Issues
|
||||
|
||||
All `payload` and `@payloadcms/*` packages must be on exactly the same version and installed only once.
|
||||
|
||||
When two copies—or two different versions—of any of these packages (or of `react` / `react-dom`) appear in your dependency graph, you can see puzzling runtime errors. The most frequent is a broken React context:
|
||||
|
||||
```bash
|
||||
TypeError: Cannot destructure property 'config' of...
|
||||
```
|
||||
|
||||
This happens because one package imports a hook (most commonly `useConfig`) from _version A_ while the context provider comes from _version B_. The fix is always the same: make sure every Payload-related and React package resolves to the same module.
|
||||
|
||||
### Confirm whether duplicates exist
|
||||
|
||||
The first thing to do is to confirm whether duplicative dependencies do in fact exist.
|
||||
|
||||
There are two ways to do this:
|
||||
|
||||
1. Using pnpm's built-in inspection tool
|
||||
|
||||
```bash
|
||||
pnpm why @payloadcms/ui
|
||||
```
|
||||
|
||||
This prints the dependency tree and shows which versions are being installed. If you see more than one distinct version—or the same version listed under different paths—you have duplication.
|
||||
|
||||
2. Manual check (works with any package manager)
|
||||
|
||||
```bash
|
||||
find node_modules -name package.json \
|
||||
-exec grep -H '"name": "@payloadcms/ui"' {} \;
|
||||
```
|
||||
|
||||
Most of these hits are likely symlinks created by pnpm. Edit the matching package.json files (temporarily add a comment or change a description) to confirm whether they point to the same physical folder or to multiple copies.
|
||||
|
||||
Perform the same two checks for react and react-dom; a second copy of React can cause identical symptoms.
|
||||
|
||||
#### If no duplicates are found
|
||||
|
||||
`@payloadcms/ui` intentionally contains two bundles of itself, so you may see dual paths even when everything is correct. Inside the Payload Admin UI you must import only:
|
||||
|
||||
- `@payloadcms/ui`
|
||||
- `@payloadcms/ui/rsc`
|
||||
- `@payloadcms/ui/shared`
|
||||
|
||||
Any other deep import such as `@payloadcms/ui/elements/Button` should **only** be used in your own frontend, outside of the Payload Admin Panel. Those deep entries are published un-bundled to help you tree-shake and ship a smaller client bundle if you only need a few components from `@payloadcms/ui`.
|
||||
|
||||
### Fixing depedendency issues
|
||||
|
||||
These steps assume `pnpm`, which the Payload team recommends and uses internally. The principles apply to other package managers like npm and yarn as well. Do note that yarn 1.x is not supported by Payload.
|
||||
|
||||
1. Pin every critical package to an exact version
|
||||
|
||||
In package.json remove `^` or `~` from all versions of:
|
||||
|
||||
- `payload`
|
||||
- `@payloadcms/*`
|
||||
- `react`
|
||||
- `react-dom`
|
||||
|
||||
Prefixes allow your package manager to float to a newer minor/patch release, causing mismatches.
|
||||
|
||||
2. Delete node_modules
|
||||
|
||||
Old packages often linger even after you change versions or removed them from your package.json. Deleting node_modules ensures a clean slate.
|
||||
|
||||
3. Re-install dependencies
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
#### If the error persists
|
||||
|
||||
1. Clean the global store (pnpm only)
|
||||
|
||||
```bash
|
||||
pnpm store prune
|
||||
```
|
||||
|
||||
2. Delete the lockfile
|
||||
|
||||
Depending on your package manager, this could be `pnpm-lock.yaml`, `package-lock.json`, or `yarn.lock`.
|
||||
|
||||
Make sure you delete the lockfile **and** the node_modules folder at the same time, then run `pnpm install`. This forces a fresh, consistent resolution for all packages. It will also update all packages with dynamic versions to the latest version.
|
||||
|
||||
While it's best practice to manage dependencies in such a way where the lockfile can easily be re-generated (often this is the easiest way to resolve dependency issues), this may break your project if you have not tested the latest versions of your dependencies.
|
||||
|
||||
If you are using a version control system, make sure to commit your lockfile after this step.
|
||||
|
||||
3. Deduplicate anything that slipped through
|
||||
|
||||
```bash
|
||||
pnpm dedupe
|
||||
```
|
||||
|
||||
**Still stuck?**
|
||||
|
||||
- Switch to `pnpm` if you are on npm. Its symlinked store helps reducing accidental duplication.
|
||||
- Inspect the lockfile directly for peer-dependency violations.
|
||||
- Check project-level .npmrc / .pnpmfile.cjs overrides.
|
||||
- Run [Syncpack](https://www.npmjs.com/package/syncpack) to enforce identical versions of every `@payloadcms/*`, `react`, and `react-dom` reference.
|
||||
|
||||
Absolute last resort: add Webpack aliases so that all imports of a given package resolve to the same path (e.g. `resolve.alias['react'] = path.resolve('./node_modules/react')`). Keep this only until you can fix the underlying version skew.
|
||||
|
||||
## "Unauthorized, you must be logged in to make this request" when attempting to log in
|
||||
### "Unauthorized, you must be logged in to make this request" when attempting to log in
|
||||
|
||||
This means that your auth cookie is not being set or accepted correctly upon logging in. To resolve check the following settings in your Payload Config:
|
||||
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
|
||||

|
||||

|
||||
_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
|
||||
|
||||
@@ -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.
|
||||
|
||||

|
||||

|
||||
_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
|
||||
|
||||
@@ -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.
|
||||
|
||||

|
||||

|
||||
_Comparing an old version to a newer version of a document_
|
||||
|
||||
**With Versions, you can:**
|
||||
|
||||
1
next-env.d.ts
vendored
1
next-env.d.ts
vendored
@@ -1,6 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
/// <reference path="./.next/types/routes.d.ts" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
10
package.json
10
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload-monorepo",
|
||||
"version": "3.54.0",
|
||||
"version": "3.49.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"workspaces": [
|
||||
@@ -147,8 +147,8 @@
|
||||
"@types/jest": "29.5.12",
|
||||
"@types/minimist": "1.2.5",
|
||||
"@types/node": "22.15.30",
|
||||
"@types/react": "19.1.12",
|
||||
"@types/react-dom": "19.1.9",
|
||||
"@types/react": "19.1.8",
|
||||
"@types/react-dom": "19.1.6",
|
||||
"@types/shelljs": "0.8.15",
|
||||
"chalk": "^4.1.2",
|
||||
"comment-json": "^4.2.3",
|
||||
@@ -175,8 +175,8 @@
|
||||
"playwright": "1.54.1",
|
||||
"playwright-core": "1.54.1",
|
||||
"prettier": "3.5.3",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"rimraf": "6.0.1",
|
||||
"sharp": "0.32.6",
|
||||
"shelljs": "0.8.5",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/admin-bar",
|
||||
"version": "3.54.0",
|
||||
"version": "3.49.1",
|
||||
"description": "An admin bar for React apps using Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -42,8 +42,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@types/react": "19.1.12",
|
||||
"@types/react-dom": "19.1.9",
|
||||
"@types/react": "19.1.8",
|
||||
"@types/react-dom": "19.1.6",
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-payload-app",
|
||||
"version": "3.54.0",
|
||||
"version": "3.49.1",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "3.54.0",
|
||||
"version": "3.49.1",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -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 ----')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -331,7 +331,7 @@ export function mongooseAdapter({
|
||||
}
|
||||
}
|
||||
|
||||
export { compatibilityOptions } from './utilities/compatibilityOptions.js'
|
||||
export { compatabilityOptions } from './utilities/compatabilityOptions.js'
|
||||
|
||||
/**
|
||||
* Attempt to find migrations directory.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -63,10 +63,7 @@ const migrateModelWithBatching = async ({
|
||||
},
|
||||
},
|
||||
})),
|
||||
{
|
||||
session, // Timestamps are manually added by the write transform
|
||||
timestamps: false,
|
||||
},
|
||||
{ session },
|
||||
)
|
||||
|
||||
skip += batchSize
|
||||
|
||||
@@ -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' })
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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({
|
||||
@@ -52,24 +50,11 @@ export const updateOne: UpdateOne = async function updateOne(
|
||||
|
||||
let result
|
||||
|
||||
let updateData: UpdateQuery<any> = data
|
||||
|
||||
const $inc: Record<string, number> = {}
|
||||
const $push: Record<string, { $each: any[] } | any> = {}
|
||||
|
||||
transform({ $inc, $push, adapter: this, data, fields, operation: 'write' })
|
||||
|
||||
const updateOps: UpdateQuery<any> = {}
|
||||
|
||||
let updateData: UpdateQuery<any> = data
|
||||
transform({ $inc, adapter: this, data, fields, operation: 'write' })
|
||||
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
|
||||
updateData = { $inc, $set: updateData }
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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,
|
||||
@@ -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) => {
|
||||
|
||||
@@ -209,7 +209,6 @@ const sanitizeDate = ({
|
||||
|
||||
type Args = {
|
||||
$inc?: Record<string, number>
|
||||
$push?: Record<string, { $each: any[] } | any>
|
||||
/** instance of the adapter */
|
||||
adapter: MongooseAdapter
|
||||
/** data to transform, can be an array of documents or a single document */
|
||||
@@ -399,7 +398,6 @@ const stripFields = ({
|
||||
|
||||
export const transform = ({
|
||||
$inc,
|
||||
$push,
|
||||
adapter,
|
||||
data,
|
||||
fields,
|
||||
@@ -414,16 +412,7 @@ export const transform = ({
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
for (const item of data) {
|
||||
transform({
|
||||
$inc,
|
||||
$push,
|
||||
adapter,
|
||||
data: item,
|
||||
fields,
|
||||
globalSlug,
|
||||
operation,
|
||||
validateRelationships,
|
||||
})
|
||||
transform({ $inc, adapter, data: item, fields, globalSlug, operation, validateRelationships })
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -481,39 +470,6 @@ export const transform = ({
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
$push &&
|
||||
field.type === 'array' &&
|
||||
operation === 'write' &&
|
||||
field.name in ref &&
|
||||
ref[field.name]
|
||||
) {
|
||||
const value = ref[field.name]
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
delete ref[field.name]
|
||||
}
|
||||
}
|
||||
|
||||
if (field.type === 'date' && operation === 'read' && field.name in ref && ref[field.name]) {
|
||||
if (config.localization && fieldShouldBeLocalized({ field, parentIsLocalized })) {
|
||||
const fieldRef = ref[field.name] as Record<string, unknown>
|
||||
@@ -592,15 +548,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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-postgres",
|
||||
"version": "3.54.0",
|
||||
"version": "3.49.1",
|
||||
"description": "The officially supported Postgres database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-sqlite",
|
||||
"version": "3.54.0",
|
||||
"version": "3.49.1",
|
||||
"description": "The officially supported SQLite database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-vercel-postgres",
|
||||
"version": "3.54.0",
|
||||
"version": "3.49.1",
|
||||
"description": "Vercel Postgres adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/drizzle",
|
||||
"version": "3.54.0",
|
||||
"version": "3.49.1",
|
||||
"description": "A library of shared functions used by different payload database adapters",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -110,32 +110,19 @@ export const sanitizeQueryValue = ({
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to convert a single date value to ISO string
|
||||
const convertDateToISO = (item: unknown): unknown => {
|
||||
if (typeof item === 'string') {
|
||||
if (item === 'null' || item === '') {
|
||||
return null
|
||||
}
|
||||
const date = new Date(item)
|
||||
return Number.isNaN(date.getTime()) ? undefined : date.toISOString()
|
||||
} else if (typeof item === 'number') {
|
||||
return new Date(item).toISOString()
|
||||
} else if (item instanceof Date) {
|
||||
return item.toISOString()
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
if (field.type === 'date' && operator !== 'exists') {
|
||||
if (Array.isArray(formattedValue)) {
|
||||
// Handle arrays of dates for 'in' and 'not_in' operators
|
||||
formattedValue = formattedValue.map(convertDateToISO).filter((item) => item !== undefined)
|
||||
} else {
|
||||
const converted = convertDateToISO(val)
|
||||
if (converted === undefined) {
|
||||
return { operator, value: undefined }
|
||||
if (typeof val === 'string') {
|
||||
if (val === 'null' || val === '') {
|
||||
formattedValue = null
|
||||
} else {
|
||||
const date = new Date(val)
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return { operator, value: undefined }
|
||||
}
|
||||
formattedValue = date.toISOString()
|
||||
}
|
||||
formattedValue = converted
|
||||
} else if (typeof val === 'number') {
|
||||
formattedValue = new Date(val).toISOString()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,6 @@ export const transformArray = ({
|
||||
data.forEach((arrayRow, i) => {
|
||||
const newRow: ArrayRowToInsert = {
|
||||
arrays: {},
|
||||
arraysToPush: {},
|
||||
locales: {},
|
||||
row: {
|
||||
_order: i + 1,
|
||||
@@ -105,7 +104,6 @@ export const transformArray = ({
|
||||
traverseFields({
|
||||
adapter,
|
||||
arrays: newRow.arrays,
|
||||
arraysToPush: newRow.arraysToPush,
|
||||
baseTableName,
|
||||
blocks,
|
||||
blocksToDelete,
|
||||
|
||||
@@ -78,7 +78,6 @@ export const transformBlocks = ({
|
||||
|
||||
const newRow: BlockRowToInsert = {
|
||||
arrays: {},
|
||||
arraysToPush: {},
|
||||
locales: {},
|
||||
row: {
|
||||
_order: i + 1,
|
||||
@@ -117,7 +116,6 @@ export const transformBlocks = ({
|
||||
traverseFields({
|
||||
adapter,
|
||||
arrays: newRow.arrays,
|
||||
arraysToPush: newRow.arraysToPush,
|
||||
baseTableName,
|
||||
blocks,
|
||||
blocksToDelete,
|
||||
|
||||
@@ -27,7 +27,6 @@ export const transformForWrite = ({
|
||||
// Split out the incoming data into rows to insert / delete
|
||||
const rowToInsert: RowToInsert = {
|
||||
arrays: {},
|
||||
arraysToPush: {},
|
||||
blocks: {},
|
||||
blocksToDelete: new Set(),
|
||||
locales: {},
|
||||
@@ -46,7 +45,6 @@ export const transformForWrite = ({
|
||||
traverseFields({
|
||||
adapter,
|
||||
arrays: rowToInsert.arrays,
|
||||
arraysToPush: rowToInsert.arraysToPush,
|
||||
baseTableName: tableName,
|
||||
blocks: rowToInsert.blocks,
|
||||
blocksToDelete: rowToInsert.blocksToDelete,
|
||||
|
||||
@@ -4,7 +4,13 @@ import { fieldIsVirtual, fieldShouldBeLocalized } from 'payload/shared'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { DrizzleAdapter } from '../../types.js'
|
||||
import type { NumberToDelete, RelationshipToDelete, RowToInsert, TextToDelete } from './types.js'
|
||||
import type {
|
||||
ArrayRowToInsert,
|
||||
BlockRowToInsert,
|
||||
NumberToDelete,
|
||||
RelationshipToDelete,
|
||||
TextToDelete,
|
||||
} from './types.js'
|
||||
|
||||
import { isArrayOfRows } from '../../utilities/isArrayOfRows.js'
|
||||
import { resolveBlockTableName } from '../../utilities/validateExistingBlockIsIdentical.js'
|
||||
@@ -17,20 +23,16 @@ import { transformTexts } from './texts.js'
|
||||
|
||||
type Args = {
|
||||
adapter: DrizzleAdapter
|
||||
/**
|
||||
* This will delete the array table and then re-insert all the new array rows.
|
||||
*/
|
||||
arrays: RowToInsert['arrays']
|
||||
/**
|
||||
* Array rows to push to the existing array. This will simply create
|
||||
* a new row in the array table.
|
||||
*/
|
||||
arraysToPush: RowToInsert['arraysToPush']
|
||||
arrays: {
|
||||
[tableName: string]: ArrayRowToInsert[]
|
||||
}
|
||||
/**
|
||||
* This is the name of the base table
|
||||
*/
|
||||
baseTableName: string
|
||||
blocks: RowToInsert['blocks']
|
||||
blocks: {
|
||||
[blockType: string]: BlockRowToInsert[]
|
||||
}
|
||||
blocksToDelete: Set<string>
|
||||
/**
|
||||
* A snake-case field prefix, representing prior fields
|
||||
@@ -80,7 +82,6 @@ type Args = {
|
||||
export const traverseFields = ({
|
||||
adapter,
|
||||
arrays,
|
||||
arraysToPush,
|
||||
baseTableName,
|
||||
blocks,
|
||||
blocksToDelete,
|
||||
@@ -128,24 +129,13 @@ export const traverseFields = ({
|
||||
if (field.type === 'array') {
|
||||
const arrayTableName = adapter.tableNameMap.get(`${parentTableName}_${columnName}`)
|
||||
|
||||
if (!arrays[arrayTableName]) {
|
||||
arrays[arrayTableName] = []
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -168,35 +158,18 @@ export const traverseFields = ({
|
||||
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)
|
||||
}
|
||||
arrays[arrayTableName] = arrays[arrayTableName].concat(newRows)
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
let value = data[field.name]
|
||||
let push = false
|
||||
if (typeof value === 'object' && '$push' in value) {
|
||||
value = Array.isArray(value.$push) ? value.$push : [value.$push]
|
||||
push = true
|
||||
}
|
||||
|
||||
const newRows = transformArray({
|
||||
adapter,
|
||||
arrayTableName,
|
||||
baseTableName,
|
||||
blocks,
|
||||
blocksToDelete,
|
||||
data: value,
|
||||
data: data[field.name],
|
||||
field,
|
||||
numbers,
|
||||
numbersToDelete,
|
||||
@@ -210,17 +183,7 @@ export const traverseFields = ({
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
|
||||
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)
|
||||
}
|
||||
arrays[arrayTableName] = arrays[arrayTableName].concat(newRows)
|
||||
}
|
||||
|
||||
return
|
||||
@@ -301,7 +264,6 @@ export const traverseFields = ({
|
||||
traverseFields({
|
||||
adapter,
|
||||
arrays,
|
||||
arraysToPush,
|
||||
baseTableName,
|
||||
blocks,
|
||||
blocksToDelete,
|
||||
@@ -336,7 +298,6 @@ export const traverseFields = ({
|
||||
traverseFields({
|
||||
adapter,
|
||||
arrays,
|
||||
arraysToPush,
|
||||
baseTableName,
|
||||
blocks,
|
||||
blocksToDelete,
|
||||
@@ -585,19 +546,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 +570,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') {
|
||||
|
||||
@@ -2,9 +2,6 @@ export type ArrayRowToInsert = {
|
||||
arrays: {
|
||||
[tableName: string]: ArrayRowToInsert[]
|
||||
}
|
||||
arraysToPush: {
|
||||
[tableName: string]: ArrayRowToInsert[]
|
||||
}
|
||||
locales: {
|
||||
[locale: string]: Record<string, unknown>
|
||||
}
|
||||
@@ -15,9 +12,6 @@ export type BlockRowToInsert = {
|
||||
arrays: {
|
||||
[tableName: string]: ArrayRowToInsert[]
|
||||
}
|
||||
arraysToPush: {
|
||||
[tableName: string]: ArrayRowToInsert[]
|
||||
}
|
||||
locales: {
|
||||
[locale: string]: Record<string, unknown>
|
||||
}
|
||||
@@ -43,9 +37,6 @@ export type RowToInsert = {
|
||||
arrays: {
|
||||
[tableName: string]: ArrayRowToInsert[]
|
||||
}
|
||||
arraysToPush: {
|
||||
[tableName: string]: ArrayRowToInsert[]
|
||||
}
|
||||
blocks: {
|
||||
[tableName: string]: BlockRowToInsert[]
|
||||
}
|
||||
|
||||
@@ -13,13 +13,9 @@ export const updateJobs: UpdateJobs = async function updateMany(
|
||||
this: DrizzleAdapter,
|
||||
{ id, data, limit: limitArg, 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 whereToUse: Where = id ? { id: { equals: id } } : whereArg
|
||||
const limit = id ? 1 : limitArg
|
||||
|
||||
|
||||
@@ -42,54 +42,23 @@ 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 { row } = transformForWrite({
|
||||
adapter,
|
||||
data,
|
||||
enableAtomicWrites: true,
|
||||
fields,
|
||||
tableName,
|
||||
})
|
||||
const { row } = transformedForWrite
|
||||
const { arraysToPush } = transformedForWrite
|
||||
|
||||
const drizzle = db as LibSQLDatabase
|
||||
|
||||
// First, handle $push arrays
|
||||
|
||||
if (arraysToPush && Object.keys(arraysToPush)?.length) {
|
||||
await insertArrays({
|
||||
adapter,
|
||||
arrays: [arraysToPush],
|
||||
db,
|
||||
parentRows: [insertedRow],
|
||||
uuidMap: {},
|
||||
})
|
||||
}
|
||||
|
||||
// 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.
|
||||
await drizzle
|
||||
.update(adapter.tables[tableName])
|
||||
.set(row)
|
||||
.where(eq(adapter.tables[tableName].id, id))
|
||||
}
|
||||
await drizzle
|
||||
.update(adapter.tables[tableName])
|
||||
.set(row)
|
||||
.where(eq(adapter.tables[tableName].id, id))
|
||||
return ignoreResult === 'idOnly' ? ({ id } as T) : null
|
||||
}
|
||||
|
||||
@@ -105,22 +74,6 @@ 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) {
|
||||
// Nothing to update => just fetch current row and return
|
||||
findManyArgs.where = eq(adapter.tables[tableName].id, insertedRow.id)
|
||||
|
||||
const doc = await db.query[tableName].findFirst(findManyArgs)
|
||||
|
||||
return transform<T>({
|
||||
adapter,
|
||||
config: adapter.payload.config,
|
||||
data: doc,
|
||||
fields,
|
||||
joinQuery: false,
|
||||
tableName,
|
||||
})
|
||||
}
|
||||
|
||||
if (findManyKeysLength === 0 || hasOnlyColumns) {
|
||||
// Optimization - No need for joins => can simply use returning(). This is optimal for very simple collections
|
||||
// without complex fields that live in separate tables like blocks, arrays, relationships, etc.
|
||||
@@ -476,9 +429,9 @@ export const upsertRow = async <T extends Record<string, unknown> | TypeWithID>(
|
||||
|
||||
await insertArrays({
|
||||
adapter,
|
||||
arrays: [rowToInsert.arrays, rowToInsert.arraysToPush],
|
||||
arrays: [rowToInsert.arrays],
|
||||
db,
|
||||
parentRows: [insertedRow, insertedRow],
|
||||
parentRows: [insertedRow],
|
||||
uuidMap: arraysBlocksUUIDMap,
|
||||
})
|
||||
|
||||
|
||||
@@ -32,9 +32,6 @@ export const insertArrays = async ({
|
||||
const rowsByTable: RowsByTable = {}
|
||||
|
||||
arrays.forEach((arraysByTable, parentRowIndex) => {
|
||||
if (!arraysByTable || Object.keys(arraysByTable).length === 0) {
|
||||
return
|
||||
}
|
||||
Object.entries(arraysByTable).forEach(([tableName, arrayRows]) => {
|
||||
// If the table doesn't exist in map, initialize it
|
||||
if (!rowsByTable[tableName]) {
|
||||
|
||||
@@ -20,6 +20,7 @@ export const shouldUseOptimizedUpsertRow = ({
|
||||
}
|
||||
|
||||
if (
|
||||
field.type === 'array' ||
|
||||
field.type === 'blocks' ||
|
||||
((field.type === 'text' ||
|
||||
field.type === 'relationship' ||
|
||||
@@ -34,17 +35,6 @@ export const shouldUseOptimizedUpsertRow = ({
|
||||
return false
|
||||
}
|
||||
|
||||
if (field.type === 'array') {
|
||||
if (typeof value === 'object' && '$push' in value && value.$push) {
|
||||
return shouldUseOptimizedUpsertRow({
|
||||
// Only check first row - this function cares about field definitions. Each array row will have the same field definitions.
|
||||
data: Array.isArray(value.$push) ? value.$push?.[0] : value.$push,
|
||||
fields: field.flattenedFields,
|
||||
})
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if (
|
||||
(field.type === 'group' || field.type === 'tab') &&
|
||||
value &&
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/email-nodemailer",
|
||||
"version": "3.54.0",
|
||||
"version": "3.49.1",
|
||||
"description": "Payload Nodemailer Email Adapter",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/email-resend",
|
||||
"version": "3.54.0",
|
||||
"version": "3.49.1",
|
||||
"description": "Payload Resend Email Adapter",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"eslint-plugin-jsx-a11y": "6.10.2",
|
||||
"eslint-plugin-perfectionist": "3.9.1",
|
||||
"eslint-plugin-react-compiler": "19.1.0-rc.2",
|
||||
"eslint-plugin-react-hooks": "0.0.0-experimental-b1b0955f-20250901",
|
||||
"eslint-plugin-react-hooks": "0.0.0-experimental-d331ba04-20250307",
|
||||
"eslint-plugin-regexp": "2.7.0",
|
||||
"globals": "16.0.0",
|
||||
"typescript": "5.7.3",
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"eslint-plugin-jest-dom": "5.5.0",
|
||||
"eslint-plugin-jsx-a11y": "6.10.2",
|
||||
"eslint-plugin-perfectionist": "3.9.1",
|
||||
"eslint-plugin-react-hooks": "0.0.0-experimental-b1b0955f-20250901",
|
||||
"eslint-plugin-react-hooks": "0.0.0-experimental-d331ba04-20250307",
|
||||
"eslint-plugin-regexp": "2.7.0",
|
||||
"globals": "16.0.0",
|
||||
"typescript": "5.7.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/graphql",
|
||||
"version": "3.54.0",
|
||||
"version": "3.49.1",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -379,11 +379,9 @@ export const fieldToSchemaMap: FieldToSchemaMap = {
|
||||
),
|
||||
},
|
||||
hasNextPage: { type: new GraphQLNonNull(GraphQLBoolean) },
|
||||
totalDocs: { type: GraphQLInt },
|
||||
},
|
||||
}),
|
||||
args: {
|
||||
count: { type: GraphQLBoolean },
|
||||
limit: {
|
||||
type: GraphQLInt,
|
||||
},
|
||||
@@ -404,7 +402,7 @@ export const fieldToSchemaMap: FieldToSchemaMap = {
|
||||
},
|
||||
async resolve(parent, args, context: Context) {
|
||||
const { collection } = field
|
||||
const { count = false, limit, page, sort, where } = args
|
||||
const { limit, page, sort, where } = args
|
||||
const { req } = context
|
||||
|
||||
const draft = Boolean(args.draft ?? context.req.query?.draft)
|
||||
@@ -431,7 +429,7 @@ export const fieldToSchemaMap: FieldToSchemaMap = {
|
||||
throw new Error('GraphQL with array of join.field.collection is not implemented')
|
||||
}
|
||||
|
||||
const { docs, totalDocs } = await req.payload.find({
|
||||
const { docs } = await req.payload.find({
|
||||
collection,
|
||||
depth: 0,
|
||||
draft,
|
||||
@@ -441,7 +439,7 @@ export const fieldToSchemaMap: FieldToSchemaMap = {
|
||||
locale: req.locale,
|
||||
overrideAccess: false,
|
||||
page,
|
||||
pagination: count ? true : false,
|
||||
pagination: false,
|
||||
req,
|
||||
sort,
|
||||
where: fullWhere,
|
||||
@@ -456,7 +454,6 @@ export const fieldToSchemaMap: FieldToSchemaMap = {
|
||||
return {
|
||||
docs: shouldSlice ? docs.slice(0, -1) : docs,
|
||||
hasNextPage: limit === 0 ? false : limit < docs.length,
|
||||
...(count ? { totalDocs } : {}),
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ export const formatName = (string: string): string => {
|
||||
.replace(/\)/g, '_')
|
||||
.replace(/'/g, '_')
|
||||
.replace(/ /g, '')
|
||||
.replace(/\[|\]/g, '_')
|
||||
|
||||
return formatted || '_'
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-react",
|
||||
"version": "3.54.0",
|
||||
"version": "3.49.1",
|
||||
"description": "The official React SDK for Payload Live Preview",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -46,8 +46,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@types/react": "19.1.12",
|
||||
"@types/react-dom": "19.1.9",
|
||||
"@types/react": "19.1.8",
|
||||
"@types/react-dom": "19.1.6",
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-vue",
|
||||
"version": "3.54.0",
|
||||
"version": "3.49.1",
|
||||
"description": "The official Vue SDK for Payload Live Preview",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview",
|
||||
"version": "3.54.0",
|
||||
"version": "3.49.1",
|
||||
"description": "The official live preview JavaScript SDK for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/next",
|
||||
"version": "3.54.0",
|
||||
"version": "3.49.1",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -120,10 +120,10 @@
|
||||
"@next/eslint-plugin-next": "15.4.4",
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@types/busboy": "1.5.4",
|
||||
"@types/react": "19.1.12",
|
||||
"@types/react-dom": "19.1.9",
|
||||
"@types/react": "19.1.8",
|
||||
"@types/react-dom": "19.1.6",
|
||||
"@types/uuid": "10.0.0",
|
||||
"babel-plugin-react-compiler": "19.1.0-rc.3",
|
||||
"babel-plugin-react-compiler": "19.1.0-rc.2",
|
||||
"esbuild": "0.25.5",
|
||||
"esbuild-sass-plugin": "3.3.1",
|
||||
"payload": "workspace:*",
|
||||
|
||||
@@ -9,7 +9,7 @@ const handlerBuilder =
|
||||
async (
|
||||
request: Request,
|
||||
args: {
|
||||
params: Promise<{ slug?: string[] }>
|
||||
params: Promise<{ slug: string[] }>
|
||||
},
|
||||
): Promise<Response> => {
|
||||
const awaitedConfig = await config
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ServerFunction, ServerFunctionHandler } from 'payload'
|
||||
|
||||
import { _internal_renderFieldHandler, copyDataFromLocaleHandler } from '@payloadcms/ui/rsc'
|
||||
import { copyDataFromLocaleHandler } from '@payloadcms/ui/rsc'
|
||||
import { buildFormStateHandler } from '@payloadcms/ui/utilities/buildFormState'
|
||||
import { buildTableStateHandler } from '@payloadcms/ui/utilities/buildTableState'
|
||||
import { getFolderResultsComponentAndDataHandler } from '@payloadcms/ui/utilities/getFolderResultsComponentAndData'
|
||||
@@ -11,26 +11,19 @@ import { renderDocumentSlotsHandler } from '../views/Document/renderDocumentSlot
|
||||
import { renderListHandler } from '../views/List/handleServerFunction.js'
|
||||
import { initReq } from './initReq.js'
|
||||
|
||||
const baseServerFunctions: Record<string, ServerFunction<any, any>> = {
|
||||
const serverFunctions: Record<string, ServerFunction> = {
|
||||
'copy-data-from-locale': copyDataFromLocaleHandler,
|
||||
'form-state': buildFormStateHandler,
|
||||
'get-folder-results-component-and-data': getFolderResultsComponentAndDataHandler,
|
||||
'render-document': renderDocumentHandler,
|
||||
'render-document-slots': renderDocumentSlotsHandler,
|
||||
'render-field': _internal_renderFieldHandler,
|
||||
'render-list': renderListHandler,
|
||||
'schedule-publish': schedulePublishHandler,
|
||||
'table-state': buildTableStateHandler,
|
||||
}
|
||||
|
||||
export const handleServerFunctions: ServerFunctionHandler = async (args) => {
|
||||
const {
|
||||
name: fnKey,
|
||||
args: fnArgs,
|
||||
config: configPromise,
|
||||
importMap,
|
||||
serverFunctions: extraServerFunctions,
|
||||
} = args
|
||||
const { name: fnKey, args: fnArgs, config: configPromise, importMap } = args
|
||||
|
||||
const { req } = await initReq({
|
||||
configPromise,
|
||||
@@ -44,11 +37,6 @@ export const handleServerFunctions: ServerFunctionHandler = async (args) => {
|
||||
req,
|
||||
}
|
||||
|
||||
const serverFunctions = {
|
||||
...baseServerFunctions,
|
||||
...(extraServerFunctions || {}),
|
||||
}
|
||||
|
||||
const fn = serverFunctions[fnKey]
|
||||
|
||||
if (!fn) {
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
@import '~@payloadcms/ui/scss';
|
||||
|
||||
$tab-width: 24px;
|
||||
$tab-width: 16px;
|
||||
|
||||
@layer payload-default {
|
||||
.query-inspector {
|
||||
--tab-width: 24px;
|
||||
|
||||
&__json-children {
|
||||
position: relative;
|
||||
|
||||
&--nested {
|
||||
& li {
|
||||
padding-left: 8px;
|
||||
padding-left: $tab-width;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,14 +23,6 @@ $tab-width: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
&__row-line {
|
||||
&--nested {
|
||||
.query-inspector__json-children {
|
||||
padding-left: var(--tab-width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__list-wrap {
|
||||
position: relative;
|
||||
}
|
||||
@@ -47,16 +37,10 @@ $tab-width: 24px;
|
||||
border-bottom-right-radius: 0;
|
||||
position: relative;
|
||||
display: flex;
|
||||
column-gap: 14px;
|
||||
row-gap: 10px;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
left: 0;
|
||||
left: -3px;
|
||||
width: calc(100% + 3px);
|
||||
background-color: var(--theme-elevation-50);
|
||||
|
||||
&:not(.query-inspector__list-toggle--empty) {
|
||||
margin-left: calc(var(--tab-width) * -1 - 10px);
|
||||
}
|
||||
|
||||
svg .stroke {
|
||||
stroke: var(--theme-elevation-400);
|
||||
@@ -98,31 +82,13 @@ $tab-width: 24px;
|
||||
&__bracket {
|
||||
position: relative;
|
||||
|
||||
&--position-end {
|
||||
left: 2px;
|
||||
width: calc(100% - 5px);
|
||||
&--nested {
|
||||
margin-left: $tab-width;
|
||||
}
|
||||
}
|
||||
|
||||
// Some specific rules targetting the very top of the nested JSON structure or very first items since they need slightly different styling
|
||||
&__results {
|
||||
& > .query-inspector__row-line--nested {
|
||||
& > .query-inspector__list-toggle {
|
||||
margin-left: 0;
|
||||
column-gap: 6px;
|
||||
|
||||
.query-inspector__toggle-row-icon {
|
||||
margin-left: -4px;
|
||||
}
|
||||
}
|
||||
|
||||
& > .query-inspector__json-children {
|
||||
padding-left: calc(var(--base) * 1);
|
||||
}
|
||||
|
||||
& > .query-inspector__bracket--nested > .query-inspector__bracket--position-end {
|
||||
padding-left: 16px;
|
||||
}
|
||||
&--position-end {
|
||||
left: 1px;
|
||||
width: calc(100% - 5px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -143,7 +143,7 @@ export const handleGroupBy = async ({
|
||||
},
|
||||
})
|
||||
|
||||
let heading = valueOrRelationshipID
|
||||
let heading = valueOrRelationshipID || req.i18n.t('general:noValue')
|
||||
|
||||
if (
|
||||
groupByField?.type === 'relationship' &&
|
||||
@@ -155,24 +155,14 @@ export const handleGroupBy = async ({
|
||||
valueOrRelationshipID
|
||||
}
|
||||
|
||||
if (groupByField.type === 'date' && valueOrRelationshipID) {
|
||||
if (groupByField.type === 'date') {
|
||||
heading = formatDate({
|
||||
date: String(valueOrRelationshipID),
|
||||
date: String(heading),
|
||||
i18n: req.i18n,
|
||||
pattern: clientConfig.admin.dateFormat,
|
||||
})
|
||||
}
|
||||
|
||||
if (groupByField.type === 'checkbox') {
|
||||
if (valueOrRelationshipID === true) {
|
||||
heading = req.i18n.t('general:true')
|
||||
}
|
||||
|
||||
if (valueOrRelationshipID === false) {
|
||||
heading = req.i18n.t('general:false')
|
||||
}
|
||||
}
|
||||
|
||||
if (groupData.docs && groupData.docs.length > 0) {
|
||||
const { columnState: newColumnState, Table: NewTable } = renderTable({
|
||||
clientCollectionConfig,
|
||||
@@ -184,7 +174,7 @@ export const handleGroupBy = async ({
|
||||
enableRowSelections,
|
||||
groupByFieldPath,
|
||||
groupByValue: valueOrRelationshipID,
|
||||
heading: heading || req.i18n.t('general:noValue'),
|
||||
heading,
|
||||
i18n: req.i18n,
|
||||
key: `table-${valueOrRelationshipID}`,
|
||||
orderableFieldName: collectionConfig.orderable === true ? '_order' : undefined,
|
||||
|
||||
@@ -138,14 +138,16 @@ export const renderListView = async (
|
||||
throw new Error('not-found')
|
||||
}
|
||||
|
||||
const baseFilterConstraint = await (
|
||||
collectionConfig.admin?.baseFilter ?? collectionConfig.admin?.baseListFilter
|
||||
)?.({
|
||||
limit: query.limit,
|
||||
page: query.page,
|
||||
req,
|
||||
sort: query.sort,
|
||||
})
|
||||
let baseListFilter = undefined
|
||||
|
||||
if (typeof collectionConfig.admin?.baseListFilter === 'function') {
|
||||
baseListFilter = await collectionConfig.admin.baseListFilter({
|
||||
limit: query.limit,
|
||||
page: query.page,
|
||||
req,
|
||||
sort: query.sort,
|
||||
})
|
||||
}
|
||||
|
||||
let queryPreset: QueryPreset | undefined
|
||||
let queryPresetPermissions: SanitizedCollectionPermission | undefined
|
||||
@@ -153,7 +155,7 @@ export const renderListView = async (
|
||||
let whereWithMergedSearch = mergeListSearchAndWhere({
|
||||
collectionConfig,
|
||||
search: typeof query?.search === 'string' ? query.search : undefined,
|
||||
where: combineWhereConstraints([query?.where, baseFilterConstraint]),
|
||||
where: combineWhereConstraints([query?.where, baseListFilter]),
|
||||
})
|
||||
|
||||
if (trash === true) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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`,
|
||||
|
||||
@@ -17,16 +17,10 @@ import {
|
||||
type SanitizedFieldPermissions,
|
||||
type VersionField,
|
||||
} from 'payload'
|
||||
import {
|
||||
fieldIsID,
|
||||
fieldShouldBeLocalized,
|
||||
getFieldPaths,
|
||||
getFieldPermissions,
|
||||
getUniqueListBy,
|
||||
tabHasName,
|
||||
} from 'payload/shared'
|
||||
import { fieldIsID, fieldShouldBeLocalized, getUniqueListBy, tabHasName } from 'payload/shared'
|
||||
|
||||
import { diffComponents } from './fields/index.js'
|
||||
import { getFieldPathsModified } from './utilities/getFieldPathsModified.js'
|
||||
|
||||
export type BuildVersionFieldsArgs = {
|
||||
clientSchemaMap: ClientFieldSchemaMap
|
||||
@@ -90,7 +84,7 @@ export const buildVersionFields = ({
|
||||
continue
|
||||
}
|
||||
|
||||
const { indexPath, path, schemaPath } = getFieldPaths({
|
||||
const { indexPath, path, schemaPath } = getFieldPathsModified({
|
||||
field,
|
||||
index: fieldIndex,
|
||||
parentIndexPath,
|
||||
@@ -229,16 +223,21 @@ const buildVersionField = ({
|
||||
BuildVersionFieldsArgs,
|
||||
'fields' | 'parentIndexPath' | 'versionFromSiblingData' | 'versionToSiblingData'
|
||||
>): BaseVersionField | null => {
|
||||
const { permissions, read: hasReadPermission } = getFieldPermissions({
|
||||
field,
|
||||
operation: 'read',
|
||||
parentName: parentPath?.includes('.')
|
||||
? parentPath.split('.')[parentPath.split('.').length - 1]
|
||||
: parentPath,
|
||||
permissions: fieldPermissions,
|
||||
})
|
||||
const fieldName: null | string = 'name' in field ? field.name : null
|
||||
|
||||
if (!hasReadPermission) {
|
||||
const hasPermission =
|
||||
fieldPermissions === true ||
|
||||
!fieldName ||
|
||||
fieldPermissions?.[fieldName] === true ||
|
||||
fieldPermissions?.[fieldName]?.read
|
||||
|
||||
const subFieldPermissions =
|
||||
fieldPermissions === true ||
|
||||
!fieldName ||
|
||||
fieldPermissions?.[fieldName] === true ||
|
||||
fieldPermissions?.[fieldName]?.fields
|
||||
|
||||
if (!hasPermission) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -286,54 +285,34 @@ const buildVersionField = ({
|
||||
indexPath: tabIndexPath,
|
||||
path: tabPath,
|
||||
schemaPath: tabSchemaPath,
|
||||
} = getFieldPaths({
|
||||
} = getFieldPathsModified({
|
||||
field: tabAsField,
|
||||
index: tabIndex,
|
||||
parentIndexPath: indexPath,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
})
|
||||
|
||||
let tabPermissions: typeof fieldPermissions = undefined
|
||||
|
||||
if (typeof permissions === 'boolean') {
|
||||
tabPermissions = permissions
|
||||
} else if (permissions && typeof permissions === 'object') {
|
||||
if ('name' in tab) {
|
||||
tabPermissions =
|
||||
typeof permissions.fields?.[tab.name] === 'object'
|
||||
? permissions.fields?.[tab.name].fields
|
||||
: permissions.fields?.[tab.name]
|
||||
} else {
|
||||
tabPermissions = permissions.fields
|
||||
}
|
||||
}
|
||||
|
||||
const tabVersion = {
|
||||
name: 'name' in tab ? tab.name : null,
|
||||
fields: buildVersionFields({
|
||||
clientSchemaMap,
|
||||
customDiffComponents,
|
||||
entitySlug,
|
||||
fieldPermissions: tabPermissions,
|
||||
fieldPermissions,
|
||||
fields: tab.fields,
|
||||
i18n,
|
||||
modifiedOnly,
|
||||
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)
|
||||
@@ -345,13 +324,6 @@ const buildVersionField = ({
|
||||
}
|
||||
} // At this point, we are dealing with a `row`, `collapsible`, etc
|
||||
else if ('fields' in field) {
|
||||
let subfieldPermissions: typeof fieldPermissions = undefined
|
||||
|
||||
if (typeof permissions === 'boolean') {
|
||||
subfieldPermissions = permissions
|
||||
} else if (permissions && typeof permissions === 'object') {
|
||||
subfieldPermissions = permissions.fields
|
||||
}
|
||||
if (field.type === 'array' && (valueTo || valueFrom)) {
|
||||
const maxLength = Math.max(
|
||||
Array.isArray(valueTo) ? valueTo.length : 0,
|
||||
@@ -367,15 +339,15 @@ const buildVersionField = ({
|
||||
clientSchemaMap,
|
||||
customDiffComponents,
|
||||
entitySlug,
|
||||
fieldPermissions: subfieldPermissions,
|
||||
fieldPermissions,
|
||||
fields: field.fields,
|
||||
i18n,
|
||||
modifiedOnly,
|
||||
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,
|
||||
@@ -391,7 +363,7 @@ const buildVersionField = ({
|
||||
clientSchemaMap,
|
||||
customDiffComponents,
|
||||
entitySlug,
|
||||
fieldPermissions: subfieldPermissions,
|
||||
fieldPermissions,
|
||||
fields: field.fields,
|
||||
i18n,
|
||||
modifiedOnly,
|
||||
@@ -449,32 +421,19 @@ const buildVersionField = ({
|
||||
}
|
||||
}
|
||||
|
||||
let blockPermissions: typeof fieldPermissions = undefined
|
||||
|
||||
if (permissions === true) {
|
||||
blockPermissions = true
|
||||
} else {
|
||||
const permissionsBlockSpecific = permissions?.blocks?.[blockSlugToMatch]
|
||||
if (permissionsBlockSpecific === true) {
|
||||
blockPermissions = true
|
||||
} else {
|
||||
blockPermissions = permissionsBlockSpecific?.fields
|
||||
}
|
||||
}
|
||||
|
||||
baseVersionField.rows[i] = buildVersionFields({
|
||||
clientSchemaMap,
|
||||
customDiffComponents,
|
||||
entitySlug,
|
||||
fieldPermissions: blockPermissions,
|
||||
fieldPermissions,
|
||||
fields,
|
||||
i18n,
|
||||
modifiedOnly,
|
||||
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,
|
||||
@@ -500,7 +459,7 @@ const buildVersionField = ({
|
||||
*/
|
||||
diffMethod: 'diffWordsWithSpace',
|
||||
field: clientField,
|
||||
fieldPermissions: typeof permissions === 'object' ? permissions.fields : permissions,
|
||||
fieldPermissions: subFieldPermissions,
|
||||
parentIsLocalized,
|
||||
|
||||
nestingLevel: nestingLevel ? nestingLevel : undefined,
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/payload-cloud",
|
||||
"version": "3.54.0",
|
||||
"version": "3.49.1",
|
||||
"description": "The official Payload Cloud plugin",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload",
|
||||
"version": "3.54.0",
|
||||
"version": "3.49.1",
|
||||
"description": "Node, React, Headless CMS and Application Framework built on Next.js",
|
||||
"keywords": [
|
||||
"admin panel",
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { MarkOptional } from 'ts-essentials'
|
||||
|
||||
import type { RowField, RowFieldClient } from '../../fields/config/types.js'
|
||||
import type {
|
||||
ClientComponentProps,
|
||||
ClientFieldBase,
|
||||
FieldClientComponent,
|
||||
FieldPaths,
|
||||
@@ -22,7 +21,9 @@ import type {
|
||||
|
||||
type RowFieldClientWithoutType = MarkOptional<RowFieldClient, 'type'>
|
||||
|
||||
type RowFieldBaseClientProps = Omit<FieldPaths, 'path'> & Pick<ClientComponentProps, 'forceRender'>
|
||||
type RowFieldBaseClientProps = {
|
||||
readonly forceRender?: boolean
|
||||
} & Omit<FieldPaths, 'path'>
|
||||
|
||||
export type RowFieldClientProps = Omit<ClientFieldBase<RowFieldClientWithoutType>, 'path'> &
|
||||
RowFieldBaseClientProps
|
||||
|
||||
@@ -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'>
|
||||
|
||||
@@ -21,13 +21,6 @@ export type ClientFieldWithOptionalType = MarkOptional<ClientField, 'type'>
|
||||
export type ClientComponentProps = {
|
||||
customComponents?: FormField['customComponents']
|
||||
field: ClientBlock | ClientField | ClientTab
|
||||
/**
|
||||
* Controls the rendering behavior of the fields, i.e. defers rendering until they intersect with the viewport using the Intersection Observer API.
|
||||
*
|
||||
* If true, the fields will be rendered immediately, rather than waiting for them to intersect with the viewport.
|
||||
*
|
||||
* If a number is provided, will immediately render fields _up to that index_.
|
||||
*/
|
||||
forceRender?: boolean
|
||||
permissions?: SanitizedFieldPermissions
|
||||
readOnly?: boolean
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -36,31 +36,6 @@ export type ServerFunctionHandler = (
|
||||
args: {
|
||||
config: Promise<SanitizedConfig> | SanitizedConfig
|
||||
importMap: ImportMap
|
||||
/**
|
||||
* A map of server function names to their implementations. These are
|
||||
* registered alongside the base server functions and can be called
|
||||
* using the useServerFunctions() hook.
|
||||
*
|
||||
* @example
|
||||
* const { serverFunction } = useServerFunctions()
|
||||
*
|
||||
* const callServerFunction = useCallback(() => {
|
||||
*
|
||||
* async function call() {
|
||||
* const result = (await serverFunction({
|
||||
* name: 'record-key',
|
||||
* args: {
|
||||
* // Your args
|
||||
* },
|
||||
* }))
|
||||
*
|
||||
* // Do someting with the result
|
||||
* }
|
||||
*
|
||||
* void call()
|
||||
* }, [serverFunction])
|
||||
*/
|
||||
serverFunctions?: Record<string, ServerFunction<any, any>>
|
||||
} & ServerFunctionClientArgs,
|
||||
) => Promise<unknown>
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user