Compare commits
1 Commits
revert-111
...
fix/join-h
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b9a2a4a0c |
2
.github/comments/invalid-reproduction.md
vendored
2
.github/comments/invalid-reproduction.md
vendored
@@ -4,7 +4,7 @@ Depending on the quality of reproduction steps, this issue may be closed if no r
|
||||
|
||||
### Why was this issue marked with the `invalid-reproduction` label?
|
||||
|
||||
To be able to investigate, we need access to a reproduction to identify what triggered the issue. We prefer a link to a public GitHub repository created with `create-payload-app@latest -t blank` or a forked/branched version of this repository with tests added (more info in the [reproduction-guide](https://github.com/payloadcms/payload/blob/main/.github/reproduction-guide.md)).
|
||||
To be able to investigate, we need access to a reproduction to identify what triggered the issue. We prefer a link to a public GitHub repository created with `create-payload-app@beta -t blank` or a forked/branched version of this repository with tests added (more info in the [reproduction-guide](https://github.com/payloadcms/payload/blob/main/.github/reproduction-guide.md)).
|
||||
|
||||
To make sure the issue is resolved as quickly as possible, please make sure that the reproduction is as **minimal** as possible. This means that you should **remove unnecessary code, files, and dependencies** that do not contribute to the issue. Ensure your reproduction does not depend on secrets, 3rd party registries, private dependencies, or any other data that cannot be made public. Avoid a reproduction including a whole monorepo (unless relevant to the issue). The easier it is to reproduce the issue, the quicker we can help.
|
||||
|
||||
|
||||
@@ -158,7 +158,6 @@ object with:
|
||||
|
||||
- `docs` an array of related documents or only IDs if the depth is reached
|
||||
- `hasNextPage` a boolean indicating if there are additional documents
|
||||
- `totalDocs` a total number of documents, exists only if `count: true` is passed to the join query
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -172,8 +171,7 @@ object with:
|
||||
}
|
||||
// { ... }
|
||||
],
|
||||
"hasNextPage": false,
|
||||
"totalDocs": 10, // if count: true is passed
|
||||
"hasNextPage": false
|
||||
}
|
||||
// other fields...
|
||||
}
|
||||
@@ -186,7 +184,6 @@ object with:
|
||||
|
||||
- `docs` an array of `relationTo` - the collection slug of the document and `value` - the document itself or the ID if the depth is reached
|
||||
- `hasNextPage` a boolean indicating if there are additional documents
|
||||
- `totalDocs` a total number of documents, exists only if `count: true` is passed to the join query
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -203,8 +200,7 @@ object with:
|
||||
}
|
||||
// { ... }
|
||||
],
|
||||
"hasNextPage": false,
|
||||
"totalDocs": 10, // if count: true is passed
|
||||
"hasNextPage": false
|
||||
}
|
||||
// other fields...
|
||||
}
|
||||
@@ -219,11 +215,10 @@ returning. This is useful for performance reasons when you don't need the relate
|
||||
The following query options are supported:
|
||||
|
||||
| Property | Description |
|
||||
| ----------- | --------------------------------------------------------------------------------------------------- |
|
||||
|-------------|-----------------------------------------------------------------------------------------------------|
|
||||
| **`limit`** | The maximum related documents to be returned, default is 10. |
|
||||
| **`where`** | An optional `Where` query to filter joined documents. Will be merged with the field `where` object. |
|
||||
| **`sort`** | A string used to order related results |
|
||||
| **`count`** | Whether include the count of related documents or not. Not included by default |
|
||||
|
||||
These can be applied to the local API, GraphQL, and REST API.
|
||||
|
||||
|
||||
@@ -44,31 +44,3 @@ const createdJob = await payload.jobs.queue({
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
#### Cancelling Jobs
|
||||
|
||||
Payload allows you to cancel jobs that are either queued or currently running. When cancelling a running job, the current task will finish executing, but no subsequent tasks will run. This happens because the job checks its cancellation status between tasks.
|
||||
|
||||
##### Cancel a Single Job
|
||||
|
||||
To cancel a specific job, use the `payload.jobs.cancelByID` method with the job's ID:
|
||||
|
||||
```ts
|
||||
await payload.jobs.cancelByID({
|
||||
id: createdJob.id,
|
||||
})
|
||||
```
|
||||
|
||||
##### Cancel Multiple Jobs
|
||||
|
||||
To cancel multiple jobs at once, use the `payload.jobs.cancel` method with a `Where` query:
|
||||
|
||||
```ts
|
||||
await payload.jobs.cancel({
|
||||
where: {
|
||||
workflowSlug: {
|
||||
equals: 'createPost',
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
@@ -1113,57 +1113,6 @@ plugins: [
|
||||
|
||||
If you have custom features for `@payloadcms/richtext-lexical` you will need to migrate your code to the new API. Read more about the new API in the [documentation](https://payloadcms.com/docs/rich-text/building-custom-features).
|
||||
|
||||
## Reserved Field names
|
||||
|
||||
Payload reserves certain field names for internal use. Using any of the following names in your collections or globals will result in those fields being sanitized from the config, which can cause deployment errors. Ensure that any conflicting fields are renamed before migrating.
|
||||
|
||||
### General Reserved Names
|
||||
|
||||
- `file`
|
||||
- `_id` (MongoDB only)
|
||||
- `__v` (MongoDB only)
|
||||
|
||||
**Important Note**: It is recommended to avoid using field names with an underscore (`_`) prefix unless explicitly required by a plugin. Payload uses this prefix for internal columns, which can lead to conflicts in certain SQL conditions. The following are examples of reserved internal columns (this list is not exhaustive and other internal fields may also apply):
|
||||
|
||||
- `_order`
|
||||
- `_path`
|
||||
- `_uuid`
|
||||
- `_parent_id`
|
||||
- `_locale`
|
||||
|
||||
### Auth-Related Reserved Names
|
||||
|
||||
These are restricted if your collection uses `auth: true` and does not have `disableAuthStrategy: true`:
|
||||
- `salt`
|
||||
- `hash`
|
||||
- `apiKey` (when `auth.useAPIKey: true` is enabled)
|
||||
- `useAPIKey` (when `auth.useAPIKey: true` is enabled)
|
||||
- `resetPasswordToken`
|
||||
- `resetPasswordExpiration`
|
||||
- `password`
|
||||
- `email`
|
||||
- `username`
|
||||
|
||||
### Upload-Related Reserved Names
|
||||
|
||||
These apply if your collection has `upload: true` configured:
|
||||
|
||||
- `filename`
|
||||
- `mimetype`
|
||||
- `filesize`
|
||||
- `width`
|
||||
- `height`
|
||||
- `focalX`
|
||||
- `focalY`
|
||||
- `url`
|
||||
- `thumbnailURL`
|
||||
|
||||
If `imageSizes` is configured, the following are also reserved:
|
||||
|
||||
- `sizes`
|
||||
|
||||
If any of these names are found in your collection / global fields, update them before migrating to avoid unexpected issues.
|
||||
|
||||
## Upgrade from previous beta
|
||||
|
||||
Reference this [community-made site](https://payload-releases-filter.vercel.app/?version=3&from=152429656&to=188243150&sort=asc&breaking=on). Set your version, sort by oldest first, enable breaking changes only.
|
||||
|
||||
@@ -6,7 +6,7 @@ desc: Starting to build your own plugin? Find everything you need and learn best
|
||||
keywords: plugins, template, config, configuration, extensions, custom, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
Building your own [Payload Plugin](./overview) is easy, and if you're already familiar with Payload then you'll have everything you need to get started. You can either start from scratch or use the [Plugin Template](#plugin-template) to get up and running quickly.
|
||||
Building your own [Payload Plugin](./overview) is easy, and if you're already familiar with Payload then you'll have everything you need to get started. You can either start from scratch or use the [Plugin Template](#plugin-template) to get up and running quickly.
|
||||
|
||||
<Banner type="success">
|
||||
To use the template, run `npx create-payload-app@latest --template plugin` directly in
|
||||
@@ -19,7 +19,7 @@ Our plugin template includes everything you need to build a full life-cycle plug
|
||||
- A local dev environment to develop the plugin
|
||||
- Test suite with integrated GitHub workflow
|
||||
|
||||
By abstracting your code into a plugin, you'll be able to reuse your feature across multiple projects and make it available for other developers to use.
|
||||
By abstracting your code into a plugin, you'll be able to reuse your feature across multiple projects and make it available for other developers to use.
|
||||
|
||||
## Plugins Recap
|
||||
|
||||
@@ -75,7 +75,7 @@ The purpose of the **dev** folder is to provide a sanitized local Payload projec
|
||||
|
||||
Do **not** store any of the plugin functionality in this folder - it is purely an environment to _assist_ you with developing the plugin.
|
||||
|
||||
If you're starting from scratch, you can easily setup a dev environment like this:
|
||||
If you're starting from scratch, you can easily setup a dev environment like this:
|
||||
|
||||
```
|
||||
mkdir dev
|
||||
@@ -83,7 +83,7 @@ cd dev
|
||||
npx create-payload-app@latest
|
||||
```
|
||||
|
||||
If you're using the plugin template, the dev folder is built out for you and the `samplePlugin` has already been installed in `dev/payload.config.ts`.
|
||||
If you're using the plugin template, the dev folder is built out for you and the `samplePlugin` has already been installed in `dev/payload.config.ts`.
|
||||
|
||||
```
|
||||
plugins: [
|
||||
@@ -96,7 +96,7 @@ If you're using the plugin template, the dev folder is built out for you and the
|
||||
|
||||
You can add to the `dev/payload.config.ts` and build out the dev project as needed to test your plugin.
|
||||
|
||||
When you're ready to start development, navigate into this folder with `cd dev`
|
||||
When you're ready to start development, navigate into this folder with `cd dev`
|
||||
|
||||
And then start the project with `pnpm dev` and pull up `http://localhost:3000` in your browser.
|
||||
|
||||
@@ -108,7 +108,7 @@ A good test suite is essential to ensure quality and stability in your plugin. P
|
||||
|
||||
Jest organizes tests into test suites and cases. We recommend creating tests based on the expected behavior of your plugin from start to finish. Read more about tests in the [Jest documentation.](https://jestjs.io/)
|
||||
|
||||
The plugin template provides a stubbed out test suite at `dev/plugin.spec.ts` which is ready to go - just add in your own test conditions and you're all set!
|
||||
The plugin template provides a stubbed out test suite at `dev/plugin.spec.ts` which is ready to go - just add in your own test conditions and you're all set!
|
||||
|
||||
```
|
||||
let payload: Payload
|
||||
@@ -160,7 +160,7 @@ export const seed = async (payload: Payload): Promise<void> => {
|
||||
|
||||
## Building a Plugin
|
||||
|
||||
Now that we have our environment setup and dev project ready to go - it's time to build the plugin!
|
||||
Now that we have our environment setup and dev project ready to go - it's time to build the plugin!
|
||||
|
||||
|
||||
```
|
||||
@@ -217,7 +217,7 @@ To reiterate, the essence of a [Payload Plugin](./overview) is simply to extend
|
||||
|
||||
We are going to use spread syntax to allow us to add data to existing arrays without losing the existing data. It is crucial to spread the existing data correctly, else this can cause adverse behavior and conflicts with Payload Config and other plugins.
|
||||
|
||||
Let's say you want to build a plugin that adds a new collection:
|
||||
Let's say you want to build a plugin that adds a new collection:
|
||||
|
||||
```
|
||||
config.collections = [
|
||||
@@ -227,7 +227,7 @@ config.collections = [
|
||||
]
|
||||
```
|
||||
|
||||
First, you need to spread the `config.collections` to ensure that we don't lose the existing collections. Then you can add any additional collections, just as you would in a regular Payload Config.
|
||||
First, you need to spread the `config.collections` to ensure that we don't lose the existing collections. Then you can add any additional collections, just as you would in a regular Payload Config.
|
||||
|
||||
This same logic is applied to other array and object like properties such as admin, globals and hooks:
|
||||
|
||||
@@ -284,7 +284,7 @@ For a better user experience, provide a way to disable the plugin without uninst
|
||||
|
||||
### Include tests in your GitHub CI workflow
|
||||
|
||||
If you've configured tests for your package, integrate them into your workflow to run the tests each time you commit to the plugin repository. Learn more about [how to configure tests into your GitHub CI workflow.](https://docs.github.com/en/actions/use-cases-and-examples/building-and-testing/building-and-testing-nodejs)
|
||||
If you've configured tests for your package, integrate them into your workflow to run the tests each time you commit to the plugin repository. Learn more about [how to configure tests into your GitHub CI workflow.](https://docs.github.com/en/actions/use-cases-and-examples/building-and-testing/building-and-testing-nodejs)
|
||||
|
||||
### Publish your finished plugin to npm
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ The plugin accepts an object with the following properties:
|
||||
|
||||
```ts
|
||||
type MultiTenantPluginConfig<ConfigTypes = unknown> = {
|
||||
/**
|
||||
/**
|
||||
* After a tenant is deleted, the plugin will attempt to clean up related documents
|
||||
* - removing documents with the tenant ID
|
||||
* - removing the tenant from users
|
||||
@@ -158,16 +158,6 @@ type MultiTenantPluginConfig<ConfigTypes = unknown> = {
|
||||
rowFields?: never
|
||||
tenantFieldAccess?: never
|
||||
}
|
||||
/**
|
||||
* Customize tenant selector label
|
||||
*
|
||||
* Either a string or an object where the keys are locales and the values are the string labels
|
||||
*/
|
||||
tenantSelectorLabel?:
|
||||
| Partial<{
|
||||
[key in AcceptedLanguages]?: string
|
||||
}>
|
||||
| string
|
||||
/**
|
||||
* The slug for the tenant collection
|
||||
*
|
||||
@@ -186,14 +176,6 @@ type MultiTenantPluginConfig<ConfigTypes = unknown> = {
|
||||
* Opt out of adding access constraints to the tenants collection
|
||||
*/
|
||||
useTenantsCollectionAccess?: boolean
|
||||
/**
|
||||
* Opt out including the baseListFilter to filter tenants by selected tenant
|
||||
*/
|
||||
useTenantsListFilter?: boolean
|
||||
/**
|
||||
* Opt out including the baseListFilter to filter users by selected tenant
|
||||
*/
|
||||
useUsersTenantFilter?: boolean
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ Lexical saves data in JSON - this is great for storage and flexibility and allow
|
||||
|
||||
## Lexical => JSX
|
||||
|
||||
For React-based frontends, converting Lexical content to JSX is the recommended rendering approach. Import the RichText component from @payloadcms/richtext-lexical/react and pass the Lexical content to it:
|
||||
If your frontend uses React, converting Lexical to JSX is the recommended way to render rich text content. Import the `RichText` component from `@payloadcms/richtext-lexical/react` and pass the Lexical content to it:
|
||||
|
||||
```tsx
|
||||
import React from 'react'
|
||||
@@ -24,130 +24,46 @@ export const MyComponent = ({ data }: { data: SerializedEditorState }) => {
|
||||
}
|
||||
```
|
||||
|
||||
The `RichText` component includes built-in serializers for common Lexical nodes but allows customization through the `converters` prop. In our [website template](https://github.com/payloadcms/payload/blob/main/templates/website/src/components/RichText/index.tsx) you have an example of how to use `converters` to render custom blocks, custom nodes and override existing converters.
|
||||
The `RichText` component includes built-in serializers for common Lexical nodes but allows customization through the `converters` prop.
|
||||
|
||||
In our website template [you have an example](https://github.com/payloadcms/payload/blob/main/templates/website/src/components/RichText/index.tsx) of how to use `converters` to render custom blocks.
|
||||
|
||||
|
||||
<Banner type="default">
|
||||
When fetching data, ensure your `depth` setting is high enough to fully populate Lexical nodes such as uploads. The JSX converter requires fully populated data to work correctly.
|
||||
The JSX converter expects the input data to be fully populated. When fetching data, ensure the `depth` setting is high enough, to ensure that lexical nodes such as uploads are populated.
|
||||
</Banner>
|
||||
|
||||
### Converting Internal Links
|
||||
### Converting Lexical Blocks to JSX
|
||||
|
||||
By default, Payload doesn't know how to convert **internal** links to JSX, as it doesn't know what the corresponding URL of the internal link is. You'll notice that you get a "found internal link, but internalDocToHref is not provided" error in the console when you try to render content with internal links.
|
||||
|
||||
To fix this, you need to pass the `internalDocToHref` prop to `LinkJSXConverter`. This prop is a function that receives the link node and returns the URL of the document.
|
||||
In order to convert lexical blocks or inline blocks to JSX, you will have to pass the converter for your block to the RichText component. This converter is not included by default, as Payload doesn't know how to render your custom blocks.
|
||||
|
||||
```tsx
|
||||
import type { DefaultNodeTypes, SerializedLinkNode } from '@payloadcms/richtext-lexical'
|
||||
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
|
||||
|
||||
import React from 'react'
|
||||
import {
|
||||
type JSXConvertersFunction,
|
||||
LinkJSXConverter,
|
||||
RichText,
|
||||
} from '@payloadcms/richtext-lexical/react'
|
||||
import React from 'react'
|
||||
|
||||
const internalDocToHref = ({ linkNode }: { linkNode: SerializedLinkNode }) => {
|
||||
const { relationTo, value } = linkNode.fields.doc!
|
||||
if (typeof value !== 'object') {
|
||||
throw new Error('Expected value to be an object')
|
||||
}
|
||||
const slug = value.slug
|
||||
return relationTo === 'posts' ? `/posts/${slug}` : `/${slug}`
|
||||
}
|
||||
|
||||
const jsxConverters: JSXConvertersFunction<DefaultNodeTypes> = ({ defaultConverters }) => ({
|
||||
...defaultConverters,
|
||||
...LinkJSXConverter({ internalDocToHref }),
|
||||
})
|
||||
|
||||
export const MyComponent: React.FC<{
|
||||
lexicalData: SerializedEditorState
|
||||
}> = ({ lexicalData }) => {
|
||||
return <RichText converters={jsxConverters} data={lexicalData} />
|
||||
}
|
||||
```
|
||||
|
||||
### Converting Lexical Blocks
|
||||
|
||||
To convert Lexical Blocks or Inline Blocks to JSX, pass the converter for your block to the `RichText` component. This converter is not included by default, as Payload doesn't know how to render your custom blocks.
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
import type { MyInlineBlock, MyTextBlock } from '@/payload-types'
|
||||
import type { DefaultNodeTypes, SerializedBlockNode } from '@payloadcms/richtext-lexical'
|
||||
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
|
||||
|
||||
import { type JSXConvertersFunction, RichText } from '@payloadcms/richtext-lexical/react'
|
||||
import React from 'react'
|
||||
|
||||
// Extend the default node types with your custom blocks for full type safety
|
||||
type NodeTypes = DefaultNodeTypes | SerializedBlockNode<MyInlineBlock | MyTextBlock>
|
||||
|
||||
const jsxConverters: JSXConvertersFunction<NodeTypes> = ({ defaultConverters }) => ({
|
||||
const jsxConverters: JSXConvertersFunction = ({ defaultConverters }) => ({
|
||||
...defaultConverters,
|
||||
blocks: {
|
||||
// Each key should match your block's slug
|
||||
// myTextBlock is the slug of the block
|
||||
myTextBlock: ({ node }) => <div style={{ backgroundColor: 'red' }}>{node.fields.text}</div>,
|
||||
},
|
||||
inlineBlocks: {
|
||||
// Each key should match your inline block's slug
|
||||
// myInlineBlock is the slug of the block
|
||||
myInlineBlock: ({ node }) => <span>{node.fields.text}</span>,
|
||||
},
|
||||
})
|
||||
|
||||
export const MyComponent: React.FC<{
|
||||
lexicalData: SerializedEditorState
|
||||
}> = ({ lexicalData }) => {
|
||||
return <RichText converters={jsxConverters} data={lexicalData} />
|
||||
}
|
||||
```
|
||||
|
||||
### Overriding Default JSX Converters
|
||||
|
||||
You can override any of the default JSX converters by passing passing your custom converter, keyed to the node type, to the `converters` prop / the converters function.
|
||||
|
||||
Example - overriding the upload node converter to use next/image:
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
import type { DefaultNodeTypes, SerializedUploadNode } from '@payloadcms/richtext-lexical'
|
||||
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
|
||||
|
||||
import { type JSXConvertersFunction, RichText } from '@payloadcms/richtext-lexical/react'
|
||||
import Image from 'next/image'
|
||||
import React from 'react'
|
||||
|
||||
type NodeTypes = DefaultNodeTypes
|
||||
|
||||
// Custom upload converter component that uses next/image
|
||||
const CustomUploadComponent: React.FC<{
|
||||
node: SerializedUploadNode
|
||||
}> = ({ node }) => {
|
||||
if (node.relationTo === 'uploads') {
|
||||
const uploadDoc = node.value
|
||||
if (typeof uploadDoc !== 'object') {
|
||||
return null
|
||||
}
|
||||
const { alt, height, url, width } = uploadDoc
|
||||
return <Image alt={alt} height={height} src={url} width={width} />
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const jsxConverters: JSXConvertersFunction<NodeTypes> = ({ defaultConverters }) => ({
|
||||
...defaultConverters,
|
||||
// Override the default upload converter
|
||||
upload: ({ node }) => {
|
||||
return <CustomUploadComponent node={node} />
|
||||
},
|
||||
})
|
||||
|
||||
export const MyComponent: React.FC<{
|
||||
lexicalData: SerializedEditorState
|
||||
}> = ({ lexicalData }) => {
|
||||
return <RichText converters={jsxConverters} data={lexicalData} />
|
||||
export const MyComponent = ({ lexicalData }) => {
|
||||
return (
|
||||
<RichText
|
||||
converters={jsxConverters}
|
||||
data={lexicalData.lexicalWithBlocks as SerializedEditorState}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -29,9 +29,9 @@ Using the BlocksFeature, you can add both inline blocks (= can be inserted into
|
||||
|
||||
### Example: Code Field Block with language picker
|
||||
|
||||
This example demonstrates how to create a custom code field block with a language picker using the `BlocksFeature`. First, make sure to explicitly install `@payloadcms/ui` in your project.
|
||||
This example demonstrates how to create a custom code field block with a language picker using the `BlocksFeature`. Make sure to manually install `@payloadcms/ui`first.
|
||||
|
||||
Field Config:
|
||||
Field config:
|
||||
|
||||
```ts
|
||||
import {
|
||||
@@ -91,6 +91,7 @@ CodeComponent.tsx:
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
import type { CodeFieldClient, CodeFieldClientProps } from 'payload'
|
||||
|
||||
import { CodeField, useFormFields } from '@payloadcms/ui'
|
||||
@@ -104,8 +105,6 @@ const languageKeyToMonacoLanguageMap = {
|
||||
tsx: 'typescript',
|
||||
}
|
||||
|
||||
type Language = keyof typeof languageKeyToMonacoLanguageMap
|
||||
|
||||
export const Code: React.FC<CodeFieldClientProps> = ({
|
||||
autoComplete,
|
||||
field,
|
||||
@@ -119,10 +118,10 @@ export const Code: React.FC<CodeFieldClientProps> = ({
|
||||
}) => {
|
||||
const languageField = useFormFields(([fields]) => fields['language'])
|
||||
|
||||
const language: Language =
|
||||
(languageField?.value as Language) || (languageField?.initialValue as Language) || 'ts'
|
||||
const language: string =
|
||||
(languageField?.value as string) || (languageField.initialValue as string) || 'typescript'
|
||||
|
||||
const label = languages[language]
|
||||
const label = languages[language as keyof typeof languages]
|
||||
|
||||
const props: CodeFieldClient = useMemo<CodeFieldClient>(
|
||||
() => ({
|
||||
@@ -130,10 +129,9 @@ export const Code: React.FC<CodeFieldClientProps> = ({
|
||||
type: 'code',
|
||||
admin: {
|
||||
...field.admin,
|
||||
editorOptions: undefined,
|
||||
label,
|
||||
language: languageKeyToMonacoLanguageMap[language] || language,
|
||||
},
|
||||
label,
|
||||
}),
|
||||
[field, language, label],
|
||||
)
|
||||
|
||||
@@ -30,7 +30,6 @@ pnpm add @payloadcms/storage-vercel-blob
|
||||
- Configure the `collections` object to specify which collections should use the Vercel Blob adapter. The slug _must_ match one of your existing collection slugs.
|
||||
- Ensure you have `BLOB_READ_WRITE_TOKEN` set in your Vercel environment variables. This is usually set by Vercel automatically after adding blob storage to your project.
|
||||
- When enabled, this package will automatically set `disableLocalStorage` to `true` for each collection.
|
||||
- When deploying to Vercel, server uploads are limited with 4.5MB. Set `clientUploads` to `true` to do uploads directly on the client.
|
||||
|
||||
```ts
|
||||
import { vercelBlobStorage } from '@payloadcms/storage-vercel-blob'
|
||||
@@ -65,7 +64,6 @@ export default buildConfig({
|
||||
| `addRandomSuffix` | Add a random suffix to the uploaded file name in Vercel Blob storage | `false` |
|
||||
| `cacheControlMaxAge` | Cache-Control max-age in seconds | `365 * 24 * 60 * 60` (1 Year) |
|
||||
| `token` | Vercel Blob storage read/write token | `''` |
|
||||
| `clientUploads` | Do uploads directly on the client to bypass limits on Vercel. | |
|
||||
|
||||
## S3 Storage
|
||||
[`@payloadcms/storage-s3`](https://www.npmjs.com/package/@payloadcms/storage-s3)
|
||||
@@ -81,7 +79,6 @@ pnpm add @payloadcms/storage-s3
|
||||
- Configure the `collections` object to specify which collections should use the S3 Storage adapter. The slug _must_ match one of your existing collection slugs.
|
||||
- The `config` object can be any [`S3ClientConfig`](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/s3) object (from [`@aws-sdk/client-s3`](https://github.com/aws/aws-sdk-js-v3)). _This is highly dependent on your AWS setup_. Check the AWS documentation for more information.
|
||||
- When enabled, this package will automatically set `disableLocalStorage` to `true` for each collection.
|
||||
- When deploying to Vercel, server uploads are limited with 4.5MB. Set `clientUploads` to `true` to do uploads directly on the client. You must allow CORS PUT method for the bucket to your website.
|
||||
|
||||
```ts
|
||||
import { s3Storage } from '@payloadcms/storage-s3'
|
||||
@@ -129,7 +126,6 @@ pnpm add @payloadcms/storage-azure
|
||||
|
||||
- Configure the `collections` object to specify which collections should use the Azure Blob adapter. The slug _must_ match one of your existing collection slugs.
|
||||
- When enabled, this package will automatically set `disableLocalStorage` to `true` for each collection.
|
||||
- When deploying to Vercel, server uploads are limited with 4.5MB. Set `clientUploads` to `true` to do uploads directly on the client. You must allow CORS PUT method to your website.
|
||||
|
||||
```ts
|
||||
import { azureStorage } from '@payloadcms/storage-azure'
|
||||
@@ -165,7 +161,6 @@ export default buildConfig({
|
||||
| `baseURL` | Base URL for the Azure Blob storage account | |
|
||||
| `connectionString` | Azure Blob storage connection string | |
|
||||
| `containerName` | Azure Blob storage container name | |
|
||||
| `clientUploads` | Do uploads directly on the client to bypass limits on Vercel. | |
|
||||
|
||||
## Google Cloud Storage
|
||||
[`@payloadcms/storage-gcs`](https://www.npmjs.com/package/@payloadcms/storage-gcs)
|
||||
@@ -180,7 +175,6 @@ pnpm add @payloadcms/storage-gcs
|
||||
|
||||
- Configure the `collections` object to specify which collections should use the Google Cloud Storage adapter. The slug _must_ match one of your existing collection slugs.
|
||||
- When enabled, this package will automatically set `disableLocalStorage` to `true` for each collection.
|
||||
- When deploying to Vercel, server uploads are limited with 4.5MB. Set `clientUploads` to `true` to do uploads directly on the client. You must allow CORS PUT method for the bucket to your website.
|
||||
|
||||
```ts
|
||||
import { gcsStorage } from '@payloadcms/storage-gcs'
|
||||
@@ -209,14 +203,13 @@ export default buildConfig({
|
||||
|
||||
### Configuration Options#gcs-configuration
|
||||
|
||||
| Option | Description | Default |
|
||||
| --------------- | --------------------------------------------------------------------------------------------------- | --------- |
|
||||
| `enabled` | Whether or not to enable the plugin | `true` |
|
||||
| `collections` | Collections to apply the storage to | |
|
||||
| `bucket` | The name of the bucket to use | |
|
||||
| `options` | Google Cloud Storage client configuration. See [Docs](https://github.com/googleapis/nodejs-storage) | |
|
||||
| `acl` | Access control list for files that are uploaded | `Private` |
|
||||
| `clientUploads` | Do uploads directly on the client to bypass limits on Vercel. | |
|
||||
| Option | Description | Default |
|
||||
| ------------- | --------------------------------------------------------------------------------------------------- | --------- |
|
||||
| `enabled` | Whether or not to enable the plugin | `true` |
|
||||
| `collections` | Collections to apply the storage to | |
|
||||
| `bucket` | The name of the bucket to use | |
|
||||
| `options` | Google Cloud Storage client configuration. See [Docs](https://github.com/googleapis/nodejs-storage) | |
|
||||
| `acl` | Access control list for files that are uploaded | `Private` |
|
||||
|
||||
|
||||
## Uploadthing Storage
|
||||
@@ -233,7 +226,6 @@ pnpm add @payloadcms/storage-uploadthing
|
||||
- Configure the `collections` object to specify which collections should use uploadthing. The slug _must_ match one of your existing collection slugs and be an `upload` type.
|
||||
- Get a token from Uploadthing and set it as `token` in the `options` object.
|
||||
- `acl` is optional and defaults to `public-read`.
|
||||
- When deploying to Vercel, server uploads are limited with 4.5MB. Set `clientUploads` to `true` to do uploads directly on the client.
|
||||
|
||||
```ts
|
||||
export default buildConfig({
|
||||
@@ -254,14 +246,13 @@ export default buildConfig({
|
||||
|
||||
### Configuration Options#uploadthing-configuration
|
||||
|
||||
| Option | Description | Default |
|
||||
| ---------------- | ------------------------------------------------------------- | ------------- |
|
||||
| `token` | Token from Uploadthing. Required. | |
|
||||
| `acl` | Access control list for files that are uploaded | `public-read` |
|
||||
| `logLevel` | Log level for Uploadthing | `info` |
|
||||
| `fetch` | Custom fetch function | `fetch` |
|
||||
| `defaultKeyType` | Default key type for file operations | `fileKey` |
|
||||
| `clientUploads` | Do uploads directly on the client to bypass limits on Vercel. | |
|
||||
| Option | Description | Default |
|
||||
| ---------------- | ----------------------------------------------- | ------------- |
|
||||
| `token` | Token from Uploadthing. Required. | |
|
||||
| `acl` | Access control list for files that are uploaded | `public-read` |
|
||||
| `logLevel` | Log level for Uploadthing | `info` |
|
||||
| `fetch` | Custom fetch function | `fetch` |
|
||||
| `defaultKeyType` | Default key type for file operations | `fileKey` |
|
||||
|
||||
|
||||
## Custom Storage Adapters
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { CollectionAfterLoginHook } from 'payload'
|
||||
|
||||
import { mergeHeaders, generateCookie, getCookieExpiration } from 'payload'
|
||||
import { mergeHeaders } from '@payloadcms/next/utilities'
|
||||
import { generateCookie, getCookieExpiration } from 'payload'
|
||||
|
||||
export const setCookieBasedOnDomain: CollectionAfterLoginHook = async ({ req, user }) => {
|
||||
const relatedOrg = await req.payload.find({
|
||||
|
||||
14
package.json
14
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload-monorepo",
|
||||
"version": "3.25.0",
|
||||
"version": "3.24.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -117,7 +117,7 @@
|
||||
"devDependencies": {
|
||||
"@jest/globals": "29.7.0",
|
||||
"@libsql/client": "0.14.0",
|
||||
"@next/bundle-analyzer": "15.2.0",
|
||||
"@next/bundle-analyzer": "15.1.5",
|
||||
"@payloadcms/db-postgres": "workspace:*",
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@payloadcms/eslint-plugin": "workspace:*",
|
||||
@@ -132,8 +132,8 @@
|
||||
"@types/jest": "29.5.12",
|
||||
"@types/minimist": "1.2.5",
|
||||
"@types/node": "22.5.4",
|
||||
"@types/react": "19.0.10",
|
||||
"@types/react-dom": "19.0.4",
|
||||
"@types/react": "19.0.1",
|
||||
"@types/react-dom": "19.0.1",
|
||||
"@types/shelljs": "0.8.15",
|
||||
"chalk": "^4.1.2",
|
||||
"comment-json": "^4.2.3",
|
||||
@@ -153,7 +153,7 @@
|
||||
"lint-staged": "15.2.7",
|
||||
"minimist": "1.2.8",
|
||||
"mongodb-memory-server": "^10",
|
||||
"next": "15.2.0",
|
||||
"next": "15.1.5",
|
||||
"open": "^10.1.0",
|
||||
"p-limit": "^5.0.0",
|
||||
"playwright": "1.50.0",
|
||||
@@ -173,6 +173,10 @@
|
||||
"turbo": "^2.3.3",
|
||||
"typescript": "5.7.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.0.0 || ^19.0.0-rc-65a56d0e-20241020",
|
||||
"react-dom": "^19.0.0 || ^19.0.0-rc-65a56d0e-20241020"
|
||||
},
|
||||
"packageManager": "pnpm@9.7.1",
|
||||
"engines": {
|
||||
"node": "^18.20.2 || >=20.9.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-payload-app",
|
||||
"version": "3.25.0",
|
||||
"version": "3.24.0",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "3.25.0",
|
||||
"version": "3.24.0",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { CreateOptions } from 'mongoose'
|
||||
import type { Create } from 'payload'
|
||||
import type { Create, Document } from 'payload'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
|
||||
@@ -9,7 +9,7 @@ import { transform } from './utilities/transform.js'
|
||||
|
||||
export const create: Create = async function create(
|
||||
this: MongooseAdapter,
|
||||
{ collection, data, req, returning },
|
||||
{ collection, data, req },
|
||||
) {
|
||||
const Model = this.collections[collection]
|
||||
const options: CreateOptions = {
|
||||
@@ -34,9 +34,6 @@ export const create: Create = async function create(
|
||||
} catch (error) {
|
||||
handleError({ collection, error, req })
|
||||
}
|
||||
if (returning === false) {
|
||||
return null
|
||||
}
|
||||
|
||||
doc = doc.toObject()
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { transform } from './utilities/transform.js'
|
||||
|
||||
export const createGlobal: CreateGlobal = async function createGlobal(
|
||||
this: MongooseAdapter,
|
||||
{ slug, data, req, returning },
|
||||
{ slug, data, req },
|
||||
) {
|
||||
const Model = this.globals
|
||||
|
||||
@@ -25,9 +25,6 @@ export const createGlobal: CreateGlobal = async function createGlobal(
|
||||
}
|
||||
|
||||
let [result] = (await Model.create([data], options)) as any
|
||||
if (returning === false) {
|
||||
return null
|
||||
}
|
||||
|
||||
result = result.toObject()
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ export const createGlobalVersion: CreateGlobalVersion = async function createGlo
|
||||
parent,
|
||||
publishedLocale,
|
||||
req,
|
||||
returning,
|
||||
snapshot,
|
||||
updatedAt,
|
||||
versionData,
|
||||
@@ -76,10 +75,6 @@ export const createGlobalVersion: CreateGlobalVersion = async function createGlo
|
||||
options,
|
||||
)
|
||||
|
||||
if (returning === false) {
|
||||
return null
|
||||
}
|
||||
|
||||
doc = doc.toObject()
|
||||
|
||||
transform({
|
||||
|
||||
@@ -16,7 +16,6 @@ export const createVersion: CreateVersion = async function createVersion(
|
||||
parent,
|
||||
publishedLocale,
|
||||
req,
|
||||
returning,
|
||||
snapshot,
|
||||
updatedAt,
|
||||
versionData,
|
||||
@@ -87,10 +86,6 @@ export const createVersion: CreateVersion = async function createVersion(
|
||||
options,
|
||||
)
|
||||
|
||||
if (returning === false) {
|
||||
return null
|
||||
}
|
||||
|
||||
doc = doc.toObject()
|
||||
|
||||
transform({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { MongooseUpdateQueryOptions } from 'mongoose'
|
||||
import type { QueryOptions } from 'mongoose'
|
||||
import type { DeleteOne } from 'payload'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
@@ -10,10 +10,10 @@ import { transform } from './utilities/transform.js'
|
||||
|
||||
export const deleteOne: DeleteOne = async function deleteOne(
|
||||
this: MongooseAdapter,
|
||||
{ collection, req, returning, select, where },
|
||||
{ collection, req, select, where },
|
||||
) {
|
||||
const Model = this.collections[collection]
|
||||
const options: MongooseUpdateQueryOptions = {
|
||||
const options: QueryOptions = {
|
||||
projection: buildProjectionFromSelect({
|
||||
adapter: this,
|
||||
fields: this.payload.collections[collection].config.flattenedFields,
|
||||
@@ -29,11 +29,6 @@ export const deleteOne: DeleteOne = async function deleteOne(
|
||||
where,
|
||||
})
|
||||
|
||||
if (returning === false) {
|
||||
await Model.deleteOne(query, options)?.lean()
|
||||
return null
|
||||
}
|
||||
|
||||
const doc = await Model.findOneAndDelete(query, options)?.lean()
|
||||
|
||||
if (!doc) {
|
||||
|
||||
@@ -16,7 +16,6 @@ import type {
|
||||
TypeWithVersion,
|
||||
UpdateGlobalArgs,
|
||||
UpdateGlobalVersionArgs,
|
||||
UpdateManyArgs,
|
||||
UpdateOneArgs,
|
||||
UpdateVersionArgs,
|
||||
} from 'payload'
|
||||
@@ -54,7 +53,6 @@ import { commitTransaction } from './transactions/commitTransaction.js'
|
||||
import { rollbackTransaction } from './transactions/rollbackTransaction.js'
|
||||
import { updateGlobal } from './updateGlobal.js'
|
||||
import { updateGlobalVersion } from './updateGlobalVersion.js'
|
||||
import { updateMany } from './updateMany.js'
|
||||
import { updateOne } from './updateOne.js'
|
||||
import { updateVersion } from './updateVersion.js'
|
||||
import { upsert } from './upsert.js'
|
||||
@@ -162,7 +160,6 @@ declare module 'payload' {
|
||||
updateGlobalVersion: <T extends TypeWithID = TypeWithID>(
|
||||
args: { options?: QueryOptions } & UpdateGlobalVersionArgs<T>,
|
||||
) => Promise<TypeWithVersion<T>>
|
||||
|
||||
updateOne: (args: { options?: QueryOptions } & UpdateOneArgs) => Promise<Document>
|
||||
updateVersion: <T extends TypeWithID = TypeWithID>(
|
||||
args: { options?: QueryOptions } & UpdateVersionArgs<T>,
|
||||
@@ -203,7 +200,6 @@ export function mongooseAdapter({
|
||||
mongoMemoryServer,
|
||||
sessions: {},
|
||||
transactionOptions: transactionOptions === false ? undefined : transactionOptions,
|
||||
updateMany,
|
||||
url,
|
||||
versions: {},
|
||||
// DatabaseAdapter
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { MongooseUpdateQueryOptions } from 'mongoose'
|
||||
import type { QueryOptions } from 'mongoose'
|
||||
import type { UpdateGlobal } from 'payload'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
@@ -9,12 +9,12 @@ import { transform } from './utilities/transform.js'
|
||||
|
||||
export const updateGlobal: UpdateGlobal = async function updateGlobal(
|
||||
this: MongooseAdapter,
|
||||
{ slug, data, options: optionsArgs = {}, req, returning, select },
|
||||
{ slug, data, options: optionsArgs = {}, req, select },
|
||||
) {
|
||||
const Model = this.globals
|
||||
const fields = this.payload.config.globals.find((global) => global.slug === slug).fields
|
||||
|
||||
const options: MongooseUpdateQueryOptions = {
|
||||
const options: QueryOptions = {
|
||||
...optionsArgs,
|
||||
lean: true,
|
||||
new: true,
|
||||
@@ -28,11 +28,6 @@ export const updateGlobal: UpdateGlobal = async function updateGlobal(
|
||||
|
||||
transform({ adapter: this, data, fields, globalSlug: slug, operation: 'write' })
|
||||
|
||||
if (returning === false) {
|
||||
await Model.updateOne({ globalType: slug }, data, options)
|
||||
return null
|
||||
}
|
||||
|
||||
const result: any = await Model.findOneAndUpdate({ globalType: slug }, data, options)
|
||||
|
||||
transform({ adapter: this, data: result, fields, globalSlug: slug, operation: 'read' })
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { MongooseUpdateQueryOptions } from 'mongoose'
|
||||
import type { QueryOptions } from 'mongoose'
|
||||
|
||||
import { buildVersionGlobalFields, type TypeWithID, type UpdateGlobalVersionArgs } from 'payload'
|
||||
|
||||
@@ -17,7 +17,6 @@ export async function updateGlobalVersion<T extends TypeWithID>(
|
||||
locale,
|
||||
options: optionsArgs = {},
|
||||
req,
|
||||
returning,
|
||||
select,
|
||||
versionData,
|
||||
where,
|
||||
@@ -29,7 +28,7 @@ export async function updateGlobalVersion<T extends TypeWithID>(
|
||||
const currentGlobal = this.payload.config.globals.find((global) => global.slug === globalSlug)
|
||||
const fields = buildVersionGlobalFields(this.payload.config, currentGlobal)
|
||||
const flattenedFields = buildVersionGlobalFields(this.payload.config, currentGlobal, true)
|
||||
const options: MongooseUpdateQueryOptions = {
|
||||
const options: QueryOptions = {
|
||||
...optionsArgs,
|
||||
lean: true,
|
||||
new: true,
|
||||
@@ -50,11 +49,6 @@ export async function updateGlobalVersion<T extends TypeWithID>(
|
||||
|
||||
transform({ adapter: this, data: versionData, fields, operation: 'write' })
|
||||
|
||||
if (returning === false) {
|
||||
await VersionModel.updateOne(query, versionData, options)
|
||||
return null
|
||||
}
|
||||
|
||||
const doc = await VersionModel.findOneAndUpdate(query, versionData, options)
|
||||
|
||||
if (!doc) {
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
import type { MongooseUpdateQueryOptions } from 'mongoose'
|
||||
import type { UpdateMany } from 'payload'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
|
||||
import { buildQuery } from './queries/buildQuery.js'
|
||||
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
||||
import { getSession } from './utilities/getSession.js'
|
||||
import { handleError } from './utilities/handleError.js'
|
||||
import { transform } from './utilities/transform.js'
|
||||
|
||||
export const updateMany: UpdateMany = async function updateMany(
|
||||
this: MongooseAdapter,
|
||||
{ collection, data, locale, options: optionsArgs = {}, req, returning, select, where },
|
||||
) {
|
||||
const Model = this.collections[collection]
|
||||
const fields = this.payload.collections[collection].config.fields
|
||||
|
||||
const options: MongooseUpdateQueryOptions = {
|
||||
...optionsArgs,
|
||||
lean: true,
|
||||
new: true,
|
||||
projection: buildProjectionFromSelect({
|
||||
adapter: this,
|
||||
fields: this.payload.collections[collection].config.flattenedFields,
|
||||
select,
|
||||
}),
|
||||
session: await getSession(this, req),
|
||||
}
|
||||
|
||||
const query = await buildQuery({
|
||||
adapter: this,
|
||||
collectionSlug: collection,
|
||||
fields: this.payload.collections[collection].config.flattenedFields,
|
||||
locale,
|
||||
where,
|
||||
})
|
||||
|
||||
transform({ adapter: this, data, fields, operation: 'write' })
|
||||
|
||||
try {
|
||||
await Model.updateMany(query, data, options)
|
||||
} catch (error) {
|
||||
handleError({ collection, error, req })
|
||||
}
|
||||
|
||||
if (returning === false) {
|
||||
return null
|
||||
}
|
||||
|
||||
const result = await Model.find(query, {}, options)
|
||||
|
||||
transform({
|
||||
adapter: this,
|
||||
data: result,
|
||||
fields,
|
||||
operation: 'read',
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { MongooseUpdateQueryOptions } from 'mongoose'
|
||||
import type { QueryOptions } from 'mongoose'
|
||||
import type { UpdateOne } from 'payload'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
@@ -11,22 +11,12 @@ import { transform } from './utilities/transform.js'
|
||||
|
||||
export const updateOne: UpdateOne = async function updateOne(
|
||||
this: MongooseAdapter,
|
||||
{
|
||||
id,
|
||||
collection,
|
||||
data,
|
||||
locale,
|
||||
options: optionsArgs = {},
|
||||
req,
|
||||
returning,
|
||||
select,
|
||||
where: whereArg,
|
||||
},
|
||||
{ id, collection, data, locale, options: optionsArgs = {}, req, select, where: whereArg },
|
||||
) {
|
||||
const where = id ? { id: { equals: id } } : whereArg
|
||||
const Model = this.collections[collection]
|
||||
const fields = this.payload.collections[collection].config.fields
|
||||
const options: MongooseUpdateQueryOptions = {
|
||||
const options: QueryOptions = {
|
||||
...optionsArgs,
|
||||
lean: true,
|
||||
new: true,
|
||||
@@ -51,12 +41,7 @@ export const updateOne: UpdateOne = async function updateOne(
|
||||
transform({ adapter: this, data, fields, operation: 'write' })
|
||||
|
||||
try {
|
||||
if (returning === false) {
|
||||
await Model.updateOne(query, data, options)
|
||||
return null
|
||||
} else {
|
||||
result = await Model.findOneAndUpdate(query, data, options)
|
||||
}
|
||||
result = await Model.findOneAndUpdate(query, data, options)
|
||||
} catch (error) {
|
||||
handleError({ collection, error, req })
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { MongooseUpdateQueryOptions } from 'mongoose'
|
||||
import type { QueryOptions } from 'mongoose'
|
||||
|
||||
import { buildVersionCollectionFields, type UpdateVersion } from 'payload'
|
||||
|
||||
@@ -11,7 +11,7 @@ import { transform } from './utilities/transform.js'
|
||||
|
||||
export const updateVersion: UpdateVersion = async function updateVersion(
|
||||
this: MongooseAdapter,
|
||||
{ id, collection, locale, options: optionsArgs = {}, req, returning, select, versionData, where },
|
||||
{ id, collection, locale, options: optionsArgs = {}, req, select, versionData, where },
|
||||
) {
|
||||
const VersionModel = this.versions[collection]
|
||||
const whereToUse = where || { id: { equals: id } }
|
||||
@@ -26,7 +26,7 @@ export const updateVersion: UpdateVersion = async function updateVersion(
|
||||
true,
|
||||
)
|
||||
|
||||
const options: MongooseUpdateQueryOptions = {
|
||||
const options: QueryOptions = {
|
||||
...optionsArgs,
|
||||
lean: true,
|
||||
new: true,
|
||||
@@ -47,11 +47,6 @@ export const updateVersion: UpdateVersion = async function updateVersion(
|
||||
|
||||
transform({ adapter: this, data: versionData, fields, operation: 'write' })
|
||||
|
||||
if (returning === false) {
|
||||
await VersionModel.updateOne(query, versionData, options)
|
||||
return null
|
||||
}
|
||||
|
||||
const doc = await VersionModel.findOneAndUpdate(query, versionData, options)
|
||||
|
||||
if (!doc) {
|
||||
|
||||
@@ -4,16 +4,7 @@ import type { MongooseAdapter } from './index.js'
|
||||
|
||||
export const upsert: Upsert = async function upsert(
|
||||
this: MongooseAdapter,
|
||||
{ collection, data, locale, req, returning, select, where },
|
||||
{ collection, data, locale, req, select, where },
|
||||
) {
|
||||
return this.updateOne({
|
||||
collection,
|
||||
data,
|
||||
locale,
|
||||
options: { upsert: true },
|
||||
req,
|
||||
returning,
|
||||
select,
|
||||
where,
|
||||
})
|
||||
return this.updateOne({ collection, data, locale, options: { upsert: true }, req, select, where })
|
||||
}
|
||||
|
||||
@@ -78,7 +78,6 @@ export const buildJoinAggregation = async ({
|
||||
}
|
||||
|
||||
const {
|
||||
count = false,
|
||||
limit: limitJoin = join.field.defaultLimit ?? 10,
|
||||
page,
|
||||
sort: sortJoin = join.field.defaultSort || collectionConfig.defaultSort,
|
||||
@@ -122,28 +121,6 @@ export const buildJoinAggregation = async ({
|
||||
const alias = `${as}.docs.${collectionSlug}`
|
||||
aliases.push(alias)
|
||||
|
||||
const basePipeline = [
|
||||
{
|
||||
$addFields: {
|
||||
relationTo: {
|
||||
$literal: collectionSlug,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$match: {
|
||||
$and: [
|
||||
{
|
||||
$expr: {
|
||||
$eq: [`$${join.field.on}`, '$$root_id_'],
|
||||
},
|
||||
},
|
||||
$match,
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
aggregate.push({
|
||||
$lookup: {
|
||||
as: alias,
|
||||
@@ -152,7 +129,25 @@ export const buildJoinAggregation = async ({
|
||||
root_id_: '$_id',
|
||||
},
|
||||
pipeline: [
|
||||
...basePipeline,
|
||||
{
|
||||
$addFields: {
|
||||
relationTo: {
|
||||
$literal: collectionSlug,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$match: {
|
||||
$and: [
|
||||
{
|
||||
$expr: {
|
||||
$eq: [`$${join.field.on}`, '$$root_id_'],
|
||||
},
|
||||
},
|
||||
$match,
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
$sort: {
|
||||
[sortProperty]: sortDirection,
|
||||
@@ -174,24 +169,6 @@ export const buildJoinAggregation = async ({
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
if (count) {
|
||||
aggregate.push({
|
||||
$lookup: {
|
||||
as: `${as}.totalDocs.${alias}`,
|
||||
from: adapter.collections[collectionSlug].collection.name,
|
||||
let: {
|
||||
root_id_: '$_id',
|
||||
},
|
||||
pipeline: [
|
||||
...basePipeline,
|
||||
{
|
||||
$count: 'result',
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
aggregate.push({
|
||||
@@ -202,23 +179,6 @@ export const buildJoinAggregation = async ({
|
||||
},
|
||||
})
|
||||
|
||||
if (count) {
|
||||
aggregate.push({
|
||||
$addFields: {
|
||||
[`${as}.totalDocs`]: {
|
||||
$add: aliases.map((alias) => ({
|
||||
$ifNull: [
|
||||
{
|
||||
$first: `$${as}.totalDocs.${alias}.result`,
|
||||
},
|
||||
0,
|
||||
],
|
||||
})),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
aggregate.push({
|
||||
$set: {
|
||||
[`${as}.docs`]: {
|
||||
@@ -235,17 +195,17 @@ export const buildJoinAggregation = async ({
|
||||
const sliceValue = page ? [(page - 1) * limitJoin, limitJoin] : [limitJoin]
|
||||
|
||||
aggregate.push({
|
||||
$addFields: {
|
||||
[`${as}.hasNextPage`]: {
|
||||
$gt: [{ $size: `$${as}.docs` }, limitJoin || Number.MAX_VALUE],
|
||||
$set: {
|
||||
[`${as}.docs`]: {
|
||||
$slice: [`$${as}.docs`, ...sliceValue],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
aggregate.push({
|
||||
$set: {
|
||||
[`${as}.docs`]: {
|
||||
$slice: [`$${as}.docs`, ...sliceValue],
|
||||
$addFields: {
|
||||
[`${as}.hasNextPage`]: {
|
||||
$gt: [{ $size: `$${as}.docs` }, limitJoin || Number.MAX_VALUE],
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -262,7 +222,6 @@ export const buildJoinAggregation = async ({
|
||||
}
|
||||
|
||||
const {
|
||||
count,
|
||||
limit: limitJoin = join.field.defaultLimit ?? 10,
|
||||
page,
|
||||
sort: sortJoin = join.field.defaultSort || collectionConfig.defaultSort,
|
||||
@@ -315,31 +274,6 @@ export const buildJoinAggregation = async ({
|
||||
polymorphicSuffix = '.value'
|
||||
}
|
||||
|
||||
const addTotalDocsAggregation = (as: string, foreignField: string) =>
|
||||
aggregate.push(
|
||||
{
|
||||
$lookup: {
|
||||
as: `${as}.totalDocs`,
|
||||
foreignField,
|
||||
from: adapter.collections[slug].collection.name,
|
||||
localField: versions ? 'parent' : '_id',
|
||||
pipeline: [
|
||||
{
|
||||
$match,
|
||||
},
|
||||
{
|
||||
$count: 'result',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
$addFields: {
|
||||
[`${as}.totalDocs`]: { $ifNull: [{ $first: `$${as}.totalDocs.result` }, 0] },
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if (adapter.payload.config.localization && locale === 'all') {
|
||||
adapter.payload.config.localization.localeCodes.forEach((code) => {
|
||||
const as = `${versions ? `version.${join.joinPath}` : join.joinPath}${code}`
|
||||
@@ -370,7 +304,6 @@ export const buildJoinAggregation = async ({
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if (limitJoin > 0) {
|
||||
aggregate.push({
|
||||
$addFields: {
|
||||
@@ -380,10 +313,6 @@ export const buildJoinAggregation = async ({
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if (count) {
|
||||
addTotalDocsAggregation(as, `${join.field.on}${code}${polymorphicSuffix}`)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
const localeSuffix =
|
||||
@@ -430,11 +359,6 @@ export const buildJoinAggregation = async ({
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if (count) {
|
||||
addTotalDocsAggregation(as, foreignField)
|
||||
}
|
||||
|
||||
if (limitJoin > 0) {
|
||||
aggregate.push({
|
||||
$addFields: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-postgres",
|
||||
"version": "3.25.0",
|
||||
"version": "3.24.0",
|
||||
"description": "The officially supported Postgres database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -33,7 +33,6 @@ import {
|
||||
rollbackTransaction,
|
||||
updateGlobal,
|
||||
updateGlobalVersion,
|
||||
updateMany,
|
||||
updateOne,
|
||||
updateVersion,
|
||||
} from '@payloadcms/drizzle'
|
||||
@@ -186,7 +185,6 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
|
||||
rollbackTransaction,
|
||||
updateGlobal,
|
||||
updateGlobalVersion,
|
||||
updateMany,
|
||||
updateOne,
|
||||
updateVersion,
|
||||
upsert: updateOne,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-sqlite",
|
||||
"version": "3.25.0",
|
||||
"version": "3.24.0",
|
||||
"description": "The officially supported SQLite database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -34,7 +34,6 @@ import {
|
||||
rollbackTransaction,
|
||||
updateGlobal,
|
||||
updateGlobalVersion,
|
||||
updateMany,
|
||||
updateOne,
|
||||
updateVersion,
|
||||
} from '@payloadcms/drizzle'
|
||||
@@ -121,7 +120,6 @@ export function sqliteAdapter(args: Args): DatabaseAdapterObj<SQLiteAdapter> {
|
||||
tableNameMap: new Map<string, string>(),
|
||||
tables: {},
|
||||
transactionOptions: args.transactionOptions || undefined,
|
||||
updateMany,
|
||||
versionsSuffix: args.versionsSuffix || '_v',
|
||||
|
||||
// DatabaseAdapter
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-vercel-postgres",
|
||||
"version": "3.25.0",
|
||||
"version": "3.24.0",
|
||||
"description": "Vercel Postgres adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -33,7 +33,6 @@ import {
|
||||
rollbackTransaction,
|
||||
updateGlobal,
|
||||
updateGlobalVersion,
|
||||
updateMany,
|
||||
updateOne,
|
||||
updateVersion,
|
||||
} from '@payloadcms/drizzle'
|
||||
@@ -187,7 +186,6 @@ export function vercelPostgresAdapter(args: Args = {}): DatabaseAdapterObj<Verce
|
||||
rollbackTransaction,
|
||||
updateGlobal,
|
||||
updateGlobalVersion,
|
||||
updateMany,
|
||||
updateOne,
|
||||
updateVersion,
|
||||
upsert: updateOne,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/drizzle",
|
||||
"version": "3.25.0",
|
||||
"version": "3.24.0",
|
||||
"description": "A library of shared functions used by different payload database adapters",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -9,7 +9,7 @@ import { getTransaction } from './utilities/getTransaction.js'
|
||||
|
||||
export const create: Create = async function create(
|
||||
this: DrizzleAdapter,
|
||||
{ collection: collectionSlug, data, req, select, returning },
|
||||
{ collection: collectionSlug, data, req, select },
|
||||
) {
|
||||
const db = await getTransaction(this, req)
|
||||
const collection = this.payload.collections[collectionSlug].config
|
||||
@@ -25,12 +25,7 @@ export const create: Create = async function create(
|
||||
req,
|
||||
select,
|
||||
tableName,
|
||||
ignoreResult: returning === false,
|
||||
})
|
||||
|
||||
if (returning === false) {
|
||||
return null
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { getTransaction } from './utilities/getTransaction.js'
|
||||
|
||||
export async function createGlobal<T extends Record<string, unknown>>(
|
||||
this: DrizzleAdapter,
|
||||
{ slug, data, req, returning }: CreateGlobalArgs,
|
||||
{ slug, data, req }: CreateGlobalArgs,
|
||||
): Promise<T> {
|
||||
const db = await getTransaction(this, req)
|
||||
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug)
|
||||
@@ -26,13 +26,8 @@ export async function createGlobal<T extends Record<string, unknown>>(
|
||||
operation: 'create',
|
||||
req,
|
||||
tableName,
|
||||
ignoreResult: returning === false,
|
||||
})
|
||||
|
||||
if (returning === false) {
|
||||
return null
|
||||
}
|
||||
|
||||
result.globalType = slug
|
||||
|
||||
return result
|
||||
|
||||
@@ -21,7 +21,6 @@ export async function createGlobalVersion<T extends TypeWithID>(
|
||||
snapshot,
|
||||
updatedAt,
|
||||
versionData,
|
||||
returning,
|
||||
}: CreateGlobalVersionArgs,
|
||||
) {
|
||||
const db = await getTransaction(this, req)
|
||||
@@ -46,7 +45,6 @@ export async function createGlobalVersion<T extends TypeWithID>(
|
||||
req,
|
||||
select,
|
||||
tableName,
|
||||
ignoreResult: returning === false ? 'idOnly' : false,
|
||||
})
|
||||
|
||||
const table = this.tables[tableName]
|
||||
@@ -61,9 +59,5 @@ export async function createGlobalVersion<T extends TypeWithID>(
|
||||
})
|
||||
}
|
||||
|
||||
if (returning === false) {
|
||||
return null
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ export async function createVersion<T extends TypeWithID>(
|
||||
snapshot,
|
||||
updatedAt,
|
||||
versionData,
|
||||
returning,
|
||||
}: CreateVersionArgs<T>,
|
||||
) {
|
||||
const db = await getTransaction(this, req)
|
||||
@@ -73,9 +72,5 @@ export async function createVersion<T extends TypeWithID>(
|
||||
})
|
||||
}
|
||||
|
||||
if (returning === false) {
|
||||
return null
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import { getTransaction } from './utilities/getTransaction.js'
|
||||
|
||||
export const deleteOne: DeleteOne = async function deleteOne(
|
||||
this: DrizzleAdapter,
|
||||
{ collection: collectionSlug, req, select, where: whereArg, returning },
|
||||
{ collection: collectionSlug, req, select, where: whereArg },
|
||||
) {
|
||||
const db = await getTransaction(this, req)
|
||||
const collection = this.payload.collections[collectionSlug].config
|
||||
@@ -59,16 +59,13 @@ export const deleteOne: DeleteOne = async function deleteOne(
|
||||
docToDelete = await db.query[tableName].findFirst(findManyArgs)
|
||||
}
|
||||
|
||||
const result =
|
||||
returning === false
|
||||
? null
|
||||
: transform({
|
||||
adapter: this,
|
||||
config: this.payload.config,
|
||||
data: docToDelete,
|
||||
fields: collection.flattenedFields,
|
||||
joinQuery: false,
|
||||
})
|
||||
const result = transform({
|
||||
adapter: this,
|
||||
config: this.payload.config,
|
||||
data: docToDelete,
|
||||
fields: collection.flattenedFields,
|
||||
joinQuery: false,
|
||||
})
|
||||
|
||||
await this.deleteWhere({
|
||||
db,
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { LibSQLDatabase } from 'drizzle-orm/libsql'
|
||||
import type { SQLiteSelectBase } from 'drizzle-orm/sqlite-core'
|
||||
import type { FlattenedField, JoinQuery, SelectMode, SelectType, Where } from 'payload'
|
||||
|
||||
import { and, asc, count, desc, eq, or, sql } from 'drizzle-orm'
|
||||
import { and, asc, desc, eq, or, sql } from 'drizzle-orm'
|
||||
import { fieldIsVirtual, fieldShouldBeLocalized } from 'payload/shared'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
@@ -386,7 +386,6 @@ export const traverseFields = ({
|
||||
}
|
||||
|
||||
const {
|
||||
count: shouldCount = false,
|
||||
limit: limitArg = field.defaultLimit ?? 10,
|
||||
page,
|
||||
sort = field.defaultSort,
|
||||
@@ -481,13 +480,6 @@ export const traverseFields = ({
|
||||
sqlWhere = and(sqlWhere, buildSQLWhere(where, subQueryAlias))
|
||||
}
|
||||
|
||||
if (shouldCount) {
|
||||
currentArgs.extras[`${columnName}_count`] = sql`${db
|
||||
.select({ count: count() })
|
||||
.from(sql`${currentQuery.as(subQueryAlias)}`)
|
||||
.where(sqlWhere)}`.as(`${columnName}_count`)
|
||||
}
|
||||
|
||||
currentQuery = currentQuery.orderBy(sortOrder(sql`"sortPath"`)) as SQLSelect
|
||||
|
||||
if (page && limit !== 0) {
|
||||
@@ -619,20 +611,6 @@ export const traverseFields = ({
|
||||
.orderBy(() => orderBy.map(({ column, order }) => order(column))),
|
||||
}).as(subQueryAlias)
|
||||
|
||||
if (shouldCount) {
|
||||
currentArgs.extras[`${columnName}_count`] = sql`${db
|
||||
.select({
|
||||
count: count(),
|
||||
})
|
||||
.from(
|
||||
sql`${db
|
||||
.select(selectFields as any)
|
||||
.from(newAliasTable)
|
||||
.where(subQueryWhere)
|
||||
.as(`${subQueryAlias}_count_subquery`)}`,
|
||||
)}`.as(`${subQueryAlias}_count`)
|
||||
}
|
||||
|
||||
currentArgs.extras[columnName] = sql`${db
|
||||
.select({
|
||||
result: jsonAggBuildObject(adapter, {
|
||||
|
||||
@@ -31,10 +31,9 @@ export { buildRawSchema } from './schema/buildRawSchema.js'
|
||||
export { beginTransaction } from './transactions/beginTransaction.js'
|
||||
export { commitTransaction } from './transactions/commitTransaction.js'
|
||||
export { rollbackTransaction } from './transactions/rollbackTransaction.js'
|
||||
export { updateOne } from './update.js'
|
||||
export { updateGlobal } from './updateGlobal.js'
|
||||
export { updateGlobalVersion } from './updateGlobalVersion.js'
|
||||
export { updateMany } from './updateMany.js'
|
||||
export { updateOne } from './updateOne.js'
|
||||
export { updateVersion } from './updateVersion.js'
|
||||
export { upsertRow } from './upsertRow/index.js'
|
||||
export { buildCreateMigration } from './utilities/buildCreateMigration.js'
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { FlattenedBlock, FlattenedField, JoinQuery, SanitizedConfig } from 'payload'
|
||||
|
||||
import { fieldIsVirtual, fieldShouldBeLocalized } from 'payload/shared'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { DrizzleAdapter } from '../../types.js'
|
||||
import type { BlocksMap } from '../../utilities/createBlocksMap.js'
|
||||
@@ -399,7 +398,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
}
|
||||
|
||||
if (field.type === 'join') {
|
||||
const { count, limit = field.defaultLimit ?? 10 } =
|
||||
const { limit = field.defaultLimit ?? 10 } =
|
||||
joinQuery?.[`${fieldPrefix.replaceAll('_', '.')}${field.name}`] || {}
|
||||
|
||||
// raw hasMany results from SQLite
|
||||
@@ -408,8 +407,8 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
}
|
||||
|
||||
let fieldResult:
|
||||
| { docs: unknown[]; hasNextPage: boolean; totalDocs?: number }
|
||||
| Record<string, { docs: unknown[]; hasNextPage: boolean; totalDocs?: number }>
|
||||
| { docs: unknown[]; hasNextPage: boolean }
|
||||
| Record<string, { docs: unknown[]; hasNextPage: boolean }>
|
||||
if (Array.isArray(fieldData)) {
|
||||
if (isLocalized && adapter.payload.config.localization) {
|
||||
fieldResult = fieldData.reduce(
|
||||
@@ -450,17 +449,6 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
}
|
||||
}
|
||||
|
||||
if (count) {
|
||||
const countPath = `${fieldName}_count`
|
||||
if (typeof table[countPath] !== 'undefined') {
|
||||
let value = Number(table[countPath])
|
||||
if (Number.isNaN(value)) {
|
||||
value = 0
|
||||
}
|
||||
fieldResult.totalDocs = value
|
||||
}
|
||||
}
|
||||
|
||||
result[field.name] = fieldResult
|
||||
return result
|
||||
}
|
||||
@@ -619,7 +607,6 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
deletions,
|
||||
fieldPrefix: groupFieldPrefix,
|
||||
fields: field.flattenedFields,
|
||||
joinQuery,
|
||||
numbers,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
path: `${sanitizedPath}${field.name}`,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { LibSQLDatabase } from 'drizzle-orm/libsql'
|
||||
import type { UpdateOne } from 'payload'
|
||||
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { DrizzleAdapter } from './types.js'
|
||||
|
||||
import { buildFindManyArgs } from './find/buildFindManyArgs.js'
|
||||
import buildQuery from './queries/buildQuery.js'
|
||||
import { selectDistinct } from './queries/selectDistinct.js'
|
||||
import { upsertRow } from './upsertRow/index.js'
|
||||
@@ -12,17 +12,7 @@ import { getTransaction } from './utilities/getTransaction.js'
|
||||
|
||||
export const updateOne: UpdateOne = async function updateOne(
|
||||
this: DrizzleAdapter,
|
||||
{
|
||||
id,
|
||||
collection: collectionSlug,
|
||||
data,
|
||||
joins: joinQuery,
|
||||
locale,
|
||||
req,
|
||||
select,
|
||||
where: whereArg,
|
||||
returning,
|
||||
},
|
||||
{ id, collection: collectionSlug, data, joins: joinQuery, locale, req, select, where: whereArg },
|
||||
) {
|
||||
const db = await getTransaction(this, req)
|
||||
const collection = this.payload.collections[collectionSlug].config
|
||||
@@ -38,7 +28,6 @@ export const updateOne: UpdateOne = async function updateOne(
|
||||
where: whereToUse,
|
||||
})
|
||||
|
||||
// selectDistinct will only return if there are joins
|
||||
const selectDistinctResult = await selectDistinct({
|
||||
adapter: this,
|
||||
chainedMethods: [{ args: [1], method: 'limit' }],
|
||||
@@ -51,18 +40,22 @@ export const updateOne: UpdateOne = async function updateOne(
|
||||
|
||||
if (selectDistinctResult?.[0]?.id) {
|
||||
idToUpdate = selectDistinctResult?.[0]?.id
|
||||
|
||||
// If id wasn't passed but `where` without any joins, retrieve it with findFirst
|
||||
} else if (whereArg && !joins.length) {
|
||||
const table = this.tables[tableName]
|
||||
const findManyArgs = buildFindManyArgs({
|
||||
adapter: this,
|
||||
depth: 0,
|
||||
fields: collection.flattenedFields,
|
||||
joinQuery: false,
|
||||
select: {},
|
||||
tableName,
|
||||
})
|
||||
|
||||
const docsToUpdate = await (db as LibSQLDatabase)
|
||||
.select({
|
||||
id: table.id,
|
||||
})
|
||||
.from(table)
|
||||
.where(where)
|
||||
.limit(1)
|
||||
idToUpdate = docsToUpdate?.[0]?.id
|
||||
findManyArgs.where = where
|
||||
|
||||
const docToUpdate = await db.query[tableName].findFirst(findManyArgs)
|
||||
idToUpdate = docToUpdate?.id
|
||||
}
|
||||
|
||||
const result = await upsertRow({
|
||||
@@ -76,12 +69,7 @@ export const updateOne: UpdateOne = async function updateOne(
|
||||
req,
|
||||
select,
|
||||
tableName,
|
||||
ignoreResult: returning === false,
|
||||
})
|
||||
|
||||
if (returning === false) {
|
||||
return null
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import { getTransaction } from './utilities/getTransaction.js'
|
||||
|
||||
export async function updateGlobal<T extends Record<string, unknown>>(
|
||||
this: DrizzleAdapter,
|
||||
{ slug, data, req, select, returning }: UpdateGlobalArgs,
|
||||
{ slug, data, req, select }: UpdateGlobalArgs,
|
||||
): Promise<T> {
|
||||
const db = await getTransaction(this, req)
|
||||
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug)
|
||||
@@ -26,13 +26,8 @@ export async function updateGlobal<T extends Record<string, unknown>>(
|
||||
req,
|
||||
select,
|
||||
tableName,
|
||||
ignoreResult: returning === false,
|
||||
})
|
||||
|
||||
if (returning === false) {
|
||||
return null
|
||||
}
|
||||
|
||||
result.globalType = slug
|
||||
|
||||
return result
|
||||
|
||||
@@ -16,16 +16,7 @@ import { getTransaction } from './utilities/getTransaction.js'
|
||||
|
||||
export async function updateGlobalVersion<T extends TypeWithID>(
|
||||
this: DrizzleAdapter,
|
||||
{
|
||||
id,
|
||||
global,
|
||||
locale,
|
||||
req,
|
||||
select,
|
||||
versionData,
|
||||
where: whereArg,
|
||||
returning,
|
||||
}: UpdateGlobalVersionArgs<T>,
|
||||
{ id, global, locale, req, select, versionData, where: whereArg }: UpdateGlobalVersionArgs<T>,
|
||||
) {
|
||||
const db = await getTransaction(this, req)
|
||||
const globalConfig: SanitizedGlobalConfig = this.payload.globals.config.find(
|
||||
@@ -58,12 +49,7 @@ export async function updateGlobalVersion<T extends TypeWithID>(
|
||||
select,
|
||||
tableName,
|
||||
where,
|
||||
ignoreResult: returning === false,
|
||||
})
|
||||
|
||||
if (returning === false) {
|
||||
return null
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
import type { LibSQLDatabase } from 'drizzle-orm/libsql'
|
||||
import type { UpdateMany } from 'payload'
|
||||
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { DrizzleAdapter } from './types.js'
|
||||
|
||||
import buildQuery from './queries/buildQuery.js'
|
||||
import { selectDistinct } from './queries/selectDistinct.js'
|
||||
import { upsertRow } from './upsertRow/index.js'
|
||||
import { getTransaction } from './utilities/getTransaction.js'
|
||||
|
||||
export const updateMany: UpdateMany = async function updateMany(
|
||||
this: DrizzleAdapter,
|
||||
{
|
||||
collection: collectionSlug,
|
||||
data,
|
||||
joins: joinQuery,
|
||||
locale,
|
||||
req,
|
||||
returning,
|
||||
select,
|
||||
where: whereToUse,
|
||||
},
|
||||
) {
|
||||
const db = await getTransaction(this, req)
|
||||
const collection = this.payload.collections[collectionSlug].config
|
||||
const tableName = this.tableNameMap.get(toSnakeCase(collection.slug))
|
||||
|
||||
const { joins, selectFields, where } = buildQuery({
|
||||
adapter: this,
|
||||
fields: collection.flattenedFields,
|
||||
locale,
|
||||
tableName,
|
||||
where: whereToUse,
|
||||
})
|
||||
|
||||
let idsToUpdate: (number | string)[] = []
|
||||
|
||||
const selectDistinctResult = await selectDistinct({
|
||||
adapter: this,
|
||||
db,
|
||||
joins,
|
||||
selectFields,
|
||||
tableName,
|
||||
where,
|
||||
})
|
||||
|
||||
if (selectDistinctResult?.[0]?.id) {
|
||||
idsToUpdate = selectDistinctResult?.map((doc) => doc.id)
|
||||
|
||||
// If id wasn't passed but `where` without any joins, retrieve it with findFirst
|
||||
} else if (whereToUse && !joins.length) {
|
||||
const _db = db as LibSQLDatabase
|
||||
|
||||
const table = this.tables[tableName]
|
||||
|
||||
const docsToUpdate = await _db
|
||||
.select({
|
||||
id: table.id,
|
||||
})
|
||||
.from(table)
|
||||
.where(where)
|
||||
|
||||
idsToUpdate = docsToUpdate?.map((doc) => doc.id)
|
||||
}
|
||||
|
||||
if (!idsToUpdate.length) {
|
||||
return []
|
||||
}
|
||||
|
||||
const results = []
|
||||
|
||||
// TODO: We need to batch this to reduce the amount of db calls. This can get very slow if we are updating a lot of rows.
|
||||
for (const idToUpdate of idsToUpdate) {
|
||||
const result = await upsertRow({
|
||||
id: idToUpdate,
|
||||
adapter: this,
|
||||
data,
|
||||
db,
|
||||
fields: collection.flattenedFields,
|
||||
ignoreResult: returning === false,
|
||||
joinQuery,
|
||||
operation: 'update',
|
||||
req,
|
||||
select,
|
||||
tableName,
|
||||
})
|
||||
results.push(result)
|
||||
}
|
||||
|
||||
if (returning === false) {
|
||||
return null
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
@@ -16,16 +16,7 @@ import { getTransaction } from './utilities/getTransaction.js'
|
||||
|
||||
export async function updateVersion<T extends TypeWithID>(
|
||||
this: DrizzleAdapter,
|
||||
{
|
||||
id,
|
||||
collection,
|
||||
locale,
|
||||
req,
|
||||
select,
|
||||
versionData,
|
||||
where: whereArg,
|
||||
returning,
|
||||
}: UpdateVersionArgs<T>,
|
||||
{ id, collection, locale, req, select, versionData, where: whereArg }: UpdateVersionArgs<T>,
|
||||
) {
|
||||
const db = await getTransaction(this, req)
|
||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||
@@ -56,12 +47,7 @@ export async function updateVersion<T extends TypeWithID>(
|
||||
select,
|
||||
tableName,
|
||||
where,
|
||||
ignoreResult: returning === false,
|
||||
})
|
||||
|
||||
if (returning === false) {
|
||||
return null
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -428,10 +428,6 @@ export const upsertRow = async <T extends Record<string, unknown> | TypeWithID>(
|
||||
}
|
||||
}
|
||||
|
||||
if (ignoreResult === 'idOnly') {
|
||||
return { id: insertedRow.id } as T
|
||||
}
|
||||
|
||||
if (ignoreResult) {
|
||||
return data as T
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ type BaseArgs = {
|
||||
* When true, skips reading the data back from the database and returns the input data
|
||||
* @default false
|
||||
*/
|
||||
ignoreResult?: boolean | 'idOnly'
|
||||
ignoreResult?: boolean
|
||||
joinQuery?: JoinQuery
|
||||
path?: string
|
||||
req?: Partial<PayloadRequest>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/email-nodemailer",
|
||||
"version": "3.25.0",
|
||||
"version": "3.24.0",
|
||||
"description": "Payload Nodemailer Email Adapter",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/email-resend",
|
||||
"version": "3.25.0",
|
||||
"version": "3.24.0",
|
||||
"description": "Payload Resend Email Adapter",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/graphql",
|
||||
"version": "3.25.0",
|
||||
"version": "3.24.0",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-react",
|
||||
"version": "3.25.0",
|
||||
"version": "3.24.0",
|
||||
"description": "The official React SDK for Payload Live Preview",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -45,8 +45,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@types/react": "19.0.10",
|
||||
"@types/react-dom": "19.0.4",
|
||||
"@types/react": "19.0.1",
|
||||
"@types/react-dom": "19.0.1",
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-vue",
|
||||
"version": "3.25.0",
|
||||
"version": "3.24.0",
|
||||
"description": "The official Vue SDK for Payload Live Preview",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview",
|
||||
"version": "3.25.0",
|
||||
"version": "3.24.0",
|
||||
"description": "The official live preview JavaScript SDK for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/next",
|
||||
"version": "3.25.0",
|
||||
"version": "3.24.0",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -107,8 +107,8 @@
|
||||
"@next/eslint-plugin-next": "15.1.5",
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@types/busboy": "1.5.4",
|
||||
"@types/react": "19.0.10",
|
||||
"@types/react-dom": "19.0.4",
|
||||
"@types/react": "19.0.1",
|
||||
"@types/react-dom": "19.0.1",
|
||||
"@types/uuid": "10.0.0",
|
||||
"babel-plugin-react-compiler": "19.0.0-beta-714736e-20250131",
|
||||
"esbuild": "0.24.2",
|
||||
|
||||
@@ -44,26 +44,23 @@ export const DefaultNavClient: React.FC<{
|
||||
id = `nav-global-${slug}`
|
||||
}
|
||||
|
||||
const LinkElement = Link || 'a'
|
||||
const activeCollection =
|
||||
pathname.startsWith(href) && ['/', undefined].includes(pathname[href.length])
|
||||
|
||||
const Label = (
|
||||
<span className={`${baseClass}__link-label`}>{getTranslation(label, i18n)}</span>
|
||||
)
|
||||
|
||||
if (activeCollection) {
|
||||
return (
|
||||
<div className={`${baseClass}__link active`} id={id} key={i}>
|
||||
<div className={`${baseClass}__link-indicator`} />
|
||||
{Label}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Link className={`${baseClass}__link`} href={href} id={id} key={i} prefetch={false}>
|
||||
{Label}
|
||||
</Link>
|
||||
<LinkElement
|
||||
className={[`${baseClass}__link`, activeCollection && `active`]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
href={href}
|
||||
id={id}
|
||||
key={i}
|
||||
prefetch={Link ? false : undefined}
|
||||
>
|
||||
{activeCollection && <div className={`${baseClass}__link-indicator`} />}
|
||||
<span className={`${baseClass}__link-label`}>{getTranslation(label, i18n)}</span>
|
||||
</LinkElement>
|
||||
)
|
||||
})}
|
||||
</NavGroup>
|
||||
|
||||
@@ -93,32 +93,36 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
padding-block: base(0.125);
|
||||
padding-inline-start: 0;
|
||||
padding-inline-end: base(1.5);
|
||||
text-decoration: none;
|
||||
nav {
|
||||
a {
|
||||
position: relative;
|
||||
padding-block: base(0.125);
|
||||
padding-inline-start: 0;
|
||||
padding-inline-end: base(1.5);
|
||||
display: flex;
|
||||
text-decoration: none;
|
||||
|
||||
&:focus:not(:focus-visible) {
|
||||
box-shadow: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
&:focus:not(:focus-visible) {
|
||||
box-shadow: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&.active {
|
||||
font-weight: normal;
|
||||
padding-left: 0;
|
||||
font-weight: 600;
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
&.active {
|
||||
font-weight: normal;
|
||||
padding-left: 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a.nav__link {
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
text-decoration: underline;
|
||||
}
|
||||
&__link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__link-indicator {
|
||||
@@ -144,7 +148,7 @@
|
||||
padding: var(--app-header-height) var(--gutter-h) base(2);
|
||||
}
|
||||
|
||||
&__link {
|
||||
nav a {
|
||||
font-size: base(0.875);
|
||||
line-height: base(1.5);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@ import type { ImportMap, LanguageOptions, SanitizedConfig, ServerFunctionClient
|
||||
import { rtlLanguages } from '@payloadcms/translations'
|
||||
import { ProgressBar, RootProvider } from '@payloadcms/ui'
|
||||
import { getClientConfig } from '@payloadcms/ui/utilities/getClientConfig'
|
||||
import { cookies as nextCookies } from 'next/headers.js'
|
||||
import { headers as getHeaders, cookies as nextCookies } from 'next/headers.js'
|
||||
import { getPayload, getRequestLanguage, parseCookies } from 'payload'
|
||||
import React from 'react'
|
||||
|
||||
import { getNavPrefs } from '../../elements/Nav/getNavPrefs.js'
|
||||
@@ -33,16 +34,16 @@ export const RootLayout = async ({
|
||||
}) => {
|
||||
checkDependencies()
|
||||
|
||||
const {
|
||||
const config = await configPromise
|
||||
|
||||
const headers = await getHeaders()
|
||||
const cookies = parseCookies(headers)
|
||||
|
||||
const languageCode = getRequestLanguage({
|
||||
config,
|
||||
cookies,
|
||||
headers,
|
||||
languageCode,
|
||||
permissions,
|
||||
req,
|
||||
req: {
|
||||
payload: { config },
|
||||
},
|
||||
} = await initReq({ configPromise, importMap, key: 'RootLayout' })
|
||||
})
|
||||
|
||||
const theme = getRequestTheme({
|
||||
config,
|
||||
@@ -50,6 +51,10 @@ export const RootLayout = async ({
|
||||
headers,
|
||||
})
|
||||
|
||||
const payload = await getPayload({ config, importMap })
|
||||
|
||||
const { permissions, req } = await initReq(config)
|
||||
|
||||
const dir = (rtlLanguages as unknown as AcceptedLanguages[]).includes(languageCode)
|
||||
? 'RTL'
|
||||
: 'LTR'
|
||||
@@ -129,11 +134,11 @@ export const RootLayout = async ({
|
||||
{Array.isArray(config.admin?.components?.providers) &&
|
||||
config.admin?.components?.providers.length > 0 ? (
|
||||
<NestProviders
|
||||
importMap={req.payload.importMap}
|
||||
importMap={payload.importMap}
|
||||
providers={config.admin?.components?.providers}
|
||||
serverProps={{
|
||||
i18n: req.i18n,
|
||||
payload: req.payload,
|
||||
payload,
|
||||
permissions,
|
||||
user: req.user,
|
||||
}}
|
||||
|
||||
@@ -13,11 +13,7 @@ import { initReq } from './initReq.js'
|
||||
export const handleServerFunctions: ServerFunctionHandler = async (args) => {
|
||||
const { name: fnKey, args: fnArgs, config: configPromise, importMap } = args
|
||||
|
||||
const { req } = await initReq({
|
||||
configPromise,
|
||||
importMap,
|
||||
key: 'RootLayout',
|
||||
})
|
||||
const { req } = await initReq(configPromise)
|
||||
|
||||
const augmentedArgs: Parameters<ServerFunction>[0] = {
|
||||
...fnArgs,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { InitPageResult, VisibleEntities } from 'payload'
|
||||
|
||||
import { headers as getHeaders } from 'next/headers.js'
|
||||
import { notFound } from 'next/navigation.js'
|
||||
import { isEntityHidden } from 'payload'
|
||||
import { getPayload, isEntityHidden, parseCookies } from 'payload'
|
||||
import * as qs from 'qs-esm'
|
||||
|
||||
import type { Args } from './types.js'
|
||||
@@ -17,38 +18,31 @@ export const initPage = async ({
|
||||
importMap,
|
||||
route,
|
||||
searchParams,
|
||||
useLayoutReq,
|
||||
}: Args): Promise<InitPageResult> => {
|
||||
const headers = await getHeaders()
|
||||
const payload = await getPayload({ config: configPromise, importMap })
|
||||
const queryString = `${qs.stringify(searchParams ?? {}, { addQueryPrefix: true })}`
|
||||
|
||||
const {
|
||||
cookies,
|
||||
locale,
|
||||
permissions,
|
||||
req,
|
||||
req: { payload },
|
||||
} = await initReq({
|
||||
configPromise,
|
||||
importMap,
|
||||
key: useLayoutReq ? 'RootLayout' : 'initPage',
|
||||
overrides: {
|
||||
fallbackLocale: false,
|
||||
req: {
|
||||
query: qs.parse(queryString, {
|
||||
depth: 10,
|
||||
ignoreQueryPrefix: true,
|
||||
}),
|
||||
},
|
||||
urlSuffix: `${route}${searchParams ? queryString : ''}`,
|
||||
},
|
||||
})
|
||||
|
||||
const {
|
||||
collections,
|
||||
globals,
|
||||
routes: { admin: adminRoute },
|
||||
} = payload.config
|
||||
|
||||
const cookies = parseCookies(headers)
|
||||
|
||||
const { locale, permissions, req } = await initReq(payload.config, {
|
||||
fallbackLocale: false,
|
||||
req: {
|
||||
headers,
|
||||
query: qs.parse(queryString, {
|
||||
depth: 10,
|
||||
ignoreQueryPrefix: true,
|
||||
}),
|
||||
url: `${payload.config.serverURL}${route}${searchParams ? queryString : ''}`,
|
||||
},
|
||||
})
|
||||
|
||||
const languageOptions = Object.entries(payload.config.i18n.supportedLanguages || {}).reduce(
|
||||
(acc, [language, languageConfig]) => {
|
||||
if (Object.keys(payload.config.i18n.supportedLanguages).includes(language)) {
|
||||
|
||||
@@ -20,15 +20,4 @@ export type Args = {
|
||||
* The search parameters of the current route provided to all pages in Next.js.
|
||||
*/
|
||||
searchParams: { [key: string]: string | string[] | undefined }
|
||||
/**
|
||||
* If `useLayoutReq` is `true`, this page will use the cached `req` created by the root layout
|
||||
* instead of creating a new one.
|
||||
*
|
||||
* This improves performance for pages that are able to share the same `req` as the root layout,
|
||||
* as permissions do not need to be re-calculated.
|
||||
*
|
||||
* If the page has unique query and url params that need to be part of the `req` object, or if you
|
||||
* need permissions calculation to respect those you should not use this property.
|
||||
*/
|
||||
useLayoutReq?: boolean
|
||||
}
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
import type { AcceptedLanguages, I18n, I18nClient } from '@payloadcms/translations'
|
||||
import type {
|
||||
ImportMap,
|
||||
Locale,
|
||||
Payload,
|
||||
PayloadRequest,
|
||||
SanitizedConfig,
|
||||
SanitizedPermissions,
|
||||
User,
|
||||
} from 'payload'
|
||||
import type { I18n, I18nClient } from '@payloadcms/translations'
|
||||
import type { Locale, PayloadRequest, SanitizedConfig, SanitizedPermissions } from 'payload'
|
||||
|
||||
import { initI18n } from '@payloadcms/translations'
|
||||
import { headers as getHeaders } from 'next/headers.js'
|
||||
@@ -19,114 +11,78 @@ import {
|
||||
getRequestLanguage,
|
||||
parseCookies,
|
||||
} from 'payload'
|
||||
import { cache } from 'react'
|
||||
|
||||
import { getRequestLocale } from './getRequestLocale.js'
|
||||
import { selectiveCache } from './selectiveCache.js'
|
||||
|
||||
type Result = {
|
||||
cookies: Map<string, string>
|
||||
headers: Awaited<ReturnType<typeof getHeaders>>
|
||||
languageCode: AcceptedLanguages
|
||||
locale?: Locale
|
||||
permissions: SanitizedPermissions
|
||||
req: PayloadRequest
|
||||
}
|
||||
|
||||
type PartialResult = {
|
||||
i18n: I18nClient
|
||||
languageCode: AcceptedLanguages
|
||||
payload: Payload
|
||||
responseHeaders: Headers
|
||||
user: null | User
|
||||
}
|
||||
export const initReq = cache(async function (
|
||||
configPromise: Promise<SanitizedConfig> | SanitizedConfig,
|
||||
overrides?: Parameters<typeof createLocalReq>[0],
|
||||
): Promise<Result> {
|
||||
const config = await configPromise
|
||||
const payload = await getPayload({ config })
|
||||
|
||||
// Create cache instances for different parts of our application
|
||||
const partialReqCache = selectiveCache<PartialResult>('partialReq')
|
||||
const reqCache = selectiveCache<Result>('req')
|
||||
|
||||
/**
|
||||
* Initializes a full request object, including the `req` object and access control.
|
||||
* As access control and getting the request locale is dependent on the current URL and
|
||||
*/
|
||||
export const initReq = async function ({
|
||||
configPromise,
|
||||
importMap,
|
||||
key,
|
||||
overrides,
|
||||
}: {
|
||||
configPromise: Promise<SanitizedConfig> | SanitizedConfig
|
||||
importMap: ImportMap
|
||||
key: string
|
||||
overrides?: Parameters<typeof createLocalReq>[0]
|
||||
}): Promise<Result> {
|
||||
const headers = await getHeaders()
|
||||
const cookies = parseCookies(headers)
|
||||
|
||||
const partialResult = await partialReqCache.get(async () => {
|
||||
const config = await configPromise
|
||||
const payload = await getPayload({ config, importMap })
|
||||
const languageCode = getRequestLanguage({
|
||||
config,
|
||||
cookies,
|
||||
headers,
|
||||
})
|
||||
const i18n: I18nClient = await initI18n({
|
||||
config: config.i18n,
|
||||
context: 'client',
|
||||
language: languageCode,
|
||||
})
|
||||
const languageCode = getRequestLanguage({
|
||||
config,
|
||||
cookies,
|
||||
headers,
|
||||
})
|
||||
|
||||
const { responseHeaders, user } = await executeAuthStrategies({
|
||||
headers,
|
||||
payload,
|
||||
})
|
||||
const i18n: I18nClient = await initI18n({
|
||||
config: config.i18n,
|
||||
context: 'client',
|
||||
language: languageCode,
|
||||
})
|
||||
|
||||
return {
|
||||
i18n,
|
||||
languageCode,
|
||||
payload,
|
||||
responseHeaders,
|
||||
user,
|
||||
}
|
||||
}, 'global')
|
||||
/**
|
||||
* Cannot simply call `payload.auth` here, as we need the user to get the locale, and we need the locale to get the access results
|
||||
* I.e. the `payload.auth` function would call `getAccessResults` without a fully-formed `req` object
|
||||
*/
|
||||
const { responseHeaders, user } = await executeAuthStrategies({
|
||||
headers,
|
||||
payload,
|
||||
})
|
||||
|
||||
return reqCache.get(async () => {
|
||||
const { i18n, languageCode, payload, responseHeaders, user } = partialResult
|
||||
const { req: reqOverrides, ...optionsOverrides } = overrides || {}
|
||||
|
||||
const { req: reqOverrides, ...optionsOverrides } = overrides || {}
|
||||
|
||||
const req = await createLocalReq(
|
||||
{
|
||||
req: {
|
||||
headers,
|
||||
host: headers.get('host'),
|
||||
i18n: i18n as I18n,
|
||||
responseHeaders,
|
||||
user,
|
||||
...(reqOverrides || {}),
|
||||
},
|
||||
...(optionsOverrides || {}),
|
||||
const req = await createLocalReq(
|
||||
{
|
||||
req: {
|
||||
headers,
|
||||
host: headers.get('host'),
|
||||
i18n: i18n as I18n,
|
||||
responseHeaders,
|
||||
url: `${payload.config.serverURL}`,
|
||||
user,
|
||||
...(reqOverrides || {}),
|
||||
},
|
||||
payload,
|
||||
)
|
||||
...(optionsOverrides || {}),
|
||||
},
|
||||
payload,
|
||||
)
|
||||
|
||||
const locale = await getRequestLocale({
|
||||
req,
|
||||
})
|
||||
const locale = await getRequestLocale({
|
||||
req,
|
||||
})
|
||||
|
||||
req.locale = locale?.code
|
||||
req.locale = locale?.code
|
||||
|
||||
const permissions = await getAccessResults({
|
||||
req,
|
||||
})
|
||||
const permissions = await getAccessResults({
|
||||
req,
|
||||
})
|
||||
|
||||
return {
|
||||
cookies,
|
||||
headers,
|
||||
languageCode,
|
||||
locale,
|
||||
permissions,
|
||||
req,
|
||||
}
|
||||
}, key)
|
||||
}
|
||||
return {
|
||||
locale,
|
||||
permissions,
|
||||
req,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
import { cache } from 'react'
|
||||
|
||||
type CachedValue = object
|
||||
|
||||
// Module-scoped cache container that holds all cached, stable containers
|
||||
// - these may hold the stable value, or a promise to the stable value
|
||||
const globalCacheContainer: Record<
|
||||
string,
|
||||
<TValue extends object = CachedValue>(
|
||||
...args: unknown[]
|
||||
) => {
|
||||
value: null | Promise<TValue> | TValue
|
||||
}
|
||||
> = {}
|
||||
|
||||
/**
|
||||
* Creates a selective cache function that provides more control over React's request-level caching behavior.
|
||||
*
|
||||
* @param namespace - A namespace to group related cached values
|
||||
* @returns A function that manages cached values within the specified namespace
|
||||
*/
|
||||
export function selectiveCache<TValue extends object = CachedValue>(namespace: string) {
|
||||
// Create a stable namespace container if it doesn't exist
|
||||
if (!globalCacheContainer[namespace]) {
|
||||
globalCacheContainer[namespace] = cache((...args) => ({
|
||||
value: null,
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or creates a cached value for a specific key within the namespace
|
||||
*
|
||||
* @param key - The key to identify the cached value
|
||||
* @param factory - A function that produces the value if not cached
|
||||
* @returns The cached or newly created value
|
||||
*/
|
||||
const getCached = async (factory: () => Promise<TValue>, ...cacheArgs): Promise<TValue> => {
|
||||
const stableObjectFn = globalCacheContainer[namespace]
|
||||
const stableObject = stableObjectFn<TValue>(...cacheArgs)
|
||||
|
||||
if (
|
||||
stableObject?.value &&
|
||||
'then' in stableObject.value &&
|
||||
typeof stableObject.value?.then === 'function'
|
||||
) {
|
||||
return await stableObject.value
|
||||
}
|
||||
|
||||
stableObject.value = factory()
|
||||
|
||||
return await stableObject.value
|
||||
}
|
||||
|
||||
return {
|
||||
get: getCached,
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
import type {
|
||||
AdminViewServerProps,
|
||||
ListPreferences,
|
||||
ListQuery,
|
||||
ListViewClientProps,
|
||||
ListViewServerPropsOnly,
|
||||
Where,
|
||||
} from 'payload'
|
||||
|
||||
import { DefaultListView, HydrateAuthProvider, ListQueryProvider } from '@payloadcms/ui'
|
||||
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
|
||||
import { renderFilters, renderTable, upsertPreferences } from '@payloadcms/ui/rsc'
|
||||
import { formatAdminURL, mergeListSearchAndWhere } from '@payloadcms/ui/shared'
|
||||
import { notFound } from 'next/navigation.js'
|
||||
import {
|
||||
type AdminViewServerProps,
|
||||
type ColumnPreference,
|
||||
type ListPreferences,
|
||||
type ListQuery,
|
||||
type ListViewClientProps,
|
||||
type ListViewServerPropsOnly,
|
||||
type Where,
|
||||
} from 'payload'
|
||||
import { isNumber, transformColumnsToPreferences } from 'payload/shared'
|
||||
import { isNumber } from 'payload/shared'
|
||||
import React, { Fragment } from 'react'
|
||||
|
||||
import { renderListViewSlots } from './renderListViewSlots.js'
|
||||
@@ -72,20 +72,10 @@ export const renderListView = async (
|
||||
|
||||
const query = queryFromArgs || queryFromReq
|
||||
|
||||
const columns: ColumnPreference[] = transformColumnsToPreferences(
|
||||
query?.columns as ColumnPreference[] | string,
|
||||
)
|
||||
|
||||
/**
|
||||
* @todo: find a pattern to avoid setting preferences on hard navigation, i.e. direct links, page refresh, etc.
|
||||
* This will ensure that prefs are only updated when explicitly set by the user
|
||||
* This could potentially be done by injecting a `sessionID` into the params and comparing it against a session cookie
|
||||
*/
|
||||
const listPreferences = await upsertPreferences<ListPreferences>({
|
||||
key: `${collectionSlug}-list`,
|
||||
req,
|
||||
value: {
|
||||
columns,
|
||||
limit: isNumber(query?.limit) ? Number(query.limit) : undefined,
|
||||
sort: query?.sort as string,
|
||||
},
|
||||
@@ -151,7 +141,6 @@ export const renderListView = async (
|
||||
clientCollectionConfig,
|
||||
collectionConfig,
|
||||
columnPreferences: listPreferences?.columns,
|
||||
columns,
|
||||
customCellProps,
|
||||
docs: data.docs,
|
||||
drawerSlug,
|
||||
@@ -214,11 +203,9 @@ export const renderListView = async (
|
||||
<Fragment>
|
||||
<HydrateAuthProvider permissions={permissions} />
|
||||
<ListQueryProvider
|
||||
columns={transformColumnsToPreferences(columnState)}
|
||||
data={data}
|
||||
defaultLimit={limit}
|
||||
defaultSort={sort}
|
||||
listPreferences={listPreferences}
|
||||
modifySearchParams={!isInDrawer}
|
||||
>
|
||||
{RenderServerComponent({
|
||||
|
||||
@@ -58,7 +58,6 @@ export const NotFoundPage = async ({
|
||||
redirectUnauthenticatedUser: true,
|
||||
route: formatAdminURL({ adminRoute, path: '/not-found' }),
|
||||
searchParams,
|
||||
useLayoutReq: true,
|
||||
})
|
||||
|
||||
const params = await paramsPromise
|
||||
|
||||
@@ -14,7 +14,7 @@ export { generateVerifyMetadata } from './meta.js'
|
||||
export async function Verify({ initPageResult, params, searchParams }: AdminViewServerProps) {
|
||||
// /:collectionSlug/verify/:token
|
||||
|
||||
const [collectionSlug, verify, token] = params.segments
|
||||
const [collectionSlug, token] = params.segments
|
||||
const { locale, permissions, req } = initPageResult
|
||||
|
||||
const {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type {
|
||||
Document,
|
||||
DocumentViewServerProps,
|
||||
Locale,
|
||||
OptionObject,
|
||||
SanitizedCollectionPermission,
|
||||
SanitizedGlobalPermission,
|
||||
@@ -143,29 +142,26 @@ export async function VersionView(props: DocumentViewServerProps) {
|
||||
}
|
||||
}
|
||||
|
||||
let selectedLocales: OptionObject[] = []
|
||||
const selectedLocales: OptionObject[] = []
|
||||
if (localization) {
|
||||
let locales: Locale[] = []
|
||||
if (localeCodesFromParams) {
|
||||
for (const code of localeCodesFromParams) {
|
||||
const locale = localization.locales.find((locale) => locale.code === code)
|
||||
if (!locale) {
|
||||
continue
|
||||
if (locale) {
|
||||
selectedLocales.push({
|
||||
label: locale.label,
|
||||
value: locale.code,
|
||||
})
|
||||
}
|
||||
locales.push(locale)
|
||||
}
|
||||
} else {
|
||||
locales = localization.locales
|
||||
for (const { code, label } of localization.locales) {
|
||||
selectedLocales.push({
|
||||
label,
|
||||
value: code,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (localization.filterAvailableLocales) {
|
||||
locales = (await localization.filterAvailableLocales({ locales, req })) || []
|
||||
}
|
||||
|
||||
selectedLocales = locales.map((locale) => ({
|
||||
label: locale.label,
|
||||
value: locale.code,
|
||||
}))
|
||||
}
|
||||
|
||||
const latestVersion =
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/payload-cloud",
|
||||
"version": "3.25.0",
|
||||
"version": "3.24.0",
|
||||
"description": "The official Payload Cloud plugin",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload",
|
||||
"version": "3.25.0",
|
||||
"version": "3.24.0",
|
||||
"description": "Node, React, Headless CMS and Application Framework built on Next.js",
|
||||
"keywords": [
|
||||
"admin panel",
|
||||
@@ -74,7 +74,7 @@
|
||||
"build": "rimraf .dist && rimraf tsconfig.tsbuildinfo && pnpm copyfiles && pnpm build:types && pnpm build:swc && pnpm build:esbuild",
|
||||
"build:esbuild": "echo skipping esbuild",
|
||||
"build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
|
||||
"build:types": "tsc --emitDeclarationOnly --outDir dist",
|
||||
"build:types": "concurrently --group \"tsc --emitDeclarationOnly --outDir dist\" \"tsc-strict\"",
|
||||
"clean": "rimraf -g {dist,*.tsbuildinfo}",
|
||||
"clean:cache": "rimraf node_modules/.cache",
|
||||
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
|
||||
@@ -124,13 +124,15 @@
|
||||
"@types/pluralize": "0.0.33",
|
||||
"@types/uuid": "10.0.0",
|
||||
"@types/ws": "^8.5.10",
|
||||
"concurrently": "9.1.2",
|
||||
"copyfiles": "2.4.1",
|
||||
"cross-env": "7.0.3",
|
||||
"esbuild": "0.24.2",
|
||||
"graphql-http": "^1.22.0",
|
||||
"react-datepicker": "7.6.0",
|
||||
"rimraf": "6.0.1",
|
||||
"sharp": "0.32.6"
|
||||
"sharp": "0.32.6",
|
||||
"typescript-strict-plugin": "2.4.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"graphql": "^16.8.1"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-strict-ignore
|
||||
import type { GenericLanguages, I18n } from '@payloadcms/translations'
|
||||
import type { JSONSchema4 } from 'json-schema'
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// @ts-strict-ignore
|
||||
|
||||
import type { I18nClient } from '@payloadcms/translations'
|
||||
|
||||
import type { ClientField, Field, FieldTypes, Tab } from '../../fields/config/types.js'
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { SanitizedConfig } from '../../config/types.js'
|
||||
import type { PaginatedDocs } from '../../database/types.js'
|
||||
import type { CollectionSlug, ColumnPreference } from '../../index.js'
|
||||
import type { PayloadRequest, Sort, Where } from '../../types/index.js'
|
||||
import type { ColumnsFromURL } from '../../utilities/transformColumnPreferences.js'
|
||||
|
||||
export type DefaultServerFunctionArgs = {
|
||||
importMap: ImportMap
|
||||
@@ -39,11 +38,6 @@ export type ServerFunctionHandler = (
|
||||
) => Promise<unknown>
|
||||
|
||||
export type ListQuery = {
|
||||
/*
|
||||
* This is an of strings, i.e. `['title', '-slug']`
|
||||
* Use `transformColumnsToPreferences` to convert it back and forth
|
||||
*/
|
||||
columns?: ColumnsFromURL
|
||||
limit?: string
|
||||
page?: string
|
||||
/*
|
||||
|
||||
@@ -42,14 +42,8 @@ export type ListViewClientProps = {
|
||||
disableBulkEdit?: boolean
|
||||
enableRowSelections?: boolean
|
||||
hasCreatePermission: boolean
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
listPreferences?: ListPreferences
|
||||
newDocumentURL: string
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
preferenceKey?: string
|
||||
renderedFilters?: Map<string, React.ReactNode>
|
||||
resolvedFilterOptions?: Map<string, ResolvedFilterOptions>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-strict-ignore
|
||||
import type { SanitizedCollectionConfig } from './../collections/config/types.js'
|
||||
|
||||
type CookieOptions = {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-strict-ignore
|
||||
import crypto from 'crypto'
|
||||
|
||||
const algorithm = 'aes-256-ctr'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-strict-ignore
|
||||
import { status as httpStatus } from 'http-status'
|
||||
|
||||
import type { PayloadHandler } from '../../config/types.js'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-strict-ignore
|
||||
import { status as httpStatus } from 'http-status'
|
||||
|
||||
import type { PayloadHandler } from '../../config/types.js'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-strict-ignore
|
||||
import { status as httpStatus } from 'http-status'
|
||||
|
||||
import type { PayloadHandler } from '../../config/types.js'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-strict-ignore
|
||||
import { status as httpStatus } from 'http-status'
|
||||
|
||||
import type { PayloadHandler } from '../../config/types.js'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-strict-ignore
|
||||
import { status as httpStatus } from 'http-status'
|
||||
|
||||
import type { PayloadHandler } from '../../config/types.js'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-strict-ignore
|
||||
import { status as httpStatus } from 'http-status'
|
||||
|
||||
import type { PayloadHandler } from '../../config/types.js'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-strict-ignore
|
||||
import type { AllOperations, PayloadRequest } from '../types/index.js'
|
||||
import type { Permissions, SanitizedPermissions } from './types.js'
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-strict-ignore
|
||||
import type { CollectionConfig } from '../collections/config/types.js'
|
||||
import type { Field, TabAsField } from '../fields/config/types.js'
|
||||
import type { PayloadRequest } from '../types/index.js'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-strict-ignore
|
||||
import type { Auth } from './types.js'
|
||||
|
||||
export const getLoginOptions = (
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-strict-ignore
|
||||
import crypto from 'crypto'
|
||||
import { status as httpStatus } from 'http-status'
|
||||
import { URL } from 'url'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-strict-ignore
|
||||
import type { CollectionSlug, Payload, RequestContext } from '../../../index.js'
|
||||
import type { PayloadRequest } from '../../../types/index.js'
|
||||
import type { Result } from '../forgotPassword.js'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-strict-ignore
|
||||
import type {
|
||||
AuthOperationsFromCollectionSlug,
|
||||
Collection,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-strict-ignore
|
||||
import { decodeJwt } from 'jose'
|
||||
|
||||
import type { Collection } from '../../collections/config/types.js'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-strict-ignore
|
||||
import url from 'url'
|
||||
|
||||
import type { BeforeOperationHook, Collection } from '../../collections/config/types.js'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-strict-ignore
|
||||
import { status as httpStatus } from 'http-status'
|
||||
|
||||
import type {
|
||||
|
||||
@@ -48,7 +48,6 @@ export const verifyEmailOperation = async (args: Args): Promise<boolean> => {
|
||||
_verified: true,
|
||||
},
|
||||
req,
|
||||
returning: false,
|
||||
})
|
||||
|
||||
if (shouldCommit) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-strict-ignore
|
||||
import { URL } from 'url'
|
||||
|
||||
import type { Collection } from '../collections/config/types.js'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-strict-ignore
|
||||
import crypto from 'crypto'
|
||||
|
||||
import type { SanitizedCollectionConfig } from '../../collections/config/types.js'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-strict-ignore
|
||||
import { jwtVerify } from 'jose'
|
||||
|
||||
import type { Payload, Where } from '../../types/index.js'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-strict-ignore
|
||||
import crypto from 'crypto'
|
||||
import scmp from 'scmp'
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// @ts-strict-ignore
|
||||
|
||||
import type { AdminViewConfig } from '../../admin/views/index.js'
|
||||
import type { SanitizedConfig } from '../../config/types.js'
|
||||
import type { AddToImportMap, Imports, InternalImportMap } from './index.js'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-strict-ignore
|
||||
/* eslint-disable @typescript-eslint/no-unused-expressions */
|
||||
import type { PayloadComponent, SanitizedConfig } from '../../config/types.js'
|
||||
import type { Block, Field, Tab } from '../../fields/config/types.js'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-strict-ignore
|
||||
import type { PayloadComponent } from '../../config/types.js'
|
||||
|
||||
export function parsePayloadComponent(PayloadComponent: PayloadComponent): {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-strict-ignore
|
||||
/* eslint-disable no-console */
|
||||
import { Cron } from 'croner'
|
||||
import minimist from 'minimist'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-strict-ignore
|
||||
import nextEnvImport from '@next/env'
|
||||
|
||||
import { findUpSync } from '../utilities/findUp.js'
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user