Compare commits

..

17 Commits

Author SHA1 Message Date
Sasha
4283bebc37 chore: union with Client / VercelClient 2024-10-18 22:28:11 +03:00
Sasha
4082680099 fix(drizzle): expose db.drizzle.$client type 2024-10-17 23:35:05 +03:00
Sasha
9056b9fe9b fix(db-mongodb): virtual fields within row / collapsible / tabs (#8733)
Fixes https://github.com/payloadcms/payload/issues/8674
2024-10-17 16:23:45 -04:00
Paul
82f278931b fix(ui): padding on relationship fields when no options or loading text is displayed (#8767) 2024-10-17 19:46:28 +00:00
Germán Jabloñski
a7895560a6 fix(richtext-lexical): fix CLS on collapsed/expanded state of Lexical blocks (#8029)
## Description

The goal is to reduce CLS on collapsed/expanded state of Lexical blocks.
That state is stored as "preferences" and is different for each user.
As Payload has been working so far, if the state of a Lexical block was
"collapsed", it was rendered expanded, and when the correct state was
obtained from the server, it was collapsed producing a CLS with a poor
UX.

My original idea was to get the correct state on the first render.
Talking to @AlessioGr and @jmikrut, we saw that this can be a bit
difficult or challenging, since the feature on the server does not have
access to the Payload object, nor to the user who is making the request.

I was instructed to mimic the behavior of blocks not in Lexial
(`\ui\src\fields\Collapsible\index.tsx`). There the blocks are rendered
after the collapse/expand state is obtained in a useEffect.

In the following video, the case where the first block is collapsed is
shown, rendering everything with a "fast 4G" connection throttle.


https://github.com/user-attachments/assets/078e37c7-6540-4183-a266-bd751cc9d78e

Yes, it's a slight improvement over current behavior. But it could be
much better. There are request waterfalls several levels deep, and
plenty of CLS still.

Unless there is some very big tradeoff that I'm not aware of, I think
it's worth exposing the Payload object and the user to the server in
order to get the correct state on the first render.

And if that's not possible and the request has to be made on the client,
I think initializing the state as collapsed and then expanding it is
better than not showing it at all.

Another observation that is evident from the video, is that there are
several sources or causes of CLS besides the expanded/collapsed state of
the blocks.

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## Checklist:

- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] Existing test suite passes locally with my changes
- [x] I have made corresponding changes to the documentation
2024-10-17 19:36:14 +00:00
Paul
1f0d8da182 fix(plugin-seo): description and title fields now respect given minLength and maxLength rules for passing validation (#8765)
Previously minLength or maxLength was not being respected and the
components would use default values only.
2024-10-17 19:28:54 +00:00
Said Akhrarov
c91f21bb78 docs: fix incorrect link for outside-nextjs in local-api importing it section (#8764)
Currently in the `beta` docs at the bottom of [Local API Overview Import
It
section](https://payloadcms.com/docs/beta/local-api/overview#importing-it)
there is a link for _Outside Nextjs_ which incorrectly sends you to
`/docs/beta/beta/local-api/outside-nextjs` instead of
`docs/beta/local-api/outside-nextjs`.

Interestingly enough, a `Not Found` component/message is not rendered
and instead you see a blank screen.

---------

Co-authored-by: Sasha <64744993+r1tsuu@users.noreply.github.com>
2024-10-17 13:13:37 -06:00
Elliot DeNolf
7136515f8d chore(release): v3.0.0-beta.116 [skip ci] 2024-10-17 09:05:45 -04:00
Sasha
73102e97fe fix(drizzle): bump drizzle-orm in drizzle package to 0.35.1 (#8759)
Fixes https://github.com/payloadcms/payload/issues/8757. Other packages
have `drizzle-orm` `0.35.1`, but this does not (`0.34.1-1f15bfd`).
2024-10-17 07:14:02 -04:00
Sasha
f37e476758 fix(db-postgres): esm compatible import of pg (#8758)
Fixes this error that doesn't occur in our monorepo but on users
projects.
<img width="1040" alt="image"
src="https://github.com/user-attachments/assets/6b410959-9108-4022-ae0a-64bc4be6de67">
2024-10-17 10:02:30 +00:00
Sasha
90bca15f52 fix(drizzle): enforce uniqueness on index names (#8754)
Fixes https://github.com/payloadcms/payload/issues/8752

Previously, trying to define a config like this:
```ts
{
  type: 'text',
  name: 'someText',
  index: true,
},
{
  type: 'array',
  name: 'some',
  index: true,
  fields: [
    {
      type: 'text',
      name: 'text',
      index: true,
    },
  ],
}
```

Lead to the error:
```
Warning  We've found duplicated index name across public schema. Please rename your index in either the demonstration table or the table with the duplicated index name
```

Now, if we encounter duplicates, we increment the name like this:
`collection_some_text_idx`
`collection_some_text_1_idx`

---------

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2024-10-17 02:05:27 +00:00
Sasha
872b205acc fix(drizzle): select hasMany nested to array + tab/group (#8737)
Fixes https://github.com/payloadcms/payload/issues/8732
2024-10-16 21:52:48 -04:00
Jarrod Flesch
99b4359e89 fix: pg where filters not respected in policy generation (#8753)
Fixes https://github.com/payloadcms/payload/issues/8224

Fixes an issue with PG `where` filters not being respected when
generating doc policies/permissions by utilizing the combineQueries
function in getEntityPolicies function.
2024-10-16 16:54:23 -04:00
Dan Ribbens
b269d33278 chore: bump drizzle-kit 0.26.2 (#8750) 2024-10-16 16:32:54 -04:00
Patrik
c63b7bc606 fix: disables document locking of payload-locked-documents, preferences, & migrations collections (#8744)
Fixes #8589 

### Issue: 
There were problems with updating documents in
`payload-locked-documents` collection i.e when "taking over" a document
- a `patch` request is sent to `payload-locked-documents` to update the
user (owner).

However, as a result, this `update` operation would lock that
corresponding doc in `payload-locked-documents` and therefore error on
the `patch` request.

### Fix:
Disable document locking entirely from `payload-locked-documents` &
`preferences` & `migrations` collections
2024-10-16 14:46:39 -04:00
Elliot DeNolf
0fb92d3a0a chore(release): v3.0.0-beta.115 [skip ci] 2024-10-16 14:20:27 -04:00
Jarrod Flesch
99228b62ce chore: improves getLatestCollectionVersion where constraints (#8746) 2024-10-16 14:12:43 -04:00
85 changed files with 546 additions and 144 deletions

View File

@@ -145,7 +145,7 @@ Instead, we utilize component paths to reference React Components. This method e
When constructing the `ClientConfig`, Payload uses the component paths as keys to fetch the corresponding React Component imports from the Import Map. It then substitutes the `PayloadComponent` with a `MappedComponent`. A `MappedComponent` includes the React Component and additional metadata, such as whether it's a server or a client component and which props it should receive. These components are then rendered through the `<RenderComponent />` component within the Payload Admin Panel.
Import maps are regenerated whenever you modify any element related to component paths. This regeneration occurs at startup and whenever Hot Module Replacement (HMR) runs. If the import maps fail to regenerate during HMR, you can restart your application and execute the `payload generate:importmap` command to manually create a new import map. If you encounter any errors running this command, see the [Troubleshooting](/docs/beta/local-api/outside-nextjs#troubleshooting) section.
Import maps are regenerated whenever you modify any element related to component paths. This regeneration occurs at startup and whenever Hot Module Replacement (HMR) runs. If the import maps fail to regenerate during HMR, you can restart your application and execute the `payload generate:importmap` command to manually create a new import map. If you encounter any errors running this command, see the [Troubleshooting](../local-api/outside-nextjs#troubleshooting) section.
### Component paths in external packages

View File

@@ -97,7 +97,7 @@ Cookies can cross subdomains without being considered third party cookies, for e
##### 2. Configure cookies
If option 1 isn't possible, then you can get around this limitation by [configuring your cookies](https://payloadcms.com/docs/beta/authentication/overview#config-options) on your authentication collection to achieve the following setup:
If option 1 isn't possible, then you can get around this limitation by [configuring your cookies](./overview#config-options) on your authentication collection to achieve the following setup:
```
SameSite: None // allows the cookie to cross domains
@@ -122,7 +122,7 @@ Configuration example:
},
```
If you're configuring [cors](https://payloadcms.com/docs/beta/production/preventing-abuse#cross-origin-resource-sharing-cors) in your Payload config, you won't be able to use a wildcard anymore, you'll need to specify the list of allowed domains.
If you're configuring [cors](../production/preventing-abuse#cross-origin-resource-sharing-cors) in your Payload config, you won't be able to use a wildcard anymore, you'll need to specify the list of allowed domains.
<Banner type="success">

View File

@@ -84,7 +84,7 @@ export default buildConfig({
## Email
Powered by [Resend](https://resend.com), Payload Cloud comes with integrated email support out of the box. No configuration is needed, and you can use `payload.sendEmail()` to send email right from your Payload app. To learn more about sending email with Payload, checkout the [Email Configuration](https://payloadcms.com/docs/email/overview) overview.
Powered by [Resend](https://resend.com), Payload Cloud comes with integrated email support out of the box. No configuration is needed, and you can use `payload.sendEmail()` to send email right from your Payload app. To learn more about sending email with Payload, checkout the [Email Configuration](../email/overview) overview.
If you are on the Pro or Enterprise plan, you can add your own custom Email domain name. From the Email page of your projects Settings, add the domain you wish to use for email delivery. This will generate a set of DNS records. Add these records to your DNS provider and click verify to check that your records are resolving properly. Once verified, your emails will now be sent from your custom domain name.

View File

@@ -157,7 +157,7 @@ You can disable this setting and solely use migrations to manage your local deve
For this reason, we suggest that you leave `push` as its default setting and treat your local dev database as a sandbox.
For more information about push mode and prototyping in development, [click here](/docs/beta/database/postgres#prototyping-in-dev-mode).
For more information about push mode and prototyping in development, [click here](./postgres#prototyping-in-dev-mode).
The typical workflow in Payload is to build out your Payload configs, install plugins, and make progress in development mode - allowing Drizzle to push your changes to your local database for you. Once you're finished, you can create a migration.

View File

@@ -99,7 +99,7 @@ Alternatively, you can disable `push` and rely solely on migrations to keep your
In Postgres, migrations are a fundamental aspect of working with Payload and you should become familiar with how they work.
For more information about migrations, [click here](/docs/beta/database/migrations#when-to-run-migrations).
For more information about migrations, [click here](./migrations#when-to-run-migrations).
## Drizzle schema hooks

View File

@@ -78,7 +78,7 @@ Alternatively, you can disable `push` and rely solely on migrations to keep your
In SQLite, migrations are a fundamental aspect of working with Payload and you should become familiar with how they work.
For more information about migrations, [click here](/docs/beta/database/migrations#when-to-run-migrations).
For more information about migrations, [click here](./migrations#when-to-run-migrations).
## Drizzle schema hooks

View File

@@ -71,7 +71,7 @@ import config from '@payload-config'
const payload = await getPayload({ config })
```
Both options function in exactly the same way outside of one having HMR support and the other not. For more information about using Payload outside of Next.js, [click here](/docs/beta/local-api/outside-nextjs).
Both options function in exactly the same way outside of one having HMR support and the other not. For more information about using Payload outside of Next.js, [click here](./outside-nextjs).
## Local options available

View File

@@ -138,7 +138,7 @@ const beforeEmail: BeforeEmail<FormSubmission> = (emailsToSend, beforeChangePara
### `defaultToEmail`
Provide a fallback for the email address to send form submissions to. If the email in form configuration does not have a to email set, this email address will be used. If this is not provided then it falls back to the `defaultFromAddress` in your [email configuration](https://payloadcms.com/docs/beta/email/overview).
Provide a fallback for the email address to send form submissions to. If the email in form configuration does not have a to email set, this email address will be used. If this is not provided then it falls back to the `defaultFromAddress` in your [email configuration](../email/overview).
```ts
// payload.config.ts
@@ -412,7 +412,7 @@ formBuilder({
## Email
This plugin relies on the [email configuration](https://payloadcms.com/docs/beta/email/overview) defined in your payload configuration. It will read from your config and attempt to send your emails using the credentials provided.
This plugin relies on the [email configuration](../email/overview) defined in your payload configuration. It will read from your config and attempt to send your emails using the credentials provided.
### Email formatting

View File

@@ -276,6 +276,10 @@ OverviewField({
})
```
<Banner type="info">
Tip: You can override the length rules by changing the minLength and maxLength props on the fields. In the case of the OverviewField you can use `titleOverrides` and `descriptionOverrides` to override the length rules.
</Banner>
## TypeScript
All types can be directly imported:

View File

@@ -1,6 +1,6 @@
{
"name": "payload-monorepo",
"version": "3.0.0-beta.115",
"version": "3.0.0-beta.116",
"private": true,
"type": "module",
"scripts": {
@@ -132,7 +132,7 @@
"create-payload-app": "workspace:*",
"cross-env": "7.0.3",
"dotenv": "16.4.5",
"drizzle-kit": "0.26.1",
"drizzle-kit": "0.26.2",
"drizzle-orm": "0.35.1",
"escape-html": "^1.0.3",
"execa": "5.1.1",

View File

@@ -1,6 +1,6 @@
{
"name": "create-payload-app",
"version": "3.0.0-beta.115",
"version": "3.0.0-beta.116",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

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

View File

@@ -246,6 +246,10 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
buildSchemaOptions: BuildSchemaOptions,
): void => {
field.fields.forEach((subField: Field) => {
if (fieldIsVirtual(subField)) {
return
}
const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[subField.type]
if (addFieldSchema) {
@@ -501,6 +505,10 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
buildSchemaOptions: BuildSchemaOptions,
): void => {
field.fields.forEach((subField: Field) => {
if (fieldIsVirtual(subField)) {
return
}
const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[subField.type]
if (addFieldSchema) {
@@ -545,6 +553,9 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
): void => {
field.tabs.forEach((tab) => {
if (tabHasName(tab)) {
if (fieldIsVirtual(tab)) {
return
}
const baseSchema = {
type: buildSchema(config, tab.fields, {
disableUnique: buildSchemaOptions.disableUnique,
@@ -562,6 +573,9 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
})
} else {
tab.fields.forEach((subField: Field) => {
if (fieldIsVirtual(subField)) {
return
}
const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[subField.type]
if (addFieldSchema) {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-postgres",
"version": "3.0.0-beta.115",
"version": "3.0.0-beta.116",
"description": "The officially supported Postgres database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {
@@ -50,7 +50,7 @@
"@payloadcms/drizzle": "workspace:*",
"@types/pg": "8.10.2",
"console-table-printer": "2.11.2",
"drizzle-kit": "0.26.1",
"drizzle-kit": "0.26.2",
"drizzle-orm": "0.35.1",
"pg": "8.11.3",
"prompts": "2.4.2",

View File

@@ -136,6 +136,7 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
findGlobalVersions,
findOne,
findVersions,
indexes: new Set<string>(),
init,
insert,
migrate,

View File

@@ -9,7 +9,7 @@ import type {
import type { DrizzleAdapter } from '@payloadcms/drizzle/types'
import type { DrizzleConfig } from 'drizzle-orm'
import type { PgSchema, PgTableFn, PgTransactionConfig } from 'drizzle-orm/pg-core'
import type { Pool, PoolConfig } from 'pg'
import type { Client, Pool, PoolConfig } from 'pg'
export type Args = {
/**
@@ -62,7 +62,9 @@ declare module 'payload' {
afterSchemaInit: PostgresSchemaHook[]
beforeSchemaInit: PostgresSchemaHook[]
beginTransaction: (options?: PgTransactionConfig) => Promise<null | number | string>
drizzle: PostgresDB
drizzle: {
$client: Client | Pool
} & PostgresDB
enums: Record<string, GenericEnum>
/**
* An object keyed on each table, with a key value pair where the constraint name is the key, followed by the dot-notation field name

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-sqlite",
"version": "3.0.0-beta.115",
"version": "3.0.0-beta.116",
"description": "The officially supported SQLite database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {
@@ -48,7 +48,7 @@
"@libsql/client": "0.14.0",
"@payloadcms/drizzle": "workspace:*",
"console-table-printer": "2.11.2",
"drizzle-kit": "0.26.1",
"drizzle-kit": "0.26.2",
"drizzle-orm": "0.35.1",
"prompts": "2.4.2",
"to-snake-case": "1.0.0",

View File

@@ -1,5 +1,4 @@
import type { DrizzleAdapter } from '@payloadcms/drizzle/types'
import type { LibSQLDatabase } from 'drizzle-orm/libsql'
import type { Connect } from 'payload'
import { createClient } from '@libsql/client'

View File

@@ -133,6 +133,7 @@ export function sqliteAdapter(args: Args): DatabaseAdapterObj<SQLiteAdapter> {
findGlobalVersions,
findOne,
findVersions,
indexes: new Set<string>(),
init,
insert,
migrate,

View File

@@ -1,3 +1,4 @@
import type { DrizzleAdapter } from '@payloadcms/drizzle/types'
import type { Relation } from 'drizzle-orm'
import type {
AnySQLiteColumn,
@@ -9,7 +10,7 @@ import type {
} from 'drizzle-orm/sqlite-core'
import type { Field, SanitizedJoins } from 'payload'
import { createTableName } from '@payloadcms/drizzle'
import { buildIndexName, createTableName } from '@payloadcms/drizzle'
import { relations, sql } from 'drizzle-orm'
import {
foreignKey,
@@ -416,21 +417,25 @@ export const buildTable = ({
foreignColumns: [adapter.tables[formattedRelationTo].id],
}).onDelete('cascade')
const indexName = [colName]
const indexColumns = [colName]
const unique = !disableUnique && uniqueRelationships.has(relationTo)
if (unique) {
indexName.push('path')
indexColumns.push('path')
}
if (hasLocalizedRelationshipField) {
indexName.push('locale')
indexColumns.push('locale')
}
relationExtraConfig[`${relationTo}IdIdx`] = createIndex({
name: indexName,
columnName: `${formattedRelationTo}_id`,
tableName: relationshipsTableName,
const indexName = buildIndexName({
name: `${relationshipsTableName}_${formattedRelationTo}_id`,
adapter: adapter as unknown as DrizzleAdapter,
})
relationExtraConfig[indexName] = createIndex({
name: indexColumns,
indexName,
unique,
})
})

View File

@@ -3,13 +3,12 @@ import type { AnySQLiteColumn } from 'drizzle-orm/sqlite-core'
import { index, uniqueIndex } from 'drizzle-orm/sqlite-core'
type CreateIndexArgs = {
columnName: string
indexName: string
name: string | string[]
tableName: string
unique?: boolean
}
export const createIndex = ({ name, columnName, tableName, unique }: CreateIndexArgs) => {
export const createIndex = ({ name, indexName, unique }: CreateIndexArgs) => {
return (table: { [x: string]: AnySQLiteColumn }) => {
let columns
if (Array.isArray(name)) {
@@ -21,8 +20,8 @@ export const createIndex = ({ name, columnName, tableName, unique }: CreateIndex
columns = [table[name]]
}
if (unique) {
return uniqueIndex(`${tableName}_${columnName}_idx`).on(columns[0], ...columns.slice(1))
return uniqueIndex(indexName).on(columns[0], ...columns.slice(1))
}
return index(`${tableName}_${columnName}_idx`).on(columns[0], ...columns.slice(1))
return index(indexName).on(columns[0], ...columns.slice(1))
}
}

View File

@@ -1,8 +1,10 @@
import type { DrizzleAdapter } from '@payloadcms/drizzle/types'
import type { Relation } from 'drizzle-orm'
import type { IndexBuilder, SQLiteColumnBuilder } from 'drizzle-orm/sqlite-core'
import type { Field, SanitizedJoins, TabAsField } from 'payload'
import {
buildIndexName,
createTableName,
hasLocalesTable,
validateExistingBlockIsIdentical,
@@ -164,10 +166,15 @@ export const traverseFields = ({
}
adapter.fieldConstraints[rootTableName][`${columnName}_idx`] = constraintValue
}
targetIndexes[`${newTableName}_${field.name}Idx`] = createIndex({
const indexName = buildIndexName({
name: `${newTableName}_${columnName}`,
adapter: adapter as unknown as DrizzleAdapter,
})
targetIndexes[indexName] = createIndex({
name: field.localized ? [fieldName, '_locale'] : fieldName,
columnName,
tableName: newTableName,
indexName,
unique,
})
}

View File

@@ -167,7 +167,9 @@ declare module 'payload' {
extends Omit<Args, 'idType' | 'logger' | 'migrationDir' | 'pool'>,
DrizzleAdapter {
beginTransaction: (options?: SQLiteTransactionConfig) => Promise<null | number | string>
drizzle: LibSQLDatabase
drizzle: { $client: Client } & LibSQLDatabase<
Record<string, GenericRelation | GenericTable> & Record<string, unknown>
>
/**
* An object keyed on each table, with a key value pair where the constraint name is the key, followed by the dot-notation field name
* Used for returning properly formed errors from unique fields

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-vercel-postgres",
"version": "3.0.0-beta.115",
"version": "3.0.0-beta.116",
"description": "Vercel Postgres adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {
@@ -50,7 +50,7 @@
"@payloadcms/drizzle": "workspace:*",
"@vercel/postgres": "^0.9.0",
"console-table-printer": "2.11.2",
"drizzle-kit": "0.26.1",
"drizzle-kit": "0.26.2",
"drizzle-orm": "0.35.1",
"pg": "8.11.3",
"prompts": "2.4.2",

View File

@@ -90,6 +90,7 @@ export function vercelPostgresAdapter(args: Args = {}): DatabaseAdapterObj<Verce
fieldConstraints: {},
getMigrationTemplate,
idType: postgresIDType,
indexes: new Set<string>(),
initializing,
localesSuffix: args.localesSuffix || '_locales',
logger: args.logger,

View File

@@ -7,7 +7,7 @@ import type {
PostgresSchemaHook,
} from '@payloadcms/drizzle/postgres'
import type { DrizzleAdapter } from '@payloadcms/drizzle/types'
import type { VercelPool, VercelPostgresPoolConfig } from '@vercel/postgres'
import type { VercelClient, VercelPool, VercelPostgresPoolConfig } from '@vercel/postgres'
import type { DrizzleConfig } from 'drizzle-orm'
import type { PgSchema, PgTableFn, PgTransactionConfig } from 'drizzle-orm/pg-core'
@@ -56,6 +56,9 @@ export type Args = {
}
export type VercelPostgresAdapter = {
drizzle: {
$client: VercelClient | VercelPool
} & PostgresDB
pool?: VercelPool
poolOptions?: Args['pool']
} & BasePostgresAdapter
@@ -67,7 +70,9 @@ declare module 'payload' {
afterSchemaInit: PostgresSchemaHook[]
beforeSchemaInit: PostgresSchemaHook[]
beginTransaction: (options?: PgTransactionConfig) => Promise<null | number | string>
drizzle: PostgresDB
drizzle: {
$client: VercelClient | VercelPool
} & PostgresDB
enums: Record<string, GenericEnum>
/**
* An object keyed on each table, with a key value pair where the constraint name is the key, followed by the dot-notation field name

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/drizzle",
"version": "3.0.0-beta.115",
"version": "3.0.0-beta.116",
"description": "A library of shared functions used by different payload database adapters",
"homepage": "https://payloadcms.com",
"repository": {
@@ -46,7 +46,7 @@
},
"dependencies": {
"console-table-printer": "2.11.2",
"drizzle-orm": "0.34.1-1f15bfd",
"drizzle-orm": "0.35.1",
"prompts": "2.4.2",
"to-snake-case": "1.0.0",
"uuid": "9.0.0"

View File

@@ -32,6 +32,7 @@ export { updateGlobal } from './updateGlobal.js'
export { updateGlobalVersion } from './updateGlobalVersion.js'
export { updateVersion } from './updateVersion.js'
export { upsertRow } from './upsertRow/index.js'
export { buildIndexName } from './utilities/buildIndexName.js'
export { executeSchemaHooks } from './utilities/executeSchemaHooks.js'
export { extendDrizzleTable } from './utilities/extendDrizzleTable.js'
export { hasLocalesTable } from './utilities/hasLocalesTable.js'

View File

@@ -54,7 +54,7 @@ export const createDatabase = async function (this: BasePostgresAdapter, args: A
}
// import pg only when createDatabase is used
const pg = await import('pg')
const pg = await import('pg').then((mod) => mod.default)
const managementClient = new pg.Client(managementClientConfig)

View File

@@ -30,6 +30,7 @@ import type {
} from '../types.js'
import { createTableName } from '../../createTableName.js'
import { buildIndexName } from '../../utilities/buildIndexName.js'
import { createIndex } from './createIndex.js'
import { parentIDColumnMap } from './parentIDColumnMap.js'
import { setColumnID } from './setColumnID.js'
@@ -389,21 +390,25 @@ export const buildTable = ({
foreignColumns: [adapter.tables[formattedRelationTo].id],
}).onDelete('cascade')
const indexName = [colName]
const indexColumns = [colName]
const unique = !disableUnique && uniqueRelationships.has(relationTo)
if (unique) {
indexName.push('path')
indexColumns.push('path')
}
if (hasLocalizedRelationshipField) {
indexName.push('locale')
indexColumns.push('locale')
}
relationExtraConfig[`${relationTo}IdIdx`] = createIndex({
name: indexName,
columnName: `${formattedRelationTo}_id`,
tableName: relationshipsTableName,
const indexName = buildIndexName({
name: `${relationshipsTableName}_${formattedRelationTo}_id`,
adapter,
})
relationExtraConfig[indexName] = createIndex({
name: indexColumns,
indexName,
unique,
})
})

View File

@@ -3,13 +3,12 @@ import { index, uniqueIndex } from 'drizzle-orm/pg-core'
import type { GenericColumn } from '../types.js'
type CreateIndexArgs = {
columnName: string
indexName: string
name: string | string[]
tableName: string
unique?: boolean
}
export const createIndex = ({ name, columnName, tableName, unique }: CreateIndexArgs) => {
export const createIndex = ({ name, indexName, unique }: CreateIndexArgs) => {
return (table: { [x: string]: GenericColumn }) => {
let columns
if (Array.isArray(name)) {
@@ -21,8 +20,8 @@ export const createIndex = ({ name, columnName, tableName, unique }: CreateIndex
columns = [table[name]]
}
if (unique) {
return uniqueIndex(`${tableName}_${columnName}_idx`).on(columns[0], ...columns.slice(1))
return uniqueIndex(indexName).on(columns[0], ...columns.slice(1))
}
return index(`${tableName}_${columnName}_idx`).on(columns[0], ...columns.slice(1))
return index(indexName).on(columns[0], ...columns.slice(1))
}
}

View File

@@ -30,6 +30,7 @@ import type {
} from '../types.js'
import { createTableName } from '../../createTableName.js'
import { buildIndexName } from '../../utilities/buildIndexName.js'
import { hasLocalesTable } from '../../utilities/hasLocalesTable.js'
import { validateExistingBlockIsIdentical } from '../../utilities/validateExistingBlockIsIdentical.js'
import { buildTable } from './build.js'
@@ -169,10 +170,12 @@ export const traverseFields = ({
}
adapter.fieldConstraints[rootTableName][`${columnName}_idx`] = constraintValue
}
targetIndexes[`${newTableName}_${field.name}Idx`] = createIndex({
const indexName = buildIndexName({ name: `${newTableName}_${columnName}`, adapter })
targetIndexes[indexName] = createIndex({
name: field.localized ? [fieldName, '_locale'] : fieldName,
columnName,
tableName: newTableName,
indexName,
unique,
})
}

View File

@@ -21,7 +21,7 @@ import type {
} from 'drizzle-orm/pg-core'
import type { PgTableFn } from 'drizzle-orm/pg-core/table'
import type { Payload, PayloadRequest } from 'payload'
import type { ClientConfig, QueryResult } from 'pg'
import type { Client, ClientConfig, Pool, QueryResult } from 'pg'
import type { extendDrizzleTable, Operators } from '../index.js'
import type { BuildQueryJoinAliases, DrizzleAdapter, TransactionPg } from '../types.js'
@@ -134,7 +134,9 @@ export type BasePostgresAdapter = {
defaultDrizzleSnapshot: DrizzleSnapshotJSON
deleteWhere: DeleteWhere
disableCreateDatabase: boolean
drizzle: PostgresDB
drizzle: {
$client: Client | Pool
} & PostgresDB
dropDatabase: DropDatabase
enums: Record<string, GenericEnum>
execute: Execute<unknown>

View File

@@ -212,6 +212,9 @@ export const traverseFields = ({
if (typeof data[field.name] === 'object' && data[field.name] !== null) {
if (field.localized) {
Object.entries(data[field.name]).forEach(([localeKey, localeData]) => {
// preserve array ID if there is
localeData._uuid = data.id || data._uuid
traverseFields({
adapter,
arrays,
@@ -237,6 +240,10 @@ export const traverseFields = ({
})
})
} else {
// preserve array ID if there is
const groupData = data[field.name] as Record<string, unknown>
groupData._uuid = data.id || data._uuid
traverseFields({
adapter,
arrays,
@@ -244,7 +251,7 @@ export const traverseFields = ({
blocks,
blocksToDelete,
columnPrefix: `${columnName}_`,
data: data[field.name] as Record<string, unknown>,
data: groupData,
existingLocales,
fieldPrefix: `${fieldName}_`,
fields: field.fields,
@@ -275,6 +282,9 @@ export const traverseFields = ({
if (typeof data[tab.name] === 'object' && data[tab.name] !== null) {
if (tab.localized) {
Object.entries(data[tab.name]).forEach(([localeKey, localeData]) => {
// preserve array ID if there is
localeData._uuid = data.id || data._uuid
traverseFields({
adapter,
arrays,
@@ -300,6 +310,10 @@ export const traverseFields = ({
})
})
} else {
const tabData = data[tab.name] as Record<string, unknown>
// preserve array ID if there is
tabData._uuid = data.id || data._uuid
traverseFields({
adapter,
arrays,
@@ -307,7 +321,7 @@ export const traverseFields = ({
blocks,
blocksToDelete,
columnPrefix: `${columnPrefix || ''}${toSnakeCase(tab.name)}_`,
data: data[tab.name] as Record<string, unknown>,
data: tabData,
existingLocales,
fieldPrefix: `${fieldPrefix || ''}${tab.name}_`,
fields: tab.fields,

View File

@@ -174,6 +174,7 @@ export interface DrizzleAdapter extends BaseDatabaseAdapter {
fieldConstraints: Record<string, Record<string, string>>
getMigrationTemplate: (args: MigrationTemplateArgs) => string
idType: 'serial' | 'uuid'
indexes: Set<string>
initializing: Promise<void>
insert: Insert
localesSuffix?: string

View File

@@ -0,0 +1,24 @@
import type { DrizzleAdapter } from '../types.js'
export const buildIndexName = ({
name,
adapter,
number = 0,
}: {
adapter: DrizzleAdapter
name: string
number?: number
}): string => {
const indexName = `${name}${number ? `_${number}` : ''}_idx`
if (!adapter.indexes.has(indexName)) {
adapter.indexes.add(indexName)
return indexName
}
return buildIndexName({
name,
adapter,
number: number + 1,
})
}

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/graphql",
"version": "3.0.0-beta.115",
"version": "3.0.0-beta.116",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/next",
"version": "3.0.0-beta.115",
"version": "3.0.0-beta.116",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

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

View File

@@ -18,4 +18,5 @@ export const migrationsCollection: CollectionConfig = {
},
],
graphQL: false,
lockDocuments: false,
}

View File

@@ -29,4 +29,5 @@ export const getLockedDocumentsCollection = (config: Config): CollectionConfig =
required: true,
},
],
lockDocuments: false,
})

View File

@@ -70,6 +70,7 @@ const getPreferencesCollection = (config: Config): CollectionConfig => ({
type: 'json',
},
],
lockDocuments: false,
})
export default getPreferencesCollection

View File

@@ -47,8 +47,12 @@ export const checkDocumentLockStatus = async ({
throw new Error('Either collectionSlug or globalSlug must be provided.')
}
if (!isLockingEnabled) {
return
}
// Only perform lock checks if overrideLock is false and locking is enabled
if (!overrideLock && isLockingEnabled !== false) {
if (!overrideLock) {
const defaultLockErrorMessage = collectionSlug
? `Document with ID ${id} is currently locked by another user and cannot be modified.`
: `Global document with slug "${globalSlug}" is currently locked by another user and cannot be modified.`

View File

@@ -5,6 +5,7 @@ import type { Field, FieldAccess } from '../fields/config/types.js'
import type { SanitizedGlobalConfig } from '../globals/config/types.js'
import type { AllOperations, Document, PayloadRequest, Where } from '../types/index.js'
import { combineQueries } from '../database/combineQueries.js'
import { tabHasName } from '../fields/config/types.js'
type Args = {
@@ -63,17 +64,7 @@ export async function getEntityPolicies<T extends Args>(args: T): Promise<Return
overrideAccess: true,
pagination: false,
req,
where: {
...where,
and: [
...(where.and || []),
{
id: {
equals: id,
},
},
],
},
where: combineQueries(where, { id: { equals: id } }),
})
return paginatedRes?.docs?.[0] || undefined

View File

@@ -1,6 +1,6 @@
import type { Where } from '../../types/index.js'
export const appendVersionToQueryKey = (query: Where): Where => {
export const appendVersionToQueryKey = (query: Where = {}): Where => {
return Object.entries(query).reduce((res, [key, val]) => {
if (['AND', 'and', 'OR', 'or'].includes(key) && Array.isArray(val)) {
return {

View File

@@ -3,6 +3,9 @@ import type { FindOneArgs } from '../database/types.js'
import type { Payload, PayloadRequest } from '../types/index.js'
import type { TypeWithVersion } from './types.js'
import { combineQueries } from '../database/combineQueries.js'
import { appendVersionToQueryKey } from './drafts/appendVersionToQueryKey.js'
type Args = {
config: SanitizedCollectionConfig
id: number | string
@@ -33,7 +36,7 @@ export const getLatestCollectionVersion = async <T extends TypeWithID = any>({
pagination: false,
req,
sort: '-updatedAt',
where: whereQuery,
where: combineQueries(appendVersionToQueryKey(query.where), whereQuery),
})
;[latestVersion] = docs
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-cloud-storage",
"version": "3.0.0-beta.115",
"version": "3.0.0-beta.116",
"description": "The official cloud storage plugin for Payload CMS",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-cloud",
"version": "3.0.0-beta.115",
"version": "3.0.0-beta.116",
"description": "The official Payload Cloud plugin",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-form-builder",
"version": "3.0.0-beta.115",
"version": "3.0.0-beta.116",
"description": "Form builder plugin for Payload CMS",
"keywords": [
"payload",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-nested-docs",
"version": "3.0.0-beta.115",
"version": "3.0.0-beta.116",
"description": "The official Nested Docs plugin for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-redirects",
"version": "3.0.0-beta.115",
"version": "3.0.0-beta.116",
"description": "Redirects plugin for Payload",
"keywords": [
"payload",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-search",
"version": "3.0.0-beta.115",
"version": "3.0.0-beta.116",
"description": "Search plugin for Payload",
"keywords": [
"payload",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-sentry",
"version": "3.0.0-beta.115",
"version": "3.0.0-beta.116",
"description": "Sentry plugin for Payload",
"keywords": [
"payload",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-seo",
"version": "3.0.0-beta.115",
"version": "3.0.0-beta.116",
"description": "SEO plugin for Payload",
"keywords": [
"payload",

View File

@@ -22,7 +22,7 @@ import type { GenerateDescription } from '../../types.js'
import { defaults } from '../../defaults.js'
import { LengthIndicator } from '../../ui/LengthIndicator.js'
const { maxLength, minLength } = defaults.description
const { maxLength: maxLengthDefault, minLength: minLengthDefault } = defaults.description
type MetaDescriptionProps = {
readonly hasGenerateDescriptionFn: boolean
@@ -35,6 +35,8 @@ export const MetaDescriptionComponent: React.FC<MetaDescriptionProps> = (props)
components: { Label },
},
label,
maxLength: maxLengthFromProps,
minLength: minLengthFromProps,
required,
},
hasGenerateDescriptionFn,
@@ -55,6 +57,9 @@ export const MetaDescriptionComponent: React.FC<MetaDescriptionProps> = (props)
const { getData } = useForm()
const docInfo = useDocumentInfo()
const maxLength = maxLengthFromProps || maxLengthDefault
const minLength = minLengthFromProps || minLengthDefault
const field: FieldType<string> = useField({
path: pathFromContext,
} as Options)

View File

@@ -23,7 +23,7 @@ import { defaults } from '../../defaults.js'
import { LengthIndicator } from '../../ui/LengthIndicator.js'
import '../index.scss'
const { maxLength, minLength } = defaults.title
const { maxLength: maxLengthDefault, minLength: minLengthDefault } = defaults.title
type MetaTitleProps = {
readonly hasGenerateTitleFn: boolean
@@ -36,6 +36,8 @@ export const MetaTitleComponent: React.FC<MetaTitleProps> = (props) => {
components: { Label },
},
label,
maxLength: maxLengthFromProps,
minLength: minLengthFromProps,
required,
},
field: fieldFromProps,
@@ -60,6 +62,9 @@ export const MetaTitleComponent: React.FC<MetaTitleProps> = (props) => {
const { getData } = useForm()
const docInfo = useDocumentInfo()
const minLength = minLengthFromProps || minLengthDefault
const maxLength = maxLengthFromProps || maxLengthDefault
const { errorMessage, setValue, showError, value } = field
const regenerateTitle = useCallback(async () => {

View File

@@ -10,25 +10,32 @@ import type { PluginSEOTranslationKeys, PluginSEOTranslations } from '../../tran
import { defaults } from '../../defaults.js'
const {
description: { maxLength: maxDesc, minLength: minDesc },
title: { maxLength: maxTitle, minLength: minTitle },
description: { maxLength: maxDescDefault, minLength: minDescDefault },
title: { maxLength: maxTitleDefault, minLength: minTitleDefault },
} = defaults
type OverviewProps = {
descriptionOverrides?: {
maxLength?: number
minLength?: number
}
descriptionPath?: string
imagePath?: string
titleOverrides?: {
maxLength?: number
minLength?: number
}
titlePath?: string
} & UIField
export const OverviewComponent: React.FC<OverviewProps> = ({
descriptionOverrides,
descriptionPath: descriptionPathFromContext,
imagePath: imagePathFromContext,
titleOverrides,
titlePath: titlePathFromContext,
}) => {
const {
// dispatchFields,
getFields,
} = useForm()
const { getFields } = useForm()
const descriptionPath = descriptionPathFromContext || 'meta.description'
const titlePath = titlePathFromContext || 'meta.title'
@@ -47,6 +54,11 @@ export const OverviewComponent: React.FC<OverviewProps> = ({
const [descIsValid, setDescIsValid] = useState<boolean | undefined>()
const [imageIsValid, setImageIsValid] = useState<boolean | undefined>()
const minDesc = descriptionOverrides?.minLength || minDescDefault
const maxDesc = descriptionOverrides?.maxLength || maxDescDefault
const minTitle = titleOverrides?.minLength || minTitleDefault
const maxTitle = titleOverrides?.maxLength || maxTitleDefault
const resetAll = useCallback(() => {
const fields = getFields()
const fieldsWithoutMeta = fields

View File

@@ -1,6 +1,10 @@
import type { UIField } from 'payload'
interface FieldFunctionProps {
descriptionOverrides?: {
maxLength?: number
minLength?: number
}
/**
* Path to the description field to use for the preview
*
@@ -14,6 +18,10 @@ interface FieldFunctionProps {
*/
imagePath?: string
overrides?: Partial<UIField>
titleOverrides?: {
maxLength?: number
minLength?: number
}
/**
* Path to the title field to use for the preview
*
@@ -25,9 +33,11 @@ interface FieldFunctionProps {
type FieldFunction = ({ overrides }: FieldFunctionProps) => UIField
export const OverviewField: FieldFunction = ({
descriptionOverrides,
descriptionPath,
imagePath,
overrides,
titleOverrides,
titlePath,
}) => {
return {
@@ -37,8 +47,10 @@ export const OverviewField: FieldFunction = ({
components: {
Field: {
clientProps: {
descriptionOverrides,
descriptionPath,
imagePath,
titleOverrides,
titlePath,
},
path: '@payloadcms/plugin-seo/client#OverviewComponent',

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-stripe",
"version": "3.0.0-beta.115",
"version": "3.0.0-beta.116",
"description": "Stripe plugin for Payload",
"keywords": [
"payload",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/richtext-lexical",
"version": "3.0.0-beta.115",
"version": "3.0.0-beta.116",
"description": "The officially supported Lexical richtext adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -17,7 +17,7 @@ import {
} from '@payloadcms/ui'
import { dequal } from 'dequal/lite'
import { $getNodeByKey } from 'lexical'
import React, { useCallback } from 'react'
import React, { useCallback, useEffect } from 'react'
import type { LexicalRichTextFieldProps } from '../../../../types.js'
import type { BlockFields } from '../../server/nodes/BlocksNode.js'
@@ -70,21 +70,15 @@ export const BlockContent: React.FC<Props> = (props) => {
// is important to consider for the data path used in setDocFieldPreferences
const { getDocPreferences, setDocFieldPreferences } = useDocumentInfo()
const [isCollapsed, setIsCollapsed] = React.useState<boolean>(() => {
let initialState = false
const [isCollapsed, setIsCollapsed] = React.useState<boolean>()
useEffect(() => {
void getDocPreferences().then((currentDocPreferences) => {
const currentFieldPreferences = currentDocPreferences?.fields[field.name]
const collapsedArray = currentFieldPreferences?.collapsed
if (collapsedArray && collapsedArray.includes(formData.id)) {
initialState = true
setIsCollapsed(true)
}
setIsCollapsed(collapsedArray ? collapsedArray.includes(formData.id) : false)
})
return initialState
})
}, [field.name, formData.id, getDocPreferences])
const hasSubmitted = useFormSubmitted()
@@ -180,6 +174,10 @@ export const BlockContent: React.FC<Props> = (props) => {
})
}, [editor, nodeKey])
if (typeof isCollapsed !== 'boolean') {
return null
}
return (
<React.Fragment>
<Collapsible

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/richtext-slate",
"version": "3.0.0-beta.115",
"version": "3.0.0-beta.116",
"description": "The officially supported Slate richtext adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/storage-azure",
"version": "3.0.0-beta.115",
"version": "3.0.0-beta.116",
"description": "Payload storage adapter for Azure Blob Storage",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/storage-gcs",
"version": "3.0.0-beta.115",
"version": "3.0.0-beta.116",
"description": "Payload storage adapter for Google Cloud Storage",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/storage-s3",
"version": "3.0.0-beta.115",
"version": "3.0.0-beta.116",
"description": "Payload storage adapter for Amazon S3",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/storage-uploadthing",
"version": "3.0.0-beta.115",
"version": "3.0.0-beta.116",
"description": "Payload storage adapter for uploadthing",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/storage-vercel-blob",
"version": "3.0.0-beta.115",
"version": "3.0.0-beta.116",
"description": "Payload storage adapter for Vercel Blob Storage",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/translations",
"version": "3.0.0-beta.115",
"version": "3.0.0-beta.116",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/ui",
"version": "3.0.0-beta.115",
"version": "3.0.0-beta.116",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -13,6 +13,10 @@
flex-wrap: nowrap;
}
.rs__menu-notice {
padding: base(0.5) base(0.6);
}
.rs__indicators {
gap: calc(var(--base) / 4);
}

26
pnpm-lock.yaml generated
View File

@@ -109,8 +109,8 @@ importers:
specifier: 16.4.5
version: 16.4.5
drizzle-kit:
specifier: 0.26.1
version: 0.26.1
specifier: 0.26.2
version: 0.26.2
drizzle-orm:
specifier: 0.35.1
version: 0.35.1(@libsql/client@0.14.0(bufferutil@4.0.8))(@neondatabase/serverless@0.9.4)(@opentelemetry/api@1.9.0)(@types/pg@8.11.6)(@vercel/postgres@0.9.0)(pg@8.11.3)(react@19.0.0-rc-3edc000d-20240926)(types-react@19.0.0-rc.1)
@@ -315,8 +315,8 @@ importers:
specifier: 2.11.2
version: 2.11.2
drizzle-kit:
specifier: 0.26.1
version: 0.26.1
specifier: 0.26.2
version: 0.26.2
drizzle-orm:
specifier: 0.35.1
version: 0.35.1(@libsql/client@0.14.0(bufferutil@4.0.8)(utf-8-validate@6.0.4))(@neondatabase/serverless@0.9.4)(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(@vercel/postgres@0.9.0)(pg@8.11.3)(react@19.0.0-rc-3edc000d-20240926)(types-react@19.0.0-rc.1)
@@ -361,8 +361,8 @@ importers:
specifier: 2.11.2
version: 2.11.2
drizzle-kit:
specifier: 0.26.1
version: 0.26.1
specifier: 0.26.2
version: 0.26.2
drizzle-orm:
specifier: 0.35.1
version: 0.35.1(@libsql/client@0.14.0(bufferutil@4.0.8)(utf-8-validate@6.0.4))(@neondatabase/serverless@0.9.4)(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(@vercel/postgres@0.9.0)(pg@8.11.3)(react@19.0.0-rc-3edc000d-20240926)(types-react@19.0.0-rc.1)
@@ -401,8 +401,8 @@ importers:
specifier: 2.11.2
version: 2.11.2
drizzle-kit:
specifier: 0.26.1
version: 0.26.1
specifier: 0.26.2
version: 0.26.2
drizzle-orm:
specifier: 0.35.1
version: 0.35.1(@libsql/client@0.14.0(bufferutil@4.0.8)(utf-8-validate@6.0.4))(@neondatabase/serverless@0.9.4)(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(@vercel/postgres@0.9.0)(pg@8.11.3)(react@19.0.0-rc-3edc000d-20240926)(types-react@19.0.0-rc.1)
@@ -1727,8 +1727,8 @@ importers:
specifier: 16.4.5
version: 16.4.5
drizzle-kit:
specifier: 0.26.1
version: 0.26.1
specifier: 0.26.2
version: 0.26.2
eslint-plugin-playwright:
specifier: 1.6.2
version: 1.6.2(eslint-plugin-jest@28.8.1(@typescript-eslint/eslint-plugin@8.3.0(@typescript-eslint/parser@8.3.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.6.2))(eslint@9.9.1(jiti@1.21.6))(typescript@5.6.2))(eslint@9.9.1(jiti@1.21.6))(jest@29.7.0(@types/node@22.5.4)(babel-plugin-macros@3.1.0))(typescript@5.6.2))(eslint@9.9.1(jiti@1.21.6))
@@ -5869,8 +5869,8 @@ packages:
resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==}
engines: {node: '>=12'}
drizzle-kit@0.26.1:
resolution: {integrity: sha512-5/e1tzOPicPDooCm/uJIU9mWK3eD5dhW5EY61TQyVVo29xYxFLmZpXlBdOYlbfDHBsNhVzhb0XjWFmAAj7d7WA==}
drizzle-kit@0.26.2:
resolution: {integrity: sha512-cMq8omEKywjIy5KcqUo6LvEFxkl8/zYHsgYjFVXjmPWWtuW4blcz+YW9+oIhoaALgs2ebRjzXwsJgN9i6P49Dw==}
hasBin: true
drizzle-orm@0.35.1:
@@ -15436,7 +15436,7 @@ snapshots:
dotenv@16.4.5: {}
drizzle-kit@0.26.1:
drizzle-kit@0.26.2:
dependencies:
'@drizzle-team/brocli': 0.10.1
'@esbuild-kit/esm-loader': 2.6.5

View File

@@ -318,6 +318,42 @@ export default buildConfigWithDefaults({
virtual: true,
fields: [],
},
{
type: 'row',
fields: [
{
type: 'text',
name: 'textWithinRow',
virtual: true,
},
],
},
{
type: 'collapsible',
fields: [
{
type: 'text',
name: 'textWithinCollapsible',
virtual: true,
},
],
label: 'Colllapsible',
},
{
type: 'tabs',
tabs: [
{
label: 'tab',
fields: [
{
type: 'text',
name: 'textWithinTabs',
virtual: true,
},
],
},
],
},
],
},
],

View File

@@ -810,6 +810,21 @@ describe('database', () => {
expect(resLocal.textHooked).toBe('hooked')
})
it('should not save a nested field to tabs/row/collapsible with virtual: true to the db', async () => {
const res = await payload.create({
data: {
textWithinCollapsible: '1',
textWithinRow: '2',
textWithinTabs: '3',
},
collection: 'fields-persistance',
})
expect(res.textWithinCollapsible).toBeUndefined()
expect(res.textWithinRow).toBeUndefined()
expect(res.textWithinTabs).toBeUndefined()
})
})
it('should not allow to query by a field with `virtual: true`', async () => {

View File

@@ -60,6 +60,7 @@ export interface UserAuthOperations {
export interface Post {
id: string;
title: string;
hasTransaction?: boolean | null;
throwAfterChange?: boolean | null;
updatedAt: string;
createdAt: string;
@@ -225,6 +226,9 @@ export interface FieldsPersistance {
id?: string | null;
}[]
| null;
textWithinRow?: string | null;
textWithinCollapsible?: string | null;
textWithinTabs?: string | null;
updatedAt: string;
createdAt: string;
}
@@ -289,14 +293,10 @@ export interface PayloadLockedDocument {
value: string | User;
} | null);
globalSlug?: string | null;
_lastEdited: {
user: {
relationTo: 'users';
value: string | User;
};
editedAt?: string | null;
user: {
relationTo: 'users';
value: string | User;
};
isLocked?: boolean | null;
updatedAt: string;
createdAt: string;
}

View File

@@ -120,6 +120,23 @@ const IndexedFields: CollectionConfig = {
],
label: 'Collapsible',
},
{
type: 'text',
name: 'someText',
index: true,
},
{
type: 'array',
name: 'some',
index: true,
fields: [
{
type: 'text',
name: 'text',
index: true,
},
],
},
],
versions: true,
}

View File

@@ -82,6 +82,88 @@ const SelectFields: CollectionConfig = {
},
],
},
{
name: 'array',
type: 'array',
fields: [
{
name: 'selectHasMany',
hasMany: true,
type: 'select',
admin: {
isClearable: true,
isSortable: true,
},
options: [
{
label: 'Value One',
value: 'one',
},
{
label: 'Value Two',
value: 'two',
},
{
label: 'Value Three',
value: 'three',
},
{
label: 'Value Four',
value: 'four',
},
{
label: 'Value Five',
value: 'five',
},
{
label: 'Value Six',
value: 'six',
},
],
},
{
name: 'group',
type: 'group',
fields: [
{
name: 'selectHasMany',
hasMany: true,
type: 'select',
admin: {
isClearable: true,
isSortable: true,
},
options: [
{
label: 'Value One',
value: 'one',
},
{
label: 'Value Two',
value: 'two',
},
{
label: 'Value Three',
value: 'three',
},
{
label: 'Value Four',
value: 'four',
},
{
label: 'Value Five',
value: 'five',
},
{
label: 'Value Six',
value: 'six',
},
],
},
],
},
],
},
{
name: 'selectHasManyLocalized',
type: 'select',

View File

@@ -552,6 +552,54 @@ describe('Fields', () => {
expect(hitResult).toBeDefined()
expect(missResult).toBeFalsy()
})
it('should CRUD within array hasMany', async () => {
const doc = await payload.create({
collection: 'select-fields',
data: { array: [{ selectHasMany: ['one', 'two'] }] },
})
expect(doc.array[0].selectHasMany).toStrictEqual(['one', 'two'])
const upd = await payload.update({
collection: 'select-fields',
id: doc.id,
data: {
array: [
{
id: doc.array[0].id,
selectHasMany: ['six'],
},
],
},
})
expect(upd.array[0].selectHasMany).toStrictEqual(['six'])
})
it('should CRUD within array + group hasMany', async () => {
const doc = await payload.create({
collection: 'select-fields',
data: { array: [{ group: { selectHasMany: ['one', 'two'] } }] },
})
expect(doc.array[0].group.selectHasMany).toStrictEqual(['one', 'two'])
const upd = await payload.update({
collection: 'select-fields',
id: doc.id,
data: {
array: [
{
id: doc.array[0].id,
group: { selectHasMany: ['six'] },
},
],
},
})
expect(upd.array[0].group.selectHasMany).toStrictEqual(['six'])
})
})
describe('number', () => {

View File

@@ -60,6 +60,7 @@ export interface Config {
'uploads-multi': UploadsMulti;
'uploads-poly': UploadsPoly;
'uploads-multi-poly': UploadsMultiPoly;
'uploads-restricted': UploadsRestricted;
'ui-fields': UiField;
'payload-locked-documents': PayloadLockedDocument;
'payload-preferences': PayloadPreference;
@@ -1061,6 +1062,13 @@ export interface IndexedField {
};
collapsibleLocalizedUnique?: string | null;
collapsibleTextUnique?: string | null;
someText?: string | null;
some?:
| {
text?: string | null;
id?: string | null;
}[]
| null;
updatedAt: string;
createdAt: string;
}
@@ -1328,6 +1336,15 @@ export interface SelectField {
select?: ('one' | 'two' | 'three') | null;
selectReadOnly?: ('one' | 'two' | 'three') | null;
selectHasMany?: ('one' | 'two' | 'three' | 'four' | 'five' | 'six')[] | null;
array?:
| {
selectHasMany?: ('one' | 'two' | 'three' | 'four' | 'five' | 'six')[] | null;
group?: {
selectHasMany?: ('one' | 'two' | 'three' | 'four' | 'five' | 'six')[] | null;
};
id?: string | null;
}[]
| null;
selectHasManyLocalized?: ('one' | 'two')[] | null;
selectI18n?: ('one' | 'two' | 'three') | null;
simple?: ('One' | 'Two' | 'Three') | null;
@@ -1619,6 +1636,19 @@ export interface UploadsMultiPoly {
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "uploads-restricted".
*/
export interface UploadsRestricted {
id: string;
text?: string | null;
uploadWithoutRestriction?: (string | null) | Upload;
uploadWithAllowCreateFalse?: (string | null) | Upload;
uploadMultipleWithAllowCreateFalse?: (string | Upload)[] | null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "ui-fields".
@@ -1764,6 +1794,10 @@ export interface PayloadLockedDocument {
relationTo: 'uploads-multi-poly';
value: string | UploadsMultiPoly;
} | null)
| ({
relationTo: 'uploads-restricted';
value: string | UploadsRestricted;
} | null)
| ({
relationTo: 'ui-fields';
value: string | UiField;

View File

@@ -611,4 +611,48 @@ describe('Locked documents', () => {
expect(docsFromLocksCollection.docs).toHaveLength(0)
})
it('should allow take over on locked doc (simulates take over modal from admin ui)', async () => {
const newPost7 = await payload.create({
collection: postsSlug,
data: {
text: 'new post 7',
},
})
const lockedDocInstance = await payload.create({
collection: lockedDocumentCollection,
data: {
editedAt: new Date().toISOString(),
user: {
relationTo: 'users',
value: user2.id,
},
document: {
relationTo: 'posts',
value: newPost7.id,
},
globalSlug: undefined,
},
})
// This is the take over action - changing the user to the current user
await payload.update({
collection: 'payload-locked-documents',
data: {
user: { relationTo: 'users', value: user?.id },
},
id: lockedDocInstance.id,
})
const docsFromLocksCollection = await payload.find({
collection: lockedDocumentCollection,
where: {
'user.value': { equals: user.id },
},
})
expect(docsFromLocksCollection.docs).toHaveLength(1)
expect(docsFromLocksCollection.docs[0].user.value?.id).toEqual(user.id)
})
})

View File

@@ -64,7 +64,7 @@
"comment-json": "^4.2.3",
"create-payload-app": "workspace:*",
"dotenv": "16.4.5",
"drizzle-kit": "0.26.1",
"drizzle-kit": "0.26.2",
"eslint-plugin-playwright": "1.6.2",
"execa": "5.1.1",
"file-type": "19.3.0",