Compare commits

...

52 Commits

Author SHA1 Message Date
Elliot DeNolf
6c2eecc47e chore(release): v3.0.0-beta.119 [skip ci] 2024-10-25 16:11:53 -04:00
Paul
4f88fb046f chore: update docs mention on payload cloud plugin (#8869) 2024-10-25 13:09:35 -06:00
Elliot DeNolf
1b1dc82cfb feat!: rename @payloadcms/plugin-cloud (#8828)
BREAKING CHANGE: Rename `@payloadcms/plugin-cloud` to
`@payloadcms/payload-cloud`. Anyone using the existing plugin will need
to switch to using the new package.

## Why?

Since v3 will be using _fixed versioning_, all versions of `^3` must be
available. Unfortunately, the `@payloadcms/plugin-cloud` version has
already breached that version number. Renaming will allow it to be on
the same version as other monorepo packages.

Additionally, the name `plugin-cloud` is quite ambiguous and sometimes
is confused with `plugin-cloud-storage`, so using `payload-cloud` feels
like a good move to make this more evident.
2024-10-24 21:19:15 -04:00
Paul
2df8f94a75 fix(plugin-search): issues with overriding the search collection slug consistently across hooks (#8847)
Fixes https://github.com/payloadcms/payload/issues/8842
2024-10-25 00:29:39 +00:00
Paul
e72e81c3da fix(ui): upload buttons being hidden at mobile screen widths (#8860) 2024-10-24 22:29:29 +00:00
Elliot DeNolf
b1b571d53e ci: do not run pr-title on synchronize event 2024-10-24 16:48:05 -04:00
Paul
085e127217 fix(ui): leave without saving when changing /account theme (#8724)
Fixes an annoying instance where on the /account page if you change your
theme then navigate away the Leaving without save popup is triggered
even though you don't need to submit a form or trigger a save in order
to change your admin theme.
2024-10-24 16:46:19 -04:00
Anders Semb Hermansen
4d44c378ed feat: sort by multiple fields (#8799)
This change adds support for sort with multiple fields in local API and
REST API. Related discussion #2089

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2024-10-24 15:46:30 -04:00
Sasha
6e919cc83a perf: index globals versions timestamps by default (#8821) 2024-10-24 14:58:59 -04:00
Elliot DeNolf
1a15425b59 fix(cpa): properly detect if pnpm exists on windows (#8855)
Use `where <command>` if using windows when detecting package manager
2024-10-24 14:53:11 -04:00
Sasha
9069bd3fac fix(db-postgres): sort by localized fields (#8839)
### What?
Fixes https://github.com/payloadcms/payload/issues/5152 issue related to
sorting by a localized field with SQLite / Postgres database adapters.

### Why?
It was an incorrect behaviour.


### How?
Modifies the `getTableColumnFromPath` file to have correct join
conditions. Previously if you had this structure in the _locales table
_locale title parent
en          A    1
es          B     1
we sorted by everything that's here, but we need to sort only by the
passed locale.

Additionally fixes a typescript error in `dev.ts` that I added here
https://github.com/payloadcms/payload/pull/8834

Also, removes the condition with `joins.length` in `countDistinct`. It
was there as for this issue
https://github.com/payloadcms/payload/issues/4889 because sorting by a
localized property caused duplication. This can simnifically improve
performance for `.find` with nested querying/sorting on large data sets,
because `count(*)` is faster than `count(DISTINCT id)`
2024-10-24 14:47:58 -04:00
Hristiyan Dodov
2e11068f49 feat(richtext-slate): add useElement hook to richtext-slate client exports (#8856)
I'm extending the Slate editor with a custom component and everything
works great, except I have to import `useElement()` like this:

```tsx
import { useElement } from 'node_modules/.pnpm/@payloadcms+richtext-slate@3.0.0-beta.113_monaco-editor@0.51.0_next@15.0.0-canary.191_@babel+_qmdxs6s5hpzjhuopohgawpvl6i/node_modules/@payloadcms/richtext-slate/dist/field/providers/ElementProvider.js'

export function Element() {
	const { attributes, children } = useElement()
	return (
		<p {...attributes} className="rich-text-preheading">
			{children}
		</p>
	)
}
```

That's because it's not in the `@payloadcms/richtext-slate/client`
module. This PR fixes this and would allow me to do:

```tsx
import { useElement } from '@payloadcms/richtext-slate/client'
```
2024-10-24 11:50:09 -06:00
Elliot DeNolf
749a7d9131 chore(cpa): remove beta from postgres selection 2024-10-24 13:07:38 -04:00
Elliot DeNolf
b482da63c6 chore(release): v3.0.0-beta.118 [skip ci] 2024-10-23 22:07:05 -04:00
MotorcycleEnjoyer
0fcbce3a01 feat(templates): add a copy button to website template code blocks (#8794)
## WHAT
- Adds a copy code button to the Code Blocks in V3 Beta Website
Template.
- Uses the existing button from `@/components/ui/button`
- SVG from this website: https://uxwing.com/copy-icon/


https://github.com/user-attachments/assets/2f6e720a-de37-40b5-a3bf-c748a69502b5

## WHY
- Copy-Code button is convenient for users looking at code blocks in
tutorials/documentation/etc

## ISSUES
- Button color invert isn't immediate on refresh in darkmode


https://github.com/user-attachments/assets/f1093a27-73dd-47cb-8fc9-2f4bc301b80c
2024-10-23 20:47:15 +00:00
Sasha
1caa383608 fix: reduce import map diff when config changes (#8846)
### What?
Reduces difference in the `importMap.js` file when config changes.

### How?
Instead of appending the length, appends the hash of the path.

#### Before: 
Example of the diff when 1 component gets removed
<img width="374" alt="image"
src="https://github.com/user-attachments/assets/7aff02bd-ef55-4e40-963f-1cc3890e5957">
output:
```ts
import { TestComponent as TestComponent_84 } from 'test/admin/components/TestComponent.js'
```

#### After:
Targets only necessary for this component lines:
<img width="359" alt="image"
src="https://github.com/user-attachments/assets/99ba0ebd-cff4-4169-9622-e4c491e23eef">

Output:
```ts
import { TestComponent as TestComponent_d010fadde249c7cd3feed0eef58fe83c } from 'test/admin/components/TestComponent.js'
```

Fixes https://github.com/payloadcms/payload/issues/8841
2024-10-23 23:33:53 +03:00
Jessica Chowdhury
03c07026c5 feat: add localized indicator to field label (#8602)
On any localized field, appends `locale` on the end of the label.
2024-10-23 15:06:30 -04:00
Sasha
9f66114577 chore: jest reporter log failed tests count (#8810)
for example 3 tests passed but 1 failed, shows:
> 1 FAILED

instead of

> 4 FAILED

example of the previous behaviour

![image](https://github.com/user-attachments/assets/78c4bc76-caa4-4bf8-943f-2b6b690ce763)

![image](https://github.com/user-attachments/assets/7f261ac3-17dd-474d-87a3-47ad6cbacd68)
2024-10-22 23:22:20 +03:00
Sasha
52b1d332db chore: fixes dev server doesn't drop the database (#8834)
Apparently, `nextDev` seems to run in a different process and has its
own env variables, we can run the dev server the same way we run it for
E2Es instead via `createServer`.
2024-10-22 23:19:48 +03:00
Sasha
5ea8d2c196 docs: append the beta tag to plugins installation commands (#8831)
Example:
```sh
pnpm add @payloadcms/plugin-sentry
```

to:

```sh
pnpm add @payloadcms/plugin-sentry@beta
```

Because of this, people can be confused with the wrong installed
version. We'll change it back on stable
2024-10-22 19:50:48 +03:00
Sasha
8af00f2deb fix: join field works on collections with versions enabled (#8715)
- Fixes errors with drizzle when building the schema
https://github.com/payloadcms/payload/issues/8680
- Adds `joins` to `db.queryDrafts` to have them when doing `.find` with
`draft: true`
2024-10-22 11:05:55 -04:00
Sasha
4c396c720e fix(db-postgres): alias already in use in this query (#8823)
Fixes https://github.com/payloadcms/payload/issues/8517

Reuses existing joins instead of adding new, duplicate ones which
causes:
```
Error: Alias "" is already used in this query.
```
2024-10-22 10:14:38 -04:00
Elliot DeNolf
69125504af chore(release): v3.0.0-beta.117 [skip ci] 2024-10-22 09:33:50 -04:00
Elliot DeNolf
74266bdd9a feat!: bump next.js to 15.0.0 (#8825)
Bump Next.js to 15.0.0
2024-10-21 23:12:22 -04:00
Elliot DeNolf
9fb86655a9 fix: drizzle init args (#8818)
Adjust drizzle init for changes in drizzle 0.35.0
https://github.com/drizzle-team/drizzle-orm/releases/tag/0.35.0

The pool/connection should now be passed as the `client` arg when
initializing drizzle.

```ts
this.drizzle = drizzle({
  client: this.poolOptions ? new VercelPool(this.poolOptions) : sql,
  logger,
  schema: this.schema,
})
```

This was causing an issue where running `payload migrate` on Vercel was
causing drizzle to attempt to `127.0.0.1:5432` instead of the specified
environment variable in the adapter 🤔
2024-10-21 21:39:37 -04:00
Patrik
2908c9adde fix(next, ui): ensures selectAll in the list view ignores locked documents (#8813)
Fixes #8783
2024-10-21 16:18:34 -04:00
Sasha
fe25b54fff fix(ui): unique ids for nested rows on row duplicate to prevent errors with postgres (#8790)
Fixes https://github.com/payloadcms/payload/issues/8784

This works for any deep level, both arrays and blocks.
2024-10-21 08:51:26 -04:00
Anders Semb Hermansen
ef8a5b1f3e perf: replace jsonwebtoken with jose (#8217)
The jose package has 0 dependencies and is tree shakable ESM.
So we get lower bundle size and get rid of 10 dependencies.
2024-10-18 10:04:37 -06:00
Sasha
197e3bc010 docs: corrects old imports (#8769)
1
`import type { Field } from 'payload/types'`
to
`import type { Field } from 'payload'`
2
`import { buildConfig } from 'payload/config'`
to
`import { buildConfig } from 'payload'`

3
```
import { SelectInput, useField } from 'payload/components/forms';
import { useAuth } from 'payload/components/utilities';
```
to
`import { SelectInput, useAuth, useField } from '@payloadcms/ui'`

4
uses `import type` for `import type { CollectionConfig } from 'payload'`
2024-10-18 10:47:47 +03:00
Because789
36acfee288 docs: fix missing TextField import (#8688)
Fixes a missing import in field prop example in
docs/beta/admin/fields.mdx.

<!--

For external contributors, please include:

- A summary of the pull request and any related issues it fixes.
- Reasoning for the changes made or any additional context that may be
useful.

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

 -->
2024-10-18 10:47:31 +03:00
Alessio Gravili
062a333779 perf: upgrade pino and pino-pretty, reducing the total amount of dependencies (#8776)
BEFORE:

22 dependencies, 2MB total size

![CleanShot 2024-10-18 at 00 11
48@2x](https://github.com/user-attachments/assets/abd20ac1-fc66-4d5b-bbda-bdcf89846a0a)

AFTER:

12 dependencies, 1MB total size

![CleanShot 2024-10-18 at 00 12
44@2x](https://github.com/user-attachments/assets/6f22e2e3-0eed-4b48-8e51-1f5156e9efd3)
2024-10-18 06:58:56 +00:00
Alessio Gravili
fa929120e7 chore: upgrade typescript from 5.6.2 to 5.6.3, upgrade playwright from 1.46.0 to 1.48.1 (#8775) 2024-10-18 06:09:51 +00:00
Alessio Gravili
f3bec93d76 fix(richtext-lexical): richtext fields in drawers aren't editable, inline toolbar artifacts are shown for readOnly editors (#8774)
Fixes this:


https://github.com/user-attachments/assets/cf78082d-9054-4324-90cd-c81219a4f26d
2024-10-18 05:38:48 +00:00
Germán Jabloñski
fa49215078 chore: add internationalization guidelines to CONTRIBUTING.md (#8755) 2024-10-18 03:50:31 +00:00
Alessio Gravili
aedf3c8a76 fix(richtext-*): ensure admin panel doesn't freeze with some field configurations consisting of 2+ richtext fields (#8773)
See comments in code for proper explanation. In some cases, where 2
richtext `editor`s referencing the same `editor` are used, the admin
panel will hang. That's because the server will send their client props
that have the same object reference down to the client twice.

Next.js sometimes does not like this and, ever since one of the v15
canaries, started to hang
2024-10-18 03:22:05 +00: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
Dan Ribbens
7019f22aad chore: bump drizzle-orm 0.35.1 (#8742) 2024-10-16 15:14:20 +00:00
Said Akhrarov
c4fa885e84 fix(ui): restrict file picking via upload config mimetypes (#8710)
Fixes #8673

This PR restricts inputs with `type="file"` to only those mimetypes
specified in collection upload configs. This also works for the input in
`bulkUpload` and drag-and-drop capabilities by omitting dropped files if
they do not conform to the upload config mimetypes. This PR also assumes
that an upload config with an empty mimetype array should accept all
files since the negation of that statement makes an upload collection
redundant.
2024-10-16 09:24:21 -04:00
288 changed files with 3855 additions and 1642 deletions

View File

@@ -5,7 +5,6 @@ on:
types:
- opened
- edited
- synchronize
permissions:
pull-requests: write

View File

@@ -122,3 +122,19 @@ This is how you can preview changes you made locally to the docs:
4. Add a `DOCS_DIR` environment variable to the `.env` file which points to the absolute path of your modified docs folder. For example `DOCS_DIR=/Users/yourname/Documents/GitHub/payload/docs`
5. Run `yarn run fetchDocs:local`. If this was successful, you should see no error messages and the following output: _Docs successfully written to /.../website/src/app/docs.json_. There could be error messages if you have incorrect markdown in your local docs folder. In this case, it will tell you how you can fix it
6. You're done! Now you can start the website locally using `yarn run dev` and preview the docs under [http://localhost:3000/docs/](http://localhost:3000/docs/)
## Internationalization (i18n)
If your PR adds a string to the UI, we need to make sure to translate it into all the languages that Payload supports. To do that:
- Find the appropriate internationalization file for your package. These are typically located in `packages/translations/src/languages`, although some packages (e.g., richtext-lexical) have separate i18n files for each feature.
- Add the string to the English locale "en".
- Translate it to other languages. You can use the `translateNewKeys` script if you have an OpenAI API key in your `.env` (under `OPENAI_KEY`), or you can use ChatGPT or Google translate - whatever is easier for you. For payload core translations (in packages/translations) you can run the `translateNewKeys` script using `cd packages/translations && pnpm translateNewKeys`. For lexical translations, you can run it using `cd packages/richtext-lexical && pnpm translateNewKeys`. External contributors can skip this step and leave it to us.
To display translation strings in the UI, make sure to use the `t` utility of the `useTranslation` hook:
```ts
const { t } = useTranslation()
// ...
t('yourStringKey')
```

View File

@@ -79,7 +79,7 @@ Returns a boolean which allows/denies access to the `create` request.
To add create Access Control to a Collection, use the `create` property in the [Collection Config](../collections/overview):
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const CollectionWithCreateAccess: CollectionConfig = {
// ...

View File

@@ -33,7 +33,7 @@ Access Control is specific to the operation of the request.
To add Access Control to a Field, use the `access` property in the [Field Config](../fields/overview):
```ts
import { CollectionConfig } from 'payload';
import type { CollectionConfig } from 'payload';
export const Posts: CollectionConfig = {
slug: 'posts',

View File

@@ -11,7 +11,7 @@ The behavior of [Collections](../configuration/collections) within the [Admin Pa
To configure Admin Options for Collections, use the `admin` property in your Collection Config:
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
@@ -89,7 +89,7 @@ It is possible to display a Preview Button within the Edit View of the Admin Pan
To configure the Preview Button, set the `admin.preview` property to a function in your [Collection Config](../configuration/collections):
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const Posts: CollectionConfig = {
// ...
@@ -126,7 +126,7 @@ All Collections receive their own List View which displays a paginated list of d
To configure pagination options, use the `admin.pagination` property in your [Collection Config](../configuration/collections):
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const Posts: CollectionConfig = {
// ...
@@ -155,7 +155,7 @@ In the List View, there is a "search" box that allows you to quickly find a docu
To define which fields should be searched, use the `admin.listSearchableFields` property in your [Collection Config](../configuration/collections):
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const Posts: CollectionConfig = {
// ...

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

@@ -217,6 +217,7 @@ Client Component:
'use client'
import React from 'react'
import type { TextFieldClientComponent } from 'payload'
import { TextField } from '@payloadcms/ui'
export const MyTextField: TextFieldClientComponent = ({ field }) => {
return <TextField field={field} />

View File

@@ -29,7 +29,7 @@ The lockDocuments property exists on both the Collection Config and the Global C
Heres an example configuration for document locking:
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const Posts: CollectionConfig = {
slug: 'posts',

View File

@@ -151,7 +151,7 @@ Collection Metadata is the metadata that is applied to all pages within any give
To customize Collection Metadata, use the `admin.meta` key within your Collection Config:
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...

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

@@ -38,7 +38,7 @@ At its core a strategy simply takes information from the incoming request and re
Your `authenticate` method should return an object containing a Payload user document and any optional headers that you'd like Payload to set for you when we return a response.
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const Users: CollectionConfig = {
slug: 'users',

View File

@@ -15,7 +15,7 @@ Email Verification forces users to prove they have access to the email address t
To enable Email Verification, use the `auth.verify` property on your [Collection Config](../configuration/collections):
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const Customers: CollectionConfig = {
// ...
@@ -42,7 +42,7 @@ The following options are available:
Function that accepts one argument, containing `{ req, token, user }`, that allows for overriding the HTML within emails that are sent to users indicating how to validate their account. The function should return a string that supports HTML, which can optionally be a full HTML email.
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const Customers: CollectionConfig = {
// ...
@@ -74,7 +74,7 @@ export const Customers: CollectionConfig = {
Similarly to the above `generateEmailHTML`, you can also customize the subject of the email. The function argument are the same but you can only return a string - not HTML.
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const Customers: CollectionConfig = {
// ...
@@ -95,7 +95,7 @@ export const Customers: CollectionConfig = {
You can customize how the Forgot Password workflow operates with the following options on the `auth.forgotPassword` property:
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const Customers: CollectionConfig = {
// ...
@@ -119,7 +119,7 @@ The following options are available:
This function allows for overriding the HTML within emails that are sent to users attempting to reset their password. The function should return a string that supports HTML, which can be a full HTML email.
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const Customers: CollectionConfig = {
// ...
@@ -179,7 +179,7 @@ The following arguments are passed to the `generateEmailHTML` function:
Similarly to the above `generateEmailHTML`, you can also customize the subject of the email. The function argument are the same but you can only return a string - not HTML.
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const Customers: CollectionConfig = {
// ...

View File

@@ -25,7 +25,7 @@ When Authentication is enabled on a [Collection](../configuration/collections),
To enable Authentication on a Collection, use the `auth` property in the [Collection Config](../configuration/collection#auth):
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const Users: CollectionConfig = {
// ...
@@ -48,7 +48,7 @@ Any [Collection](../configuration/collections) can opt-in to supporting Authenti
To enable Authentication on a Collection, use the `auth` property in the [Collection Config](../configuration/collections):
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const Admins: CollectionConfig = {
// ...

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.
@@ -98,14 +98,14 @@ From there, you are ready to make updates to your project. When you are ready to
Projects generated from a template will come pre-configured with the official Cloud Plugin, but if you are using your own repository you will need to add this into your project. To do so, add the plugin to your Payload Config:
`yarn add @payloadcms/plugin-cloud`
`yarn add @payloadcms/payload-cloud`
```js
import { payloadCloud } from '@payloadcms/plugin-cloud'
import { payloadCloudPlugin } from '@payloadcms/payload-cloud'
import { buildConfig } from 'payload'
export default buildConfig({
plugins: [payloadCloud()],
plugins: [payloadCloudPlugin()],
// rest of config
})
```
@@ -115,6 +115,11 @@ export default buildConfig({
over Payload Cloud's email service.
</Banner>
<Banner type="info">
Good to know: the Payload Cloud Plugin was previously named `@payloadcms/plugin-cloud`. If you are
using this plugin, you should update to the new package name.
</Banner>
#### **Optional configuration**
If you wish to opt-out of any Payload cloud features, the plugin also accepts options to do so.

View File

@@ -37,7 +37,7 @@ It's often best practice to write your Collections in separate files and then im
Here is what a simple Collection Config might look like:
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const Posts: CollectionConfig = {
slug: 'posts',
@@ -57,26 +57,26 @@ export const Posts: CollectionConfig = {
The following options are available:
| Option | Description |
|------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`admin`** | The configuration options for the Admin Panel. [More details](../admin/collections). |
| **`access`** | Provide Access Control functions to define exactly who should be able to do what with Documents in this Collection. [More details](../access-control/collections). |
| **`auth`** | Specify options if you would like this Collection to feature authentication. [More details](../authentication/overview). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`disableDuplicate`** | When true, do not show the "Duplicate" button while editing documents within this Collection and prevent `duplicate` from all APIs. |
| **`defaultSort`** | Pass a top-level field to sort by default in the Collection List View. Prefix the name of the field with a minus symbol ("-") to sort in descending order. |
| **`dbName`** | Custom table or Collection name depending on the Database Adapter. Auto-generated from slug if not defined. |
| **`endpoints`** | Add custom routes to the REST API. Set to `false` to disable routes. [More details](../rest-api/overview#custom-endpoints). |
| **`fields`** \* | Array of field types that will determine the structure and functionality of the data stored within this Collection. [More details](../fields/overview). |
| **`graphQL`** | An object with `singularName` and `pluralName` strings used in schema generation. Auto-generated from slug if not defined. Set to `false` to disable GraphQL. |
| **`hooks`** | Entry point for Hooks. [More details](../hooks/overview#collection-hooks). |
| **`labels`** | Singular and plural labels for use in identifying this Collection throughout Payload. Auto-generated from slug if not defined. |
| **`lockDocuments`** | Enables or disables document locking. By default, document locking is enabled. Set to an object to configure, or set to `false` to disable locking. [More details](../admin/locked-documents). |
| **`slug`** \* | Unique, URL-friendly string that will act as an identifier for this Collection. |
| **`timestamps`** | Set to false to disable documents' automatically generated `createdAt` and `updatedAt` timestamps. |
| **`typescript`** | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
| **`upload`** | Specify options if you would like this Collection to support file uploads. For more, consult the [Uploads](../upload/overview) documentation. |
| **`versions`** | Set to true to enable default options, or configure with object properties. [More details](../versions/overview#collection-config). |
| Option | Description |
|------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`admin`** | The configuration options for the Admin Panel. [More details](../admin/collections). |
| **`access`** | Provide Access Control functions to define exactly who should be able to do what with Documents in this Collection. [More details](../access-control/collections). |
| **`auth`** | Specify options if you would like this Collection to feature authentication. [More details](../authentication/overview). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`disableDuplicate`** | When true, do not show the "Duplicate" button while editing documents within this Collection and prevent `duplicate` from all APIs. |
| **`defaultSort`** | Pass a top-level field to sort by default in the Collection List View. Prefix the name of the field with a minus symbol ("-") to sort in descending order. Multiple fields can be specified by using a string array. |
| **`dbName`** | Custom table or Collection name depending on the Database Adapter. Auto-generated from slug if not defined. |
| **`endpoints`** | Add custom routes to the REST API. Set to `false` to disable routes. [More details](../rest-api/overview#custom-endpoints). |
| **`fields`** \* | Array of field types that will determine the structure and functionality of the data stored within this Collection. [More details](../fields/overview). |
| **`graphQL`** | An object with `singularName` and `pluralName` strings used in schema generation. Auto-generated from slug if not defined. Set to `false` to disable GraphQL. |
| **`hooks`** | Entry point for Hooks. [More details](../hooks/overview#collection-hooks). |
| **`labels`** | Singular and plural labels for use in identifying this Collection throughout Payload. Auto-generated from slug if not defined. |
| **`lockDocuments`** | Enables or disables document locking. By default, document locking is enabled. Set to an object to configure, or set to `false` to disable locking. [More details](../admin/locked-documents). |
| **`slug`** \* | Unique, URL-friendly string that will act as an identifier for this Collection. |
| **`timestamps`** | Set to false to disable documents' automatically generated `createdAt` and `updatedAt` timestamps. |
| **`typescript`** | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
| **`upload`** | Specify options if you would like this Collection to support file uploads. For more, consult the [Uploads](../upload/overview) documentation. |
| **`versions`** | Set to true to enable default options, or configure with object properties. [More details](../versions/overview#collection-config). |
_\* An asterisk denotes that a property is required._

View File

@@ -117,7 +117,7 @@ While Payload's built-in features come fully translated, you may also want to tr
To do this, provide the translations wherever applicable, keyed to the language code:
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const Articles: CollectionConfig = {
slug: 'articles',

View File

@@ -216,7 +216,7 @@ Cross-origin resource sharing (CORS) can be configured with either a whitelist a
Here's an example showing how to allow incoming requests from any domain:
```ts
import { buildConfig } from 'payload/config'
import { buildConfig } from 'payload'
export default buildConfig({
// ...
@@ -227,7 +227,7 @@ export default buildConfig({
Here's an example showing how to append a new header (`x-custom-header`) in `Access-Control-Allow-Headers`:
```ts
import { buildConfig } from 'payload/config'
import { buildConfig } from 'payload'
export default buildConfig({
// ...

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

@@ -24,7 +24,7 @@ Arrays are useful for many different types of content from simple to complex, su
To create an Array Field, set the `type` to `array` in your [Field Config](./overview):
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyArrayField: Field = {
// ...
@@ -69,7 +69,7 @@ _\* An asterisk denotes that a property is required._
To customize the appearance and behavior of the Array Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyArrayField: Field = {
// ...
@@ -92,7 +92,7 @@ The Array Field inherits all of the default options from the base [Field Admin C
In this example, we have an Array Field called `slider` that contains a set of fields for a simple image slider. Each row in the array has a `title`, `image`, and `caption`. We also customize the row label to display the title if it exists, or a default label if it doesn't.
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',

View File

@@ -24,7 +24,7 @@ Blocks are a great way to create a flexible content model that can be used to bu
To add a Blocks Field, set the `type` to `blocks` in your [Field Config](./overview):
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyBlocksField: Field = {
// ...
@@ -67,7 +67,7 @@ _\* An asterisk denotes that a property is required._
The customize the appearance and behavior of the Blocks Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyBlocksField: Field = {
// ...

View File

@@ -18,7 +18,7 @@ The Checkbox Field saves a boolean in the database.
To add a Checkbox Field, set the `type` to `checkbox` in your [Field Config](./overview):
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyCheckboxField: Field = {
// ...
@@ -53,7 +53,7 @@ _\* An asterisk denotes that a property is required._
Here is an example of a Checkbox Field in a Collection:
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',

View File

@@ -19,7 +19,7 @@ The Code Field saves a string in the database, but provides the [Admin Panel](..
To add a Code Field, set the `type` to `code` in your [Field Config](./overview):
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyBlocksField: Field = {
// ...
@@ -57,7 +57,7 @@ _\* An asterisk denotes that a property is required._
The customize the appearance and behavior of the Code Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyCodeField: Field = {
// ...
@@ -79,7 +79,7 @@ The Code Field inherits all of the default options from the base [Field Admin Co
`collections/ExampleCollection.ts
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',

View File

@@ -18,7 +18,7 @@ The Collapsible Field is presentational-only and only affects the Admin Panel. B
To add a Collapsible Field, set the `type` to `collapsible` in your [Field Config](./overview):
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyCollapsibleField: Field = {
// ...
@@ -47,7 +47,7 @@ _\* An asterisk denotes that a property is required._
The customize the appearance and behavior of the Collapsible Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyCollapsibleField: Field = {
// ...
@@ -68,7 +68,7 @@ The Collapsible Field inherits all of the default options from the base [Field A
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',

View File

@@ -18,7 +18,7 @@ The Date Field saves a Date in the database and provides the [Admin Panel](../ad
To add a Date Field, set the `type` to `date` in your [Field Config](./overview):
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyDateField: Field = {
// ...
@@ -53,7 +53,7 @@ _\* An asterisk denotes that a property is required._
The customize the appearance and behavior of the Date Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyDateField: Field = {
// ...
@@ -97,7 +97,7 @@ When only `pickerAppearance` is set, an equivalent format will be rendered in th
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',

View File

@@ -18,7 +18,7 @@ The Email Field enforces that the value provided is a valid email address.
To create an Email Field, set the `type` to `email` in your [Field Config](./overview):
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyEmailField: Field = {
// ...
@@ -54,7 +54,7 @@ _\* An asterisk denotes that a property is required._
The customize the appearance and behavior of the Email Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyEmailField: Field = {
// ...
@@ -76,7 +76,7 @@ The Email Field inherits all of the default options from the base [Field Admin C
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',

View File

@@ -18,7 +18,7 @@ The Group Field allows [Fields](./overview) to be nested under a common property
To add a Group Field, set the `type` to `group` in your [Field Config](./overview):
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyGroupField: Field = {
// ...
@@ -58,7 +58,7 @@ _\* An asterisk denotes that a property is required._
The customize the appearance and behavior of the Group Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyGroupField: Field = {
// ...
@@ -79,7 +79,7 @@ The Group Field inherits all of the default options from the base [Field Admin C
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',

View File

@@ -30,7 +30,7 @@ collection you are joining. This will reference the collection and path of the f
To add a Relationship Field, set the `type` to `join` in your [Field Config](./overview):
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyJoinField: Field = {
// highlight-start

View File

@@ -19,7 +19,7 @@ The JSON Field saves actual JSON in the database, which differs from the Code fi
To add a JSON Field, set the `type` to `json` in your [Field Config](./overview):
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyJSONField: Field = {
// ...
@@ -56,7 +56,7 @@ _\* An asterisk denotes that a property is required._
The customize the appearance and behavior of the JSON Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyJSONField: Field = {
// ...
@@ -77,7 +77,7 @@ The JSON Field inherits all of the default options from the base [Field Admin Co
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
@@ -102,7 +102,7 @@ If you only provide a URL to a schema, Payload will fetch the desired schema if
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
@@ -135,7 +135,7 @@ export const ExampleCollection: CollectionConfig = {
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',

View File

@@ -18,7 +18,7 @@ The Number Field stores and validates numeric entry and supports additional nume
To add a Number Field, set the `type` to `number` in your [Field Config](./overview):
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyNumberField: Field = {
// ...
@@ -59,7 +59,7 @@ _\* An asterisk denotes that a property is required._
The customize the appearance and behavior of the Number Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyNumberField: Field = {
// ...
@@ -82,7 +82,7 @@ The Number Field inherits all of the default options from the base [Field Admin
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',

View File

@@ -18,7 +18,7 @@ The Point Field saves a pair of coordinates in the database and assigns an index
To add a Point Field, set the `type` to `point` in your [Field Config](./overview):
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyPointField: Field = {
// ...
@@ -59,7 +59,7 @@ _\* An asterisk denotes that a property is required._
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',

View File

@@ -18,7 +18,7 @@ The Radio Field allows for the selection of one value from a predefined set of p
To add a Radio Field, set the `type` to `radio` in your [Field Config](./overview):
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyRadioField: Field = {
// ...
@@ -69,7 +69,7 @@ _\* An asterisk denotes that a property is required._
The customize the appearance and behavior of the Radio Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyRadioField: Field = {
// ...
@@ -90,7 +90,7 @@ The Radio Field inherits all of the default options from the base [Field Admin C
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',

View File

@@ -24,7 +24,7 @@ The Relationship field is used in a variety of ways, including:
To add a Relationship Field, set the `type` to `relationship` in your [Field Config](./overview):
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyRelationshipField: Field = {
// ...
@@ -74,7 +74,7 @@ _\* An asterisk denotes that a property is required._
The customize the appearance and behavior of the Relationship Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyRelationshipField: Field = {
// ...
@@ -159,7 +159,7 @@ called with an argument object with the following properties:
## Example
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',

View File

@@ -63,7 +63,7 @@ _\* An asterisk denotes that a property is required._
The customize the appearance and behavior of the Rich Text Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyRichTextField: Field = {
// ...

View File

@@ -18,7 +18,7 @@ The Row Field is presentational-only and only affects the [Admin Panel](../admin
To add a Row Field, set the `type` to `row` in your [Field Config](./overview):
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyRowField: Field = {
// ...
@@ -46,7 +46,7 @@ _\* An asterisk denotes that a property is required._
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',

View File

@@ -18,7 +18,7 @@ The Select Field provides a dropdown-style interface for choosing options from a
To add a Select Field, set the `type` to `select` in your [Field Config](./overview):
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MySelectField: Field = {
// ...
@@ -71,7 +71,7 @@ _\* An asterisk denotes that a property is required._
The customize the appearance and behavior of the Select Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MySelectField: Field = {
// ...
@@ -93,7 +93,7 @@ The Select Field inherits all of the default options from the base [Field Admin
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
@@ -156,8 +156,7 @@ You can import the existing Select component directly from Payload, then extend
```ts
import * as React from 'react';
import { SelectInput, useField } from 'payload/components/forms';
import { useAuth } from 'payload/components/utilities';
import { SelectInput, useAuth, useField } from '@payloadcms/ui';
type CustomSelectProps = {
path: string;

View File

@@ -18,7 +18,7 @@ The Tabs Field is presentational-only and only affects the [Admin Panel](../admi
To add a Tabs Field, set the `type` to `tabs` in your [Field Config](./overview):
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyTabsField: Field = {
// ...
@@ -59,7 +59,7 @@ _\* An asterisk denotes that a property is required._
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',

View File

@@ -18,7 +18,7 @@ The Text Field is one of the most commonly used fields. It saves a string to the
To add a Text Field, set the `type` to `text` in your [Field Config](./overview):
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyTextField: Field = {
// ...
@@ -59,7 +59,7 @@ _\* An asterisk denotes that a property is required._
The customize the appearance and behavior of the Text Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyTextField: Field = {
// ...
@@ -82,7 +82,7 @@ The Text Field inherits all of the default options from the base [Field Admin Co
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',

View File

@@ -18,7 +18,7 @@ The Textarea Field is nearly identical to the [Text Field](./text) but it featur
To add a Textarea Field, set the `type` to `textarea` in your [Field Config](./overview):
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyTextareaField: Field = {
// ...
@@ -56,7 +56,7 @@ _\* An asterisk denotes that a property is required._
The customize the appearance and behavior of the Textarea Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyTextareaField: Field = {
// ...
@@ -79,7 +79,7 @@ The Textarea Field inherits all of the default options from the base [Field Admi
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',

View File

@@ -18,7 +18,7 @@ With the UI Field, you can:
To add a UI Field, set the `type` to `ui` in your [Field Config](./overview):
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyUIField: Field = {
// ...
@@ -44,7 +44,7 @@ _\* An asterisk denotes that a property is required._
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',

View File

@@ -25,7 +25,7 @@ caption="Admin Panel screenshot of an Upload field"
To create an Upload Field, set the `type` to `upload` in your [Field Config](./overview):
```ts
import type { Field } from 'payload/types'
import type { Field } from 'payload'
export const MyUploadField: Field = {
// ...
@@ -73,7 +73,7 @@ _\* An asterisk denotes that a property is required._
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',

View File

@@ -13,7 +13,7 @@ In Payload the schema is controlled by your collections and globals. All you nee
Install `@payloadcms/graphql` as a dev dependency:
```bash
pnpm add @payloadcms/graphql --save-dev
pnpm add @payloadcms/graphql@beta -D
```
Run the following command to generate the schema:

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

@@ -36,7 +36,7 @@ Forms can be as simple or complex as you need, from a basic contact form, to a m
Install the plugin using any JavaScript package manager like [Yarn](https://yarnpkg.com), [NPM](https://npmjs.com), or [PNPM](https://pnpm.io):
```bash
pnpm add @payloadcms/plugin-form-builder
pnpm add @payloadcms/plugin-form-builder@beta
```
## Basic Usage
@@ -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

@@ -48,7 +48,7 @@ Install the plugin using any JavaScript package manager like [Yarn](https://yarn
or [PNPM](https://pnpm.io):
```bash
pnpm add @payloadcms/plugin-nested-docs
pnpm add @payloadcms/plugin-nested-docs@beta
```
## Basic Usage
@@ -177,7 +177,7 @@ You can also extend the built-in `parent` and `breadcrumbs` fields per collectio
and `createBreadcrumbField` methods. They will merge your customizations overtop the plugin's base field configurations.
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
import { createParentField } from '@payloadcms/plugin-nested-docs/fields'
import { createBreadcrumbsField } from '@payloadcms/plugin-nested-docs/fields'

View File

@@ -32,7 +32,7 @@ For example, if you have a page at `/about` and you want to change it to `/about
Install the plugin using any JavaScript package manager like [Yarn](https://yarnpkg.com), [NPM](https://npmjs.com), or [PNPM](https://pnpm.io):
```bash
pnpm add @payloadcms/plugin-redirects
pnpm add @payloadcms/plugin-redirects@beta
```
## Basic Usage

View File

@@ -39,7 +39,7 @@ This plugin is a great way to implement a fast, immersive search experience such
Install the plugin using any JavaScript package manager like [Yarn](https://yarnpkg.com), [NPM](https://npmjs.com), or [PNPM](https://pnpm.io):
```bash
pnpm add @payloadcms/plugin-search
pnpm add @payloadcms/plugin-search@beta
```
## Basic Usage

View File

@@ -39,7 +39,7 @@ This multi-faceted software offers a range of features that will help you manage
Install the plugin using any JavaScript package manager like [Yarn](https://yarnpkg.com), [NPM](https://npmjs.com), or [PNPM](https://pnpm.io):
```bash
pnpm add @payloadcms/plugin-sentry
pnpm add @payloadcms/plugin-sentry@beta
```
## Sentry for Next.js setup

View File

@@ -37,7 +37,7 @@ To help you visualize what your page might look like in a search engine, a previ
Install the plugin using any JavaScript package manager like [Yarn](https://yarnpkg.com), [NPM](https://npmjs.com), or [PNPM](https://pnpm.io):
```bash
pnpm add @payloadcms/plugin-seo
pnpm add @payloadcms/plugin-seo@beta
```
## Basic Usage
@@ -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

@@ -39,7 +39,7 @@ The beauty of this plugin is the entirety of your application's content and busi
Install the plugin using any JavaScript package manager like [Yarn](https://yarnpkg.com), [NPM](https://npmjs.com), or [PNPM](https://pnpm.io):
```bash
pnpm add @payloadcms/plugin-stripe
pnpm add @payloadcms/plugin-stripe@beta
```
## Basic Usage

View File

@@ -6,7 +6,7 @@ desc: Payload sort allows you to order your documents by a field in ascending or
keywords: query, documents, pagination, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
Documents in Payload can be easily sorted by a specific [Field](../fields/overview). When querying Documents, you can pass the name of any top-level field, and the response will sort the Documents by that field in _ascending_ order. If prefixed with a minus symbol ("-"), they will be sorted in _descending_ order.
Documents in Payload can be easily sorted by a specific [Field](../fields/overview). When querying Documents, you can pass the name of any top-level field, and the response will sort the Documents by that field in _ascending_ order. If prefixed with a minus symbol ("-"), they will be sorted in _descending_ order. In Local API multiple fields can be specificed by using an array of strings. In REST API multiple fields can be specified by separating fields with comma. The minus symbol can be in front of individual fields.
Because sorting is handled by the database, the field cannot be a [Virtual Field](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges). It must be stored in the database to be searchable.
@@ -30,6 +30,19 @@ const getPosts = async () => {
}
```
To sort by multiple fields, you can use the `sort` option with fields in an array:
```ts
const getPosts = async () => {
const posts = await payload.find({
collection: 'posts',
sort: ['priority', '-createdAt'], // highlight-line
})
return posts
}
```
## REST API
To sort in the [REST API](../rest-api/overview), you can use the `sort` parameter in your query:
@@ -40,6 +53,14 @@ fetch('https://localhost:3000/api/posts?sort=-createdAt') // highlight-line
.then((data) => console.log(data))
```
To sort by multiple fields, you can use the `sort` parameter with fields separated by comma:
```ts
fetch('https://localhost:3000/api/posts?sort=priority,-createdAt') // highlight-line
.then((response) => response.json())
.then((data) => console.log(data))
```
## GraphQL API
To sort in the [GraphQL API](../graphql/overview), you can use the `sort` parameter in your query:

View File

@@ -591,7 +591,7 @@ Each endpoint object needs to have:
Example:
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
// a collection of 'orders' with an additional route for tracking details, reachable at /api/orders/:id/tracking
export const Orders: CollectionConfig = {

View File

@@ -34,7 +34,7 @@ export default buildConfig({
And here's an example for how to install the Slate editor on a field-by-field basis, while customizing its options:
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
import { slateEditor } from '@payloadcms/richtext-slate'
export const Pages: CollectionConfig = {

View File

@@ -43,7 +43,7 @@ Every Payload Collection can opt-in to supporting Uploads by specifying the `upl
</Banner>
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const Media: CollectionConfig = {
slug: 'media',
@@ -217,7 +217,7 @@ You can specify how Payload retrieves admin thumbnails for your upload-enabled C
1. `adminThumbnail` as a **string**, equal to one of your provided image size names.
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const Media: CollectionConfig = {
slug: 'media',
@@ -246,7 +246,7 @@ export const Media: CollectionConfig = {
2. `adminThumbnail` as a **function** that takes the document's data and sends back a full URL to load the thumbnail.
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const Media: CollectionConfig = {
slug: 'media',
@@ -267,7 +267,7 @@ Some example values are: `image/*`, `audio/*`, `video/*`, `image/png`, `applicat
**Example mimeTypes usage:**
```ts
import { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const Media: CollectionConfig = {
slug: 'media',

View File

@@ -22,7 +22,7 @@ Payload offers additional storage adapters to handle file uploads. These adapter
### Installation
```sh
pnpm add @payloadcms/storage-vercel-blob
pnpm add @payloadcms/storage-vercel-blob@beta
```
### Usage
@@ -71,7 +71,7 @@ export default buildConfig({
### Installation
```sh
pnpm add @payloadcms/storage-s3
pnpm add @payloadcms/storage-s3@beta
```
### Usage
@@ -119,7 +119,7 @@ See the the [AWS SDK Package](https://github.com/aws/aws-sdk-js-v3) and [`S3Clie
### Installation
```sh
pnpm add @payloadcms/storage-azure
pnpm add @payloadcms/storage-azure@beta
```
### Usage
@@ -168,7 +168,7 @@ export default buildConfig({
### Installation
```sh
pnpm add @payloadcms/storage-gcs
pnpm add @payloadcms/storage-gcs@beta
```
### Usage
@@ -218,7 +218,7 @@ export default buildConfig({
### Installation
```sh
pnpm add @payloadcms/storage-uploadthing
pnpm add @payloadcms/storage-uploadthing@beta
```
### Usage
@@ -261,7 +261,7 @@ If you need to create a custom storage adapter, you can use the [`@payloadcms/pl
### Installation
`pnpm add @payloadcms/plugin-cloud-storage`
`pnpm add @payloadcms/plugin-cloud-storage@beta`
### Usage

View File

@@ -1,6 +1,6 @@
{
"name": "payload-monorepo",
"version": "3.0.0-beta.114",
"version": "3.0.0-beta.119",
"private": true,
"type": "module",
"scripts": {
@@ -29,7 +29,7 @@
"build:live-preview-vue": "turbo build --filter \"@payloadcms/live-preview-vue\"",
"build:next": "turbo build --filter \"@payloadcms/next\"",
"build:payload": "turbo build --filter payload",
"build:plugin-cloud": "turbo build --filter \"@payloadcms/plugin-cloud\"",
"build:payload-cloud": "turbo build --filter \"@payloadcms/payload-cloud\"",
"build:plugin-cloud-storage": "turbo build --filter \"@payloadcms/plugin-cloud-storage\"",
"build:plugin-form-builder": "turbo build --filter \"@payloadcms/plugin-form-builder\"",
"build:plugin-nested-docs": "turbo build --filter \"@payloadcms/plugin-nested-docs\"",
@@ -105,12 +105,12 @@
"devDependencies": {
"@jest/globals": "29.7.0",
"@libsql/client": "0.14.0",
"@next/bundle-analyzer": "15.0.0-canary.173",
"@next/bundle-analyzer": "15.0.0",
"@payloadcms/db-postgres": "workspace:*",
"@payloadcms/eslint-config": "workspace:*",
"@payloadcms/eslint-plugin": "workspace:*",
"@payloadcms/live-preview-react": "workspace:*",
"@playwright/test": "1.46.0",
"@playwright/test": "1.48.1",
"@sentry/nextjs": "^8.33.1",
"@sentry/node": "^8.33.1",
"@swc-node/register": "1.10.9",
@@ -132,8 +132,8 @@
"create-payload-app": "workspace:*",
"cross-env": "7.0.3",
"dotenv": "16.4.5",
"drizzle-kit": "0.26.1",
"drizzle-orm": "0.35.0",
"drizzle-kit": "0.26.2",
"drizzle-orm": "0.35.1",
"escape-html": "^1.0.3",
"execa": "5.1.1",
"form-data": "3.0.1",
@@ -145,15 +145,15 @@
"lint-staged": "15.2.7",
"minimist": "1.2.8",
"mongodb-memory-server": "^9.0",
"next": "15.0.0-canary.173",
"next": "15.0.0",
"open": "^10.1.0",
"p-limit": "^5.0.0",
"playwright": "1.46.0",
"playwright-core": "1.46.0",
"playwright": "1.48.1",
"playwright-core": "1.48.1",
"prettier": "3.3.3",
"prompts": "2.4.2",
"react": "19.0.0-rc-3edc000d-20240926",
"react-dom": "19.0.0-rc-3edc000d-20240926",
"react": "19.0.0-rc-65a56d0e-20241020",
"react-dom": "19.0.0-rc-65a56d0e-20241020",
"rimraf": "3.0.2",
"semver": "^7.5.4",
"sharp": "0.32.6",
@@ -164,11 +164,11 @@
"tempy": "1.0.1",
"tsx": "4.19.1",
"turbo": "^2.1.3",
"typescript": "5.6.2"
"typescript": "5.6.3"
},
"peerDependencies": {
"react": "^19.0.0 || ^19.0.0-rc-3edc000d-20240926",
"react-dom": "^19.0.0 || ^19.0.0-rc-3edc000d-20240926"
"react": "^19.0.0 || ^19.0.0-rc-65a56d0e-20241020",
"react-dom": "^19.0.0 || ^19.0.0-rc-65a56d0e-20241020"
},
"packageManager": "pnpm@9.7.1",
"engines": {

View File

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

View File

@@ -54,7 +54,7 @@ function getEnvironmentPackageManager(): PackageManager {
async function commandExists(command: string): Promise<boolean> {
try {
await execa.command(`command -v ${command}`)
await execa.command(process.platform === 'win32' ? `where ${command}` : `command -v ${command}`)
return true
} catch {
return false

View File

@@ -226,7 +226,7 @@ async function installDeps(projectDir: string, packageManager: PackageManager, d
'payload',
'@payloadcms/next',
'@payloadcms/richtext-lexical',
'@payloadcms/plugin-cloud',
'@payloadcms/payload-cloud',
].map((pkg) => `${pkg}@beta`)
packagesToInstall.push(`@payloadcms/db-${dbType}@beta`)

View File

@@ -82,8 +82,8 @@ const vercelBlobStorageReplacement: StorageAdapterReplacement = {
const payloadCloudReplacement: StorageAdapterReplacement = {
configReplacement: [' payloadCloudPlugin(),'],
importReplacement: "import { payloadCloudPlugin } from '@payloadcms/plugin-cloud'",
packageName: '@payloadcms/plugin-cloud',
importReplacement: "import { payloadCloudPlugin } from '@payloadcms/payload-cloud'",
packageName: '@payloadcms/payload-cloud',
}
// Removes placeholders

View File

@@ -18,7 +18,7 @@ const dbChoiceRecord: Record<DbType, DbChoice> = {
},
postgres: {
dbConnectionPrefix: 'postgres://postgres:<password>@127.0.0.1:5432/',
title: 'PostgreSQL (beta)',
title: 'PostgreSQL',
value: 'postgres',
},
sqlite: {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-mongodb",
"version": "3.0.0-beta.114",
"version": "3.0.0-beta.119",
"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,5 +1,5 @@
import type { PaginateOptions } from 'mongoose'
import type { Field, SanitizedConfig } from 'payload'
import type { Field, SanitizedConfig, Sort } from 'payload'
import { getLocalizedSortProperty } from './getLocalizedSortProperty.js'
@@ -7,7 +7,7 @@ type Args = {
config: SanitizedConfig
fields: Field[]
locale: string
sort: string
sort: Sort
timestamps: boolean
}
@@ -25,32 +25,41 @@ export const buildSortParam = ({
sort,
timestamps,
}: Args): PaginateOptions['sort'] => {
let sortProperty: string
let sortDirection: SortDirection = 'desc'
if (!sort) {
if (timestamps) {
sortProperty = 'createdAt'
sort = '-createdAt'
} else {
sortProperty = '_id'
sort = '-id'
}
} else if (sort.indexOf('-') === 0) {
sortProperty = sort.substring(1)
} else {
sortProperty = sort
sortDirection = 'asc'
}
if (sortProperty === 'id') {
sortProperty = '_id'
} else {
sortProperty = getLocalizedSortProperty({
if (typeof sort === 'string') {
sort = [sort]
}
const sorting = sort.reduce<PaginateOptions['sort']>((acc, item) => {
let sortProperty: string
let sortDirection: SortDirection
if (item.indexOf('-') === 0) {
sortProperty = item.substring(1)
sortDirection = 'desc'
} else {
sortProperty = item
sortDirection = 'asc'
}
if (sortProperty === 'id') {
acc['_id'] = sortDirection
return acc
}
const localizedProperty = getLocalizedSortProperty({
config,
fields,
locale,
segments: sortProperty.split('.'),
})
}
acc[localizedProperty] = sortDirection
return acc
}, {})
return { [sortProperty]: sortDirection }
return sorting
}

View File

@@ -6,12 +6,23 @@ import { combineQueries, flattenWhereToOperators } from 'payload'
import type { MongooseAdapter } from './index.js'
import { buildSortParam } from './queries/buildSortParam.js'
import { buildJoinAggregation } from './utilities/buildJoinAggregation.js'
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
import { withSession } from './withSession.js'
export const queryDrafts: QueryDrafts = async function queryDrafts(
this: MongooseAdapter,
{ collection, limit, locale, page, pagination, req = {} as PayloadRequest, sort: sortArg, where },
{
collection,
joins,
limit,
locale,
page,
pagination,
req = {} as PayloadRequest,
sort: sortArg,
where,
},
) {
const VersionModel = this.versions[collection]
const collectionConfig = this.payload.collections[collection].config
@@ -89,7 +100,29 @@ export const queryDrafts: QueryDrafts = async function queryDrafts(
paginationOptions.options.limit = limit
}
const result = await VersionModel.paginate(versionQuery, paginationOptions)
let result
const aggregate = await buildJoinAggregation({
adapter: this,
collection,
collectionConfig,
joins,
limit,
locale,
query: versionQuery,
versions: true,
})
// build join aggregation
if (aggregate) {
result = await VersionModel.aggregatePaginate(
VersionModel.aggregate(aggregate),
paginationOptions,
)
} else {
result = await VersionModel.paginate(versionQuery, paginationOptions)
}
const docs = JSON.parse(JSON.stringify(result.docs))
return {

View File

@@ -15,6 +15,8 @@ type BuildJoinAggregationArgs = {
locale: string
// the where clause for the top collection
query?: Where
/** whether the query is from drafts */
versions?: boolean
}
export const buildJoinAggregation = async ({
@@ -25,6 +27,7 @@ export const buildJoinAggregation = async ({
limit,
locale,
query,
versions,
}: BuildJoinAggregationArgs): Promise<PipelineStage[] | undefined> => {
if (Object.keys(collectionConfig.joins).length === 0 || joins === false) {
return
@@ -90,7 +93,7 @@ export const buildJoinAggregation = async ({
if (adapter.payload.config.localization && locale === 'all') {
adapter.payload.config.localization.localeCodes.forEach((code) => {
const as = `${join.schemaPath}${code}`
const as = `${versions ? `version.${join.schemaPath}` : join.schemaPath}${code}`
aggregate.push(
{
@@ -98,7 +101,7 @@ export const buildJoinAggregation = async ({
as: `${as}.docs`,
foreignField: `${join.field.on}${code}`,
from: slug,
localField: '_id',
localField: versions ? 'parent' : '_id',
pipeline,
},
},
@@ -131,7 +134,7 @@ export const buildJoinAggregation = async ({
} else {
const localeSuffix =
join.field.localized && adapter.payload.config.localization && locale ? `.${locale}` : ''
const as = `${join.schemaPath}${localeSuffix}`
const as = `${versions ? `version.${join.schemaPath}` : join.schemaPath}${localeSuffix}`
aggregate.push(
{
@@ -139,7 +142,7 @@ export const buildJoinAggregation = async ({
as: `${as}.docs`,
foreignField: `${join.field.on}${localeSuffix}`,
from: slug,
localField: '_id',
localField: versions ? 'parent' : '_id',
pipeline,
},
},

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-postgres",
"version": "3.0.0-beta.114",
"version": "3.0.0-beta.119",
"description": "The officially supported Postgres database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {
@@ -43,15 +43,15 @@
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"prepack": "pnpm clean && pnpm turbo build",
"prepublishOnly": "pnpm clean && pnpm turbo build",
"prepublishOnly": "pnpm clean && pnpm turbo build --filter=./",
"renamePredefinedMigrations": "node --no-deprecation --import @swc-node/register/esm-register ./scripts/renamePredefinedMigrations.ts"
},
"dependencies": {
"@payloadcms/drizzle": "workspace:*",
"@types/pg": "8.10.2",
"console-table-printer": "2.11.2",
"drizzle-kit": "0.26.1",
"drizzle-orm": "0.35.0",
"drizzle-kit": "0.26.2",
"drizzle-orm": "0.35.1",
"pg": "8.11.3",
"prompts": "2.4.2",
"to-snake-case": "1.0.0",

View File

@@ -66,7 +66,7 @@ export const connect: Connect = async function connect(
}
const logger = this.logger || false
this.drizzle = drizzle(this.pool, { logger, schema: this.schema })
this.drizzle = drizzle({ client: this.pool, logger, schema: this.schema })
if (!hotReload) {
if (process.env.PAYLOAD_DROP_DATABASE === 'true') {

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

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

View File

@@ -1,7 +1,7 @@
import type { ChainedMethods } from '@payloadcms/drizzle/types'
import { chainMethods } from '@payloadcms/drizzle'
import { count, sql } from 'drizzle-orm'
import { count } from 'drizzle-orm'
import type { CountDistinct, SQLiteAdapter } from './types.js'
@@ -22,11 +22,7 @@ export const countDistinct: CountDistinct = async function countDistinct(
methods: chainedMethods,
query: db
.select({
count:
joins.length > 0
? sql`count
(DISTINCT ${this.tables[tableName].id})`.mapWith(Number)
: count(),
count: count(),
})
.from(this.tables[tableName])
.where(where),

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

@@ -70,7 +70,6 @@ export const init: Init = async function init(this: SQLiteAdapter) {
disableNotNull: !!collection?.versions?.drafts,
disableUnique: false,
fields: collection.fields,
joins: collection.joins,
locales,
tableName,
timestamps: collection.timestamps,

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,
@@ -60,7 +61,6 @@ type Args = {
disableRelsTableUnique?: boolean
disableUnique: boolean
fields: Field[]
joins?: SanitizedJoins
locales?: [string, ...string[]]
rootRelationships?: Set<string>
rootRelationsToBuild?: RelationMap
@@ -94,7 +94,6 @@ export const buildTable = ({
disableRelsTableUnique,
disableUnique = false,
fields,
joins,
locales,
rootRelationships,
rootRelationsToBuild,
@@ -143,7 +142,6 @@ export const buildTable = ({
disableUnique,
fields,
indexes,
joins,
locales,
localesColumns,
localesIndexes,
@@ -416,21 +414,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,
@@ -42,7 +44,6 @@ type Args = {
fields: (Field | TabAsField)[]
forceLocalized?: boolean
indexes: Record<string, (cols: GenericColumns) => IndexBuilder>
joins?: SanitizedJoins
locales: [string, ...string[]]
localesColumns: Record<string, SQLiteColumnBuilder>
localesIndexes: Record<string, (cols: GenericColumns) => IndexBuilder>
@@ -82,7 +83,6 @@ export const traverseFields = ({
fields,
forceLocalized,
indexes,
joins,
locales,
localesColumns,
localesIndexes,
@@ -164,10 +164,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,
})
}
@@ -662,7 +667,6 @@ export const traverseFields = ({
fields: field.fields,
forceLocalized,
indexes,
joins,
locales,
localesColumns,
localesIndexes,
@@ -718,7 +722,6 @@ export const traverseFields = ({
fields: field.fields,
forceLocalized: field.localized,
indexes,
joins,
locales,
localesColumns,
localesIndexes,
@@ -775,7 +778,6 @@ export const traverseFields = ({
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
forceLocalized,
indexes,
joins,
locales,
localesColumns,
localesIndexes,
@@ -832,7 +834,6 @@ export const traverseFields = ({
fields: field.fields,
forceLocalized,
indexes,
joins,
locales,
localesColumns,
localesIndexes,
@@ -930,30 +931,6 @@ export const traverseFields = ({
break
case 'join': {
// fieldName could be 'posts' or 'group_posts'
// using `on` as the key for the relation
const localized = adapter.payload.config.localization && field.localized
const fieldSchemaPath = `${fieldPrefix || ''}${field.name}`
let target: string
const joinConfig = joins[field.collection].find(
({ schemaPath }) => fieldSchemaPath === schemaPath,
)
if (joinConfig.targetField.hasMany) {
target = `${adapter.tableNameMap.get(toSnakeCase(field.collection))}${adapter.relationshipsSuffix}`
} else {
target = `${adapter.tableNameMap.get(toSnakeCase(field.collection))}${localized ? adapter.localesSuffix : ''}`
}
relationsToBuild.set(fieldName, {
type: 'many',
// joins are not localized on the parent table
localized: false,
relationName: field.on.replaceAll('.', '_'),
target,
})
break
}
default:
break
}

View File

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

View File

@@ -26,7 +26,8 @@ export const connect: Connect = async function connect(
const logger = this.logger || false
// Passed the poolOptions if provided,
// else have vercel/postgres detect the connection string from the environment
this.drizzle = drizzle(this.poolOptions ? new VercelPool(this.poolOptions) : sql, {
this.drizzle = drizzle({
client: this.poolOptions ? new VercelPool(this.poolOptions) : sql,
logger,
schema: this.schema,
})

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

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/drizzle",
"version": "3.0.0-beta.114",
"version": "3.0.0-beta.119",
"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

@@ -21,7 +21,7 @@ export const find: Find = async function find(
},
) {
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
const sort = typeof sortArg === 'string' ? sortArg : collectionConfig.defaultSort
const sort = sortArg !== undefined && sortArg !== null ? sortArg : collectionConfig.defaultSort
const tableName = this.tableNameMap.get(toSnakeCase(collectionConfig.slug))

View File

@@ -16,6 +16,7 @@ type BuildFindQueryArgs = {
joins?: BuildQueryJoinAliases
locale?: string
tableName: string
versions?: boolean
}
export type Result = {
@@ -34,6 +35,7 @@ export const buildFindManyArgs = ({
joins = [],
locale,
tableName,
versions,
}: BuildFindQueryArgs): Record<string, unknown> => {
const result: Result = {
extras: {},
@@ -97,6 +99,7 @@ export const buildFindManyArgs = ({
tablePath: '',
topLevelArgs: result,
topLevelTableName: tableName,
versions,
})
return result

View File

@@ -14,6 +14,7 @@ type Args = {
adapter: DrizzleAdapter
fields: Field[]
tableName: string
versions?: boolean
} & Omit<FindArgs, 'collection'>
export const findMany = async function find({
@@ -28,6 +29,7 @@ export const findMany = async function find({
skip,
sort,
tableName,
versions,
where: whereArg,
}: Args) {
const db = adapter.sessions[await req.transactionID]?.db || adapter.drizzle
@@ -57,9 +59,9 @@ export const findMany = async function find({
const selectDistinctMethods: ChainedMethods = []
if (orderBy?.order && orderBy?.column) {
if (orderBy) {
selectDistinctMethods.push({
args: [orderBy.order(orderBy.column)],
args: [() => orderBy.map(({ column, order }) => order(column))],
method: 'orderBy',
})
}
@@ -71,6 +73,7 @@ export const findMany = async function find({
joinQuery,
joins,
tableName,
versions,
})
selectDistinctMethods.push({ args: [offset], method: 'offset' })
@@ -111,7 +114,7 @@ export const findMany = async function find({
} else {
findManyArgs.limit = limit
findManyArgs.offset = offset
findManyArgs.orderBy = orderBy.order(orderBy.column)
findManyArgs.orderBy = () => orderBy.map(({ column, order }) => order(column))
if (where) {
findManyArgs.where = where

View File

@@ -1,4 +1,3 @@
import type { DBQueryConfig } from 'drizzle-orm'
import type { LibSQLDatabase } from 'drizzle-orm/libsql'
import type { Field, JoinQuery } from 'payload'
@@ -26,6 +25,7 @@ type TraverseFieldArgs = {
tablePath: string
topLevelArgs: Record<string, unknown>
topLevelTableName: string
versions?: boolean
}
export const traverseFields = ({
@@ -42,6 +42,7 @@ export const traverseFields = ({
tablePath,
topLevelArgs,
topLevelTableName,
versions,
}: TraverseFieldArgs) => {
fields.forEach((field) => {
if (fieldIsVirtual(field)) {
@@ -99,6 +100,7 @@ export const traverseFields = ({
tablePath: tabTablePath,
topLevelArgs,
topLevelTableName,
versions,
})
})
@@ -223,6 +225,7 @@ export const traverseFields = ({
tablePath: `${tablePath}${toSnakeCase(field.name)}_`,
topLevelArgs,
topLevelTableName,
versions,
})
break
@@ -233,87 +236,156 @@ export const traverseFields = ({
if (joinQuery === false) {
break
}
const {
limit: limitArg = 10,
sort,
where,
} = joinQuery[`${path.replaceAll('_', '.')}${field.name}`] || {}
let limit = limitArg
if (limit !== 0) {
// get an additional document and slice it later to determine if there is a next page
limit += 1
}
const fields = adapter.payload.collections[field.collection].config.fields
const joinCollectionTableName = adapter.tableNameMap.get(toSnakeCase(field.collection))
let joinTableName = `${adapter.tableNameMap.get(toSnakeCase(field.collection))}${
field.localized && adapter.payload.config.localization ? adapter.localesSuffix : ''
}`
const joins: BuildQueryJoinAliases = []
const buildQueryResult = buildQuery({
adapter,
fields,
joins,
locale,
sort,
tableName: joinCollectionTableName,
where,
})
let subQueryWhere = buildQueryResult.where
const orderBy = buildQueryResult.orderBy
let joinLocalesCollectionTableName: string | undefined
const currentIDColumn = versions
? adapter.tables[currentTableName].parent
: adapter.tables[currentTableName].id
// Handle hasMany _rels table
if (field.hasMany) {
const db = adapter.drizzle as LibSQLDatabase
if (field.localized) {
joinTableName = adapter.tableNameMap.get(toSnakeCase(field.collection))
}
const joinTable = `${joinTableName}${adapter.relationshipsSuffix}`
const joinRelsCollectionTableName = `${joinCollectionTableName}${adapter.relationshipsSuffix}`
const joins: BuildQueryJoinAliases = [
{
if (field.localized) {
joinLocalesCollectionTableName = joinRelsCollectionTableName
}
let columnReferenceToCurrentID: string
if (versions) {
columnReferenceToCurrentID = `${topLevelTableName.replace('_', '').replace(new RegExp(`${adapter.versionsSuffix}$`), '')}_id`
} else {
columnReferenceToCurrentID = `${topLevelTableName}_id`
}
joins.push({
type: 'innerJoin',
condition: and(
eq(
adapter.tables[joinRelsCollectionTableName].parent,
adapter.tables[joinCollectionTableName].id,
),
eq(
sql.raw(`"${joinRelsCollectionTableName}"."${columnReferenceToCurrentID}"`),
currentIDColumn,
),
eq(adapter.tables[joinRelsCollectionTableName].path, field.on),
),
table: adapter.tables[joinRelsCollectionTableName],
})
} else {
// Handle localized without hasMany
const foreignColumn = field.on.replaceAll('.', '_')
if (field.localized) {
joinLocalesCollectionTableName = `${joinCollectionTableName}${adapter.localesSuffix}`
joins.push({
type: 'innerJoin',
condition: and(
eq(adapter.tables[joinTable].parent, adapter.tables[joinTableName].id),
eq(
sql.raw(`"${joinTable}"."${topLevelTableName}_id"`),
adapter.tables[currentTableName].id,
adapter.tables[joinLocalesCollectionTableName]._parentID,
adapter.tables[joinCollectionTableName].id,
),
eq(
adapter.tables[joinLocalesCollectionTableName][foreignColumn],
currentIDColumn,
),
eq(adapter.tables[joinTable].path, field.on),
),
table: adapter.tables[joinTable],
},
]
const { orderBy, where: subQueryWhere } = buildQuery({
adapter,
fields,
joins,
locale,
sort,
tableName: joinCollectionTableName,
where: {},
})
const chainedMethods: ChainedMethods = []
joins.forEach(({ type, condition, table }) => {
chainedMethods.push({
args: [table, condition],
method: type ?? 'leftJoin',
table: adapter.tables[joinLocalesCollectionTableName],
})
// Handle without localized and without hasMany, just a condition append to where. With localized the inner join handles eq.
} else {
const constraint = eq(
adapter.tables[joinCollectionTableName][foreignColumn],
currentIDColumn,
)
if (subQueryWhere) {
subQueryWhere = and(subQueryWhere, constraint)
} else {
subQueryWhere = constraint
}
}
}
const chainedMethods: ChainedMethods = []
joins.forEach(({ type, condition, table }) => {
chainedMethods.push({
args: [table, condition],
method: type ?? 'leftJoin',
})
})
const subQuery = chainMethods({
methods: chainedMethods,
query: db
.select({
id: adapter.tables[joinTableName].id,
...(field.localized && {
locale: adapter.tables[joinTable].locale,
}),
})
.from(adapter.tables[joinTableName])
.where(subQueryWhere)
.orderBy(orderBy.order(orderBy.column))
.limit(limit),
if (limit !== 0) {
chainedMethods.push({
args: [limit],
method: 'limit',
})
}
const columnName = `${path.replaceAll('.', '_')}${field.name}`
const db = adapter.drizzle as LibSQLDatabase
const jsonObjectSelect = field.localized
? sql.raw(`'_parentID', "id", '_locale', "locale"`)
: sql.raw(`'id', "id"`)
const subQuery = chainMethods({
methods: chainedMethods,
query: db
.select({
id: adapter.tables[joinCollectionTableName].id,
...(joinLocalesCollectionTableName && {
locale:
adapter.tables[joinLocalesCollectionTableName].locale ||
adapter.tables[joinLocalesCollectionTableName]._locale,
}),
})
.from(adapter.tables[joinCollectionTableName])
.where(subQueryWhere)
.orderBy(() => orderBy.map(({ column, order }) => order(column))),
})
if (adapter.name === 'sqlite') {
currentArgs.extras[columnName] = sql`
const columnName = `${path.replaceAll('.', '_')}${field.name}`
const jsonObjectSelect = field.localized
? sql.raw(
`'_parentID', "id", '_locale', "${adapter.tables[joinLocalesCollectionTableName].locale ? 'locale' : '_locale'}"`,
)
: sql.raw(`'id', "id"`)
if (adapter.name === 'sqlite') {
currentArgs.extras[columnName] = sql`
COALESCE((
SELECT json_group_array(json_object(${jsonObjectSelect}))
FROM (
@@ -321,8 +393,8 @@ export const traverseFields = ({
) AS ${sql.raw(`${columnName}_sub`)}
), '[]')
`.as(columnName)
} else {
currentArgs.extras[columnName] = sql`
} else {
currentArgs.extras[columnName] = sql`
COALESCE((
SELECT json_agg(json_build_object(${jsonObjectSelect}))
FROM (
@@ -330,41 +402,8 @@ export const traverseFields = ({
) AS ${sql.raw(`${columnName}_sub`)}
), '[]'::json)
`.as(columnName)
}
break
}
const selectFields = {}
const withJoin: DBQueryConfig<'many', true, any, any> = {
columns: selectFields,
}
if (limit) {
withJoin.limit = limit
}
if (field.localized) {
withJoin.columns._locale = true
withJoin.columns._parentID = true
} else {
withJoin.columns.id = true
withJoin.columns.parent = true
}
const { orderBy, where: joinWhere } = buildQuery({
adapter,
fields,
joins,
locale,
sort,
tableName: joinTableName,
where,
})
if (joinWhere) {
withJoin.where = () => joinWhere
}
withJoin.orderBy = orderBy.order(orderBy.column)
currentArgs.with[`${path.replaceAll('.', '_')}${field.name}`] = withJoin
break
}

View File

@@ -24,7 +24,7 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV
const globalConfig: SanitizedGlobalConfig = this.payload.globals.config.find(
({ slug }) => slug === global,
)
const sort = typeof sortArg === 'string' ? sortArg : '-createdAt'
const sort = sortArg !== undefined && sortArg !== null ? sortArg : '-createdAt'
const tableName = this.tableNameMap.get(
`_${toSnakeCase(globalConfig.slug)}${this.versionsSuffix}`,

View File

@@ -22,7 +22,7 @@ export const findVersions: FindVersions = async function findVersions(
},
) {
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
const sort = typeof sortArg === 'string' ? sortArg : collectionConfig.defaultSort
const sort = sortArg !== undefined && sortArg !== null ? sortArg : collectionConfig.defaultSort
const tableName = this.tableNameMap.get(
`_${toSnakeCase(collectionConfig.slug)}${this.versionsSuffix}`,

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

@@ -1,4 +1,4 @@
import { count, sql } from 'drizzle-orm'
import { count } from 'drizzle-orm'
import type { ChainedMethods, TransactionPg } from '../types.js'
import type { BasePostgresAdapter, CountDistinct } from './types.js'
@@ -22,11 +22,7 @@ export const countDistinct: CountDistinct = async function countDistinct(
methods: chainedMethods,
query: (db as TransactionPg)
.select({
count:
joins.length > 0
? sql`count
(DISTINCT ${this.tables[tableName].id})`.mapWith(Number)
: count(),
count: count(),
})
.from(this.tables[tableName])
.where(where),

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

@@ -57,7 +57,6 @@ export const init: Init = async function init(this: BasePostgresAdapter) {
disableNotNull: !!collection?.versions?.drafts,
disableUnique: false,
fields: collection.fields,
joins: collection.joins,
tableName,
timestamps: collection.timestamps,
versions: false,

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'
@@ -49,7 +50,6 @@ type Args = {
disableRelsTableUnique?: boolean
disableUnique: boolean
fields: Field[]
joins?: SanitizedJoins
rootRelationships?: Set<string>
rootRelationsToBuild?: RelationMap
rootTableIDColType?: string
@@ -82,7 +82,6 @@ export const buildTable = ({
disableRelsTableUnique = false,
disableUnique = false,
fields,
joins,
rootRelationships,
rootRelationsToBuild,
rootTableIDColType,
@@ -132,7 +131,6 @@ export const buildTable = ({
disableUnique,
fields,
indexes,
joins,
localesColumns,
localesIndexes,
newTableName: tableName,
@@ -389,21 +387,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'
@@ -49,7 +50,6 @@ type Args = {
fields: (Field | TabAsField)[]
forceLocalized?: boolean
indexes: Record<string, (cols: GenericColumns) => IndexBuilder>
joins?: SanitizedJoins
localesColumns: Record<string, PgColumnBuilder>
localesIndexes: Record<string, (cols: GenericColumns) => IndexBuilder>
newTableName: string
@@ -88,7 +88,6 @@ export const traverseFields = ({
fields,
forceLocalized,
indexes,
joins,
localesColumns,
localesIndexes,
newTableName,
@@ -169,10 +168,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,
})
}
@@ -669,7 +670,6 @@ export const traverseFields = ({
fields: field.fields,
forceLocalized,
indexes,
joins,
localesColumns,
localesIndexes,
newTableName,
@@ -724,7 +724,6 @@ export const traverseFields = ({
fields: field.fields,
forceLocalized: field.localized,
indexes,
joins,
localesColumns,
localesIndexes,
newTableName: `${parentTableName}_${columnName}`,
@@ -780,7 +779,6 @@ export const traverseFields = ({
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
forceLocalized,
indexes,
joins,
localesColumns,
localesIndexes,
newTableName,
@@ -836,7 +834,6 @@ export const traverseFields = ({
fields: field.fields,
forceLocalized,
indexes,
joins,
localesColumns,
localesIndexes,
newTableName,
@@ -933,30 +930,6 @@ export const traverseFields = ({
break
case 'join': {
// fieldName could be 'posts' or 'group_posts'
// using `on` as the key for the relation
const localized = adapter.payload.config.localization && field.localized
const fieldSchemaPath = `${fieldPrefix || ''}${field.name}`
let target: string
const joinConfig = joins[field.collection].find(
({ schemaPath }) => fieldSchemaPath === schemaPath,
)
if (joinConfig.targetField.hasMany) {
target = `${adapter.tableNameMap.get(toSnakeCase(field.collection))}${adapter.relationshipsSuffix}`
} else {
target = `${adapter.tableNameMap.get(toSnakeCase(field.collection))}${localized ? adapter.localesSuffix : ''}`
}
relationsToBuild.set(fieldName, {
type: 'many',
// joins are not localized on the parent table
localized: false,
relationName: field.on.replaceAll('.', '_'),
target,
})
break
}
default:
break
}

View File

@@ -0,0 +1,25 @@
import type { SQL } from 'drizzle-orm'
import type { PgTableWithColumns } from 'drizzle-orm/pg-core'
import type { GenericTable } from '../types.js'
import type { BuildQueryJoinAliases } from './buildQuery.js'
import { getNameFromDrizzleTable } from '../utilities/getNameFromDrizzleTable.js'
export const addJoinTable = ({
type,
condition,
joins,
table,
}: {
condition: SQL
joins: BuildQueryJoinAliases
table: GenericTable | PgTableWithColumns<any>
type?: 'innerJoin' | 'leftJoin' | 'rightJoin'
}) => {
const name = getNameFromDrizzleTable(table)
if (!joins.some((eachJoin) => getNameFromDrizzleTable(eachJoin.table) === name)) {
joins.push({ type, condition, table })
}
}

View File

@@ -1,4 +1,4 @@
import type { Field } from 'payload'
import type { Field, Sort } from 'payload'
import { asc, desc } from 'drizzle-orm'
@@ -13,7 +13,7 @@ type Args = {
joins: BuildQueryJoinAliases
locale?: string
selectFields: Record<string, GenericColumn>
sort?: string
sort?: Sort
tableName: string
}
@@ -29,55 +29,55 @@ export const buildOrderBy = ({
sort,
tableName,
}: Args): BuildQueryResult['orderBy'] => {
const orderBy: BuildQueryResult['orderBy'] = {
column: null,
order: null,
const orderBy: BuildQueryResult['orderBy'] = []
if (!sort) {
const createdAt = adapter.tables[tableName]?.createdAt
if (createdAt) {
sort = '-createdAt'
} else {
sort = '-id'
}
}
if (sort) {
let sortPath
if (typeof sort === 'string') {
sort = [sort]
}
if (sort[0] === '-') {
sortPath = sort.substring(1)
orderBy.order = desc
for (const sortItem of sort) {
let sortProperty: string
let sortDirection: 'asc' | 'desc'
if (sortItem[0] === '-') {
sortProperty = sortItem.substring(1)
sortDirection = 'desc'
} else {
sortPath = sort
orderBy.order = asc
sortProperty = sortItem
sortDirection = 'asc'
}
try {
const { columnName: sortTableColumnName, table: sortTable } = getTableColumnFromPath({
adapter,
collectionPath: sortPath,
collectionPath: sortProperty,
fields,
joins,
locale,
pathSegments: sortPath.replace(/__/g, '.').split('.'),
pathSegments: sortProperty.replace(/__/g, '.').split('.'),
selectFields,
tableName,
useAlias: true,
value: sortPath,
value: sortProperty,
})
orderBy.column = sortTable?.[sortTableColumnName]
if (sortTable?.[sortTableColumnName]) {
orderBy.push({
column: sortTable[sortTableColumnName],
order: sortDirection === 'asc' ? asc : desc,
})
selectFields[sortTableColumnName] = sortTable[sortTableColumnName]
}
} catch (err) {
// continue
}
}
if (!orderBy?.column) {
orderBy.order = desc
const createdAt = adapter.tables[tableName]?.createdAt
if (createdAt) {
orderBy.column = createdAt
} else {
orderBy.column = adapter.tables[tableName].id
}
}
if (orderBy.column) {
selectFields.sort = orderBy.column
}
return orderBy
}

View File

@@ -1,6 +1,6 @@
import type { asc, desc, SQL } from 'drizzle-orm'
import type { PgTableWithColumns } from 'drizzle-orm/pg-core'
import type { Field, Where } from 'payload'
import type { Field, Sort, Where } from 'payload'
import type { DrizzleAdapter, GenericColumn, GenericTable } from '../types.js'
@@ -18,7 +18,7 @@ type BuildQueryArgs = {
fields: Field[]
joins?: BuildQueryJoinAliases
locale?: string
sort?: string
sort?: Sort
tableName: string
where: Where
}
@@ -28,7 +28,7 @@ export type BuildQueryResult = {
orderBy: {
column: GenericColumn
order: typeof asc | typeof desc
}
}[]
selectFields: Record<string, GenericColumn>
where: SQL
}

View File

@@ -13,6 +13,7 @@ import type { DrizzleAdapter, GenericColumn } from '../types.js'
import type { BuildQueryJoinAliases } from './buildQuery.js'
import { isPolymorphicRelationship } from '../utilities/isPolymorphicRelationship.js'
import { addJoinTable } from './addJoinTable.js'
import { getTableAlias } from './getTableAlias.js'
type Constraint = {
@@ -53,7 +54,6 @@ type Args = {
* If creating a new table name for arrays and blocks, this suffix should be appended to the table name
*/
tableNameSuffix?: string
useAlias?: boolean
/**
* The raw value of the query before sanitization
*/
@@ -79,7 +79,6 @@ export const getTableColumnFromPath = ({
selectFields,
tableName,
tableNameSuffix = '',
useAlias,
value,
}: Args): TableColumn => {
const fieldPath = incomingSegments[0]
@@ -141,7 +140,6 @@ export const getTableColumnFromPath = ({
selectFields,
tableName: newTableName,
tableNameSuffix,
useAlias,
value,
})
}
@@ -162,7 +160,6 @@ export const getTableColumnFromPath = ({
selectFields,
tableName: newTableName,
tableNameSuffix: `${tableNameSuffix}${toSnakeCase(field.name)}_`,
useAlias,
value,
})
}
@@ -181,7 +178,6 @@ export const getTableColumnFromPath = ({
selectFields,
tableName: newTableName,
tableNameSuffix,
useAlias,
value,
})
}
@@ -190,17 +186,17 @@ export const getTableColumnFromPath = ({
if (locale && field.localized && adapter.payload.config.localization) {
newTableName = `${tableName}${adapter.localesSuffix}`
joins.push({
condition: eq(adapter.tables[tableName].id, adapter.tables[newTableName]._parentID),
let condition = eq(adapter.tables[tableName].id, adapter.tables[newTableName]._parentID)
if (locale !== 'all') {
condition = and(condition, eq(adapter.tables[newTableName]._locale, locale))
}
addJoinTable({
condition,
joins,
table: adapter.tables[newTableName],
})
if (locale !== 'all') {
constraints.push({
columnName: '_locale',
table: adapter.tables[newTableName],
value: locale,
})
}
}
return getTableColumnFromPath({
adapter,
@@ -217,7 +213,6 @@ export const getTableColumnFromPath = ({
selectFields,
tableName: newTableName,
tableNameSuffix: `${tableNameSuffix}${toSnakeCase(field.name)}_`,
useAlias,
value,
})
}
@@ -229,23 +224,24 @@ export const getTableColumnFromPath = ({
)
if (locale && field.localized && adapter.payload.config.localization) {
joins.push({
condition: and(
eq(adapter.tables[tableName].id, adapter.tables[newTableName].parent),
eq(adapter.tables[newTableName]._locale, locale),
),
const conditions = [
eq(adapter.tables[tableName].id, adapter.tables[newTableName].parent),
eq(adapter.tables[newTableName]._locale, locale),
]
if (locale !== 'all') {
conditions.push(eq(adapter.tables[newTableName]._locale, locale))
}
addJoinTable({
condition: and(...conditions),
joins,
table: adapter.tables[newTableName],
})
if (locale !== 'all') {
constraints.push({
columnName: '_locale',
table: adapter.tables[newTableName],
value: locale,
})
}
} else {
joins.push({
addJoinTable({
condition: eq(adapter.tables[tableName].id, adapter.tables[newTableName].parent),
joins,
table: adapter.tables[newTableName],
})
}
@@ -276,20 +272,20 @@ export const getTableColumnFromPath = ({
]
if (locale && field.localized && adapter.payload.config.localization) {
joins.push({
condition: and(...joinConstraints, eq(adapter.tables[newTableName]._locale, locale)),
const conditions = [...joinConstraints]
if (locale !== 'all') {
conditions.push(eq(adapter.tables[newTableName]._locale, locale))
}
addJoinTable({
condition: and(...conditions),
joins,
table: adapter.tables[newTableName],
})
if (locale !== 'all') {
constraints.push({
columnName: 'locale',
table: adapter.tables[newTableName],
value: locale,
})
}
} else {
joins.push({
addJoinTable({
condition: and(...joinConstraints),
joins,
table: adapter.tables[newTableName],
})
}
@@ -313,23 +309,20 @@ export const getTableColumnFromPath = ({
constraintPath = `${constraintPath}${field.name}.%.`
if (locale && field.localized && adapter.payload.config.localization) {
joins.push({
condition: and(
eq(arrayParentTable.id, adapter.tables[newTableName]._parentID),
eq(adapter.tables[newTableName]._locale, locale),
),
const conditions = [eq(arrayParentTable.id, adapter.tables[newTableName]._parentID)]
if (locale !== 'all') {
conditions.push(eq(adapter.tables[newTableName]._locale, locale))
}
addJoinTable({
condition: and(...conditions),
joins,
table: adapter.tables[newTableName],
})
if (locale !== 'all') {
constraints.push({
columnName: '_locale',
table: adapter.tables[newTableName],
value: locale,
})
}
} else {
joins.push({
addJoinTable({
condition: eq(arrayParentTable.id, adapter.tables[newTableName]._parentID),
joins,
table: adapter.tables[newTableName],
})
}
@@ -345,7 +338,6 @@ export const getTableColumnFromPath = ({
rootTableName,
selectFields,
tableName: newTableName,
useAlias,
value,
})
}
@@ -404,7 +396,6 @@ export const getTableColumnFromPath = ({
rootTableName,
selectFields: blockSelectFields,
tableName: newTableName,
useAlias,
value,
})
} catch (error) {
@@ -417,23 +408,21 @@ export const getTableColumnFromPath = ({
constraints = constraints.concat(blockConstraints)
selectFields = { ...selectFields, ...blockSelectFields }
if (field.localized && adapter.payload.config.localization) {
joins.push({
condition: and(
eq(
(aliasTable || adapter.tables[tableName]).id,
adapter.tables[newTableName]._parentID,
),
eq(adapter.tables[newTableName]._locale, locale),
const conditions = [
eq(
(aliasTable || adapter.tables[tableName]).id,
adapter.tables[newTableName]._parentID,
),
]
if (locale !== 'all') {
conditions.push(eq(adapter.tables[newTableName]._locale, locale))
}
joins.push({
condition: and(...conditions),
table: adapter.tables[newTableName],
})
if (locale) {
constraints.push({
columnName: '_locale',
table: adapter.tables[newTableName],
value: locale,
})
}
} else {
joins.push({
condition: eq(
@@ -471,21 +460,18 @@ export const getTableColumnFromPath = ({
// Join in the relationships table
if (locale && field.localized && adapter.payload.config.localization) {
const conditions = [
eq((aliasTable || adapter.tables[rootTableName]).id, aliasRelationshipTable.parent),
like(aliasRelationshipTable.path, `${constraintPath}${field.name}`),
]
if (locale !== 'all') {
conditions.push(eq(aliasRelationshipTable.locale, locale))
}
joins.push({
condition: and(
eq((aliasTable || adapter.tables[rootTableName]).id, aliasRelationshipTable.parent),
eq(aliasRelationshipTable.locale, locale),
like(aliasRelationshipTable.path, `${constraintPath}${field.name}`),
),
condition: and(...conditions),
table: aliasRelationshipTable,
})
if (locale !== 'all') {
constraints.push({
columnName: 'locale',
table: aliasRelationshipTable,
value: locale,
})
}
} else {
// Join in the relationships table
joins.push({
@@ -641,7 +627,6 @@ export const getTableColumnFromPath = ({
rootTableName: newTableName,
selectFields,
tableName: newTableName,
useAlias,
value,
})
} else if (
@@ -661,15 +646,22 @@ export const getTableColumnFromPath = ({
tableName: `${rootTableName}${adapter.localesSuffix}`,
})
joins.push({
condition: and(
eq(aliasLocaleTable._parentID, adapter.tables[rootTableName].id),
eq(aliasLocaleTable._locale, locale),
),
table: aliasLocaleTable,
const condtions = [eq(aliasLocaleTable._parentID, adapter.tables[rootTableName].id)]
if (locale !== 'all') {
condtions.push(eq(aliasLocaleTable._locale, locale))
}
const localesTable = adapter.tables[`${rootTableName}${adapter.localesSuffix}`]
addJoinTable({
condition: and(...condtions),
joins,
table: localesTable,
})
joins.push({
condition: eq(aliasLocaleTable[columnName], newAliasTable.id),
condition: eq(localesTable[columnName], newAliasTable.id),
table: newAliasTable,
})
} else {
@@ -694,7 +686,6 @@ export const getTableColumnFromPath = ({
pathSegments: pathSegments.slice(1),
selectFields,
tableName: newTableName,
useAlias,
value,
})
}
@@ -716,24 +707,21 @@ export const getTableColumnFromPath = ({
const parentTable = aliasTable || adapter.tables[tableName]
newTableName = `${tableName}${adapter.localesSuffix}`
newTable = useAlias
? getTableAlias({ adapter, tableName: newTableName }).newAliasTable
: adapter.tables[newTableName]
newTable = adapter.tables[newTableName]
joins.push({
condition: eq(parentTable.id, newTable._parentID),
let condition = eq(parentTable.id, newTable._parentID)
if (locale !== 'all') {
condition = and(condition, eq(newTable._locale, locale))
}
addJoinTable({
condition,
joins,
table: newTable,
})
aliasTable = undefined
if (locale !== 'all') {
constraints.push({
columnName: '_locale',
table: newTable,
value: locale,
})
}
}
const targetTable = aliasTable || newTable

View File

@@ -1,4 +1,4 @@
import type { PayloadRequest, QueryDrafts, SanitizedCollectionConfig } from 'payload'
import type { JoinQuery, PayloadRequest, QueryDrafts, SanitizedCollectionConfig } from 'payload'
import { buildVersionCollectionFields, combineQueries } from 'payload'
import toSnakeCase from 'to-snake-case'
@@ -9,7 +9,17 @@ import { findMany } from './find/findMany.js'
export const queryDrafts: QueryDrafts = async function queryDrafts(
this: DrizzleAdapter,
{ collection, limit, locale, page = 1, pagination, req = {} as PayloadRequest, sort, where },
{
collection,
joins,
limit,
locale,
page = 1,
pagination,
req = {} as PayloadRequest,
sort,
where,
},
) {
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
const tableName = this.tableNameMap.get(
@@ -22,6 +32,7 @@ export const queryDrafts: QueryDrafts = async function queryDrafts(
const result = await findMany({
adapter: this,
fields,
joins,
limit,
locale,
page,
@@ -29,6 +40,7 @@ export const queryDrafts: QueryDrafts = async function queryDrafts(
req,
sort,
tableName,
versions: true,
where: combinedWhere,
})

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,

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