Compare commits
86 Commits
v3.0.0-alp
...
v3.0.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f582efe98d | ||
|
|
540579f520 | ||
|
|
24c348dc49 | ||
|
|
4b4c245507 | ||
|
|
400f68d1aa | ||
|
|
5bb27ed9cd | ||
|
|
833498c269 | ||
|
|
80507d487b | ||
|
|
08f4ebaaf8 | ||
|
|
4c418525eb | ||
|
|
4962f6c926 | ||
|
|
50f0e9298c | ||
|
|
89efcc5db1 | ||
|
|
c3119a5632 | ||
|
|
069bbd92b0 | ||
|
|
c4422a2593 | ||
|
|
3aab9d368e | ||
|
|
b6afab63b2 | ||
|
|
b93f5e9c44 | ||
|
|
630082035f | ||
|
|
c74e41fc76 | ||
|
|
08ce7c58b5 | ||
|
|
1853fde379 | ||
|
|
f29d22ca95 | ||
|
|
5ea5f928ab | ||
|
|
efd6d35eb3 | ||
|
|
2b2538f13a | ||
|
|
9375dae179 | ||
|
|
674bb3758d | ||
|
|
cedf9a2eb8 | ||
|
|
7d2dc5b6c6 | ||
|
|
9d2aad7bf9 | ||
|
|
f085d7609b | ||
|
|
a49243a42a | ||
|
|
e79f431f14 | ||
|
|
38e5b6e8e3 | ||
|
|
35bdb785c4 | ||
|
|
25cb146fde | ||
|
|
c10f0f4a9e | ||
|
|
3a3a7f6e16 | ||
|
|
1d3b500962 | ||
|
|
f39f95af6d | ||
|
|
6fec2bbe1c | ||
|
|
2412134073 | ||
|
|
136545d1fd | ||
|
|
684c4d2113 | ||
|
|
6624fd0401 | ||
|
|
31502d2da3 | ||
|
|
3ee39ecca3 | ||
|
|
89e7c305e7 | ||
|
|
60dd71c59e | ||
|
|
0936f77930 | ||
|
|
3c09b95a8c | ||
|
|
780f26f135 | ||
|
|
77618674a0 | ||
|
|
c9c89a6005 | ||
|
|
d1276c4299 | ||
|
|
69730b6c95 | ||
|
|
9147d30152 | ||
|
|
6217c70fb5 | ||
|
|
17352c9a56 | ||
|
|
1b6026304f | ||
|
|
91f9973c6c | ||
|
|
ca2acee38a | ||
|
|
3bd455ced2 | ||
|
|
2c25abd143 | ||
|
|
08a9fb8cd7 | ||
|
|
d8a38b4e35 | ||
|
|
e64660f2d8 | ||
|
|
1a11466e69 | ||
|
|
78ab2fbe09 | ||
|
|
0c45d5773a | ||
|
|
f48335444b | ||
|
|
81b33cee5c | ||
|
|
020dcaad75 | ||
|
|
9bc56bcfc7 | ||
|
|
8325fadeb3 | ||
|
|
d54275b3bd | ||
|
|
29d20423a3 | ||
|
|
e539816253 | ||
|
|
922ce9ef5f | ||
|
|
b73ec6ae94 | ||
|
|
4a11bf956d | ||
|
|
3b3bb6c80a | ||
|
|
0f323ff2e3 | ||
|
|
3305c65ae6 |
7
.github/workflows/main.yml
vendored
7
.github/workflows/main.yml
vendored
@@ -6,6 +6,10 @@ on:
|
||||
push:
|
||||
branches: ['main', 'alpha']
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -246,8 +250,9 @@ jobs:
|
||||
suite:
|
||||
- _community
|
||||
- access-control
|
||||
# - admin
|
||||
- admin
|
||||
- auth
|
||||
- email
|
||||
- field-error-states
|
||||
- fields-relationship
|
||||
# - fields
|
||||
|
||||
11
.vscode/launch.json
vendored
11
.vscode/launch.json
vendored
@@ -23,6 +23,17 @@
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
},
|
||||
{
|
||||
"command": "node --no-deprecation test/loader/init.js",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"name": "Run Loader",
|
||||
"request": "launch",
|
||||
"type": "node-terminal",
|
||||
"env": {
|
||||
"LOADER_TEST_FILE_PATH": "./dependency-test.js"
|
||||
// "LOADER_TEST_FILE_PATH": "../fields/config.ts"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "node --no-deprecation test/dev.js admin",
|
||||
"cwd": "${workspaceFolder}",
|
||||
|
||||
@@ -14,8 +14,8 @@ type Args = {
|
||||
}
|
||||
}
|
||||
|
||||
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
|
||||
generatePageMetadata({ config, params, searchParams })
|
||||
export const generateMetadata = ({ params }: Args): Promise<Metadata> =>
|
||||
generatePageMetadata({ config, params })
|
||||
|
||||
const NotFound = ({ params, searchParams }: Args) => NotFoundPage({ config, params, searchParams })
|
||||
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import QueryString from 'qs'
|
||||
|
||||
import { PAYLOAD_SERVER_URL } from './serverURL.js'
|
||||
|
||||
export const fetchDoc = async <T>(args: {
|
||||
collection: string
|
||||
depth?: number
|
||||
id?: string
|
||||
slug?: string
|
||||
}): Promise<T> => {
|
||||
const { id, slug, collection, depth = 2 } = args || {}
|
||||
|
||||
const queryString = QueryString.stringify(
|
||||
{
|
||||
...(slug ? { 'where[slug][equals]': slug } : {}),
|
||||
...(depth ? { depth } : {}),
|
||||
},
|
||||
{ addQueryPrefix: true },
|
||||
)
|
||||
|
||||
const doc: T = await fetch(`${PAYLOAD_SERVER_URL}/api/${collection}${queryString}`, {
|
||||
cache: 'no-store',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'GET',
|
||||
})
|
||||
?.then((res) => res.json())
|
||||
?.then((res) => {
|
||||
if (res.errors) throw new Error(res?.errors?.[0]?.message ?? 'Error fetching doc')
|
||||
return res?.docs?.[0]
|
||||
})
|
||||
|
||||
return doc
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import { PAYLOAD_SERVER_URL } from './serverURL.js'
|
||||
|
||||
export const fetchDocs = async <T>(collection: string): Promise<T[]> => {
|
||||
const docs: T[] = await fetch(`${PAYLOAD_SERVER_URL}/api/${collection}?depth=0&limit=100`, {
|
||||
cache: 'no-store',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'GET',
|
||||
})
|
||||
?.then((res) => res.json())
|
||||
?.then((res) => {
|
||||
if (res.errors) throw new Error(res?.errors?.[0]?.message ?? 'Error fetching docs')
|
||||
|
||||
return res?.docs
|
||||
})
|
||||
|
||||
return docs
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import type { Footer } from '../../../test/live-preview/payload-types.js'
|
||||
|
||||
import { PAYLOAD_SERVER_URL } from './serverURL.js'
|
||||
|
||||
export async function fetchFooter(): Promise<Footer> {
|
||||
if (!PAYLOAD_SERVER_URL) throw new Error('PAYLOAD_SERVER_URL not found')
|
||||
|
||||
const footer = await fetch(`${PAYLOAD_SERVER_URL}/api/globals/footer`, {
|
||||
cache: 'no-store',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'GET',
|
||||
})
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error('Error fetching doc')
|
||||
return res.json()
|
||||
})
|
||||
?.then((res) => {
|
||||
if (res?.errors) throw new Error(res?.errors[0]?.message || 'Error fetching footer')
|
||||
return res
|
||||
})
|
||||
|
||||
return footer
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import type { Header } from '../../../test/live-preview/payload-types.js'
|
||||
|
||||
import { PAYLOAD_SERVER_URL } from './serverURL.js'
|
||||
|
||||
export async function fetchHeader(): Promise<Header> {
|
||||
if (!PAYLOAD_SERVER_URL) throw new Error('PAYLOAD_SERVER_URL not found')
|
||||
|
||||
const header = await fetch(`${PAYLOAD_SERVER_URL}/api/globals/header`, {
|
||||
cache: 'no-store',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'GET',
|
||||
})
|
||||
?.then((res) => {
|
||||
if (!res.ok) throw new Error('Error fetching doc')
|
||||
return res.json()
|
||||
})
|
||||
?.then((res) => {
|
||||
if (res?.errors) throw new Error(res?.errors[0]?.message || 'Error fetching header')
|
||||
return res
|
||||
})
|
||||
|
||||
return header
|
||||
}
|
||||
@@ -639,12 +639,12 @@ export const CustomArrayManager = () => {
|
||||
|
||||
The `useCollapsible` hook allows you to control parent collapsibles:
|
||||
|
||||
| Property | Description |
|
||||
| ----------------------- | ------------------------------------------------------------------------------------------------------------ | --- |
|
||||
| **`collapsed`** | State of the collapsible. `true` if open, `false` if collapsed |
|
||||
| **`isVisible`** | If nested, determine if the nearest collapsible is visible. `true` if no parent is closed, `false` otherwise |
|
||||
| **`toggle`** | Toggles the state of the nearest collapsible |
|
||||
| **`withinCollapsible`** | Determine when you are within another collaspible | |
|
||||
| Property | Description |
|
||||
| ------------------------- | ------------------------------------------------------------------------------------------------------------ | --- |
|
||||
| **`isCollapsed`** | State of the collapsible. `true` if open, `false` if collapsed |
|
||||
| **`isVisible`** | If nested, determine if the nearest collapsible is visible. `true` if no parent is closed, `false` otherwise |
|
||||
| **`toggle`** | Toggles the state of the nearest collapsible |
|
||||
| **`isWithinCollapsible`** | Determine when you are within another collaspible | |
|
||||
|
||||
**Example:**
|
||||
|
||||
@@ -654,10 +654,11 @@ import React from 'react'
|
||||
import { useCollapsible } from 'payload/components/utilities'
|
||||
|
||||
const CustomComponent: React.FC = () => {
|
||||
const { collapsed, toggle } = useCollapsible()
|
||||
const { isCollapsed, toggle } = useCollapsible()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p className="field-type">I am {collapsed ? 'closed' : 'open'}</p>
|
||||
<p className="field-type">I am {isCollapsed ? 'closed' : 'open'}</p>
|
||||
<button onClick={toggle} type="button">
|
||||
Toggle
|
||||
</button>
|
||||
|
||||
@@ -13,23 +13,25 @@ It's often best practice to write your Collections in separate files and then im
|
||||
|
||||
## Options
|
||||
|
||||
| Option | Description |
|
||||
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **`slug`** \* | Unique, URL-friendly string that will act as an identifier for this Collection. |
|
||||
| **`fields`** \* | Array of field types that will determine the structure and functionality of the data stored within this Collection. [Click here](/docs/fields/overview) for a full list of field types as well as how to configure them. |
|
||||
| **`labels`** | Singular and plural labels for use in identifying this Collection throughout Payload. Auto-generated from slug if not defined. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-options). |
|
||||
| **`hooks`** | Entry points to "tie in" to Collection actions at specific points. [More](/docs/hooks/overview#collection-hooks) |
|
||||
| **`access`** | Provide access control functions to define exactly who should be able to do what with Documents in this Collection. [More](/docs/access-control/overview/#collections) |
|
||||
| **`auth`** | Specify options if you would like this Collection to feature authentication. For more, consult the [Authentication](/docs/authentication/config) documentation. |
|
||||
| **`upload`** | Specify options if you would like this Collection to support file uploads. For more, consult the [Uploads](/docs/upload/overview) documentation. |
|
||||
| **`timestamps`** | Set to false to disable documents' automatically generated `createdAt` and `updatedAt` timestamps. |
|
||||
| **`versions`** | Set to true to enable default options, or configure with object properties. [More](/docs/versions/overview#collection-config) |
|
||||
| **`endpoints`** | Add custom routes to the REST API. Set to `false` to disable routes. [More](/docs/rest-api/overview#custom-endpoints) |
|
||||
| **`graphQL`** | An object with `singularName` and `pluralName` strings used in schema generation. Auto-generated from slug if not defined. Set to `false` to disable GraphQL. |
|
||||
| **`typescript`** | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
|
||||
| **`defaultSort`** | Pass a top-level field to sort by default in the collection List view. Prefix the name of the field with a minus symbol ("-") to sort in descending order. |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| Option | Description |
|
||||
|------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **`slug`** \* | Unique, URL-friendly string that will act as an identifier for this Collection. |
|
||||
| **`fields`** \* | Array of field types that will determine the structure and functionality of the data stored within this Collection. [Click here](/docs/fields/overview) for a full list of field types as well as how to configure them. |
|
||||
| **`labels`** | Singular and plural labels for use in identifying this Collection throughout Payload. Auto-generated from slug if not defined. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-options). |
|
||||
| **`hooks`** | Entry points to "tie in" to Collection actions at specific points. [More](/docs/hooks/overview#collection-hooks) |
|
||||
| **`access`** | Provide access control functions to define exactly who should be able to do what with Documents in this Collection. [More](/docs/access-control/overview/#collections) |
|
||||
| **`auth`** | Specify options if you would like this Collection to feature authentication. For more, consult the [Authentication](/docs/authentication/config) documentation. |
|
||||
| **`upload`** | Specify options if you would like this Collection to support file uploads. For more, consult the [Uploads](/docs/upload/overview) documentation. |
|
||||
| **`timestamps`** | Set to false to disable documents' automatically generated `createdAt` and `updatedAt` timestamps. |
|
||||
| **`versions`** | Set to true to enable default options, or configure with object properties. [More](/docs/versions/overview#collection-config) |
|
||||
| **`endpoints`** | Add custom routes to the REST API. Set to `false` to disable routes. [More](/docs/rest-api/overview#custom-endpoints) |
|
||||
| **`graphQL`** | An object with `singularName` and `pluralName` strings used in schema generation. Auto-generated from slug if not defined. Set to `false` to disable GraphQL. |
|
||||
| **`typescript`** | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
|
||||
| **`disableDuplicate`** | When true, do not show the "Duplicate" button while editing documents within this collection and prevent `duplicate` from all APIs. |
|
||||
| **`defaultSort`** | Pass a top-level field to sort by default in the collection List view. Prefix the name of the field with a minus symbol ("-") to sort in descending order. |
|
||||
| **`dbName`** | Custom table or collection name depending on the database adapter. Auto-generated from slug if not defined.
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
@@ -75,7 +77,6 @@ property on a collection's config.
|
||||
| `useAsTitle` | Specify a top-level field to use for a document title throughout the Admin panel. If no field is defined, the ID of the document is used as the title. |
|
||||
| `description` | Text or React component to display below the Collection label in the List view to give editors more information. |
|
||||
| `defaultColumns` | Array of field names that correspond to which columns to show by default in this collection's List view. |
|
||||
| `disableDuplicate ` | Disables the "Duplicate" button while editing documents within this collection. |
|
||||
| `hideAPIURL` | Hides the "API URL" meta field while editing documents within this collection. |
|
||||
| `enableRichTextLink` | The [Rich Text](/docs/fields/rich-text) field features a `Link` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
|
||||
| `enableRichTextRelationship` | The [Rich Text](/docs/fields/rich-text) field features a `Relationship` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
|
||||
|
||||
@@ -26,6 +26,7 @@ As with Collection configs, it's often best practice to write your Globals in se
|
||||
| **`graphQL.name`** | Text used in schema generation. Auto-generated from slug if not defined. |
|
||||
| **`typescript`** | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`dbName`** | Custom table or collection name for this global depending on the database adapter. Auto-generated from slug if not defined.
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
|
||||
@@ -38,12 +38,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. |
|
||||
| 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'. |
|
||||
|
||||
|
||||
|
||||
### Access to Drizzle
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ keywords: array, fields, config, configuration, documentation, Content Managemen
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
| **`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. |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ Blocks are defined as separate configs of their own.
|
||||
| **`imageAltText`** | Customize this block's image thumbnail alt text. |
|
||||
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). |
|
||||
| **`graphQL.singularName`** | Text to use for the GraphQL schema name. Auto-generated from slug if not defined. NOTE: this is set for deprecation, prefer `interfaceName`. |
|
||||
| **`dbName`** | Custom table name for this block type when using SQL database adapter ([Postgres](/docs/database/postgres)). Auto-generated from slug if not defined.
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
|
||||
#### Auto-generated data per block
|
||||
|
||||
@@ -20,27 +20,27 @@ keywords: number, fields, config, configuration, documentation, Content Manageme
|
||||
|
||||
### Config
|
||||
|
||||
| Option | Description |
|
||||
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. |
|
||||
| **`min`** | Minimum value accepted. Used in the default `validation` function. |
|
||||
| **`max`** | Maximum value accepted. Used in the default `validation` function. |
|
||||
| **`hasMany`** | Makes this field an ordered array of numbers instead of just a single number. |
|
||||
| **`minRows`** | Minimum number of numbers in the numbers array, if `hasMany` is set to true. |
|
||||
| **`maxRows`** | Maximum number of numbers in the numbers array, if `hasMany` is set to true. |
|
||||
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
|
||||
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
|
||||
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
|
||||
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
|
||||
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
|
||||
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
|
||||
| **`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 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). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| Option | Description |
|
||||
|--------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. |
|
||||
| **`min`** | Minimum value accepted. Used in the default `validation` function. |
|
||||
| **`max`** | Maximum value accepted. Used in the default `validation` function. |
|
||||
| **`hasMany`** | Makes this field an ordered array of numbers instead of just a single number. |
|
||||
| **`minRows`** | Minimum number of numbers in the numbers array, if `hasMany` is set to true. |
|
||||
| **`maxRows`** | Maximum number of numbers in the numbers array, if `hasMany` is set to true. |
|
||||
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
|
||||
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
|
||||
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
|
||||
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
|
||||
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
|
||||
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
|
||||
| **`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 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). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ keywords: radio, fields, config, configuration, documentation, Content Managemen
|
||||
| **`required`** | Require this field to have a value. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
| **`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.
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
|
||||
@@ -38,6 +38,8 @@ keywords: select, multi-select, fields, config, configuration, documentation, Co
|
||||
| **`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. |
|
||||
| **`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. |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
|
||||
@@ -10,6 +10,12 @@ const withBundleAnalyzer = bundleAnalyzer({
|
||||
export default withBundleAnalyzer(
|
||||
withPayload({
|
||||
reactStrictMode: false,
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true,
|
||||
},
|
||||
typescript: {
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
async redirects() {
|
||||
return [
|
||||
{
|
||||
|
||||
27
package.json
27
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload-monorepo",
|
||||
"version": "3.0.0-alpha.50",
|
||||
"version": "3.0.0-alpha.59",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"workspaces:": [
|
||||
@@ -76,7 +76,7 @@
|
||||
"@octokit/core": "^5.1.0",
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@payloadcms/live-preview-react": "workspace:*",
|
||||
"@playwright/test": "^1.42.1",
|
||||
"@playwright/test": "1.43.0",
|
||||
"@swc/cli": "^0.1.62",
|
||||
"@swc/jest": "0.2.36",
|
||||
"@testing-library/jest-dom": "6.4.2",
|
||||
@@ -88,13 +88,13 @@
|
||||
"@types/conventional-changelog-writer": "^4.0.10",
|
||||
"@types/fs-extra": "^11.0.2",
|
||||
"@types/jest": "29.5.12",
|
||||
"@types/minimist": "1.2.2",
|
||||
"@types/node": "20.11.28",
|
||||
"@types/minimist": "1.2.5",
|
||||
"@types/node": "20.12.5",
|
||||
"@types/prompts": "^2.4.5",
|
||||
"@types/qs": "6.9.7",
|
||||
"@types/react": "18.2.15",
|
||||
"@types/qs": "6.9.14",
|
||||
"@types/react": "18.2.74",
|
||||
"@types/semver": "^7.5.3",
|
||||
"@types/shelljs": "0.8.12",
|
||||
"@types/shelljs": "0.8.15",
|
||||
"add-stream": "^1.0.0",
|
||||
"chalk": "^4.1.2",
|
||||
"comment-json": "^4.2.3",
|
||||
@@ -131,11 +131,11 @@
|
||||
"node-mocks-http": "^1.14.1",
|
||||
"nodemon": "3.0.3",
|
||||
"open": "^10.1.0",
|
||||
"p-map": "^7.0.2",
|
||||
"p-limit": "^5.0.0",
|
||||
"pino": "8.15.0",
|
||||
"pino-pretty": "10.2.0",
|
||||
"playwright": "^1.42.1",
|
||||
"playwright-core": "^1.42.1",
|
||||
"playwright": "1.43.0",
|
||||
"playwright-core": "1.43.0",
|
||||
"prettier": "^3.0.3",
|
||||
"prompts": "2.4.2",
|
||||
"qs": "6.11.2",
|
||||
@@ -155,7 +155,7 @@
|
||||
"ts-node": "10.9.1",
|
||||
"tsx": "^4.7.1",
|
||||
"turbo": "^1.13.2",
|
||||
"typescript": "5.4.2",
|
||||
"typescript": "5.4.4",
|
||||
"uuid": "^9.0.1",
|
||||
"yocto-queue": "^1.0.0"
|
||||
},
|
||||
@@ -195,8 +195,7 @@
|
||||
"domexception": "4"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"playwright@1.42.1": "patches/playwright@1.42.1.patch"
|
||||
"playwright@1.43.0": "patches/playwright@1.43.0.patch"
|
||||
}
|
||||
},
|
||||
"packageManager": "pnpm@8.15.4+sha256.cea6d0bdf2de3a0549582da3983c70c92ffc577ff4410cbf190817ddc35137c2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-payload-app",
|
||||
"version": "3.0.0-alpha.50",
|
||||
"version": "3.0.0-alpha.54",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"homepage": "https://payloadcms.com",
|
||||
@@ -19,7 +19,7 @@
|
||||
"build:swc": "swc ./src -d ./dist --config-file .swcrc",
|
||||
"clean": "rimraf {dist,*.tsbuildinfo}",
|
||||
"test": "jest",
|
||||
"prepublishOnly": "pnpm test && pnpm clean && pnpm build"
|
||||
"prepublishOnly": "pnpm clean && pnpm build"
|
||||
},
|
||||
"files": [
|
||||
"package.json",
|
||||
@@ -48,7 +48,7 @@
|
||||
"@types/esprima": "^4.0.6",
|
||||
"@types/fs-extra": "^9.0.12",
|
||||
"@types/jest": "^27.0.3",
|
||||
"@types/node": "^16.6.2"
|
||||
"@types/node": "20.12.5"
|
||||
},
|
||||
"exports": {
|
||||
"./commands": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "3.0.0-alpha.50",
|
||||
"version": "3.0.0-alpha.59",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -30,7 +30,6 @@
|
||||
"get-port": "5.1.1",
|
||||
"http-status": "1.6.2",
|
||||
"mongoose": "6.12.3",
|
||||
"mongoose-aggregate-paginate-v2": "1.0.6",
|
||||
"mongoose-paginate-v2": "1.7.22",
|
||||
"prompts": "2.4.2",
|
||||
"uuid": "9.0.0"
|
||||
|
||||
@@ -45,7 +45,9 @@ export const createMigration: CreateMigration = async function createMigration({
|
||||
|
||||
// Check if predefined migration exists
|
||||
if (fs.existsSync(cleanPath)) {
|
||||
let migration = await eval(`${require ? 'require' : 'import'}(${cleanPath})`)
|
||||
let migration = await eval(
|
||||
`${typeof require === 'function' ? 'require' : 'import'}(${cleanPath})`,
|
||||
)
|
||||
if ('default' in migration) migration = migration.default
|
||||
const { down, up } = migration
|
||||
|
||||
|
||||
@@ -5,11 +5,7 @@ import type { SanitizedCollectionConfig } from 'payload/types'
|
||||
|
||||
import mongoose from 'mongoose'
|
||||
import paginate from 'mongoose-paginate-v2'
|
||||
import {
|
||||
buildVersionCollectionFields,
|
||||
buildVersionGlobalFields,
|
||||
getVersionsModelName,
|
||||
} from 'payload/versions'
|
||||
import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload/versions'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
import type { CollectionModel } from './types.js'
|
||||
@@ -18,13 +14,14 @@ import buildCollectionSchema from './models/buildCollectionSchema.js'
|
||||
import { buildGlobalModel } from './models/buildGlobalModel.js'
|
||||
import buildSchema from './models/buildSchema.js'
|
||||
import getBuildQueryPlugin from './queries/buildQuery.js'
|
||||
import { getDBName } from './utilities/getDBName.js'
|
||||
|
||||
export const init: Init = function init(this: MongooseAdapter) {
|
||||
this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => {
|
||||
const schema = buildCollectionSchema(collection, this.payload.config)
|
||||
|
||||
if (collection.versions) {
|
||||
const versionModelName = getVersionsModelName(collection)
|
||||
const versionModelName = getDBName({ config: collection, versions: true })
|
||||
|
||||
const versionCollectionFields = buildVersionCollectionFields(collection)
|
||||
|
||||
@@ -54,7 +51,7 @@ export const init: Init = function init(this: MongooseAdapter) {
|
||||
}
|
||||
|
||||
const model = mongoose.model(
|
||||
collection.slug,
|
||||
getDBName({ config: collection }),
|
||||
schema,
|
||||
this.autoPluralization === true ? undefined : collection.slug,
|
||||
) as CollectionModel
|
||||
@@ -72,7 +69,7 @@ export const init: Init = function init(this: MongooseAdapter) {
|
||||
|
||||
this.payload.config.globals.forEach((global) => {
|
||||
if (global.versions) {
|
||||
const versionModelName = getVersionsModelName(global)
|
||||
const versionModelName = getDBName({ config: global, versions: true })
|
||||
|
||||
const versionGlobalFields = buildVersionGlobalFields(global)
|
||||
|
||||
|
||||
@@ -363,7 +363,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
}
|
||||
if (field.localized && config.localization) {
|
||||
config.localization.locales.forEach((locale) => {
|
||||
schema.index({ [`${field.name}.${locale}`]: '2dsphere' }, indexOptions)
|
||||
schema.index({ [`${field.name}.${locale.code}`]: '2dsphere' }, indexOptions)
|
||||
})
|
||||
} else {
|
||||
schema.index({ [field.name]: '2dsphere' }, indexOptions)
|
||||
|
||||
41
packages/db-mongodb/src/utilities/getDBName.ts
Normal file
41
packages/db-mongodb/src/utilities/getDBName.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import type { DBIdentifierName } from 'payload/database'
|
||||
|
||||
type Args = {
|
||||
config: {
|
||||
dbName?: DBIdentifierName
|
||||
enumName?: DBIdentifierName
|
||||
name?: string
|
||||
slug?: string
|
||||
}
|
||||
locales?: boolean
|
||||
target?: 'dbName' | 'enumName'
|
||||
versions?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to name database enums and collections
|
||||
* Returns the collection or enum name for a given entity
|
||||
*/
|
||||
export const getDBName = ({
|
||||
config: { name, slug },
|
||||
config,
|
||||
target = 'dbName',
|
||||
versions = false,
|
||||
}: Args): string => {
|
||||
let result: string
|
||||
let custom = config[target]
|
||||
|
||||
if (!custom && target === 'enumName') {
|
||||
custom = config['dbName']
|
||||
}
|
||||
|
||||
if (custom) {
|
||||
result = typeof custom === 'function' ? custom({}) : custom
|
||||
} else {
|
||||
result = name ?? slug
|
||||
}
|
||||
|
||||
if (versions) result = `_${result}_versions`
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-postgres",
|
||||
"version": "3.0.0-alpha.50",
|
||||
"version": "3.0.0-alpha.59",
|
||||
"description": "The officially supported Postgres database adapter for Payload",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import type { Create } from 'payload/database'
|
||||
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
import { upsertRow } from './upsertRow/index.js'
|
||||
|
||||
export const create: Create = async function create(
|
||||
@@ -20,7 +19,10 @@ export const create: Create = async function create(
|
||||
fields: collection.fields,
|
||||
operation: 'create',
|
||||
req,
|
||||
tableName: toSnakeCase(collectionSlug),
|
||||
tableName: getTableName({
|
||||
adapter: this,
|
||||
config: collection,
|
||||
}),
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import type { CreateGlobalArgs } from 'payload/database'
|
||||
import type { PayloadRequest, TypeWithID } from 'payload/types'
|
||||
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
import { upsertRow } from './upsertRow/index.js'
|
||||
|
||||
export async function createGlobal<T extends TypeWithID>(
|
||||
@@ -21,7 +20,10 @@ export async function createGlobal<T extends TypeWithID>(
|
||||
fields: globalConfig.fields,
|
||||
operation: 'create',
|
||||
req,
|
||||
tableName: toSnakeCase(slug),
|
||||
tableName: getTableName({
|
||||
adapter: this,
|
||||
config: globalConfig,
|
||||
}),
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
@@ -4,10 +4,10 @@ import type { PayloadRequest, TypeWithID } from 'payload/types'
|
||||
import { sql } from 'drizzle-orm'
|
||||
import { type CreateGlobalVersionArgs } from 'payload/database'
|
||||
import { buildVersionGlobalFields } from 'payload/versions'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
import { upsertRow } from './upsertRow/index.js'
|
||||
|
||||
export async function createGlobalVersion<T extends TypeWithID>(
|
||||
@@ -16,8 +16,11 @@ export async function createGlobalVersion<T extends TypeWithID>(
|
||||
) {
|
||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||
const global = this.payload.globals.config.find(({ slug }) => slug === globalSlug)
|
||||
const globalTableName = toSnakeCase(globalSlug)
|
||||
const tableName = `_${globalTableName}_v`
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: global,
|
||||
versions: true,
|
||||
})
|
||||
|
||||
const result = await upsertRow<TypeWithVersion<T>>({
|
||||
adapter: this,
|
||||
|
||||
@@ -3,15 +3,17 @@ import type { DrizzleSnapshotJSON } from 'drizzle-kit/payload'
|
||||
import type { CreateMigration } from 'payload/database'
|
||||
|
||||
import fs from 'fs'
|
||||
import { createRequire } from 'module'
|
||||
import prompts from 'prompts'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
|
||||
const migrationTemplate = (
|
||||
upSQL?: string,
|
||||
downSQL?: string,
|
||||
) => `import { MigrateUpArgs, MigrateDownArgs } from '@payloadcms/db-postgres'
|
||||
import { sql } from 'drizzle-orm'
|
||||
) => `import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
|
||||
|
||||
export async function up({ payload }: MigrateUpArgs): Promise<void> {
|
||||
${
|
||||
@@ -60,9 +62,7 @@ export const createMigration: CreateMigration = async function createMigration(
|
||||
fs.mkdirSync(dir)
|
||||
}
|
||||
|
||||
const { generateDrizzleJson, generateMigration } = require
|
||||
? require('drizzle-kit/payload')
|
||||
: await import('drizzle-kit/payload')
|
||||
const { generateDrizzleJson, generateMigration } = require('drizzle-kit/payload')
|
||||
|
||||
const [yyymmdd, hhmmss] = new Date().toISOString().split('T')
|
||||
const formattedDate = yyymmdd.replace(/\D/g, '')
|
||||
|
||||
@@ -3,10 +3,10 @@ import type { PayloadRequest, TypeWithID } from 'payload/types'
|
||||
|
||||
import { sql } from 'drizzle-orm'
|
||||
import { buildVersionCollectionFields } from 'payload/versions'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
import { upsertRow } from './upsertRow/index.js'
|
||||
|
||||
export async function createVersion<T extends TypeWithID>(
|
||||
@@ -21,8 +21,11 @@ export async function createVersion<T extends TypeWithID>(
|
||||
) {
|
||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||
const collection = this.payload.collections[collectionSlug].config
|
||||
const collectionTableName = toSnakeCase(collectionSlug)
|
||||
const tableName = `_${collectionTableName}_v`
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: collection,
|
||||
versions: true,
|
||||
})
|
||||
|
||||
const result = await upsertRow<TypeWithVersion<T>>({
|
||||
adapter: this,
|
||||
@@ -40,7 +43,15 @@ export async function createVersion<T extends TypeWithID>(
|
||||
})
|
||||
|
||||
const table = this.tables[tableName]
|
||||
const relationshipsTable = this.tables[`${tableName}_rels`]
|
||||
const relationshipsTable =
|
||||
this.tables[
|
||||
getTableName({
|
||||
adapter: this,
|
||||
config: collection,
|
||||
relationships: true,
|
||||
versions: true,
|
||||
})
|
||||
]
|
||||
|
||||
if (collection.versions.drafts) {
|
||||
await db.execute(sql`
|
||||
|
||||
@@ -2,11 +2,11 @@ import type { DeleteMany } from 'payload/database'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
|
||||
import { inArray } from 'drizzle-orm'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { findMany } from './find/findMany.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
|
||||
export const deleteMany: DeleteMany = async function deleteMany(
|
||||
this: PostgresAdapter,
|
||||
@@ -14,7 +14,7 @@ export const deleteMany: DeleteMany = async function deleteMany(
|
||||
) {
|
||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||
const collectionConfig = this.payload.collections[collection].config
|
||||
const tableName = toSnakeCase(collection)
|
||||
const tableName = getTableName({ adapter: this, config: collectionConfig })
|
||||
|
||||
const result = await findMany({
|
||||
adapter: this,
|
||||
|
||||
@@ -2,13 +2,13 @@ import type { DeleteOne } from 'payload/database'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
|
||||
import { eq } from 'drizzle-orm'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { buildFindManyArgs } from './find/buildFindManyArgs.js'
|
||||
import buildQuery from './queries/buildQuery.js'
|
||||
import { selectDistinct } from './queries/selectDistinct.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
import { transform } from './transform/read/index.js'
|
||||
|
||||
export const deleteOne: DeleteOne = async function deleteOne(
|
||||
@@ -17,7 +17,10 @@ export const deleteOne: DeleteOne = async function deleteOne(
|
||||
) {
|
||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||
const collection = this.payload.collections[collectionSlug].config
|
||||
const tableName = toSnakeCase(collectionSlug)
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: collection,
|
||||
})
|
||||
let docToDelete: Record<string, unknown>
|
||||
|
||||
const { joinAliases, joins, selectFields, where } = await buildQuery({
|
||||
|
||||
@@ -3,11 +3,11 @@ import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types'
|
||||
|
||||
import { inArray } from 'drizzle-orm'
|
||||
import { buildVersionCollectionFields } from 'payload/versions'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { findMany } from './find/findMany.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
|
||||
export const deleteVersions: DeleteVersions = async function deleteVersion(
|
||||
this: PostgresAdapter,
|
||||
@@ -16,7 +16,11 @@ export const deleteVersions: DeleteVersions = async function deleteVersion(
|
||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||
|
||||
const tableName = `_${toSnakeCase(collection)}_v`
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: collectionConfig,
|
||||
versions: true,
|
||||
})
|
||||
const fields = buildVersionCollectionFields(collectionConfig)
|
||||
|
||||
const { docs } = await findMany({
|
||||
|
||||
@@ -1,38 +1,41 @@
|
||||
import type { Find } from 'payload/database'
|
||||
import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types'
|
||||
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { findMany } from './find/findMany.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
|
||||
export const find: Find = async function find(
|
||||
this: PostgresAdapter,
|
||||
{
|
||||
collection,
|
||||
limit: limitArg,
|
||||
limit,
|
||||
locale,
|
||||
page = 1,
|
||||
pagination,
|
||||
req = {} as PayloadRequest,
|
||||
sort: sortArg,
|
||||
where: whereArg,
|
||||
where,
|
||||
},
|
||||
) {
|
||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||
const sort = typeof sortArg === 'string' ? sortArg : collectionConfig.defaultSort
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: collectionConfig,
|
||||
})
|
||||
|
||||
return findMany({
|
||||
adapter: this,
|
||||
fields: collectionConfig.fields,
|
||||
limit: limitArg,
|
||||
limit,
|
||||
locale,
|
||||
page,
|
||||
pagination,
|
||||
req,
|
||||
sort,
|
||||
tableName: toSnakeCase(collection),
|
||||
where: whereArg,
|
||||
tableName,
|
||||
where,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
import { fieldAffectsData, tabHasName } from 'payload/types'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from '../types.js'
|
||||
import type { Result } from './buildFindManyArgs.js'
|
||||
|
||||
import { getTableName } from '../schema/getTableName.js'
|
||||
|
||||
type TraverseFieldArgs = {
|
||||
_locales: Record<string, unknown>
|
||||
adapter: PostgresAdapter
|
||||
@@ -78,9 +79,22 @@ export const traverseFields = ({
|
||||
with: {},
|
||||
}
|
||||
|
||||
const arrayTableName = `${currentTableName}_${path}${toSnakeCase(field.name)}`
|
||||
const arrayTableName = getTableName({
|
||||
adapter,
|
||||
config: field,
|
||||
parentTableName: currentTableName,
|
||||
prefix: `${currentTableName}_${path}`,
|
||||
})
|
||||
|
||||
if (adapter.tables[`${arrayTableName}_locales`]) withArray.with._locales = _locales
|
||||
const arrayTableNameWithLocales = getTableName({
|
||||
adapter,
|
||||
config: field,
|
||||
locales: true,
|
||||
parentTableName: currentTableName,
|
||||
prefix: `${currentTableName}_${path}`,
|
||||
})
|
||||
|
||||
if (adapter.tables[arrayTableNameWithLocales]) withArray.with._locales = _locales
|
||||
currentArgs.with[`${path}${field.name}`] = withArray
|
||||
|
||||
traverseFields({
|
||||
@@ -128,9 +142,16 @@ export const traverseFields = ({
|
||||
with: {},
|
||||
}
|
||||
|
||||
const tableName = `${topLevelTableName}_blocks_${toSnakeCase(block.slug)}`
|
||||
const tableName = getTableName({
|
||||
adapter,
|
||||
config: block,
|
||||
parentTableName: topLevelTableName,
|
||||
prefix: `${topLevelTableName}_blocks_`,
|
||||
})
|
||||
|
||||
if (adapter.tables[`${tableName}_locales`]) withBlock.with._locales = _locales
|
||||
if (adapter.tables[`${tableName}${adapter.localesSuffix}`]) {
|
||||
withBlock.with._locales = _locales
|
||||
}
|
||||
topLevelArgs.with[blockKey] = withBlock
|
||||
|
||||
traverseFields({
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import type { FindGlobal } from 'payload/database'
|
||||
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { findMany } from './find/findMany.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
|
||||
export const findGlobal: FindGlobal = async function findGlobal(
|
||||
this: PostgresAdapter,
|
||||
{ slug, locale, req, where },
|
||||
) {
|
||||
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug)
|
||||
const tableName = toSnakeCase(slug)
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: globalConfig,
|
||||
})
|
||||
|
||||
const {
|
||||
docs: [doc],
|
||||
|
||||
@@ -2,11 +2,11 @@ import type { FindGlobalVersions } from 'payload/database'
|
||||
import type { PayloadRequest, SanitizedGlobalConfig } from 'payload/types'
|
||||
|
||||
import { buildVersionGlobalFields } from 'payload/versions'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { findMany } from './find/findMany.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
|
||||
export const findGlobalVersions: FindGlobalVersions = async function findGlobalVersions(
|
||||
this: PostgresAdapter,
|
||||
@@ -27,7 +27,11 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV
|
||||
)
|
||||
const sort = typeof sortArg === 'string' ? sortArg : '-createdAt'
|
||||
|
||||
const tableName = `_${toSnakeCase(global)}_v`
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: globalConfig,
|
||||
versions: true,
|
||||
})
|
||||
const fields = buildVersionGlobalFields(globalConfig)
|
||||
|
||||
return findMany({
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import type { FindOneArgs } from 'payload/database'
|
||||
import type { PayloadRequest, SanitizedCollectionConfig, TypeWithID } from 'payload/types'
|
||||
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { findMany } from './find/findMany.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
|
||||
export async function findOne<T extends TypeWithID>(
|
||||
this: PostgresAdapter,
|
||||
{ collection, locale, req = {} as PayloadRequest, where: incomingWhere }: FindOneArgs,
|
||||
{ collection, locale, req = {} as PayloadRequest, where }: FindOneArgs,
|
||||
): Promise<T> {
|
||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: collectionConfig,
|
||||
})
|
||||
|
||||
const { docs } = await findMany({
|
||||
adapter: this,
|
||||
@@ -22,8 +25,8 @@ export async function findOne<T extends TypeWithID>(
|
||||
pagination: false,
|
||||
req,
|
||||
sort: undefined,
|
||||
tableName: toSnakeCase(collection),
|
||||
where: incomingWhere,
|
||||
tableName,
|
||||
where,
|
||||
})
|
||||
|
||||
return docs?.[0] || null
|
||||
|
||||
@@ -2,11 +2,11 @@ import type { FindVersions } from 'payload/database'
|
||||
import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types'
|
||||
|
||||
import { buildVersionCollectionFields } from 'payload/versions'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { findMany } from './find/findMany.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
|
||||
export const findVersions: FindVersions = async function findVersions(
|
||||
this: PostgresAdapter,
|
||||
@@ -25,7 +25,11 @@ export const findVersions: FindVersions = async function findVersions(
|
||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||
const sort = typeof sortArg === 'string' ? sortArg : collectionConfig.defaultSort
|
||||
|
||||
const tableName = `_${toSnakeCase(collection)}_v`
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: collectionConfig,
|
||||
versions: true,
|
||||
})
|
||||
const fields = buildVersionCollectionFields(collectionConfig)
|
||||
|
||||
return findMany({
|
||||
|
||||
@@ -40,6 +40,8 @@ import { updateVersion } from './updateVersion.js'
|
||||
|
||||
export type { MigrateDownArgs, MigrateUpArgs } from './types.js'
|
||||
|
||||
export { sql } from 'drizzle-orm'
|
||||
|
||||
export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter> {
|
||||
function adapter({ payload }: { payload: Payload }) {
|
||||
const migrationDir = findMigrationDir(args.migrationDir)
|
||||
@@ -49,20 +51,24 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
|
||||
name: 'postgres',
|
||||
|
||||
// Postgres-specific
|
||||
blockTableNames: {},
|
||||
drizzle: undefined,
|
||||
enums: {},
|
||||
fieldConstraints: {},
|
||||
idType,
|
||||
localesSuffix: args.localesSuffix || '_locales',
|
||||
logger: args.logger,
|
||||
pgSchema: undefined,
|
||||
pool: undefined,
|
||||
poolOptions: args.pool,
|
||||
push: args.push,
|
||||
relations: {},
|
||||
relationshipsSuffix: args.relationshipsSuffix || '_rels',
|
||||
schema: {},
|
||||
schemaName: args.schemaName,
|
||||
sessions: {},
|
||||
tables: {},
|
||||
versionsSuffix: args.versionsSuffix || '_v',
|
||||
|
||||
// DatabaseAdapter
|
||||
beginTransaction,
|
||||
|
||||
@@ -4,11 +4,11 @@ import type { SanitizedCollectionConfig } from 'payload/types'
|
||||
|
||||
import { pgEnum, pgSchema, pgTable } from 'drizzle-orm/pg-core'
|
||||
import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload/versions'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { buildTable } from './schema/build.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
|
||||
export const init: Init = function init(this: PostgresAdapter) {
|
||||
if (this.schemaName) {
|
||||
@@ -25,7 +25,10 @@ export const init: Init = function init(this: PostgresAdapter) {
|
||||
}
|
||||
|
||||
this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => {
|
||||
const tableName = toSnakeCase(collection.slug)
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: collection,
|
||||
})
|
||||
|
||||
buildTable({
|
||||
adapter: this,
|
||||
@@ -37,10 +40,15 @@ export const init: Init = function init(this: PostgresAdapter) {
|
||||
fields: collection.fields,
|
||||
tableName,
|
||||
timestamps: collection.timestamps,
|
||||
versions: false,
|
||||
})
|
||||
|
||||
if (collection.versions) {
|
||||
const versionsTableName = `_${tableName}_v`
|
||||
const versionsTableName = getTableName({
|
||||
adapter: this,
|
||||
config: collection,
|
||||
versions: true,
|
||||
})
|
||||
const versionFields = buildVersionCollectionFields(collection)
|
||||
|
||||
buildTable({
|
||||
@@ -53,12 +61,13 @@ export const init: Init = function init(this: PostgresAdapter) {
|
||||
fields: versionFields,
|
||||
tableName: versionsTableName,
|
||||
timestamps: true,
|
||||
versions: true,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
this.payload.config.globals.forEach((global) => {
|
||||
const tableName = toSnakeCase(global.slug)
|
||||
const tableName = getTableName({ adapter: this, config: global })
|
||||
|
||||
buildTable({
|
||||
adapter: this,
|
||||
@@ -70,10 +79,11 @@ export const init: Init = function init(this: PostgresAdapter) {
|
||||
fields: global.fields,
|
||||
tableName,
|
||||
timestamps: false,
|
||||
versions: false,
|
||||
})
|
||||
|
||||
if (global.versions) {
|
||||
const versionsTableName = `_${tableName}_v`
|
||||
const versionsTableName = getTableName({ adapter: this, config: global, versions: true })
|
||||
const versionFields = buildVersionGlobalFields(global)
|
||||
|
||||
buildTable({
|
||||
@@ -86,6 +96,7 @@ export const init: Init = function init(this: PostgresAdapter) {
|
||||
fields: versionFields,
|
||||
tableName: versionsTableName,
|
||||
timestamps: true,
|
||||
versions: true,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { Payload } from 'payload'
|
||||
import type { Migration } from 'payload/database'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
|
||||
import { createRequire } from 'module'
|
||||
import {
|
||||
commitTransaction,
|
||||
initTransaction,
|
||||
@@ -17,6 +18,8 @@ import { createMigrationTable } from './utilities/createMigrationTable.js'
|
||||
import { migrationTableExists } from './utilities/migrationTableExists.js'
|
||||
import { parseError } from './utilities/parseError.js'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
|
||||
export async function migrate(this: PostgresAdapter): Promise<void> {
|
||||
const { payload } = this
|
||||
const migrationFiles = await readMigrationFiles({ payload })
|
||||
@@ -82,16 +85,14 @@ export async function migrate(this: PostgresAdapter): Promise<void> {
|
||||
}
|
||||
|
||||
async function runMigrationFile(payload: Payload, migration: Migration, batch: number) {
|
||||
const { generateDrizzleJson } = require
|
||||
? require('drizzle-kit/payload')
|
||||
: await import('drizzle-kit/payload')
|
||||
const { generateDrizzleJson } = require('drizzle-kit/payload')
|
||||
|
||||
const start = Date.now()
|
||||
const req = { payload } as PayloadRequest
|
||||
|
||||
payload.logger.info({ msg: `Migrating: ${migration.name}` })
|
||||
|
||||
const pgAdapter = payload.db as PostgresAdapter
|
||||
const pgAdapter = payload.db
|
||||
const drizzleJSON = generateDrizzleJson(pgAdapter.schema)
|
||||
|
||||
try {
|
||||
|
||||
@@ -14,6 +14,8 @@ import { v4 as uuid } from 'uuid'
|
||||
import type { GenericColumn, GenericTable, PostgresAdapter } from '../types.js'
|
||||
import type { BuildQueryJoinAliases, BuildQueryJoins } from './buildQuery.js'
|
||||
|
||||
import { getTableName } from '../schema/getTableName.js'
|
||||
|
||||
type Constraint = {
|
||||
columnName: string
|
||||
table: GenericTable | PgTableWithColumns<any>
|
||||
@@ -183,7 +185,13 @@ export const getTableColumnFromPath = ({
|
||||
|
||||
case 'group': {
|
||||
if (locale && field.localized && adapter.payload.config.localization) {
|
||||
newTableName = `${tableName}_locales`
|
||||
newTableName = getTableName({
|
||||
adapter,
|
||||
config: field,
|
||||
locales: true,
|
||||
parentTableName: tableName,
|
||||
prefix: `${tableName}_`,
|
||||
})
|
||||
|
||||
joins[tableName] = eq(
|
||||
adapter.tables[tableName].id,
|
||||
@@ -218,7 +226,12 @@ export const getTableColumnFromPath = ({
|
||||
}
|
||||
|
||||
case 'array': {
|
||||
newTableName = `${tableName}_${tableNameSuffix}${toSnakeCase(field.name)}`
|
||||
newTableName = getTableName({
|
||||
adapter,
|
||||
config: field,
|
||||
parentTableName: `${tableName}_${tableNameSuffix}`,
|
||||
prefix: `${tableName}_${tableNameSuffix}`,
|
||||
})
|
||||
constraintPath = `${constraintPath}${field.name}.%.`
|
||||
if (locale && field.localized && adapter.payload.config.localization) {
|
||||
joins[newTableName] = and(
|
||||
@@ -265,7 +278,12 @@ export const getTableColumnFromPath = ({
|
||||
const blockTypes = Array.isArray(value) ? value : [value]
|
||||
blockTypes.forEach((blockType) => {
|
||||
const block = field.blocks.find((block) => block.slug === blockType)
|
||||
newTableName = `${tableName}_blocks_${toSnakeCase(block.slug)}`
|
||||
newTableName = getTableName({
|
||||
adapter,
|
||||
config: block,
|
||||
parentTableName: tableName,
|
||||
prefix: `${tableName}_blocks_`,
|
||||
})
|
||||
joins[newTableName] = eq(
|
||||
adapter.tables[tableName].id,
|
||||
adapter.tables[newTableName]._parentID,
|
||||
@@ -285,7 +303,12 @@ export const getTableColumnFromPath = ({
|
||||
}
|
||||
|
||||
const hasBlockField = field.blocks.some((block) => {
|
||||
newTableName = `${tableName}_blocks_${toSnakeCase(block.slug)}`
|
||||
newTableName = getTableName({
|
||||
adapter,
|
||||
config: block,
|
||||
parentTableName: tableName,
|
||||
prefix: `${tableName}_blocks_`,
|
||||
})
|
||||
constraintPath = `${constraintPath}${field.name}.%.`
|
||||
let result
|
||||
const blockConstraints = []
|
||||
@@ -351,7 +374,7 @@ export const getTableColumnFromPath = ({
|
||||
case 'relationship':
|
||||
case 'upload': {
|
||||
let relationshipFields
|
||||
const relationTableName = `${rootTableName}_rels`
|
||||
const relationTableName = `${rootTableName}${adapter.relationshipsSuffix}`
|
||||
const newCollectionPath = pathSegments.slice(1).join('.')
|
||||
const aliasRelationshipTableName = uuid()
|
||||
const aliasRelationshipTable = alias(
|
||||
@@ -360,22 +383,45 @@ export const getTableColumnFromPath = ({
|
||||
)
|
||||
|
||||
// Join in the relationships table
|
||||
joinAliases.push({
|
||||
condition: and(
|
||||
eq((aliasTable || adapter.tables[rootTableName]).id, aliasRelationshipTable.parent),
|
||||
like(aliasRelationshipTable.path, `${constraintPath}${field.name}`),
|
||||
),
|
||||
table: aliasRelationshipTable,
|
||||
})
|
||||
if (locale && field.localized && adapter.payload.config.localization) {
|
||||
joinAliases.push({
|
||||
condition: and(
|
||||
eq((aliasTable || adapter.tables[rootTableName]).id, aliasRelationshipTable.parent),
|
||||
eq(aliasRelationshipTable.locale, locale),
|
||||
like(aliasRelationshipTable.path, `${constraintPath}${field.name}`),
|
||||
),
|
||||
table: aliasRelationshipTable,
|
||||
})
|
||||
if (locale !== 'all') {
|
||||
constraints.push({
|
||||
columnName: 'locale',
|
||||
table: aliasRelationshipTable,
|
||||
value: locale,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// Join in the relationships table
|
||||
joinAliases.push({
|
||||
condition: and(
|
||||
eq((aliasTable || adapter.tables[rootTableName]).id, aliasRelationshipTable.parent),
|
||||
like(aliasRelationshipTable.path, `${constraintPath}${field.name}`),
|
||||
),
|
||||
table: aliasRelationshipTable,
|
||||
})
|
||||
}
|
||||
|
||||
selectFields[`${relationTableName}.path`] = aliasRelationshipTable.path
|
||||
|
||||
let newAliasTable
|
||||
|
||||
if (typeof field.relationTo === 'string') {
|
||||
newTableName = `${toSnakeCase(field.relationTo)}`
|
||||
const relationshipConfig = adapter.payload.collections[field.relationTo].config
|
||||
newTableName = getTableName({
|
||||
adapter,
|
||||
config: relationshipConfig,
|
||||
})
|
||||
// parent to relationship join table
|
||||
relationshipFields = adapter.payload.collections[field.relationTo].config.fields
|
||||
relationshipFields = relationshipConfig.fields
|
||||
|
||||
newAliasTable = alias(adapter.tables[newTableName], toSnakeCase(uuid()))
|
||||
|
||||
@@ -394,7 +440,11 @@ export const getTableColumnFromPath = ({
|
||||
}
|
||||
} else if (newCollectionPath === 'value') {
|
||||
const tableColumnsNames = field.relationTo.map(
|
||||
(relationTo) => `"${aliasRelationshipTableName}"."${toSnakeCase(relationTo)}_id"`,
|
||||
(relationTo) =>
|
||||
`"${aliasRelationshipTableName}"."${getTableName({
|
||||
adapter,
|
||||
config: adapter.payload.collections[relationTo].config,
|
||||
})}_id"`,
|
||||
)
|
||||
return {
|
||||
constraints,
|
||||
@@ -441,7 +491,7 @@ export const getTableColumnFromPath = ({
|
||||
if (field.localized && adapter.payload.config.localization) {
|
||||
// If localized, we go to localized table and set aliasTable to undefined
|
||||
// so it is not picked up below to be used as targetTable
|
||||
newTableName = `${tableName}_locales`
|
||||
newTableName = `${tableName}${adapter.localesSuffix}`
|
||||
|
||||
const parentTable = aliasTable || adapter.tables[tableName]
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types'
|
||||
|
||||
import { type QueryDrafts, combineQueries } from 'payload/database'
|
||||
import { buildVersionCollectionFields } from 'payload/versions'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import { findMany } from './find/findMany.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
|
||||
export const queryDrafts: QueryDrafts = async function queryDrafts({
|
||||
collection,
|
||||
@@ -17,7 +17,11 @@ export const queryDrafts: QueryDrafts = async function queryDrafts({
|
||||
where,
|
||||
}) {
|
||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||
const tableName = `_${toSnakeCase(collection)}_v`
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: collectionConfig,
|
||||
versions: true,
|
||||
})
|
||||
const fields = buildVersionCollectionFields(collectionConfig)
|
||||
|
||||
const combinedWhere = combineQueries({ latest: { equals: true } }, where)
|
||||
|
||||
@@ -11,10 +11,10 @@ import type { Field } from 'payload/types'
|
||||
import { relations } from 'drizzle-orm'
|
||||
import { index, integer, numeric, serial, timestamp, unique, varchar } from 'drizzle-orm/pg-core'
|
||||
import { fieldAffectsData } from 'payload/types'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { GenericColumns, GenericTable, IDType, PostgresAdapter } from '../types.js'
|
||||
|
||||
import { getTableName } from './getTableName.js'
|
||||
import { parentIDColumnMap } from './parentIDColumnMap.js'
|
||||
import { setColumnID } from './setColumnID.js'
|
||||
import { traverseFields } from './traverseFields.js'
|
||||
@@ -35,6 +35,7 @@ type Args = {
|
||||
rootTableName?: string
|
||||
tableName: string
|
||||
timestamps?: boolean
|
||||
versions: boolean
|
||||
}
|
||||
|
||||
type Result = {
|
||||
@@ -59,6 +60,7 @@ export const buildTable = ({
|
||||
rootTableName: incomingRootTableName,
|
||||
tableName,
|
||||
timestamps,
|
||||
versions,
|
||||
}: Args): Result => {
|
||||
const rootTableName = incomingRootTableName || tableName
|
||||
const columns: Record<string, PgColumnBuilder> = baseColumns
|
||||
@@ -113,6 +115,7 @@ export const buildTable = ({
|
||||
rootRelationsToBuild: rootRelationsToBuild || relationsToBuild,
|
||||
rootTableIDColType: rootTableIDColType || idColType,
|
||||
rootTableName,
|
||||
versions,
|
||||
}))
|
||||
|
||||
if (timestamps) {
|
||||
@@ -147,7 +150,7 @@ export const buildTable = ({
|
||||
adapter.tables[tableName] = table
|
||||
|
||||
if (hasLocalizedField) {
|
||||
const localeTableName = `${tableName}_locales`
|
||||
const localeTableName = `${tableName}${adapter.localesSuffix}`
|
||||
localesColumns.id = serial('id').primaryKey()
|
||||
localesColumns._locale = adapter.enums.enum__locales('_locale').notNull()
|
||||
localesColumns._parentID = parentIDColumnMap[idColType]('_parent_id')
|
||||
@@ -288,11 +291,16 @@ export const buildTable = ({
|
||||
}
|
||||
|
||||
relationships.forEach((relationTo) => {
|
||||
const formattedRelationTo = toSnakeCase(relationTo)
|
||||
const relationshipConfig = adapter.payload.collections[relationTo].config
|
||||
const formattedRelationTo = getTableName({
|
||||
adapter,
|
||||
config: relationshipConfig,
|
||||
throwValidationError: true,
|
||||
})
|
||||
let colType = adapter.idType === 'uuid' ? 'uuid' : 'integer'
|
||||
const relatedCollectionCustomID = adapter.payload.collections[
|
||||
relationTo
|
||||
].config.fields.find((field) => fieldAffectsData(field) && field.name === 'id')
|
||||
const relatedCollectionCustomID = relationshipConfig.fields.find(
|
||||
(field) => fieldAffectsData(field) && field.name === 'id',
|
||||
)
|
||||
if (relatedCollectionCustomID?.type === 'number') colType = 'numeric'
|
||||
if (relatedCollectionCustomID?.type === 'text') colType = 'varchar'
|
||||
|
||||
@@ -301,7 +309,7 @@ export const buildTable = ({
|
||||
).references(() => adapter.tables[formattedRelationTo].id, { onDelete: 'cascade' })
|
||||
})
|
||||
|
||||
const relationshipsTableName = `${tableName}_rels`
|
||||
const relationshipsTableName = `${tableName}${adapter.relationshipsSuffix}`
|
||||
|
||||
relationshipsTable = adapter.pgSchema.table(
|
||||
relationshipsTableName,
|
||||
@@ -333,7 +341,11 @@ export const buildTable = ({
|
||||
}
|
||||
|
||||
relationships.forEach((relationTo) => {
|
||||
const relatedTableName = toSnakeCase(relationTo)
|
||||
const relatedTableName = getTableName({
|
||||
adapter,
|
||||
config: adapter.payload.collections[relationTo].config,
|
||||
throwValidationError: true,
|
||||
})
|
||||
const idColumnName = `${relationTo}ID`
|
||||
result[idColumnName] = one(adapter.tables[relatedTableName], {
|
||||
fields: [relationshipsTable[idColumnName]],
|
||||
|
||||
75
packages/db-postgres/src/schema/getTableName.ts
Normal file
75
packages/db-postgres/src/schema/getTableName.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import type { DBIdentifierName } from 'payload/database'
|
||||
|
||||
import { APIError } from 'payload/errors'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from '../types.js'
|
||||
|
||||
type Args = {
|
||||
adapter: PostgresAdapter
|
||||
/** The collection, global or field config **/
|
||||
config: {
|
||||
dbName?: DBIdentifierName
|
||||
enumName?: DBIdentifierName
|
||||
name?: string
|
||||
slug?: string
|
||||
}
|
||||
/** Localized tables need to be given the locales suffix */
|
||||
locales?: boolean
|
||||
/** For nested tables passed for the user custom dbName functions to handle their own iterations */
|
||||
parentTableName?: string
|
||||
/** For sub tables (array for example) this needs to include the parentTableName */
|
||||
prefix?: string
|
||||
/** Adds the relationships suffix */
|
||||
relationships?: boolean
|
||||
/** For tables based on fields that could have both enumName and dbName (ie: select with hasMany), default: 'dbName' */
|
||||
target?: 'dbName' | 'enumName'
|
||||
throwValidationError?: boolean
|
||||
/** Adds the versions suffix, should only be used on the base collection to duplicate suffixing */
|
||||
versions?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to name database enums and tables
|
||||
* Returns the table or enum name for a given entity
|
||||
*/
|
||||
export const getTableName = ({
|
||||
adapter,
|
||||
config: { name, slug },
|
||||
config,
|
||||
locales = false,
|
||||
parentTableName,
|
||||
prefix = '',
|
||||
relationships = false,
|
||||
target = 'dbName',
|
||||
throwValidationError = false,
|
||||
versions = false,
|
||||
}: Args): string => {
|
||||
let result: string
|
||||
let custom = config[target]
|
||||
|
||||
if (!custom && target === 'enumName') {
|
||||
custom = config['dbName']
|
||||
}
|
||||
|
||||
if (custom) {
|
||||
result = typeof custom === 'function' ? custom({ tableName: parentTableName }) : custom
|
||||
} else {
|
||||
result = `${prefix}${toSnakeCase(name ?? slug)}`
|
||||
}
|
||||
|
||||
if (locales) result = `${result}${adapter.localesSuffix}`
|
||||
if (versions) result = `_${result}${adapter.versionsSuffix}`
|
||||
if (relationships) result = `${result}${adapter.relationshipsSuffix}`
|
||||
|
||||
if (!throwValidationError) {
|
||||
return result
|
||||
}
|
||||
|
||||
if (result.length > 63) {
|
||||
throw new APIError(
|
||||
`Exceeded max identifier length for table or enum name of 63 characters. Invalid name: ${result}`,
|
||||
)
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import type { GenericColumns, IDType, PostgresAdapter } from '../types.js'
|
||||
import { hasLocalesTable } from '../utilities/hasLocalesTable.js'
|
||||
import { buildTable } from './build.js'
|
||||
import { createIndex } from './createIndex.js'
|
||||
import { getTableName } from './getTableName.js'
|
||||
import { idToUUID } from './idToUUID.js'
|
||||
import { parentIDColumnMap } from './parentIDColumnMap.js'
|
||||
import { validateExistingBlockIsIdentical } from './validateExistingBlockIsIdentical.js'
|
||||
@@ -53,6 +54,7 @@ type Args = {
|
||||
rootRelationsToBuild?: Map<string, string>
|
||||
rootTableIDColType: string
|
||||
rootTableName: string
|
||||
versions: boolean
|
||||
}
|
||||
|
||||
type Result = {
|
||||
@@ -86,7 +88,9 @@ export const traverseFields = ({
|
||||
rootRelationsToBuild,
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
versions,
|
||||
}: Args): Result => {
|
||||
const throwValidationError = true
|
||||
let hasLocalizedField = false
|
||||
let hasLocalizedRelationshipField = false
|
||||
let hasManyTextField: 'index' | boolean = false
|
||||
@@ -217,7 +221,15 @@ export const traverseFields = ({
|
||||
|
||||
case 'radio':
|
||||
case 'select': {
|
||||
const enumName = `enum_${newTableName}_${toSnakeCase(field.name)}`
|
||||
const enumName = getTableName({
|
||||
adapter,
|
||||
config: field,
|
||||
parentTableName: newTableName,
|
||||
prefix: `enum_${newTableName}_`,
|
||||
target: 'enumName',
|
||||
throwValidationError,
|
||||
versions,
|
||||
})
|
||||
|
||||
adapter.enums[enumName] = pgEnum(
|
||||
enumName,
|
||||
@@ -231,7 +243,14 @@ export const traverseFields = ({
|
||||
)
|
||||
|
||||
if (field.type === 'select' && field.hasMany) {
|
||||
const selectTableName = `${newTableName}_${toSnakeCase(field.name)}`
|
||||
const selectTableName = getTableName({
|
||||
adapter,
|
||||
config: field,
|
||||
parentTableName: newTableName,
|
||||
prefix: `${newTableName}_`,
|
||||
throwValidationError,
|
||||
versions,
|
||||
})
|
||||
const baseColumns: Record<string, PgColumnBuilder> = {
|
||||
order: integer('order').notNull(),
|
||||
parent: parentIDColumnMap[parentIDColType]('parent_id')
|
||||
@@ -266,6 +285,7 @@ export const traverseFields = ({
|
||||
disableUnique,
|
||||
fields: [],
|
||||
tableName: selectTableName,
|
||||
versions,
|
||||
})
|
||||
|
||||
relationsToBuild.set(fieldName, selectTableName)
|
||||
@@ -296,7 +316,13 @@ export const traverseFields = ({
|
||||
case 'array': {
|
||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
||||
|
||||
const arrayTableName = `${newTableName}_${toSnakeCase(field.name)}`
|
||||
const arrayTableName = getTableName({
|
||||
adapter,
|
||||
config: field,
|
||||
parentTableName: newTableName,
|
||||
prefix: `${newTableName}_`,
|
||||
throwValidationError,
|
||||
})
|
||||
const baseColumns: Record<string, PgColumnBuilder> = {
|
||||
_order: integer('_order').notNull(),
|
||||
_parentID: parentIDColumnMap[parentIDColType]('_parent_id')
|
||||
@@ -334,6 +360,7 @@ export const traverseFields = ({
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
tableName: arrayTableName,
|
||||
versions,
|
||||
})
|
||||
|
||||
if (subHasManyTextField) {
|
||||
@@ -356,7 +383,7 @@ export const traverseFields = ({
|
||||
}
|
||||
|
||||
if (hasLocalesTable(field.fields)) {
|
||||
result._locales = many(adapter.tables[`${arrayTableName}_locales`])
|
||||
result._locales = many(adapter.tables[`${arrayTableName}${adapter.localesSuffix}`])
|
||||
}
|
||||
|
||||
subRelationsToBuild.forEach((val, key) => {
|
||||
@@ -375,7 +402,13 @@ export const traverseFields = ({
|
||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
||||
|
||||
field.blocks.forEach((block) => {
|
||||
const blockTableName = `${rootTableName}_blocks_${toSnakeCase(block.slug)}`
|
||||
const blockTableName = getTableName({
|
||||
adapter,
|
||||
config: block,
|
||||
parentTableName: rootTableName,
|
||||
prefix: `${rootTableName}_blocks_`,
|
||||
throwValidationError,
|
||||
})
|
||||
if (!adapter.tables[blockTableName]) {
|
||||
const baseColumns: Record<string, PgColumnBuilder> = {
|
||||
_order: integer('_order').notNull(),
|
||||
@@ -416,6 +449,7 @@ export const traverseFields = ({
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
tableName: blockTableName,
|
||||
versions,
|
||||
})
|
||||
|
||||
if (subHasManyTextField) {
|
||||
@@ -439,7 +473,9 @@ export const traverseFields = ({
|
||||
}
|
||||
|
||||
if (hasLocalesTable(block.fields)) {
|
||||
result._locales = many(adapter.tables[`${blockTableName}_locales`])
|
||||
result._locales = many(
|
||||
adapter.tables[`${blockTableName}${adapter.localesSuffix}`],
|
||||
)
|
||||
}
|
||||
|
||||
subRelationsToBuild.forEach((val, key) => {
|
||||
@@ -451,7 +487,7 @@ export const traverseFields = ({
|
||||
)
|
||||
|
||||
adapter.relations[`relations_${blockTableName}`] = blockTableRelations
|
||||
} else if (process.env.NODE_ENV !== 'production') {
|
||||
} else if (process.env.NODE_ENV !== 'production' && !versions) {
|
||||
validateExistingBlockIsIdentical({
|
||||
block,
|
||||
localized: field.localized,
|
||||
@@ -459,7 +495,7 @@ export const traverseFields = ({
|
||||
table: adapter.tables[blockTableName],
|
||||
})
|
||||
}
|
||||
|
||||
adapter.blockTableNames[`${rootTableName}.${toSnakeCase(block.slug)}`] = blockTableName
|
||||
rootRelationsToBuild.set(`_blocks_${block.slug}`, blockTableName)
|
||||
})
|
||||
|
||||
@@ -498,6 +534,7 @@ export const traverseFields = ({
|
||||
rootRelationsToBuild,
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
versions,
|
||||
})
|
||||
|
||||
if (groupHasLocalizedField) hasLocalizedField = true
|
||||
@@ -540,6 +577,7 @@ export const traverseFields = ({
|
||||
rootRelationsToBuild,
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
versions,
|
||||
})
|
||||
|
||||
if (groupHasLocalizedField) hasLocalizedField = true
|
||||
@@ -583,6 +621,7 @@ export const traverseFields = ({
|
||||
rootRelationsToBuild,
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
versions,
|
||||
})
|
||||
|
||||
if (tabHasLocalizedField) hasLocalizedField = true
|
||||
@@ -626,6 +665,7 @@ export const traverseFields = ({
|
||||
rootRelationsToBuild,
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
versions,
|
||||
})
|
||||
|
||||
if (rowHasLocalizedField) hasLocalizedField = true
|
||||
|
||||
@@ -24,11 +24,14 @@ export type DrizzleDB = NodePgDatabase<Record<string, unknown>>
|
||||
|
||||
export type Args = {
|
||||
idType?: 'serial' | 'uuid'
|
||||
localesSuffix?: string
|
||||
logger?: DrizzleConfig['logger']
|
||||
migrationDir?: string
|
||||
pool: PoolConfig
|
||||
push?: boolean
|
||||
relationshipsSuffix?: string
|
||||
schemaName?: string
|
||||
versionsSuffix?: string
|
||||
}
|
||||
|
||||
export type GenericColumn = PgColumn<
|
||||
@@ -58,6 +61,10 @@ export type DrizzleTransaction = PgTransaction<
|
||||
>
|
||||
|
||||
export type PostgresAdapter = BaseDatabaseAdapter & {
|
||||
/**
|
||||
* Used internally to map the block name to the table name
|
||||
*/
|
||||
blockTableNames: Record<string, string>
|
||||
drizzle: DrizzleDB
|
||||
enums: Record<string, GenericEnum>
|
||||
/**
|
||||
@@ -66,12 +73,14 @@ export type PostgresAdapter = BaseDatabaseAdapter & {
|
||||
*/
|
||||
fieldConstraints: Record<string, Record<string, string>>
|
||||
idType: Args['idType']
|
||||
localesSuffix?: string
|
||||
logger: DrizzleConfig['logger']
|
||||
pgSchema?: { table: PgTableFn } | PgSchema
|
||||
pool: Pool
|
||||
poolOptions: Args['pool']
|
||||
push: boolean
|
||||
relations: Record<string, GenericRelation>
|
||||
relationshipsSuffix?: string
|
||||
schema: Record<string, GenericEnum | GenericRelation | GenericTable>
|
||||
schemaName?: Args['schemaName']
|
||||
sessions: {
|
||||
@@ -82,6 +91,7 @@ export type PostgresAdapter = BaseDatabaseAdapter & {
|
||||
}
|
||||
}
|
||||
tables: Record<string, GenericTable | PgTableWithColumns<any>>
|
||||
versionsSuffix?: string
|
||||
}
|
||||
|
||||
export type IDType = 'integer' | 'numeric' | 'uuid' | 'varchar'
|
||||
@@ -98,9 +108,11 @@ declare module 'payload' {
|
||||
drizzle: DrizzleDB
|
||||
enums: Record<string, GenericEnum>
|
||||
fieldConstraints: Record<string, Record<string, string>>
|
||||
localeSuffix?: string
|
||||
pool: Pool
|
||||
push: boolean
|
||||
relations: Record<string, GenericRelation>
|
||||
relationshipsSuffix?: string
|
||||
schema: Record<string, GenericEnum | GenericRelation | GenericTable>
|
||||
sessions: {
|
||||
[id: string]: {
|
||||
@@ -110,5 +122,6 @@ declare module 'payload' {
|
||||
}
|
||||
}
|
||||
tables: Record<string, GenericTable>
|
||||
versionsSuffix?: string
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import type { UpdateOne } from 'payload/database'
|
||||
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import buildQuery from './queries/buildQuery.js'
|
||||
import { selectDistinct } from './queries/selectDistinct.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
import { upsertRow } from './upsertRow/index.js'
|
||||
|
||||
export const updateOne: UpdateOne = async function updateOne(
|
||||
@@ -14,7 +13,10 @@ export const updateOne: UpdateOne = async function updateOne(
|
||||
) {
|
||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||
const collection = this.payload.collections[collectionSlug].config
|
||||
const tableName = toSnakeCase(collectionSlug)
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: collection,
|
||||
})
|
||||
const whereToUse = whereArg || { id: { equals: id } }
|
||||
let idToUpdate = id
|
||||
|
||||
@@ -49,7 +51,7 @@ export const updateOne: UpdateOne = async function updateOne(
|
||||
fields: collection.fields,
|
||||
operation: 'update',
|
||||
req,
|
||||
tableName: toSnakeCase(collectionSlug),
|
||||
tableName,
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import type { UpdateGlobalArgs } from 'payload/database'
|
||||
import type { PayloadRequest, TypeWithID } from 'payload/types'
|
||||
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
import { upsertRow } from './upsertRow/index.js'
|
||||
|
||||
export async function updateGlobal<T extends TypeWithID>(
|
||||
@@ -13,7 +12,10 @@ export async function updateGlobal<T extends TypeWithID>(
|
||||
): Promise<T> {
|
||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug)
|
||||
const tableName = toSnakeCase(slug)
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: globalConfig,
|
||||
})
|
||||
|
||||
const existingGlobal = await db.query[tableName].findFirst({})
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@ import type { TypeWithVersion, UpdateGlobalVersionArgs } from 'payload/database'
|
||||
import type { PayloadRequest, SanitizedGlobalConfig, TypeWithID } from 'payload/types'
|
||||
|
||||
import { buildVersionGlobalFields } from 'payload/versions'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import buildQuery from './queries/buildQuery.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
import { upsertRow } from './upsertRow/index.js'
|
||||
|
||||
export async function updateGlobalVersion<T extends TypeWithID>(
|
||||
@@ -25,7 +25,11 @@ export async function updateGlobalVersion<T extends TypeWithID>(
|
||||
({ slug }) => slug === global,
|
||||
)
|
||||
const whereToUse = whereArg || { id: { equals: id } }
|
||||
const tableName = `_${toSnakeCase(global)}_v`
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: globalConfig,
|
||||
versions: true,
|
||||
})
|
||||
const fields = buildVersionGlobalFields(globalConfig)
|
||||
|
||||
const { where } = await buildQuery({
|
||||
|
||||
@@ -2,11 +2,11 @@ import type { TypeWithVersion, UpdateVersionArgs } from 'payload/database'
|
||||
import type { PayloadRequest, SanitizedCollectionConfig, TypeWithID } from 'payload/types'
|
||||
|
||||
import { buildVersionCollectionFields } from 'payload/versions'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import buildQuery from './queries/buildQuery.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
import { upsertRow } from './upsertRow/index.js'
|
||||
|
||||
export async function updateVersion<T extends TypeWithID>(
|
||||
@@ -23,7 +23,11 @@ export async function updateVersion<T extends TypeWithID>(
|
||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||
const whereToUse = whereArg || { id: { equals: id } }
|
||||
const tableName = `_${toSnakeCase(collection)}_v`
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: collectionConfig,
|
||||
versions: true,
|
||||
})
|
||||
const fields = buildVersionCollectionFields(collectionConfig)
|
||||
|
||||
const { where } = await buildQuery({
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
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.
|
||||
*
|
||||
@@ -11,9 +14,7 @@ import type { PostgresAdapter } from '../types.js'
|
||||
* @returns {Promise<void>} - A promise that resolves once the schema push is complete.
|
||||
*/
|
||||
export const pushDevSchema = async (db: PostgresAdapter) => {
|
||||
const { pushSchema } = require
|
||||
? require('drizzle-kit/payload')
|
||||
: await import('drizzle-kit/payload')
|
||||
const { pushSchema } = require('drizzle-kit/payload')
|
||||
|
||||
// This will prompt if clarifications are needed for Drizzle to push new schema
|
||||
const { apply, hasDataLoss, statementsToExecute, warnings } = await pushSchema(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/graphql",
|
||||
"version": "3.0.0-alpha.50",
|
||||
"version": "3.0.0-alpha.59",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.d.ts",
|
||||
"type": "module",
|
||||
|
||||
@@ -238,12 +238,14 @@ function initCollectionsGraphQL({ config, graphqlResult }: InitCollectionsGraphQ
|
||||
resolve: getDeleteResolver(collection),
|
||||
}
|
||||
|
||||
graphqlResult.Mutation.fields[`duplicate${singularName}`] = {
|
||||
type: collection.graphQL.type,
|
||||
args: {
|
||||
id: { type: new GraphQLNonNull(idType) },
|
||||
},
|
||||
resolve: duplicateResolver(collection),
|
||||
if (collectionConfig.disableDuplicate !== true) {
|
||||
graphqlResult.Mutation.fields[`duplicate${singularName}`] = {
|
||||
type: collection.graphQL.type,
|
||||
args: {
|
||||
id: { type: new GraphQLNonNull(idType) },
|
||||
},
|
||||
resolve: duplicateResolver(collection),
|
||||
}
|
||||
}
|
||||
|
||||
if (collectionConfig.versions) {
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react": "18.2.74",
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
import { copyRecursiveSync } from './utilities/copyRecursiveSync'
|
||||
import { copyFile } from './utilities/copyFile'
|
||||
|
||||
const install = () => {
|
||||
const useSrc = fs.existsSync(path.resolve(process.cwd(), './src'))
|
||||
const hasAppFolder = fs.existsSync(path.resolve(process.cwd(), `./${useSrc ? 'src/' : 'app'}`))
|
||||
|
||||
if (!hasAppFolder) {
|
||||
console.error(
|
||||
`You need to have a ${
|
||||
useSrc ? 'src/' : 'app/'
|
||||
} folder in your project before running this command.`,
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const basePath = useSrc ? './src' : '.'
|
||||
|
||||
// Copy handlers into /api
|
||||
copyRecursiveSync(
|
||||
path.resolve(__dirname, './templates/pages/api'),
|
||||
path.resolve(process.cwd(), `${basePath}/pages/api`),
|
||||
)
|
||||
|
||||
// Copy admin into /app
|
||||
copyRecursiveSync(
|
||||
path.resolve(__dirname, './templates/app'),
|
||||
path.resolve(process.cwd(), `${basePath}/app`),
|
||||
)
|
||||
|
||||
const payloadConfigPath = path.resolve(process.cwd(), `${basePath}/payload`)
|
||||
|
||||
if (!fs.existsSync(payloadConfigPath)) {
|
||||
fs.mkdirSync(payloadConfigPath)
|
||||
}
|
||||
|
||||
// Copy payload initialization
|
||||
copyFile(
|
||||
path.resolve(__dirname, './templates/payloadClient.ts'),
|
||||
path.resolve(process.cwd(), `${basePath}/payload/payloadClient.ts`),
|
||||
)
|
||||
|
||||
// Copy base payload config
|
||||
copyFile(
|
||||
path.resolve(__dirname, './templates/payload.config.ts'),
|
||||
path.resolve(process.cwd(), `${basePath}/payload/payload.config.ts`),
|
||||
)
|
||||
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
export default install()
|
||||
@@ -1,7 +0,0 @@
|
||||
import fs from 'fs'
|
||||
|
||||
export const copyFile = (source, target) => {
|
||||
if (!fs.existsSync(target)) {
|
||||
fs.writeFileSync(target, fs.readFileSync(source))
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
export function copyRecursiveSync(src, dest) {
|
||||
var exists = fs.existsSync(src)
|
||||
var stats = exists && fs.statSync(src)
|
||||
var isDirectory = exists && stats && stats.isDirectory()
|
||||
if (isDirectory) {
|
||||
fs.mkdirSync(dest, { recursive: true })
|
||||
fs.readdirSync(src).forEach(function (childItemName) {
|
||||
copyRecursiveSync(path.join(src, childItemName), path.join(dest, childItemName))
|
||||
})
|
||||
} else {
|
||||
fs.copyFileSync(src, dest)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/next",
|
||||
"version": "3.0.0-alpha.50",
|
||||
"version": "3.0.0-alpha.59",
|
||||
"main": "./src/index.js",
|
||||
"types": "./src/index.js",
|
||||
"type": "module",
|
||||
@@ -10,9 +10,6 @@
|
||||
"url": "https://github.com/payloadcms/payload.git",
|
||||
"directory": "packages/next"
|
||||
},
|
||||
"bin": {
|
||||
"@payloadcms/next": "./dist/bin/index.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "pnpm copyfiles && pnpm build:swc && pnpm build:types && pnpm build:webpack && rm dist/prod/index.js",
|
||||
"build:swc": "swc ./src -d ./dist --config-file .swcrc",
|
||||
@@ -39,8 +36,8 @@
|
||||
"devDependencies": {
|
||||
"@next/eslint-plugin-next": "^14.1.0",
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@types/react": "18.2.15",
|
||||
"@types/react-dom": "18.2.7",
|
||||
"@types/react": "18.2.74",
|
||||
"@types/react-dom": "18.2.24",
|
||||
"@types/ws": "^8.5.10",
|
||||
"css-loader": "^6.10.0",
|
||||
"css-minimizer-webpack-plugin": "^6.0.0",
|
||||
@@ -80,7 +77,7 @@
|
||||
},
|
||||
"publishConfig": {
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
"./css": {
|
||||
"import": "./dist/prod/styles.css",
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export { getNextI18n } from '../utilities/getNextI18n.js'
|
||||
export { getPayloadHMR } from '../utilities/getPayloadHMR.js'
|
||||
|
||||
@@ -46,7 +46,7 @@ export const buildFormState = async ({ req }: { req: PayloadRequest }) => {
|
||||
|
||||
// Run the admin access function from the config if it exists
|
||||
if (adminAccessFunction) {
|
||||
const canAccessAdmin = await adminAccessFunction(req)
|
||||
const canAccessAdmin = await adminAccessFunction({ req })
|
||||
|
||||
if (!canAccessAdmin) {
|
||||
return Response.json(null, {
|
||||
|
||||
@@ -378,7 +378,7 @@ export const POST =
|
||||
res = await (
|
||||
endpoints.collection.POST[`doc-${slug2}-by-id`] as CollectionRouteHandlerWithID
|
||||
)({ id: slug3, collection, req })
|
||||
} else if (slug3 === 'duplicate') {
|
||||
} else if (slug3 === 'duplicate' && collection.config.disableDuplicate !== true) {
|
||||
// /:collection/:id/duplicate
|
||||
res = await endpoints.collection.POST.duplicate({ id: slug2, collection, req })
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import type { Payload, PayloadRequest } from 'payload/types'
|
||||
|
||||
import { getAccessResults, getAuthenticatedUser, parseCookies } from 'payload/auth'
|
||||
|
||||
type Args = {
|
||||
headers: Request['headers']
|
||||
payload: Payload
|
||||
}
|
||||
|
||||
export const auth = async ({ headers, payload }: Args) => {
|
||||
const cookies = parseCookies(headers)
|
||||
|
||||
const user = await getAuthenticatedUser({
|
||||
cookies,
|
||||
headers,
|
||||
payload,
|
||||
})
|
||||
|
||||
const permissions = await getAccessResults({
|
||||
req: {
|
||||
context: {},
|
||||
headers,
|
||||
i18n: undefined,
|
||||
payload,
|
||||
payloadAPI: 'REST',
|
||||
t: undefined,
|
||||
user,
|
||||
} as PayloadRequest,
|
||||
})
|
||||
|
||||
return {
|
||||
cookies,
|
||||
permissions,
|
||||
user,
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import type {
|
||||
|
||||
import { initI18n } from '@payloadcms/translations'
|
||||
import { translations } from '@payloadcms/translations/api'
|
||||
import { getAuthenticatedUser } from 'payload/auth'
|
||||
import { executeAuthStrategies } from 'payload/auth'
|
||||
import { parseCookies } from 'payload/auth'
|
||||
import { getDataLoader } from 'payload/utilities'
|
||||
import qs from 'qs'
|
||||
@@ -117,7 +117,7 @@ export const createPayloadRequest = async ({
|
||||
if (data) req.json = () => Promise.resolve(data)
|
||||
req.payloadDataLoader = getDataLoader(req)
|
||||
|
||||
req.user = await getAuthenticatedUser({
|
||||
req.user = await executeAuthStrategies({
|
||||
cookies,
|
||||
headers: req.headers,
|
||||
isGraphQL,
|
||||
|
||||
@@ -12,11 +12,11 @@ import { translations } from '@payloadcms/translations/client'
|
||||
import { findLocaleFromCode } from '@payloadcms/ui/utilities/findLocaleFromCode'
|
||||
import { headers as getHeaders } from 'next/headers.js'
|
||||
import { notFound, redirect } from 'next/navigation.js'
|
||||
import { parseCookies } from 'payload/auth'
|
||||
import { createLocalReq, isEntityHidden } from 'payload/utilities'
|
||||
import qs from 'qs'
|
||||
|
||||
import { getPayloadHMR } from '../utilities/getPayloadHMR.js'
|
||||
import { auth } from './auth.js'
|
||||
import { getRequestLanguage } from './getRequestLanguage.js'
|
||||
|
||||
type Args = {
|
||||
@@ -35,12 +35,43 @@ export const initPage = async ({
|
||||
const headers = getHeaders()
|
||||
const localeParam = searchParams?.locale as string
|
||||
const payload = await getPayloadHMR({ config: configPromise })
|
||||
const { collections, globals, localization, routes } = payload.config
|
||||
|
||||
const { cookies, permissions, user } = await auth({
|
||||
headers,
|
||||
payload,
|
||||
const queryString = `${qs.stringify(searchParams ?? {}, { addQueryPrefix: true })}`
|
||||
const defaultLocale =
|
||||
localization && localization.defaultLocale ? localization.defaultLocale : 'en'
|
||||
const localeCode = localeParam || defaultLocale
|
||||
const locale = localization && findLocaleFromCode(localization, localeCode)
|
||||
const cookies = parseCookies(headers)
|
||||
const language = getRequestLanguage({ config: payload.config, cookies, headers })
|
||||
|
||||
const i18n = initI18n({
|
||||
config: payload.config.i18n,
|
||||
context: 'client',
|
||||
language,
|
||||
translations,
|
||||
})
|
||||
|
||||
const req = createLocalReq(
|
||||
{
|
||||
fallbackLocale: null,
|
||||
locale: locale.code,
|
||||
req: {
|
||||
i18n,
|
||||
query: qs.parse(queryString, {
|
||||
depth: 10,
|
||||
ignoreQueryPrefix: true,
|
||||
}),
|
||||
url: `${payload.config.serverURL}${route}${searchParams ? queryString : ''}`,
|
||||
} as PayloadRequest,
|
||||
},
|
||||
payload,
|
||||
)
|
||||
|
||||
const { permissions, user } = await payload.auth({ headers, req })
|
||||
|
||||
req.user = user
|
||||
|
||||
const visibleEntities: VisibleEntities = {
|
||||
collections: payload.config.collections
|
||||
.map(({ slug, admin: { hidden } }) => (!isEntityHidden({ hidden, user }) ? slug : null))
|
||||
@@ -56,8 +87,6 @@ export const initPage = async ({
|
||||
const globalSlug = entityType === 'globals' ? entitySlug : undefined
|
||||
const docID = collectionSlug && createOrID !== 'create' ? createOrID : undefined
|
||||
|
||||
const { collections, globals, localization, routes } = payload.config
|
||||
|
||||
if (redirectUnauthenticatedUser && !user && route !== '/login') {
|
||||
if (searchParams && 'redirect' in searchParams) delete searchParams.redirect
|
||||
|
||||
@@ -68,38 +97,6 @@ export const initPage = async ({
|
||||
redirect(`${routes.admin}/login?redirect=${route + stringifiedSearchParams}`)
|
||||
}
|
||||
|
||||
const defaultLocale =
|
||||
localization && localization.defaultLocale ? localization.defaultLocale : 'en'
|
||||
const localeCode = localeParam || defaultLocale
|
||||
const locale = localization && findLocaleFromCode(localization, localeCode)
|
||||
const language = getRequestLanguage({ config: payload.config, cookies, headers })
|
||||
|
||||
const i18n = initI18n({
|
||||
config: payload.config.i18n,
|
||||
context: 'client',
|
||||
language,
|
||||
translations,
|
||||
})
|
||||
|
||||
const queryString = `${qs.stringify(searchParams ?? {}, { addQueryPrefix: true })}`
|
||||
|
||||
const req = createLocalReq(
|
||||
{
|
||||
fallbackLocale: null,
|
||||
locale: locale.code,
|
||||
req: {
|
||||
i18n,
|
||||
query: qs.parse(queryString, {
|
||||
depth: 10,
|
||||
ignoreQueryPrefix: true,
|
||||
}),
|
||||
url: `${payload.config.serverURL}${route}${searchParams ? queryString : ''}`,
|
||||
} as PayloadRequest,
|
||||
user,
|
||||
},
|
||||
payload,
|
||||
)
|
||||
|
||||
let collectionConfig: SanitizedCollectionConfig
|
||||
let globalConfig: SanitizedGlobalConfig
|
||||
|
||||
|
||||
@@ -106,43 +106,42 @@ export const Auth: React.FC<Props> = (props) => {
|
||||
required
|
||||
/>
|
||||
<ConfirmPassword disabled={readOnly} />
|
||||
{!requirePassword && (
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
disabled={readOnly}
|
||||
onClick={() => handleChangePassword(false)}
|
||||
size="small"
|
||||
>
|
||||
{t('general:cancel')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{((!changingPassword && !requirePassword) || operation === 'update') && (
|
||||
<div className={`${baseClass}__controls`}>
|
||||
{!changingPassword && !requirePassword && (
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
disabled={readOnly}
|
||||
id="change-password"
|
||||
onClick={() => handleChangePassword(true)}
|
||||
size="small"
|
||||
>
|
||||
{t('authentication:changePassword')}
|
||||
</Button>
|
||||
)}
|
||||
{operation === 'update' && (
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
disabled={readOnly}
|
||||
onClick={() => unlock()}
|
||||
size="small"
|
||||
>
|
||||
{t('authentication:forceUnlock')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={`${baseClass}__controls`}>
|
||||
{changingPassword && !requirePassword && (
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
disabled={readOnly}
|
||||
onClick={() => handleChangePassword(false)}
|
||||
size="small"
|
||||
>
|
||||
{t('general:cancel')}
|
||||
</Button>
|
||||
)}
|
||||
{!changingPassword && !requirePassword && (
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
disabled={readOnly}
|
||||
id="change-password"
|
||||
onClick={() => handleChangePassword(true)}
|
||||
size="small"
|
||||
>
|
||||
{t('authentication:changePassword')}
|
||||
</Button>
|
||||
)}
|
||||
{operation === 'update' && (
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
disabled={readOnly}
|
||||
onClick={() => unlock()}
|
||||
size="small"
|
||||
>
|
||||
{t('authentication:forceUnlock')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)}
|
||||
{useAPIKey && (
|
||||
|
||||
@@ -6,11 +6,12 @@ import './index.scss'
|
||||
|
||||
const baseClass = 'live-preview-iframe'
|
||||
|
||||
export const IFrame: React.FC<{
|
||||
ref: React.Ref<HTMLIFrameElement>
|
||||
type Props = {
|
||||
setIframeHasLoaded: (value: boolean) => void
|
||||
url: string
|
||||
}> = forwardRef((props, ref) => {
|
||||
}
|
||||
|
||||
export const IFrame = forwardRef<HTMLIFrameElement, Props>((props, ref) => {
|
||||
const { setIframeHasLoaded, url } = props
|
||||
|
||||
const { zoom } = useLivePreviewContext()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { LivePreviewConfig } from 'payload/config'
|
||||
import type { EditViewComponent } from 'payload/types'
|
||||
import type { EditViewComponent, TypeWithID } from 'payload/types'
|
||||
|
||||
import { notFound } from 'next/navigation.js'
|
||||
import React from 'react'
|
||||
|
||||
import { LivePreviewClient } from './index.client.js'
|
||||
@@ -11,6 +12,7 @@ export const LivePreviewView: EditViewComponent = async (props) => {
|
||||
|
||||
const {
|
||||
collectionConfig,
|
||||
docID,
|
||||
globalConfig,
|
||||
locale,
|
||||
req: {
|
||||
@@ -22,8 +24,30 @@ export const LivePreviewView: EditViewComponent = async (props) => {
|
||||
} = {},
|
||||
} = initPageResult
|
||||
|
||||
// TODO(JAKE): not sure what `data` is or what it should be
|
||||
const data = 'data' in props ? props.data : {}
|
||||
let data: TypeWithID
|
||||
|
||||
try {
|
||||
if (collectionConfig) {
|
||||
data = await initPageResult.req.payload.findByID({
|
||||
id: docID,
|
||||
collection: collectionConfig.slug,
|
||||
depth: 0,
|
||||
draft: true,
|
||||
fallbackLocale: null,
|
||||
})
|
||||
}
|
||||
|
||||
if (globalConfig) {
|
||||
data = await initPageResult.req.payload.findGlobal({
|
||||
slug: globalConfig.slug,
|
||||
depth: 0,
|
||||
draft: true,
|
||||
fallbackLocale: null,
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
let livePreviewConfig: LivePreviewConfig = topLevelLivePreviewConfig
|
||||
|
||||
@@ -54,10 +78,11 @@ export const LivePreviewView: EditViewComponent = async (props) => {
|
||||
const url =
|
||||
typeof livePreviewConfig?.url === 'function'
|
||||
? await livePreviewConfig.url({
|
||||
collectionConfig,
|
||||
data,
|
||||
documentInfo: {}, // TODO: recreate this object server-side, see `useDocumentInfo`
|
||||
// @ts-expect-error
|
||||
globalConfig,
|
||||
locale,
|
||||
payload: initPageResult.req.payload,
|
||||
})
|
||||
: livePreviewConfig?.url
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { I18n } from '@payloadcms/translations'
|
||||
import type { Metadata } from 'next'
|
||||
import type { SanitizedConfig } from 'payload/types'
|
||||
|
||||
import { getNextI18n } from '@payloadcms/next/utilities'
|
||||
import { HydrateClientUser } from '@payloadcms/ui/elements/HydrateClientUser'
|
||||
import { DefaultTemplate } from '@payloadcms/ui/templates/Default'
|
||||
import React, { Fragment } from 'react'
|
||||
@@ -10,13 +11,18 @@ import { initPage } from '../../utilities/initPage.js'
|
||||
import { NotFoundClient } from './index.client.js'
|
||||
|
||||
export const generatePageMetadata = async ({
|
||||
i18n,
|
||||
config: configPromise,
|
||||
}: {
|
||||
config: SanitizedConfig
|
||||
i18n: I18n
|
||||
config: Promise<SanitizedConfig> | SanitizedConfig
|
||||
params?: { [key: string]: string | string[] }
|
||||
//eslint-disable-next-line @typescript-eslint/require-await
|
||||
}): Promise<Metadata> => {
|
||||
const config = await configPromise
|
||||
|
||||
const i18n = getNextI18n({
|
||||
config,
|
||||
})
|
||||
|
||||
return {
|
||||
title: i18n.t('general:notFound'),
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ export const generatePageMetadata = async ({ config: configPromise, params }: Ar
|
||||
const isGlobal = segmentOne === 'globals'
|
||||
const isCollection = segmentOne === 'collections'
|
||||
|
||||
const i18n = await getNextI18n({
|
||||
const i18n = getNextI18n({
|
||||
config,
|
||||
})
|
||||
|
||||
|
||||
@@ -55,14 +55,16 @@ const Select: React.FC<
|
||||
|
||||
if (version === comparison) placeholder = `[${i18n.t('general:noValue')}]`
|
||||
|
||||
const options = 'options' in field.fieldComponentProps && field.fieldComponentProps.options
|
||||
|
||||
const comparisonToRender =
|
||||
typeof comparison !== 'undefined'
|
||||
? getTranslatedOptions(getOptionsToRender(comparison, field.options, field.hasMany), i18n)
|
||||
? getTranslatedOptions(getOptionsToRender(comparison, options, field.hasMany), i18n)
|
||||
: placeholder
|
||||
|
||||
const versionToRender =
|
||||
typeof version !== 'undefined'
|
||||
? getTranslatedOptions(getOptionsToRender(version, field.options, field.hasMany), i18n)
|
||||
? getTranslatedOptions(getOptionsToRender(version, options, field.hasMany), i18n)
|
||||
: placeholder
|
||||
|
||||
return (
|
||||
|
||||
@@ -12,7 +12,7 @@ if (process.env.DISABLE_SWC !== 'true') {
|
||||
const dirname = path.dirname(filename)
|
||||
const url = pathToFileURL(dirname).toString() + '/'
|
||||
|
||||
register('./dist/bin/register/index.js', url)
|
||||
register('./dist/bin/loader/index.js', url)
|
||||
}
|
||||
|
||||
bin()
|
||||
|
||||
5
packages/payload/node.d.ts
vendored
Normal file
5
packages/payload/node.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
export {
|
||||
importConfig,
|
||||
importWithoutClientFiles,
|
||||
} from './dist/utilities/importWithoutClientFiles.js'
|
||||
//# sourceMappingURL=node.d.ts.map
|
||||
6
packages/payload/node.js
Normal file
6
packages/payload/node.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export {
|
||||
importConfig,
|
||||
importWithoutClientFiles,
|
||||
} from './dist/utilities/importWithoutClientFiles.js'
|
||||
|
||||
//# sourceMappingURL=node.js.map
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload",
|
||||
"version": "3.0.0-alpha.50",
|
||||
"version": "3.0.0-alpha.59",
|
||||
"description": "Node, React and MongoDB Headless CMS and Application Framework",
|
||||
"license": "MIT",
|
||||
"main": "./src/index.ts",
|
||||
@@ -61,7 +61,6 @@
|
||||
"nodemailer": "6.9.10",
|
||||
"pino": "8.15.0",
|
||||
"pino-pretty": "10.2.0",
|
||||
"pirates": "^4.0.6",
|
||||
"pluralize": "8.0.0",
|
||||
"probe-image-size": "^7.2.3",
|
||||
"sanitize-filename": "1.6.3",
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { Translations } from '@payloadcms/translations'
|
||||
|
||||
import type { Permissions } from '../../auth/index.js'
|
||||
import type { SanitizedCollectionConfig } from '../../collections/config/types.js'
|
||||
import type { Locale } from '../../config/types.js'
|
||||
import type { SanitizedGlobalConfig } from '../../globals/config/types.js'
|
||||
import type { PayloadRequest } from '../../types/index.js'
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { AuthStrategyFunctionArgs, User } from './index.js'
|
||||
|
||||
export const getAuthenticatedUser = async (
|
||||
export const executeAuthStrategies = async (
|
||||
args: AuthStrategyFunctionArgs,
|
||||
): Promise<User | null> => {
|
||||
return args.payload.authStrategies.reduce(async (accumulatorPromise, strategy) => {
|
||||
@@ -19,10 +19,7 @@ export async function getAccessResults({ req }: GetAccessResultsArgs): Promise<P
|
||||
|
||||
if (userCollectionConfig) {
|
||||
results.canAccessAdmin = userCollectionConfig.access.admin
|
||||
? await userCollectionConfig.access.admin({
|
||||
payload,
|
||||
user,
|
||||
})
|
||||
? await userCollectionConfig.access.admin({ req })
|
||||
: isLoggedIn
|
||||
} else {
|
||||
results.canAccessAdmin = false
|
||||
|
||||
55
packages/payload/src/auth/operations/auth.ts
Normal file
55
packages/payload/src/auth/operations/auth.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import type { PayloadRequest } from '../../types/index.js'
|
||||
import type { Permissions, User } from '../types.js'
|
||||
|
||||
import { commitTransaction } from '../../utilities/commitTransaction.js'
|
||||
import { initTransaction } from '../../utilities/initTransaction.js'
|
||||
import { killTransaction } from '../../utilities/killTransaction.js'
|
||||
import { parseCookies } from '../cookies.js'
|
||||
import { executeAuthStrategies } from '../executeAuthStrategies.js'
|
||||
import { getAccessResults } from '../getAccessResults.js'
|
||||
|
||||
export type AuthArgs = {
|
||||
headers: Request['headers']
|
||||
req: Omit<PayloadRequest, 'user'>
|
||||
}
|
||||
|
||||
export type AuthResult = {
|
||||
cookies: Map<string, string>
|
||||
permissions: Permissions
|
||||
user: User | null
|
||||
}
|
||||
|
||||
export const auth = async (args: AuthArgs): Promise<AuthResult> => {
|
||||
const { headers } = args
|
||||
const req = args.req as PayloadRequest
|
||||
const { payload } = req
|
||||
|
||||
const cookies = parseCookies(headers)
|
||||
|
||||
try {
|
||||
const shouldCommit = await initTransaction(req)
|
||||
|
||||
const user = await executeAuthStrategies({
|
||||
cookies,
|
||||
headers,
|
||||
payload,
|
||||
})
|
||||
|
||||
req.user = user
|
||||
|
||||
const permissions = await getAccessResults({
|
||||
req,
|
||||
})
|
||||
|
||||
if (shouldCommit) await commitTransaction(req)
|
||||
|
||||
return {
|
||||
cookies,
|
||||
permissions,
|
||||
user,
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
await killTransaction(req)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
15
packages/payload/src/auth/operations/local/auth.ts
Normal file
15
packages/payload/src/auth/operations/local/auth.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import type { Payload } from '../../../index.js'
|
||||
import type { PayloadRequest } from '../../../types/index.js'
|
||||
import type { AuthArgs, AuthResult } from '../auth.js'
|
||||
|
||||
import { createLocalReq } from '../../../utilities/createLocalReq.js'
|
||||
import { auth as authOperation } from '../auth.js'
|
||||
|
||||
export const auth = async (payload: Payload, options: AuthArgs): Promise<AuthResult> => {
|
||||
const { headers } = options
|
||||
|
||||
return await authOperation({
|
||||
headers,
|
||||
req: createLocalReq({ req: options.req as PayloadRequest }, payload),
|
||||
})
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { auth } from './auth.js'
|
||||
import forgotPassword from './forgotPassword.js'
|
||||
import login from './login.js'
|
||||
import resetPassword from './resetPassword.js'
|
||||
@@ -5,6 +6,7 @@ import unlock from './unlock.js'
|
||||
import verifyEmail from './verifyEmail.js'
|
||||
|
||||
export default {
|
||||
auth,
|
||||
forgotPassword,
|
||||
login,
|
||||
resetPassword,
|
||||
|
||||
40
packages/payload/src/bin/loader/compile.ts
Normal file
40
packages/payload/src/bin/loader/compile.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import type * as ts from 'typescript'
|
||||
|
||||
import { transform } from '@swc-node/core'
|
||||
import { SourcemapMap } from '@swc-node/sourcemap-support'
|
||||
|
||||
import { tsCompilerOptionsToSwcConfig } from './read-default-tsconfig.js'
|
||||
|
||||
const injectInlineSourceMap = ({
|
||||
code,
|
||||
filename,
|
||||
map,
|
||||
}: {
|
||||
code: string
|
||||
filename: string
|
||||
map: string | undefined
|
||||
}): string => {
|
||||
if (map) {
|
||||
SourcemapMap.set(filename, map)
|
||||
const base64Map = Buffer.from(map, 'utf8').toString('base64')
|
||||
const sourceMapContent = `//# sourceMappingURL=data:application/json;charset=utf-8;base64,${base64Map}`
|
||||
return `${code}\n${sourceMapContent}`
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
export async function compile(
|
||||
sourcecode: string,
|
||||
filename: string,
|
||||
options: ts.CompilerOptions & { fallbackToTs?: (filename: string) => boolean },
|
||||
): Promise<string> {
|
||||
if (filename.endsWith('.d.ts')) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const swcRegisterConfig = tsCompilerOptionsToSwcConfig(options, filename)
|
||||
|
||||
return transform(sourcecode, filename, swcRegisterConfig).then(({ code, map }) => {
|
||||
return injectInlineSourceMap({ code, filename, map })
|
||||
})
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import ts from 'typescript'
|
||||
import { fileURLToPath, pathToFileURL } from 'url'
|
||||
|
||||
import { CLIENT_EXTENSIONS } from './clientExtensions.js'
|
||||
import { compile } from './register.js'
|
||||
import { compile } from './compile.js'
|
||||
|
||||
interface ResolveContext {
|
||||
conditions: string[]
|
||||
@@ -26,6 +26,8 @@ type ResolveFn = (...args: Required<ResolveArgs>) => Promise<ResolveResult>
|
||||
const locatedConfig = getTsconfig()
|
||||
const tsconfig = locatedConfig.config.compilerOptions as unknown as ts.CompilerOptions
|
||||
|
||||
// Don't resolve d.ts files, because we aren't type-checking
|
||||
tsconfig.noDtsResolution = true
|
||||
tsconfig.module = ts.ModuleKind.ESNext
|
||||
tsconfig.moduleResolution = ts.ModuleResolutionKind.NodeNext
|
||||
|
||||
@@ -38,18 +40,25 @@ const host: ts.ModuleResolutionHost = {
|
||||
fileExists: ts.sys.fileExists,
|
||||
readFile: ts.sys.readFile,
|
||||
}
|
||||
const EXTENSIONS: string[] = [ts.Extension.Ts, ts.Extension.Tsx, ts.Extension.Dts, ts.Extension.Mts]
|
||||
const TS_EXTENSIONS: string[] = [
|
||||
ts.Extension.Ts,
|
||||
ts.Extension.Tsx,
|
||||
ts.Extension.Dts,
|
||||
ts.Extension.Mts,
|
||||
]
|
||||
|
||||
export const resolve: ResolveFn = async (specifier, context, nextResolve) => {
|
||||
const isTS = EXTENSIONS.some((ext) => specifier.endsWith(ext))
|
||||
const isTS = TS_EXTENSIONS.some((ext) => specifier.endsWith(ext))
|
||||
const isClient = CLIENT_EXTENSIONS.some((ext) => specifier.endsWith(ext))
|
||||
|
||||
// If a client file is resolved, we'll set `format: client`
|
||||
// and short circuit, so the load step
|
||||
// will return source code of empty object
|
||||
if (isClient) {
|
||||
const nextResult = await nextResolve(specifier, context, nextResolve)
|
||||
const specifierSegments = specifier.split('.')
|
||||
|
||||
return {
|
||||
format: '.' + specifierSegments[specifierSegments.length - 1],
|
||||
format: 'client',
|
||||
shortCircuit: true,
|
||||
url: nextResult.url,
|
||||
}
|
||||
@@ -64,9 +73,28 @@ export const resolve: ResolveFn = async (specifier, context, nextResolve) => {
|
||||
}
|
||||
}
|
||||
|
||||
// import/require from external library
|
||||
if (context.parentURL.includes('/node_modules/') && !isTS) {
|
||||
return nextResolve(specifier)
|
||||
// Try and resolve normally
|
||||
// This could fail, so we need to swallow that error
|
||||
// and keep going
|
||||
let nextResult: ResolveResult
|
||||
|
||||
// First, try to
|
||||
if (!isTS) {
|
||||
try {
|
||||
nextResult = await nextResolve(specifier, context, nextResolve)
|
||||
} catch (_) {
|
||||
// swallow error
|
||||
}
|
||||
}
|
||||
|
||||
if (nextResult) {
|
||||
const nextResultIsTS = TS_EXTENSIONS.some((ext) => nextResult.url.endsWith(ext))
|
||||
|
||||
return {
|
||||
...nextResult,
|
||||
format: nextResultIsTS ? 'ts' : nextResult.format,
|
||||
shortCircuit: true,
|
||||
}
|
||||
}
|
||||
|
||||
const { resolvedModule } = ts.resolveModuleName(
|
||||
@@ -77,14 +105,11 @@ export const resolve: ResolveFn = async (specifier, context, nextResolve) => {
|
||||
moduleResolutionCache,
|
||||
)
|
||||
|
||||
// import from local project to local project TS file
|
||||
if (
|
||||
resolvedModule &&
|
||||
!resolvedModule.resolvedFileName.includes('/node_modules/') &&
|
||||
EXTENSIONS.includes(resolvedModule.extension)
|
||||
) {
|
||||
if (resolvedModule) {
|
||||
const resolvedIsTS = TS_EXTENSIONS.includes(resolvedModule.extension)
|
||||
|
||||
return {
|
||||
format: 'ts',
|
||||
format: resolvedIsTS ? 'ts' : undefined,
|
||||
shortCircuit: true,
|
||||
url: pathToFileURL(resolvedModule.resolvedFileName).href,
|
||||
}
|
||||
@@ -92,9 +117,8 @@ export const resolve: ResolveFn = async (specifier, context, nextResolve) => {
|
||||
|
||||
// import from local project to either:
|
||||
// - something TS couldn't resolve
|
||||
// - external library
|
||||
// - local project non-TS file
|
||||
return nextResolve(specifier)
|
||||
return nextResolve(specifier, context, nextResolve)
|
||||
}
|
||||
|
||||
interface LoadContext {
|
||||
@@ -127,11 +151,11 @@ if (tsconfig.paths) {
|
||||
}
|
||||
|
||||
export const load: LoadFn = async (url, context, nextLoad) => {
|
||||
if (CLIENT_EXTENSIONS.some((e) => context.format === e)) {
|
||||
const rawSource = '{}'
|
||||
if (context.format === 'client') {
|
||||
const rawSource = 'export default {}'
|
||||
|
||||
return {
|
||||
format: 'json',
|
||||
format: 'module',
|
||||
shortCircuit: true,
|
||||
source: rawSource,
|
||||
}
|
||||
@@ -140,7 +164,7 @@ export const load: LoadFn = async (url, context, nextLoad) => {
|
||||
if (context.format === 'ts') {
|
||||
const { source } = await nextLoad(url, context)
|
||||
const code = typeof source === 'string' ? source : Buffer.from(source).toString()
|
||||
const compiled = await compile(code, fileURLToPath(url), swcOptions, true)
|
||||
const compiled = await compile(code, fileURLToPath(url), swcOptions)
|
||||
return {
|
||||
format: 'module',
|
||||
shortCircuit: true,
|
||||
@@ -1,125 +0,0 @@
|
||||
import type { Options } from '@swc-node/core'
|
||||
|
||||
import { transform, transformSync } from '@swc-node/core'
|
||||
import { SourcemapMap, installSourceMapSupport } from '@swc-node/sourcemap-support'
|
||||
import { getTsconfig } from 'get-tsconfig'
|
||||
import { platform } from 'os'
|
||||
import { resolve } from 'path'
|
||||
import { addHook } from 'pirates'
|
||||
import * as ts from 'typescript'
|
||||
|
||||
import { tsCompilerOptionsToSwcConfig } from './read-default-tsconfig.js'
|
||||
|
||||
const DEFAULT_EXTENSIONS = ['.js', '.jsx', '.es6', '.es', '.mjs', '.ts', '.tsx']
|
||||
const PLATFORM = platform()
|
||||
|
||||
const injectInlineSourceMap = ({
|
||||
code,
|
||||
filename,
|
||||
map,
|
||||
}: {
|
||||
code: string
|
||||
filename: string
|
||||
map: string | undefined
|
||||
}): string => {
|
||||
if (map) {
|
||||
SourcemapMap.set(filename, map)
|
||||
const base64Map = Buffer.from(map, 'utf8').toString('base64')
|
||||
const sourceMapContent = `//# sourceMappingURL=data:application/json;charset=utf-8;base64,${base64Map}`
|
||||
return `${code}\n${sourceMapContent}`
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
export function compile(
|
||||
sourcecode: string,
|
||||
filename: string,
|
||||
options: ts.CompilerOptions & { fallbackToTs?: (filename: string) => boolean },
|
||||
): string
|
||||
|
||||
export function compile(
|
||||
sourcecode: string,
|
||||
filename: string,
|
||||
options: ts.CompilerOptions & { fallbackToTs?: (filename: string) => boolean },
|
||||
async: false,
|
||||
): string
|
||||
|
||||
export function compile(
|
||||
sourcecode: string,
|
||||
filename: string,
|
||||
options: ts.CompilerOptions & { fallbackToTs?: (filename: string) => boolean },
|
||||
async: true,
|
||||
): Promise<string>
|
||||
|
||||
export function compile(
|
||||
sourcecode: string,
|
||||
filename: string,
|
||||
options: ts.CompilerOptions & { fallbackToTs?: (filename: string) => boolean },
|
||||
async: boolean,
|
||||
): Promise<string> | string
|
||||
|
||||
export function compile(
|
||||
sourcecode: string,
|
||||
filename: string,
|
||||
options: ts.CompilerOptions & { fallbackToTs?: (filename: string) => boolean },
|
||||
async = false,
|
||||
) {
|
||||
if (filename.endsWith('.d.ts')) {
|
||||
return ''
|
||||
}
|
||||
if (options.files && (options.files as string[]).length) {
|
||||
if (
|
||||
PLATFORM === 'win32' &&
|
||||
(options.files as string[]).every((file) => filename !== resolve(process.cwd(), file))
|
||||
) {
|
||||
return sourcecode
|
||||
}
|
||||
if (
|
||||
PLATFORM !== 'win32' &&
|
||||
(options.files as string[]).every((file) => !filename.endsWith(file))
|
||||
) {
|
||||
return sourcecode
|
||||
}
|
||||
}
|
||||
if (options && typeof options.fallbackToTs === 'function' && options.fallbackToTs(filename)) {
|
||||
delete options.fallbackToTs
|
||||
const { outputText, sourceMapText } = ts.transpileModule(sourcecode, {
|
||||
compilerOptions: options,
|
||||
fileName: filename,
|
||||
})
|
||||
return injectInlineSourceMap({ code: outputText, filename, map: sourceMapText })
|
||||
}
|
||||
|
||||
let swcRegisterConfig: Options
|
||||
if (process.env.SWCRC) {
|
||||
// when SWCRC environment variable is set to true it will use swcrc file
|
||||
swcRegisterConfig = {
|
||||
swc: {
|
||||
swcrc: true,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
swcRegisterConfig = tsCompilerOptionsToSwcConfig(options, filename)
|
||||
}
|
||||
|
||||
if (async) {
|
||||
return transform(sourcecode, filename, swcRegisterConfig).then(({ code, map }) => {
|
||||
return injectInlineSourceMap({ code, filename, map })
|
||||
})
|
||||
} else {
|
||||
const { code, map } = transformSync(sourcecode, filename, swcRegisterConfig)
|
||||
return injectInlineSourceMap({ code, filename, map })
|
||||
}
|
||||
}
|
||||
|
||||
export function register(options: Partial<ts.CompilerOptions> = {}, hookOpts = {}) {
|
||||
const locatedConfig = getTsconfig()
|
||||
const tsconfig = locatedConfig.config.compilerOptions as unknown as ts.CompilerOptions
|
||||
options = tsconfig
|
||||
// options.module = ts.ModuleKind.CommonJS
|
||||
installSourceMapSupport()
|
||||
return addHook((code, filename) => compile(code, filename, options), {
|
||||
exts: DEFAULT_EXTENSIONS,
|
||||
...hookOpts,
|
||||
})
|
||||
}
|
||||
@@ -99,7 +99,7 @@ const sanitizeCollection = (
|
||||
if (sanitized.upload === true) sanitized.upload = {}
|
||||
|
||||
// disable duplicate for uploads by default
|
||||
sanitized.admin.disableDuplicate = sanitized.admin.disableDuplicate || true
|
||||
sanitized.disableDuplicate = sanitized.disableDuplicate || true
|
||||
|
||||
sanitized.upload.staticDir = sanitized.upload.staticDir || sanitized.slug
|
||||
sanitized.admin.useAsTitle =
|
||||
@@ -140,7 +140,7 @@ const sanitizeCollection = (
|
||||
}
|
||||
|
||||
// disable duplicate for auth enabled collections by default
|
||||
sanitized.admin.disableDuplicate = sanitized.admin.disableDuplicate || true
|
||||
sanitized.disableDuplicate = sanitized.disableDuplicate || true
|
||||
|
||||
if (!sanitized.auth.strategies) {
|
||||
sanitized.auth.strategies = []
|
||||
|
||||
@@ -59,7 +59,6 @@ const collectionSchema = joi.object().keys({
|
||||
}),
|
||||
defaultColumns: joi.array().items(joi.string()),
|
||||
description: joi.alternatives().try(joi.string(), componentSchema),
|
||||
disableDuplicate: joi.bool(),
|
||||
enableRichTextLink: joi.boolean(),
|
||||
enableRichTextRelationship: joi.boolean(),
|
||||
group: joi.alternatives().try(joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
|
||||
@@ -109,7 +108,9 @@ const collectionSchema = joi.object().keys({
|
||||
joi.boolean(),
|
||||
),
|
||||
custom: joi.object().pattern(joi.string(), joi.any()),
|
||||
dbName: joi.alternatives().try(joi.string(), joi.func()),
|
||||
defaultSort: joi.string(),
|
||||
disableDuplicate: joi.bool(),
|
||||
endpoints: endpointsSchema,
|
||||
fields: joi.array(),
|
||||
graphQL: joi.alternatives().try(
|
||||
@@ -136,12 +137,6 @@ const collectionSchema = joi.object().keys({
|
||||
beforeRead: joi.array().items(joi.func()),
|
||||
beforeValidate: joi.array().items(joi.func()),
|
||||
}),
|
||||
indexes: joi.array().items(
|
||||
joi.object().keys({
|
||||
fields: joi.object().required(),
|
||||
options: joi.object(),
|
||||
}),
|
||||
),
|
||||
labels: joi.object({
|
||||
plural: joi
|
||||
.alternatives()
|
||||
|
||||
@@ -251,7 +251,6 @@ export type CollectionAdminOptions = {
|
||||
* Custom description for collection
|
||||
*/
|
||||
description?: EntityDescription
|
||||
disableDuplicate?: boolean
|
||||
enableRichTextLink?: boolean
|
||||
enableRichTextRelationship?: boolean
|
||||
/**
|
||||
@@ -294,7 +293,7 @@ export type CollectionConfig = {
|
||||
* Access control
|
||||
*/
|
||||
access?: {
|
||||
admin?: (args?: any) => Promise<boolean> | boolean
|
||||
admin?: ({ req }: { req: PayloadRequest }) => Promise<boolean> | boolean
|
||||
create?: Access
|
||||
delete?: Access
|
||||
read?: Access
|
||||
@@ -318,6 +317,10 @@ export type CollectionConfig = {
|
||||
* Default field to sort by in collection list view
|
||||
*/
|
||||
defaultSort?: string
|
||||
/**
|
||||
* When true, do not show the "Duplicate" button while editing documents within this collection and prevent `duplicate` from all APIs
|
||||
*/
|
||||
disableDuplicate?: boolean
|
||||
/**
|
||||
* Custom rest api endpoints, set false to disable all rest endpoints for this collection.
|
||||
*/
|
||||
|
||||
@@ -58,7 +58,7 @@ export default async function createLocal<TSlug extends keyof GeneratedTypes['co
|
||||
)
|
||||
}
|
||||
|
||||
const req = await createLocalReq(options, payload)
|
||||
const req = createLocalReq(options, payload)
|
||||
req.file = file ?? (await getFileByPath(filePath))
|
||||
|
||||
return createOperation<TSlug>({
|
||||
|
||||
@@ -43,7 +43,14 @@ export async function duplicate<TSlug extends keyof GeneratedTypes['collections'
|
||||
)
|
||||
}
|
||||
|
||||
const req = await createLocalReq(options, payload)
|
||||
if (collection.config.disableDuplicate === false) {
|
||||
throw new APIError(
|
||||
`The collection with slug ${String(collectionSlug)} cannot be duplicated.`,
|
||||
400,
|
||||
)
|
||||
}
|
||||
|
||||
const req = createLocalReq(options, payload)
|
||||
|
||||
return duplicateOperation<TSlug>({
|
||||
id,
|
||||
|
||||
@@ -18,6 +18,7 @@ export type ServerOnlyRootProperties = keyof Pick<
|
||||
| 'csrf'
|
||||
| 'db'
|
||||
| 'editor'
|
||||
| 'email'
|
||||
| 'endpoints'
|
||||
| 'hooks'
|
||||
| 'onInit'
|
||||
@@ -59,6 +60,7 @@ export const createClientConfig = async (
|
||||
'typescript',
|
||||
'cors',
|
||||
'csrf',
|
||||
'email',
|
||||
// `admin`, `onInit`, `localization`, `collections`, and `globals` are all handled separately
|
||||
]
|
||||
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import type pino from 'pino'
|
||||
|
||||
import { createRequire } from 'module'
|
||||
import path from 'path'
|
||||
|
||||
import type { SanitizedConfig } from './types.js'
|
||||
|
||||
import { CLIENT_EXTENSIONS } from '../bin/register/clientExtensions.js'
|
||||
import Logger from '../utilities/logger.js'
|
||||
import { findConfig } from './find.js'
|
||||
import { validateSchema } from './validate.js'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
|
||||
const loadConfig = async (logger?: pino.Logger): Promise<SanitizedConfig> => {
|
||||
const localLogger = logger ?? Logger()
|
||||
|
||||
const configPath = findConfig()
|
||||
|
||||
CLIENT_EXTENSIONS.forEach((ext) => {
|
||||
require.extensions[ext] = () => null
|
||||
})
|
||||
|
||||
const configPromise = await import(configPath)
|
||||
|
||||
let config = await configPromise
|
||||
|
||||
if ('default' in config) config = await config.default
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
config = validateSchema(config, localLogger)
|
||||
}
|
||||
|
||||
return {
|
||||
...config,
|
||||
paths: {
|
||||
config: configPath,
|
||||
configDir: path.dirname(configPath),
|
||||
rawConfig: configPath,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default loadConfig
|
||||
@@ -63,9 +63,11 @@ export type LivePreviewConfig = {
|
||||
*/
|
||||
url?:
|
||||
| ((args: {
|
||||
collectionConfig?: SanitizedCollectionConfig
|
||||
data: Record<string, any>
|
||||
documentInfo: any // TODO: remove or populate this
|
||||
globalConfig?: SanitizedGlobalConfig
|
||||
locale: Locale
|
||||
payload: Payload
|
||||
}) => Promise<string> | string)
|
||||
| string
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ export const readMigrationFiles = async ({
|
||||
files.map(async (filePath) => {
|
||||
// eval used to circumvent errors bundling
|
||||
let migration = await eval(
|
||||
`${require ? 'require' : 'import'}('${filePath.replaceAll('\\', '/')}')`,
|
||||
`${typeof require === 'function' ? 'require' : 'import'}('${filePath.replaceAll('\\', '/')}')`,
|
||||
)
|
||||
if ('default' in migration) migration = migration.default
|
||||
|
||||
|
||||
@@ -411,3 +411,10 @@ export type DatabaseAdapterResult<T = BaseDatabaseAdapter> = {
|
||||
defaultIDType: 'number' | 'text'
|
||||
init: (args: { payload: Payload }) => T
|
||||
}
|
||||
|
||||
export type DBIdentifierName =
|
||||
| ((Args: {
|
||||
/** The name of the parent table when using relational DBs */
|
||||
tableName?: string
|
||||
}) => string)
|
||||
| string
|
||||
|
||||
14
packages/payload/src/errors/MissingEditorProp.ts
Normal file
14
packages/payload/src/errors/MissingEditorProp.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { Field } from '../fields/config/types.js'
|
||||
|
||||
import { fieldAffectsData } from '../fields/config/types.js'
|
||||
import APIError from './APIError.js'
|
||||
|
||||
class MissingEditorProp extends APIError {
|
||||
constructor(field: Field) {
|
||||
super(
|
||||
`RichText field${fieldAffectsData(field) ? ` "${field.name}"` : ''} is missing the editor prop`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default MissingEditorProp
|
||||
@@ -1,7 +1,7 @@
|
||||
export * from '../auth/index.js'
|
||||
export { default as executeAccess } from '../auth/executeAccess.js'
|
||||
export { executeAuthStrategies } from '../auth/executeAuthStrategies.js'
|
||||
export { getAccessResults } from '../auth/getAccessResults.js'
|
||||
export { getAuthenticatedUser } from '../auth/getAuthenticatedUser.js'
|
||||
|
||||
export type {
|
||||
AuthStrategyFunction,
|
||||
|
||||
@@ -12,6 +12,7 @@ export type {
|
||||
CreateMigration,
|
||||
CreateVersion,
|
||||
CreateVersionArgs,
|
||||
DBIdentifierName,
|
||||
DatabaseAdapterResult as DatabaseAdapterObj,
|
||||
DeleteMany,
|
||||
DeleteManyArgs,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user