Compare commits
1 Commits
v3.0.0-bet
...
v3.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
429f1a1ccd |
1
.github/workflows/pr-title.yml
vendored
1
.github/workflows/pr-title.yml
vendored
@@ -5,6 +5,7 @@ on:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- synchronize
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
@@ -122,19 +122,3 @@ 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')
|
||||
```
|
||||
|
||||
@@ -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 type { CollectionConfig } from 'payload'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const CollectionWithCreateAccess: CollectionConfig = {
|
||||
// ...
|
||||
|
||||
@@ -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 type { CollectionConfig } from 'payload';
|
||||
import { CollectionConfig } from 'payload';
|
||||
|
||||
export const Posts: CollectionConfig = {
|
||||
slug: 'posts',
|
||||
|
||||
@@ -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 type { CollectionConfig } from 'payload'
|
||||
import { 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 type { CollectionConfig } from 'payload'
|
||||
import { 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 type { CollectionConfig } from 'payload'
|
||||
import { 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 type { CollectionConfig } from 'payload'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const Posts: CollectionConfig = {
|
||||
// ...
|
||||
|
||||
@@ -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](../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](/docs/beta/local-api/outside-nextjs#troubleshooting) section.
|
||||
|
||||
### Component paths in external packages
|
||||
|
||||
|
||||
@@ -217,7 +217,6 @@ 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} />
|
||||
|
||||
@@ -29,7 +29,7 @@ The lockDocuments property exists on both the Collection Config and the Global C
|
||||
Here’s an example configuration for document locking:
|
||||
|
||||
```ts
|
||||
import type { CollectionConfig } from 'payload'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const Posts: CollectionConfig = {
|
||||
slug: 'posts',
|
||||
|
||||
@@ -36,7 +36,7 @@ To customize Root Metadata, use the `admin.meta` key in your Payload Config:
|
||||
{
|
||||
rel: 'icon',
|
||||
type: 'image/png',
|
||||
url: '/favicon.png',
|
||||
href: '/favicon.png',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -80,12 +80,12 @@ To customize icons, use the `icons` key within the `admin.meta` object in your P
|
||||
{
|
||||
rel: 'icon',
|
||||
type: 'image/png',
|
||||
url: '/favicon.png',
|
||||
href: '/favicon.png',
|
||||
},
|
||||
{
|
||||
rel: 'apple-touch-icon',
|
||||
type: 'image/png',
|
||||
url: '/apple-touch-icon.png',
|
||||
href: '/apple-touch-icon.png',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -140,7 +140,7 @@ The following options are available for Open Graph Metadata:
|
||||
| Key | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| **`description`** | `string` | The description of the Admin Panel. |
|
||||
| **`images`** | `OGImageConfig` or `OGImageConfig[]` | An array of image objects. |
|
||||
| **`images`** | `OGImageConfig | OGImageConfig[]` | An array of image objects. |
|
||||
| **`siteName`** | `string` | The name of the site. |
|
||||
| **`title`** | `string` | The title of the Admin Panel. |
|
||||
|
||||
@@ -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 type { CollectionConfig } from 'payload'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const MyCollection: CollectionConfig = {
|
||||
// ...
|
||||
|
||||
@@ -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](./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](https://payloadcms.com/docs/beta/authentication/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](../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](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.
|
||||
|
||||
|
||||
<Banner type="success">
|
||||
|
||||
@@ -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 type { CollectionConfig } from 'payload'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const Users: CollectionConfig = {
|
||||
slug: 'users',
|
||||
|
||||
@@ -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 type { CollectionConfig } from 'payload'
|
||||
import { 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 type { CollectionConfig } from 'payload'
|
||||
import { 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 type { CollectionConfig } from 'payload'
|
||||
import { 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 type { CollectionConfig } from 'payload'
|
||||
import { 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 type { CollectionConfig } from 'payload'
|
||||
import { 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 type { CollectionConfig } from 'payload'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const Customers: CollectionConfig = {
|
||||
// ...
|
||||
|
||||
@@ -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 type { CollectionConfig } from 'payload'
|
||||
import { 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 type { CollectionConfig } from 'payload'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const Admins: CollectionConfig = {
|
||||
// ...
|
||||
|
||||
@@ -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](../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](https://payloadcms.com/docs/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 project’s 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/payload-cloud`
|
||||
`yarn add @payloadcms/plugin-cloud`
|
||||
|
||||
```js
|
||||
import { payloadCloudPlugin } from '@payloadcms/payload-cloud'
|
||||
import { payloadCloud } from '@payloadcms/plugin-cloud'
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
export default buildConfig({
|
||||
plugins: [payloadCloudPlugin()],
|
||||
plugins: [payloadCloud()],
|
||||
// rest of config
|
||||
})
|
||||
```
|
||||
@@ -115,11 +115,6 @@ 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.
|
||||
|
||||
@@ -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 type { CollectionConfig } from 'payload'
|
||||
import { 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. 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). |
|
||||
| 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). |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
|
||||
@@ -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 type { CollectionConfig } from 'payload'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const Articles: CollectionConfig = {
|
||||
slug: 'articles',
|
||||
|
||||
@@ -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'
|
||||
import { buildConfig } from 'payload/config'
|
||||
|
||||
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'
|
||||
import { buildConfig } from 'payload/config'
|
||||
|
||||
export default buildConfig({
|
||||
// ...
|
||||
|
||||
@@ -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](./postgres#prototyping-in-dev-mode).
|
||||
For more information about push mode and prototyping in development, [click here](/docs/beta/database/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.
|
||||
|
||||
|
||||
@@ -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](./migrations#when-to-run-migrations).
|
||||
For more information about migrations, [click here](/docs/beta/database/migrations#when-to-run-migrations).
|
||||
|
||||
## Drizzle schema hooks
|
||||
|
||||
|
||||
@@ -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](./migrations#when-to-run-migrations).
|
||||
For more information about migrations, [click here](/docs/beta/database/migrations#when-to-run-migrations).
|
||||
|
||||
## Drizzle schema hooks
|
||||
|
||||
|
||||
@@ -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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
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 type { CollectionConfig } from 'payload'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const ExampleCollection: CollectionConfig = {
|
||||
slug: 'example-collection',
|
||||
|
||||
@@ -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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyBlocksField: Field = {
|
||||
// ...
|
||||
|
||||
@@ -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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
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 type { CollectionConfig } from 'payload'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const ExampleCollection: CollectionConfig = {
|
||||
slug: 'example-collection',
|
||||
|
||||
@@ -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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
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 type { CollectionConfig } from 'payload'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const ExampleCollection: CollectionConfig = {
|
||||
slug: 'example-collection',
|
||||
|
||||
@@ -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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
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 type { CollectionConfig } from 'payload'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const ExampleCollection: CollectionConfig = {
|
||||
slug: 'example-collection',
|
||||
|
||||
@@ -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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
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 type { CollectionConfig } from 'payload'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const ExampleCollection: CollectionConfig = {
|
||||
slug: 'example-collection',
|
||||
|
||||
@@ -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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
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 type { CollectionConfig } from 'payload'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const ExampleCollection: CollectionConfig = {
|
||||
slug: 'example-collection',
|
||||
|
||||
@@ -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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
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 type { CollectionConfig } from 'payload'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const ExampleCollection: CollectionConfig = {
|
||||
slug: 'example-collection',
|
||||
|
||||
@@ -6,10 +6,8 @@ desc: The Join field provides the ability to work on related documents. Learn ho
|
||||
keywords: join, relationship, junction, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
The Join Field is used to make Relationship and Upload fields available in the opposite direction. With a Join you can
|
||||
edit and view collections
|
||||
having reference to a specific collection document. The field itself acts as a virtual field, in that no new data is
|
||||
stored on the collection with a Join
|
||||
The Join Field is used to make Relationship and Upload fields available in the opposite direction. With a Join you can edit and view collections
|
||||
having reference to a specific collection document. The field itself acts as a virtual field, in that no new data is stored on the collection with a Join
|
||||
field. Instead, the Admin UI surfaces the related documents for a better editing experience and is surfaced by Payload's
|
||||
APIs.
|
||||
|
||||
@@ -21,10 +19,10 @@ The Join field is useful in scenarios including:
|
||||
- Displaying where a document or upload is used in other documents
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/join.png"
|
||||
srcDark="https://payloadcms.com/images/docs/fields/join-dark.png"
|
||||
alt="Shows Join field in the Payload Admin Panel"
|
||||
caption="Admin Panel screenshot of Join field"
|
||||
srcLight="https://payloadcms.com/images/docs/fields/join.png"
|
||||
srcDark="https://payloadcms.com/images/docs/fields/join-dark.png"
|
||||
alt="Shows Join field in the Payload Admin Panel"
|
||||
caption="Admin Panel screenshot of Join field"
|
||||
/>
|
||||
|
||||
For the Join field to work, you must have an existing [relationship](./relationship) or [upload](./upload) field in the
|
||||
@@ -32,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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyJoinField: Field = {
|
||||
// highlight-start
|
||||
@@ -113,11 +111,9 @@ related docs from a new pseudo-junction collection called `categories_posts`. No
|
||||
third junction collection, and can be surfaced on both Posts and Categories. But, importantly, you could add
|
||||
additional "context" fields to this shared junction collection.
|
||||
|
||||
For example, on this `categories_posts` collection, in addition to having the `category` and `post` fields, we could add
|
||||
custom "context" fields like `featured` or `spotlight`,
|
||||
For example, on this `categories_posts` collection, in addition to having the `category` and `post` fields, we could add custom "context" fields like `featured` or `spotlight`,
|
||||
which would allow you to store additional information directly on relationships.
|
||||
The `join` field gives you complete control over any type of relational architecture in Payload, all wrapped up in a
|
||||
powerful Admin UI.
|
||||
The `join` field gives you complete control over any type of relational architecture in Payload, all wrapped up in a powerful Admin UI.
|
||||
|
||||
## Config Options
|
||||
|
||||
@@ -130,11 +126,11 @@ powerful Admin UI.
|
||||
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
|
||||
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
|
||||
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
|
||||
| **`defaultLimit`** | The number of documents to return. Set to 0 to return all related documents. |
|
||||
| **`defaultSort`** | The field name used to specify the order the joined documents are returned. |
|
||||
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
|
||||
| **`required`** | Require this field to have a value. |
|
||||
| **`admin`** | Admin-specific configuration. |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins). |
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema. |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
@@ -154,12 +150,12 @@ object with:
|
||||
{
|
||||
"id": "66e3431a3f23e684075aaeb9",
|
||||
// other fields...
|
||||
"category": "66e3431a3f23e684075aae9c"
|
||||
}
|
||||
"category": "66e3431a3f23e684075aae9c",
|
||||
},
|
||||
// { ... }
|
||||
],
|
||||
"hasNextPage": false
|
||||
}
|
||||
},
|
||||
// other fields...
|
||||
}
|
||||
```
|
||||
@@ -217,8 +213,7 @@ You can specify as many `joins` parameters as needed for the same or different j
|
||||
|
||||
### GraphQL
|
||||
|
||||
The GraphQL API supports the same query options as the local and REST APIs. You can specify the query options for each
|
||||
join field in your query.
|
||||
The GraphQL API supports the same query options as the local and REST APIs. You can specify the query options for each join field in your query.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -231,9 +226,9 @@ query {
|
||||
limit: 5
|
||||
where: {
|
||||
author: {
|
||||
equals: "66e3431a3f23e684075aaeb9"
|
||||
equals: "66e3431a3f23e684075aaeb9"
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
docs {
|
||||
title
|
||||
|
||||
@@ -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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
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 type { CollectionConfig } from 'payload'
|
||||
import { 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 type { CollectionConfig } from 'payload'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const ExampleCollection: CollectionConfig = {
|
||||
slug: 'example-collection',
|
||||
@@ -135,7 +135,7 @@ export const ExampleCollection: CollectionConfig = {
|
||||
`collections/ExampleCollection.ts`
|
||||
|
||||
```ts
|
||||
import type { CollectionConfig } from 'payload'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const ExampleCollection: CollectionConfig = {
|
||||
slug: 'example-collection',
|
||||
|
||||
@@ -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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
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 type { CollectionConfig } from 'payload'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const ExampleCollection: CollectionConfig = {
|
||||
slug: 'example-collection',
|
||||
|
||||
@@ -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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyPointField: Field = {
|
||||
// ...
|
||||
@@ -59,7 +59,7 @@ _\* An asterisk denotes that a property is required._
|
||||
`collections/ExampleCollection.ts`
|
||||
|
||||
```ts
|
||||
import type { CollectionConfig } from 'payload'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const ExampleCollection: CollectionConfig = {
|
||||
slug: 'example-collection',
|
||||
|
||||
@@ -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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
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 type { CollectionConfig } from 'payload'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const ExampleCollection: CollectionConfig = {
|
||||
slug: 'example-collection',
|
||||
|
||||
@@ -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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyRelationshipField: Field = {
|
||||
// ...
|
||||
@@ -159,7 +159,7 @@ called with an argument object with the following properties:
|
||||
## Example
|
||||
|
||||
```ts
|
||||
import type { CollectionConfig } from 'payload'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const ExampleCollection: CollectionConfig = {
|
||||
slug: 'example-collection',
|
||||
|
||||
@@ -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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyRichTextField: Field = {
|
||||
// ...
|
||||
|
||||
@@ -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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyRowField: Field = {
|
||||
// ...
|
||||
@@ -46,7 +46,7 @@ _\* An asterisk denotes that a property is required._
|
||||
`collections/ExampleCollection.ts`
|
||||
|
||||
```ts
|
||||
import type { CollectionConfig } from 'payload'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const ExampleCollection: CollectionConfig = {
|
||||
slug: 'example-collection',
|
||||
|
||||
@@ -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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
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 type { CollectionConfig } from 'payload'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const ExampleCollection: CollectionConfig = {
|
||||
slug: 'example-collection',
|
||||
@@ -156,7 +156,8 @@ You can import the existing Select component directly from Payload, then extend
|
||||
|
||||
```ts
|
||||
import * as React from 'react';
|
||||
import { SelectInput, useAuth, useField } from '@payloadcms/ui';
|
||||
import { SelectInput, useField } from 'payload/components/forms';
|
||||
import { useAuth } from 'payload/components/utilities';
|
||||
|
||||
type CustomSelectProps = {
|
||||
path: string;
|
||||
|
||||
@@ -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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyTabsField: Field = {
|
||||
// ...
|
||||
@@ -59,7 +59,7 @@ _\* An asterisk denotes that a property is required._
|
||||
`collections/ExampleCollection.ts`
|
||||
|
||||
```ts
|
||||
import type { CollectionConfig } from 'payload'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const ExampleCollection: CollectionConfig = {
|
||||
slug: 'example-collection',
|
||||
|
||||
@@ -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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
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 type { CollectionConfig } from 'payload'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const ExampleCollection: CollectionConfig = {
|
||||
slug: 'example-collection',
|
||||
|
||||
@@ -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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
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 type { CollectionConfig } from 'payload'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const ExampleCollection: CollectionConfig = {
|
||||
slug: 'example-collection',
|
||||
|
||||
@@ -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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyUIField: Field = {
|
||||
// ...
|
||||
@@ -44,7 +44,7 @@ _\* An asterisk denotes that a property is required._
|
||||
`collections/ExampleCollection.ts`
|
||||
|
||||
```ts
|
||||
import type { CollectionConfig } from 'payload'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const ExampleCollection: CollectionConfig = {
|
||||
slug: 'example-collection',
|
||||
|
||||
@@ -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'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyUploadField: Field = {
|
||||
// ...
|
||||
@@ -73,7 +73,7 @@ _\* An asterisk denotes that a property is required._
|
||||
`collections/ExampleCollection.ts`
|
||||
|
||||
```ts
|
||||
import type { CollectionConfig } from 'payload'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const ExampleCollection: CollectionConfig = {
|
||||
slug: 'example-collection',
|
||||
|
||||
@@ -54,12 +54,12 @@ To install a Database Adapter, you can run **one** of the following commands:
|
||||
|
||||
- To install the [MongoDB Adapter](../database/mongodb), run:
|
||||
```bash
|
||||
pnpm i @payloadcms/db-mongodb@beta
|
||||
pnpm i @payloadcms/db-mongodb
|
||||
```
|
||||
|
||||
- To install the [Postgres Adapter](../database/postgres), run:
|
||||
```bash
|
||||
pnpm i @payloadcms/db-postgres@beta
|
||||
pnpm i @payloadcms/db-postgres
|
||||
```
|
||||
|
||||
<Banner type="success">
|
||||
|
||||
@@ -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@beta -D
|
||||
pnpm add @payloadcms/graphql --save-dev
|
||||
```
|
||||
|
||||
Run the following command to generate the schema:
|
||||
|
||||
@@ -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](./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](/docs/beta/local-api/outside-nextjs).
|
||||
|
||||
## Local options available
|
||||
|
||||
|
||||
@@ -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@beta
|
||||
pnpm add @payloadcms/plugin-form-builder
|
||||
```
|
||||
|
||||
## 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](../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](https://payloadcms.com/docs/beta/email/overview).
|
||||
|
||||
```ts
|
||||
// payload.config.ts
|
||||
@@ -412,7 +412,7 @@ formBuilder({
|
||||
|
||||
## Email
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
### Email formatting
|
||||
|
||||
|
||||
@@ -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@beta
|
||||
pnpm add @payloadcms/plugin-nested-docs
|
||||
```
|
||||
|
||||
## 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 type { CollectionConfig } from 'payload'
|
||||
import { CollectionConfig } from 'payload'
|
||||
import { createParentField } from '@payloadcms/plugin-nested-docs/fields'
|
||||
import { createBreadcrumbsField } from '@payloadcms/plugin-nested-docs/fields'
|
||||
|
||||
|
||||
@@ -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@beta
|
||||
pnpm add @payloadcms/plugin-redirects
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
@@ -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@beta
|
||||
pnpm add @payloadcms/plugin-search
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
@@ -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@beta
|
||||
pnpm add @payloadcms/plugin-sentry
|
||||
```
|
||||
|
||||
## Sentry for Next.js setup
|
||||
|
||||
@@ -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@beta
|
||||
pnpm add @payloadcms/plugin-seo
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
@@ -276,10 +276,6 @@ 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:
|
||||
|
||||
@@ -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@beta
|
||||
pnpm add @payloadcms/plugin-stripe
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
@@ -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. 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.
|
||||
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.
|
||||
|
||||
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,19 +30,6 @@ 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:
|
||||
@@ -53,14 +40,6 @@ 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:
|
||||
|
||||
@@ -591,7 +591,7 @@ Each endpoint object needs to have:
|
||||
Example:
|
||||
|
||||
```ts
|
||||
import type { CollectionConfig } from 'payload'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
// a collection of 'orders' with an additional route for tracking details, reachable at /api/orders/:id/tracking
|
||||
export const Orders: CollectionConfig = {
|
||||
|
||||
@@ -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 type { CollectionConfig } from 'payload'
|
||||
import { CollectionConfig } from 'payload'
|
||||
import { slateEditor } from '@payloadcms/richtext-slate'
|
||||
|
||||
export const Pages: CollectionConfig = {
|
||||
|
||||
@@ -43,7 +43,7 @@ Every Payload Collection can opt-in to supporting Uploads by specifying the `upl
|
||||
</Banner>
|
||||
|
||||
```ts
|
||||
import type { CollectionConfig } from 'payload'
|
||||
import { 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 type { CollectionConfig } from 'payload'
|
||||
import { 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 type { CollectionConfig } from 'payload'
|
||||
import { 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 type { CollectionConfig } from 'payload'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const Media: CollectionConfig = {
|
||||
slug: 'media',
|
||||
|
||||
@@ -22,7 +22,7 @@ Payload offers additional storage adapters to handle file uploads. These adapter
|
||||
### Installation
|
||||
|
||||
```sh
|
||||
pnpm add @payloadcms/storage-vercel-blob@beta
|
||||
pnpm add @payloadcms/storage-vercel-blob
|
||||
```
|
||||
|
||||
### Usage
|
||||
@@ -71,7 +71,7 @@ export default buildConfig({
|
||||
### Installation
|
||||
|
||||
```sh
|
||||
pnpm add @payloadcms/storage-s3@beta
|
||||
pnpm add @payloadcms/storage-s3
|
||||
```
|
||||
|
||||
### 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@beta
|
||||
pnpm add @payloadcms/storage-azure
|
||||
```
|
||||
|
||||
### Usage
|
||||
@@ -168,7 +168,7 @@ export default buildConfig({
|
||||
### Installation
|
||||
|
||||
```sh
|
||||
pnpm add @payloadcms/storage-gcs@beta
|
||||
pnpm add @payloadcms/storage-gcs
|
||||
```
|
||||
|
||||
### Usage
|
||||
@@ -218,7 +218,7 @@ export default buildConfig({
|
||||
### Installation
|
||||
|
||||
```sh
|
||||
pnpm add @payloadcms/storage-uploadthing@beta
|
||||
pnpm add @payloadcms/storage-uploadthing
|
||||
```
|
||||
|
||||
### 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@beta`
|
||||
`pnpm add @payloadcms/plugin-cloud-storage`
|
||||
|
||||
### Usage
|
||||
|
||||
|
||||
26
package.json
26
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload-monorepo",
|
||||
"version": "3.0.0-beta.120",
|
||||
"version": "3.0.0-beta.115",
|
||||
"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:payload-cloud": "turbo build --filter \"@payloadcms/payload-cloud\"",
|
||||
"build:plugin-cloud": "turbo build --filter \"@payloadcms/plugin-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",
|
||||
"@next/bundle-analyzer": "15.0.0-canary.173",
|
||||
"@payloadcms/db-postgres": "workspace:*",
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@payloadcms/eslint-plugin": "workspace:*",
|
||||
"@payloadcms/live-preview-react": "workspace:*",
|
||||
"@playwright/test": "1.48.1",
|
||||
"@playwright/test": "1.46.0",
|
||||
"@sentry/nextjs": "^8.33.1",
|
||||
"@sentry/node": "^8.33.1",
|
||||
"@swc-node/register": "1.10.9",
|
||||
@@ -132,7 +132,7 @@
|
||||
"create-payload-app": "workspace:*",
|
||||
"cross-env": "7.0.3",
|
||||
"dotenv": "16.4.5",
|
||||
"drizzle-kit": "0.26.2",
|
||||
"drizzle-kit": "0.26.1",
|
||||
"drizzle-orm": "0.35.1",
|
||||
"escape-html": "^1.0.3",
|
||||
"execa": "5.1.1",
|
||||
@@ -145,15 +145,15 @@
|
||||
"lint-staged": "15.2.7",
|
||||
"minimist": "1.2.8",
|
||||
"mongodb-memory-server": "^9.0",
|
||||
"next": "15.0.0",
|
||||
"next": "15.0.0-canary.173",
|
||||
"open": "^10.1.0",
|
||||
"p-limit": "^5.0.0",
|
||||
"playwright": "1.48.1",
|
||||
"playwright-core": "1.48.1",
|
||||
"playwright": "1.46.0",
|
||||
"playwright-core": "1.46.0",
|
||||
"prettier": "3.3.3",
|
||||
"prompts": "2.4.2",
|
||||
"react": "19.0.0-rc-65a56d0e-20241020",
|
||||
"react-dom": "19.0.0-rc-65a56d0e-20241020",
|
||||
"react": "19.0.0-rc-3edc000d-20240926",
|
||||
"react-dom": "19.0.0-rc-3edc000d-20240926",
|
||||
"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.3"
|
||||
"typescript": "5.6.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.0.0 || ^19.0.0-rc-65a56d0e-20241020",
|
||||
"react-dom": "^19.0.0 || ^19.0.0-rc-65a56d0e-20241020"
|
||||
"react": "^19.0.0 || ^19.0.0-rc-3edc000d-20240926",
|
||||
"react-dom": "^19.0.0 || ^19.0.0-rc-3edc000d-20240926"
|
||||
},
|
||||
"packageManager": "pnpm@9.7.1",
|
||||
"engines": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-payload-app",
|
||||
"version": "3.0.0-beta.120",
|
||||
"version": "3.0.0-beta.115",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -54,7 +54,7 @@ function getEnvironmentPackageManager(): PackageManager {
|
||||
|
||||
async function commandExists(command: string): Promise<boolean> {
|
||||
try {
|
||||
await execa.command(process.platform === 'win32' ? `where ${command}` : `command -v ${command}`)
|
||||
await execa.command(`command -v ${command}`)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
|
||||
@@ -226,7 +226,7 @@ async function installDeps(projectDir: string, packageManager: PackageManager, d
|
||||
'payload',
|
||||
'@payloadcms/next',
|
||||
'@payloadcms/richtext-lexical',
|
||||
'@payloadcms/payload-cloud',
|
||||
'@payloadcms/plugin-cloud',
|
||||
].map((pkg) => `${pkg}@beta`)
|
||||
|
||||
packagesToInstall.push(`@payloadcms/db-${dbType}@beta`)
|
||||
|
||||
@@ -82,8 +82,8 @@ const vercelBlobStorageReplacement: StorageAdapterReplacement = {
|
||||
|
||||
const payloadCloudReplacement: StorageAdapterReplacement = {
|
||||
configReplacement: [' payloadCloudPlugin(),'],
|
||||
importReplacement: "import { payloadCloudPlugin } from '@payloadcms/payload-cloud'",
|
||||
packageName: '@payloadcms/payload-cloud',
|
||||
importReplacement: "import { payloadCloudPlugin } from '@payloadcms/plugin-cloud'",
|
||||
packageName: '@payloadcms/plugin-cloud',
|
||||
}
|
||||
|
||||
// Removes placeholders
|
||||
|
||||
@@ -18,7 +18,7 @@ const dbChoiceRecord: Record<DbType, DbChoice> = {
|
||||
},
|
||||
postgres: {
|
||||
dbConnectionPrefix: 'postgres://postgres:<password>@127.0.0.1:5432/',
|
||||
title: 'PostgreSQL',
|
||||
title: 'PostgreSQL (beta)',
|
||||
value: 'postgres',
|
||||
},
|
||||
sqlite: {
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
import fs from 'fs-extra'
|
||||
import path from 'path'
|
||||
|
||||
import type { CliArgs, DbType, ProjectTemplate } from '../types.js'
|
||||
import type { CliArgs, ProjectTemplate } from '../types.js'
|
||||
|
||||
import { debug, error } from '../utils/log.js'
|
||||
|
||||
/** Parse and swap .env.example values and write .env */
|
||||
export async function writeEnvFile(args: {
|
||||
cliArgs: CliArgs
|
||||
databaseType?: DbType
|
||||
databaseUri: string
|
||||
payloadSecret: string
|
||||
projectDir: string
|
||||
template?: ProjectTemplate
|
||||
}): Promise<void> {
|
||||
const { cliArgs, databaseType, databaseUri, payloadSecret, projectDir, template } = args
|
||||
const { cliArgs, databaseUri, payloadSecret, projectDir, template } = args
|
||||
|
||||
if (cliArgs['--dry-run']) {
|
||||
debug(`DRY RUN: .env file created`)
|
||||
@@ -42,7 +41,7 @@ export async function writeEnvFile(args: {
|
||||
}
|
||||
|
||||
const split = line.split('=')
|
||||
let key = split[0]
|
||||
const key = split[0]
|
||||
let value = split[1]
|
||||
|
||||
if (
|
||||
@@ -51,9 +50,6 @@ export async function writeEnvFile(args: {
|
||||
key === 'DATABASE_URI' ||
|
||||
key === 'POSTGRES_URL'
|
||||
) {
|
||||
if (databaseType === 'vercel-postgres') {
|
||||
key = 'POSTGRES_URL'
|
||||
}
|
||||
value = databaseUri
|
||||
}
|
||||
if (key === 'PAYLOAD_SECRET' || key === 'PAYLOAD_SECRET_KEY') {
|
||||
|
||||
@@ -180,7 +180,6 @@ export class Main {
|
||||
|
||||
await writeEnvFile({
|
||||
cliArgs: this.args,
|
||||
databaseType: dbDetails.type,
|
||||
databaseUri: dbDetails.dbUri,
|
||||
payloadSecret: generateSecret(),
|
||||
projectDir,
|
||||
@@ -223,7 +222,6 @@ export class Main {
|
||||
})
|
||||
await writeEnvFile({
|
||||
cliArgs: this.args,
|
||||
databaseType: dbDetails.type,
|
||||
databaseUri: dbDetails.dbUri,
|
||||
payloadSecret,
|
||||
projectDir,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "3.0.0-beta.120",
|
||||
"version": "3.0.0-beta.115",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -246,10 +246,6 @@ 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) {
|
||||
@@ -505,10 +501,6 @@ 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) {
|
||||
@@ -553,9 +545,6 @@ 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,
|
||||
@@ -573,9 +562,6 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
})
|
||||
} else {
|
||||
tab.fields.forEach((subField: Field) => {
|
||||
if (fieldIsVirtual(subField)) {
|
||||
return
|
||||
}
|
||||
const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[subField.type]
|
||||
|
||||
if (addFieldSchema) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { PaginateOptions } from 'mongoose'
|
||||
import type { Field, SanitizedConfig, Sort } from 'payload'
|
||||
import type { Field, SanitizedConfig } from 'payload'
|
||||
|
||||
import { getLocalizedSortProperty } from './getLocalizedSortProperty.js'
|
||||
|
||||
@@ -7,7 +7,7 @@ type Args = {
|
||||
config: SanitizedConfig
|
||||
fields: Field[]
|
||||
locale: string
|
||||
sort: Sort
|
||||
sort: string
|
||||
timestamps: boolean
|
||||
}
|
||||
|
||||
@@ -25,41 +25,32 @@ export const buildSortParam = ({
|
||||
sort,
|
||||
timestamps,
|
||||
}: Args): PaginateOptions['sort'] => {
|
||||
let sortProperty: string
|
||||
let sortDirection: SortDirection = 'desc'
|
||||
|
||||
if (!sort) {
|
||||
if (timestamps) {
|
||||
sort = '-createdAt'
|
||||
sortProperty = 'createdAt'
|
||||
} else {
|
||||
sort = '-id'
|
||||
sortProperty = '_id'
|
||||
}
|
||||
} else if (sort.indexOf('-') === 0) {
|
||||
sortProperty = sort.substring(1)
|
||||
} else {
|
||||
sortProperty = sort
|
||||
sortDirection = 'asc'
|
||||
}
|
||||
|
||||
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({
|
||||
if (sortProperty === 'id') {
|
||||
sortProperty = '_id'
|
||||
} else {
|
||||
sortProperty = getLocalizedSortProperty({
|
||||
config,
|
||||
fields,
|
||||
locale,
|
||||
segments: sortProperty.split('.'),
|
||||
})
|
||||
acc[localizedProperty] = sortDirection
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
|
||||
return sorting
|
||||
return { [sortProperty]: sortDirection }
|
||||
}
|
||||
|
||||
@@ -78,11 +78,7 @@ export const sanitizeQueryValue = ({
|
||||
return { operator: formattedOperator, val: undefined }
|
||||
}
|
||||
}
|
||||
} else if (Array.isArray(val) || (typeof val === 'string' && val.split(',').length > 1)) {
|
||||
if (typeof val === 'string') {
|
||||
formattedValue = createArrayFromCommaDelineated(val)
|
||||
}
|
||||
|
||||
} else if (Array.isArray(val)) {
|
||||
formattedValue = formattedValue.reduce((formattedValues, inVal) => {
|
||||
const newValues = [inVal]
|
||||
if (!hasCustomID) {
|
||||
|
||||
@@ -6,23 +6,12 @@ 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,
|
||||
joins,
|
||||
limit,
|
||||
locale,
|
||||
page,
|
||||
pagination,
|
||||
req = {} as PayloadRequest,
|
||||
sort: sortArg,
|
||||
where,
|
||||
},
|
||||
{ collection, limit, locale, page, pagination, req = {} as PayloadRequest, sort: sortArg, where },
|
||||
) {
|
||||
const VersionModel = this.versions[collection]
|
||||
const collectionConfig = this.payload.collections[collection].config
|
||||
@@ -100,29 +89,7 @@ export const queryDrafts: QueryDrafts = async function queryDrafts(
|
||||
paginationOptions.options.limit = limit
|
||||
}
|
||||
|
||||
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 result = await VersionModel.paginate(versionQuery, paginationOptions)
|
||||
const docs = JSON.parse(JSON.stringify(result.docs))
|
||||
|
||||
return {
|
||||
|
||||
@@ -15,8 +15,6 @@ 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 ({
|
||||
@@ -27,7 +25,6 @@ export const buildJoinAggregation = async ({
|
||||
limit,
|
||||
locale,
|
||||
query,
|
||||
versions,
|
||||
}: BuildJoinAggregationArgs): Promise<PipelineStage[] | undefined> => {
|
||||
if (Object.keys(collectionConfig.joins).length === 0 || joins === false) {
|
||||
return
|
||||
@@ -57,8 +54,8 @@ export const buildJoinAggregation = async ({
|
||||
const joinModel = adapter.collections[join.field.collection]
|
||||
|
||||
const {
|
||||
limit: limitJoin = join.field.defaultLimit ?? 10,
|
||||
sort: sortJoin = join.field.defaultSort || collectionConfig.defaultSort,
|
||||
limit: limitJoin = 10,
|
||||
sort: sortJoin,
|
||||
where: whereJoin,
|
||||
} = joins?.[join.schemaPath] || {}
|
||||
|
||||
@@ -66,7 +63,7 @@ export const buildJoinAggregation = async ({
|
||||
config: adapter.payload.config,
|
||||
fields: adapter.payload.collections[slug].config.fields,
|
||||
locale,
|
||||
sort: sortJoin,
|
||||
sort: sortJoin || collectionConfig.defaultSort,
|
||||
timestamps: true,
|
||||
})
|
||||
const sortProperty = Object.keys(sort)[0]
|
||||
@@ -93,7 +90,7 @@ export const buildJoinAggregation = async ({
|
||||
|
||||
if (adapter.payload.config.localization && locale === 'all') {
|
||||
adapter.payload.config.localization.localeCodes.forEach((code) => {
|
||||
const as = `${versions ? `version.${join.schemaPath}` : join.schemaPath}${code}`
|
||||
const as = `${join.schemaPath}${code}`
|
||||
|
||||
aggregate.push(
|
||||
{
|
||||
@@ -101,7 +98,7 @@ export const buildJoinAggregation = async ({
|
||||
as: `${as}.docs`,
|
||||
foreignField: `${join.field.on}${code}`,
|
||||
from: slug,
|
||||
localField: versions ? 'parent' : '_id',
|
||||
localField: '_id',
|
||||
pipeline,
|
||||
},
|
||||
},
|
||||
@@ -134,7 +131,7 @@ export const buildJoinAggregation = async ({
|
||||
} else {
|
||||
const localeSuffix =
|
||||
join.field.localized && adapter.payload.config.localization && locale ? `.${locale}` : ''
|
||||
const as = `${versions ? `version.${join.schemaPath}` : join.schemaPath}${localeSuffix}`
|
||||
const as = `${join.schemaPath}${localeSuffix}`
|
||||
|
||||
aggregate.push(
|
||||
{
|
||||
@@ -142,7 +139,7 @@ export const buildJoinAggregation = async ({
|
||||
as: `${as}.docs`,
|
||||
foreignField: `${join.field.on}${localeSuffix}`,
|
||||
from: slug,
|
||||
localField: versions ? 'parent' : '_id',
|
||||
localField: '_id',
|
||||
pipeline,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-postgres",
|
||||
"version": "3.0.0-beta.120",
|
||||
"version": "3.0.0-beta.115",
|
||||
"description": "The officially supported Postgres database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -43,14 +43,14 @@
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"prepack": "pnpm clean && pnpm turbo build",
|
||||
"prepublishOnly": "pnpm clean && pnpm turbo build --filter=./",
|
||||
"prepublishOnly": "pnpm clean && pnpm turbo build",
|
||||
"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.2",
|
||||
"drizzle-kit": "0.26.1",
|
||||
"drizzle-orm": "0.35.1",
|
||||
"pg": "8.11.3",
|
||||
"prompts": "2.4.2",
|
||||
|
||||
@@ -66,7 +66,7 @@ export const connect: Connect = async function connect(
|
||||
}
|
||||
|
||||
const logger = this.logger || false
|
||||
this.drizzle = drizzle({ client: this.pool, logger, schema: this.schema })
|
||||
this.drizzle = drizzle(this.pool, { logger, schema: this.schema })
|
||||
|
||||
if (!hotReload) {
|
||||
if (process.env.PAYLOAD_DROP_DATABASE === 'true') {
|
||||
|
||||
@@ -136,7 +136,6 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
|
||||
findGlobalVersions,
|
||||
findOne,
|
||||
findVersions,
|
||||
indexes: new Set<string>(),
|
||||
init,
|
||||
insert,
|
||||
migrate,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-sqlite",
|
||||
"version": "3.0.0-beta.120",
|
||||
"version": "3.0.0-beta.115",
|
||||
"description": "The officially supported SQLite database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -48,7 +48,7 @@
|
||||
"@libsql/client": "0.14.0",
|
||||
"@payloadcms/drizzle": "workspace:*",
|
||||
"console-table-printer": "2.11.2",
|
||||
"drizzle-kit": "0.26.2",
|
||||
"drizzle-kit": "0.26.1",
|
||||
"drizzle-orm": "0.35.1",
|
||||
"prompts": "2.4.2",
|
||||
"to-snake-case": "1.0.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ChainedMethods } from '@payloadcms/drizzle/types'
|
||||
|
||||
import { chainMethods } from '@payloadcms/drizzle'
|
||||
import { count } from 'drizzle-orm'
|
||||
import { count, sql } from 'drizzle-orm'
|
||||
|
||||
import type { CountDistinct, SQLiteAdapter } from './types.js'
|
||||
|
||||
@@ -22,7 +22,11 @@ export const countDistinct: CountDistinct = async function countDistinct(
|
||||
methods: chainedMethods,
|
||||
query: db
|
||||
.select({
|
||||
count: count(),
|
||||
count:
|
||||
joins.length > 0
|
||||
? sql`count
|
||||
(DISTINCT ${this.tables[tableName].id})`.mapWith(Number)
|
||||
: count(),
|
||||
})
|
||||
.from(this.tables[tableName])
|
||||
.where(where),
|
||||
|
||||
@@ -133,7 +133,6 @@ export function sqliteAdapter(args: Args): DatabaseAdapterObj<SQLiteAdapter> {
|
||||
findGlobalVersions,
|
||||
findOne,
|
||||
findVersions,
|
||||
indexes: new Set<string>(),
|
||||
init,
|
||||
insert,
|
||||
migrate,
|
||||
|
||||
@@ -70,6 +70,7 @@ 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,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { DrizzleAdapter } from '@payloadcms/drizzle/types'
|
||||
import type { Relation } from 'drizzle-orm'
|
||||
import type {
|
||||
AnySQLiteColumn,
|
||||
@@ -10,7 +9,7 @@ import type {
|
||||
} from 'drizzle-orm/sqlite-core'
|
||||
import type { Field, SanitizedJoins } from 'payload'
|
||||
|
||||
import { buildIndexName, createTableName } from '@payloadcms/drizzle'
|
||||
import { createTableName } from '@payloadcms/drizzle'
|
||||
import { relations, sql } from 'drizzle-orm'
|
||||
import {
|
||||
foreignKey,
|
||||
@@ -61,6 +60,7 @@ type Args = {
|
||||
disableRelsTableUnique?: boolean
|
||||
disableUnique: boolean
|
||||
fields: Field[]
|
||||
joins?: SanitizedJoins
|
||||
locales?: [string, ...string[]]
|
||||
rootRelationships?: Set<string>
|
||||
rootRelationsToBuild?: RelationMap
|
||||
@@ -94,6 +94,7 @@ export const buildTable = ({
|
||||
disableRelsTableUnique,
|
||||
disableUnique = false,
|
||||
fields,
|
||||
joins,
|
||||
locales,
|
||||
rootRelationships,
|
||||
rootRelationsToBuild,
|
||||
@@ -142,6 +143,7 @@ export const buildTable = ({
|
||||
disableUnique,
|
||||
fields,
|
||||
indexes,
|
||||
joins,
|
||||
locales,
|
||||
localesColumns,
|
||||
localesIndexes,
|
||||
@@ -414,25 +416,21 @@ export const buildTable = ({
|
||||
foreignColumns: [adapter.tables[formattedRelationTo].id],
|
||||
}).onDelete('cascade')
|
||||
|
||||
const indexColumns = [colName]
|
||||
const indexName = [colName]
|
||||
|
||||
const unique = !disableUnique && uniqueRelationships.has(relationTo)
|
||||
|
||||
if (unique) {
|
||||
indexColumns.push('path')
|
||||
indexName.push('path')
|
||||
}
|
||||
if (hasLocalizedRelationshipField) {
|
||||
indexColumns.push('locale')
|
||||
indexName.push('locale')
|
||||
}
|
||||
|
||||
const indexName = buildIndexName({
|
||||
name: `${relationshipsTableName}_${formattedRelationTo}_id`,
|
||||
adapter: adapter as unknown as DrizzleAdapter,
|
||||
})
|
||||
|
||||
relationExtraConfig[indexName] = createIndex({
|
||||
name: indexColumns,
|
||||
indexName,
|
||||
relationExtraConfig[`${relationTo}IdIdx`] = createIndex({
|
||||
name: indexName,
|
||||
columnName: `${formattedRelationTo}_id`,
|
||||
tableName: relationshipsTableName,
|
||||
unique,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,12 +3,13 @@ import type { AnySQLiteColumn } from 'drizzle-orm/sqlite-core'
|
||||
import { index, uniqueIndex } from 'drizzle-orm/sqlite-core'
|
||||
|
||||
type CreateIndexArgs = {
|
||||
indexName: string
|
||||
columnName: string
|
||||
name: string | string[]
|
||||
tableName: string
|
||||
unique?: boolean
|
||||
}
|
||||
|
||||
export const createIndex = ({ name, indexName, unique }: CreateIndexArgs) => {
|
||||
export const createIndex = ({ name, columnName, tableName, unique }: CreateIndexArgs) => {
|
||||
return (table: { [x: string]: AnySQLiteColumn }) => {
|
||||
let columns
|
||||
if (Array.isArray(name)) {
|
||||
@@ -20,8 +21,8 @@ export const createIndex = ({ name, indexName, unique }: CreateIndexArgs) => {
|
||||
columns = [table[name]]
|
||||
}
|
||||
if (unique) {
|
||||
return uniqueIndex(indexName).on(columns[0], ...columns.slice(1))
|
||||
return uniqueIndex(`${tableName}_${columnName}_idx`).on(columns[0], ...columns.slice(1))
|
||||
}
|
||||
return index(indexName).on(columns[0], ...columns.slice(1))
|
||||
return index(`${tableName}_${columnName}_idx`).on(columns[0], ...columns.slice(1))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
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,
|
||||
@@ -44,6 +42,7 @@ 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>
|
||||
@@ -83,6 +82,7 @@ export const traverseFields = ({
|
||||
fields,
|
||||
forceLocalized,
|
||||
indexes,
|
||||
joins,
|
||||
locales,
|
||||
localesColumns,
|
||||
localesIndexes,
|
||||
@@ -164,15 +164,10 @@ export const traverseFields = ({
|
||||
}
|
||||
adapter.fieldConstraints[rootTableName][`${columnName}_idx`] = constraintValue
|
||||
}
|
||||
|
||||
const indexName = buildIndexName({
|
||||
name: `${newTableName}_${columnName}`,
|
||||
adapter: adapter as unknown as DrizzleAdapter,
|
||||
})
|
||||
|
||||
targetIndexes[indexName] = createIndex({
|
||||
targetIndexes[`${newTableName}_${field.name}Idx`] = createIndex({
|
||||
name: field.localized ? [fieldName, '_locale'] : fieldName,
|
||||
indexName,
|
||||
columnName,
|
||||
tableName: newTableName,
|
||||
unique,
|
||||
})
|
||||
}
|
||||
@@ -667,6 +662,7 @@ export const traverseFields = ({
|
||||
fields: field.fields,
|
||||
forceLocalized,
|
||||
indexes,
|
||||
joins,
|
||||
locales,
|
||||
localesColumns,
|
||||
localesIndexes,
|
||||
@@ -722,6 +718,7 @@ export const traverseFields = ({
|
||||
fields: field.fields,
|
||||
forceLocalized: field.localized,
|
||||
indexes,
|
||||
joins,
|
||||
locales,
|
||||
localesColumns,
|
||||
localesIndexes,
|
||||
@@ -778,6 +775,7 @@ export const traverseFields = ({
|
||||
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
|
||||
forceLocalized,
|
||||
indexes,
|
||||
joins,
|
||||
locales,
|
||||
localesColumns,
|
||||
localesIndexes,
|
||||
@@ -834,6 +832,7 @@ export const traverseFields = ({
|
||||
fields: field.fields,
|
||||
forceLocalized,
|
||||
indexes,
|
||||
joins,
|
||||
locales,
|
||||
localesColumns,
|
||||
localesIndexes,
|
||||
@@ -931,6 +930,30 @@ 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
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-vercel-postgres",
|
||||
"version": "3.0.0-beta.120",
|
||||
"version": "3.0.0-beta.115",
|
||||
"description": "Vercel Postgres adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -50,7 +50,7 @@
|
||||
"@payloadcms/drizzle": "workspace:*",
|
||||
"@vercel/postgres": "^0.9.0",
|
||||
"console-table-printer": "2.11.2",
|
||||
"drizzle-kit": "0.26.2",
|
||||
"drizzle-kit": "0.26.1",
|
||||
"drizzle-orm": "0.35.1",
|
||||
"pg": "8.11.3",
|
||||
"prompts": "2.4.2",
|
||||
|
||||
@@ -26,8 +26,7 @@ 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({
|
||||
client: this.poolOptions ? new VercelPool(this.poolOptions) : sql,
|
||||
this.drizzle = drizzle(this.poolOptions ? new VercelPool(this.poolOptions) : sql, {
|
||||
logger,
|
||||
schema: this.schema,
|
||||
})
|
||||
|
||||
@@ -90,7 +90,6 @@ export function vercelPostgresAdapter(args: Args = {}): DatabaseAdapterObj<Verce
|
||||
fieldConstraints: {},
|
||||
getMigrationTemplate,
|
||||
idType: postgresIDType,
|
||||
indexes: new Set<string>(),
|
||||
initializing,
|
||||
localesSuffix: args.localesSuffix || '_locales',
|
||||
logger: args.logger,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/drizzle",
|
||||
"version": "3.0.0-beta.120",
|
||||
"version": "3.0.0-beta.115",
|
||||
"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.35.1",
|
||||
"drizzle-orm": "0.34.1-1f15bfd",
|
||||
"prompts": "2.4.2",
|
||||
"to-snake-case": "1.0.0",
|
||||
"uuid": "9.0.0"
|
||||
|
||||
@@ -21,7 +21,7 @@ export const find: Find = async function find(
|
||||
},
|
||||
) {
|
||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||
const sort = sortArg !== undefined && sortArg !== null ? sortArg : collectionConfig.defaultSort
|
||||
const sort = typeof sortArg === 'string' ? sortArg : collectionConfig.defaultSort
|
||||
|
||||
const tableName = this.tableNameMap.get(toSnakeCase(collectionConfig.slug))
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ type BuildFindQueryArgs = {
|
||||
joins?: BuildQueryJoinAliases
|
||||
locale?: string
|
||||
tableName: string
|
||||
versions?: boolean
|
||||
}
|
||||
|
||||
export type Result = {
|
||||
@@ -35,7 +34,6 @@ export const buildFindManyArgs = ({
|
||||
joins = [],
|
||||
locale,
|
||||
tableName,
|
||||
versions,
|
||||
}: BuildFindQueryArgs): Record<string, unknown> => {
|
||||
const result: Result = {
|
||||
extras: {},
|
||||
@@ -99,7 +97,6 @@ export const buildFindManyArgs = ({
|
||||
tablePath: '',
|
||||
topLevelArgs: result,
|
||||
topLevelTableName: tableName,
|
||||
versions,
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
@@ -14,7 +14,6 @@ type Args = {
|
||||
adapter: DrizzleAdapter
|
||||
fields: Field[]
|
||||
tableName: string
|
||||
versions?: boolean
|
||||
} & Omit<FindArgs, 'collection'>
|
||||
|
||||
export const findMany = async function find({
|
||||
@@ -29,7 +28,6 @@ export const findMany = async function find({
|
||||
skip,
|
||||
sort,
|
||||
tableName,
|
||||
versions,
|
||||
where: whereArg,
|
||||
}: Args) {
|
||||
const db = adapter.sessions[await req.transactionID]?.db || adapter.drizzle
|
||||
@@ -59,9 +57,9 @@ export const findMany = async function find({
|
||||
|
||||
const selectDistinctMethods: ChainedMethods = []
|
||||
|
||||
if (orderBy) {
|
||||
if (orderBy?.order && orderBy?.column) {
|
||||
selectDistinctMethods.push({
|
||||
args: [() => orderBy.map(({ column, order }) => order(column))],
|
||||
args: [orderBy.order(orderBy.column)],
|
||||
method: 'orderBy',
|
||||
})
|
||||
}
|
||||
@@ -73,7 +71,6 @@ export const findMany = async function find({
|
||||
joinQuery,
|
||||
joins,
|
||||
tableName,
|
||||
versions,
|
||||
})
|
||||
|
||||
selectDistinctMethods.push({ args: [offset], method: 'offset' })
|
||||
@@ -114,7 +111,7 @@ export const findMany = async function find({
|
||||
} else {
|
||||
findManyArgs.limit = limit
|
||||
findManyArgs.offset = offset
|
||||
findManyArgs.orderBy = () => orderBy.map(({ column, order }) => order(column))
|
||||
findManyArgs.orderBy = orderBy.order(orderBy.column)
|
||||
|
||||
if (where) {
|
||||
findManyArgs.where = where
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { DBQueryConfig } from 'drizzle-orm'
|
||||
import type { LibSQLDatabase } from 'drizzle-orm/libsql'
|
||||
import type { Field, JoinQuery } from 'payload'
|
||||
|
||||
@@ -25,7 +26,6 @@ type TraverseFieldArgs = {
|
||||
tablePath: string
|
||||
topLevelArgs: Record<string, unknown>
|
||||
topLevelTableName: string
|
||||
versions?: boolean
|
||||
}
|
||||
|
||||
export const traverseFields = ({
|
||||
@@ -42,7 +42,6 @@ export const traverseFields = ({
|
||||
tablePath,
|
||||
topLevelArgs,
|
||||
topLevelTableName,
|
||||
versions,
|
||||
}: TraverseFieldArgs) => {
|
||||
fields.forEach((field) => {
|
||||
if (fieldIsVirtual(field)) {
|
||||
@@ -100,7 +99,6 @@ export const traverseFields = ({
|
||||
tablePath: tabTablePath,
|
||||
topLevelArgs,
|
||||
topLevelTableName,
|
||||
versions,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -225,7 +223,6 @@ export const traverseFields = ({
|
||||
tablePath: `${tablePath}${toSnakeCase(field.name)}_`,
|
||||
topLevelArgs,
|
||||
topLevelTableName,
|
||||
versions,
|
||||
})
|
||||
|
||||
break
|
||||
@@ -236,158 +233,87 @@ export const traverseFields = ({
|
||||
if (joinQuery === false) {
|
||||
break
|
||||
}
|
||||
|
||||
const {
|
||||
limit: limitArg = field.defaultLimit ?? 10,
|
||||
sort = field.defaultSort,
|
||||
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 joinRelsCollectionTableName = `${joinCollectionTableName}${adapter.relationshipsSuffix}`
|
||||
|
||||
const db = adapter.drizzle as LibSQLDatabase
|
||||
if (field.localized) {
|
||||
joinLocalesCollectionTableName = joinRelsCollectionTableName
|
||||
joinTableName = adapter.tableNameMap.get(toSnakeCase(field.collection))
|
||||
}
|
||||
const joinTable = `${joinTableName}${adapter.relationshipsSuffix}`
|
||||
|
||||
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({
|
||||
const joins: BuildQueryJoinAliases = [
|
||||
{
|
||||
type: 'innerJoin',
|
||||
condition: and(
|
||||
eq(adapter.tables[joinTable].parent, adapter.tables[joinTableName].id),
|
||||
eq(
|
||||
adapter.tables[joinLocalesCollectionTableName]._parentID,
|
||||
adapter.tables[joinCollectionTableName].id,
|
||||
),
|
||||
eq(
|
||||
adapter.tables[joinLocalesCollectionTableName][foreignColumn],
|
||||
currentIDColumn,
|
||||
sql.raw(`"${joinTable}"."${topLevelTableName}_id"`),
|
||||
adapter.tables[currentTableName].id,
|
||||
),
|
||||
eq(adapter.tables[joinTable].path, field.on),
|
||||
),
|
||||
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,
|
||||
)
|
||||
table: adapter.tables[joinTable],
|
||||
},
|
||||
]
|
||||
|
||||
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 { orderBy, where: subQueryWhere } = buildQuery({
|
||||
adapter,
|
||||
fields,
|
||||
joins,
|
||||
locale,
|
||||
sort,
|
||||
tableName: joinCollectionTableName,
|
||||
where: {},
|
||||
})
|
||||
})
|
||||
|
||||
if (limit !== 0) {
|
||||
chainedMethods.push({
|
||||
args: [limit],
|
||||
method: 'limit',
|
||||
})
|
||||
}
|
||||
const chainedMethods: ChainedMethods = []
|
||||
|
||||
const db = adapter.drizzle as LibSQLDatabase
|
||||
|
||||
const subQuery = chainMethods({
|
||||
methods: chainedMethods,
|
||||
query: db
|
||||
.select({
|
||||
id: adapter.tables[joinCollectionTableName].id,
|
||||
...(joinLocalesCollectionTableName && {
|
||||
locale:
|
||||
adapter.tables[joinLocalesCollectionTableName].locale ||
|
||||
adapter.tables[joinLocalesCollectionTableName]._locale,
|
||||
}),
|
||||
joins.forEach(({ type, condition, table }) => {
|
||||
chainedMethods.push({
|
||||
args: [table, condition],
|
||||
method: type ?? 'leftJoin',
|
||||
})
|
||||
.from(adapter.tables[joinCollectionTableName])
|
||||
.where(subQueryWhere)
|
||||
.orderBy(() => orderBy.map(({ column, order }) => order(column))),
|
||||
})
|
||||
})
|
||||
|
||||
const columnName = `${path.replaceAll('.', '_')}${field.name}`
|
||||
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),
|
||||
})
|
||||
|
||||
const jsonObjectSelect = field.localized
|
||||
? sql.raw(
|
||||
`'_parentID', "id", '_locale', "${adapter.tables[joinLocalesCollectionTableName].locale ? 'locale' : '_locale'}"`,
|
||||
)
|
||||
: sql.raw(`'id', "id"`)
|
||||
const columnName = `${path.replaceAll('.', '_')}${field.name}`
|
||||
|
||||
if (adapter.name === 'sqlite') {
|
||||
currentArgs.extras[columnName] = sql`
|
||||
const jsonObjectSelect = field.localized
|
||||
? sql.raw(`'_parentID', "id", '_locale', "locale"`)
|
||||
: sql.raw(`'id', "id"`)
|
||||
|
||||
if (adapter.name === 'sqlite') {
|
||||
currentArgs.extras[columnName] = sql`
|
||||
COALESCE((
|
||||
SELECT json_group_array(json_object(${jsonObjectSelect}))
|
||||
FROM (
|
||||
@@ -395,8 +321,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 (
|
||||
@@ -404,8 +330,41 @@ 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
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV
|
||||
const globalConfig: SanitizedGlobalConfig = this.payload.globals.config.find(
|
||||
({ slug }) => slug === global,
|
||||
)
|
||||
const sort = sortArg !== undefined && sortArg !== null ? sortArg : '-createdAt'
|
||||
const sort = typeof sortArg === 'string' ? sortArg : '-createdAt'
|
||||
|
||||
const tableName = this.tableNameMap.get(
|
||||
`_${toSnakeCase(globalConfig.slug)}${this.versionsSuffix}`,
|
||||
|
||||
@@ -22,7 +22,7 @@ export const findVersions: FindVersions = async function findVersions(
|
||||
},
|
||||
) {
|
||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||
const sort = sortArg !== undefined && sortArg !== null ? sortArg : collectionConfig.defaultSort
|
||||
const sort = typeof sortArg === 'string' ? sortArg : collectionConfig.defaultSort
|
||||
|
||||
const tableName = this.tableNameMap.get(
|
||||
`_${toSnakeCase(collectionConfig.slug)}${this.versionsSuffix}`,
|
||||
|
||||
@@ -32,7 +32,6 @@ 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'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { count } from 'drizzle-orm'
|
||||
import { count, sql } from 'drizzle-orm'
|
||||
|
||||
import type { ChainedMethods, TransactionPg } from '../types.js'
|
||||
import type { BasePostgresAdapter, CountDistinct } from './types.js'
|
||||
@@ -22,7 +22,11 @@ export const countDistinct: CountDistinct = async function countDistinct(
|
||||
methods: chainedMethods,
|
||||
query: (db as TransactionPg)
|
||||
.select({
|
||||
count: count(),
|
||||
count:
|
||||
joins.length > 0
|
||||
? sql`count
|
||||
(DISTINCT ${this.tables[tableName].id})`.mapWith(Number)
|
||||
: count(),
|
||||
})
|
||||
.from(this.tables[tableName])
|
||||
.where(where),
|
||||
|
||||
@@ -27,9 +27,8 @@ type Args = {
|
||||
schemaName?: string
|
||||
}
|
||||
export const createDatabase = async function (this: BasePostgresAdapter, args: Args = {}) {
|
||||
// POSTGRES_URL - default Vercel env
|
||||
const connectionString =
|
||||
this.poolOptions?.connectionString ?? process.env.POSTGRES_URL ?? process.env.DATABASE_URL
|
||||
// DATABASE_URL - default Vercel env
|
||||
const connectionString = this.poolOptions?.connectionString ?? process.env.DATABASE_URL
|
||||
let managementClientConfig: ClientConfig = {}
|
||||
let dbName = args.name
|
||||
const schemaName = this.schemaName || 'public'
|
||||
@@ -55,7 +54,7 @@ export const createDatabase = async function (this: BasePostgresAdapter, args: A
|
||||
}
|
||||
|
||||
// import pg only when createDatabase is used
|
||||
const pg = await import('pg').then((mod) => mod.default)
|
||||
const pg = await import('pg')
|
||||
|
||||
const managementClient = new pg.Client(managementClientConfig)
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ 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,
|
||||
|
||||
@@ -30,7 +30,6 @@ 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'
|
||||
@@ -50,6 +49,7 @@ type Args = {
|
||||
disableRelsTableUnique?: boolean
|
||||
disableUnique: boolean
|
||||
fields: Field[]
|
||||
joins?: SanitizedJoins
|
||||
rootRelationships?: Set<string>
|
||||
rootRelationsToBuild?: RelationMap
|
||||
rootTableIDColType?: string
|
||||
@@ -82,6 +82,7 @@ export const buildTable = ({
|
||||
disableRelsTableUnique = false,
|
||||
disableUnique = false,
|
||||
fields,
|
||||
joins,
|
||||
rootRelationships,
|
||||
rootRelationsToBuild,
|
||||
rootTableIDColType,
|
||||
@@ -131,6 +132,7 @@ export const buildTable = ({
|
||||
disableUnique,
|
||||
fields,
|
||||
indexes,
|
||||
joins,
|
||||
localesColumns,
|
||||
localesIndexes,
|
||||
newTableName: tableName,
|
||||
@@ -387,25 +389,21 @@ export const buildTable = ({
|
||||
foreignColumns: [adapter.tables[formattedRelationTo].id],
|
||||
}).onDelete('cascade')
|
||||
|
||||
const indexColumns = [colName]
|
||||
const indexName = [colName]
|
||||
|
||||
const unique = !disableUnique && uniqueRelationships.has(relationTo)
|
||||
|
||||
if (unique) {
|
||||
indexColumns.push('path')
|
||||
indexName.push('path')
|
||||
}
|
||||
if (hasLocalizedRelationshipField) {
|
||||
indexColumns.push('locale')
|
||||
indexName.push('locale')
|
||||
}
|
||||
|
||||
const indexName = buildIndexName({
|
||||
name: `${relationshipsTableName}_${formattedRelationTo}_id`,
|
||||
adapter,
|
||||
})
|
||||
|
||||
relationExtraConfig[indexName] = createIndex({
|
||||
name: indexColumns,
|
||||
indexName,
|
||||
relationExtraConfig[`${relationTo}IdIdx`] = createIndex({
|
||||
name: indexName,
|
||||
columnName: `${formattedRelationTo}_id`,
|
||||
tableName: relationshipsTableName,
|
||||
unique,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,12 +3,13 @@ import { index, uniqueIndex } from 'drizzle-orm/pg-core'
|
||||
import type { GenericColumn } from '../types.js'
|
||||
|
||||
type CreateIndexArgs = {
|
||||
indexName: string
|
||||
columnName: string
|
||||
name: string | string[]
|
||||
tableName: string
|
||||
unique?: boolean
|
||||
}
|
||||
|
||||
export const createIndex = ({ name, indexName, unique }: CreateIndexArgs) => {
|
||||
export const createIndex = ({ name, columnName, tableName, unique }: CreateIndexArgs) => {
|
||||
return (table: { [x: string]: GenericColumn }) => {
|
||||
let columns
|
||||
if (Array.isArray(name)) {
|
||||
@@ -20,8 +21,8 @@ export const createIndex = ({ name, indexName, unique }: CreateIndexArgs) => {
|
||||
columns = [table[name]]
|
||||
}
|
||||
if (unique) {
|
||||
return uniqueIndex(indexName).on(columns[0], ...columns.slice(1))
|
||||
return uniqueIndex(`${tableName}_${columnName}_idx`).on(columns[0], ...columns.slice(1))
|
||||
}
|
||||
return index(indexName).on(columns[0], ...columns.slice(1))
|
||||
return index(`${tableName}_${columnName}_idx`).on(columns[0], ...columns.slice(1))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ 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'
|
||||
@@ -50,6 +49,7 @@ 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,6 +88,7 @@ export const traverseFields = ({
|
||||
fields,
|
||||
forceLocalized,
|
||||
indexes,
|
||||
joins,
|
||||
localesColumns,
|
||||
localesIndexes,
|
||||
newTableName,
|
||||
@@ -168,12 +169,10 @@ export const traverseFields = ({
|
||||
}
|
||||
adapter.fieldConstraints[rootTableName][`${columnName}_idx`] = constraintValue
|
||||
}
|
||||
|
||||
const indexName = buildIndexName({ name: `${newTableName}_${columnName}`, adapter })
|
||||
|
||||
targetIndexes[indexName] = createIndex({
|
||||
targetIndexes[`${newTableName}_${field.name}Idx`] = createIndex({
|
||||
name: field.localized ? [fieldName, '_locale'] : fieldName,
|
||||
indexName,
|
||||
columnName,
|
||||
tableName: newTableName,
|
||||
unique,
|
||||
})
|
||||
}
|
||||
@@ -670,6 +669,7 @@ export const traverseFields = ({
|
||||
fields: field.fields,
|
||||
forceLocalized,
|
||||
indexes,
|
||||
joins,
|
||||
localesColumns,
|
||||
localesIndexes,
|
||||
newTableName,
|
||||
@@ -724,6 +724,7 @@ export const traverseFields = ({
|
||||
fields: field.fields,
|
||||
forceLocalized: field.localized,
|
||||
indexes,
|
||||
joins,
|
||||
localesColumns,
|
||||
localesIndexes,
|
||||
newTableName: `${parentTableName}_${columnName}`,
|
||||
@@ -779,6 +780,7 @@ export const traverseFields = ({
|
||||
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
|
||||
forceLocalized,
|
||||
indexes,
|
||||
joins,
|
||||
localesColumns,
|
||||
localesIndexes,
|
||||
newTableName,
|
||||
@@ -834,6 +836,7 @@ export const traverseFields = ({
|
||||
fields: field.fields,
|
||||
forceLocalized,
|
||||
indexes,
|
||||
joins,
|
||||
localesColumns,
|
||||
localesIndexes,
|
||||
newTableName,
|
||||
@@ -930,6 +933,30 @@ 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
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
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 })
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Field, Sort } from 'payload'
|
||||
import type { Field } from 'payload'
|
||||
|
||||
import { asc, desc } from 'drizzle-orm'
|
||||
|
||||
@@ -13,7 +13,7 @@ type Args = {
|
||||
joins: BuildQueryJoinAliases
|
||||
locale?: string
|
||||
selectFields: Record<string, GenericColumn>
|
||||
sort?: Sort
|
||||
sort?: string
|
||||
tableName: string
|
||||
}
|
||||
|
||||
@@ -29,55 +29,55 @@ export const buildOrderBy = ({
|
||||
sort,
|
||||
tableName,
|
||||
}: Args): BuildQueryResult['orderBy'] => {
|
||||
const orderBy: BuildQueryResult['orderBy'] = []
|
||||
|
||||
if (!sort) {
|
||||
const createdAt = adapter.tables[tableName]?.createdAt
|
||||
if (createdAt) {
|
||||
sort = '-createdAt'
|
||||
} else {
|
||||
sort = '-id'
|
||||
}
|
||||
const orderBy: BuildQueryResult['orderBy'] = {
|
||||
column: null,
|
||||
order: null,
|
||||
}
|
||||
|
||||
if (typeof sort === 'string') {
|
||||
sort = [sort]
|
||||
}
|
||||
if (sort) {
|
||||
let sortPath
|
||||
|
||||
for (const sortItem of sort) {
|
||||
let sortProperty: string
|
||||
let sortDirection: 'asc' | 'desc'
|
||||
if (sortItem[0] === '-') {
|
||||
sortProperty = sortItem.substring(1)
|
||||
sortDirection = 'desc'
|
||||
if (sort[0] === '-') {
|
||||
sortPath = sort.substring(1)
|
||||
orderBy.order = desc
|
||||
} else {
|
||||
sortProperty = sortItem
|
||||
sortDirection = 'asc'
|
||||
sortPath = sort
|
||||
orderBy.order = asc
|
||||
}
|
||||
|
||||
try {
|
||||
const { columnName: sortTableColumnName, table: sortTable } = getTableColumnFromPath({
|
||||
adapter,
|
||||
collectionPath: sortProperty,
|
||||
collectionPath: sortPath,
|
||||
fields,
|
||||
joins,
|
||||
locale,
|
||||
pathSegments: sortProperty.replace(/__/g, '.').split('.'),
|
||||
pathSegments: sortPath.replace(/__/g, '.').split('.'),
|
||||
selectFields,
|
||||
tableName,
|
||||
value: sortProperty,
|
||||
useAlias: true,
|
||||
value: sortPath,
|
||||
})
|
||||
if (sortTable?.[sortTableColumnName]) {
|
||||
orderBy.push({
|
||||
column: sortTable[sortTableColumnName],
|
||||
order: sortDirection === 'asc' ? asc : desc,
|
||||
})
|
||||
|
||||
selectFields[sortTableColumnName] = sortTable[sortTableColumnName]
|
||||
}
|
||||
orderBy.column = 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
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user