Compare commits
41 Commits
v3.0.0-bet
...
v3.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd592cb3a2 | ||
|
|
6d066c2ba4 | ||
|
|
1dc428823a | ||
|
|
c8da9b148c | ||
|
|
2021028d64 | ||
|
|
2ea56fe0f8 | ||
|
|
ea16119af7 | ||
|
|
97837f0708 | ||
|
|
e734d51760 | ||
|
|
5655266daa | ||
|
|
f9e5573c1e | ||
|
|
e823051a8e | ||
|
|
49df61d9ec | ||
|
|
4704c8db2a | ||
|
|
a64f37e014 | ||
|
|
55c6ce92b0 | ||
|
|
2ecbcee378 | ||
|
|
70f2e1698a | ||
|
|
8ba39aa5ca | ||
|
|
f8c79d2f84 | ||
|
|
128d72185d | ||
|
|
abc786d864 | ||
|
|
791fa68820 | ||
|
|
2796d2100f | ||
|
|
cbac62a36f | ||
|
|
b5afc62e14 | ||
|
|
0627272d6c | ||
|
|
51f1c8e7e8 | ||
|
|
09ad6e4280 | ||
|
|
c129c10f0f | ||
|
|
904ec0160e | ||
|
|
b2814eb67c | ||
|
|
c405e5958f | ||
|
|
a35979f74e | ||
|
|
863abc0e6b | ||
|
|
b9cf6c73a9 | ||
|
|
f2b3305cb0 | ||
|
|
b3e8ddf302 | ||
|
|
b6d4bc4d37 | ||
|
|
83ad453a89 | ||
|
|
4e6a7d489c |
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@@ -292,6 +292,7 @@ jobs:
|
||||
- access-control
|
||||
- admin__e2e__1
|
||||
- admin__e2e__2
|
||||
- admin-root
|
||||
- auth
|
||||
- field-error-states
|
||||
- fields-relationship
|
||||
@@ -390,6 +391,7 @@ jobs:
|
||||
# job-summary: true
|
||||
|
||||
app-build-with-packed:
|
||||
if: false # Disable until package resolution in tgzs can be figured out
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
|
||||
|
||||
16
.github/workflows/pr-title.yml
vendored
16
.github/workflows/pr-title.yml
vendored
@@ -38,6 +38,7 @@ jobs:
|
||||
db-\*
|
||||
db-mongodb
|
||||
db-postgres
|
||||
db-sqlite
|
||||
email-nodemailer
|
||||
eslint
|
||||
graphql
|
||||
@@ -101,3 +102,18 @@ jobs:
|
||||
with:
|
||||
header: pr-title-lint-error
|
||||
delete: true
|
||||
|
||||
label-pr-on-open:
|
||||
name: label-pr-on-open
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Tag with main branch with v2
|
||||
if: github.event.action == 'opened' && github.event.pull_request.base.ref == 'main'
|
||||
uses: actions-ecosystem/action-add-labels@v1
|
||||
with:
|
||||
labels: v2
|
||||
- name: Tag with beta branch with v3
|
||||
if: github.event.action == 'opened' && github.event.pull_request.base.ref == 'beta'
|
||||
uses: actions-ecosystem/action-add-labels@v1
|
||||
with:
|
||||
labels: v3
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -22,6 +22,10 @@ meta_shared.json
|
||||
# Ignore test directory media folder/files
|
||||
/media
|
||||
test/media
|
||||
*payloadtests.db
|
||||
*payloadtests.db-journal
|
||||
*payloadtests.db-shm
|
||||
*payloadtests.db-wal
|
||||
/versions
|
||||
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/node,macos,windows,webstorm,sublimetext,visualstudiocode
|
||||
|
||||
6
.idea/payload.iml
generated
6
.idea/payload.iml
generated
@@ -75,8 +75,12 @@
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/ui/.swc" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/ui/.turbo" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/ui/dist" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/drizzle/.turbo" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/drizzle/dist" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/db-sqlite/.turbo" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/db-sqlite/dist" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
</module>
|
||||
|
||||
@@ -266,12 +266,10 @@ export const myField: Field = {
|
||||
|
||||
_For details on how to build Custom Components, see [Building Custom Components](./components#building-custom-components)._
|
||||
|
||||
All Label Components receive the following props:
|
||||
Custom Label Components receive all [Field Component](#the-field-component) props, plus the following props:
|
||||
|
||||
| Property | Description |
|
||||
| -------------- | ---------------------------------------------------------------- |
|
||||
| **`label`** | Label value provided in field, it can be used with i18n. |
|
||||
| **`required`** | The `admin.required` property defined in the [Field Config](../fields/overview). |
|
||||
| **`schemaPath`** | The path to the field in the schema. Similar to `path`, but without dynamic indices. |
|
||||
|
||||
<Banner type="success">
|
||||
@@ -279,6 +277,36 @@ All Label Components receive the following props:
|
||||
All [Custom Server Components](./components) receive the `payload` and `i18n` properties by default. See [Building Custom Components](./components#building-custom-components) for more details.
|
||||
</Banner>
|
||||
|
||||
#### TypeScript
|
||||
|
||||
When building Custom Error Components, you can import the component props to ensure type safety in your component. There is an explicit type for the Error Component, one for every [Field Type](../fields/overview). The convention is to append `ErrorComponent` to the type of field, i.e. `TextFieldErrorComponent`.
|
||||
|
||||
```tsx
|
||||
import type {
|
||||
ArrayFieldLabelComponent,
|
||||
BlocksFieldLabelComponent,
|
||||
CheckboxFieldLabelComponent,
|
||||
CodeFieldLabelComponent,
|
||||
CollapsibleFieldLabelComponent,
|
||||
DateFieldLabelComponent,
|
||||
EmailFieldLabelComponent,
|
||||
GroupFieldLabelComponent,
|
||||
HiddenFieldLabelComponent,
|
||||
JSONFieldLabelComponent,
|
||||
NumberFieldLabelComponent,
|
||||
PointFieldLabelComponent,
|
||||
RadioFieldLabelComponent,
|
||||
RelationshipFieldLabelComponent,
|
||||
RichTextFieldLabelComponent,
|
||||
RowFieldLabelComponent,
|
||||
SelectFieldLabelComponent,
|
||||
TabsFieldLabelComponent,
|
||||
TextFieldLabelComponent,
|
||||
TextareaFieldLabelComponent,
|
||||
UploadFieldLabelComponent
|
||||
} from 'payload'
|
||||
```
|
||||
|
||||
### The Error Component
|
||||
|
||||
The Error Component is rendered when a field fails validation. It is typically displayed beneath the field input in a visually-compelling style.
|
||||
@@ -301,7 +329,7 @@ export const myField: Field = {
|
||||
|
||||
_For details on how to build Custom Components, see [Building Custom Components](./components#building-custom-components)._
|
||||
|
||||
All Error Components receive the following props:
|
||||
Custom Error Components receive all [Field Component](#the-field-component) props, plus the following props:
|
||||
|
||||
| Property | Description |
|
||||
| --------------- | ------------------------------------------------------------- |
|
||||
@@ -312,6 +340,36 @@ All Error Components receive the following props:
|
||||
All [Custom Server Components](./components) receive the `payload` and `i18n` properties by default. See [Building Custom Components](./components#building-custom-components) for more details.
|
||||
</Banner>
|
||||
|
||||
#### TypeScript
|
||||
|
||||
When building Custom Error Components, you can import the component props to ensure type safety in your component. There is an explicit type for the Error Component, one for every [Field Type](../fields/overview). The convention is to append `ErrorComponent` to the type of field, i.e. `TextFieldErrorComponent`.
|
||||
|
||||
```tsx
|
||||
import type {
|
||||
ArrayFieldErrorComponent,
|
||||
BlocksFieldErrorComponent,
|
||||
CheckboxFieldErrorComponent,
|
||||
CodeFieldErrorComponent,
|
||||
CollapsibleFieldErrorComponent,
|
||||
DateFieldErrorComponent,
|
||||
EmailFieldErrorComponent,
|
||||
GroupFieldErrorComponent,
|
||||
HiddenFieldErrorComponent,
|
||||
JSONFieldErrorComponent,
|
||||
NumberFieldErrorComponent,
|
||||
PointFieldErrorComponent,
|
||||
RadioFieldErrorComponent,
|
||||
RelationshipFieldErrorComponent,
|
||||
RichTextFieldErrorComponent,
|
||||
RowFieldErrorComponent,
|
||||
SelectFieldErrorComponent,
|
||||
TabsFieldErrorComponent,
|
||||
TextFieldErrorComponent,
|
||||
TextareaFieldErrorComponent,
|
||||
UploadFieldErrorComponent
|
||||
} from 'payload'
|
||||
```
|
||||
|
||||
### The Description Property
|
||||
|
||||
Field Descriptions are used to provide additional information to the editor about a field, such as special instructions. Their placement varies from field to field, but typically are displayed with subtle style differences beneath the field inputs.
|
||||
@@ -406,7 +464,7 @@ export const MyCollectionConfig: SanitizedCollectionConfig = {
|
||||
|
||||
_For details on how to build a Custom Description, see [Building Custom Components](./components#building-custom-components)._
|
||||
|
||||
All Description Components receive the following props:
|
||||
Custom Description Components receive all [Field Component](#the-field-component) props, plus the following props:
|
||||
|
||||
| Property | Description |
|
||||
| -------------- | ---------------------------------------------------------------- |
|
||||
@@ -417,6 +475,36 @@ All Description Components receive the following props:
|
||||
All [Custom Server Components](./components) receive the `payload` and `i18n` properties by default. See [Building Custom Components](./components#building-custom-components) for more details.
|
||||
</Banner>
|
||||
|
||||
#### TypeScript
|
||||
|
||||
When building Custom Description Components, you can import the component props to ensure type safety in your component. There is an explicit type for the Description Component, one for every [Field Type](../fields/overview). The convention is to append `DescriptionComponent` to the type of field, i.e. `TextFieldDescriptionComponent`.
|
||||
|
||||
```tsx
|
||||
import type {
|
||||
ArrayFieldDescriptionComponent,
|
||||
BlocksFieldDescriptionComponent,
|
||||
CheckboxFieldDescriptionComponent,
|
||||
CodeFieldDescriptionComponent,
|
||||
CollapsibleFieldDescriptionComponent,
|
||||
DateFieldDescriptionComponent,
|
||||
EmailFieldDescriptionComponent,
|
||||
GroupFieldDescriptionComponent,
|
||||
HiddenFieldDescriptionComponent,
|
||||
JSONFieldDescriptionComponent,
|
||||
NumberFieldDescriptionComponent,
|
||||
PointFieldDescriptionComponent,
|
||||
RadioFieldDescriptionComponent,
|
||||
RelationshipFieldDescriptionComponent,
|
||||
RichTextFieldDescriptionComponent,
|
||||
RowFieldDescriptionComponent,
|
||||
SelectFieldDescriptionComponent,
|
||||
TabsFieldDescriptionComponent,
|
||||
TextFieldDescriptionComponent,
|
||||
TextareaFieldDescriptionComponent,
|
||||
UploadFieldDescriptionComponent
|
||||
} from 'payload'
|
||||
```
|
||||
|
||||
### afterInput and beforeInput
|
||||
|
||||
With these properties you can add multiple components _before_ and _after_ the input element, as their name suggests. This is useful when you need to render additional elements alongside the field without replacing the entire field component.
|
||||
|
||||
@@ -169,19 +169,32 @@ The following options are available:
|
||||
|
||||
| Option | Default route | Description |
|
||||
| ------------------ | ----------------------- | ------------------------------------- |
|
||||
| `admin` | `/admin` | The Admin Panel itself. |
|
||||
| `admin` | `/admin` | The Admin Panel itself. |
|
||||
| `api` | `/api` | The [REST API](../rest-api/overview) base path. |
|
||||
| `graphQL` | `/graphql` | The [GraphQL API](../graphql/overview) base path. |
|
||||
| `graphQLPlayground`| `/graphql-playground` | The GraphQL Playground. |
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Warning:</strong>
|
||||
Changing Root-level Routes _after_ your project was generated will also require you to manually update the corresponding directories in your project. For example, changing `routes.admin` will require you to rename the `(payload)/admin` directory in your project to match the new route. [More details](#project-structure).
|
||||
</Banner>
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Tip:</strong>
|
||||
You can easily add _new_ routes to the Admin Panel through the `endpoints` property of the Payload Config. See [Custom Endpoints](../rest-api/overview#custom-endpoints) for more information.
|
||||
You can easily add _new_ routes to the Admin Panel through [Custom Endpoints](../rest-api/overview#custom-endpoints) and [Custom Views](./views).
|
||||
</Banner>
|
||||
|
||||
#### Customizing Root-level Routes
|
||||
|
||||
You can change the Root-level Routes as needed, such as to mount the Admin Panel at the root of your application.
|
||||
|
||||
Changing Root-level Routes also requires a change to [Project Structure](#project-structure) to match the new route. For example, if you set `routes.admin` to `/`, you would need to completely remove the `admin` directory from the project structure:
|
||||
|
||||
```plaintext
|
||||
app/
|
||||
├─ (payload)/
|
||||
├── [[...segments]]/
|
||||
├──── ...
|
||||
```
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Note:</strong>
|
||||
If you set Root-level Routes _before_ auto-generating the Admin Panel, your [Project Structure](#project-structure) will already be set up correctly.
|
||||
</Banner>
|
||||
|
||||
### Admin-level Routes
|
||||
|
||||
@@ -37,6 +37,7 @@ export default buildConfig({
|
||||
| `disableIndexHints` | Set to true to disable hinting to MongoDB to use 'id' as index. This is currently done when counting documents for pagination, as it increases the speed of the count function used in that query. Disabling this optimization might fix some problems with AWS DocumentDB. Defaults to false |
|
||||
| `migrationDir` | Customize the directory that migrations are stored. |
|
||||
| `transactionOptions` | An object with configuration properties used in [transactions](https://www.mongodb.com/docs/manual/core/transactions/) or `false` which will disable the use of transactions. |
|
||||
| `collation` | Enable language-specific string comparison with customizable options. Available on MongoDB 3.4+. Defaults locale to "en". Example: `{ strength: 3 }`. For a full list of collation options and their definitions, see the [MongoDB documentation](https://www.mongodb.com/docs/manual/reference/collation/). |
|
||||
|
||||
## Access to Mongoose models
|
||||
|
||||
|
||||
@@ -33,18 +33,18 @@ export default buildConfig({
|
||||
|
||||
## Options
|
||||
|
||||
| Option | Description |
|
||||
|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `pool` \* | [Pool connection options](https://orm.drizzle.team/docs/quick-postgresql/node-postgres) that will be passed to Drizzle and `node-postgres`. |
|
||||
| `push` | Disable Drizzle's [`db push`](https://orm.drizzle.team/kit-docs/overview#prototyping-with-db-push) in development mode. By default, `push` is enabled for development mode only. |
|
||||
| `migrationDir` | Customize the directory that migrations are stored. |
|
||||
| `logger` | The instance of the logger to be passed to drizzle. By default Payload's will be used. |
|
||||
| `schemaName` | A string for the postgres schema to use, defaults to 'public'. |
|
||||
| `localesSuffix` | A string appended to the end of table names for storing localized fields. Default is '_locales'. |
|
||||
| `relationshipsSuffix` | A string appended to the end of table names for storing relationships. Default is '_rels'. |
|
||||
| `versionsSuffix` | A string appended to the end of table names for storing versions. Defaults to '_v'. |
|
||||
|
||||
|
||||
| Option | Description |
|
||||
|-----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `pool` \* | [Pool connection options](https://orm.drizzle.team/docs/quick-postgresql/node-postgres) that will be passed to Drizzle and `node-postgres`. |
|
||||
| `push` | Disable Drizzle's [`db push`](https://orm.drizzle.team/kit-docs/overview#prototyping-with-db-push) in development mode. By default, `push` is enabled for development mode only. |
|
||||
| `migrationDir` | Customize the directory that migrations are stored. |
|
||||
| `logger` | The instance of the logger to be passed to drizzle. By default Payload's will be used. |
|
||||
| `schemaName` (experimental) | A string for the postgres schema to use, defaults to 'public'. |
|
||||
| `idType` | A string of 'serial', or 'uuid' that is used for the data type given to id columns. |
|
||||
| `transactionOptions` | A PgTransactionConfig object for transactions, or set to `false` to disable using transactions. [More details](https://orm.drizzle.team/docs/transactions) |
|
||||
| `localesSuffix` | A string appended to the end of table names for storing localized fields. Default is '_locales'. |
|
||||
| `relationshipsSuffix` | A string appended to the end of table names for storing relationships. Default is '_rels'. |
|
||||
| `versionsSuffix` | A string appended to the end of table names for storing versions. Defaults to '_v'. |
|
||||
|
||||
## Access to Drizzle
|
||||
|
||||
|
||||
81
docs/database/sqlite.mdx
Normal file
81
docs/database/sqlite.mdx
Normal file
@@ -0,0 +1,81 @@
|
||||
---
|
||||
title: SQLite
|
||||
label: SQLite
|
||||
order: 60
|
||||
desc: Payload supports SQLite through an officially supported Drizzle Database Adapter.
|
||||
keywords: SQLite, documentation, typescript, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
To use Payload with SQLite, install the package `@payloadcms/db-sqlite`. It leverages Drizzle ORM and `libSQL` to interact with a SQLite database that you provide.
|
||||
|
||||
It automatically manages changes to your database for you in development mode, and exposes a full suite of migration controls for you to leverage in order to keep other database environments in sync with your schema. DDL transformations are automatically generated.
|
||||
|
||||
To configure Payload to use SQLite, pass the `sqliteAdapter` to your Payload Config as follows:
|
||||
|
||||
```ts
|
||||
import { sqliteAdapter } from '@payloadcms/db-sqlite'
|
||||
|
||||
export default buildConfig({
|
||||
// Your config goes here
|
||||
collections: [
|
||||
// Collections go here
|
||||
],
|
||||
// Configure the SQLite adapter here
|
||||
db: sqliteAdapter({
|
||||
// SQLite-specific arguments go here.
|
||||
// `client.url` is required.
|
||||
client: {
|
||||
url: process.env.DATABASE_URL,
|
||||
authToken: process.env.DATABASE_AUTH_TOKEN,
|
||||
}
|
||||
}),
|
||||
})
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
| Option | Description |
|
||||
|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `client` \* | [Client connection options](https://orm.drizzle.team/docs/get-started-sqlite#turso) that will be passed to `createClient` from `@libsql/client`. |
|
||||
| `push` | Disable Drizzle's [`db push`](https://orm.drizzle.team/kit-docs/overview#prototyping-with-db-push) in development mode. By default, `push` is enabled for development mode only. |
|
||||
| `migrationDir` | Customize the directory that migrations are stored. |
|
||||
| `logger` | The instance of the logger to be passed to drizzle. By default Payload's will be used. |
|
||||
| `transactionOptions` | A SQLiteTransactionConfig object for transactions, or set to `false` to disable using transactions. [More details](https://orm.drizzle.team/docs/transactions) |
|
||||
| `localesSuffix` | A string appended to the end of table names for storing localized fields. Default is '_locales'. |
|
||||
| `relationshipsSuffix` | A string appended to the end of table names for storing relationships. Default is '_rels'. |
|
||||
| `versionsSuffix` | A string appended to the end of table names for storing versions. Defaults to '_v'. |
|
||||
|
||||
|
||||
|
||||
## Access to Drizzle
|
||||
|
||||
After Payload is initialized, this adapter will expose the full power of Drizzle to you for use if you need it.
|
||||
|
||||
You can access Drizzle as follows:
|
||||
|
||||
```text
|
||||
payload.db.drizzle
|
||||
```
|
||||
|
||||
## Tables and relations
|
||||
|
||||
In addition to exposing Drizzle directly, all of the tables and Drizzle relations are exposed for you via the `payload.db` property as well.
|
||||
|
||||
- Tables - `payload.db.tables`
|
||||
- Relations - `payload.db.relations`
|
||||
|
||||
## Prototyping in development mode
|
||||
|
||||
Drizzle exposes two ways to work locally in development mode.
|
||||
|
||||
The first is [`db push`](https://orm.drizzle.team/kit-docs/overview#prototyping-with-db-push), which automatically pushes changes you make to your Payload Config (and therefore, Drizzle schema) to your database so you don't have to manually migrate every time you change your Payload Config. This only works in development mode, and should not be mixed with manually running [`migrate`](/docs/database/migrations) commands.
|
||||
|
||||
You will be warned if any changes that you make will entail data loss while in development mode. Push is enabled by default, but you can opt out if you'd like.
|
||||
|
||||
Alternatively, you can disable `push` and rely solely on migrations to keep your local database in sync with your Payload Config.
|
||||
|
||||
## Migration workflows
|
||||
|
||||
In SQLite, migrations are a fundamental aspect of working with Payload and you should become familiar with how they work.
|
||||
|
||||
For more information about migrations, [click here](/docs/beta/database/migrations#when-to-run-migrations).
|
||||
@@ -68,3 +68,7 @@ The following functions can be used for managing transactions:
|
||||
`payload.db.beginTransaction` - Starts a new session and returns a transaction ID for use in other Payload Local API calls.
|
||||
`payload.db.commitTransaction` - Takes the identifier for the transaction, finalizes any changes.
|
||||
`payload.db.rollbackTransaction` - Takes the identifier for the transaction, discards any changes.
|
||||
|
||||
## Disabling Transactions
|
||||
|
||||
If you wish to disable transactions entirely, you can do so by passing `false` as the `transactionOptions` in your database adapter configuration. All the official Payload database adapters support this option.
|
||||
|
||||
@@ -2,14 +2,17 @@
|
||||
title: Array Field
|
||||
label: Array
|
||||
order: 20
|
||||
desc: Array fields are intended for sets of repeating fields, that you define. Learn how to use array fields, see examples and options.
|
||||
desc: Array Fields are intended for sets of repeating fields, that you define. Learn how to use Array Fields, see examples and options.
|
||||
keywords: array, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Array field type is used when you need to have a set of "repeating" fields. It stores an array
|
||||
of objects containing the fields that you define. Its uses can be simple or highly complex.
|
||||
</Banner>
|
||||
The Array Field is used when you need to have a set of "repeating" [Fields](./overview). It stores an array of objects containing fields that you define. These fields can be of any type, including other arrays to achieve infinitely nested structures.
|
||||
|
||||
Arrays are useful for many different types of content from simple to complex, such as:
|
||||
|
||||
- A "slider" with an image ([upload field](/docs/fields/upload)) and a caption ([text field](/docs/fields/text))
|
||||
- Navigational structures where editors can specify nav items containing pages ([relationship field](/docs/fields/relationship)), an "open in new tab" [checkbox field](/docs/fields/checkbox)
|
||||
- Event agenda "timeslots" where you need to specify start & end time ([date field](/docs/fields/date)), label ([text field](/docs/fields/text)), and Learn More page [relationship](/docs/fields/relationship)
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/array.png"
|
||||
@@ -18,13 +21,23 @@ keywords: array, fields, config, configuration, documentation, Content Managemen
|
||||
caption="Admin Panel screenshot of an Array field with two Rows"
|
||||
/>
|
||||
|
||||
**Example uses:**
|
||||
To create an Array Field, set the `type` to `array` in your [Field Config](./overview):
|
||||
|
||||
- A "slider" with an image ([upload field](/docs/fields/upload)) and a caption ([text field](/docs/fields/text))
|
||||
- Navigational structures where editors can specify nav items containing pages ([relationship field](/docs/fields/relationship)), an "open in new tab" [checkbox field](/docs/fields/checkbox)
|
||||
- Event agenda "timeslots" where you need to specify start & end time ([date field](/docs/fields/date)), label ([text field](/docs/fields/text)), and Learn More page [relationship](/docs/fields/relationship)
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
## Config
|
||||
export const MyArrayField: Field = {
|
||||
// ...
|
||||
// highlight-start
|
||||
type: 'array',
|
||||
fields: [
|
||||
// ...
|
||||
],
|
||||
// highlight-end
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -42,7 +55,7 @@ keywords: array, fields, config, configuration, documentation, Content Managemen
|
||||
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this Array will be kept, so there is no need to specify each nested field as `localized`. |
|
||||
| **`required`** | Require this field to have a value. |
|
||||
| **`labels`** | Customize the row labels appearing in the Admin dashboard. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). |
|
||||
| **`dbName`** | Custom table name for the field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. |
|
||||
@@ -50,9 +63,22 @@ keywords: array, fields, config, configuration, documentation, Content Managemen
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Admin Config
|
||||
## Admin Options
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following properties:
|
||||
The customize the appearance and behavior of the Array Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyArrayField: Field = {
|
||||
// ...
|
||||
admin: { // highlight-line
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The Array Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
|
||||
|
||||
| Option | Description |
|
||||
| ------------------------- | -------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -62,7 +88,7 @@ In addition to the default [field admin config](/docs/fields/overview#admin-conf
|
||||
|
||||
## Example
|
||||
|
||||
`collections/ExampleCollection.ts`
|
||||
In this example, we have an Array Field called `slider` that contains a set of fields for a simple image slider. Each row in the array has a `title`, `image`, and `caption`. We also customize the row label to display the title if it exists, or a default label if it doesn't.
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
@@ -2,16 +2,17 @@
|
||||
title: Blocks Field
|
||||
label: Blocks
|
||||
order: 30
|
||||
desc: The Blocks field type is a great layout build and can be used to construct any flexible content model. Learn how to use Block fields, see examples and options.
|
||||
desc: The Blocks Field is a great layout build and can be used to construct any flexible content model. Learn how to use Block Fields, see examples and options.
|
||||
keywords: blocks, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Blocks field type is <strong>incredibly powerful</strong> and can be used as a{' '}
|
||||
<em>layout builder</em> as well as to define any other flexible content model you can think of. It
|
||||
stores an array of objects, where each object must match the shape of one of your provided block
|
||||
configs.
|
||||
</Banner>
|
||||
The Blocks Field is <strong>incredibly powerful</strong>, storing an array of objects based on the fields that your define, where each item in the array is a "block" with its own unique schema.
|
||||
|
||||
Blocks are a great way to create a flexible content model that can be used to build a wide variety of content types, including:
|
||||
|
||||
- A layout builder tool that grants editors to design highly customizable page or post layouts. Blocks could include configs such as `Quote`, `CallToAction`, `Slider`, `Content`, `Gallery`, or others.
|
||||
- A form builder tool where available block configs might be `Text`, `Select`, or `Checkbox`.
|
||||
- Virtual event agenda "timeslots" where a timeslot could either be a `Break`, a `Presentation`, or a `BreakoutSession`.
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/blocks.png"
|
||||
@@ -20,13 +21,23 @@ keywords: blocks, fields, config, configuration, documentation, Content Manageme
|
||||
caption="Admin Panel screenshot of add Blocks drawer view"
|
||||
/>
|
||||
|
||||
**Example uses:**
|
||||
To add a Blocks Field, set the `type` to `blocks` in your [Field Config](./overview):
|
||||
|
||||
- A layout builder tool that grants editors to design highly customizable page or post layouts. Blocks could include configs such as `Quote`, `CallToAction`, `Slider`, `Content`, `Gallery`, or others.
|
||||
- A form builder tool where available block configs might be `Text`, `Select`, or `Checkbox`.
|
||||
- Virtual event agenda "timeslots" where a timeslot could either be a `Break`, a `Presentation`, or a `BreakoutSession`.
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
## Field config
|
||||
export const MyBlocksField: Field = {
|
||||
// ...
|
||||
// highlight-start
|
||||
type: 'blocks',
|
||||
blocks: [
|
||||
// ...
|
||||
],
|
||||
// highlight-end
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -44,22 +55,35 @@ keywords: blocks, fields, config, configuration, documentation, Content Manageme
|
||||
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this field will be kept, so there is no need to specify each nested field as `localized`. |
|
||||
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
|
||||
| **`labels`** | Customize the block row labels appearing in the Admin dashboard. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
|
||||
| **`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._
|
||||
|
||||
## Admin Config
|
||||
## Admin Options
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following properties:
|
||||
The customize the appearance and behavior of the Blocks Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyBlocksField: Field = {
|
||||
// ...
|
||||
admin: { // highlight-line
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The Blocks Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
|
||||
|
||||
| Option | Description |
|
||||
| ------------------- | ---------------------------------- |
|
||||
| **`initCollapsed`** | Set the initial collapsed state |
|
||||
| **`isSortable`** | Disable order sorting by setting this value to `false` |
|
||||
|
||||
## Block configs
|
||||
## Block Configs
|
||||
|
||||
Blocks are defined as separate configs of their own.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ desc: Checkbox field types allow the developer to save a boolean value in the da
|
||||
keywords: checkbox, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>The Checkbox field type saves a boolean in the database.</Banner>
|
||||
The Checkbox Field saves a boolean in the database.
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/checkbox.png"
|
||||
@@ -15,7 +15,18 @@ keywords: checkbox, fields, config, configuration, documentation, Content Manage
|
||||
caption="Admin Panel screenshot of Checkbox field with Text field below"
|
||||
/>
|
||||
|
||||
## Config
|
||||
To add a Checkbox Field, set the `type` to `checkbox` in your [Field Config](./overview):
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyCheckboxField: Field = {
|
||||
// ...
|
||||
type: 'checkbox', // highlight-line
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -30,7 +41,7 @@ keywords: checkbox, fields, config, configuration, documentation, Content Manage
|
||||
| **`defaultValue`** | Provide data to be used for this field's default value, will default to false if field is also `required`. [More](/docs/fields/overview#default-values) |
|
||||
| **`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. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
|
||||
| **`admin`** | Admin-specific configuration. [More details](../admin/fields#admin-options). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
|
||||
@@ -38,7 +49,7 @@ _\* An asterisk denotes that a property is required._
|
||||
|
||||
## Example
|
||||
|
||||
`collections/ExampleCollection.ts`
|
||||
Here is an example of a Checkbox Field in a Collection:
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
@@ -7,21 +7,27 @@ desc: The Code field type will store any string in the Database. Learn how to us
|
||||
keywords: code, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Code field type saves a string in the database, but provides the Admin Panel with a code
|
||||
editor styled interface.
|
||||
</Banner>
|
||||
The Code Field saves a string in the database, but provides the [Admin Panel](../admin/overview) with a code editor styled interface.
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/code.png"
|
||||
srcDark="https://payloadcms.com/images/docs/fields/code-dark.png"
|
||||
alt="Shows a Code field in the Payload Admin Panel"
|
||||
caption="Admin Panel screenshot of a Code field"
|
||||
caption="This field is using the `monaco-react` editor syntax highlighting."
|
||||
/>
|
||||
|
||||
This field uses the `monaco-react` editor syntax highlighting.
|
||||
To add a Code Field, set the `type` to `code` in your [Field Config](./overview):
|
||||
|
||||
## Config
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyBlocksField: Field = {
|
||||
// ...
|
||||
type: 'code', // highlight-line
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -39,15 +45,28 @@ This field uses the `monaco-react` editor syntax highlighting.
|
||||
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
|
||||
| **`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. See below for [more detail](#admin-config). |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-options). |
|
||||
| **`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._
|
||||
|
||||
## Admin Config
|
||||
## Admin Options
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following properties:
|
||||
The customize the appearance and behavior of the Code Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyCodeField: Field = {
|
||||
// ...
|
||||
admin: { // highlight-line
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The Code Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
|
||||
|
||||
| Option | Description |
|
||||
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
@@ -6,10 +6,7 @@ desc: With the Collapsible field, you can place fields within a collapsible layo
|
||||
keywords: row, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Collapsible field is presentational-only and only affects the Admin Panel. By using it, you
|
||||
can place fields within a nice layout component that can be collapsed / expanded.
|
||||
</Banner>
|
||||
The Collapsible Field is presentational-only and only affects the Admin Panel. By using it, you can place fields within a nice layout component that can be collapsed / expanded.
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/collapsible.png"
|
||||
@@ -18,20 +15,49 @@ keywords: row, fields, config, configuration, documentation, Content Management
|
||||
caption="Admin Panel screenshot of a Collapsible field"
|
||||
/>
|
||||
|
||||
## Config
|
||||
To add a Collapsible Field, set the `type` to `collapsible` in your [Field Config](./overview):
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyCollapsibleField: Field = {
|
||||
// ...
|
||||
// highlight-start
|
||||
type: 'collapsible',
|
||||
fields: [
|
||||
// ...
|
||||
],
|
||||
// highlight-end
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`label`** \* | A label to render within the header of the collapsible component. This can be a string, function or react component. Function/components receive `({ data, path })` as args. |
|
||||
| **`fields`** \* | Array of field types to nest within this Collapsible. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Admin Config
|
||||
## Admin Options
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following properties:
|
||||
The customize the appearance and behavior of the Collapsible Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyCollapsibleField: Field = {
|
||||
// ...
|
||||
admin: { // highlight-line
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The Collapsible Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
|
||||
|
||||
| Option | Description |
|
||||
| ------------------- | ------------------------------- |
|
||||
|
||||
@@ -6,21 +6,27 @@ desc: The Date field type stores a Date in the database. Learn how to use and cu
|
||||
keywords: date, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Date field type saves a Date in the database and provides the Admin Panel with a customizable
|
||||
time picker interface.
|
||||
</Banner>
|
||||
The Date Field saves a Date in the database and provides the [Admin Panel](../admin/overview) with a customizable time picker interface.
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/date.png"
|
||||
srcDark="https://payloadcms.com/images/docs/fields/date-dark.png"
|
||||
alt="Shows a Date field in the Payload Admin Panel"
|
||||
caption="Admin Panel screenshot of a Date field"
|
||||
caption="This field is using the `react-datepicker` component for UI."
|
||||
/>
|
||||
|
||||
This field uses [`react-datepicker`](https://www.npmjs.com/package/react-datepicker) for the Admin Panel component.
|
||||
To add a Date Field, set the `type` to `date` in your [Field Config](./overview):
|
||||
|
||||
## Config
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyDateField: Field = {
|
||||
// ...
|
||||
type: 'date', // highlight-line
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -35,15 +41,28 @@ This field uses [`react-datepicker`](https://www.npmjs.com/package/react-datepic
|
||||
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
|
||||
| **`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. See below for [more detail](#admin-config). |
|
||||
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
|
||||
| **`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._
|
||||
|
||||
## Admin Config
|
||||
## Admin Options
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can customize the following fields that will adjust how the component displays in the Admin Panel via the `date` property.
|
||||
The customize the appearance and behavior of the Date Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyDateField: Field = {
|
||||
// ...
|
||||
admin: { // highlight-line
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The Date Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
|
||||
|
||||
| Property | Description |
|
||||
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
@@ -6,7 +6,7 @@ desc: The Email field enforces that the value provided is a valid email address.
|
||||
keywords: email, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>The Email field enforces that the value provided is a valid email address.</Banner>
|
||||
The Email Field enforces that the value provided is a valid email address.
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/email.png"
|
||||
@@ -15,7 +15,18 @@ keywords: email, fields, config, configuration, documentation, Content Managemen
|
||||
caption="Admin Panel screenshot of an Email field"
|
||||
/>
|
||||
|
||||
## Config
|
||||
To create an Email Field, set the `type` to `email` in your [Field Config](./overview):
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyEmailField: Field = {
|
||||
// ...
|
||||
type: 'email', // highlight-line
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -31,23 +42,33 @@ keywords: email, fields, config, configuration, documentation, Content Managemen
|
||||
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
|
||||
| **`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. See below for [more detail](#admin-config). |
|
||||
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
|
||||
| **`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._
|
||||
|
||||
## Admin config
|
||||
## Admin Options
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), this field type allows for the following `admin` properties:
|
||||
The customize the appearance and behavior of the Email Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
|
||||
|
||||
**`placeholder`**
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
Set this property to define a placeholder string for the field.
|
||||
export const MyEmailField: Field = {
|
||||
// ...
|
||||
admin: { // highlight-line
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**`autoComplete`**
|
||||
The Email Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
|
||||
|
||||
Set this property to a string that will be used for browser autocomplete.
|
||||
| Property | Description |
|
||||
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`placeholder`** | Set this property to define a placeholder string for the field. |
|
||||
| **`autoComplete`** | Set this property to a string that will be used for browser autocomplete. |
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -6,10 +6,7 @@ desc: The Group field allows other fields to be nested under a common property.
|
||||
keywords: group, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Group field allows fields to be nested under a common property name. It also groups fields
|
||||
together visually in the Admin Panel.
|
||||
</Banner>
|
||||
The Group Field allows [Fields](./overview) to be nested under a common property name. It also groups fields together visually in the [Admin Panel](../admin/overview).
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/group.png"
|
||||
@@ -18,7 +15,23 @@ keywords: group, fields, config, configuration, documentation, Content Managemen
|
||||
caption="Admin Panel screenshot of a Group field"
|
||||
/>
|
||||
|
||||
## Config
|
||||
To add a Group Field, set the `type` to `group` in your [Field Config](./overview):
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyGroupField: Field = {
|
||||
// ...
|
||||
// highlight-start
|
||||
type: 'group',
|
||||
fields: [
|
||||
// ...
|
||||
],
|
||||
// highlight-end
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -32,20 +45,33 @@ keywords: group, fields, config, configuration, documentation, Content Managemen
|
||||
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
|
||||
| **`defaultValue`** | Provide an object of data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
|
||||
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this Group will be kept, so there is no need to specify each nested field as `localized`. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). |
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Admin config
|
||||
## Admin Options
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), the Group allows for the following admin property:
|
||||
The customize the appearance and behavior of the Group Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
|
||||
|
||||
**`hideGutter`**
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
Set this property to `true` to hide this field's gutter within the Admin Panel. The field gutter is rendered as a vertical line and padding, but often if this field is nested within a Group, Block, or Array, you may want to hide the gutter.
|
||||
export const MyGroupField: Field = {
|
||||
// ...
|
||||
admin: { // highlight-line
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The Group Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
|
||||
|
||||
| Option | Description |
|
||||
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`hideGutter`** | Set this property to `true` to hide this field's gutter within the Admin Panel. The field gutter is rendered as a vertical line and padding, but often if this field is nested within a Group, Block, or Array, you may want to hide the gutter. |
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -7,21 +7,27 @@ desc: The JSON field type will store any string in the Database. Learn how to us
|
||||
keywords: json, jsonSchema, schema, validation, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The JSON field type saves actual JSON in the database, which differs from the Code field that
|
||||
saves the value as a string in the database.
|
||||
</Banner>
|
||||
The JSON Field saves actual JSON in the database, which differs from the Code field that saves the value as a string in the database.
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/json.png"
|
||||
srcDark="https://payloadcms.com/images/docs/fields/json-dark.png"
|
||||
alt="Shows a JSON field in the Payload Admin Panel"
|
||||
caption="Admin Panel screenshot of a JSON field"
|
||||
caption="This field is using the `monaco-react` editor syntax highlighting."
|
||||
/>
|
||||
|
||||
This field uses the `monaco-react` editor syntax highlighting.
|
||||
To add a JSON Field, set the `type` to `json` in your [Field Config](./overview):
|
||||
|
||||
## Config
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyJSONField: Field = {
|
||||
// ...
|
||||
type: 'json', // highlight-line
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -38,15 +44,28 @@ This field uses the `monaco-react` editor syntax highlighting.
|
||||
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
|
||||
| **`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. See below for [more detail](#admin-config). |
|
||||
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
|
||||
| **`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._
|
||||
|
||||
## Admin Config
|
||||
## Admin Options
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following properties:
|
||||
The customize the appearance and behavior of the JSON Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyJSONField: Field = {
|
||||
// ...
|
||||
admin: { // highlight-line
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The JSON Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
|
||||
|
||||
| Option | Description |
|
||||
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
@@ -6,10 +6,7 @@ desc: Number fields store and validate numeric data. Learn how to use and format
|
||||
keywords: number, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Number field stores and validates numeric entry and supports additional numerical validation
|
||||
and formatting features.
|
||||
</Banner>
|
||||
The Number Field stores and validates numeric entry and supports additional numerical validation and formatting features.
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/number.png"
|
||||
@@ -18,7 +15,18 @@ keywords: number, fields, config, configuration, documentation, Content Manageme
|
||||
caption="Admin Panel screenshot of a Number field"
|
||||
/>
|
||||
|
||||
## Config
|
||||
To add a Number Field, set the `type` to `number` in your [Field Config](./overview):
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyNumberField: Field = {
|
||||
// ...
|
||||
type: 'number', // highlight-line
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -39,27 +47,34 @@ keywords: number, fields, config, configuration, documentation, Content Manageme
|
||||
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
|
||||
| **`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. See below for [more detail](#admin-config). |
|
||||
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
|
||||
| **`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._
|
||||
|
||||
## Admin config
|
||||
## Admin Options
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), this field type allows for the following `admin` properties:
|
||||
The customize the appearance and behavior of the Number Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
|
||||
|
||||
**`step`**
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
Set a value for the number field to increment / decrement using browser controls.
|
||||
export const MyNumberField: Field = {
|
||||
// ...
|
||||
admin: { // highlight-line
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**`placeholder`**
|
||||
The Number Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
|
||||
|
||||
Set this property to define a placeholder string for the field.
|
||||
|
||||
**`autoComplete`**
|
||||
|
||||
Set this property to a string that will be used for browser autocomplete.
|
||||
| Property | Description |
|
||||
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`step`** | Set a value for the number field to increment / decrement using browser controls. |
|
||||
| **`placeholder`** | Set this property to define a placeholder string for the field. |
|
||||
| **`autoComplete`** | Set this property to a string that will be used for browser autocomplete. |
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -294,7 +294,7 @@ When using custom validation functions, Payload will use yours in place of the d
|
||||
To reuse default field validations, call them from within your custom validation function:
|
||||
|
||||
```ts
|
||||
import { text } from 'payload/fields/validations'
|
||||
import { text } from 'payload/shared'
|
||||
|
||||
const field: Field = {
|
||||
name: 'notBad',
|
||||
|
||||
@@ -3,18 +3,10 @@ title: Point Field
|
||||
label: Point
|
||||
order: 110
|
||||
desc: The Point field type stores coordinates in the database. Learn how to use Point field for geolocation and geometry.
|
||||
|
||||
keywords: point, geolocation, geospatial, geojson, 2dsphere, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Point field type saves a pair of coordinates in the database and assigns an index for location
|
||||
related queries.
|
||||
</Banner>
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Note:</strong> The Point field type is currently only supported in MongoDB.
|
||||
</Banner>
|
||||
The Point Field saves a pair of coordinates in the database and assigns an index for location related queries. The data structure in the database matches the GeoJSON structure to represent point. The Payload APIs simplifies the object data to only the [longitude, latitude] location.
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/point.png"
|
||||
@@ -23,7 +15,21 @@ keywords: point, geolocation, geospatial, geojson, 2dsphere, config, configurati
|
||||
caption="Admin Panel screenshot of a Point field"
|
||||
/>
|
||||
|
||||
The data structure in the database matches the GeoJSON structure to represent point. The Payload APIs simplifies the object data to only the [longitude, latitude] location.
|
||||
To add a Point Field, set the `type` to `point` in your [Field Config](./overview):
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyPointField: Field = {
|
||||
// ...
|
||||
type: 'point', // highlight-line
|
||||
}
|
||||
```
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Important:</strong>
|
||||
The Point Field is currently only supported in MongoDB.
|
||||
</Banner>
|
||||
|
||||
## Config
|
||||
|
||||
@@ -41,16 +47,12 @@ The data structure in the database matches the GeoJSON structure to represent po
|
||||
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
|
||||
| **`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. See below for [more detail](#admin-config). |
|
||||
| **`admin`** | Admin-specific configuration. [More details](../admin/fields#admin-options). |
|
||||
| **`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._
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Note:</strong> The Point field type is currently only supported in MongoDB.
|
||||
</Banner>
|
||||
|
||||
## Example
|
||||
|
||||
`collections/ExampleCollection.ts`
|
||||
|
||||
@@ -6,10 +6,7 @@ desc: The Radio field type allows for the selection of one value from a predefin
|
||||
keywords: radio, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Radio Group field type allows for the selection of one value from a predefined set of possible
|
||||
values and presents a radio group-style set of inputs to the Admin Panel.
|
||||
</Banner>
|
||||
The Radio Field allows for the selection of one value from a predefined set of possible values and presents a radio group-style set of inputs to the [Admin Panel](../admin/overview).
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/radio.png"
|
||||
@@ -18,7 +15,23 @@ keywords: radio, fields, config, configuration, documentation, Content Managemen
|
||||
caption="Admin Panel screenshot of a Radio field"
|
||||
/>
|
||||
|
||||
## Config
|
||||
To add a Radio Field, set the `type` to `radio` in your [Field Config](./overview):
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyRadioField: Field = {
|
||||
// ...
|
||||
// highlight-start
|
||||
type: 'radio',
|
||||
options: [
|
||||
// ...
|
||||
]
|
||||
// highlight-end
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -34,7 +47,7 @@ keywords: radio, fields, config, configuration, documentation, Content Managemen
|
||||
| **`defaultValue`** | Provide data to be used for this field's default value. The default value must exist within provided values in `options`. [More](/docs/fields/overview#default-values) |
|
||||
| **`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. See below for [more detail](#admin-config). |
|
||||
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`enumName`** | Custom enum name for this field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. |
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
@@ -50,13 +63,26 @@ _\* An asterisk denotes that a property is required._
|
||||
being used as a GraphQL enum.
|
||||
</Banner>
|
||||
|
||||
## Admin config
|
||||
## Admin Options
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), the Radio Group field type allows for the specification of the following `admin` properties:
|
||||
The customize the appearance and behavior of the Radio Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
|
||||
|
||||
**`layout`**
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
The `layout` property allows for the radio group to be styled as a horizonally or vertically distributed list. The default value is `horizontal`.
|
||||
export const MyRadioField: Field = {
|
||||
// ...
|
||||
admin: { // highlight-line
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The Radio Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
|
||||
|
||||
| Property | Description |
|
||||
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`layout`** | Allows for the radio group to be styled as a horizonally or vertically distributed list. The default value is `horizontal`. |
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -6,10 +6,7 @@ desc: The Relationship field provides the ability to relate documents together.
|
||||
keywords: relationship, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Relationship field is one of the most powerful fields Payload features. It provides for the
|
||||
ability to easily relate documents together.
|
||||
</Banner>
|
||||
The Relationship Field is one of the most powerful fields Payload features. It provides for the ability to easily relate documents together.
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/relationship.png"
|
||||
@@ -18,13 +15,27 @@ keywords: relationship, fields, config, configuration, documentation, Content Ma
|
||||
caption="Admin Panel screenshot of a Relationship field"
|
||||
/>
|
||||
|
||||
**Example uses:**
|
||||
The Relationship field is used in a variety of ways, including:
|
||||
|
||||
- To add `Product` documents to an `Order` document
|
||||
- To allow for an `Order` to feature a `placedBy` relationship to either an `Organization` or `User` collection
|
||||
- To assign `Category` documents to `Post` documents
|
||||
|
||||
## Config
|
||||
To add a Relationship Field, set the `type` to `relationship` in your [Field Config](./overview):
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyRelationshipField: Field = {
|
||||
// ...
|
||||
// highlight-start
|
||||
type: 'relationship',
|
||||
relationTo: 'products',
|
||||
// highlight-end
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -46,7 +57,7 @@ keywords: relationship, fields, config, configuration, documentation, Content Ma
|
||||
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
|
||||
| **`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. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
|
||||
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
|
||||
@@ -54,30 +65,33 @@ _\* An asterisk denotes that a property is required._
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Tip:</strong>
|
||||
<br />
|
||||
The [Depth](../queries/depth) parameter can be used to automatically populate
|
||||
related documents that are returned by the API.
|
||||
The [Depth](../queries/depth) parameter can be used to automatically populate related documents that are returned by the API.
|
||||
</Banner>
|
||||
|
||||
## Admin config
|
||||
## Admin Options
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), the Relationship field type also
|
||||
allows for the following admin-specific properties:
|
||||
The customize the appearance and behavior of the Relationship Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
|
||||
|
||||
**`isSortable`**
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
Set to `true` if you'd like this field to be sortable within the Admin UI using drag and drop (only works when `hasMany`
|
||||
is set to `true`).
|
||||
export const MyRelationshipField: Field = {
|
||||
// ...
|
||||
admin: { // highlight-line
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**`allowCreate`**
|
||||
The Relationship Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
|
||||
|
||||
Set to `false` if you'd like to disable the ability to create new documents from within the relationship field (hides
|
||||
the "Add new" button in the admin UI).
|
||||
| Property | Description |
|
||||
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`isSortable`** | Set to `true` if you'd like this field to be sortable within the Admin UI using drag and drop (only works when `hasMany` is set to `true`). |
|
||||
| **`allowCreate`** | Set to `false` if you'd like to disable the ability to create new documents from within the relationship field. |
|
||||
| **`sortOptions`** | Define a default sorting order for the options within a Relationship field's dropdown. [More](#sortOptions) |
|
||||
|
||||
**`sortOptions`**
|
||||
|
||||
The `sortOptions` property allows you to define a default sorting order for the options within a Relationship field's
|
||||
dropdown. This can be particularly useful for ensuring that the most relevant options are presented first to the user.
|
||||
### Sort Options
|
||||
|
||||
You can specify `sortOptions` in two ways:
|
||||
|
||||
@@ -179,7 +193,7 @@ You can learn more about writing queries [here](/docs/queries/overview).
|
||||
When a relationship field has both <strong>filterOptions</strong> and a custom{' '}
|
||||
<strong>validate</strong> function, the api will not validate <strong>filterOptions</strong>{' '}
|
||||
unless you call the default relationship field validation function imported from{' '}
|
||||
<strong>payload/fields/validations</strong> in your validate function.
|
||||
<strong>payload/shared</strong> in your validate function.
|
||||
</Banner>
|
||||
|
||||
## How the data is saved
|
||||
|
||||
@@ -6,10 +6,7 @@ desc: The Rich Text field allows dynamic content to be written through the Admin
|
||||
keywords: rich text, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Rich Text field is a powerful way to allow editors to write dynamic content. The content is
|
||||
saved as JSON in the database and can be converted into any format, including HTML, that you need.
|
||||
</Banner>
|
||||
The Rich Text Field is a powerful way to allow editors to write dynamic content. The content is saved as JSON in the database and can be converted into any format, including HTML, that you need.
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/richtext.png"
|
||||
@@ -39,7 +36,7 @@ Right now, Payload is officially supporting two rich text editors:
|
||||
will allow you to apply your learnings elsewhere as well.
|
||||
</Banner>
|
||||
|
||||
## Config
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -53,29 +50,36 @@ Right now, Payload is officially supporting two rich text editors:
|
||||
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
|
||||
| **`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. See below for [more detail](#admin-config). |
|
||||
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
|
||||
| **`editor`** | Override the rich text editor specified in your base configuration for this field. |
|
||||
| **`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._
|
||||
|
||||
## Admin config
|
||||
## Admin Options
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), the Rich Text editor allows for the following admin properties:
|
||||
The customize the appearance and behavior of the Rich Text Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
|
||||
|
||||
**`placeholder`**
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
Set this property to define a placeholder string in the text input.
|
||||
export const MyRichTextField: Field = {
|
||||
// ...
|
||||
admin: { // highlight-line
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**`hideGutter`**
|
||||
The Rich Text Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
|
||||
|
||||
Set this property to `true` to hide this field's gutter within the Admin Panel. The field gutter is rendered as a vertical line and padding, but often if this field is nested within a Group, Block, or Array, you may want to hide the gutter.
|
||||
| Property | Description |
|
||||
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`placeholder`** | Set this property to define a placeholder string for the field. |
|
||||
| **`hideGutter`** | Set this property to `true` to hide this field's gutter within the Admin Panel. |
|
||||
| **`rtl`** | Override the default text direction of the Admin Panel for this field. Set to `true` to force right-to-left text direction. |
|
||||
|
||||
**`rtl`**
|
||||
|
||||
Override the default text direction of the Admin Panel for this field. Set to `true` to force right-to-left text direction.
|
||||
|
||||
## Editor-specific options
|
||||
## Editor-specific Options
|
||||
|
||||
For a ton more editor-specific options, including how to build custom rich text elements directly into your editor, take a look at either the [Slate docs](/docs/rich-text/slate) or the [Lexical docs](/docs/rich-text/lexical) depending on which editor you're using.
|
||||
|
||||
@@ -6,10 +6,7 @@ desc: With the Row field you can arrange fields next to each other in the Admin
|
||||
keywords: row, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Row field is presentational-only and only affects the Admin Panel. By using it, you can
|
||||
arrange fields next to each other horizontally.
|
||||
</Banner>
|
||||
The Row Field is presentational-only and only affects the [Admin Panel](../admin/overview). By using it, you can arrange [Fields](./overview) next to each other horizontally.
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/row.png"
|
||||
@@ -18,12 +15,28 @@ keywords: row, fields, config, configuration, documentation, Content Management
|
||||
caption="Admin Panel screenshot of a Row field"
|
||||
/>
|
||||
|
||||
## Config
|
||||
To add a Row Field, set the `type` to `row` in your [Field Config](./overview):
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyRowField: Field = {
|
||||
// ...
|
||||
// highlight-start
|
||||
type: 'row',
|
||||
fields: [
|
||||
// ...
|
||||
]
|
||||
// highlight-end
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`fields`** \* | Array of field types to nest within this Row. |
|
||||
| **`admin`** | Admin-specific configuration excluding `description`, `readOnly`, and `hidden`. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
|
||||
| **`admin`** | Admin-specific configuration excluding `description`, `readOnly`, and `hidden`. [More details](../admin/fields#admin-options). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
@@ -6,10 +6,7 @@ desc: The Select field provides a dropdown-style interface for choosing options
|
||||
keywords: select, multi-select, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Select field provides a dropdown-style interface for choosing options from a predefined list
|
||||
as an enumeration.
|
||||
</Banner>
|
||||
The Select Field provides a dropdown-style interface for choosing options from a predefined list as an enumeration.
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/select.png"
|
||||
@@ -18,7 +15,23 @@ keywords: select, multi-select, fields, config, configuration, documentation, Co
|
||||
caption="Admin Panel screenshot of a Select field"
|
||||
/>
|
||||
|
||||
## Config
|
||||
To add a Select Field, set the `type` to `select` in your [Field Config](./overview):
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MySelectField: Field = {
|
||||
// ...
|
||||
// highlight-start
|
||||
type: 'select',
|
||||
options: [
|
||||
// ...
|
||||
]
|
||||
// highlight-end
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -36,7 +49,7 @@ keywords: select, multi-select, fields, config, configuration, documentation, Co
|
||||
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
|
||||
| **`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. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
|
||||
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-options) for more details. |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`enumName`** | Custom enum name for this field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. |
|
||||
| **`dbName`** | Custom table name (if `hasMany` set to `true`) for this field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. |
|
||||
@@ -46,24 +59,33 @@ _\* An asterisk denotes that a property is required._
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Important:</strong>
|
||||
<br />
|
||||
Option values should be strings that do not contain hyphens or special characters due to GraphQL
|
||||
enumeration naming constraints. Underscores are allowed. If you determine you need your option
|
||||
values to be non-strings or contain special characters, they will be formatted accordingly before
|
||||
being used as a GraphQL enum.
|
||||
</Banner>
|
||||
|
||||
## Admin config
|
||||
## Admin Options
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), the Select field type also allows for the following admin-specific properties:
|
||||
The customize the appearance and behavior of the Select Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
|
||||
|
||||
**`isClearable`**
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
Set to `true` if you'd like this field to be clearable within the Admin UI.
|
||||
export const MySelectField: Field = {
|
||||
// ...
|
||||
admin: { // highlight-line
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**`isSortable`**
|
||||
The Select Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
|
||||
|
||||
Set to `true` if you'd like this field to be sortable within the Admin UI using drag and drop. (Only works when `hasMany` is set to `true`)
|
||||
| Property | Description |
|
||||
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`isClearable`** | Set to `true` if you'd like this field to be clearable within the Admin UI. |
|
||||
| **`isSortable`** | Set to `true` if you'd like this field to be sortable within the Admin UI using drag and drop. (Only works when `hasMany` is set to `true`) |
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -6,11 +6,7 @@ desc: The Tabs field is a great way to organize complex editing experiences into
|
||||
keywords: tabs, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Tabs field is presentational-only and only affects the Admin Panel (unless a tab is named). By
|
||||
using it, you can place fields within a nice layout component that separates certain sub-fields by
|
||||
a tabbed interface.
|
||||
</Banner>
|
||||
The Tabs Field is presentational-only and only affects the [Admin Panel](../admin/overview) (unless a tab is named). By using it, you can place fields within a nice layout component that separates certain sub-fields by a tabbed interface.
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/tabs.png"
|
||||
@@ -19,12 +15,28 @@ keywords: tabs, fields, config, configuration, documentation, Content Management
|
||||
caption="Tabs field type used to separate Hero fields from Page Layout"
|
||||
/>
|
||||
|
||||
## Config
|
||||
To add a Tabs Field, set the `type` to `tabs` in your [Field Config](./overview):
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyTabsField: Field = {
|
||||
// ...
|
||||
// highlight-start
|
||||
type: 'tabs',
|
||||
tabs: [
|
||||
// ...
|
||||
]
|
||||
// highlight-end
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **`tabs`** \* | Array of tabs to render within this Tabs field. |
|
||||
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
|
||||
| **`admin`** | Admin-specific configuration. [More details](../admin/fields#admin-options). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
|
||||
### Tab-specific Config
|
||||
|
||||
@@ -6,10 +6,7 @@ desc: Text field types simply save a string to the database and provide the Admi
|
||||
keywords: text, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Text field type is one of the most commonly used fields. It saves a string to the database and
|
||||
provides the Admin Panel with a simple text input.
|
||||
</Banner>
|
||||
The Text Field is one of the most commonly used fields. It saves a string to the database and provides the [Admin Panel](../admin/overview) with a simple text input.
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/text.png"
|
||||
@@ -18,7 +15,18 @@ keywords: text, fields, config, configuration, documentation, Content Management
|
||||
caption="Admin Panel screenshot of a Text field and read-only Text field"
|
||||
/>
|
||||
|
||||
## Config
|
||||
To add a Text Field, set the `type` to `text` in your [Field Config](./overview):
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyTextField: Field = {
|
||||
// ...
|
||||
type: 'text', // highlight-line
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -36,7 +44,7 @@ keywords: text, fields, config, configuration, documentation, Content Management
|
||||
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
|
||||
| **`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. See below for [more detail](#admin-config). |
|
||||
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`hasMany`** | Makes this field an ordered array of text instead of just a single text. |
|
||||
| **`minRows`** | Minimum number of texts in the array, if `hasMany` is set to true. |
|
||||
@@ -45,21 +53,28 @@ keywords: text, fields, config, configuration, documentation, Content Management
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Admin config
|
||||
## Admin Options
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), the Text field type allows for the following `admin` properties:
|
||||
The customize the appearance and behavior of the Text Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
|
||||
|
||||
**`placeholder`**
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
Set this property to define a placeholder string in the text input.
|
||||
export const MyTextField: Field = {
|
||||
// ...
|
||||
admin: { // highlight-line
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**`autoComplete`**
|
||||
The Text Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
|
||||
|
||||
Set this property to a string that will be used for browser autocomplete.
|
||||
|
||||
**`rtl`**
|
||||
|
||||
Override the default text direction of the Admin Panel for this field. Set to `true` to force right-to-left text direction.
|
||||
| Option | Description |
|
||||
| -------------- | ---------------------------------------------------------------------------------------------------------------- |
|
||||
| **`placeholder`** | Set this property to define a placeholder string in the text input. |
|
||||
| **`autoComplete`** | Set this property to a string that will be used for browser autocomplete. |
|
||||
| **`rtl`** | Override the default text direction of the Admin Panel for this field. Set to `true` to force right-to-left text direction. |
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -6,10 +6,7 @@ desc: Textarea field types save a string to the database, similar to the Text fi
|
||||
keywords: textarea, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Textarea field is almost identical to the Text field but it features a slightly larger input
|
||||
that is better suited to edit longer text.
|
||||
</Banner>
|
||||
The Textarea Field is nearly identical to the [Text Field](./text) but it features a slightly larger input that is better suited to edit longer text.
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/textarea.png"
|
||||
@@ -18,7 +15,18 @@ keywords: textarea, fields, config, configuration, documentation, Content Manage
|
||||
caption="Admin Panel screenshot of a Textarea field and read-only Textarea field"
|
||||
/>
|
||||
|
||||
## Config
|
||||
To add a Textarea Field, set the `type` to `textarea` in your [Field Config](./overview):
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyTextareaField: Field = {
|
||||
// ...
|
||||
type: 'textarea', // highlight-line
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -36,27 +44,34 @@ keywords: textarea, fields, config, configuration, documentation, Content Manage
|
||||
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
|
||||
| **`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. See below for [more detail](#admin-config). |
|
||||
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
|
||||
| **`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._
|
||||
|
||||
## Admin config
|
||||
## Admin Options
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), the Textarea field type allows for the following `admin` properties:
|
||||
The customize the appearance and behavior of the Textarea Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
|
||||
|
||||
**`placeholder`**
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
Set this property to define a placeholder string in the textarea.
|
||||
export const MyTextareaField: Field = {
|
||||
// ...
|
||||
admin: { // highlight-line
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**`autoComplete`**
|
||||
The Textarea Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
|
||||
|
||||
Set this property to a string that will be used for browser autocomplete.
|
||||
|
||||
**`rtl`**
|
||||
|
||||
Override the default text direction of the Admin Panel for this field. Set to `true` to force right-to-left text direction.
|
||||
| Option | Description |
|
||||
| -------------- | ---------------------------------------------------------------------------------------------------------------- |
|
||||
| **`placeholder`** | Set this property to define a placeholder string in the textarea. |
|
||||
| **`autoComplete`** | Set this property to a string that will be used for browser autocomplete. |
|
||||
| **`rtl`** | Override the default text direction of the Admin Panel for this field. Set to `true` to force right-to-left text direction. |
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -6,31 +6,34 @@ desc: UI fields are purely presentational and allow developers to customize the
|
||||
keywords: custom field, react component, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The UI (user interface) field gives you a ton of power to add your own React components directly
|
||||
into the Admin Panel, nested directly within your other fields. It has absolutely no effect on the
|
||||
data of your documents. It is presentational-only.
|
||||
</Banner>
|
||||
The UI (user interface) Field gives you a ton of power to add your own React components directly into the [Admin Panel](../admin/overview), nested directly within your other fields. It has absolutely no effect on the data of your documents. It is presentational-only. Think of it as a way for you to "plug in" your own React components directly within your other fields, so you can provide your editors with new controls exactly where you want them to go.
|
||||
|
||||
This field is helpful if you need to build in custom functionality via React components within the Admin Panel. Think of it as a way for you to "plug in" your own React components directly within your other fields, so you can provide your editors with new controls exactly where you want them to go.
|
||||
|
||||
With this field, you can also inject custom `Cell` components that appear as additional columns within collections' List views.
|
||||
|
||||
**Example uses:**
|
||||
With the UI Field, you can:
|
||||
|
||||
- Add a custom message or block of text within the body of an Edit View to describe the purpose of surrounding fields
|
||||
- Add a "Refund" button to an Order's Edit View sidebar, which might make a fetch call to a custom `refund` endpoint
|
||||
- Add a "view page" button into a Pages List View to give editors a shortcut to view a page on the frontend of the site
|
||||
- Build a "clear cache" button or similar mechanism to manually clear caches of specific documents
|
||||
|
||||
## Config
|
||||
To add a UI Field, set the `type` to `ui` in your [Field Config](./overview):
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyUIField: Field = {
|
||||
// ...
|
||||
type: 'ui', // highlight-line
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`name`** \* | A unique identifier for this field. |
|
||||
| **`label`** | Human-readable label for this UI field. |
|
||||
| **`admin.components.Field`** \* | React component to be rendered for this field within the Edit View. [More](/docs/admin/components/#field-component) |
|
||||
| **`admin.components.Cell`** | React component to be rendered as a Cell within collection List views. [More](/docs/admin/components/#field-component) |
|
||||
| **`admin.components.Field`** \* | React component to be rendered for this field within the Edit View. [More](../admin/components/#field-component) |
|
||||
| **`admin.components.Cell`** | React component to be rendered as a Cell within collection List views. [More](../admin/components/#field-component) |
|
||||
| **`admin.disableListColumn`** | Set `disableListColumn` to `true` to prevent the UI field from appearing in the list view column selector. |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
|
||||
|
||||
@@ -6,18 +6,13 @@ desc: Upload fields will allow a file to be uploaded, only from a collection sup
|
||||
keywords: upload, images media, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Upload field allows for the selection of a Document from a collection supporting Uploads, and
|
||||
formats the selection as a thumbnail in the Admin Panel.
|
||||
</Banner>
|
||||
The Upload Field allows for the selection of a Document from a Collection supporting [Uploads](../upload/overview), and formats the selection as a thumbnail in the Admin Panel.
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Important:</strong>
|
||||
<br />
|
||||
To use this field, you need to have a Collection configured to allow Uploads. For more
|
||||
information, [click here](/docs/upload/overview) to read about how to enable Uploads on a
|
||||
collection by collection basis.
|
||||
</Banner>
|
||||
Upload fields are useful for a variety of use cases, such as:
|
||||
|
||||
- To provide a `Page` with a featured image
|
||||
- To allow for a `Product` to deliver a downloadable asset like PDF or MP3
|
||||
- To give a layout building block the ability to feature a background image
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/upload.png"
|
||||
@@ -26,13 +21,26 @@ keywords: upload, images media, fields, config, configuration, documentation, Co
|
||||
caption="Admin Panel screenshot of an Upload field"
|
||||
/>
|
||||
|
||||
**Example uses:**
|
||||
To create an Upload Field, set the `type` to `upload` in your [Field Config](./overview):
|
||||
|
||||
- To provide a `Page` with a featured image
|
||||
- To allow for a `Product` to deliver a downloadable asset like PDF or MP3
|
||||
- To give a layout building block the ability to feature a background image
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
## Config
|
||||
export const MyUploadField: Field = {
|
||||
// ...
|
||||
// highlight-start
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
// highlight-end
|
||||
}
|
||||
```
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Important:</strong>
|
||||
To use the Upload Field, you must have a [Collection](../configuration/collections) configured to allow [Uploads](../upload/overview).
|
||||
</Banner>
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -51,7 +59,7 @@ keywords: upload, images media, fields, config, configuration, documentation, Co
|
||||
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
|
||||
| **`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. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
|
||||
| **`admin`** | Admin-specific configuration. [Admin Options](../admin/fields#admin-options). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
|
||||
@@ -115,5 +123,5 @@ You can learn more about writing queries [here](/docs/queries/overview).
|
||||
When an upload field has both <strong>filterOptions</strong> and a custom{' '}
|
||||
<strong>validate</strong> function, the api will not validate <strong>filterOptions</strong>{' '}
|
||||
unless you call the default upload field validation function imported from{' '}
|
||||
<strong>payload/fields/validations</strong> in your validate function.
|
||||
<strong>payload/shared</strong> in your validate function.
|
||||
</Banner>
|
||||
|
||||
@@ -79,6 +79,8 @@ app/
|
||||
├── // Your app files
|
||||
```
|
||||
|
||||
_For an exact reference of the `(payload)` directory, see [Project Structure](../admin/overview#project-structure)._
|
||||
|
||||
<Banner type="warning">
|
||||
You may need to copy all of your existing frontend files, including your existing root layout, into its own newly created [Route Group](https://nextjs.org/docs/app/building-your-application/routing/route-groups), i.e. `(my-app)`.
|
||||
</Banner>
|
||||
|
||||
@@ -28,7 +28,7 @@ No matter what you're building, Payload will give you backend superpowers. It ca
|
||||
|
||||
### Open source - deploy anywhere, including Vercel
|
||||
|
||||
It's fully open source with an MIT license and you can self-host anywhere that you can run a Node app. You can also deploy serverless to hosts like Vercel, right inside your existing Next.js app folder.
|
||||
It's fully open source with an MIT license and you can self-host anywhere that you can run a Node.js app. You can also deploy serverless to hosts like Vercel, right inside your existing Next.js application.
|
||||
|
||||
### Code-first and version controlled
|
||||
|
||||
|
||||
@@ -335,7 +335,6 @@ import {
|
||||
FieldMap,
|
||||
File,
|
||||
Form,
|
||||
FormFieldBase,
|
||||
FormLoadingOverlayToggle,
|
||||
FormSubmit,
|
||||
GenerateConfirmation,
|
||||
|
||||
@@ -104,6 +104,7 @@ _An asterisk denotes that an option is required._
|
||||
| **`resizeOptions`** | An object passed to the the Sharp image library to resize the uploaded file. [More](https://sharp.pixelplumbing.com/api-resize) |
|
||||
| **`staticDir`** | The folder directory to use to store media in. Can be either an absolute path or relative to the directory that contains your config. Defaults to your collection slug |
|
||||
| **`trimOptions`** | An object passed to the the Sharp image library to trim the uploaded file. [More](https://sharp.pixelplumbing.com/api-resize#trim) |
|
||||
| **`withMetadata`** | If specified, appends metadata to the output image file. Accepts a boolean or a function that receives `metadata` and `req`, returning a boolean. |
|
||||
|
||||
|
||||
### Payload-wide Upload Options
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload-monorepo",
|
||||
"version": "3.0.0-beta.68",
|
||||
"version": "3.0.0-beta.71",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -15,6 +15,8 @@
|
||||
"build:create-payload-app": "turbo build --filter create-payload-app",
|
||||
"build:db-mongodb": "turbo build --filter db-mongodb",
|
||||
"build:db-postgres": "turbo build --filter db-postgres",
|
||||
"build:db-sqlite": "turbo build --filter db-sqlite",
|
||||
"build:drizzle": "turbo build --filter drizzle",
|
||||
"build:email-nodemailer": "turbo build --filter email-nodemailer",
|
||||
"build:email-resend": "turbo build --filter email-resend",
|
||||
"build:eslint-config": "turbo build --filter eslint-config",
|
||||
@@ -22,6 +24,7 @@
|
||||
"build:graphql": "turbo build --filter graphql",
|
||||
"build:live-preview": "turbo build --filter live-preview",
|
||||
"build:live-preview-react": "turbo build --filter live-preview-react",
|
||||
"build:live-preview-vue": "turbo build --filter live-preview-vue",
|
||||
"build:next": "turbo build --filter next",
|
||||
"build:payload": "turbo build --filter payload",
|
||||
"build:plugin-cloud": "turbo build --filter plugin-cloud",
|
||||
@@ -92,6 +95,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "29.7.0",
|
||||
"@libsql/client": "0.6.2",
|
||||
"@next/bundle-analyzer": "15.0.0-canary.53",
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@payloadcms/eslint-plugin": "workspace:*",
|
||||
@@ -115,7 +119,6 @@
|
||||
"create-payload-app": "workspace:*",
|
||||
"cross-env": "7.0.3",
|
||||
"dotenv": "16.4.5",
|
||||
"drizzle-kit": "0.20.14-1f2c838",
|
||||
"drizzle-orm": "0.29.4",
|
||||
"escape-html": "^1.0.3",
|
||||
"execa": "5.1.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-payload-app",
|
||||
"version": "3.0.0-beta.68",
|
||||
"version": "3.0.0-beta.71",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -50,6 +50,7 @@
|
||||
"dependencies": {
|
||||
"@clack/prompts": "^0.7.0",
|
||||
"@sindresorhus/slugify": "^1.1.0",
|
||||
"@swc/core": "^1.6.13",
|
||||
"arg": "^5.0.0",
|
||||
"chalk": "^4.1.0",
|
||||
"comment-json": "^4.2.3",
|
||||
|
||||
@@ -10,22 +10,23 @@ export async function getPackageManager(args: {
|
||||
const { cliArgs, projectDir } = args
|
||||
|
||||
try {
|
||||
// Check for yarn.lock, package-lock.json, or pnpm-lock.yaml
|
||||
// Check for flag or lockfile
|
||||
let detected: PackageManager = 'npm'
|
||||
if (
|
||||
cliArgs?.['--use-pnpm'] ||
|
||||
fse.existsSync(`${projectDir}/pnpm-lock.yaml`) ||
|
||||
(await commandExists('pnpm'))
|
||||
) {
|
||||
if (cliArgs?.['--use-pnpm'] || fse.existsSync(`${projectDir}/pnpm-lock.yaml`)) {
|
||||
detected = 'pnpm'
|
||||
} else if (
|
||||
cliArgs?.['--use-yarn'] ||
|
||||
fse.existsSync(`${projectDir}/yarn.lock`) ||
|
||||
(await commandExists('yarn'))
|
||||
) {
|
||||
} else if (cliArgs?.['--use-yarn'] || fse.existsSync(`${projectDir}/yarn.lock`)) {
|
||||
detected = 'yarn'
|
||||
} else if (cliArgs?.['--use-npm'] || fse.existsSync(`${projectDir}/package-lock.json`)) {
|
||||
detected = 'npm'
|
||||
} else {
|
||||
// Otherwise check for existing commands
|
||||
if (await commandExists('pnpm')) {
|
||||
detected = 'pnpm'
|
||||
} else if (await commandExists('yarn')) {
|
||||
detected = 'yarn'
|
||||
} else {
|
||||
detected = 'npm'
|
||||
}
|
||||
}
|
||||
|
||||
return detected
|
||||
|
||||
@@ -79,7 +79,7 @@ export async function initNext(args: InitNextArgs): Promise<InitNextResult> {
|
||||
const installSpinner = p.spinner()
|
||||
installSpinner.start('Installing Payload and dependencies...')
|
||||
|
||||
const configurationResult = installAndConfigurePayload({
|
||||
const configurationResult = await installAndConfigurePayload({
|
||||
...args,
|
||||
nextAppDetails,
|
||||
nextConfigType,
|
||||
@@ -143,15 +143,16 @@ async function addPayloadConfigToTsConfig(projectDir: string, isSrcDir: boolean)
|
||||
}
|
||||
}
|
||||
|
||||
function installAndConfigurePayload(
|
||||
async function installAndConfigurePayload(
|
||||
args: {
|
||||
nextAppDetails: NextAppDetails
|
||||
nextConfigType: NextConfigType
|
||||
useDistFiles?: boolean
|
||||
} & InitNextArgs,
|
||||
):
|
||||
): Promise<
|
||||
| { payloadConfigPath: string; success: true }
|
||||
| { payloadConfigPath?: string; reason: string; success: false } {
|
||||
| { payloadConfigPath?: string; reason: string; success: false }
|
||||
> {
|
||||
const {
|
||||
'--debug': debug,
|
||||
nextAppDetails: { isSrcDir, nextAppDir, nextConfigPath } = {},
|
||||
@@ -212,7 +213,7 @@ function installAndConfigurePayload(
|
||||
copyRecursiveSync(templateSrcDir, path.dirname(nextConfigPath), debug)
|
||||
|
||||
// Wrap next.config.js with withPayload
|
||||
wrapNextConfig({ nextConfigPath, nextConfigType })
|
||||
await wrapNextConfig({ nextConfigPath, nextConfigType })
|
||||
|
||||
return {
|
||||
payloadConfigPath: path.resolve(nextAppDir, '../payload.config.ts'),
|
||||
@@ -240,7 +241,7 @@ export async function getNextAppDetails(projectDir: string): Promise<NextAppDeta
|
||||
const isSrcDir = fs.existsSync(path.resolve(projectDir, 'src'))
|
||||
|
||||
const nextConfigPath: string | undefined = (
|
||||
await globby('next.config.*js', { absolute: true, cwd: projectDir })
|
||||
await globby('next.config.*(t|j)s', { absolute: true, cwd: projectDir })
|
||||
)?.[0]
|
||||
|
||||
if (!nextConfigPath || nextConfigPath.length === 0) {
|
||||
@@ -286,8 +287,13 @@ export async function getNextAppDetails(projectDir: string): Promise<NextAppDeta
|
||||
function getProjectType(args: {
|
||||
nextConfigPath: string
|
||||
packageObj: Record<string, unknown>
|
||||
}): 'cjs' | 'esm' {
|
||||
}): NextConfigType {
|
||||
const { nextConfigPath, packageObj } = args
|
||||
|
||||
if (nextConfigPath.endsWith('.ts')) {
|
||||
return 'ts'
|
||||
}
|
||||
|
||||
if (nextConfigPath.endsWith('.mjs')) {
|
||||
return 'esm'
|
||||
}
|
||||
|
||||
@@ -3,6 +3,35 @@ import { jest } from '@jest/globals'
|
||||
|
||||
import { parseAndModifyConfigContent, withPayloadStatement } from './wrap-next-config.js'
|
||||
|
||||
const tsConfigs = {
|
||||
defaultNextConfig: `import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {};
|
||||
export default nextConfig;`,
|
||||
|
||||
nextConfigExportNamedDefault: `import type { NextConfig } from "next";
|
||||
const nextConfig: NextConfig = {};
|
||||
const wrapped = someFunc(asdf);
|
||||
export { wrapped as default };
|
||||
`,
|
||||
nextConfigWithFunc: `import type { NextConfig } from "next";
|
||||
const nextConfig: NextConfig = {};
|
||||
export default someFunc(nextConfig);
|
||||
`,
|
||||
nextConfigWithFuncMultiline: `import type { NextConfig } from "next";
|
||||
const nextConfig: NextConfig = {};
|
||||
export default someFunc(
|
||||
nextConfig
|
||||
);
|
||||
`,
|
||||
nextConfigWithSpread: `import type { NextConfig } from "next";
|
||||
const nextConfig: NextConfig = {
|
||||
...someConfig,
|
||||
};
|
||||
export default nextConfig;
|
||||
`,
|
||||
}
|
||||
|
||||
const esmConfigs = {
|
||||
defaultNextConfig: `/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {};
|
||||
@@ -52,27 +81,66 @@ module.exports = nextConfig;
|
||||
}
|
||||
|
||||
describe('parseAndInsertWithPayload', () => {
|
||||
describe('ts', () => {
|
||||
const configType = 'ts'
|
||||
const importStatement = withPayloadStatement[configType]
|
||||
|
||||
it('should parse the default next config', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
tsConfigs.defaultNextConfig,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(importStatement)
|
||||
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
|
||||
})
|
||||
|
||||
it('should parse the config with a function', async () => {
|
||||
const { modifiedConfigContent: modifiedConfigContent2 } = await parseAndModifyConfigContent(
|
||||
tsConfigs.nextConfigWithFunc,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent2).toContain('withPayload(someFunc(nextConfig))')
|
||||
})
|
||||
|
||||
it('should parse the config with a multi-lined function', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
tsConfigs.nextConfigWithFuncMultiline,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(importStatement)
|
||||
expect(modifiedConfigContent).toMatch(/withPayload\(someFunc\(\n {2}nextConfig\n\)\)/)
|
||||
})
|
||||
|
||||
it('should parse the config with a spread', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
tsConfigs.nextConfigWithSpread,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(importStatement)
|
||||
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
|
||||
})
|
||||
})
|
||||
describe('esm', () => {
|
||||
const configType = 'esm'
|
||||
const importStatement = withPayloadStatement[configType]
|
||||
it('should parse the default next config', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
it('should parse the default next config', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
esmConfigs.defaultNextConfig,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(importStatement)
|
||||
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
|
||||
})
|
||||
it('should parse the config with a function', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
it('should parse the config with a function', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
esmConfigs.nextConfigWithFunc,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain('withPayload(someFunc(nextConfig))')
|
||||
})
|
||||
|
||||
it('should parse the config with a function on a new line', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
it('should parse the config with a multi-lined function', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
esmConfigs.nextConfigWithFuncMultiline,
|
||||
configType,
|
||||
)
|
||||
@@ -80,8 +148,8 @@ describe('parseAndInsertWithPayload', () => {
|
||||
expect(modifiedConfigContent).toMatch(/withPayload\(someFunc\(\n {2}nextConfig\n\)\)/)
|
||||
})
|
||||
|
||||
it('should parse the config with a spread', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
it('should parse the config with a spread', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
esmConfigs.nextConfigWithSpread,
|
||||
configType,
|
||||
)
|
||||
@@ -90,10 +158,10 @@ describe('parseAndInsertWithPayload', () => {
|
||||
})
|
||||
|
||||
// Unsupported: export { wrapped as default }
|
||||
it('should give warning with a named export as default', () => {
|
||||
it('should give warning with a named export as default', async () => {
|
||||
const warnLogSpy = jest.spyOn(p.log, 'warn').mockImplementation(() => {})
|
||||
|
||||
const { modifiedConfigContent, success } = parseAndModifyConfigContent(
|
||||
const { modifiedConfigContent, success } = await parseAndModifyConfigContent(
|
||||
esmConfigs.nextConfigExportNamedDefault,
|
||||
configType,
|
||||
)
|
||||
@@ -109,39 +177,39 @@ describe('parseAndInsertWithPayload', () => {
|
||||
describe('cjs', () => {
|
||||
const configType = 'cjs'
|
||||
const requireStatement = withPayloadStatement[configType]
|
||||
it('should parse the default next config', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
it('should parse the default next config', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
cjsConfigs.defaultNextConfig,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(requireStatement)
|
||||
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
|
||||
})
|
||||
it('should parse anonymous default config', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
it('should parse anonymous default config', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
cjsConfigs.anonConfig,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(requireStatement)
|
||||
expect(modifiedConfigContent).toContain('withPayload({})')
|
||||
})
|
||||
it('should parse the config with a function', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
it('should parse the config with a function', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
cjsConfigs.nextConfigWithFunc,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain('withPayload(someFunc(nextConfig))')
|
||||
})
|
||||
it('should parse the config with a function on a new line', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
it('should parse the config with a multi-lined function', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
cjsConfigs.nextConfigWithFuncMultiline,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(requireStatement)
|
||||
expect(modifiedConfigContent).toMatch(/withPayload\(someFunc\(\n {2}nextConfig\n\)\)/)
|
||||
})
|
||||
it('should parse the config with a named export as default', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
it('should parse the config with a named export as default', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
cjsConfigs.nextConfigExportNamedDefault,
|
||||
configType,
|
||||
)
|
||||
@@ -149,8 +217,8 @@ describe('parseAndInsertWithPayload', () => {
|
||||
expect(modifiedConfigContent).toContain('withPayload(wrapped)')
|
||||
})
|
||||
|
||||
it('should parse the config with a spread', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
it('should parse the config with a spread', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
cjsConfigs.nextConfigWithSpread,
|
||||
configType,
|
||||
)
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
import type { Program } from 'esprima-next'
|
||||
import type { ExportDefaultExpression, ModuleItem } from '@swc/core'
|
||||
|
||||
import swc from '@swc/core'
|
||||
import chalk from 'chalk'
|
||||
import { Syntax, parseModule } from 'esprima-next'
|
||||
import fs from 'fs'
|
||||
|
||||
import type { NextConfigType } from '../types.js'
|
||||
|
||||
import { log, warning } from '../utils/log.js'
|
||||
|
||||
export const withPayloadStatement = {
|
||||
cjs: `const { withPayload } = require('@payloadcms/next/withPayload')\n`,
|
||||
esm: `import { withPayload } from '@payloadcms/next/withPayload'\n`,
|
||||
cjs: `const { withPayload } = require("@payloadcms/next/withPayload");`,
|
||||
esm: `import { withPayload } from "@payloadcms/next/withPayload";`,
|
||||
ts: `import { withPayload } from "@payloadcms/next/withPayload";`,
|
||||
}
|
||||
|
||||
type NextConfigType = 'cjs' | 'esm'
|
||||
|
||||
export const wrapNextConfig = (args: {
|
||||
export const wrapNextConfig = async (args: {
|
||||
nextConfigPath: string
|
||||
nextConfigType: NextConfigType
|
||||
}) => {
|
||||
const { nextConfigPath, nextConfigType: configType } = args
|
||||
const configContent = fs.readFileSync(nextConfigPath, 'utf8')
|
||||
const { modifiedConfigContent: newConfig, success } = parseAndModifyConfigContent(
|
||||
const { modifiedConfigContent: newConfig, success } = await parseAndModifyConfigContent(
|
||||
configContent,
|
||||
configType,
|
||||
)
|
||||
@@ -34,113 +36,142 @@ export const wrapNextConfig = (args: {
|
||||
/**
|
||||
* Parses config content with AST and wraps it with withPayload function
|
||||
*/
|
||||
export function parseAndModifyConfigContent(
|
||||
export async function parseAndModifyConfigContent(
|
||||
content: string,
|
||||
configType: NextConfigType,
|
||||
): { modifiedConfigContent: string; success: boolean } {
|
||||
content = withPayloadStatement[configType] + content
|
||||
): Promise<{ modifiedConfigContent: string; success: boolean }> {
|
||||
content = withPayloadStatement[configType] + '\n' + content
|
||||
|
||||
let ast: Program | undefined
|
||||
try {
|
||||
ast = parseModule(content, { loc: true })
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
warning(`Unable to parse Next config. Error: ${error.message} `)
|
||||
warnUserWrapNotSuccessful(configType)
|
||||
}
|
||||
return {
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
console.log({ configType, content })
|
||||
|
||||
if (configType === 'esm') {
|
||||
const exportDefaultDeclaration = ast.body.find(
|
||||
(p) => p.type === Syntax.ExportDefaultDeclaration,
|
||||
) as Directive | undefined
|
||||
if (configType === 'cjs' || configType === 'esm') {
|
||||
try {
|
||||
const ast = parseModule(content, { loc: true })
|
||||
|
||||
const exportNamedDeclaration = ast.body.find(
|
||||
(p) => p.type === Syntax.ExportNamedDeclaration,
|
||||
) as ExportNamedDeclaration | undefined
|
||||
if (configType === 'cjs') {
|
||||
// Find `module.exports = X`
|
||||
const moduleExports = ast.body.find(
|
||||
(p) =>
|
||||
p.type === Syntax.ExpressionStatement &&
|
||||
p.expression?.type === Syntax.AssignmentExpression &&
|
||||
p.expression.left?.type === Syntax.MemberExpression &&
|
||||
p.expression.left.object?.type === Syntax.Identifier &&
|
||||
p.expression.left.object.name === 'module' &&
|
||||
p.expression.left.property?.type === Syntax.Identifier &&
|
||||
p.expression.left.property.name === 'exports',
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
) as any
|
||||
|
||||
if (!exportDefaultDeclaration && !exportNamedDeclaration) {
|
||||
throw new Error('Could not find ExportDefaultDeclaration in next.config.js')
|
||||
}
|
||||
if (moduleExports && moduleExports.expression.right?.loc) {
|
||||
const modifiedConfigContent = insertBeforeAndAfter(
|
||||
content,
|
||||
moduleExports.expression.right.loc,
|
||||
)
|
||||
return { modifiedConfigContent, success: true }
|
||||
}
|
||||
|
||||
if (exportDefaultDeclaration && exportDefaultDeclaration.declaration?.loc) {
|
||||
const modifiedConfigContent = insertBeforeAndAfter(
|
||||
content,
|
||||
exportDefaultDeclaration.declaration.loc,
|
||||
)
|
||||
return { modifiedConfigContent, success: true }
|
||||
} else if (exportNamedDeclaration) {
|
||||
const exportSpecifier = exportNamedDeclaration.specifiers.find(
|
||||
(s) =>
|
||||
s.type === 'ExportSpecifier' &&
|
||||
s.exported?.name === 'default' &&
|
||||
s.local?.type === 'Identifier' &&
|
||||
s.local?.name,
|
||||
)
|
||||
|
||||
if (exportSpecifier) {
|
||||
warning('Could not automatically wrap next.config.js with withPayload.')
|
||||
warning('Automatic wrapping of named exports as default not supported yet.')
|
||||
|
||||
warnUserWrapNotSuccessful(configType)
|
||||
return {
|
||||
return Promise.resolve({
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
})
|
||||
} else if (configType === 'esm') {
|
||||
const exportDefaultDeclaration = ast.body.find(
|
||||
(p) => p.type === Syntax.ExportDefaultDeclaration,
|
||||
) as Directive | undefined
|
||||
|
||||
const exportNamedDeclaration = ast.body.find(
|
||||
(p) => p.type === Syntax.ExportNamedDeclaration,
|
||||
) as ExportNamedDeclaration | undefined
|
||||
|
||||
if (!exportDefaultDeclaration && !exportNamedDeclaration) {
|
||||
throw new Error('Could not find ExportDefaultDeclaration in next.config.js')
|
||||
}
|
||||
|
||||
if (exportDefaultDeclaration && exportDefaultDeclaration.declaration?.loc) {
|
||||
const modifiedConfigContent = insertBeforeAndAfter(
|
||||
content,
|
||||
exportDefaultDeclaration.declaration.loc,
|
||||
)
|
||||
return { modifiedConfigContent, success: true }
|
||||
} else if (exportNamedDeclaration) {
|
||||
const exportSpecifier = exportNamedDeclaration.specifiers.find(
|
||||
(s) =>
|
||||
s.type === 'ExportSpecifier' &&
|
||||
s.exported?.name === 'default' &&
|
||||
s.local?.type === 'Identifier' &&
|
||||
s.local?.name,
|
||||
)
|
||||
|
||||
if (exportSpecifier) {
|
||||
warning('Could not automatically wrap next.config.js with withPayload.')
|
||||
warning('Automatic wrapping of named exports as default not supported yet.')
|
||||
|
||||
warnUserWrapNotSuccessful(configType)
|
||||
return {
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
warning('Could not automatically wrap Next config with withPayload.')
|
||||
warnUserWrapNotSuccessful(configType)
|
||||
return Promise.resolve({
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
})
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
warning(`Unable to parse Next config. Error: ${error.message} `)
|
||||
warnUserWrapNotSuccessful(configType)
|
||||
}
|
||||
return {
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
} else if (configType === 'ts') {
|
||||
const { moduleItems, parseOffset } = await compileTypeScriptFileToAST(content)
|
||||
|
||||
warning('Could not automatically wrap Next config with withPayload.')
|
||||
warnUserWrapNotSuccessful(configType)
|
||||
return {
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
}
|
||||
} else if (configType === 'cjs') {
|
||||
// Find `module.exports = X`
|
||||
const moduleExports = ast.body.find(
|
||||
(p) =>
|
||||
p.type === Syntax.ExpressionStatement &&
|
||||
p.expression?.type === Syntax.AssignmentExpression &&
|
||||
p.expression.left?.type === Syntax.MemberExpression &&
|
||||
p.expression.left.object?.type === Syntax.Identifier &&
|
||||
p.expression.left.object.name === 'module' &&
|
||||
p.expression.left.property?.type === Syntax.Identifier &&
|
||||
p.expression.left.property.name === 'exports',
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
) as any
|
||||
const exportDefaultDeclaration = moduleItems.find(
|
||||
(m) =>
|
||||
m.type === 'ExportDefaultExpression' &&
|
||||
(m.expression.type === 'Identifier' || m.expression.type === 'CallExpression'),
|
||||
) as ExportDefaultExpression | undefined
|
||||
|
||||
if (moduleExports && moduleExports.expression.right?.loc) {
|
||||
const modifiedConfigContent = insertBeforeAndAfter(
|
||||
if (exportDefaultDeclaration) {
|
||||
if (!('span' in exportDefaultDeclaration.expression)) {
|
||||
warning('Could not automatically wrap Next config with withPayload.')
|
||||
warnUserWrapNotSuccessful(configType)
|
||||
return Promise.resolve({
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
})
|
||||
}
|
||||
|
||||
const modifiedConfigContent = insertBeforeAndAfterSWC(
|
||||
content,
|
||||
moduleExports.expression.right.loc,
|
||||
exportDefaultDeclaration.expression.span,
|
||||
parseOffset,
|
||||
)
|
||||
return { modifiedConfigContent, success: true }
|
||||
}
|
||||
|
||||
return {
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
|
||||
warning('Could not automatically wrap Next config with withPayload.')
|
||||
warnUserWrapNotSuccessful(configType)
|
||||
return {
|
||||
return Promise.resolve({
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function warnUserWrapNotSuccessful(configType: NextConfigType) {
|
||||
// Output directions for user to update next.config.js
|
||||
const withPayloadMessage = `
|
||||
|
||||
${chalk.bold(`Please manually wrap your existing next.config.js with the withPayload function. Here is an example:`)}
|
||||
${chalk.bold(`Please manually wrap your existing Next config with the withPayload function. Here is an example:`)}
|
||||
|
||||
${withPayloadStatement[configType]}
|
||||
|
||||
@@ -148,7 +179,7 @@ function warnUserWrapNotSuccessful(configType: NextConfigType) {
|
||||
// Your Next.js config here
|
||||
}
|
||||
|
||||
${configType === 'esm' ? 'export default withPayload(nextConfig)' : 'module.exports = withPayload(nextConfig)'}
|
||||
${configType === 'cjs' ? 'module.exports = withPayload(nextConfig)' : 'export default withPayload(nextConfig)'}
|
||||
|
||||
`
|
||||
|
||||
@@ -186,7 +217,7 @@ type Loc = {
|
||||
start: { column: number; line: number }
|
||||
}
|
||||
|
||||
function insertBeforeAndAfter(content: string, loc: Loc) {
|
||||
function insertBeforeAndAfter(content: string, loc: Loc): string {
|
||||
const { end, start } = loc
|
||||
const lines = content.split('\n')
|
||||
|
||||
@@ -205,3 +236,57 @@ function insertBeforeAndAfter(content: string, loc: Loc) {
|
||||
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function insertBeforeAndAfterSWC(
|
||||
content: string,
|
||||
span: ModuleItem['span'],
|
||||
/**
|
||||
* WARNING: This is ONLY for unit tests. Defaults to 0 otherwise.
|
||||
*
|
||||
* @see compileTypeScriptFileToAST
|
||||
*/
|
||||
parseOffset: number,
|
||||
): string {
|
||||
const { end: preOffsetEnd, start: preOffsetStart } = span
|
||||
|
||||
const start = preOffsetStart - parseOffset
|
||||
const end = preOffsetEnd - parseOffset
|
||||
|
||||
const insert = (pos: number, text: string): string => {
|
||||
return content.slice(0, pos) + text + content.slice(pos)
|
||||
}
|
||||
|
||||
// insert ) after end
|
||||
content = insert(end - 1, ')')
|
||||
// insert withPayload before start
|
||||
content = insert(start - 1, 'withPayload(')
|
||||
|
||||
return content
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile typescript to AST using the swc compiler
|
||||
*/
|
||||
async function compileTypeScriptFileToAST(
|
||||
fileContent: string,
|
||||
): Promise<{ moduleItems: ModuleItem[]; parseOffset: number }> {
|
||||
let parseOffset = 0
|
||||
|
||||
/**
|
||||
* WARNING: This is ONLY for unit tests.
|
||||
*
|
||||
* Multiple instances of swc DO NOT reset the .span.end value.
|
||||
* During unit tests, the .spawn.end value is read and accounted for.
|
||||
*
|
||||
* https://github.com/swc-project/swc/issues/1366
|
||||
*/
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
parseOffset = (await swc.parse('')).span.end
|
||||
}
|
||||
|
||||
const module = await swc.parse(fileContent, {
|
||||
syntax: 'typescript',
|
||||
})
|
||||
|
||||
return { moduleItems: module.body, parseOffset }
|
||||
}
|
||||
|
||||
@@ -75,6 +75,6 @@ export type NextAppDetails = {
|
||||
nextConfigType?: NextConfigType
|
||||
}
|
||||
|
||||
export type NextConfigType = 'cjs' | 'esm'
|
||||
export type NextConfigType = 'cjs' | 'esm' | 'ts'
|
||||
|
||||
export type StorageAdapterType = 'localDisk' | 'payloadCloud' | 'vercelBlobStorage'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "3.0.0-beta.68",
|
||||
"version": "3.0.0-beta.71",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -54,6 +54,14 @@ export const find: Find = async function find(
|
||||
useEstimatedCount,
|
||||
}
|
||||
|
||||
if (this.collation) {
|
||||
const defaultLocale = 'en'
|
||||
paginationOptions.collation = {
|
||||
locale: locale && locale !== 'all' && locale !== '*' ? locale : defaultLocale,
|
||||
...this.collation,
|
||||
}
|
||||
}
|
||||
|
||||
if (!useEstimatedCount && Object.keys(query).length === 0 && this.disableIndexHints !== true) {
|
||||
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
|
||||
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
|
||||
|
||||
@@ -72,6 +72,14 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV
|
||||
useEstimatedCount,
|
||||
}
|
||||
|
||||
if (this.collation) {
|
||||
const defaultLocale = 'en'
|
||||
paginationOptions.collation = {
|
||||
locale: locale && locale !== 'all' && locale !== '*' ? locale : defaultLocale,
|
||||
...this.collation,
|
||||
}
|
||||
}
|
||||
|
||||
if (!useEstimatedCount && Object.keys(query).length === 0 && this.disableIndexHints !== true) {
|
||||
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
|
||||
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
|
||||
|
||||
@@ -69,6 +69,14 @@ export const findVersions: FindVersions = async function findVersions(
|
||||
useEstimatedCount,
|
||||
}
|
||||
|
||||
if (this.collation) {
|
||||
const defaultLocale = 'en'
|
||||
paginationOptions.collation = {
|
||||
locale: locale && locale !== 'all' && locale !== '*' ? locale : defaultLocale,
|
||||
...this.collation,
|
||||
}
|
||||
}
|
||||
|
||||
if (!useEstimatedCount && Object.keys(query).length === 0 && this.disableIndexHints !== true) {
|
||||
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
|
||||
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { TransactionOptions } from 'mongodb'
|
||||
import type { CollationOptions, TransactionOptions } from 'mongodb'
|
||||
import type { MongoMemoryReplSet } from 'mongodb-memory-server'
|
||||
import type { ClientSession, ConnectOptions, Connection } from 'mongoose'
|
||||
import type { BaseDatabaseAdapter, DatabaseAdapterObj, Payload } from 'payload'
|
||||
@@ -42,6 +42,30 @@ export type { MigrateDownArgs, MigrateUpArgs } from './types.js'
|
||||
export interface Args {
|
||||
/** Set to false to disable auto-pluralization of collection names, Defaults to true */
|
||||
autoPluralization?: boolean
|
||||
/**
|
||||
* If enabled, collation allows for language-specific rules for string comparison.
|
||||
* This configuration can include the following options:
|
||||
*
|
||||
* - `strength` (number): Comparison level (1: Primary, 2: Secondary, 3: Tertiary (default), 4: Quaternary, 5: Identical)
|
||||
* - `caseLevel` (boolean): Include case comparison at strength level 1 or 2.
|
||||
* - `caseFirst` (string): Sort order of case differences during tertiary level comparisons ("upper", "lower", "off").
|
||||
* - `numericOrdering` (boolean): Compare numeric strings as numbers.
|
||||
* - `alternate` (string): Consider whitespace and punctuation as base characters ("non-ignorable", "shifted").
|
||||
* - `maxVariable` (string): Characters considered ignorable when `alternate` is "shifted" ("punct", "space").
|
||||
* - `backwards` (boolean): Sort strings with diacritics from back of the string.
|
||||
* - `normalization` (boolean): Check if text requires normalization and perform normalization.
|
||||
*
|
||||
* Available on MongoDB version 3.4 and up.
|
||||
* The locale that gets passed is your current project's locale but defaults to "en".
|
||||
*
|
||||
* Example:
|
||||
* {
|
||||
* strength: 3
|
||||
* }
|
||||
*
|
||||
* Defaults to disabled.
|
||||
*/
|
||||
collation?: Omit<CollationOptions, 'locale'>
|
||||
/** Extra configuration options */
|
||||
connectOptions?: {
|
||||
/** Set false to disable $facet aggregation in non-supporting databases, Defaults to true */
|
||||
|
||||
@@ -57,6 +57,14 @@ export const queryDrafts: QueryDrafts = async function queryDrafts(
|
||||
useEstimatedCount,
|
||||
}
|
||||
|
||||
if (this.collation) {
|
||||
const defaultLocale = 'en'
|
||||
paginationOptions.collation = {
|
||||
locale: locale && locale !== 'all' && locale !== '*' ? locale : defaultLocale,
|
||||
...this.collation,
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!useEstimatedCount &&
|
||||
Object.keys(versionQuery).length === 0 &&
|
||||
|
||||
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'url'
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
import { commonjs } from '@hyrious/esbuild-plugin-commonjs'
|
||||
|
||||
throw new Error('asfdadsf')
|
||||
async function build() {
|
||||
const resultServer = await esbuild.build({
|
||||
entryPoints: ['src/index.ts'],
|
||||
@@ -18,9 +18,9 @@ async function build() {
|
||||
'*.scss',
|
||||
'*.css',
|
||||
'drizzle-kit',
|
||||
'libsql',
|
||||
'pg',
|
||||
'@payloadcms/translations',
|
||||
'@payloadcms/drizzle',
|
||||
'payload',
|
||||
'payload/*',
|
||||
],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-postgres",
|
||||
"version": "3.0.0-beta.68",
|
||||
"version": "3.0.0-beta.71",
|
||||
"description": "The officially supported Postgres database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -40,11 +40,12 @@
|
||||
"build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
|
||||
"build:types": "tsc --emitDeclarationOnly --outDir dist",
|
||||
"clean": "rimraf {dist,*.tsbuildinfo}",
|
||||
"prepack": "pnpm clean && pnpm turbo build",
|
||||
"prepublishOnly": "pnpm clean && pnpm turbo build",
|
||||
"renamePredefinedMigrations": "tsx ./scripts/renamePredefinedMigrations.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@libsql/client": "^0.5.2",
|
||||
"@payloadcms/drizzle": "workspace:*",
|
||||
"console-table-printer": "2.11.2",
|
||||
"drizzle-kit": "0.20.14-1f2c838",
|
||||
"drizzle-orm": "0.29.4",
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import type { DrizzleAdapter } from '@payloadcms/drizzle/types'
|
||||
import type { Connect, Payload } from 'payload'
|
||||
|
||||
import { sql } from 'drizzle-orm'
|
||||
import { pushDevSchema } from '@payloadcms/drizzle'
|
||||
import { drizzle } from 'drizzle-orm/node-postgres'
|
||||
import pg from 'pg'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { pushDevSchema } from './utilities/pushDevSchema.js'
|
||||
|
||||
const connectWithReconnect = async function ({
|
||||
adapter,
|
||||
payload,
|
||||
@@ -71,12 +70,7 @@ export const connect: Connect = async function connect(
|
||||
if (!hotReload) {
|
||||
if (process.env.PAYLOAD_DROP_DATABASE === 'true') {
|
||||
this.payload.logger.info(`---- DROPPING TABLES SCHEMA(${this.schemaName || 'public'}) ----`)
|
||||
await this.drizzle.execute(
|
||||
sql.raw(`
|
||||
drop schema if exists ${this.schemaName || 'public'} cascade;
|
||||
create schema ${this.schemaName || 'public'};
|
||||
`),
|
||||
)
|
||||
await this.dropDatabase({ adapter: this })
|
||||
this.payload.logger.info('---- DROPPED TABLES ----')
|
||||
}
|
||||
}
|
||||
@@ -92,7 +86,7 @@ export const connect: Connect = async function connect(
|
||||
process.env.PAYLOAD_MIGRATING !== 'true' &&
|
||||
this.push !== false
|
||||
) {
|
||||
await pushDevSchema(this)
|
||||
await pushDevSchema(this as unknown as DrizzleAdapter)
|
||||
}
|
||||
|
||||
if (typeof this.resolveInitializing === 'function') this.resolveInitializing()
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
import type { Count, SanitizedCollectionConfig } from 'payload'
|
||||
|
||||
import { sql } from 'drizzle-orm'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { ChainedMethods } from './find/chainMethods.js'
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { chainMethods } from './find/chainMethods.js'
|
||||
import buildQuery from './queries/buildQuery.js'
|
||||
|
||||
export const count: Count = async function count(
|
||||
this: PostgresAdapter,
|
||||
{ collection, locale, req, where: whereArg },
|
||||
) {
|
||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||
|
||||
const tableName = this.tableNameMap.get(toSnakeCase(collectionConfig.slug))
|
||||
|
||||
const db = this.sessions[await req.transactionID]?.db || this.drizzle
|
||||
const table = this.tables[tableName]
|
||||
|
||||
const { joins, where } = await buildQuery({
|
||||
adapter: this,
|
||||
fields: collectionConfig.fields,
|
||||
locale,
|
||||
tableName,
|
||||
where: whereArg,
|
||||
})
|
||||
|
||||
const selectCountMethods: ChainedMethods = []
|
||||
|
||||
Object.entries(joins).forEach(([joinTable, condition]) => {
|
||||
if (joinTable) {
|
||||
selectCountMethods.push({
|
||||
args: [this.tables[joinTable], condition],
|
||||
method: 'leftJoin',
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const countResult = await chainMethods({
|
||||
methods: selectCountMethods,
|
||||
query: db
|
||||
.select({
|
||||
count: sql<number>`count
|
||||
(DISTINCT ${this.tables[tableName].id})`,
|
||||
})
|
||||
.from(table)
|
||||
.where(where),
|
||||
})
|
||||
|
||||
return { totalDocs: Number(countResult[0].count) }
|
||||
}
|
||||
33
packages/db-postgres/src/countDistinct.ts
Normal file
33
packages/db-postgres/src/countDistinct.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { ChainedMethods, TransactionPg } from '@payloadcms/drizzle/types'
|
||||
|
||||
import { chainMethods } from '@payloadcms/drizzle'
|
||||
import { sql } from 'drizzle-orm'
|
||||
|
||||
import type { CountDistinct, PostgresAdapter } from './types.js'
|
||||
|
||||
export const countDistinct: CountDistinct = async function countDistinct(
|
||||
this: PostgresAdapter,
|
||||
{ db, joins, tableName, where },
|
||||
) {
|
||||
const chainedMethods: ChainedMethods = []
|
||||
|
||||
joins.forEach(({ condition, table }) => {
|
||||
chainedMethods.push({
|
||||
args: [table, condition],
|
||||
method: 'leftJoin',
|
||||
})
|
||||
})
|
||||
|
||||
const countResult = await chainMethods({
|
||||
methods: chainedMethods,
|
||||
query: (db as TransactionPg)
|
||||
.select({
|
||||
count: sql<string>`count
|
||||
(DISTINCT ${this.tables[tableName].id})`,
|
||||
})
|
||||
.from(this.tables[tableName])
|
||||
.where(where),
|
||||
})
|
||||
|
||||
return Number(countResult[0].count)
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
/* eslint-disable no-restricted-syntax, no-await-in-loop */
|
||||
import type { DrizzleSnapshotJSON } from 'drizzle-kit/payload'
|
||||
import type { CreateMigration, MigrationTemplateArgs } from 'payload'
|
||||
import type { CreateMigration } from 'payload'
|
||||
|
||||
import fs from 'fs'
|
||||
import { createRequire } from 'module'
|
||||
@@ -11,38 +10,11 @@ import { fileURLToPath } from 'url'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { defaultDrizzleSnapshot } from './defaultSnapshot.js'
|
||||
import { getMigrationTemplate } from './getMigrationTemplate.js'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
|
||||
const migrationTemplate = ({
|
||||
downSQL,
|
||||
imports,
|
||||
upSQL,
|
||||
}: MigrationTemplateArgs): string => `import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
|
||||
${imports ? `${imports}\n` : ''}
|
||||
export async function up({ payload, req }: MigrateUpArgs): Promise<void> {
|
||||
${upSQL}
|
||||
};
|
||||
|
||||
export async function down({ payload, req }: MigrateDownArgs): Promise<void> {
|
||||
${downSQL}
|
||||
};
|
||||
`
|
||||
|
||||
const getDefaultDrizzleSnapshot = (): DrizzleSnapshotJSON => ({
|
||||
id: '00000000-0000-0000-0000-000000000000',
|
||||
_meta: {
|
||||
columns: {},
|
||||
schemas: {},
|
||||
tables: {},
|
||||
},
|
||||
dialect: 'pg',
|
||||
enums: {},
|
||||
prevId: '00000000-0000-0000-0000-00000000000',
|
||||
schemas: {},
|
||||
tables: {},
|
||||
version: '5',
|
||||
})
|
||||
|
||||
export const createMigration: CreateMigration = async function createMigration(
|
||||
this: PostgresAdapter,
|
||||
{ file, forceAcceptWarning, migrationName, payload },
|
||||
@@ -75,7 +47,7 @@ export const createMigration: CreateMigration = async function createMigration(
|
||||
|
||||
const filePath = `${dir}/${fileName}`
|
||||
|
||||
let drizzleJsonBefore = getDefaultDrizzleSnapshot()
|
||||
let drizzleJsonBefore = defaultDrizzleSnapshot
|
||||
|
||||
if (!upSQL) {
|
||||
// Get latest migration snapshot
|
||||
@@ -121,15 +93,15 @@ export const createMigration: CreateMigration = async function createMigration(
|
||||
process.exit(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// write schema
|
||||
fs.writeFileSync(`${filePath}.json`, JSON.stringify(drizzleJsonAfter, null, 2))
|
||||
// write schema
|
||||
fs.writeFileSync(`${filePath}.json`, JSON.stringify(drizzleJsonAfter, null, 2))
|
||||
}
|
||||
|
||||
// write migration
|
||||
fs.writeFileSync(
|
||||
`${filePath}.ts`,
|
||||
migrationTemplate({
|
||||
getMigrationTemplate({
|
||||
downSQL: downSQL || ` // Migration code`,
|
||||
imports,
|
||||
upSQL: upSQL || ` // Migration code`,
|
||||
|
||||
16
packages/db-postgres/src/defaultSnapshot.ts
Normal file
16
packages/db-postgres/src/defaultSnapshot.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import type { DrizzleSnapshotJSON } from 'drizzle-kit/payload'
|
||||
|
||||
export const defaultDrizzleSnapshot: DrizzleSnapshotJSON = {
|
||||
id: '00000000-0000-0000-0000-000000000000',
|
||||
_meta: {
|
||||
columns: {},
|
||||
schemas: {},
|
||||
tables: {},
|
||||
},
|
||||
dialect: 'pg',
|
||||
enums: {},
|
||||
prevId: '00000000-0000-0000-0000-00000000000',
|
||||
schemas: {},
|
||||
tables: {},
|
||||
version: '5',
|
||||
}
|
||||
8
packages/db-postgres/src/deleteWhere.ts
Normal file
8
packages/db-postgres/src/deleteWhere.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { TransactionPg } from '@payloadcms/drizzle/types'
|
||||
|
||||
import type { DeleteWhere } from './types.js'
|
||||
|
||||
export const deleteWhere: DeleteWhere = async function deleteWhere({ db, tableName, where }) {
|
||||
const table = this.tables[tableName]
|
||||
await (db as TransactionPg).delete(table).where(where)
|
||||
}
|
||||
9
packages/db-postgres/src/dropDatabase.ts
Normal file
9
packages/db-postgres/src/dropDatabase.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { DropDatabase } from './types.js'
|
||||
|
||||
export const dropDatabase: DropDatabase = async function dropDatabase({ adapter }) {
|
||||
await adapter.execute({
|
||||
drizzle: adapter.drizzle,
|
||||
raw: `drop schema if exists ${this.schemaName || 'public'} cascade;
|
||||
create schema ${this.schemaName || 'public'};`,
|
||||
})
|
||||
}
|
||||
13
packages/db-postgres/src/execute.ts
Normal file
13
packages/db-postgres/src/execute.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { sql } from 'drizzle-orm'
|
||||
|
||||
import type { Execute } from './types.js'
|
||||
|
||||
export const execute: Execute<any> = function execute({ db, drizzle, raw, sql: statement }) {
|
||||
const executeFrom = db ?? drizzle
|
||||
|
||||
if (raw) {
|
||||
return executeFrom.execute(sql.raw(raw))
|
||||
} else {
|
||||
return executeFrom.execute(sql`${statement}`)
|
||||
}
|
||||
}
|
||||
16
packages/db-postgres/src/getMigrationTemplate.ts
Normal file
16
packages/db-postgres/src/getMigrationTemplate.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import type { MigrationTemplateArgs } from 'payload'
|
||||
|
||||
export const getMigrationTemplate = ({
|
||||
downSQL,
|
||||
imports,
|
||||
upSQL,
|
||||
}: MigrationTemplateArgs): string => `import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
|
||||
${imports ? `${imports}\n` : ''}
|
||||
export async function up({ payload, req }: MigrateUpArgs): Promise<void> {
|
||||
${upSQL}
|
||||
}
|
||||
|
||||
export async function down({ payload, req }: MigrateDownArgs): Promise<void> {
|
||||
${downSQL}
|
||||
}
|
||||
`
|
||||
@@ -1,42 +1,54 @@
|
||||
import type { DatabaseAdapterObj, Payload } from 'payload'
|
||||
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import {
|
||||
beginTransaction,
|
||||
commitTransaction,
|
||||
count,
|
||||
create,
|
||||
createGlobal,
|
||||
createGlobalVersion,
|
||||
createVersion,
|
||||
deleteMany,
|
||||
deleteOne,
|
||||
deleteVersions,
|
||||
destroy,
|
||||
find,
|
||||
findGlobal,
|
||||
findGlobalVersions,
|
||||
findMigrationDir,
|
||||
findOne,
|
||||
findVersions,
|
||||
migrate,
|
||||
migrateDown,
|
||||
migrateFresh,
|
||||
migrateRefresh,
|
||||
migrateReset,
|
||||
migrateStatus,
|
||||
operatorMap,
|
||||
queryDrafts,
|
||||
rollbackTransaction,
|
||||
updateGlobal,
|
||||
updateGlobalVersion,
|
||||
updateOne,
|
||||
updateVersion,
|
||||
} from '@payloadcms/drizzle'
|
||||
import { createDatabaseAdapter } from 'payload'
|
||||
|
||||
import type { Args, PostgresAdapter } from './types.js'
|
||||
|
||||
import { connect } from './connect.js'
|
||||
import { count } from './count.js'
|
||||
import { create } from './create.js'
|
||||
import { createGlobal } from './createGlobal.js'
|
||||
import { createGlobalVersion } from './createGlobalVersion.js'
|
||||
import { countDistinct } from './countDistinct.js'
|
||||
import { convertPathToJSONTraversal } from './createJSONQuery/convertPathToJSONTraversal.js'
|
||||
import { createJSONQuery } from './createJSONQuery/index.js'
|
||||
import { createMigration } from './createMigration.js'
|
||||
import { createVersion } from './createVersion.js'
|
||||
import { deleteMany } from './deleteMany.js'
|
||||
import { deleteOne } from './deleteOne.js'
|
||||
import { deleteVersions } from './deleteVersions.js'
|
||||
import { destroy } from './destroy.js'
|
||||
import { find } from './find.js'
|
||||
import { findGlobal } from './findGlobal.js'
|
||||
import { findGlobalVersions } from './findGlobalVersions.js'
|
||||
import { findOne } from './findOne.js'
|
||||
import { findVersions } from './findVersions.js'
|
||||
import { defaultDrizzleSnapshot } from './defaultSnapshot.js'
|
||||
import { deleteWhere } from './deleteWhere.js'
|
||||
import { dropDatabase } from './dropDatabase.js'
|
||||
import { execute } from './execute.js'
|
||||
import { getMigrationTemplate } from './getMigrationTemplate.js'
|
||||
import { init } from './init.js'
|
||||
import { migrate } from './migrate.js'
|
||||
import { migrateDown } from './migrateDown.js'
|
||||
import { migrateFresh } from './migrateFresh.js'
|
||||
import { migrateRefresh } from './migrateRefresh.js'
|
||||
import { migrateReset } from './migrateReset.js'
|
||||
import { migrateStatus } from './migrateStatus.js'
|
||||
import { queryDrafts } from './queryDrafts.js'
|
||||
import { beginTransaction } from './transactions/beginTransaction.js'
|
||||
import { commitTransaction } from './transactions/commitTransaction.js'
|
||||
import { rollbackTransaction } from './transactions/rollbackTransaction.js'
|
||||
import { updateOne } from './update.js'
|
||||
import { updateGlobal } from './updateGlobal.js'
|
||||
import { updateGlobalVersion } from './updateGlobalVersion.js'
|
||||
import { updateVersion } from './updateVersion.js'
|
||||
import { insert } from './insert.js'
|
||||
import { requireDrizzleKit } from './requireDrizzleKit.js'
|
||||
|
||||
export type { MigrateDownArgs, MigrateUpArgs } from './types.js'
|
||||
|
||||
@@ -58,13 +70,19 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
|
||||
|
||||
return createDatabaseAdapter<PostgresAdapter>({
|
||||
name: 'postgres',
|
||||
defaultDrizzleSnapshot,
|
||||
drizzle: undefined,
|
||||
enums: {},
|
||||
features: {
|
||||
json: true,
|
||||
},
|
||||
fieldConstraints: {},
|
||||
getMigrationTemplate,
|
||||
idType: postgresIDType,
|
||||
initializing,
|
||||
localesSuffix: args.localesSuffix || '_locales',
|
||||
logger: args.logger,
|
||||
operators: operatorMap,
|
||||
pgSchema: undefined,
|
||||
pool: undefined,
|
||||
poolOptions: args.pool,
|
||||
@@ -76,29 +94,37 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
|
||||
sessions: {},
|
||||
tableNameMap: new Map<string, string>(),
|
||||
tables: {},
|
||||
transactionOptions: args.transactionOptions || undefined,
|
||||
versionsSuffix: args.versionsSuffix || '_v',
|
||||
|
||||
// DatabaseAdapter
|
||||
beginTransaction,
|
||||
beginTransaction: args.transactionOptions === false ? undefined : beginTransaction,
|
||||
commitTransaction,
|
||||
connect,
|
||||
convertPathToJSONTraversal,
|
||||
count,
|
||||
countDistinct,
|
||||
create,
|
||||
createGlobal,
|
||||
createGlobalVersion,
|
||||
createJSONQuery,
|
||||
createMigration,
|
||||
createVersion,
|
||||
defaultIDType: payloadIDType,
|
||||
deleteMany,
|
||||
deleteOne,
|
||||
deleteVersions,
|
||||
deleteWhere,
|
||||
destroy,
|
||||
dropDatabase,
|
||||
execute,
|
||||
find,
|
||||
findGlobal,
|
||||
findGlobalVersions,
|
||||
findOne,
|
||||
findVersions,
|
||||
init,
|
||||
insert,
|
||||
migrate,
|
||||
migrateDown,
|
||||
migrateFresh,
|
||||
@@ -109,6 +135,7 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
|
||||
payload,
|
||||
queryDrafts,
|
||||
rejectInitializing,
|
||||
requireDrizzleKit,
|
||||
resolveInitializing,
|
||||
rollbackTransaction,
|
||||
updateGlobal,
|
||||
@@ -123,42 +150,3 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
|
||||
init: adapter,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to find migrations directory.
|
||||
*
|
||||
* Checks for the following directories in order:
|
||||
* - `migrationDir` argument from Payload config
|
||||
* - `src/migrations`
|
||||
* - `dist/migrations`
|
||||
* - `migrations`
|
||||
*
|
||||
* Defaults to `src/migrations`
|
||||
*
|
||||
* @param migrationDir
|
||||
* @returns
|
||||
*/
|
||||
function findMigrationDir(migrationDir?: string): string {
|
||||
const cwd = process.cwd()
|
||||
const srcDir = path.resolve(cwd, 'src/migrations')
|
||||
const distDir = path.resolve(cwd, 'dist/migrations')
|
||||
const relativeMigrations = path.resolve(cwd, 'migrations')
|
||||
|
||||
// Use arg if provided
|
||||
if (migrationDir) return migrationDir
|
||||
|
||||
// Check other common locations
|
||||
if (fs.existsSync(srcDir)) {
|
||||
return srcDir
|
||||
}
|
||||
|
||||
if (fs.existsSync(distDir)) {
|
||||
return distDir
|
||||
}
|
||||
|
||||
if (fs.existsSync(relativeMigrations)) {
|
||||
return relativeMigrations
|
||||
}
|
||||
|
||||
return srcDir
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import type { SanitizedCollectionConfig } from 'payload'
|
||||
import type { Init } from 'payload'
|
||||
import type { Init, SanitizedCollectionConfig } from 'payload'
|
||||
|
||||
import { createTableName } from '@payloadcms/drizzle'
|
||||
import { pgEnum, pgSchema, pgTable } from 'drizzle-orm/pg-core'
|
||||
import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
@@ -9,7 +8,6 @@ import toSnakeCase from 'to-snake-case'
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { buildTable } from './schema/build.js'
|
||||
import { createTableName } from './schema/createTableName.js'
|
||||
|
||||
export const init: Init = function init(this: PostgresAdapter) {
|
||||
if (this.schemaName) {
|
||||
@@ -17,7 +15,6 @@ export const init: Init = function init(this: PostgresAdapter) {
|
||||
} else {
|
||||
this.pgSchema = { table: pgTable }
|
||||
}
|
||||
|
||||
if (this.payload.config.localization) {
|
||||
this.enums.enum__locales = pgEnum(
|
||||
'_locales',
|
||||
|
||||
25
packages/db-postgres/src/insert.ts
Normal file
25
packages/db-postgres/src/insert.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { TransactionPg } from '@payloadcms/drizzle/types'
|
||||
|
||||
import type { Insert } from './types.js'
|
||||
|
||||
export const insert: Insert = async function insert({
|
||||
db,
|
||||
onConflictDoUpdate,
|
||||
tableName,
|
||||
values,
|
||||
}): Promise<Record<string, unknown>[]> {
|
||||
const table = this.tables[tableName]
|
||||
let result
|
||||
|
||||
if (onConflictDoUpdate) {
|
||||
result = await (db as TransactionPg)
|
||||
.insert(table)
|
||||
.values(values)
|
||||
.onConflictDoUpdate(onConflictDoUpdate)
|
||||
.returning()
|
||||
} else {
|
||||
result = await (db as TransactionPg).insert(table).values(values).returning()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -1,15 +1,17 @@
|
||||
import type { TransactionPg } from '@payloadcms/drizzle/types'
|
||||
import type { Field, Payload, PayloadRequest } from 'payload'
|
||||
|
||||
import type { DrizzleTransaction, PostgresAdapter } from '../../../types.js'
|
||||
import { upsertRow } from '@payloadcms/drizzle'
|
||||
|
||||
import type { PostgresAdapter } from '../../../types.js'
|
||||
import type { DocsToResave } from '../types.js'
|
||||
|
||||
import { upsertRow } from '../../../upsertRow/index.js'
|
||||
import { traverseFields } from './traverseFields.js'
|
||||
|
||||
type Args = {
|
||||
adapter: PostgresAdapter
|
||||
collectionSlug?: string
|
||||
db: DrizzleTransaction
|
||||
db: TransactionPg
|
||||
debug: boolean
|
||||
docsToResave: DocsToResave
|
||||
fields: Field[]
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { TransactionPg } from '@payloadcms/drizzle/types'
|
||||
import type { DrizzleSnapshotJSON } from 'drizzle-kit/payload'
|
||||
import type { Payload, PayloadRequest } from 'payload'
|
||||
|
||||
@@ -37,8 +38,8 @@ type Args = {
|
||||
* @param req
|
||||
*/
|
||||
export const migratePostgresV2toV3 = async ({ debug, payload, req }: Args) => {
|
||||
const adapter = payload.db as PostgresAdapter
|
||||
const db = adapter.sessions[await req.transactionID]?.db
|
||||
const adapter = payload.db as unknown as PostgresAdapter
|
||||
const db = adapter.sessions[await req.transactionID].db as TransactionPg
|
||||
const dir = payload.db.migrationDir
|
||||
|
||||
// get the drizzle migrateUpSQL from drizzle using the last schema
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { TransactionPg } from '@payloadcms/drizzle/types'
|
||||
import type { Field, Payload, PayloadRequest } from 'payload'
|
||||
|
||||
import { sql } from 'drizzle-orm'
|
||||
|
||||
import type { DrizzleTransaction, PostgresAdapter } from '../../types.js'
|
||||
import type { PostgresAdapter } from '../../types.js'
|
||||
import type { DocsToResave, PathsToQuery } from './types.js'
|
||||
|
||||
import { fetchAndResave } from './fetchAndResave/index.js'
|
||||
@@ -10,7 +11,7 @@ import { fetchAndResave } from './fetchAndResave/index.js'
|
||||
type Args = {
|
||||
adapter: PostgresAdapter
|
||||
collectionSlug?: string
|
||||
db: DrizzleTransaction
|
||||
db: TransactionPg
|
||||
debug: boolean
|
||||
fields: Field[]
|
||||
globalSlug?: string
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import type { TransactionPg } from '@payloadcms/drizzle/types'
|
||||
import type { Field, Payload } from 'payload'
|
||||
|
||||
import { tabHasName } from 'payload/shared'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { DrizzleTransaction, PostgresAdapter } from '../../types.js'
|
||||
import type { PostgresAdapter } from '../../types.js'
|
||||
import type { PathsToQuery } from './types.js'
|
||||
|
||||
type Args = {
|
||||
adapter: PostgresAdapter
|
||||
collectionSlug?: string
|
||||
columnPrefix: string
|
||||
db: DrizzleTransaction
|
||||
db: TransactionPg
|
||||
disableNotNull: boolean
|
||||
fields: Field[]
|
||||
globalSlug?: string
|
||||
|
||||
5
packages/db-postgres/src/requireDrizzleKit.ts
Normal file
5
packages/db-postgres/src/requireDrizzleKit.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import type { RequireDrizzleKit } from '@payloadcms/drizzle/types'
|
||||
|
||||
import { createRequire } from 'module'
|
||||
const require = createRequire(import.meta.url)
|
||||
export const requireDrizzleKit: RequireDrizzleKit = () => require('drizzle-kit/payload')
|
||||
@@ -9,6 +9,7 @@ import type {
|
||||
} from 'drizzle-orm/pg-core'
|
||||
import type { Field } from 'payload'
|
||||
|
||||
import { createTableName } from '@payloadcms/drizzle'
|
||||
import { relations } from 'drizzle-orm'
|
||||
import {
|
||||
foreignKey,
|
||||
@@ -24,7 +25,6 @@ import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { GenericColumns, GenericTable, IDType, PostgresAdapter } from '../types.js'
|
||||
|
||||
import { createTableName } from './createTableName.js'
|
||||
import { parentIDColumnMap } from './parentIDColumnMap.js'
|
||||
import { setColumnID } from './setColumnID.js'
|
||||
import { traverseFields } from './traverseFields.js'
|
||||
|
||||
@@ -3,6 +3,11 @@ import type { Relation } from 'drizzle-orm'
|
||||
import type { IndexBuilder, PgColumnBuilder } from 'drizzle-orm/pg-core'
|
||||
import type { Field, TabAsField } from 'payload'
|
||||
|
||||
import {
|
||||
createTableName,
|
||||
hasLocalesTable,
|
||||
validateExistingBlockIsIdentical,
|
||||
} from '@payloadcms/drizzle'
|
||||
import { relations } from 'drizzle-orm'
|
||||
import {
|
||||
PgNumericBuilder,
|
||||
@@ -26,13 +31,10 @@ import toSnakeCase from 'to-snake-case'
|
||||
import type { GenericColumns, IDType, PostgresAdapter } from '../types.js'
|
||||
import type { BaseExtraConfig, RelationMap } from './build.js'
|
||||
|
||||
import { hasLocalesTable } from '../utilities/hasLocalesTable.js'
|
||||
import { buildTable } from './build.js'
|
||||
import { createIndex } from './createIndex.js'
|
||||
import { createTableName } from './createTableName.js'
|
||||
import { idToUUID } from './idToUUID.js'
|
||||
import { parentIDColumnMap } from './parentIDColumnMap.js'
|
||||
import { validateExistingBlockIsIdentical } from './validateExistingBlockIsIdentical.js'
|
||||
|
||||
type Args = {
|
||||
adapter: PostgresAdapter
|
||||
|
||||
@@ -1,24 +1,30 @@
|
||||
import type { Operators } from '@payloadcms/drizzle'
|
||||
import type {
|
||||
BuildQueryJoinAliases,
|
||||
DrizzleAdapter,
|
||||
TransactionPg,
|
||||
} from '@payloadcms/drizzle/types'
|
||||
import type { DrizzleSnapshotJSON } from 'drizzle-kit/payload'
|
||||
import type {
|
||||
ColumnBaseConfig,
|
||||
ColumnDataType,
|
||||
DrizzleConfig,
|
||||
ExtractTablesWithRelations,
|
||||
Relation,
|
||||
Relations,
|
||||
SQL,
|
||||
} from 'drizzle-orm'
|
||||
import type { NodePgDatabase, NodePgQueryResultHKT } from 'drizzle-orm/node-postgres'
|
||||
import type { NodePgDatabase } from 'drizzle-orm/node-postgres'
|
||||
import type {
|
||||
PgColumn,
|
||||
PgEnum,
|
||||
PgInsertOnConflictDoUpdateConfig,
|
||||
PgSchema,
|
||||
PgTableWithColumns,
|
||||
PgTransaction,
|
||||
PgTransactionConfig,
|
||||
} from 'drizzle-orm/pg-core'
|
||||
import type { PgTableFn } from 'drizzle-orm/pg-core/table'
|
||||
import type { BaseDatabaseAdapter, Payload, PayloadRequest } from 'payload'
|
||||
import type { Pool, PoolConfig } from 'pg'
|
||||
|
||||
export type DrizzleDB = NodePgDatabase<Record<string, unknown>>
|
||||
import type { Payload, PayloadRequest } from 'payload'
|
||||
import type { Pool, PoolConfig, QueryResult } from 'pg'
|
||||
|
||||
export type Args = {
|
||||
idType?: 'serial' | 'uuid'
|
||||
@@ -28,7 +34,12 @@ export type Args = {
|
||||
pool: PoolConfig
|
||||
push?: boolean
|
||||
relationshipsSuffix?: string
|
||||
/**
|
||||
* The schema name to use for the database
|
||||
* @experimental This only works when there are not other tables or enums of the same name in the database under a different schema. Awaiting fix from Drizzle.
|
||||
*/
|
||||
schemaName?: string
|
||||
transactionOptions?: PgTransactionConfig | false
|
||||
versionsSuffix?: string
|
||||
}
|
||||
|
||||
@@ -45,22 +56,64 @@ export type GenericTable = PgTableWithColumns<{
|
||||
columns: GenericColumns
|
||||
dialect: string
|
||||
name: string
|
||||
schema: undefined
|
||||
schema: string
|
||||
}>
|
||||
|
||||
export type GenericEnum = PgEnum<[string, ...string[]]>
|
||||
|
||||
export type GenericRelation = Relations<string, Record<string, Relation<string>>>
|
||||
|
||||
export type DrizzleTransaction = PgTransaction<
|
||||
NodePgQueryResultHKT,
|
||||
Record<string, unknown>,
|
||||
ExtractTablesWithRelations<Record<string, unknown>>
|
||||
export type PostgresDB = NodePgDatabase<Record<string, unknown>>
|
||||
|
||||
export type CountDistinct = (args: {
|
||||
db: PostgresDB | TransactionPg
|
||||
joins: BuildQueryJoinAliases
|
||||
tableName: string
|
||||
where: SQL
|
||||
}) => Promise<number>
|
||||
|
||||
export type DeleteWhere = (args: {
|
||||
db: PostgresDB | TransactionPg
|
||||
tableName: string
|
||||
where: SQL
|
||||
}) => Promise<void>
|
||||
|
||||
export type DropDatabase = (args: { adapter: PostgresAdapter }) => Promise<void>
|
||||
|
||||
export type Execute<T> = (args: {
|
||||
db?: PostgresDB | TransactionPg
|
||||
drizzle?: PostgresDB
|
||||
raw?: string
|
||||
sql?: SQL<unknown>
|
||||
}) => Promise<QueryResult<Record<string, T>>>
|
||||
|
||||
export type Insert = (args: {
|
||||
db: PostgresDB | TransactionPg
|
||||
onConflictDoUpdate?: PgInsertOnConflictDoUpdateConfig<any>
|
||||
tableName: string
|
||||
values: Record<string, unknown> | Record<string, unknown>[]
|
||||
}) => Promise<Record<string, unknown>[]>
|
||||
|
||||
type PostgresDrizzleAdapter = Omit<
|
||||
DrizzleAdapter,
|
||||
| 'countDistinct'
|
||||
| 'deleteWhere'
|
||||
| 'drizzle'
|
||||
| 'dropDatabase'
|
||||
| 'execute'
|
||||
| 'insert'
|
||||
| 'operators'
|
||||
| 'relations'
|
||||
>
|
||||
|
||||
export type PostgresAdapter = BaseDatabaseAdapter & {
|
||||
drizzle: DrizzleDB
|
||||
export type PostgresAdapter = {
|
||||
countDistinct: CountDistinct
|
||||
defaultDrizzleSnapshot: DrizzleSnapshotJSON
|
||||
deleteWhere: DeleteWhere
|
||||
drizzle: PostgresDB
|
||||
dropDatabase: DropDatabase
|
||||
enums: Record<string, GenericEnum>
|
||||
execute: Execute<unknown>
|
||||
/**
|
||||
* An object keyed on each table, with a key value pair where the constraint name is the key, followed by the dot-notation field name
|
||||
* Used for returning properly formed errors from unique fields
|
||||
@@ -68,8 +121,10 @@ export type PostgresAdapter = BaseDatabaseAdapter & {
|
||||
fieldConstraints: Record<string, Record<string, string>>
|
||||
idType: Args['idType']
|
||||
initializing: Promise<void>
|
||||
insert: Insert
|
||||
localesSuffix?: string
|
||||
logger: DrizzleConfig['logger']
|
||||
operators: Operators
|
||||
pgSchema?: { table: PgTableFn } | PgSchema
|
||||
pool: Pool
|
||||
poolOptions: Args['pool']
|
||||
@@ -82,44 +137,46 @@ export type PostgresAdapter = BaseDatabaseAdapter & {
|
||||
schemaName?: Args['schemaName']
|
||||
sessions: {
|
||||
[id: string]: {
|
||||
db: DrizzleTransaction
|
||||
db: PostgresDB | TransactionPg
|
||||
reject: () => Promise<void>
|
||||
resolve: () => Promise<void>
|
||||
}
|
||||
}
|
||||
tableNameMap: Map<string, string>
|
||||
tables: Record<string, GenericTable | PgTableWithColumns<any>>
|
||||
tables: Record<string, GenericTable>
|
||||
versionsSuffix?: string
|
||||
}
|
||||
} & PostgresDrizzleAdapter
|
||||
|
||||
export type IDType = 'integer' | 'numeric' | 'uuid' | 'varchar'
|
||||
|
||||
export type PostgresAdapterResult = (args: { payload: Payload }) => PostgresAdapter
|
||||
|
||||
export type MigrateUpArgs = { payload: Payload; req?: Partial<PayloadRequest> }
|
||||
export type MigrateDownArgs = { payload: Payload; req?: Partial<PayloadRequest> }
|
||||
|
||||
declare module 'payload' {
|
||||
export interface DatabaseAdapter
|
||||
extends Omit<Args, 'migrationDir' | 'pool'>,
|
||||
BaseDatabaseAdapter {
|
||||
drizzle: DrizzleDB
|
||||
extends Omit<Args, 'idType' | 'logger' | 'migrationDir' | 'pool'>,
|
||||
DrizzleAdapter {
|
||||
drizzle: PostgresDB
|
||||
enums: Record<string, GenericEnum>
|
||||
/**
|
||||
* An object keyed on each table, with a key value pair where the constraint name is the key, followed by the dot-notation field name
|
||||
* Used for returning properly formed errors from unique fields
|
||||
*/
|
||||
fieldConstraints: Record<string, Record<string, string>>
|
||||
localeSuffix?: string
|
||||
idType: Args['idType']
|
||||
initializing: Promise<void>
|
||||
localesSuffix?: string
|
||||
logger: DrizzleConfig['logger']
|
||||
pgSchema?: { table: PgTableFn } | PgSchema
|
||||
pool: Pool
|
||||
poolOptions: Args['pool']
|
||||
push: boolean
|
||||
relations: Record<string, GenericRelation>
|
||||
rejectInitializing: () => void
|
||||
relationshipsSuffix?: string
|
||||
resolveInitializing: () => void
|
||||
schema: Record<string, GenericEnum | GenericRelation | GenericTable>
|
||||
sessions: {
|
||||
[id: string]: {
|
||||
db: DrizzleTransaction
|
||||
reject: () => Promise<void>
|
||||
resolve: () => Promise<void>
|
||||
}
|
||||
}
|
||||
tables: Record<string, GenericTable>
|
||||
schemaName?: Args['schemaName']
|
||||
tableNameMap: Map<string, string>
|
||||
versionsSuffix?: string
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import { sql } from 'drizzle-orm'
|
||||
|
||||
import type { PostgresAdapter } from '../types.js'
|
||||
|
||||
export const createMigrationTable = async (adapter: PostgresAdapter): Promise<void> => {
|
||||
const prependSchema = adapter.schemaName ? `"${adapter.schemaName}".` : ''
|
||||
|
||||
await adapter.drizzle.execute(
|
||||
sql.raw(`CREATE TABLE IF NOT EXISTS ${prependSchema}"payload_migrations" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"name" varchar,
|
||||
"batch" numeric,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
|
||||
);`),
|
||||
)
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import { sql } from 'drizzle-orm'
|
||||
|
||||
import type { DrizzleDB } from '../types.js'
|
||||
|
||||
export const migrationTableExists = async (db: DrizzleDB): Promise<boolean> => {
|
||||
const queryRes = await db.execute(sql`SELECT to_regclass('public.payload_migrations');`)
|
||||
|
||||
// Returns table name 'payload_migrations' or null
|
||||
const exists = queryRes.rows?.[0]?.to_regclass === 'payload_migrations'
|
||||
return exists
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { numeric, timestamp, varchar } from 'drizzle-orm/pg-core'
|
||||
import { createRequire } from 'module'
|
||||
import prompts from 'prompts'
|
||||
|
||||
import type { PostgresAdapter } from '../types.js'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
|
||||
/**
|
||||
* Pushes the development schema to the database using Drizzle.
|
||||
*
|
||||
* @param {PostgresAdapter} db - The PostgresAdapter instance connected to the database.
|
||||
* @returns {Promise<void>} - A promise that resolves once the schema push is complete.
|
||||
*/
|
||||
export const pushDevSchema = async (db: PostgresAdapter) => {
|
||||
const { pushSchema } = require('drizzle-kit/payload')
|
||||
|
||||
// This will prompt if clarifications are needed for Drizzle to push new schema
|
||||
const { apply, hasDataLoss, warnings } = await pushSchema(db.schema, db.drizzle)
|
||||
|
||||
if (warnings.length) {
|
||||
let message = `Warnings detected during schema push: \n\n${warnings.join('\n')}\n\n`
|
||||
|
||||
if (hasDataLoss) {
|
||||
message += `DATA LOSS WARNING: Possible data loss detected if schema is pushed.\n\n`
|
||||
}
|
||||
|
||||
message += `Accept warnings and push schema to database?`
|
||||
|
||||
const { confirm: acceptWarnings } = await prompts(
|
||||
{
|
||||
name: 'confirm',
|
||||
type: 'confirm',
|
||||
initial: false,
|
||||
message,
|
||||
},
|
||||
{
|
||||
onCancel: () => {
|
||||
process.exit(0)
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
// Exit if user does not accept warnings.
|
||||
// Q: Is this the right type of exit for this interaction?
|
||||
if (!acceptWarnings) {
|
||||
process.exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
await apply()
|
||||
|
||||
// Migration table def in order to use query using drizzle
|
||||
const migrationsSchema = db.pgSchema.table('payload_migrations', {
|
||||
name: varchar('name'),
|
||||
batch: numeric('batch'),
|
||||
created_at: timestamp('created_at'),
|
||||
updated_at: timestamp('updated_at'),
|
||||
})
|
||||
|
||||
const devPush = await db.drizzle
|
||||
.select()
|
||||
.from(migrationsSchema)
|
||||
.where(eq(migrationsSchema.batch, '-1'))
|
||||
|
||||
if (!devPush.length) {
|
||||
await db.drizzle.insert(migrationsSchema).values({
|
||||
name: 'dev',
|
||||
batch: '-1',
|
||||
})
|
||||
} else {
|
||||
await db.drizzle
|
||||
.update(migrationsSchema)
|
||||
.set({
|
||||
updated_at: new Date(),
|
||||
})
|
||||
.where(eq(migrationsSchema.batch, '-1'))
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,15 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true, // Make sure typescript knows that this module depends on their references
|
||||
"noEmit": false /* Do not emit outputs. */,
|
||||
// Make sure typescript knows that this module depends on their references
|
||||
"composite": true,
|
||||
/* Do not emit outputs. */
|
||||
"noEmit": false,
|
||||
"emitDeclarationOnly": true,
|
||||
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
|
||||
"rootDir": "./src" /* Specify the root folder within your source files. */
|
||||
/* Specify an output folder for all emitted files. */
|
||||
"outDir": "./dist",
|
||||
/* Specify the root folder within your source files. */
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"exclude": [
|
||||
"dist",
|
||||
@@ -19,6 +23,19 @@
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.spec.tsx"
|
||||
],
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.d.ts", "src/**/*.json"],
|
||||
"references": [{ "path": "../payload" }, { "path": "../translations" }]
|
||||
"include": [
|
||||
"src",
|
||||
"src/**/*.ts",
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../payload"
|
||||
},
|
||||
{
|
||||
"path": "../translations"
|
||||
},
|
||||
{
|
||||
"path": "../drizzle"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
10
packages/db-sqlite/.eslintignore
Normal file
10
packages/db-sqlite/.eslintignore
Normal file
@@ -0,0 +1,10 @@
|
||||
.tmp
|
||||
**/.git
|
||||
**/.hg
|
||||
**/.pnp.*
|
||||
**/.svn
|
||||
**/.yarn/**
|
||||
**/build
|
||||
**/dist/**
|
||||
**/node_modules
|
||||
**/temp
|
||||
7
packages/db-sqlite/.eslintrc.cjs
Normal file
7
packages/db-sqlite/.eslintrc.cjs
Normal file
@@ -0,0 +1,7 @@
|
||||
/** @type {import('eslint').Linter.Config} */
|
||||
module.exports = {
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.json'],
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
}
|
||||
1
packages/db-sqlite/.gitignore
vendored
Normal file
1
packages/db-sqlite/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/migrations
|
||||
10
packages/db-sqlite/.prettierignore
Normal file
10
packages/db-sqlite/.prettierignore
Normal file
@@ -0,0 +1,10 @@
|
||||
.tmp
|
||||
**/.git
|
||||
**/.hg
|
||||
**/.pnp.*
|
||||
**/.svn
|
||||
**/.yarn/**
|
||||
**/build
|
||||
**/dist/**
|
||||
**/node_modules
|
||||
**/temp
|
||||
15
packages/db-sqlite/.swcrc
Normal file
15
packages/db-sqlite/.swcrc
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/swcrc",
|
||||
"sourceMaps": true,
|
||||
"jsc": {
|
||||
"target": "esnext",
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"tsx": true,
|
||||
"dts": true
|
||||
}
|
||||
},
|
||||
"module": {
|
||||
"type": "es6"
|
||||
}
|
||||
}
|
||||
30
packages/db-sqlite/README.md
Normal file
30
packages/db-sqlite/README.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Payload Postgres Adapter
|
||||
|
||||
Official SQLite adapter for [Payload](https://payloadcms.com).
|
||||
|
||||
- [Main Repository](https://github.com/payloadcms/payload)
|
||||
- [Payload Docs](https://payloadcms.com/docs)
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @payloadcms/db-sqlite
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { buildConfig } from 'payload/config'
|
||||
import { sqliteAdapter } from '@payloadcms/db-sqlite'
|
||||
|
||||
export default buildConfig({
|
||||
db: sqliteAdapter({
|
||||
client: {
|
||||
url: process.env.DATABASE_URI,
|
||||
},
|
||||
}),
|
||||
// ...rest of config
|
||||
})
|
||||
```
|
||||
|
||||
More detailed usage can be found in the [Payload Docs](https://payloadcms.com/docs/configuration/overview).
|
||||
38
packages/db-sqlite/bundle.js
Normal file
38
packages/db-sqlite/bundle.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import * as esbuild from 'esbuild'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
import { commonjs } from '@hyrious/esbuild-plugin-commonjs'
|
||||
|
||||
async function build() {
|
||||
const resultServer = await esbuild.build({
|
||||
entryPoints: ['src/index.ts'],
|
||||
bundle: true,
|
||||
platform: 'node',
|
||||
format: 'esm',
|
||||
outfile: 'dist/index.js',
|
||||
splitting: false,
|
||||
external: [
|
||||
'*.scss',
|
||||
'*.css',
|
||||
'drizzle-kit',
|
||||
'libsql',
|
||||
'pg',
|
||||
'@payloadcms/translations',
|
||||
'@payloadcms/drizzle',
|
||||
'payload',
|
||||
'payload/*',
|
||||
],
|
||||
minify: true,
|
||||
metafile: true,
|
||||
tsconfig: path.resolve(dirname, './tsconfig.json'),
|
||||
plugins: [commonjs()],
|
||||
sourcemap: true,
|
||||
})
|
||||
console.log('db-sqlite bundled successfully')
|
||||
|
||||
fs.writeFileSync('meta_server.json', JSON.stringify(resultServer.metafile))
|
||||
}
|
||||
await build()
|
||||
85
packages/db-sqlite/package.json
Normal file
85
packages/db-sqlite/package.json
Normal file
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"name": "@payloadcms/db-sqlite",
|
||||
"version": "3.0.0-beta.71",
|
||||
"description": "The officially supported SQLite database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/payloadcms/payload.git",
|
||||
"directory": "packages/db-sqlite"
|
||||
},
|
||||
"license": "MIT",
|
||||
"author": "Payload <dev@payloadcms.com> (https://payloadcms.com)",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./src/index.ts",
|
||||
"require": "./src/index.ts",
|
||||
"types": "./src/index.ts"
|
||||
},
|
||||
"./types": {
|
||||
"import": "./src/types.ts",
|
||||
"require": "./src/types.ts",
|
||||
"types": "./src/types.ts"
|
||||
},
|
||||
"./migration-utils": {
|
||||
"import": "./src/exports/migration-utils.ts",
|
||||
"require": "./src/exports/migration-utils.ts",
|
||||
"types": "./src/exports/migration-utils.ts"
|
||||
}
|
||||
},
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/types.ts",
|
||||
"files": [
|
||||
"dist",
|
||||
"mock.js"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "pnpm build:swc && pnpm build:types",
|
||||
"build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
|
||||
"build:types": "tsc --emitDeclarationOnly --outDir dist",
|
||||
"clean": "rimraf {dist,*.tsbuildinfo}",
|
||||
"prepack": "pnpm clean && pnpm turbo build",
|
||||
"prepublishOnly": "pnpm clean && pnpm turbo build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@libsql/client": "^0.6.2",
|
||||
"@payloadcms/drizzle": "workspace:*",
|
||||
"console-table-printer": "2.11.2",
|
||||
"drizzle-kit": "0.20.14-1f2c838",
|
||||
"drizzle-orm": "0.29.4",
|
||||
"prompts": "2.4.2",
|
||||
"to-snake-case": "1.0.0",
|
||||
"uuid": "9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@types/pg": "8.10.2",
|
||||
"@types/to-snake-case": "1.0.0",
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
},
|
||||
"./types": {
|
||||
"import": "./dist/types.js",
|
||||
"require": "./dist/types.js",
|
||||
"types": "./dist/types.d.ts"
|
||||
},
|
||||
"./migration-utils": {
|
||||
"import": "./dist/exports/migration-utils.js",
|
||||
"require": "./dist/exports/migration-utils.js",
|
||||
"types": "./dist/exports/migration-utils.d.ts"
|
||||
}
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
}
|
||||
55
packages/db-sqlite/src/connect.ts
Normal file
55
packages/db-sqlite/src/connect.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import type { DrizzleAdapter } from '@payloadcms/drizzle/types'
|
||||
import type { LibSQLDatabase } from 'drizzle-orm/libsql'
|
||||
import type { Connect } from 'payload'
|
||||
|
||||
import { createClient } from '@libsql/client'
|
||||
import { pushDevSchema } from '@payloadcms/drizzle'
|
||||
import { drizzle } from 'drizzle-orm/libsql'
|
||||
|
||||
import type { SQLiteAdapter } from './types.js'
|
||||
|
||||
export const connect: Connect = async function connect(
|
||||
this: SQLiteAdapter,
|
||||
options = {
|
||||
hotReload: false,
|
||||
},
|
||||
) {
|
||||
const { hotReload } = options
|
||||
|
||||
this.schema = {
|
||||
...this.tables,
|
||||
...this.relations,
|
||||
}
|
||||
|
||||
try {
|
||||
if (!this.client) {
|
||||
this.client = createClient(this.clientConfig)
|
||||
}
|
||||
|
||||
const logger = this.logger || false
|
||||
this.drizzle = drizzle(this.client, { logger, schema: this.schema }) as LibSQLDatabase
|
||||
|
||||
if (!hotReload) {
|
||||
if (process.env.PAYLOAD_DROP_DATABASE === 'true') {
|
||||
this.payload.logger.info(`---- DROPPING TABLES ----`)
|
||||
await this.dropDatabase({ adapter: this })
|
||||
this.payload.logger.info('---- DROPPED TABLES ----')
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
this.payload.logger.error(`Error: cannot connect to SQLite. Details: ${err.message}`, err)
|
||||
if (typeof this.rejectInitializing === 'function') this.rejectInitializing()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Only push schema if not in production
|
||||
if (
|
||||
process.env.NODE_ENV !== 'production' &&
|
||||
process.env.PAYLOAD_MIGRATING !== 'true' &&
|
||||
this.push !== false
|
||||
) {
|
||||
await pushDevSchema(this as unknown as DrizzleAdapter)
|
||||
}
|
||||
|
||||
if (typeof this.resolveInitializing === 'function') this.resolveInitializing()
|
||||
}
|
||||
33
packages/db-sqlite/src/countDistinct.ts
Normal file
33
packages/db-sqlite/src/countDistinct.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { ChainedMethods } from '@payloadcms/drizzle/types'
|
||||
|
||||
import { chainMethods } from '@payloadcms/drizzle'
|
||||
import { sql } from 'drizzle-orm'
|
||||
|
||||
import type { CountDistinct, SQLiteAdapter } from './types.js'
|
||||
|
||||
export const countDistinct: CountDistinct = async function countDistinct(
|
||||
this: SQLiteAdapter,
|
||||
{ db, joins, tableName, where },
|
||||
) {
|
||||
const chainedMethods: ChainedMethods = []
|
||||
|
||||
joins.forEach(({ condition, table }) => {
|
||||
chainedMethods.push({
|
||||
args: [table, condition],
|
||||
method: 'leftJoin',
|
||||
})
|
||||
})
|
||||
|
||||
const countResult = await chainMethods({
|
||||
methods: chainedMethods,
|
||||
query: db
|
||||
.select({
|
||||
count: sql<number>`count
|
||||
(DISTINCT ${this.tables[tableName].id})`,
|
||||
})
|
||||
.from(this.tables[tableName])
|
||||
.where(where),
|
||||
})
|
||||
|
||||
return Number(countResult[0].count)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export const convertPathToJSONTraversal = (incomingSegments: string[]): string => {
|
||||
const segments = [...incomingSegments]
|
||||
segments.shift()
|
||||
|
||||
return segments.reduce((res, segment) => {
|
||||
const formattedSegment = Number.isNaN(parseInt(segment)) ? `'${segment}'` : segment
|
||||
return `${res}->>${formattedSegment}`
|
||||
}, '')
|
||||
}
|
||||
86
packages/db-sqlite/src/createJSONQuery/index.ts
Normal file
86
packages/db-sqlite/src/createJSONQuery/index.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import type { CreateJSONQueryArgs } from '@payloadcms/drizzle/types'
|
||||
|
||||
type FromArrayArgs = {
|
||||
isRoot?: true
|
||||
operator: string
|
||||
pathSegments: string[]
|
||||
table: string
|
||||
treatAsArray?: string[]
|
||||
value: boolean | number | string
|
||||
}
|
||||
|
||||
const fromArray = ({
|
||||
isRoot,
|
||||
operator,
|
||||
pathSegments,
|
||||
table,
|
||||
treatAsArray,
|
||||
value,
|
||||
}: FromArrayArgs) => {
|
||||
const newPathSegments = pathSegments.slice(1)
|
||||
const alias = `${pathSegments[isRoot ? 0 : 1]}_alias_${newPathSegments.length}`
|
||||
|
||||
return `EXISTS (
|
||||
SELECT 1
|
||||
FROM json_each(${table}.${pathSegments[0]}) AS ${alias}
|
||||
WHERE ${createJSONQuery({
|
||||
operator,
|
||||
pathSegments: newPathSegments,
|
||||
table: alias,
|
||||
treatAsArray,
|
||||
value,
|
||||
})}
|
||||
)`
|
||||
}
|
||||
|
||||
type CreateConstraintArgs = {
|
||||
alias?: string
|
||||
operator: string
|
||||
pathSegments: string[]
|
||||
treatAsArray?: string[]
|
||||
value: boolean | number | string
|
||||
}
|
||||
|
||||
const createConstraint = ({
|
||||
alias,
|
||||
operator,
|
||||
pathSegments,
|
||||
value,
|
||||
}: CreateConstraintArgs): string => {
|
||||
const newAlias = `${pathSegments[0]}_alias_${pathSegments.length - 1}`
|
||||
let formattedValue = value
|
||||
let formattedOperator = operator
|
||||
|
||||
if (['contains', 'like'].includes(operator)) {
|
||||
formattedOperator = 'like'
|
||||
formattedValue = `%${value}%`
|
||||
} else if (operator === 'equals') {
|
||||
formattedOperator = '='
|
||||
}
|
||||
|
||||
return `EXISTS (
|
||||
SELECT 1
|
||||
FROM json_each(${alias}.value -> '${pathSegments[0]}') AS ${newAlias}
|
||||
WHERE ${newAlias}.value ->> '${pathSegments[1]}' ${formattedOperator} '${formattedValue}'
|
||||
)`
|
||||
}
|
||||
|
||||
export const createJSONQuery = ({
|
||||
operator,
|
||||
pathSegments,
|
||||
table,
|
||||
treatAsArray,
|
||||
value,
|
||||
}: CreateJSONQueryArgs): string => {
|
||||
if (treatAsArray.includes(pathSegments[1])) {
|
||||
return fromArray({
|
||||
operator,
|
||||
pathSegments,
|
||||
table,
|
||||
treatAsArray,
|
||||
value,
|
||||
})
|
||||
}
|
||||
|
||||
return createConstraint({ alias: table, operator, pathSegments, treatAsArray, value })
|
||||
}
|
||||
116
packages/db-sqlite/src/createMigration.ts
Normal file
116
packages/db-sqlite/src/createMigration.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import type { DrizzleSnapshotJSON } from 'drizzle-kit/payload'
|
||||
import type { CreateMigration } from 'payload'
|
||||
|
||||
import fs from 'fs'
|
||||
import { createRequire } from 'module'
|
||||
import path from 'path'
|
||||
import { getPredefinedMigration } from 'payload'
|
||||
import prompts from 'prompts'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import type { SQLiteAdapter } from './types.js'
|
||||
|
||||
import { defaultDrizzleSnapshot } from './defaultSnapshot.js'
|
||||
import { getMigrationTemplate } from './getMigrationTemplate.js'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
|
||||
export const createMigration: CreateMigration = async function createMigration(
|
||||
this: SQLiteAdapter,
|
||||
{ file, forceAcceptWarning, migrationName, payload },
|
||||
) {
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
const dir = payload.db.migrationDir
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir)
|
||||
}
|
||||
const { generateSQLiteDrizzleJson, generateSQLiteMigration } = require('drizzle-kit/payload')
|
||||
const drizzleJsonAfter = await generateSQLiteDrizzleJson(this.schema)
|
||||
const [yyymmdd, hhmmss] = new Date().toISOString().split('T')
|
||||
const formattedDate = yyymmdd.replace(/\D/g, '')
|
||||
const formattedTime = hhmmss.split('.')[0].replace(/\D/g, '')
|
||||
let imports: string = ''
|
||||
let downSQL: string
|
||||
let upSQL: string
|
||||
;({ downSQL, imports, upSQL } = await getPredefinedMigration({
|
||||
dirname,
|
||||
file,
|
||||
migrationName,
|
||||
payload,
|
||||
}))
|
||||
|
||||
const timestamp = `${formattedDate}_${formattedTime}`
|
||||
|
||||
const name = migrationName || file?.split('/').slice(2).join('/')
|
||||
const fileName = `${timestamp}${name ? `_${name.replace(/\W/g, '_')}` : ''}`
|
||||
|
||||
const filePath = `${dir}/${fileName}`
|
||||
|
||||
let drizzleJsonBefore = defaultDrizzleSnapshot as any
|
||||
|
||||
if (!upSQL) {
|
||||
// Get latest migration snapshot
|
||||
const latestSnapshot = fs
|
||||
.readdirSync(dir)
|
||||
.filter((file) => file.endsWith('.json'))
|
||||
.sort()
|
||||
.reverse()?.[0]
|
||||
|
||||
if (latestSnapshot) {
|
||||
drizzleJsonBefore = JSON.parse(
|
||||
fs.readFileSync(`${dir}/${latestSnapshot}`, 'utf8'),
|
||||
) as DrizzleSnapshotJSON
|
||||
}
|
||||
|
||||
const sqlStatementsUp = await generateSQLiteMigration(drizzleJsonBefore, drizzleJsonAfter)
|
||||
const sqlStatementsDown = await generateSQLiteMigration(drizzleJsonAfter, drizzleJsonBefore)
|
||||
// need to create tables as separate statements
|
||||
const sqlExecute = 'await payload.db.drizzle.run(sql`'
|
||||
|
||||
if (sqlStatementsUp?.length) {
|
||||
upSQL = sqlStatementsUp
|
||||
.map((statement) => `${sqlExecute}${statement?.replaceAll('`', '\\`')}\`)`)
|
||||
.join('\n')
|
||||
}
|
||||
if (sqlStatementsDown?.length) {
|
||||
downSQL = sqlStatementsDown
|
||||
.map((statement) => `${sqlExecute}${statement?.replaceAll('`', '\\`')}\`)`)
|
||||
.join('\n')
|
||||
}
|
||||
|
||||
if (!upSQL?.length && !downSQL?.length && !forceAcceptWarning) {
|
||||
const { confirm: shouldCreateBlankMigration } = await prompts(
|
||||
{
|
||||
name: 'confirm',
|
||||
type: 'confirm',
|
||||
initial: false,
|
||||
message: 'No schema changes detected. Would you like to create a blank migration file?',
|
||||
},
|
||||
{
|
||||
onCancel: () => {
|
||||
process.exit(0)
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if (!shouldCreateBlankMigration) {
|
||||
process.exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
// write schema
|
||||
fs.writeFileSync(`${filePath}.json`, JSON.stringify(drizzleJsonAfter, null, 2))
|
||||
}
|
||||
|
||||
// write migration
|
||||
fs.writeFileSync(
|
||||
`${filePath}.ts`,
|
||||
getMigrationTemplate({
|
||||
downSQL: downSQL || ` // Migration code`,
|
||||
imports,
|
||||
upSQL: upSQL || ` // Migration code`,
|
||||
}),
|
||||
)
|
||||
payload.logger.info({ msg: `Migration created at ${filePath}.ts` })
|
||||
}
|
||||
14
packages/db-sqlite/src/defaultSnapshot.ts
Normal file
14
packages/db-sqlite/src/defaultSnapshot.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { DrizzleSQLiteSnapshotJSON } from 'drizzle-kit/payload'
|
||||
|
||||
export const defaultDrizzleSnapshot: DrizzleSQLiteSnapshotJSON = {
|
||||
id: '00000000-0000-0000-0000-000000000000',
|
||||
_meta: {
|
||||
columns: {},
|
||||
tables: {},
|
||||
},
|
||||
dialect: 'sqlite',
|
||||
enums: {},
|
||||
prevId: '00000000-0000-0000-0000-00000000000',
|
||||
tables: {},
|
||||
version: '5',
|
||||
}
|
||||
6
packages/db-sqlite/src/deleteWhere.ts
Normal file
6
packages/db-sqlite/src/deleteWhere.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import type { DeleteWhere } from './types.js'
|
||||
|
||||
export const deleteWhere: DeleteWhere = async function deleteWhere({ db, tableName, where }) {
|
||||
const table = this.tables[tableName]
|
||||
await db.delete(table).where(where)
|
||||
}
|
||||
21
packages/db-sqlite/src/dropDatabase.ts
Normal file
21
packages/db-sqlite/src/dropDatabase.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { DropDatabase } from './types.js'
|
||||
|
||||
const getTables = (adapter) => {
|
||||
return adapter.client.execute(`SELECT name
|
||||
FROM sqlite_master
|
||||
WHERE type = 'table'
|
||||
AND name NOT LIKE 'sqlite_%';`)
|
||||
}
|
||||
|
||||
const dropTables = (adapter, rows) => {
|
||||
const multi = `
|
||||
PRAGMA foreign_keys = OFF;\n
|
||||
${rows.map(({ name }) => `DROP TABLE IF EXISTS ${name}`).join(';\n ')};\n
|
||||
PRAGMA foreign_keys = ON;`
|
||||
return adapter.client.executeMultiple(multi)
|
||||
}
|
||||
|
||||
export const dropDatabase: DropDatabase = async function dropDatabase({ adapter }) {
|
||||
const result = await getTables(adapter)
|
||||
await dropTables(adapter, result.rows)
|
||||
}
|
||||
15
packages/db-sqlite/src/execute.ts
Normal file
15
packages/db-sqlite/src/execute.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { sql } from 'drizzle-orm'
|
||||
|
||||
import type { Execute } from './types.js'
|
||||
|
||||
export const execute: Execute<any> = function execute({ db, drizzle, raw, sql: statement }) {
|
||||
const executeFrom = db ?? drizzle
|
||||
|
||||
if (raw) {
|
||||
const result = executeFrom.run(sql.raw(raw))
|
||||
return result
|
||||
} else {
|
||||
const result = executeFrom.run(statement)
|
||||
return result
|
||||
}
|
||||
}
|
||||
16
packages/db-sqlite/src/getMigrationTemplate.ts
Normal file
16
packages/db-sqlite/src/getMigrationTemplate.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import type { MigrationTemplateArgs } from 'payload'
|
||||
|
||||
export const getMigrationTemplate = ({
|
||||
downSQL,
|
||||
imports,
|
||||
upSQL,
|
||||
}: MigrationTemplateArgs): string => `import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-sqlite'
|
||||
${imports ? `${imports}\n` : ''}
|
||||
export async function up({ payload, req }: MigrateUpArgs): Promise<void> {
|
||||
${upSQL}
|
||||
}
|
||||
|
||||
export async function down({ payload, req }: MigrateDownArgs): Promise<void> {
|
||||
${downSQL}
|
||||
}
|
||||
`
|
||||
159
packages/db-sqlite/src/index.ts
Normal file
159
packages/db-sqlite/src/index.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
import type { Operators } from '@payloadcms/drizzle'
|
||||
import type { DatabaseAdapterObj, Payload } from 'payload'
|
||||
|
||||
import {
|
||||
beginTransaction,
|
||||
commitTransaction,
|
||||
count,
|
||||
create,
|
||||
createGlobal,
|
||||
createGlobalVersion,
|
||||
createVersion,
|
||||
deleteMany,
|
||||
deleteOne,
|
||||
deleteVersions,
|
||||
destroy,
|
||||
find,
|
||||
findGlobal,
|
||||
findGlobalVersions,
|
||||
findMigrationDir,
|
||||
findOne,
|
||||
findVersions,
|
||||
migrate,
|
||||
migrateDown,
|
||||
migrateFresh,
|
||||
migrateRefresh,
|
||||
migrateReset,
|
||||
migrateStatus,
|
||||
operatorMap,
|
||||
queryDrafts,
|
||||
rollbackTransaction,
|
||||
updateGlobal,
|
||||
updateGlobalVersion,
|
||||
updateOne,
|
||||
updateVersion,
|
||||
} from '@payloadcms/drizzle'
|
||||
import { like } from 'drizzle-orm'
|
||||
import { createDatabaseAdapter } from 'payload'
|
||||
|
||||
import type { Args, SQLiteAdapter } from './types.js'
|
||||
|
||||
import { connect } from './connect.js'
|
||||
import { countDistinct } from './countDistinct.js'
|
||||
import { convertPathToJSONTraversal } from './createJSONQuery/convertPathToJSONTraversal.js'
|
||||
import { createJSONQuery } from './createJSONQuery/index.js'
|
||||
import { createMigration } from './createMigration.js'
|
||||
import { defaultDrizzleSnapshot } from './defaultSnapshot.js'
|
||||
import { deleteWhere } from './deleteWhere.js'
|
||||
import { dropDatabase } from './dropDatabase.js'
|
||||
import { execute } from './execute.js'
|
||||
import { getMigrationTemplate } from './getMigrationTemplate.js'
|
||||
import { init } from './init.js'
|
||||
import { insert } from './insert.js'
|
||||
import { requireDrizzleKit } from './requireDrizzleKit.js'
|
||||
|
||||
export type { MigrateDownArgs, MigrateUpArgs } from './types.js'
|
||||
|
||||
export { sql } from 'drizzle-orm'
|
||||
|
||||
export function sqliteAdapter(args: Args): DatabaseAdapterObj<SQLiteAdapter> {
|
||||
const postgresIDType = args.idType || 'serial'
|
||||
const payloadIDType = postgresIDType === 'serial' ? 'number' : 'text'
|
||||
|
||||
function adapter({ payload }: { payload: Payload }) {
|
||||
const migrationDir = findMigrationDir(args.migrationDir)
|
||||
let resolveInitializing
|
||||
let rejectInitializing
|
||||
|
||||
const initializing = new Promise<void>((res, rej) => {
|
||||
resolveInitializing = res
|
||||
rejectInitializing = rej
|
||||
})
|
||||
|
||||
// sqlite's like operator is case-insensitive, so we overwrite the DrizzleAdapter operators to not use ilike
|
||||
const operators = {
|
||||
...operatorMap,
|
||||
contains: like,
|
||||
like,
|
||||
} as unknown as Operators
|
||||
|
||||
return createDatabaseAdapter<SQLiteAdapter>({
|
||||
name: 'sqlite',
|
||||
client: undefined,
|
||||
clientConfig: args.client,
|
||||
defaultDrizzleSnapshot,
|
||||
drizzle: undefined,
|
||||
features: {
|
||||
json: true,
|
||||
},
|
||||
fieldConstraints: {},
|
||||
getMigrationTemplate,
|
||||
idType: postgresIDType,
|
||||
initializing,
|
||||
localesSuffix: args.localesSuffix || '_locales',
|
||||
logger: args.logger,
|
||||
operators,
|
||||
push: args.push,
|
||||
relations: {},
|
||||
relationshipsSuffix: args.relationshipsSuffix || '_rels',
|
||||
schema: {},
|
||||
schemaName: args.schemaName,
|
||||
sessions: {},
|
||||
tableNameMap: new Map<string, string>(),
|
||||
tables: {},
|
||||
transactionOptions: args.transactionOptions || undefined,
|
||||
versionsSuffix: args.versionsSuffix || '_v',
|
||||
|
||||
// DatabaseAdapter
|
||||
beginTransaction: args.transactionOptions === false ? undefined : beginTransaction,
|
||||
commitTransaction,
|
||||
connect,
|
||||
convertPathToJSONTraversal,
|
||||
count,
|
||||
countDistinct,
|
||||
create,
|
||||
createGlobal,
|
||||
createGlobalVersion,
|
||||
createJSONQuery,
|
||||
createMigration,
|
||||
createVersion,
|
||||
defaultIDType: payloadIDType,
|
||||
deleteMany,
|
||||
deleteOne,
|
||||
deleteVersions,
|
||||
deleteWhere,
|
||||
destroy,
|
||||
dropDatabase,
|
||||
execute,
|
||||
find,
|
||||
findGlobal,
|
||||
findGlobalVersions,
|
||||
findOne,
|
||||
findVersions,
|
||||
init,
|
||||
insert,
|
||||
migrate,
|
||||
migrateDown,
|
||||
migrateFresh,
|
||||
migrateRefresh,
|
||||
migrateReset,
|
||||
migrateStatus,
|
||||
migrationDir,
|
||||
payload,
|
||||
queryDrafts,
|
||||
rejectInitializing,
|
||||
requireDrizzleKit,
|
||||
resolveInitializing,
|
||||
rollbackTransaction,
|
||||
updateGlobal,
|
||||
updateGlobalVersion,
|
||||
updateOne,
|
||||
updateVersion,
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
defaultIDType: payloadIDType,
|
||||
init: adapter,
|
||||
}
|
||||
}
|
||||
108
packages/db-sqlite/src/init.ts
Normal file
108
packages/db-sqlite/src/init.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import type { DrizzleAdapter } from '@payloadcms/drizzle/types'
|
||||
import type { Init, SanitizedCollectionConfig } from 'payload'
|
||||
|
||||
import { createTableName } from '@payloadcms/drizzle'
|
||||
import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { SQLiteAdapter } from './types.js'
|
||||
|
||||
import { buildTable } from './schema/build.js'
|
||||
|
||||
export const init: Init = function init(this: SQLiteAdapter) {
|
||||
let locales: [string, ...string[]] | undefined
|
||||
if (this.payload.config.localization) {
|
||||
locales = this.payload.config.localization.locales.map(({ code }) => code) as [
|
||||
string,
|
||||
...string[],
|
||||
]
|
||||
}
|
||||
|
||||
this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => {
|
||||
createTableName({
|
||||
adapter: this as unknown as DrizzleAdapter,
|
||||
config: collection,
|
||||
})
|
||||
|
||||
if (collection.versions) {
|
||||
createTableName({
|
||||
adapter: this as unknown as DrizzleAdapter,
|
||||
config: collection,
|
||||
versions: true,
|
||||
versionsCustomName: true,
|
||||
})
|
||||
}
|
||||
})
|
||||
this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => {
|
||||
const tableName = this.tableNameMap.get(toSnakeCase(collection.slug))
|
||||
|
||||
buildTable({
|
||||
adapter: this,
|
||||
disableNotNull: !!collection?.versions?.drafts,
|
||||
disableUnique: false,
|
||||
fields: collection.fields,
|
||||
locales,
|
||||
tableName,
|
||||
timestamps: collection.timestamps,
|
||||
versions: false,
|
||||
})
|
||||
|
||||
if (collection.versions) {
|
||||
const versionsTableName = this.tableNameMap.get(
|
||||
`_${toSnakeCase(collection.slug)}${this.versionsSuffix}`,
|
||||
)
|
||||
const versionFields = buildVersionCollectionFields(collection)
|
||||
|
||||
buildTable({
|
||||
adapter: this,
|
||||
disableNotNull: !!collection.versions?.drafts,
|
||||
disableUnique: true,
|
||||
fields: versionFields,
|
||||
locales,
|
||||
tableName: versionsTableName,
|
||||
timestamps: true,
|
||||
versions: true,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
this.payload.config.globals.forEach((global) => {
|
||||
const tableName = createTableName({
|
||||
adapter: this as unknown as DrizzleAdapter,
|
||||
config: global,
|
||||
})
|
||||
|
||||
buildTable({
|
||||
adapter: this,
|
||||
disableNotNull: !!global?.versions?.drafts,
|
||||
disableUnique: false,
|
||||
fields: global.fields,
|
||||
locales,
|
||||
tableName,
|
||||
timestamps: false,
|
||||
versions: false,
|
||||
})
|
||||
|
||||
if (global.versions) {
|
||||
const versionsTableName = createTableName({
|
||||
adapter: this as unknown as DrizzleAdapter,
|
||||
config: global,
|
||||
versions: true,
|
||||
versionsCustomName: true,
|
||||
})
|
||||
const versionFields = buildVersionGlobalFields(global)
|
||||
|
||||
buildTable({
|
||||
adapter: this,
|
||||
disableNotNull: !!global.versions?.drafts,
|
||||
disableUnique: true,
|
||||
fields: versionFields,
|
||||
locales,
|
||||
tableName: versionsTableName,
|
||||
timestamps: true,
|
||||
versions: true,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
19
packages/db-sqlite/src/insert.ts
Normal file
19
packages/db-sqlite/src/insert.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { Insert } from './types.js'
|
||||
|
||||
export const insert: Insert = async function insert({
|
||||
db,
|
||||
onConflictDoUpdate,
|
||||
tableName,
|
||||
values,
|
||||
}): Promise<Record<string, unknown>[]> {
|
||||
const table = this.tables[tableName]
|
||||
let result
|
||||
|
||||
if (onConflictDoUpdate) {
|
||||
result = db.insert(table).values(values).onConflictDoUpdate(onConflictDoUpdate).returning()
|
||||
} else {
|
||||
result = db.insert(table).values(values).returning()
|
||||
}
|
||||
result = await result
|
||||
return result
|
||||
}
|
||||
15
packages/db-sqlite/src/requireDrizzleKit.ts
Normal file
15
packages/db-sqlite/src/requireDrizzleKit.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import type { RequireDrizzleKit } from '@payloadcms/drizzle/types'
|
||||
|
||||
import { createRequire } from 'module'
|
||||
const require = createRequire(import.meta.url)
|
||||
|
||||
/**
|
||||
* Dynamically requires the `drizzle-kit` package to access the `generateSQLiteDrizzleJson` and `pushSQLiteSchema` functions and exports them generically to call them from @payloadcms/drizzle.
|
||||
*/
|
||||
export const requireDrizzleKit: RequireDrizzleKit = () => {
|
||||
const {
|
||||
generateSQLiteDrizzleJson: generateDrizzleJson,
|
||||
pushSQLiteSchema: pushSchema,
|
||||
} = require('drizzle-kit/payload')
|
||||
return { generateDrizzleJson, pushSchema }
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user