Compare commits
118 Commits
fix/zhTW-t
...
v3.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4816a1638a | ||
|
|
22c53392a3 | ||
|
|
bdaa9e831d | ||
|
|
036bcd6b8f | ||
|
|
4d2bc861cf | ||
|
|
98722dc0fd | ||
|
|
629d7c3263 | ||
|
|
5f7af5317a | ||
|
|
8bb1b60964 | ||
|
|
7ef5493414 | ||
|
|
a3ac838221 | ||
|
|
14400d1cb9 | ||
|
|
7d531646fd | ||
|
|
6f6c1435c7 | ||
|
|
332b8b6f34 | ||
|
|
d40a734080 | ||
|
|
94f1dfef52 | ||
|
|
0857dbe465 | ||
|
|
71f19fba58 | ||
|
|
24b18fb0fd | ||
|
|
5731241a5c | ||
|
|
47e70abb4e | ||
|
|
0ede95f375 | ||
|
|
b723efdd3b | ||
|
|
14c513690d | ||
|
|
88f239e784 | ||
|
|
a1f6bf8a67 | ||
|
|
912dcd38df | ||
|
|
da5028cdee | ||
|
|
899faa62f1 | ||
|
|
9df6a644c9 | ||
|
|
1a6d9eaa11 | ||
|
|
7d447af277 | ||
|
|
d8baaab849 | ||
|
|
3e1523f007 | ||
|
|
fa38af025f | ||
|
|
6ca9ff847f | ||
|
|
a8824b2b51 | ||
|
|
6aa3752b16 | ||
|
|
c483a439bf | ||
|
|
74bdf1c681 | ||
|
|
7437d9fe58 | ||
|
|
51f7351962 | ||
|
|
d01fcb921b | ||
|
|
6179c938bf | ||
|
|
dbbcb658a9 | ||
|
|
16f97ad7c3 | ||
|
|
bc7445ed99 | ||
|
|
e4d024cd0d | ||
|
|
1005de8295 | ||
|
|
c79289cedf | ||
|
|
6a745be036 | ||
|
|
cee9cc33ed | ||
|
|
9a5e9313cd | ||
|
|
5401af5812 | ||
|
|
6305a1d1c2 | ||
|
|
95b96e3e9e | ||
|
|
95b3f6d40d | ||
|
|
c258a4bef1 | ||
|
|
647544a0c6 | ||
|
|
7e0a2a879c | ||
|
|
471e1388ae | ||
|
|
1da430b042 | ||
|
|
56ac06c563 | ||
|
|
4dec4bb61c | ||
|
|
99a09c49a3 | ||
|
|
88fd46bfea | ||
|
|
8a6603b3d8 | ||
|
|
f6c9f454a5 | ||
|
|
d8a5426c37 | ||
|
|
c9011dcbfd | ||
|
|
43089fd13c | ||
|
|
bb3bd9c395 | ||
|
|
ba423ab424 | ||
|
|
c23984cac3 | ||
|
|
6685a0fa7e | ||
|
|
ac4750d016 | ||
|
|
951e9fd7f2 | ||
|
|
cbd1554589 | ||
|
|
80c545933f | ||
|
|
594f319fc6 | ||
|
|
102feb9576 | ||
|
|
68274d2862 | ||
|
|
8945b7a4fa | ||
|
|
d5ef93b2ba | ||
|
|
cb0f0dba3a | ||
|
|
7b263be01b | ||
|
|
56df60f520 | ||
|
|
d5cbbc472d | ||
|
|
d987e5628a | ||
|
|
1383191f15 | ||
|
|
27297284cf | ||
|
|
3af3a91c87 | ||
|
|
23c5b71f95 | ||
|
|
2ee6a8ec3a | ||
|
|
10819b8693 | ||
|
|
83c617b452 | ||
|
|
4acb133655 | ||
|
|
6e4135e790 | ||
|
|
f0198b62f3 | ||
|
|
3ff8063ab8 | ||
|
|
8d52f1b279 | ||
|
|
24072d222c | ||
|
|
55c59e71da | ||
|
|
62233788e0 | ||
|
|
b297c5499d | ||
|
|
fb7925f272 | ||
|
|
221e873862 | ||
|
|
e7143e02e2 | ||
|
|
a1d68bd951 | ||
|
|
93ee452a2d | ||
|
|
1abaa5fc17 | ||
|
|
999059bc61 | ||
|
|
39ba39c237 | ||
|
|
009e6c2066 | ||
|
|
8bf03ae706 | ||
|
|
58ea94f6ac | ||
|
|
a2afc38894 |
24
.github/workflows/main.yml
vendored
24
.github/workflows/main.yml
vendored
@@ -12,6 +12,7 @@ concurrency:
|
||||
|
||||
env:
|
||||
NODE_VERSION: 18.20.2
|
||||
PNPM_VERSION: 8.15.7
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
@@ -68,7 +69,7 @@ jobs:
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 8
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
@@ -113,7 +114,7 @@ jobs:
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 8
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
run_install: false
|
||||
|
||||
- name: Restore build
|
||||
@@ -137,9 +138,9 @@ jobs:
|
||||
database:
|
||||
- mongodb
|
||||
- postgres
|
||||
# - postgres-custom-schema
|
||||
# - postgres-uuid
|
||||
# - supabase
|
||||
- postgres-custom-schema
|
||||
- postgres-uuid
|
||||
- supabase
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
@@ -162,7 +163,7 @@ jobs:
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 8
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
run_install: false
|
||||
|
||||
- name: Restore build
|
||||
@@ -236,7 +237,6 @@ jobs:
|
||||
- access-control
|
||||
- admin
|
||||
- auth
|
||||
- email
|
||||
- field-error-states
|
||||
- fields-relationship
|
||||
- fields
|
||||
@@ -246,6 +246,7 @@ jobs:
|
||||
- fields__collections__Lexical
|
||||
- live-preview
|
||||
- localization
|
||||
- plugin-cloud-storage
|
||||
- plugin-form-builder
|
||||
- plugin-nested-docs
|
||||
- plugin-seo
|
||||
@@ -265,7 +266,7 @@ jobs:
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 8
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
run_install: false
|
||||
|
||||
- name: Restore build
|
||||
@@ -275,6 +276,10 @@ jobs:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: Start LocalStack
|
||||
run: pnpm docker:start
|
||||
if: ${{ matrix.suite == 'plugin-cloud-storage' }}
|
||||
|
||||
- name: Install Playwright
|
||||
run: pnpm exec playwright install --with-deps
|
||||
|
||||
@@ -286,6 +291,7 @@ jobs:
|
||||
with:
|
||||
name: test-results-${{ matrix.suite }}
|
||||
path: test/test-results/
|
||||
if-no-files-found: ignore
|
||||
retention-days: 1
|
||||
|
||||
tests-type-generation:
|
||||
@@ -306,7 +312,7 @@ jobs:
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 8
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
run_install: false
|
||||
|
||||
- name: Restore build
|
||||
|
||||
97
.github/workflows/pr-title.yml
vendored
Normal file
97
.github/workflows/pr-title.yml
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
name: pr-title
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- synchronize
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
main:
|
||||
name: lint-pr-title
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: amannn/action-semantic-pull-request@v5
|
||||
id: lint_pr_title
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
types: |
|
||||
build
|
||||
chore
|
||||
ci
|
||||
docs
|
||||
feat
|
||||
fix
|
||||
perf
|
||||
refactor
|
||||
revert
|
||||
style
|
||||
test
|
||||
types
|
||||
scopes: |
|
||||
cpa
|
||||
db-\*
|
||||
db-mongodb
|
||||
db-postgres
|
||||
email-nodemailer
|
||||
eslint
|
||||
graphql
|
||||
live-preview
|
||||
live-preview-react
|
||||
next
|
||||
payload
|
||||
plugin-cloud
|
||||
plugin-cloud-storage
|
||||
plugin-form-builder
|
||||
plugin-nested-docs
|
||||
plugin-redirects
|
||||
plugin-search
|
||||
plugin-sentry
|
||||
plugin-seo
|
||||
plugin-stripe
|
||||
richtext-\*
|
||||
richtext-lexical
|
||||
richtext-slate
|
||||
storage-\*
|
||||
storage-azure
|
||||
storage-gcs
|
||||
storage-vercel-blob
|
||||
storage-s3
|
||||
translations
|
||||
ui
|
||||
templates
|
||||
examples
|
||||
|
||||
# Disallow uppercase letters at the beginning of the subject
|
||||
subjectPattern: ^(?![A-Z]).+$
|
||||
|
||||
- uses: marocchino/sticky-pull-request-comment@v2
|
||||
# When the previous steps fails, the workflow would stop. By adding this
|
||||
# condition you can continue the execution with the populated error message.
|
||||
if: always() && (steps.lint_pr_title.outputs.error_message != null)
|
||||
with:
|
||||
header: pr-title-lint-error
|
||||
message: |
|
||||
Pull Request titles must follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) and have valid scopes.
|
||||
|
||||
${{ steps.lint_pr_title.outputs.error_message }}
|
||||
|
||||
```
|
||||
feat(ui): add Button component
|
||||
^ ^ ^
|
||||
| | |__ Subject
|
||||
| |_______ Scope
|
||||
|____________ Type
|
||||
```
|
||||
|
||||
# Delete a previous comment when the issue has been resolved
|
||||
- if: ${{ steps.lint_pr_title.outputs.error_message == null }}
|
||||
uses: marocchino/sticky-pull-request-comment@v2
|
||||
with:
|
||||
header: pr-title-lint-error
|
||||
delete: true
|
||||
@@ -1,9 +1,10 @@
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||
/* DO NOT MODIFY it because it could be re-written at any time. */
|
||||
import config from '@payload-config'
|
||||
import { REST_DELETE, REST_GET, REST_PATCH, REST_POST } from '@payloadcms/next/routes'
|
||||
import { REST_DELETE, REST_GET, REST_OPTIONS, REST_PATCH, REST_POST } from '@payloadcms/next/routes'
|
||||
|
||||
export const GET = REST_GET(config)
|
||||
export const POST = REST_POST(config)
|
||||
export const DELETE = REST_DELETE(config)
|
||||
export const PATCH = REST_PATCH(config)
|
||||
export const OPTIONS = REST_OPTIONS(config)
|
||||
|
||||
@@ -4,7 +4,7 @@ label: JSON
|
||||
order: 50
|
||||
desc: The JSON field type will store any string in the Database. Learn how to use JSON fields, see examples and options.
|
||||
|
||||
keywords: json, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
|
||||
keywords: json, jsonSchema, schema, validation, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
|
||||
---
|
||||
|
||||
<Banner>
|
||||
@@ -30,6 +30,7 @@ This field uses the `monaco-react` editor syntax highlighting.
|
||||
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
|
||||
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
|
||||
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
|
||||
| **`jsonSchema`** | Provide a JSON schema that will be used for validation. [JSON schemas](https://json-schema.org/learn/getting-started-step-by-step)
|
||||
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
|
||||
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
|
||||
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
|
||||
@@ -52,7 +53,7 @@ In addition to the default [field admin config](/docs/fields/overview#admin-conf
|
||||
|
||||
### Example
|
||||
|
||||
`collections/ExampleCollection.ts
|
||||
`collections/ExampleCollection.ts`
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types'
|
||||
@@ -68,3 +69,67 @@ export const ExampleCollection: CollectionConfig = {
|
||||
],
|
||||
}
|
||||
```
|
||||
### JSON Schema Validation
|
||||
|
||||
Payload JSON fields fully support the [JSON schema](https://json-schema.org/) standard. By providing a schema in your field config, the editor will be guided in the admin UI, getting typeahead for properties and their formats automatically. When the document is saved, the default validation will prevent saving any invalid data in the field according to the schema in your config.
|
||||
|
||||
If you only provide a URL to a schema, Payload will fetch the desired schema if it is publicly available. If not, it is recommended to add the schema directly to your config or import it from another file so that it can be implemented consistently in your project.
|
||||
|
||||
|
||||
#### Local JSON Schema
|
||||
|
||||
`collections/ExampleCollection.ts`
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types'
|
||||
|
||||
export const ExampleCollection: CollectionConfig = {
|
||||
slug: 'example-collection',
|
||||
fields: [
|
||||
{
|
||||
name: 'customerJSON', // required
|
||||
type: 'json', // required
|
||||
jsonSchema: {
|
||||
uri: 'a://b/foo.json', // required
|
||||
fileMatch: ['a://b/foo.json'], // required
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
foo: {
|
||||
enum: ['bar', 'foobar'],
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
},
|
||||
],
|
||||
}
|
||||
// {"foo": "bar"} or {"foo": "foobar"} - ok
|
||||
// Attempting to create {"foo": "not-bar"} will throw an error
|
||||
```
|
||||
|
||||
#### Remote JSON Schema
|
||||
|
||||
`collections/ExampleCollection.ts`
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types'
|
||||
|
||||
export const ExampleCollection: CollectionConfig = {
|
||||
slug: 'example-collection',
|
||||
fields: [
|
||||
{
|
||||
name: 'customerJSON', // required
|
||||
type: 'json', // required
|
||||
jsonSchema: {
|
||||
uri: 'https://example.com/customer.schema.json', // required
|
||||
fileMatch: ['https://example.com/customer.schema.json'], // required
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
// If 'https://example.com/customer.schema.json' has a JSON schema
|
||||
// {"foo": "bar"} or {"foo": "foobar"} - ok
|
||||
// Attempting to create {"foo": "not-bar"} will throw an error
|
||||
```
|
||||
|
||||
@@ -42,11 +42,12 @@ export const PublicUser: CollectionConfig = {
|
||||
|
||||
**Payload will automatically open up the following queries:**
|
||||
|
||||
| Query Name | Operation |
|
||||
| ------------------ | ------------------- |
|
||||
| **`PublicUser`** | `findByID` |
|
||||
| **`PublicUsers`** | `find` |
|
||||
| **`mePublicUser`** | `me` auth operation |
|
||||
| Query Name | Operation |
|
||||
| ------------------ | ------------------- |
|
||||
| **`PublicUser`** | `findByID` |
|
||||
| **`PublicUsers`** | `find` |
|
||||
| **`countPublicUsers`** | `count` |
|
||||
| **`mePublicUser`** | `me` auth operation |
|
||||
|
||||
**And the following mutations:**
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ keywords: live preview, frontend, react, next.js, vue, nuxt.js, svelte, hook, us
|
||||
|
||||
While using Live Preview, the Admin panel emits a new `window.postMessage` event every time a change is made to the document. Your front-end application can listen for these events and re-render accordingly.
|
||||
|
||||
Wiring your front-end into Live Preview is easy. If your front-end application is built with React or Next.js, use the [`useLivePreview`](#react) React hook that Payload provides. In the future, all other major frameworks like Vue, Svelte, etc will be officially supported. If you are using any of these frameworks today, you can still integrate with Live Preview yourself using the underlying tooling that Payload provides. See [building your own hook](#building-your-own-hook) for more information.
|
||||
Wiring your front-end into Live Preview is easy. If your front-end application is built with React, Next.js, Vue or Nuxt.js, use the `useLivePreview` hook that Payload provides. In the future, all other major frameworks like Svelte will be officially supported. If you are using any of these frameworks today, you can still integrate with Live Preview yourself using the underlying tooling that Payload provides. See [building your own hook](#building-your-own-hook) for more information.
|
||||
|
||||
By default, all hooks accept the following args:
|
||||
|
||||
@@ -36,6 +36,10 @@ And return the following values:
|
||||
For example, `data?.relatedPosts?.[0]?.title`.
|
||||
</Banner>
|
||||
|
||||
<Banner type="info">
|
||||
It is important that the `depth` argument matches exactly with the depth of your initial page request. The depth property is used to populated relationships and uploads beyond their IDs. See [Depth](../getting-started/concepts#depth) for more information.
|
||||
</Banner>
|
||||
|
||||
### React
|
||||
|
||||
If your front-end application is built with React or Next.js, you can use the `useLivePreview` hook that Payload provides.
|
||||
@@ -71,11 +75,40 @@ export const PageClient: React.FC<{
|
||||
}
|
||||
```
|
||||
|
||||
<Banner type="info">
|
||||
If is important that the `depth` argument matches exactly with the depth of your initial page
|
||||
request. The depth property is used to populated relationships and uploads beyond their IDs. See
|
||||
[Depth](../getting-started/concepts#depth) for more information.
|
||||
</Banner>
|
||||
### Vue
|
||||
|
||||
If your front-end application is built with Vue 3 or Nuxt 3, you can use the `useLivePreview` composable that Payload provides.
|
||||
|
||||
First, install the `@payloadcms/live-preview-vue` package:
|
||||
|
||||
```bash
|
||||
npm install @payloadcms/live-preview-vue
|
||||
```
|
||||
|
||||
Then, use the `useLivePreview` hook in your Vue component:
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import type { PageData } from '~/types';
|
||||
import { defineProps } from 'vue';
|
||||
import { useLivePreview } from '@payloadcms/live-preview-vue';
|
||||
|
||||
// Fetch the initial data on the parent component or using async state
|
||||
const props = defineProps<{ initialData: PageData }>();
|
||||
|
||||
// The hook will take over from here and keep the preview in sync with the changes you make.
|
||||
// The `data` property will contain the live data of the document only when viewed from the Preview view of the Admin UI.
|
||||
const { data } = useLivePreview<PageData>({
|
||||
initialData: props.initialData,
|
||||
serverURL: "<PAYLOAD_SERVER_URL>",
|
||||
depth: 2,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>{{ data.title }}</h1>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Building your own hook
|
||||
|
||||
|
||||
@@ -164,6 +164,22 @@ const result = await payload.findByID({
|
||||
})
|
||||
```
|
||||
|
||||
#### Count
|
||||
|
||||
```js
|
||||
// Result will be an object with:
|
||||
// {
|
||||
// totalDocs: 10, // count of the documents satisfies query
|
||||
// }
|
||||
const result = await payload.count({
|
||||
collection: 'posts', // required
|
||||
locale: 'en',
|
||||
where: {}, // pass a `where` query here
|
||||
user: dummyUser,
|
||||
overrideAccess: false,
|
||||
})
|
||||
```
|
||||
|
||||
#### Update by ID
|
||||
|
||||
```js
|
||||
|
||||
@@ -90,6 +90,19 @@ Note: Collection slugs must be formatted in kebab-case
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "Count",
|
||||
method: "GET",
|
||||
path: "/api/{collection-slug}/count",
|
||||
description: "Count the documents",
|
||||
example: {
|
||||
slug: "count",
|
||||
req: true,
|
||||
res: {
|
||||
totalDocs: 10
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "Create",
|
||||
method: "POST",
|
||||
|
||||
10
package.json
10
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload-monorepo",
|
||||
"version": "3.0.0-beta.11",
|
||||
"version": "3.0.0-beta.18",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"workspaces:": [
|
||||
@@ -18,6 +18,7 @@
|
||||
"build:create-payload-app": "turbo build --filter create-payload-app",
|
||||
"build:db-mongodb": "turbo build --filter db-mongodb",
|
||||
"build:db-postgres": "turbo build --filter db-postgres",
|
||||
"build:email-nodemailer": "turbo build --filter email-nodemailer",
|
||||
"build:eslint-config-payload": "turbo build --filter eslint-config-payload",
|
||||
"build:graphql": "turbo build --filter graphql",
|
||||
"build:live-preview": "turbo build --filter live-preview",
|
||||
@@ -35,7 +36,7 @@
|
||||
"build:plugin-stripe": "turbo build --filter plugin-stripe",
|
||||
"build:richtext-lexical": "turbo build --filter richtext-lexical",
|
||||
"build:richtext-slate": "turbo build --filter richtext-slate",
|
||||
"build:tests": "pnpm --filter test run typecheck",
|
||||
"build:tests": "pnpm --filter payload-test-suite run typecheck",
|
||||
"build:translations": "turbo build --filter translations",
|
||||
"build:ui": "turbo build --filter ui",
|
||||
"clean": "turbo clean",
|
||||
@@ -127,7 +128,7 @@
|
||||
"lint-staged": "^14.0.1",
|
||||
"minimist": "1.2.8",
|
||||
"mongodb-memory-server": "^9.0",
|
||||
"next": "^14.2.0-canary.23",
|
||||
"next": "^14.3.0-canary.7",
|
||||
"node-mocks-http": "^1.14.1",
|
||||
"nodemon": "3.0.3",
|
||||
"open": "^10.1.0",
|
||||
@@ -164,7 +165,7 @@
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.20.2",
|
||||
"pnpm": ">=8"
|
||||
"pnpm": "^8.15.7"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{md,mdx,yml,json}": "prettier --write",
|
||||
@@ -175,6 +176,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@sentry/react": "^7.77.0",
|
||||
"ajv": "^8.12.0",
|
||||
"passport-strategy": "1.0.0"
|
||||
},
|
||||
"pnpm": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-payload-app",
|
||||
"version": "3.0.0-beta.11",
|
||||
"version": "3.0.0-beta.18",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"homepage": "https://payloadcms.com",
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import fse from 'fs-extra'
|
||||
import globby from 'globby'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import path from 'path'
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
import type { DbDetails } from '../types.js'
|
||||
|
||||
@@ -15,6 +19,34 @@ export async function configurePayloadConfig(args: {
|
||||
return
|
||||
}
|
||||
|
||||
// Update package.json
|
||||
const packageJsonPath =
|
||||
'projectDir' in args.projectDirOrConfigPath &&
|
||||
path.resolve(args.projectDirOrConfigPath.projectDir, 'package.json')
|
||||
|
||||
if (packageJsonPath && fse.existsSync(packageJsonPath)) {
|
||||
try {
|
||||
const packageObj = await fse.readJson(packageJsonPath)
|
||||
|
||||
const dbPackage = dbReplacements[args.dbDetails.type]
|
||||
|
||||
// Delete all other db adapters
|
||||
Object.values(dbReplacements).forEach((p) => {
|
||||
if (p.packageName !== dbPackage.packageName) {
|
||||
delete packageObj.dependencies[p.packageName]
|
||||
}
|
||||
})
|
||||
|
||||
// Set version of db adapter to match payload version
|
||||
packageObj.dependencies[dbPackage.packageName] = packageObj.dependencies['payload']
|
||||
|
||||
await fse.writeJson(packageJsonPath, packageObj, { spaces: 2 })
|
||||
} catch (err: unknown) {
|
||||
warning(`Unable to configure Payload in package.json`)
|
||||
warning(err instanceof Error ? err.message : '')
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
let payloadConfigPath: string | undefined
|
||||
if (!('payloadConfigPath' in args.projectDirOrConfigPath)) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "3.0.0-beta.11",
|
||||
"version": "3.0.0-beta.18",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
49
packages/db-mongodb/src/count.ts
Normal file
49
packages/db-mongodb/src/count.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import type { QueryOptions } from 'mongoose'
|
||||
import type { Count } from 'payload/database'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
|
||||
import { flattenWhereToOperators } from 'payload/database'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
|
||||
import { withSession } from './withSession.js'
|
||||
|
||||
export const count: Count = async function count(
|
||||
this: MongooseAdapter,
|
||||
{ collection, locale, req = {} as PayloadRequestWithData, where },
|
||||
) {
|
||||
const Model = this.collections[collection]
|
||||
const options: QueryOptions = withSession(this, req.transactionID)
|
||||
|
||||
let hasNearConstraint = false
|
||||
|
||||
if (where) {
|
||||
const constraints = flattenWhereToOperators(where)
|
||||
hasNearConstraint = constraints.some((prop) => Object.keys(prop).some((key) => key === 'near'))
|
||||
}
|
||||
|
||||
const query = await Model.buildQuery({
|
||||
locale,
|
||||
payload: this.payload,
|
||||
where,
|
||||
})
|
||||
|
||||
// useEstimatedCount is faster, but not accurate, as it ignores any filters. It is thus set to true if there are no filters.
|
||||
const useEstimatedCount = hasNearConstraint || !query || Object.keys(query).length === 0
|
||||
|
||||
if (!useEstimatedCount && Object.keys(query).length === 0 && this.disableIndexHints !== true) {
|
||||
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
|
||||
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
|
||||
// which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses
|
||||
// the correct indexed field
|
||||
options.hint = {
|
||||
_id: 1,
|
||||
}
|
||||
}
|
||||
|
||||
const result = await Model.countDocuments(query, options)
|
||||
|
||||
return {
|
||||
totalDocs: result,
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Create } from 'payload/database'
|
||||
import type { Document, PayloadRequest } from 'payload/types'
|
||||
import type { Document, PayloadRequestWithData } from 'payload/types'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
|
||||
@@ -8,7 +8,7 @@ import { withSession } from './withSession.js'
|
||||
|
||||
export const create: Create = async function create(
|
||||
this: MongooseAdapter,
|
||||
{ collection, data, req = {} as PayloadRequest },
|
||||
{ collection, data, req = {} as PayloadRequestWithData },
|
||||
) {
|
||||
const Model = this.collections[collection]
|
||||
const options = withSession(this, req.transactionID)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { CreateGlobal } from 'payload/database'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
|
||||
@@ -8,7 +8,7 @@ import { withSession } from './withSession.js'
|
||||
|
||||
export const createGlobal: CreateGlobal = async function createGlobal(
|
||||
this: MongooseAdapter,
|
||||
{ slug, data, req = {} as PayloadRequest },
|
||||
{ slug, data, req = {} as PayloadRequestWithData },
|
||||
) {
|
||||
const Model = this.globals
|
||||
const global = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { CreateGlobalVersion } from 'payload/database'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
import type { Document } from 'payload/types'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
@@ -8,7 +8,15 @@ import { withSession } from './withSession.js'
|
||||
|
||||
export const createGlobalVersion: CreateGlobalVersion = async function createGlobalVersion(
|
||||
this: MongooseAdapter,
|
||||
{ autosave, createdAt, globalSlug, parent, req = {} as PayloadRequest, updatedAt, versionData },
|
||||
{
|
||||
autosave,
|
||||
createdAt,
|
||||
globalSlug,
|
||||
parent,
|
||||
req = {} as PayloadRequestWithData,
|
||||
updatedAt,
|
||||
versionData,
|
||||
},
|
||||
) {
|
||||
const VersionModel = this.versions[globalSlug]
|
||||
const options = withSession(this, req.transactionID)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { CreateVersion } from 'payload/database'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
import type { Document } from 'payload/types'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
@@ -13,7 +13,7 @@ export const createVersion: CreateVersion = async function createVersion(
|
||||
collectionSlug,
|
||||
createdAt,
|
||||
parent,
|
||||
req = {} as PayloadRequest,
|
||||
req = {} as PayloadRequestWithData,
|
||||
updatedAt,
|
||||
versionData,
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { DeleteMany } from 'payload/database'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
|
||||
@@ -7,7 +7,7 @@ import { withSession } from './withSession.js'
|
||||
|
||||
export const deleteMany: DeleteMany = async function deleteMany(
|
||||
this: MongooseAdapter,
|
||||
{ collection, req = {} as PayloadRequest, where },
|
||||
{ collection, req = {} as PayloadRequestWithData, where },
|
||||
) {
|
||||
const Model = this.collections[collection]
|
||||
const options = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { DeleteOne } from 'payload/database'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
import type { Document } from 'payload/types'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
@@ -9,7 +9,7 @@ import { withSession } from './withSession.js'
|
||||
|
||||
export const deleteOne: DeleteOne = async function deleteOne(
|
||||
this: MongooseAdapter,
|
||||
{ collection, req = {} as PayloadRequest, where },
|
||||
{ collection, req = {} as PayloadRequestWithData, where },
|
||||
) {
|
||||
const Model = this.collections[collection]
|
||||
const options = withSession(this, req.transactionID)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { DeleteVersions } from 'payload/database'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
|
||||
@@ -7,7 +7,7 @@ import { withSession } from './withSession.js'
|
||||
|
||||
export const deleteVersions: DeleteVersions = async function deleteVersions(
|
||||
this: MongooseAdapter,
|
||||
{ collection, locale, req = {} as PayloadRequest, where },
|
||||
{ collection, locale, req = {} as PayloadRequestWithData, where },
|
||||
) {
|
||||
const VersionsModel = this.versions[collection]
|
||||
const options = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { PaginateOptions } from 'mongoose'
|
||||
import type { Find } from 'payload/database'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
|
||||
import { flattenWhereToOperators } from 'payload/database'
|
||||
|
||||
@@ -12,7 +12,16 @@ import { withSession } from './withSession.js'
|
||||
|
||||
export const find: Find = async function find(
|
||||
this: MongooseAdapter,
|
||||
{ collection, limit, locale, page, pagination, req = {} as PayloadRequest, sort: sortArg, where },
|
||||
{
|
||||
collection,
|
||||
limit,
|
||||
locale,
|
||||
page,
|
||||
pagination,
|
||||
req = {} as PayloadRequestWithData,
|
||||
sort: sortArg,
|
||||
where,
|
||||
},
|
||||
) {
|
||||
const Model = this.collections[collection]
|
||||
const collectionConfig = this.payload.collections[collection].config
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { FindGlobal } from 'payload/database'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
|
||||
import { combineQueries } from 'payload/database'
|
||||
|
||||
@@ -10,7 +10,7 @@ import { withSession } from './withSession.js'
|
||||
|
||||
export const findGlobal: FindGlobal = async function findGlobal(
|
||||
this: MongooseAdapter,
|
||||
{ slug, locale, req = {} as PayloadRequest, where },
|
||||
{ slug, locale, req = {} as PayloadRequestWithData, where },
|
||||
) {
|
||||
const Model = this.globals
|
||||
const options = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { PaginateOptions } from 'mongoose'
|
||||
import type { FindGlobalVersions } from 'payload/database'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
|
||||
import { flattenWhereToOperators } from 'payload/database'
|
||||
import { buildVersionGlobalFields } from 'payload/versions'
|
||||
@@ -19,7 +19,7 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV
|
||||
locale,
|
||||
page,
|
||||
pagination,
|
||||
req = {} as PayloadRequest,
|
||||
req = {} as PayloadRequestWithData,
|
||||
skip,
|
||||
sort: sortArg,
|
||||
where,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { MongooseQueryOptions } from 'mongoose'
|
||||
import type { FindOne } from 'payload/database'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
import type { Document } from 'payload/types'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
@@ -10,7 +10,7 @@ import { withSession } from './withSession.js'
|
||||
|
||||
export const findOne: FindOne = async function findOne(
|
||||
this: MongooseAdapter,
|
||||
{ collection, locale, req = {} as PayloadRequest, where },
|
||||
{ collection, locale, req = {} as PayloadRequestWithData, where },
|
||||
) {
|
||||
const Model = this.collections[collection]
|
||||
const options: MongooseQueryOptions = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { PaginateOptions } from 'mongoose'
|
||||
import type { FindVersions } from 'payload/database'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
|
||||
import { flattenWhereToOperators } from 'payload/database'
|
||||
|
||||
@@ -18,7 +18,7 @@ export const findVersions: FindVersions = async function findVersions(
|
||||
locale,
|
||||
page,
|
||||
pagination,
|
||||
req = {} as PayloadRequest,
|
||||
req = {} as PayloadRequestWithData,
|
||||
skip,
|
||||
sort: sortArg,
|
||||
where,
|
||||
|
||||
@@ -12,6 +12,7 @@ import { createDatabaseAdapter } from 'payload/database'
|
||||
import type { CollectionModel, GlobalModel } from './types.js'
|
||||
|
||||
import { connect } from './connect.js'
|
||||
import { count } from './count.js'
|
||||
import { create } from './create.js'
|
||||
import { createGlobal } from './createGlobal.js'
|
||||
import { createGlobalVersion } from './createGlobalVersion.js'
|
||||
@@ -112,6 +113,7 @@ export function mongooseAdapter({
|
||||
collections: {},
|
||||
connectOptions: connectOptions || {},
|
||||
connection: undefined,
|
||||
count,
|
||||
disableIndexHints,
|
||||
globals: undefined,
|
||||
mongoMemoryServer,
|
||||
@@ -119,7 +121,6 @@ export function mongooseAdapter({
|
||||
transactionOptions: transactionOptions === false ? undefined : transactionOptions,
|
||||
url,
|
||||
versions: {},
|
||||
|
||||
// DatabaseAdapter
|
||||
beginTransaction: transactionOptions ? beginTransaction : undefined,
|
||||
commitTransaction,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
|
||||
import {
|
||||
commitTransaction,
|
||||
@@ -50,7 +50,7 @@ export async function migrateFresh(
|
||||
msg: `Found ${migrationFiles.length} migration files.`,
|
||||
})
|
||||
|
||||
const req = { payload } as PayloadRequest
|
||||
const req = { payload } as PayloadRequestWithData
|
||||
|
||||
// Run all migrate up
|
||||
for (const migration of migrationFiles) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { PaginateOptions } from 'mongoose'
|
||||
import type { QueryDrafts } from 'payload/database'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
|
||||
import { combineQueries, flattenWhereToOperators } from 'payload/database'
|
||||
|
||||
@@ -12,7 +12,16 @@ import { withSession } from './withSession.js'
|
||||
|
||||
export const queryDrafts: QueryDrafts = async function queryDrafts(
|
||||
this: MongooseAdapter,
|
||||
{ collection, limit, locale, page, pagination, req = {} as PayloadRequest, sort: sortArg, where },
|
||||
{
|
||||
collection,
|
||||
limit,
|
||||
locale,
|
||||
page,
|
||||
pagination,
|
||||
req = {} as PayloadRequestWithData,
|
||||
sort: sortArg,
|
||||
where,
|
||||
},
|
||||
) {
|
||||
const VersionModel = this.versions[collection]
|
||||
const collectionConfig = this.payload.collections[collection].config
|
||||
|
||||
@@ -6,6 +6,10 @@ export const commitTransaction: CommitTransaction = async function commitTransac
|
||||
}
|
||||
|
||||
await this.sessions[id].commitTransaction()
|
||||
await this.sessions[id].endSession()
|
||||
try {
|
||||
await this.sessions[id].endSession()
|
||||
} catch (error) {
|
||||
// ending sessions is only best effort and won't impact anything if it fails since the transaction was committed
|
||||
}
|
||||
delete this.sessions[id]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { UpdateGlobal } from 'payload/database'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
|
||||
@@ -8,7 +8,7 @@ import { withSession } from './withSession.js'
|
||||
|
||||
export const updateGlobal: UpdateGlobal = async function updateGlobal(
|
||||
this: MongooseAdapter,
|
||||
{ slug, data, req = {} as PayloadRequest },
|
||||
{ slug, data, req = {} as PayloadRequestWithData },
|
||||
) {
|
||||
const Model = this.globals
|
||||
const options = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { UpdateGlobalVersionArgs } from 'payload/database'
|
||||
import type { PayloadRequest, TypeWithID } from 'payload/types'
|
||||
import type { PayloadRequestWithData, TypeWithID } from 'payload/types'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
|
||||
@@ -11,7 +11,7 @@ export async function updateGlobalVersion<T extends TypeWithID>(
|
||||
id,
|
||||
global,
|
||||
locale,
|
||||
req = {} as PayloadRequest,
|
||||
req = {} as PayloadRequestWithData,
|
||||
versionData,
|
||||
where,
|
||||
}: UpdateGlobalVersionArgs<T>,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { UpdateOne } from 'payload/database'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
|
||||
@@ -9,7 +9,7 @@ import { withSession } from './withSession.js'
|
||||
|
||||
export const updateOne: UpdateOne = async function updateOne(
|
||||
this: MongooseAdapter,
|
||||
{ id, collection, data, locale, req = {} as PayloadRequest, where: whereArg },
|
||||
{ id, collection, data, locale, req = {} as PayloadRequestWithData, where: whereArg },
|
||||
) {
|
||||
const where = id ? { id: { equals: id } } : whereArg
|
||||
const Model = this.collections[collection]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { UpdateVersion } from 'payload/database'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
|
||||
@@ -7,7 +7,7 @@ import { withSession } from './withSession.js'
|
||||
|
||||
export const updateVersion: UpdateVersion = async function updateVersion(
|
||||
this: MongooseAdapter,
|
||||
{ id, collection, locale, req = {} as PayloadRequest, versionData, where },
|
||||
{ id, collection, locale, req = {} as PayloadRequestWithData, versionData, where },
|
||||
) {
|
||||
const VersionModel = this.versions[collection]
|
||||
const whereToUse = where || { id: { equals: id } }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-postgres",
|
||||
"version": "3.0.0-beta.11",
|
||||
"version": "3.0.0-beta.18",
|
||||
"description": "The officially supported Postgres database adapter for Payload",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
62
packages/db-postgres/src/count.ts
Normal file
62
packages/db-postgres/src/count.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import type { Count } from 'payload/database'
|
||||
import type { SanitizedCollectionConfig } from 'payload/types'
|
||||
|
||||
import { sql } from 'drizzle-orm'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { ChainedMethods } from './find/chainMethods.js'
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { chainMethods } from './find/chainMethods.js'
|
||||
import buildQuery from './queries/buildQuery.js'
|
||||
|
||||
export const count: Count = async function count(
|
||||
this: PostgresAdapter,
|
||||
{ collection, locale, req, where: whereArg },
|
||||
) {
|
||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||
|
||||
const tableName = this.tableNameMap.get(toSnakeCase(collectionConfig.slug))
|
||||
|
||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||
const table = this.tables[tableName]
|
||||
|
||||
const { joinAliases, joins, where } = await buildQuery({
|
||||
adapter: this,
|
||||
fields: collectionConfig.fields,
|
||||
locale,
|
||||
tableName,
|
||||
where: whereArg,
|
||||
})
|
||||
|
||||
const selectCountMethods: ChainedMethods = []
|
||||
|
||||
joinAliases.forEach(({ condition, table }) => {
|
||||
selectCountMethods.push({
|
||||
args: [table, condition],
|
||||
method: 'leftJoin',
|
||||
})
|
||||
})
|
||||
|
||||
Object.entries(joins).forEach(([joinTable, condition]) => {
|
||||
if (joinTable) {
|
||||
selectCountMethods.push({
|
||||
args: [this.tables[joinTable], condition],
|
||||
method: 'leftJoin',
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const countResult = await chainMethods({
|
||||
methods: selectCountMethods,
|
||||
query: db
|
||||
.select({
|
||||
count: sql<number>`count
|
||||
(DISTINCT ${this.tables[tableName].id})`,
|
||||
})
|
||||
.from(table)
|
||||
.where(where),
|
||||
})
|
||||
|
||||
return { totalDocs: Number(countResult[0].count) }
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { Create } from 'payload/database'
|
||||
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
import { upsertRow } from './upsertRow/index.js'
|
||||
|
||||
export const create: Create = async function create(
|
||||
@@ -12,6 +13,8 @@ export const create: Create = async function create(
|
||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||
const collection = this.payload.collections[collectionSlug].config
|
||||
|
||||
const tableName = this.tableNameMap.get(toSnakeCase(collection.slug))
|
||||
|
||||
const result = await upsertRow({
|
||||
adapter: this,
|
||||
data,
|
||||
@@ -19,10 +22,7 @@ export const create: Create = async function create(
|
||||
fields: collection.fields,
|
||||
operation: 'create',
|
||||
req,
|
||||
tableName: getTableName({
|
||||
adapter: this,
|
||||
config: collection,
|
||||
}),
|
||||
tableName,
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import type { CreateGlobalArgs } from 'payload/database'
|
||||
import type { PayloadRequest, TypeWithID } from 'payload/types'
|
||||
import type { PayloadRequestWithData, TypeWithID } from 'payload/types'
|
||||
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
import { upsertRow } from './upsertRow/index.js'
|
||||
|
||||
export async function createGlobal<T extends TypeWithID>(
|
||||
this: PostgresAdapter,
|
||||
{ slug, data, req = {} as PayloadRequest }: CreateGlobalArgs,
|
||||
{ slug, data, req = {} as PayloadRequestWithData }: CreateGlobalArgs,
|
||||
): Promise<T> {
|
||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug)
|
||||
|
||||
const tableName = this.tableNameMap.get(toSnakeCase(globalConfig.slug))
|
||||
|
||||
const result = await upsertRow<T>({
|
||||
adapter: this,
|
||||
data,
|
||||
@@ -20,10 +23,7 @@ export async function createGlobal<T extends TypeWithID>(
|
||||
fields: globalConfig.fields,
|
||||
operation: 'create',
|
||||
req,
|
||||
tableName: getTableName({
|
||||
adapter: this,
|
||||
config: globalConfig,
|
||||
}),
|
||||
tableName,
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
@@ -1,26 +1,28 @@
|
||||
import type { TypeWithVersion } from 'payload/database'
|
||||
import type { PayloadRequest, TypeWithID } from 'payload/types'
|
||||
import type { PayloadRequestWithData, TypeWithID } from 'payload/types'
|
||||
|
||||
import { sql } from 'drizzle-orm'
|
||||
import { type CreateGlobalVersionArgs } from 'payload/database'
|
||||
import { buildVersionGlobalFields } from 'payload/versions'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
import { upsertRow } from './upsertRow/index.js'
|
||||
|
||||
export async function createGlobalVersion<T extends TypeWithID>(
|
||||
this: PostgresAdapter,
|
||||
{ autosave, globalSlug, req = {} as PayloadRequest, versionData }: CreateGlobalVersionArgs,
|
||||
{
|
||||
autosave,
|
||||
globalSlug,
|
||||
req = {} as PayloadRequestWithData,
|
||||
versionData,
|
||||
}: CreateGlobalVersionArgs,
|
||||
) {
|
||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||
const global = this.payload.globals.config.find(({ slug }) => slug === globalSlug)
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: global,
|
||||
versions: true,
|
||||
})
|
||||
|
||||
const tableName = this.tableNameMap.get(`_${toSnakeCase(global.slug)}${this.versionsSuffix}`)
|
||||
|
||||
const result = await upsertRow<TypeWithVersion<T>>({
|
||||
adapter: this,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { CreateVersionArgs, TypeWithVersion } from 'payload/database'
|
||||
import type { PayloadRequest, TypeWithID } from 'payload/types'
|
||||
import type { PayloadRequestWithData, TypeWithID } from 'payload/types'
|
||||
|
||||
import { sql } from 'drizzle-orm'
|
||||
import { buildVersionCollectionFields } from 'payload/versions'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
import { upsertRow } from './upsertRow/index.js'
|
||||
|
||||
export async function createVersion<T extends TypeWithID>(
|
||||
@@ -15,17 +15,18 @@ export async function createVersion<T extends TypeWithID>(
|
||||
autosave,
|
||||
collectionSlug,
|
||||
parent,
|
||||
req = {} as PayloadRequest,
|
||||
req = {} as PayloadRequestWithData,
|
||||
versionData,
|
||||
}: CreateVersionArgs<T>,
|
||||
) {
|
||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||
const collection = this.payload.collections[collectionSlug].config
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: collection,
|
||||
versions: true,
|
||||
})
|
||||
const defaultTableName = toSnakeCase(collection.slug)
|
||||
|
||||
const tableName = this.tableNameMap.get(`_${defaultTableName}${this.versionsSuffix}`)
|
||||
|
||||
const version = { ...versionData }
|
||||
if (version.id) delete version.id
|
||||
|
||||
const result = await upsertRow<TypeWithVersion<T>>({
|
||||
adapter: this,
|
||||
@@ -33,7 +34,7 @@ export async function createVersion<T extends TypeWithID>(
|
||||
autosave,
|
||||
latest: true,
|
||||
parent,
|
||||
version: versionData,
|
||||
version,
|
||||
},
|
||||
db,
|
||||
fields: buildVersionCollectionFields(collection),
|
||||
@@ -43,15 +44,9 @@ export async function createVersion<T extends TypeWithID>(
|
||||
})
|
||||
|
||||
const table = this.tables[tableName]
|
||||
|
||||
const relationshipsTable =
|
||||
this.tables[
|
||||
getTableName({
|
||||
adapter: this,
|
||||
config: collection,
|
||||
relationships: true,
|
||||
versions: true,
|
||||
})
|
||||
]
|
||||
this.tables[`_${defaultTableName}${this.versionsSuffix}${this.relationshipsSuffix}`]
|
||||
|
||||
if (collection.versions.drafts) {
|
||||
await db.execute(sql`
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import type { DeleteMany } from 'payload/database'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
|
||||
import { inArray } from 'drizzle-orm'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { findMany } from './find/findMany.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
|
||||
export const deleteMany: DeleteMany = async function deleteMany(
|
||||
this: PostgresAdapter,
|
||||
{ collection, req = {} as PayloadRequest, where },
|
||||
{ collection, req = {} as PayloadRequestWithData, where },
|
||||
) {
|
||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||
const collectionConfig = this.payload.collections[collection].config
|
||||
const tableName = getTableName({ adapter: this, config: collectionConfig })
|
||||
|
||||
const tableName = this.tableNameMap.get(toSnakeCase(collectionConfig.slug))
|
||||
|
||||
const result = await findMany({
|
||||
adapter: this,
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
import type { DeleteOne } from 'payload/database'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
|
||||
import { eq } from 'drizzle-orm'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { buildFindManyArgs } from './find/buildFindManyArgs.js'
|
||||
import buildQuery from './queries/buildQuery.js'
|
||||
import { selectDistinct } from './queries/selectDistinct.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
import { transform } from './transform/read/index.js'
|
||||
|
||||
export const deleteOne: DeleteOne = async function deleteOne(
|
||||
this: PostgresAdapter,
|
||||
{ collection: collectionSlug, req = {} as PayloadRequest, where: whereArg },
|
||||
{ collection: collectionSlug, req = {} as PayloadRequestWithData, where: whereArg },
|
||||
) {
|
||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||
const collection = this.payload.collections[collectionSlug].config
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: collection,
|
||||
})
|
||||
|
||||
const tableName = this.tableNameMap.get(toSnakeCase(collection.slug))
|
||||
|
||||
let docToDelete: Record<string, unknown>
|
||||
|
||||
const { joinAliases, joins, selectFields, where } = await buildQuery({
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
import type { DeleteVersions } from 'payload/database'
|
||||
import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types'
|
||||
import type { PayloadRequestWithData, SanitizedCollectionConfig } from 'payload/types'
|
||||
|
||||
import { inArray } from 'drizzle-orm'
|
||||
import { buildVersionCollectionFields } from 'payload/versions'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { findMany } from './find/findMany.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
|
||||
export const deleteVersions: DeleteVersions = async function deleteVersion(
|
||||
this: PostgresAdapter,
|
||||
{ collection, locale, req = {} as PayloadRequest, where: where },
|
||||
{ collection, locale, req = {} as PayloadRequestWithData, where: where },
|
||||
) {
|
||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: collectionConfig,
|
||||
versions: true,
|
||||
})
|
||||
const tableName = this.tableNameMap.get(
|
||||
`_${toSnakeCase(collectionConfig.slug)}${this.versionsSuffix}`,
|
||||
)
|
||||
|
||||
const fields = buildVersionCollectionFields(collectionConfig)
|
||||
|
||||
const { docs } = await findMany({
|
||||
|
||||
@@ -2,13 +2,12 @@ import type { Destroy } from 'payload/database'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { pushDevSchema } from './utilities/pushDevSchema.js'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
export const destroy: Destroy = async function destroy(this: PostgresAdapter) {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
await pushDevSchema(this)
|
||||
} else {
|
||||
// TODO: this hangs test suite for some reason
|
||||
// await this.pool.end()
|
||||
}
|
||||
this.enums = {}
|
||||
this.schema = {}
|
||||
this.tables = {}
|
||||
this.relations = {}
|
||||
this.fieldConstraints = {}
|
||||
this.drizzle = undefined
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import type { Find } from 'payload/database'
|
||||
import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types'
|
||||
import type { PayloadRequestWithData, SanitizedCollectionConfig } from 'payload/types'
|
||||
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { findMany } from './find/findMany.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
|
||||
export const find: Find = async function find(
|
||||
this: PostgresAdapter,
|
||||
@@ -14,17 +15,15 @@ export const find: Find = async function find(
|
||||
locale,
|
||||
page = 1,
|
||||
pagination,
|
||||
req = {} as PayloadRequest,
|
||||
req = {} as PayloadRequestWithData,
|
||||
sort: sortArg,
|
||||
where,
|
||||
},
|
||||
) {
|
||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||
const sort = typeof sortArg === 'string' ? sortArg : collectionConfig.defaultSort
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: collectionConfig,
|
||||
})
|
||||
|
||||
const tableName = this.tableNameMap.get(toSnakeCase(collectionConfig.slug))
|
||||
|
||||
return findMany({
|
||||
adapter: this,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { FindArgs } from 'payload/database'
|
||||
import type { Field, PayloadRequest, TypeWithID } from 'payload/types'
|
||||
import type { Field, PayloadRequestWithData, TypeWithID } from 'payload/types'
|
||||
|
||||
import { inArray, sql } from 'drizzle-orm'
|
||||
|
||||
@@ -25,7 +25,7 @@ export const findMany = async function find({
|
||||
locale,
|
||||
page = 1,
|
||||
pagination,
|
||||
req = {} as PayloadRequest,
|
||||
req = {} as PayloadRequestWithData,
|
||||
skip,
|
||||
sort,
|
||||
tableName,
|
||||
|
||||
@@ -2,12 +2,11 @@
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
import { fieldAffectsData, tabHasName } from 'payload/types'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from '../types.js'
|
||||
import type { Result } from './buildFindManyArgs.js'
|
||||
|
||||
import { getTableName } from '../schema/getTableName.js'
|
||||
|
||||
type TraverseFieldArgs = {
|
||||
_locales: Record<string, unknown>
|
||||
adapter: PostgresAdapter
|
||||
@@ -79,20 +78,11 @@ export const traverseFields = ({
|
||||
with: {},
|
||||
}
|
||||
|
||||
const arrayTableName = getTableName({
|
||||
adapter,
|
||||
config: field,
|
||||
parentTableName: currentTableName,
|
||||
prefix: `${currentTableName}_${path}`,
|
||||
})
|
||||
const arrayTableName = adapter.tableNameMap.get(
|
||||
`${currentTableName}_${path}${toSnakeCase(field.name)}`,
|
||||
)
|
||||
|
||||
const arrayTableNameWithLocales = getTableName({
|
||||
adapter,
|
||||
config: field,
|
||||
locales: true,
|
||||
parentTableName: currentTableName,
|
||||
prefix: `${currentTableName}_${path}`,
|
||||
})
|
||||
const arrayTableNameWithLocales = `${arrayTableName}${adapter.localesSuffix}`
|
||||
|
||||
if (adapter.tables[arrayTableNameWithLocales]) withArray.with._locales = _locales
|
||||
currentArgs.with[`${path}${field.name}`] = withArray
|
||||
@@ -142,12 +132,9 @@ export const traverseFields = ({
|
||||
with: {},
|
||||
}
|
||||
|
||||
const tableName = getTableName({
|
||||
adapter,
|
||||
config: block,
|
||||
parentTableName: topLevelTableName,
|
||||
prefix: `${topLevelTableName}_blocks_`,
|
||||
})
|
||||
const tableName = adapter.tableNameMap.get(
|
||||
`${topLevelTableName}_blocks_${toSnakeCase(block.slug)}`,
|
||||
)
|
||||
|
||||
if (adapter.tables[`${tableName}${adapter.localesSuffix}`]) {
|
||||
withBlock.with._locales = _locales
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
import type { FindGlobal } from 'payload/database'
|
||||
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { findMany } from './find/findMany.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
|
||||
export const findGlobal: FindGlobal = async function findGlobal(
|
||||
this: PostgresAdapter,
|
||||
{ slug, locale, req, where },
|
||||
) {
|
||||
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug)
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: globalConfig,
|
||||
})
|
||||
|
||||
const tableName = this.tableNameMap.get(toSnakeCase(globalConfig.slug))
|
||||
|
||||
const {
|
||||
docs: [doc],
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { FindGlobalVersions } from 'payload/database'
|
||||
import type { PayloadRequest, SanitizedGlobalConfig } from 'payload/types'
|
||||
import type { PayloadRequestWithData, SanitizedGlobalConfig } from 'payload/types'
|
||||
|
||||
import { buildVersionGlobalFields } from 'payload/versions'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { findMany } from './find/findMany.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
|
||||
export const findGlobalVersions: FindGlobalVersions = async function findGlobalVersions(
|
||||
this: PostgresAdapter,
|
||||
@@ -16,7 +16,7 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV
|
||||
locale,
|
||||
page,
|
||||
pagination,
|
||||
req = {} as PayloadRequest,
|
||||
req = {} as PayloadRequestWithData,
|
||||
skip,
|
||||
sort: sortArg,
|
||||
where,
|
||||
@@ -27,11 +27,10 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV
|
||||
)
|
||||
const sort = typeof sortArg === 'string' ? sortArg : '-createdAt'
|
||||
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: globalConfig,
|
||||
versions: true,
|
||||
})
|
||||
const tableName = this.tableNameMap.get(
|
||||
`_${toSnakeCase(globalConfig.slug)}${this.versionsSuffix}`,
|
||||
)
|
||||
|
||||
const fields = buildVersionGlobalFields(globalConfig)
|
||||
|
||||
return findMany({
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
import type { FindOneArgs } from 'payload/database'
|
||||
import type { PayloadRequest, SanitizedCollectionConfig, TypeWithID } from 'payload/types'
|
||||
import type { PayloadRequestWithData, SanitizedCollectionConfig, TypeWithID } from 'payload/types'
|
||||
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { findMany } from './find/findMany.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
|
||||
export async function findOne<T extends TypeWithID>(
|
||||
this: PostgresAdapter,
|
||||
{ collection, locale, req = {} as PayloadRequest, where }: FindOneArgs,
|
||||
{ collection, locale, req = {} as PayloadRequestWithData, where }: FindOneArgs,
|
||||
): Promise<T> {
|
||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: collectionConfig,
|
||||
})
|
||||
|
||||
const tableName = this.tableNameMap.get(toSnakeCase(collectionConfig.slug))
|
||||
|
||||
const { docs } = await findMany({
|
||||
adapter: this,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { FindVersions } from 'payload/database'
|
||||
import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types'
|
||||
import type { PayloadRequestWithData, SanitizedCollectionConfig } from 'payload/types'
|
||||
|
||||
import { buildVersionCollectionFields } from 'payload/versions'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { findMany } from './find/findMany.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
|
||||
export const findVersions: FindVersions = async function findVersions(
|
||||
this: PostgresAdapter,
|
||||
@@ -16,7 +16,7 @@ export const findVersions: FindVersions = async function findVersions(
|
||||
locale,
|
||||
page,
|
||||
pagination,
|
||||
req = {} as PayloadRequest,
|
||||
req = {} as PayloadRequestWithData,
|
||||
skip,
|
||||
sort: sortArg,
|
||||
where,
|
||||
@@ -25,11 +25,10 @@ export const findVersions: FindVersions = async function findVersions(
|
||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||
const sort = typeof sortArg === 'string' ? sortArg : collectionConfig.defaultSort
|
||||
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: collectionConfig,
|
||||
versions: true,
|
||||
})
|
||||
const tableName = this.tableNameMap.get(
|
||||
`_${toSnakeCase(collectionConfig.slug)}${this.versionsSuffix}`,
|
||||
)
|
||||
|
||||
const fields = buildVersionCollectionFields(collectionConfig)
|
||||
|
||||
return findMany({
|
||||
|
||||
@@ -8,6 +8,7 @@ import { createDatabaseAdapter } from 'payload/database'
|
||||
import type { Args, PostgresAdapter } from './types.js'
|
||||
|
||||
import { connect } from './connect.js'
|
||||
import { count } from './count.js'
|
||||
import { create } from './create.js'
|
||||
import { createGlobal } from './createGlobal.js'
|
||||
import { createGlobalVersion } from './createGlobalVersion.js'
|
||||
@@ -44,16 +45,13 @@ export { sql } from 'drizzle-orm'
|
||||
|
||||
export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter> {
|
||||
const postgresIDType = args.idType || 'serial'
|
||||
const payloadIDType = postgresIDType ? 'number' : 'text'
|
||||
const payloadIDType = postgresIDType === 'serial' ? 'number' : 'text'
|
||||
|
||||
function adapter({ payload }: { payload: Payload }) {
|
||||
const migrationDir = findMigrationDir(args.migrationDir)
|
||||
|
||||
return createDatabaseAdapter<PostgresAdapter>({
|
||||
name: 'postgres',
|
||||
|
||||
// Postgres-specific
|
||||
blockTableNames: {},
|
||||
drizzle: undefined,
|
||||
enums: {},
|
||||
fieldConstraints: {},
|
||||
@@ -69,6 +67,7 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
|
||||
schema: {},
|
||||
schemaName: args.schemaName,
|
||||
sessions: {},
|
||||
tableNameMap: new Map<string, string>(),
|
||||
tables: {},
|
||||
versionsSuffix: args.versionsSuffix || '_v',
|
||||
|
||||
@@ -76,6 +75,7 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
|
||||
beginTransaction,
|
||||
commitTransaction,
|
||||
connect,
|
||||
count,
|
||||
create,
|
||||
createGlobal,
|
||||
createGlobalVersion,
|
||||
|
||||
@@ -8,7 +8,7 @@ import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload/
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { buildTable } from './schema/build.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
import { createTableName } from './schema/createTableName.js'
|
||||
|
||||
export const init: Init = function init(this: PostgresAdapter) {
|
||||
if (this.schemaName) {
|
||||
@@ -25,7 +25,7 @@ export const init: Init = function init(this: PostgresAdapter) {
|
||||
}
|
||||
|
||||
this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => {
|
||||
const tableName = getTableName({
|
||||
const tableName = createTableName({
|
||||
adapter: this,
|
||||
config: collection,
|
||||
})
|
||||
@@ -44,10 +44,11 @@ export const init: Init = function init(this: PostgresAdapter) {
|
||||
})
|
||||
|
||||
if (collection.versions) {
|
||||
const versionsTableName = getTableName({
|
||||
const versionsTableName = createTableName({
|
||||
adapter: this,
|
||||
config: collection,
|
||||
versions: true,
|
||||
versionsCustomName: true,
|
||||
})
|
||||
const versionFields = buildVersionCollectionFields(collection)
|
||||
|
||||
@@ -67,7 +68,7 @@ export const init: Init = function init(this: PostgresAdapter) {
|
||||
})
|
||||
|
||||
this.payload.config.globals.forEach((global) => {
|
||||
const tableName = getTableName({ adapter: this, config: global })
|
||||
const tableName = createTableName({ adapter: this, config: global })
|
||||
|
||||
buildTable({
|
||||
adapter: this,
|
||||
@@ -83,7 +84,12 @@ export const init: Init = function init(this: PostgresAdapter) {
|
||||
})
|
||||
|
||||
if (global.versions) {
|
||||
const versionsTableName = getTableName({ adapter: this, config: global, versions: true })
|
||||
const versionsTableName = createTableName({
|
||||
adapter: this,
|
||||
config: global,
|
||||
versions: true,
|
||||
versionsCustomName: true,
|
||||
})
|
||||
const versionFields = buildVersionGlobalFields(global)
|
||||
|
||||
buildTable({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable no-restricted-syntax, no-await-in-loop */
|
||||
import type { Payload } from 'payload'
|
||||
import type { Migration } from 'payload/database'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
|
||||
import { createRequire } from 'module'
|
||||
import {
|
||||
@@ -88,7 +88,7 @@ async function runMigrationFile(payload: Payload, migration: Migration, batch: n
|
||||
const { generateDrizzleJson } = require('drizzle-kit/payload')
|
||||
|
||||
const start = Date.now()
|
||||
const req = { payload } as PayloadRequest
|
||||
const req = { payload } as PayloadRequestWithData
|
||||
|
||||
payload.logger.info({ msg: `Migrating: ${migration.name}` })
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable no-restricted-syntax, no-await-in-loop */
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
|
||||
import {
|
||||
commitTransaction,
|
||||
@@ -40,7 +40,7 @@ export async function migrateDown(this: PostgresAdapter): Promise<void> {
|
||||
}
|
||||
|
||||
const start = Date.now()
|
||||
const req = { payload } as PayloadRequest
|
||||
const req = { payload } as PayloadRequestWithData
|
||||
|
||||
try {
|
||||
payload.logger.info({ msg: `Migrating down: ${migrationFile.name}` })
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
|
||||
import { sql } from 'drizzle-orm'
|
||||
import {
|
||||
@@ -56,7 +56,7 @@ export async function migrateFresh(
|
||||
msg: `Found ${migrationFiles.length} migration files.`,
|
||||
})
|
||||
|
||||
const req = { payload } as PayloadRequest
|
||||
const req = { payload } as PayloadRequestWithData
|
||||
// Run all migrate up
|
||||
for (const migration of migrationFiles) {
|
||||
payload.logger.info({ msg: `Migrating: ${migration.name}` })
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable no-restricted-syntax, no-await-in-loop */
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
|
||||
import {
|
||||
commitTransaction,
|
||||
@@ -34,7 +34,7 @@ export async function migrateRefresh(this: PostgresAdapter) {
|
||||
msg: `Rolling back batch ${latestBatch} consisting of ${existingMigrations.length} migration(s).`,
|
||||
})
|
||||
|
||||
const req = { payload } as PayloadRequest
|
||||
const req = { payload } as PayloadRequestWithData
|
||||
|
||||
// Reverse order of migrations to rollback
|
||||
existingMigrations.reverse()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable no-restricted-syntax, no-await-in-loop */
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
|
||||
import {
|
||||
commitTransaction,
|
||||
@@ -27,7 +27,7 @@ export async function migrateReset(this: PostgresAdapter): Promise<void> {
|
||||
return
|
||||
}
|
||||
|
||||
const req = { payload } as PayloadRequest
|
||||
const req = { payload } as PayloadRequestWithData
|
||||
|
||||
// Rollback all migrations in order
|
||||
for (const migration of existingMigrations) {
|
||||
|
||||
@@ -14,8 +14,6 @@ import { v4 as uuid } from 'uuid'
|
||||
import type { GenericColumn, GenericTable, PostgresAdapter } from '../types.js'
|
||||
import type { BuildQueryJoinAliases, BuildQueryJoins } from './buildQuery.js'
|
||||
|
||||
import { getTableName } from '../schema/getTableName.js'
|
||||
|
||||
type Constraint = {
|
||||
columnName: string
|
||||
table: GenericTable | PgTableWithColumns<any>
|
||||
@@ -185,13 +183,7 @@ export const getTableColumnFromPath = ({
|
||||
|
||||
case 'group': {
|
||||
if (locale && field.localized && adapter.payload.config.localization) {
|
||||
newTableName = getTableName({
|
||||
adapter,
|
||||
config: field,
|
||||
locales: true,
|
||||
parentTableName: tableName,
|
||||
prefix: `${tableName}_`,
|
||||
})
|
||||
newTableName = `${tableName}${adapter.localesSuffix}`
|
||||
|
||||
joins[tableName] = eq(
|
||||
adapter.tables[tableName].id,
|
||||
@@ -227,12 +219,9 @@ export const getTableColumnFromPath = ({
|
||||
|
||||
case 'select': {
|
||||
if (field.hasMany) {
|
||||
newTableName = getTableName({
|
||||
adapter,
|
||||
config: field,
|
||||
parentTableName: `${tableName}_${tableNameSuffix}`,
|
||||
prefix: `${tableName}_${tableNameSuffix}`,
|
||||
})
|
||||
const newTableName = adapter.tableNameMap.get(
|
||||
`${tableName}_${tableNameSuffix}${toSnakeCase(field.name)}`,
|
||||
)
|
||||
|
||||
if (locale && field.localized && adapter.payload.config.localization) {
|
||||
joins[newTableName] = and(
|
||||
@@ -305,12 +294,10 @@ export const getTableColumnFromPath = ({
|
||||
}
|
||||
|
||||
case 'array': {
|
||||
newTableName = getTableName({
|
||||
adapter,
|
||||
config: field,
|
||||
parentTableName: `${tableName}_${tableNameSuffix}`,
|
||||
prefix: `${tableName}_${tableNameSuffix}`,
|
||||
})
|
||||
newTableName = adapter.tableNameMap.get(
|
||||
`${tableName}_${tableNameSuffix}${toSnakeCase(field.name)}`,
|
||||
)
|
||||
|
||||
constraintPath = `${constraintPath}${field.name}.%.`
|
||||
if (locale && field.localized && adapter.payload.config.localization) {
|
||||
joins[newTableName] = and(
|
||||
@@ -357,12 +344,11 @@ export const getTableColumnFromPath = ({
|
||||
const blockTypes = Array.isArray(value) ? value : [value]
|
||||
blockTypes.forEach((blockType) => {
|
||||
const block = field.blocks.find((block) => block.slug === blockType)
|
||||
newTableName = getTableName({
|
||||
adapter,
|
||||
config: block,
|
||||
parentTableName: tableName,
|
||||
prefix: `${tableName}_blocks_`,
|
||||
})
|
||||
|
||||
newTableName = adapter.tableNameMap.get(
|
||||
`${tableName}_blocks_${toSnakeCase(block.slug)}`,
|
||||
)
|
||||
|
||||
joins[newTableName] = eq(
|
||||
adapter.tables[tableName].id,
|
||||
adapter.tables[newTableName]._parentID,
|
||||
@@ -382,13 +368,9 @@ export const getTableColumnFromPath = ({
|
||||
}
|
||||
|
||||
const hasBlockField = field.blocks.some((block) => {
|
||||
newTableName = getTableName({
|
||||
adapter,
|
||||
config: block,
|
||||
parentTableName: tableName,
|
||||
prefix: `${tableName}_blocks_`,
|
||||
})
|
||||
newTableName = adapter.tableNameMap.get(`${tableName}_blocks_${toSnakeCase(block.slug)}`)
|
||||
constraintPath = `${constraintPath}${field.name}.%.`
|
||||
|
||||
let result
|
||||
const blockConstraints = []
|
||||
const blockSelectFields = {}
|
||||
@@ -495,10 +477,9 @@ export const getTableColumnFromPath = ({
|
||||
|
||||
if (typeof field.relationTo === 'string') {
|
||||
const relationshipConfig = adapter.payload.collections[field.relationTo].config
|
||||
newTableName = getTableName({
|
||||
adapter,
|
||||
config: relationshipConfig,
|
||||
})
|
||||
|
||||
newTableName = adapter.tableNameMap.get(toSnakeCase(relationshipConfig.slug))
|
||||
|
||||
// parent to relationship join table
|
||||
relationshipFields = relationshipConfig.fields
|
||||
|
||||
@@ -518,13 +499,13 @@ export const getTableColumnFromPath = ({
|
||||
}
|
||||
}
|
||||
} else if (newCollectionPath === 'value') {
|
||||
const tableColumnsNames = field.relationTo.map(
|
||||
(relationTo) =>
|
||||
`"${aliasRelationshipTableName}"."${getTableName({
|
||||
adapter,
|
||||
config: adapter.payload.collections[relationTo].config,
|
||||
})}_id"`,
|
||||
)
|
||||
const tableColumnsNames = field.relationTo.map((relationTo) => {
|
||||
const relationTableName = adapter.tableNameMap.get(
|
||||
toSnakeCase(adapter.payload.collections[relationTo].config.slug),
|
||||
)
|
||||
|
||||
return `"${aliasRelationshipTableName}"."${relationTableName}_id"`
|
||||
})
|
||||
return {
|
||||
constraints,
|
||||
field,
|
||||
|
||||
@@ -1,27 +1,30 @@
|
||||
import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types'
|
||||
import type { PayloadRequestWithData, SanitizedCollectionConfig } from 'payload/types'
|
||||
|
||||
import { type QueryDrafts, combineQueries } from 'payload/database'
|
||||
import { buildVersionCollectionFields } from 'payload/versions'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { findMany } from './find/findMany.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
|
||||
export const queryDrafts: QueryDrafts = async function queryDrafts({
|
||||
collection,
|
||||
limit,
|
||||
locale,
|
||||
page = 1,
|
||||
pagination,
|
||||
req = {} as PayloadRequest,
|
||||
sort,
|
||||
where,
|
||||
}) {
|
||||
export const queryDrafts: QueryDrafts = async function queryDrafts(
|
||||
this: PostgresAdapter,
|
||||
{
|
||||
collection,
|
||||
limit,
|
||||
locale,
|
||||
page = 1,
|
||||
pagination,
|
||||
req = {} as PayloadRequestWithData,
|
||||
sort,
|
||||
where,
|
||||
},
|
||||
) {
|
||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: collectionConfig,
|
||||
versions: true,
|
||||
})
|
||||
const tableName = this.tableNameMap.get(
|
||||
`_${toSnakeCase(collectionConfig.slug)}${this.versionsSuffix}`,
|
||||
)
|
||||
const fields = buildVersionCollectionFields(collectionConfig)
|
||||
|
||||
const combinedWhere = combineQueries({ latest: { equals: true } }, where)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import type { Relation } from 'drizzle-orm'
|
||||
import type {
|
||||
ForeignKeyBuilder,
|
||||
IndexBuilder,
|
||||
PgColumnBuilder,
|
||||
PgTableWithColumns,
|
||||
@@ -9,20 +10,34 @@ import type {
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
import { relations } from 'drizzle-orm'
|
||||
import { index, integer, numeric, serial, timestamp, unique, varchar } from 'drizzle-orm/pg-core'
|
||||
import { fieldAffectsData } from 'payload/types'
|
||||
import {
|
||||
foreignKey,
|
||||
index,
|
||||
integer,
|
||||
numeric,
|
||||
serial,
|
||||
timestamp,
|
||||
unique,
|
||||
varchar,
|
||||
} from 'drizzle-orm/pg-core'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { GenericColumns, GenericTable, IDType, PostgresAdapter } from '../types.js'
|
||||
|
||||
import { getTableName } from './getTableName.js'
|
||||
import { createTableName } from './createTableName.js'
|
||||
import { parentIDColumnMap } from './parentIDColumnMap.js'
|
||||
import { setColumnID } from './setColumnID.js'
|
||||
import { traverseFields } from './traverseFields.js'
|
||||
|
||||
export type BaseExtraConfig = Record<
|
||||
string,
|
||||
(cols: GenericColumns) => ForeignKeyBuilder | IndexBuilder | UniqueConstraintBuilder
|
||||
>
|
||||
|
||||
type Args = {
|
||||
adapter: PostgresAdapter
|
||||
baseColumns?: Record<string, PgColumnBuilder>
|
||||
baseExtraConfig?: Record<string, (cols: GenericColumns) => IndexBuilder | UniqueConstraintBuilder>
|
||||
baseExtraConfig?: BaseExtraConfig
|
||||
buildNumbers?: boolean
|
||||
buildRelationships?: boolean
|
||||
buildTexts?: boolean
|
||||
@@ -134,10 +149,12 @@ export const buildTable = ({
|
||||
return config
|
||||
}, {})
|
||||
|
||||
return Object.entries(indexes).reduce((acc, [colName, func]) => {
|
||||
const result = Object.entries(indexes).reduce((acc, [colName, func]) => {
|
||||
acc[colName] = func(cols)
|
||||
return acc
|
||||
}, extraConfig)
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
adapter.tables[tableName] = table
|
||||
@@ -146,9 +163,7 @@ export const buildTable = ({
|
||||
const localeTableName = `${tableName}${adapter.localesSuffix}`
|
||||
localesColumns.id = serial('id').primaryKey()
|
||||
localesColumns._locale = adapter.enums.enum__locales('_locale').notNull()
|
||||
localesColumns._parentID = parentIDColumnMap[idColType]('_parent_id')
|
||||
.references(() => table.id, { onDelete: 'cascade' })
|
||||
.notNull()
|
||||
localesColumns._parentID = parentIDColumnMap[idColType]('_parent_id').notNull()
|
||||
|
||||
localesTable = adapter.pgSchema.table(localeTableName, localesColumns, (cols) => {
|
||||
return Object.entries(localesIndexes).reduce(
|
||||
@@ -161,6 +176,11 @@ export const buildTable = ({
|
||||
cols._locale,
|
||||
cols._parentID,
|
||||
),
|
||||
_parentIdFk: foreignKey({
|
||||
name: `${localeTableName}_parent_id_fk`,
|
||||
columns: [cols._parentID],
|
||||
foreignColumns: [table.id],
|
||||
}).onDelete('cascade'),
|
||||
},
|
||||
)
|
||||
})
|
||||
@@ -182,9 +202,7 @@ export const buildTable = ({
|
||||
const columns: Record<string, PgColumnBuilder> = {
|
||||
id: serial('id').primaryKey(),
|
||||
order: integer('order').notNull(),
|
||||
parent: parentIDColumnMap[idColType]('parent_id')
|
||||
.references(() => table.id, { onDelete: 'cascade' })
|
||||
.notNull(),
|
||||
parent: parentIDColumnMap[idColType]('parent_id').notNull(),
|
||||
path: varchar('path').notNull(),
|
||||
text: varchar('text'),
|
||||
}
|
||||
@@ -194,19 +212,24 @@ export const buildTable = ({
|
||||
}
|
||||
|
||||
textsTable = adapter.pgSchema.table(textsTableName, columns, (cols) => {
|
||||
const indexes: Record<string, IndexBuilder> = {
|
||||
const config: Record<string, ForeignKeyBuilder | IndexBuilder> = {
|
||||
orderParentIdx: index(`${textsTableName}_order_parent_idx`).on(cols.order, cols.parent),
|
||||
parentFk: foreignKey({
|
||||
name: `${textsTableName}_parent_fk`,
|
||||
columns: [cols.parent],
|
||||
foreignColumns: [table.id],
|
||||
}).onDelete('cascade'),
|
||||
}
|
||||
|
||||
if (hasManyTextField === 'index') {
|
||||
indexes.text_idx = index(`${textsTableName}_text_idx`).on(cols.text)
|
||||
config.text_idx = index(`${textsTableName}_text_idx`).on(cols.text)
|
||||
}
|
||||
|
||||
if (hasLocalizedManyTextField) {
|
||||
indexes.localeParent = index(`${textsTableName}_locale_parent`).on(cols.locale, cols.parent)
|
||||
config.localeParent = index(`${textsTableName}_locale_parent`).on(cols.locale, cols.parent)
|
||||
}
|
||||
|
||||
return indexes
|
||||
return config
|
||||
})
|
||||
|
||||
adapter.tables[textsTableName] = textsTable
|
||||
@@ -227,9 +250,7 @@ export const buildTable = ({
|
||||
id: serial('id').primaryKey(),
|
||||
number: numeric('number'),
|
||||
order: integer('order').notNull(),
|
||||
parent: parentIDColumnMap[idColType]('parent_id')
|
||||
.references(() => table.id, { onDelete: 'cascade' })
|
||||
.notNull(),
|
||||
parent: parentIDColumnMap[idColType]('parent_id').notNull(),
|
||||
path: varchar('path').notNull(),
|
||||
}
|
||||
|
||||
@@ -238,22 +259,27 @@ export const buildTable = ({
|
||||
}
|
||||
|
||||
numbersTable = adapter.pgSchema.table(numbersTableName, columns, (cols) => {
|
||||
const indexes: Record<string, IndexBuilder> = {
|
||||
const config: Record<string, ForeignKeyBuilder | IndexBuilder> = {
|
||||
orderParentIdx: index(`${numbersTableName}_order_parent_idx`).on(cols.order, cols.parent),
|
||||
parentFk: foreignKey({
|
||||
name: `${numbersTableName}_parent_fk`,
|
||||
columns: [cols.parent],
|
||||
foreignColumns: [table.id],
|
||||
}).onDelete('cascade'),
|
||||
}
|
||||
|
||||
if (hasManyNumberField === 'index') {
|
||||
indexes.numberIdx = index(`${numbersTableName}_number_idx`).on(cols.number)
|
||||
config.numberIdx = index(`${numbersTableName}_number_idx`).on(cols.number)
|
||||
}
|
||||
|
||||
if (hasLocalizedManyNumberField) {
|
||||
indexes.localeParent = index(`${numbersTableName}_locale_parent`).on(
|
||||
config.localeParent = index(`${numbersTableName}_locale_parent`).on(
|
||||
cols.locale,
|
||||
cols.parent,
|
||||
)
|
||||
}
|
||||
|
||||
return indexes
|
||||
return config
|
||||
})
|
||||
|
||||
adapter.tables[numbersTableName] = numbersTable
|
||||
@@ -273,9 +299,7 @@ export const buildTable = ({
|
||||
const relationshipColumns: Record<string, PgColumnBuilder> = {
|
||||
id: serial('id').primaryKey(),
|
||||
order: integer('order'),
|
||||
parent: parentIDColumnMap[idColType]('parent_id')
|
||||
.references(() => table.id, { onDelete: 'cascade' })
|
||||
.notNull(),
|
||||
parent: parentIDColumnMap[idColType]('parent_id').notNull(),
|
||||
path: varchar('path').notNull(),
|
||||
}
|
||||
|
||||
@@ -283,9 +307,13 @@ export const buildTable = ({
|
||||
relationshipColumns.locale = adapter.enums.enum__locales('locale')
|
||||
}
|
||||
|
||||
const relationExtraConfig: BaseExtraConfig = {}
|
||||
|
||||
const relationshipsTableName = `${tableName}${adapter.relationshipsSuffix}`
|
||||
|
||||
relationships.forEach((relationTo) => {
|
||||
const relationshipConfig = adapter.payload.collections[relationTo].config
|
||||
const formattedRelationTo = getTableName({
|
||||
const formattedRelationTo = createTableName({
|
||||
adapter,
|
||||
config: relationshipConfig,
|
||||
throwValidationError: true,
|
||||
@@ -300,20 +328,38 @@ export const buildTable = ({
|
||||
|
||||
relationshipColumns[`${relationTo}ID`] = parentIDColumnMap[colType](
|
||||
`${formattedRelationTo}_id`,
|
||||
).references(() => adapter.tables[formattedRelationTo].id, { onDelete: 'cascade' })
|
||||
})
|
||||
)
|
||||
|
||||
const relationshipsTableName = `${tableName}${adapter.relationshipsSuffix}`
|
||||
relationExtraConfig[`${relationTo}IdFk`] = (cols) =>
|
||||
foreignKey({
|
||||
name: `${relationshipsTableName}_${toSnakeCase(relationTo)}_fk`,
|
||||
columns: [cols[`${relationTo}ID`]],
|
||||
foreignColumns: [adapter.tables[formattedRelationTo].id],
|
||||
}).onDelete('cascade')
|
||||
})
|
||||
|
||||
relationshipsTable = adapter.pgSchema.table(
|
||||
relationshipsTableName,
|
||||
relationshipColumns,
|
||||
(cols) => {
|
||||
const result: Record<string, unknown> = {
|
||||
order: index(`${relationshipsTableName}_order_idx`).on(cols.order),
|
||||
parentIdx: index(`${relationshipsTableName}_parent_idx`).on(cols.parent),
|
||||
pathIdx: index(`${relationshipsTableName}_path_idx`).on(cols.path),
|
||||
}
|
||||
const result: Record<string, ForeignKeyBuilder | IndexBuilder> = Object.entries(
|
||||
relationExtraConfig,
|
||||
).reduce(
|
||||
(config, [key, func]) => {
|
||||
config[key] = func(cols)
|
||||
return config
|
||||
},
|
||||
{
|
||||
order: index(`${relationshipsTableName}_order_idx`).on(cols.order),
|
||||
parentFk: foreignKey({
|
||||
name: `${relationshipsTableName}_parent_fk`,
|
||||
columns: [cols.parent],
|
||||
foreignColumns: [table.id],
|
||||
}).onDelete('cascade'),
|
||||
parentIdx: index(`${relationshipsTableName}_parent_idx`).on(cols.parent),
|
||||
pathIdx: index(`${relationshipsTableName}_path_idx`).on(cols.path),
|
||||
},
|
||||
)
|
||||
|
||||
if (hasLocalizedRelationshipField) {
|
||||
result.localeIdx = index(`${relationshipsTableName}_locale_idx`).on(cols.locale)
|
||||
@@ -335,7 +381,7 @@ export const buildTable = ({
|
||||
}
|
||||
|
||||
relationships.forEach((relationTo) => {
|
||||
const relatedTableName = getTableName({
|
||||
const relatedTableName = createTableName({
|
||||
adapter,
|
||||
config: adapter.payload.collections[relationTo].config,
|
||||
throwValidationError: true,
|
||||
|
||||
@@ -14,53 +14,59 @@ type Args = {
|
||||
name?: string
|
||||
slug?: string
|
||||
}
|
||||
/** Localized tables need to be given the locales suffix */
|
||||
locales?: boolean
|
||||
/** For nested tables passed for the user custom dbName functions to handle their own iterations */
|
||||
parentTableName?: string
|
||||
/** For sub tables (array for example) this needs to include the parentTableName */
|
||||
prefix?: string
|
||||
/** Adds the relationships suffix */
|
||||
relationships?: boolean
|
||||
/** For tables based on fields that could have both enumName and dbName (ie: select with hasMany), default: 'dbName' */
|
||||
target?: 'dbName' | 'enumName'
|
||||
throwValidationError?: boolean
|
||||
/** Adds the versions suffix, should only be used on the base collection to duplicate suffixing */
|
||||
/** Adds the versions suffix to the default table name - should only be used on the base collection to avoid duplicate suffixing */
|
||||
versions?: boolean
|
||||
/** Adds the versions suffix to custom dbName only - this is used while creating blocks / selects / arrays / etc */
|
||||
versionsCustomName?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to name database enums and tables
|
||||
* Returns the table or enum name for a given entity
|
||||
*/
|
||||
export const getTableName = ({
|
||||
export const createTableName = ({
|
||||
adapter,
|
||||
config: { name, slug },
|
||||
config,
|
||||
locales = false,
|
||||
parentTableName,
|
||||
prefix = '',
|
||||
relationships = false,
|
||||
target = 'dbName',
|
||||
throwValidationError = false,
|
||||
versions = false,
|
||||
versionsCustomName = false,
|
||||
}: Args): string => {
|
||||
let result: string
|
||||
let custom = config[target]
|
||||
let customNameDefinition = config[target]
|
||||
|
||||
if (!custom && target === 'enumName') {
|
||||
custom = config['dbName']
|
||||
let defaultTableName = `${prefix}${toSnakeCase(name ?? slug)}`
|
||||
|
||||
if (versions) defaultTableName = `_${defaultTableName}${adapter.versionsSuffix}`
|
||||
|
||||
let customTableNameResult: string
|
||||
|
||||
if (!customNameDefinition && target === 'enumName') {
|
||||
customNameDefinition = config['dbName']
|
||||
}
|
||||
|
||||
if (custom) {
|
||||
result = typeof custom === 'function' ? custom({ tableName: parentTableName }) : custom
|
||||
} else {
|
||||
result = `${prefix}${toSnakeCase(name ?? slug)}`
|
||||
if (customNameDefinition) {
|
||||
customTableNameResult =
|
||||
typeof customNameDefinition === 'function'
|
||||
? customNameDefinition({ tableName: parentTableName })
|
||||
: customNameDefinition
|
||||
|
||||
if (versionsCustomName)
|
||||
customTableNameResult = `_${customTableNameResult}${adapter.versionsSuffix}`
|
||||
}
|
||||
|
||||
if (locales) result = `${result}${adapter.localesSuffix}`
|
||||
if (versions) result = `_${result}${adapter.versionsSuffix}`
|
||||
if (relationships) result = `${result}${adapter.relationshipsSuffix}`
|
||||
const result = customTableNameResult || defaultTableName
|
||||
|
||||
adapter.tableNameMap.set(defaultTableName, result)
|
||||
|
||||
if (!throwValidationError) {
|
||||
return result
|
||||
@@ -71,5 +77,6 @@ export const getTableName = ({
|
||||
`Exceeded max identifier length for table or enum name of 63 characters. Invalid name: ${result}`,
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import type { Relation } from 'drizzle-orm'
|
||||
import type { IndexBuilder, PgColumnBuilder, UniqueConstraintBuilder } from 'drizzle-orm/pg-core'
|
||||
import type { IndexBuilder, PgColumnBuilder } from 'drizzle-orm/pg-core'
|
||||
import type { Field, TabAsField } from 'payload/types'
|
||||
|
||||
import { relations } from 'drizzle-orm'
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
PgUUIDBuilder,
|
||||
PgVarcharBuilder,
|
||||
boolean,
|
||||
foreignKey,
|
||||
index,
|
||||
integer,
|
||||
jsonb,
|
||||
@@ -23,11 +24,12 @@ import { fieldAffectsData, optionIsObject } from 'payload/types'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { GenericColumns, IDType, PostgresAdapter } from '../types.js'
|
||||
import type { BaseExtraConfig } from './build.js'
|
||||
|
||||
import { hasLocalesTable } from '../utilities/hasLocalesTable.js'
|
||||
import { buildTable } from './build.js'
|
||||
import { createIndex } from './createIndex.js'
|
||||
import { getTableName } from './getTableName.js'
|
||||
import { createTableName } from './createTableName.js'
|
||||
import { idToUUID } from './idToUUID.js'
|
||||
import { parentIDColumnMap } from './parentIDColumnMap.js'
|
||||
import { validateExistingBlockIsIdentical } from './validateExistingBlockIsIdentical.js'
|
||||
@@ -221,14 +223,13 @@ export const traverseFields = ({
|
||||
|
||||
case 'radio':
|
||||
case 'select': {
|
||||
const enumName = getTableName({
|
||||
const enumName = createTableName({
|
||||
adapter,
|
||||
config: field,
|
||||
parentTableName: newTableName,
|
||||
prefix: `enum_${newTableName}_`,
|
||||
target: 'enumName',
|
||||
throwValidationError,
|
||||
versions,
|
||||
})
|
||||
|
||||
adapter.enums[enumName] = pgEnum(
|
||||
@@ -243,27 +244,27 @@ export const traverseFields = ({
|
||||
)
|
||||
|
||||
if (field.type === 'select' && field.hasMany) {
|
||||
const selectTableName = getTableName({
|
||||
const selectTableName = createTableName({
|
||||
adapter,
|
||||
config: field,
|
||||
parentTableName: newTableName,
|
||||
prefix: `${newTableName}_`,
|
||||
throwValidationError,
|
||||
versions,
|
||||
})
|
||||
const baseColumns: Record<string, PgColumnBuilder> = {
|
||||
order: integer('order').notNull(),
|
||||
parent: parentIDColumnMap[parentIDColType]('parent_id')
|
||||
.references(() => adapter.tables[parentTableName].id, { onDelete: 'cascade' })
|
||||
.notNull(),
|
||||
parent: parentIDColumnMap[parentIDColType]('parent_id').notNull(),
|
||||
value: adapter.enums[enumName]('value'),
|
||||
}
|
||||
|
||||
const baseExtraConfig: Record<
|
||||
string,
|
||||
(cols: GenericColumns) => IndexBuilder | UniqueConstraintBuilder
|
||||
> = {
|
||||
const baseExtraConfig: BaseExtraConfig = {
|
||||
orderIdx: (cols) => index(`${selectTableName}_order_idx`).on(cols.order),
|
||||
parentFk: (cols) =>
|
||||
foreignKey({
|
||||
name: `${selectTableName}_parent_fk`,
|
||||
columns: [cols.parent],
|
||||
foreignColumns: [adapter.tables[parentTableName].id],
|
||||
}),
|
||||
parentIdx: (cols) => index(`${selectTableName}_parent_idx`).on(cols.parent),
|
||||
}
|
||||
|
||||
@@ -316,25 +317,28 @@ export const traverseFields = ({
|
||||
case 'array': {
|
||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
||||
|
||||
const arrayTableName = getTableName({
|
||||
const arrayTableName = createTableName({
|
||||
adapter,
|
||||
config: field,
|
||||
parentTableName: newTableName,
|
||||
prefix: `${newTableName}_`,
|
||||
throwValidationError,
|
||||
versionsCustomName: versions,
|
||||
})
|
||||
|
||||
const baseColumns: Record<string, PgColumnBuilder> = {
|
||||
_order: integer('_order').notNull(),
|
||||
_parentID: parentIDColumnMap[parentIDColType]('_parent_id')
|
||||
.references(() => adapter.tables[parentTableName].id, { onDelete: 'cascade' })
|
||||
.notNull(),
|
||||
_parentID: parentIDColumnMap[parentIDColType]('_parent_id').notNull(),
|
||||
}
|
||||
|
||||
const baseExtraConfig: Record<
|
||||
string,
|
||||
(cols: GenericColumns) => IndexBuilder | UniqueConstraintBuilder
|
||||
> = {
|
||||
const baseExtraConfig: BaseExtraConfig = {
|
||||
_orderIdx: (cols) => index(`${arrayTableName}_order_idx`).on(cols._order),
|
||||
_parentIDFk: (cols) =>
|
||||
foreignKey({
|
||||
name: `${arrayTableName}_parent_id_fk`,
|
||||
columns: [cols['_parentID']],
|
||||
foreignColumns: [adapter.tables[parentTableName].id],
|
||||
}).onDelete('cascade'),
|
||||
_parentIDIdx: (cols) => index(`${arrayTableName}_parent_id_idx`).on(cols._parentID),
|
||||
}
|
||||
|
||||
@@ -402,28 +406,30 @@ export const traverseFields = ({
|
||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
||||
|
||||
field.blocks.forEach((block) => {
|
||||
const blockTableName = getTableName({
|
||||
const blockTableName = createTableName({
|
||||
adapter,
|
||||
config: block,
|
||||
parentTableName: rootTableName,
|
||||
prefix: `${rootTableName}_blocks_`,
|
||||
throwValidationError,
|
||||
versionsCustomName: versions,
|
||||
})
|
||||
if (!adapter.tables[blockTableName]) {
|
||||
const baseColumns: Record<string, PgColumnBuilder> = {
|
||||
_order: integer('_order').notNull(),
|
||||
_parentID: parentIDColumnMap[rootTableIDColType]('_parent_id')
|
||||
.references(() => adapter.tables[rootTableName].id, { onDelete: 'cascade' })
|
||||
.notNull(),
|
||||
_parentID: parentIDColumnMap[rootTableIDColType]('_parent_id').notNull(),
|
||||
_path: text('_path').notNull(),
|
||||
}
|
||||
|
||||
const baseExtraConfig: Record<
|
||||
string,
|
||||
(cols: GenericColumns) => IndexBuilder | UniqueConstraintBuilder
|
||||
> = {
|
||||
const baseExtraConfig: BaseExtraConfig = {
|
||||
_orderIdx: (cols) => index(`${blockTableName}_order_idx`).on(cols._order),
|
||||
_parentIDIdx: (cols) => index(`${blockTableName}_parent_id_idx`).on(cols._parentID),
|
||||
_parentIdFk: (cols) =>
|
||||
foreignKey({
|
||||
name: `${blockTableName}_parent_id_fk`,
|
||||
columns: [cols._parentID],
|
||||
foreignColumns: [adapter.tables[rootTableName].id],
|
||||
}).onDelete('cascade'),
|
||||
_pathIdx: (cols) => index(`${blockTableName}_path_idx`).on(cols._path),
|
||||
}
|
||||
|
||||
@@ -496,7 +502,6 @@ export const traverseFields = ({
|
||||
tableLocales: adapter.tables[`${blockTableName}${adapter.localesSuffix}`],
|
||||
})
|
||||
}
|
||||
adapter.blockTableNames[`${rootTableName}.${toSnakeCase(block.slug)}`] = blockTableName
|
||||
rootRelationsToBuild.set(`_blocks_${block.slug}`, blockTableName)
|
||||
})
|
||||
|
||||
@@ -659,7 +664,7 @@ export const traverseFields = ({
|
||||
indexes,
|
||||
localesColumns,
|
||||
localesIndexes,
|
||||
newTableName: parentTableName,
|
||||
newTableName,
|
||||
parentTableName,
|
||||
relationsToBuild,
|
||||
relationships,
|
||||
|
||||
@@ -28,7 +28,7 @@ const getFlattenedFieldNames = (
|
||||
}
|
||||
|
||||
if (fieldHasSubFields(field)) {
|
||||
fieldPrefix = 'name' in field ? `${prefix}${field.name}.` : prefix
|
||||
fieldPrefix = 'name' in field ? `${prefix}${field.name}_` : prefix
|
||||
return [...fieldsToUse, ...getFlattenedFieldNames(field.fields, fieldPrefix)]
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ const getFlattenedFieldNames = (
|
||||
return [
|
||||
...fieldsToUse,
|
||||
...field.tabs.reduce((tabFields, tab) => {
|
||||
fieldPrefix = 'name' in tab ? `${prefix}.${tab.name}` : prefix
|
||||
fieldPrefix = 'name' in tab ? `${prefix}_${tab.name}` : prefix
|
||||
return [
|
||||
...tabFields,
|
||||
...(tabHasName(tab)
|
||||
@@ -51,7 +51,7 @@ const getFlattenedFieldNames = (
|
||||
return [
|
||||
...fieldsToUse,
|
||||
{
|
||||
name: `${fieldPrefix?.replace('.', '_') || ''}${field.name}`,
|
||||
name: `${fieldPrefix}${field.name}`,
|
||||
localized: field.localized,
|
||||
},
|
||||
]
|
||||
@@ -85,7 +85,11 @@ export const validateExistingBlockIsIdentical = ({
|
||||
|
||||
if (missingField) {
|
||||
throw new InvalidConfiguration(
|
||||
`The table ${rootTableName} has multiple blocks with slug ${block.slug}, but the schemas do not match. One block includes the field ${typeof missingField === 'string' ? missingField : missingField.name}, while the other block does not.`,
|
||||
`The table ${rootTableName} has multiple blocks with slug ${
|
||||
block.slug
|
||||
}, but the schemas do not match. One block includes the field ${
|
||||
typeof missingField === 'string' ? missingField : missingField.name
|
||||
}, while the other block does not.`,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ export const transformArray = ({
|
||||
texts,
|
||||
}: Args) => {
|
||||
const newRows: ArrayRowToInsert[] = []
|
||||
|
||||
const hasUUID = adapter.tables[arrayTableName]._uuid
|
||||
|
||||
if (isArrayOfRows(data)) {
|
||||
|
||||
@@ -61,7 +61,7 @@ export const transformBlocks = ({
|
||||
|
||||
if (field.localized && locale) newRow.row._locale = locale
|
||||
|
||||
const blockTableName = `${baseTableName}_blocks_${blockType}`
|
||||
const blockTableName = adapter.tableNameMap.get(`${baseTableName}_blocks_${blockType}`)
|
||||
|
||||
const hasUUID = adapter.tables[blockTableName]._uuid
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ export const traverseFields = ({
|
||||
}
|
||||
|
||||
if (field.type === 'array') {
|
||||
const arrayTableName = `${parentTableName}_${columnName}`
|
||||
const arrayTableName = adapter.tableNameMap.get(`${parentTableName}_${columnName}`)
|
||||
|
||||
if (!arrays[arrayTableName]) arrays[arrayTableName] = []
|
||||
|
||||
@@ -458,7 +458,7 @@ export const traverseFields = ({
|
||||
}
|
||||
|
||||
if (field.type === 'select' && field.hasMany) {
|
||||
const selectTableName = `${parentTableName}_${columnName}`
|
||||
const selectTableName = adapter.tableNameMap.get(`${parentTableName}_${columnName}`)
|
||||
if (!selects[selectTableName]) selects[selectTableName] = []
|
||||
|
||||
if (field.localized) {
|
||||
|
||||
@@ -17,7 +17,7 @@ import type {
|
||||
import type { PgTableFn } from 'drizzle-orm/pg-core/table'
|
||||
import type { Payload } from 'payload'
|
||||
import type { BaseDatabaseAdapter } from 'payload/database'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
import type { Pool, PoolConfig } from 'pg'
|
||||
|
||||
export type DrizzleDB = NodePgDatabase<Record<string, unknown>>
|
||||
@@ -61,10 +61,6 @@ export type DrizzleTransaction = PgTransaction<
|
||||
>
|
||||
|
||||
export type PostgresAdapter = BaseDatabaseAdapter & {
|
||||
/**
|
||||
* Used internally to map the block name to the table name
|
||||
*/
|
||||
blockTableNames: Record<string, string>
|
||||
drizzle: DrizzleDB
|
||||
enums: Record<string, GenericEnum>
|
||||
/**
|
||||
@@ -90,6 +86,7 @@ export type PostgresAdapter = BaseDatabaseAdapter & {
|
||||
resolve: () => Promise<void>
|
||||
}
|
||||
}
|
||||
tableNameMap: Map<string, string>
|
||||
tables: Record<string, GenericTable | PgTableWithColumns<any>>
|
||||
versionsSuffix?: string
|
||||
}
|
||||
@@ -98,8 +95,8 @@ export type IDType = 'integer' | 'numeric' | 'uuid' | 'varchar'
|
||||
|
||||
export type PostgresAdapterResult = (args: { payload: Payload }) => PostgresAdapter
|
||||
|
||||
export type MigrateUpArgs = { payload: Payload; req?: Partial<PayloadRequest> }
|
||||
export type MigrateDownArgs = { payload: Payload; req?: Partial<PayloadRequest> }
|
||||
export type MigrateUpArgs = { payload: Payload; req?: Partial<PayloadRequestWithData> }
|
||||
export type MigrateDownArgs = { payload: Payload; req?: Partial<PayloadRequestWithData> }
|
||||
|
||||
declare module 'payload' {
|
||||
export interface DatabaseAdapter
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import type { UpdateOne } from 'payload/database'
|
||||
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import buildQuery from './queries/buildQuery.js'
|
||||
import { selectDistinct } from './queries/selectDistinct.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
import { upsertRow } from './upsertRow/index.js'
|
||||
|
||||
export const updateOne: UpdateOne = async function updateOne(
|
||||
@@ -13,10 +14,7 @@ export const updateOne: UpdateOne = async function updateOne(
|
||||
) {
|
||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||
const collection = this.payload.collections[collectionSlug].config
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: collection,
|
||||
})
|
||||
const tableName = this.tableNameMap.get(toSnakeCase(collection.slug))
|
||||
const whereToUse = whereArg || { id: { equals: id } }
|
||||
let idToUpdate = id
|
||||
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
import type { UpdateGlobalArgs } from 'payload/database'
|
||||
import type { PayloadRequest, TypeWithID } from 'payload/types'
|
||||
import type { PayloadRequestWithData, TypeWithID } from 'payload/types'
|
||||
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
import { upsertRow } from './upsertRow/index.js'
|
||||
|
||||
export async function updateGlobal<T extends TypeWithID>(
|
||||
this: PostgresAdapter,
|
||||
{ slug, data, req = {} as PayloadRequest }: UpdateGlobalArgs,
|
||||
{ slug, data, req = {} as PayloadRequestWithData }: UpdateGlobalArgs,
|
||||
): Promise<T> {
|
||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug)
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: globalConfig,
|
||||
})
|
||||
const tableName = this.tableNameMap.get(toSnakeCase(globalConfig.slug))
|
||||
|
||||
const existingGlobal = await db.query[tableName].findFirst({})
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { TypeWithVersion, UpdateGlobalVersionArgs } from 'payload/database'
|
||||
import type { PayloadRequest, SanitizedGlobalConfig, TypeWithID } from 'payload/types'
|
||||
import type { PayloadRequestWithData, SanitizedGlobalConfig, TypeWithID } from 'payload/types'
|
||||
|
||||
import { buildVersionGlobalFields } from 'payload/versions'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import buildQuery from './queries/buildQuery.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
import { upsertRow } from './upsertRow/index.js'
|
||||
|
||||
export async function updateGlobalVersion<T extends TypeWithID>(
|
||||
@@ -15,7 +15,7 @@ export async function updateGlobalVersion<T extends TypeWithID>(
|
||||
id,
|
||||
global,
|
||||
locale,
|
||||
req = {} as PayloadRequest,
|
||||
req = {} as PayloadRequestWithData,
|
||||
versionData,
|
||||
where: whereArg,
|
||||
}: UpdateGlobalVersionArgs<T>,
|
||||
@@ -25,11 +25,11 @@ export async function updateGlobalVersion<T extends TypeWithID>(
|
||||
({ slug }) => slug === global,
|
||||
)
|
||||
const whereToUse = whereArg || { id: { equals: id } }
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: globalConfig,
|
||||
versions: true,
|
||||
})
|
||||
|
||||
const tableName = this.tableNameMap.get(
|
||||
`_${toSnakeCase(globalConfig.slug)}${this.versionsSuffix}`,
|
||||
)
|
||||
|
||||
const fields = buildVersionGlobalFields(globalConfig)
|
||||
|
||||
const { where } = await buildQuery({
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { TypeWithVersion, UpdateVersionArgs } from 'payload/database'
|
||||
import type { PayloadRequest, SanitizedCollectionConfig, TypeWithID } from 'payload/types'
|
||||
import type { PayloadRequestWithData, SanitizedCollectionConfig, TypeWithID } from 'payload/types'
|
||||
|
||||
import { buildVersionCollectionFields } from 'payload/versions'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import buildQuery from './queries/buildQuery.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
import { upsertRow } from './upsertRow/index.js'
|
||||
|
||||
export async function updateVersion<T extends TypeWithID>(
|
||||
@@ -15,7 +15,7 @@ export async function updateVersion<T extends TypeWithID>(
|
||||
id,
|
||||
collection,
|
||||
locale,
|
||||
req = {} as PayloadRequest,
|
||||
req = {} as PayloadRequestWithData,
|
||||
versionData,
|
||||
where: whereArg,
|
||||
}: UpdateVersionArgs<T>,
|
||||
@@ -23,11 +23,10 @@ export async function updateVersion<T extends TypeWithID>(
|
||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||
const whereToUse = whereArg || { id: { equals: id } }
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: collectionConfig,
|
||||
versions: true,
|
||||
})
|
||||
const tableName = this.tableNameMap.get(
|
||||
`_${toSnakeCase(collectionConfig.slug)}${this.versionsSuffix}`,
|
||||
)
|
||||
|
||||
const fields = buildVersionCollectionFields(collectionConfig)
|
||||
|
||||
const { where } = await buildQuery({
|
||||
|
||||
@@ -137,7 +137,7 @@ export const upsertRow = async <T extends TypeWithID>({
|
||||
// //////////////////////////////////
|
||||
|
||||
if (localesToInsert.length > 0) {
|
||||
const localeTable = adapter.tables[`${tableName}_locales`]
|
||||
const localeTable = adapter.tables[`${tableName}${adapter.localesSuffix}`]
|
||||
|
||||
if (operation === 'update') {
|
||||
await db.delete(localeTable).where(eq(localeTable._parentID, insertedRow.id))
|
||||
@@ -150,7 +150,7 @@ export const upsertRow = async <T extends TypeWithID>({
|
||||
// INSERT RELATIONSHIPS
|
||||
// //////////////////////////////////
|
||||
|
||||
const relationshipsTableName = `${tableName}_rels`
|
||||
const relationshipsTableName = `${tableName}${adapter.relationshipsSuffix}`
|
||||
|
||||
if (operation === 'update') {
|
||||
await deleteExistingRowsByPath({
|
||||
@@ -223,15 +223,16 @@ export const upsertRow = async <T extends TypeWithID>({
|
||||
|
||||
if (operation === 'update') {
|
||||
for (const blockName of rowToInsert.blocksToDelete) {
|
||||
const blockTableName = `${tableName}_blocks_${blockName}`
|
||||
const blockTableName = adapter.tableNameMap.get(`${tableName}_blocks_${blockName}`)
|
||||
const blockTable = adapter.tables[blockTableName]
|
||||
await db.delete(blockTable).where(eq(blockTable._parentID, insertedRow.id))
|
||||
}
|
||||
}
|
||||
|
||||
for (const [blockName, blockRows] of Object.entries(blocksToInsert)) {
|
||||
const blockTableName = adapter.tableNameMap.get(`${tableName}_blocks_${blockName}`)
|
||||
insertedBlockRows[blockName] = await db
|
||||
.insert(adapter.tables[`${tableName}_blocks_${blockName}`])
|
||||
.insert(adapter.tables[blockTableName])
|
||||
.values(blockRows.map(({ row }) => row))
|
||||
.returning()
|
||||
|
||||
@@ -258,7 +259,7 @@ export const upsertRow = async <T extends TypeWithID>({
|
||||
|
||||
if (blockLocaleRowsToInsert.length > 0) {
|
||||
await db
|
||||
.insert(adapter.tables[`${tableName}_blocks_${blockName}_locales`])
|
||||
.insert(adapter.tables[`${blockTableName}${adapter.localesSuffix}`])
|
||||
.values(blockLocaleRowsToInsert)
|
||||
.returning()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { SQL } from 'drizzle-orm'
|
||||
import type { Field, PayloadRequest } from 'payload/types'
|
||||
import type { Field, PayloadRequestWithData } from 'payload/types'
|
||||
|
||||
import type { DrizzleDB, GenericColumn, PostgresAdapter } from '../types.js'
|
||||
|
||||
@@ -9,7 +9,7 @@ type BaseArgs = {
|
||||
db: DrizzleDB
|
||||
fields: Field[]
|
||||
path?: string
|
||||
req: PayloadRequest
|
||||
req: PayloadRequestWithData
|
||||
tableName: string
|
||||
}
|
||||
|
||||
|
||||
10
packages/email-nodemailer/.eslintignore
Normal file
10
packages/email-nodemailer/.eslintignore
Normal file
@@ -0,0 +1,10 @@
|
||||
.tmp
|
||||
**/.git
|
||||
**/.hg
|
||||
**/.pnp.*
|
||||
**/.svn
|
||||
**/.yarn/**
|
||||
**/build
|
||||
**/dist/**
|
||||
**/node_modules
|
||||
**/temp
|
||||
7
packages/email-nodemailer/.eslintrc.cjs
Normal file
7
packages/email-nodemailer/.eslintrc.cjs
Normal file
@@ -0,0 +1,7 @@
|
||||
/** @type {import('eslint').Linter.Config} */
|
||||
module.exports = {
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.json'],
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
}
|
||||
10
packages/email-nodemailer/.prettierignore
Normal file
10
packages/email-nodemailer/.prettierignore
Normal file
@@ -0,0 +1,10 @@
|
||||
.tmp
|
||||
**/.git
|
||||
**/.hg
|
||||
**/.pnp.*
|
||||
**/.svn
|
||||
**/.yarn/**
|
||||
**/build
|
||||
**/dist/**
|
||||
**/node_modules
|
||||
**/temp
|
||||
15
packages/email-nodemailer/.swcrc
Normal file
15
packages/email-nodemailer/.swcrc
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/swcrc",
|
||||
"sourceMaps": true,
|
||||
"jsc": {
|
||||
"target": "esnext",
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"tsx": true,
|
||||
"dts": true
|
||||
}
|
||||
},
|
||||
"module": {
|
||||
"type": "es6"
|
||||
}
|
||||
}
|
||||
22
packages/email-nodemailer/LICENSE.md
Normal file
22
packages/email-nodemailer/LICENSE.md
Normal file
@@ -0,0 +1,22 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018-2022 Payload CMS, LLC <info@payloadcms.com>
|
||||
Portions Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
1
packages/email-nodemailer/README.md
Normal file
1
packages/email-nodemailer/README.md
Normal file
@@ -0,0 +1 @@
|
||||
# Nodemailer Email Adapter
|
||||
59
packages/email-nodemailer/package.json
Normal file
59
packages/email-nodemailer/package.json
Normal file
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"name": "@payloadcms/email-nodemailer",
|
||||
"version": "3.0.0-beta.18",
|
||||
"description": "Payload Nodemailer Email Adapter",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/payloadcms/payload.git",
|
||||
"directory": "packages/email-nodemailer"
|
||||
},
|
||||
"license": "MIT",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"author": "Payload CMS, Inc.",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "pnpm build:swc && pnpm build:types",
|
||||
"build:swc": "swc ./src -d ./dist --config-file .swcrc",
|
||||
"build:types": "tsc --emitDeclarationOnly --outDir dist",
|
||||
"build:clean": "find . \\( -type d \\( -name build -o -name dist -o -name .cache \\) -o -type f -name tsconfig.tsbuildinfo \\) -exec rm -rf {} + && pnpm build",
|
||||
"clean": "rimraf {dist,*.tsbuildinfo}",
|
||||
"prepublishOnly": "pnpm clean && pnpm turbo build"
|
||||
},
|
||||
"dependencies": {
|
||||
"nodemailer": "6.9.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./src/index.ts",
|
||||
"require": "./src/index.ts",
|
||||
"types": "./src/index.ts"
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
"registry": "https://registry.npmjs.org/",
|
||||
"types": "./dist/index.d.ts"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.20.2"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"devDependencies": {
|
||||
"payload": "workspace:*",
|
||||
"@types/nodemailer": "6.4.14"
|
||||
}
|
||||
}
|
||||
123
packages/email-nodemailer/src/index.ts
Normal file
123
packages/email-nodemailer/src/index.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
/* eslint-disable no-console */
|
||||
import type { Transporter } from 'nodemailer'
|
||||
import type SMTPConnection from 'nodemailer/lib/smtp-connection'
|
||||
import type { EmailAdapter } from 'payload/config'
|
||||
|
||||
import nodemailer from 'nodemailer'
|
||||
import { InvalidConfiguration } from 'payload/errors'
|
||||
|
||||
export type NodemailerAdapterArgs = {
|
||||
defaultFromAddress: string
|
||||
defaultFromName: string
|
||||
skipVerify?: boolean
|
||||
transport?: Transporter
|
||||
transportOptions?: SMTPConnection.Options
|
||||
}
|
||||
|
||||
type NodemailerAdapter = EmailAdapter<unknown>
|
||||
|
||||
/**
|
||||
* Creates an email adapter using nodemailer
|
||||
*
|
||||
* If no email configuration is provided, an ethereal email test account is returned
|
||||
*/
|
||||
export const nodemailerAdapter = async (
|
||||
args?: NodemailerAdapterArgs,
|
||||
): Promise<NodemailerAdapter> => {
|
||||
const { defaultFromAddress, defaultFromName, transport } = await buildEmail(args)
|
||||
|
||||
const adapter: NodemailerAdapter = () => ({
|
||||
defaultFromAddress,
|
||||
defaultFromName,
|
||||
sendEmail: async (message) => {
|
||||
return await transport.sendMail({
|
||||
from: `${defaultFromName} <${defaultFromAddress}>`,
|
||||
...message,
|
||||
})
|
||||
},
|
||||
})
|
||||
return adapter
|
||||
}
|
||||
|
||||
async function buildEmail(emailConfig?: NodemailerAdapterArgs): Promise<{
|
||||
defaultFromAddress: string
|
||||
defaultFromName: string
|
||||
transport: Transporter
|
||||
}> {
|
||||
if (!emailConfig) {
|
||||
const transport = await createMockAccount(emailConfig)
|
||||
if (!transport) throw new InvalidConfiguration('Unable to create Nodemailer test account.')
|
||||
|
||||
return {
|
||||
defaultFromAddress: 'info@payloadcms.com',
|
||||
defaultFromName: 'Payload',
|
||||
transport,
|
||||
}
|
||||
}
|
||||
|
||||
// Create or extract transport
|
||||
let transport: Transporter
|
||||
if ('transport' in emailConfig && emailConfig.transport) {
|
||||
;({ transport } = emailConfig)
|
||||
} else if ('transportOptions' in emailConfig && emailConfig.transportOptions) {
|
||||
transport = nodemailer.createTransport(emailConfig.transportOptions)
|
||||
} else {
|
||||
transport = await createMockAccount(emailConfig)
|
||||
}
|
||||
|
||||
if (emailConfig.skipVerify !== false) {
|
||||
await verifyTransport(transport)
|
||||
}
|
||||
|
||||
return {
|
||||
defaultFromAddress: emailConfig.defaultFromAddress,
|
||||
defaultFromName: emailConfig.defaultFromName,
|
||||
transport,
|
||||
}
|
||||
}
|
||||
|
||||
async function verifyTransport(transport: Transporter) {
|
||||
try {
|
||||
await transport.verify()
|
||||
} catch (err: unknown) {
|
||||
console.error({ err, msg: 'Error verifying Nodemailer transport.' })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use ethereal.email to create a mock email account
|
||||
*/
|
||||
async function createMockAccount(emailConfig?: NodemailerAdapterArgs) {
|
||||
try {
|
||||
const etherealAccount = await nodemailer.createTestAccount()
|
||||
|
||||
const smtpOptions = {
|
||||
...(emailConfig || {}),
|
||||
auth: {
|
||||
pass: etherealAccount.pass,
|
||||
user: etherealAccount.user,
|
||||
},
|
||||
fromAddress: emailConfig?.defaultFromAddress,
|
||||
fromName: emailConfig?.defaultFromName,
|
||||
host: 'smtp.ethereal.email',
|
||||
port: 587,
|
||||
secure: false,
|
||||
}
|
||||
const transport = nodemailer.createTransport(smtpOptions)
|
||||
const { pass, user, web } = etherealAccount
|
||||
|
||||
console.info('E-mail configured with ethereal.email test account. ')
|
||||
console.info(`Log into mock email provider at ${web}`)
|
||||
console.info(`Mock email account username: ${user}`)
|
||||
console.info(`Mock email account password: ${pass}`)
|
||||
return transport
|
||||
} catch (err: unknown) {
|
||||
if (err instanceof Error) {
|
||||
console.error({ err, msg: 'There was a problem setting up the mock email handler' })
|
||||
throw new InvalidConfiguration(
|
||||
`Unable to create Nodemailer test account. Error: ${err.message}`,
|
||||
)
|
||||
}
|
||||
throw new InvalidConfiguration('Unable to create Nodemailer test account.')
|
||||
}
|
||||
}
|
||||
19
packages/email-nodemailer/tsconfig.json
Normal file
19
packages/email-nodemailer/tsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true, // Make sure typescript knows that this module depends on their references
|
||||
"noEmit": false /* Do not emit outputs. */,
|
||||
"emitDeclarationOnly": true,
|
||||
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
|
||||
"rootDir": "./src" /* Specify the root folder within your source files. */,
|
||||
"strict": true,
|
||||
},
|
||||
"exclude": [
|
||||
"dist",
|
||||
"node_modules",
|
||||
],
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.d.ts", "src/**/*.json"],
|
||||
"references": [
|
||||
{ "path": "../payload" },
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/graphql",
|
||||
"version": "3.0.0-beta.11",
|
||||
"version": "3.0.0-beta.18",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.d.ts",
|
||||
"type": "module",
|
||||
@@ -27,20 +27,17 @@
|
||||
"devDependencies": {
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@types/pluralize": "^0.0.33",
|
||||
"graphql-http": "^1.22.0",
|
||||
"payload": "workspace:*",
|
||||
"ts-essentials": "7.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"graphql": "16.8.1",
|
||||
"graphql-http": "^1.22.0",
|
||||
"graphql-playground-html": "1.6.30",
|
||||
"graphql-query-complexity": "0.12.0",
|
||||
"graphql-scalars": "1.22.2",
|
||||
"graphql-type-json": "0.3.2",
|
||||
"pluralize": "8.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"payload": "workspace:*"
|
||||
"payload": "workspace:*",
|
||||
"graphql": "^16.8.1"
|
||||
},
|
||||
"publishConfig": {
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@@ -4,12 +4,12 @@ import type { GraphQLInfo } from 'payload/config'
|
||||
import type { SanitizedConfig } from 'payload/types'
|
||||
|
||||
import * as GraphQL from 'graphql'
|
||||
|
||||
import {
|
||||
createComplexityRule,
|
||||
fieldExtensionsEstimator,
|
||||
simpleEstimator,
|
||||
} from 'graphql-query-complexity'
|
||||
|
||||
} from './packages/graphql-query-complexity/index.js'
|
||||
import accessResolver from './resolvers/auth/access.js'
|
||||
import buildFallbackLocaleInputType from './schema/buildFallbackLocaleInputType.js'
|
||||
import buildLocaleInputType from './schema/buildLocaleInputType.js'
|
||||
@@ -18,10 +18,10 @@ import initCollections from './schema/initCollections.js'
|
||||
import initGlobals from './schema/initGlobals.js'
|
||||
import { wrapCustomFields } from './utilities/wrapCustomResolver.js'
|
||||
|
||||
export async function configToSchema(config: SanitizedConfig): Promise<{
|
||||
export function configToSchema(config: SanitizedConfig): {
|
||||
schema: GraphQL.GraphQLSchema
|
||||
validationRules: (args: OperationArgs<any>) => GraphQL.ValidationRule[]
|
||||
}> {
|
||||
} {
|
||||
const collections = config.collections.reduce((acc, collection) => {
|
||||
acc[collection.slug] = {
|
||||
config: collection,
|
||||
|
||||
@@ -0,0 +1,455 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/no-use-before-define */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-enum-comparison */
|
||||
/**
|
||||
* Created by Ivo Meißner on 28.07.17.
|
||||
*/
|
||||
|
||||
import type {
|
||||
DocumentNode,
|
||||
FieldNode,
|
||||
FragmentDefinitionNode,
|
||||
FragmentSpreadNode,
|
||||
GraphQLCompositeType,
|
||||
GraphQLDirective,
|
||||
GraphQLField,
|
||||
GraphQLFieldMap,
|
||||
GraphQLNamedType,
|
||||
GraphQLSchema,
|
||||
GraphQLUnionType,
|
||||
InlineFragmentNode,
|
||||
OperationDefinitionNode} from 'graphql';
|
||||
|
||||
import {
|
||||
GraphQLError,
|
||||
GraphQLInterfaceType,
|
||||
GraphQLObjectType,
|
||||
Kind,
|
||||
TypeInfo,
|
||||
ValidationContext,
|
||||
getNamedType,
|
||||
isAbstractType,
|
||||
isCompositeType,
|
||||
visit,
|
||||
visitWithTypeInfo,
|
||||
} from 'graphql'
|
||||
import {
|
||||
getArgumentValues,
|
||||
getDirectiveValues,
|
||||
getVariableValues,
|
||||
} from 'graphql/execution/values.js'
|
||||
|
||||
export type ComplexityEstimatorArgs = {
|
||||
args: { [key: string]: any }
|
||||
childComplexity: number
|
||||
context?: Record<string, any>
|
||||
field: GraphQLField<any, any>
|
||||
node: FieldNode
|
||||
type: GraphQLCompositeType
|
||||
}
|
||||
|
||||
export type ComplexityEstimator = (options: ComplexityEstimatorArgs) => number | void
|
||||
|
||||
// Complexity can be anything that is supported by the configured estimators
|
||||
export type Complexity = any
|
||||
|
||||
// Map of complexities for possible types (of Union, Interface types)
|
||||
type ComplexityMap = {
|
||||
[typeName: string]: number
|
||||
}
|
||||
|
||||
export interface QueryComplexityOptions {
|
||||
// Pass request context to the estimators via estimationContext
|
||||
context?: Record<string, any>
|
||||
|
||||
// The query variables. This is needed because the variables are not available
|
||||
// Optional function to create a custom error
|
||||
createError?: (max: number, actual: number) => GraphQLError
|
||||
|
||||
// An array of complexity estimators to use for estimating the complexity
|
||||
estimators: Array<ComplexityEstimator>
|
||||
|
||||
// Optional callback function to retrieve the determined query complexity
|
||||
// Will be invoked whether the query is rejected or not
|
||||
// The maximum allowed query complexity, queries above this threshold will be rejected
|
||||
maximumComplexity: number
|
||||
|
||||
// This can be used for logging or to implement rate limiting
|
||||
onComplete?: (complexity: number) => void
|
||||
|
||||
// specify operation name only when pass multi-operation documents
|
||||
operationName?: string
|
||||
|
||||
// in the visitor of the graphql-js library
|
||||
variables?: Record<string, any>
|
||||
}
|
||||
|
||||
function queryComplexityMessage(max: number, actual: number): string {
|
||||
return `The query exceeds the maximum complexity of ${max}. ` + `Actual complexity is ${actual}`
|
||||
}
|
||||
|
||||
export function getComplexity(options: {
|
||||
context?: Record<string, any>
|
||||
estimators: ComplexityEstimator[]
|
||||
operationName?: string
|
||||
query: DocumentNode
|
||||
schema: GraphQLSchema
|
||||
variables?: Record<string, any>
|
||||
}): number {
|
||||
const typeInfo = new TypeInfo(options.schema)
|
||||
|
||||
const errors: GraphQLError[] = []
|
||||
const context = new ValidationContext(options.schema, options.query, typeInfo, (error) =>
|
||||
errors.push(error),
|
||||
)
|
||||
const visitor = new QueryComplexity(context, {
|
||||
// Maximum complexity does not matter since we're only interested in the calculated complexity.
|
||||
context: options.context,
|
||||
estimators: options.estimators,
|
||||
maximumComplexity: Infinity,
|
||||
operationName: options.operationName,
|
||||
variables: options.variables,
|
||||
})
|
||||
|
||||
visit(options.query, visitWithTypeInfo(typeInfo, visitor))
|
||||
|
||||
// Throw first error if any
|
||||
if (errors.length) {
|
||||
throw errors.pop()
|
||||
}
|
||||
|
||||
return visitor.complexity
|
||||
}
|
||||
|
||||
export default class QueryComplexity {
|
||||
OperationDefinition: Record<string, any>
|
||||
complexity: number
|
||||
context: ValidationContext
|
||||
estimators: Array<ComplexityEstimator>
|
||||
includeDirectiveDef: GraphQLDirective
|
||||
options: QueryComplexityOptions
|
||||
requestContext?: Record<string, any>
|
||||
skipDirectiveDef: GraphQLDirective
|
||||
variableValues: Record<string, any>
|
||||
|
||||
constructor(context: ValidationContext, options: QueryComplexityOptions) {
|
||||
if (!(typeof options.maximumComplexity === 'number' && options.maximumComplexity > 0)) {
|
||||
throw new Error('Maximum query complexity must be a positive number')
|
||||
}
|
||||
|
||||
this.context = context
|
||||
this.complexity = 0
|
||||
this.options = options
|
||||
|
||||
this.includeDirectiveDef = this.context.getSchema().getDirective('include')
|
||||
this.skipDirectiveDef = this.context.getSchema().getDirective('skip')
|
||||
this.estimators = options.estimators
|
||||
this.variableValues = {}
|
||||
this.requestContext = options.context
|
||||
|
||||
this.OperationDefinition = {
|
||||
enter: this.onOperationDefinitionEnter,
|
||||
leave: this.onOperationDefinitionLeave,
|
||||
}
|
||||
}
|
||||
|
||||
createError(): GraphQLError {
|
||||
if (typeof this.options.createError === 'function') {
|
||||
return this.options.createError(this.options.maximumComplexity, this.complexity)
|
||||
}
|
||||
return new GraphQLError(queryComplexityMessage(this.options.maximumComplexity, this.complexity))
|
||||
}
|
||||
|
||||
nodeComplexity(
|
||||
node: FieldNode | FragmentDefinitionNode | InlineFragmentNode | OperationDefinitionNode,
|
||||
typeDef: GraphQLInterfaceType | GraphQLObjectType | GraphQLUnionType,
|
||||
): number {
|
||||
if (node.selectionSet) {
|
||||
let fields: GraphQLFieldMap<any, any> = {}
|
||||
if (typeDef instanceof GraphQLObjectType || typeDef instanceof GraphQLInterfaceType) {
|
||||
fields = typeDef.getFields()
|
||||
}
|
||||
|
||||
// Determine all possible types of the current node
|
||||
let possibleTypeNames: string[]
|
||||
if (isAbstractType(typeDef)) {
|
||||
possibleTypeNames = this.context
|
||||
.getSchema()
|
||||
.getPossibleTypes(typeDef)
|
||||
.map((t) => t.name)
|
||||
} else {
|
||||
possibleTypeNames = [typeDef.name]
|
||||
}
|
||||
|
||||
// Collect complexities for all possible types individually
|
||||
const selectionSetComplexities: ComplexityMap = node.selectionSet.selections.reduce(
|
||||
(
|
||||
complexities: ComplexityMap,
|
||||
childNode: FieldNode | FragmentSpreadNode | InlineFragmentNode,
|
||||
): ComplexityMap => {
|
||||
// let nodeComplexity = 0;
|
||||
let innerComplexities = complexities
|
||||
|
||||
let includeNode = true
|
||||
let skipNode = false
|
||||
|
||||
for (const directive of childNode.directives ?? []) {
|
||||
const directiveName = directive.name.value
|
||||
switch (directiveName) {
|
||||
case 'include': {
|
||||
const values = getDirectiveValues(
|
||||
this.includeDirectiveDef,
|
||||
childNode,
|
||||
this.variableValues || {},
|
||||
)
|
||||
if (typeof values.if === 'boolean') {
|
||||
includeNode = values.if
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'skip': {
|
||||
const values = getDirectiveValues(
|
||||
this.skipDirectiveDef,
|
||||
childNode,
|
||||
this.variableValues || {},
|
||||
)
|
||||
if (typeof values.if === 'boolean') {
|
||||
skipNode = values.if
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!includeNode || skipNode) {
|
||||
return complexities
|
||||
}
|
||||
|
||||
switch (childNode.kind) {
|
||||
case Kind.FIELD: {
|
||||
const field = fields[childNode.name.value]
|
||||
// Invalid field, should be caught by other validation rules
|
||||
if (!field) {
|
||||
break
|
||||
}
|
||||
const fieldType = getNamedType(field.type)
|
||||
|
||||
// Get arguments
|
||||
let args: { [key: string]: any }
|
||||
try {
|
||||
args = getArgumentValues(field, childNode, this.variableValues || {})
|
||||
} catch (e) {
|
||||
this.context.reportError(e)
|
||||
return complexities
|
||||
}
|
||||
|
||||
// Check if we have child complexity
|
||||
let childComplexity = 0
|
||||
if (isCompositeType(fieldType)) {
|
||||
childComplexity = this.nodeComplexity(childNode, fieldType)
|
||||
}
|
||||
|
||||
// Run estimators one after another and return first valid complexity
|
||||
// score
|
||||
const estimatorArgs: ComplexityEstimatorArgs = {
|
||||
type: typeDef,
|
||||
args,
|
||||
childComplexity,
|
||||
context: this.requestContext,
|
||||
field,
|
||||
node: childNode,
|
||||
}
|
||||
const validScore = this.estimators.find((estimator) => {
|
||||
const tmpComplexity = estimator(estimatorArgs)
|
||||
|
||||
if (typeof tmpComplexity === 'number' && !isNaN(tmpComplexity)) {
|
||||
innerComplexities = addComplexities(
|
||||
tmpComplexity,
|
||||
complexities,
|
||||
possibleTypeNames,
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
if (!validScore) {
|
||||
this.context.reportError(
|
||||
new GraphQLError(
|
||||
`No complexity could be calculated for field ${typeDef.name}.${field.name}. ` +
|
||||
'At least one complexity estimator has to return a complexity score.',
|
||||
),
|
||||
)
|
||||
return complexities
|
||||
}
|
||||
break
|
||||
}
|
||||
case Kind.FRAGMENT_SPREAD: {
|
||||
const fragment = this.context.getFragment(childNode.name.value)
|
||||
// Unknown fragment, should be caught by other validation rules
|
||||
if (!fragment) {
|
||||
break
|
||||
}
|
||||
const fragmentType = this.context
|
||||
.getSchema()
|
||||
.getType(fragment.typeCondition.name.value)
|
||||
// Invalid fragment type, ignore. Should be caught by other validation rules
|
||||
if (!isCompositeType(fragmentType)) {
|
||||
break
|
||||
}
|
||||
const nodeComplexity = this.nodeComplexity(fragment, fragmentType)
|
||||
if (isAbstractType(fragmentType)) {
|
||||
// Add fragment complexity for all possible types
|
||||
innerComplexities = addComplexities(
|
||||
nodeComplexity,
|
||||
complexities,
|
||||
this.context
|
||||
.getSchema()
|
||||
.getPossibleTypes(fragmentType)
|
||||
.map((t) => t.name),
|
||||
)
|
||||
} else {
|
||||
// Add complexity for object type
|
||||
innerComplexities = addComplexities(nodeComplexity, complexities, [
|
||||
fragmentType.name,
|
||||
])
|
||||
}
|
||||
break
|
||||
}
|
||||
case Kind.INLINE_FRAGMENT: {
|
||||
let inlineFragmentType: GraphQLNamedType = typeDef
|
||||
if (childNode.typeCondition && childNode.typeCondition.name) {
|
||||
inlineFragmentType = this.context
|
||||
.getSchema()
|
||||
.getType(childNode.typeCondition.name.value)
|
||||
if (!isCompositeType(inlineFragmentType)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const nodeComplexity = this.nodeComplexity(childNode, inlineFragmentType)
|
||||
if (isAbstractType(inlineFragmentType)) {
|
||||
// Add fragment complexity for all possible types
|
||||
innerComplexities = addComplexities(
|
||||
nodeComplexity,
|
||||
complexities,
|
||||
this.context
|
||||
.getSchema()
|
||||
.getPossibleTypes(inlineFragmentType)
|
||||
.map((t) => t.name),
|
||||
)
|
||||
} else {
|
||||
// Add complexity for object type
|
||||
innerComplexities = addComplexities(nodeComplexity, complexities, [
|
||||
inlineFragmentType.name,
|
||||
])
|
||||
}
|
||||
break
|
||||
}
|
||||
default: {
|
||||
innerComplexities = addComplexities(
|
||||
this.nodeComplexity(childNode, typeDef),
|
||||
complexities,
|
||||
possibleTypeNames,
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return innerComplexities
|
||||
},
|
||||
{},
|
||||
)
|
||||
// Only return max complexity of all possible types
|
||||
if (!selectionSetComplexities) {
|
||||
return NaN
|
||||
}
|
||||
return Math.max(...Object.values(selectionSetComplexities), 0)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
onOperationDefinitionEnter(operation: OperationDefinitionNode): void {
|
||||
if (
|
||||
typeof this.options.operationName === 'string' &&
|
||||
this.options.operationName !== operation.name.value
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
// Get variable values from variables that are passed from options, merged
|
||||
// with default values defined in the operation
|
||||
const { coerced, errors } = getVariableValues(
|
||||
this.context.getSchema(),
|
||||
// We have to create a new array here because input argument is not readonly in graphql ~14.6.0
|
||||
operation.variableDefinitions ? [...operation.variableDefinitions] : [],
|
||||
this.options.variables ?? {},
|
||||
)
|
||||
if (errors && errors.length) {
|
||||
// We have input validation errors, report errors and abort
|
||||
errors.forEach((error) => this.context.reportError(error))
|
||||
return
|
||||
}
|
||||
this.variableValues = coerced
|
||||
|
||||
switch (operation.operation) {
|
||||
case 'query':
|
||||
this.complexity += this.nodeComplexity(operation, this.context.getSchema().getQueryType())
|
||||
break
|
||||
case 'mutation':
|
||||
this.complexity += this.nodeComplexity(
|
||||
operation,
|
||||
this.context.getSchema().getMutationType(),
|
||||
)
|
||||
break
|
||||
case 'subscription':
|
||||
this.complexity += this.nodeComplexity(
|
||||
operation,
|
||||
this.context.getSchema().getSubscriptionType(),
|
||||
)
|
||||
break
|
||||
default:
|
||||
throw new Error(
|
||||
`Query complexity could not be calculated for operation of type ${operation.operation}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
onOperationDefinitionLeave(operation: OperationDefinitionNode): GraphQLError | void {
|
||||
if (
|
||||
typeof this.options.operationName === 'string' &&
|
||||
this.options.operationName !== operation.name.value
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.options.onComplete) {
|
||||
this.options.onComplete(this.complexity)
|
||||
}
|
||||
|
||||
if (this.complexity > this.options.maximumComplexity) {
|
||||
return this.context.reportError(this.createError())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a complexity to the complexity map for all possible types
|
||||
* @param complexity
|
||||
* @param complexityMap
|
||||
* @param possibleTypes
|
||||
*/
|
||||
function addComplexities(
|
||||
complexity: number,
|
||||
complexityMap: ComplexityMap,
|
||||
possibleTypes: string[],
|
||||
): ComplexityMap {
|
||||
for (const type of possibleTypes) {
|
||||
if (Object.prototype.hasOwnProperty.call(complexityMap, type)) {
|
||||
complexityMap[type] += complexity
|
||||
} else {
|
||||
complexityMap[type] = complexity
|
||||
}
|
||||
}
|
||||
return complexityMap
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { ValidationContext } from 'graphql'
|
||||
|
||||
import type { QueryComplexityOptions } from './QueryComplexity.js'
|
||||
|
||||
import QueryComplexity from './QueryComplexity.js'
|
||||
|
||||
export function createComplexityRule(
|
||||
options: QueryComplexityOptions,
|
||||
): (context: ValidationContext) => QueryComplexity {
|
||||
return (context: ValidationContext): QueryComplexity => {
|
||||
return new QueryComplexity(context, options)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import type { ComplexityEstimator, ComplexityEstimatorArgs } from '../../QueryComplexity.js'
|
||||
|
||||
export const fieldExtensionsEstimator = (): ComplexityEstimator => {
|
||||
return (args: ComplexityEstimatorArgs): number | void => {
|
||||
if (args.field.extensions) {
|
||||
// Calculate complexity score
|
||||
if (typeof args.field.extensions.complexity === 'number') {
|
||||
return args.childComplexity + args.field.extensions.complexity
|
||||
} else if (typeof args.field.extensions.complexity === 'function') {
|
||||
return args.field.extensions.complexity(args)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { ComplexityEstimator, ComplexityEstimatorArgs } from '../../QueryComplexity.js'
|
||||
|
||||
export const simpleEstimator = (options?: { defaultComplexity?: number }): ComplexityEstimator => {
|
||||
const defaultComplexity =
|
||||
options && typeof options.defaultComplexity === 'number' ? options.defaultComplexity : 1
|
||||
return (args: ComplexityEstimatorArgs): number | void => {
|
||||
return defaultComplexity + args.childComplexity
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export { createComplexityRule } from './createComplexityRule.js'
|
||||
export { fieldExtensionsEstimator } from './estimators/fieldExtensions/index.js'
|
||||
export { simpleEstimator } from './estimators/simple/index.js'
|
||||
73
packages/graphql/src/packages/graphql-type-json/index.ts
Normal file
73
packages/graphql/src/packages/graphql-type-json/index.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { GraphQLScalarType } from 'graphql'
|
||||
import { Kind, print } from 'graphql/language'
|
||||
|
||||
function identity(value) {
|
||||
return value
|
||||
}
|
||||
|
||||
function ensureObject(value) {
|
||||
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
||||
throw new TypeError(`JSONObject cannot represent non-object value: ${value}`)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
function parseObject(typeName, ast, variables) {
|
||||
const value = Object.create(null)
|
||||
ast.fields.forEach((field) => {
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
value[field.name.value] = parseLiteral(typeName, field.value, variables)
|
||||
})
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
function parseLiteral(typeName, ast, variables) {
|
||||
switch (ast.kind) {
|
||||
case Kind.STRING:
|
||||
case Kind.BOOLEAN:
|
||||
return ast.value
|
||||
case Kind.INT:
|
||||
case Kind.FLOAT:
|
||||
return parseFloat(ast.value)
|
||||
case Kind.OBJECT:
|
||||
return parseObject(typeName, ast, variables)
|
||||
case Kind.LIST:
|
||||
return ast.values.map((n) => parseLiteral(typeName, n, variables))
|
||||
case Kind.NULL:
|
||||
return null
|
||||
case Kind.VARIABLE:
|
||||
return variables ? variables[ast.name.value] : undefined
|
||||
default:
|
||||
throw new TypeError(`${typeName} cannot represent value: ${print(ast)}`)
|
||||
}
|
||||
}
|
||||
|
||||
// This named export is intended for users of CommonJS. Users of ES modules
|
||||
// should instead use the default export.
|
||||
export const GraphQLJSON = new GraphQLScalarType({
|
||||
name: 'JSON',
|
||||
description:
|
||||
'The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).',
|
||||
parseLiteral: (ast, variables) => parseLiteral('JSON', ast, variables),
|
||||
parseValue: identity,
|
||||
serialize: identity,
|
||||
specifiedByURL: 'http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf',
|
||||
})
|
||||
|
||||
export const GraphQLJSONObject = new GraphQLScalarType({
|
||||
name: 'JSONObject',
|
||||
description:
|
||||
'The `JSONObject` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).',
|
||||
parseLiteral: (ast, variables) => {
|
||||
if (ast.kind !== Kind.OBJECT) {
|
||||
throw new TypeError(`JSONObject cannot represent non-object value: ${print(ast)}`)
|
||||
}
|
||||
|
||||
return parseObject('JSONObject', ast, variables)
|
||||
},
|
||||
parseValue: ensureObject,
|
||||
serialize: ensureObject,
|
||||
specifiedByURL: 'http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf',
|
||||
})
|
||||
41
packages/graphql/src/resolvers/collections/count.ts
Normal file
41
packages/graphql/src/resolvers/collections/count.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import type { PayloadRequestWithData, Where } from 'payload/types'
|
||||
import type { Collection } from 'payload/types'
|
||||
|
||||
import { countOperation } from 'payload/operations'
|
||||
import { isolateObjectProperty } from 'payload/utilities'
|
||||
|
||||
import type { Context } from '../types.js'
|
||||
|
||||
export type Resolver = (
|
||||
_: unknown,
|
||||
args: {
|
||||
data: Record<string, unknown>
|
||||
locale?: string
|
||||
where?: Where
|
||||
},
|
||||
context: {
|
||||
req: PayloadRequestWithData
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
) => Promise<{ totalDocs: number }>
|
||||
|
||||
export default function countResolver(collection: Collection): Resolver {
|
||||
return async function resolver(_, args, context: Context) {
|
||||
let { req } = context
|
||||
const locale = req.locale
|
||||
const fallbackLocale = req.fallbackLocale
|
||||
req = isolateObjectProperty(req, 'locale')
|
||||
req = isolateObjectProperty(req, 'fallbackLocale')
|
||||
req.locale = args.locale || locale
|
||||
req.fallbackLocale = fallbackLocale
|
||||
|
||||
const options = {
|
||||
collection,
|
||||
req: isolateObjectProperty(req, 'transactionID'),
|
||||
where: args.where,
|
||||
}
|
||||
|
||||
const results = await countOperation(options)
|
||||
return results
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { GeneratedTypes } from 'payload'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
import type { Collection } from 'payload/types'
|
||||
import type { MarkOptional } from 'ts-essentials'
|
||||
|
||||
@@ -19,7 +19,7 @@ export type Resolver<TSlug extends keyof GeneratedTypes['collections']> = (
|
||||
locale?: string
|
||||
},
|
||||
context: {
|
||||
req: PayloadRequest
|
||||
req: PayloadRequestWithData
|
||||
},
|
||||
) => Promise<GeneratedTypes['collections'][TSlug]>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { GeneratedTypes } from 'payload'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
import type { Collection } from 'payload/types'
|
||||
|
||||
import { deleteByIDOperation } from 'payload/operations'
|
||||
@@ -14,7 +14,7 @@ export type Resolver<TSlug extends keyof GeneratedTypes['collections']> = (
|
||||
locale?: string
|
||||
},
|
||||
context: {
|
||||
req: PayloadRequest
|
||||
req: PayloadRequestWithData
|
||||
},
|
||||
) => Promise<GeneratedTypes['collections'][TSlug]>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { CollectionPermission, GlobalPermission } from 'payload/auth'
|
||||
import type { Collection, PayloadRequest } from 'payload/types'
|
||||
import type { Collection, PayloadRequestWithData } from 'payload/types'
|
||||
|
||||
import { docAccessOperation } from 'payload/operations'
|
||||
import { isolateObjectProperty } from 'payload/utilities'
|
||||
@@ -12,7 +12,7 @@ export type Resolver = (
|
||||
id: number | string
|
||||
},
|
||||
context: {
|
||||
req: PayloadRequest
|
||||
req: PayloadRequestWithData
|
||||
},
|
||||
) => Promise<CollectionPermission | GlobalPermission>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { GeneratedTypes } from 'payload'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
import type { Collection } from 'payload/types'
|
||||
|
||||
import { duplicateOperation } from 'payload/operations'
|
||||
@@ -16,7 +16,7 @@ export type Resolver<T> = (
|
||||
locale?: string
|
||||
},
|
||||
context: {
|
||||
req: PayloadRequest
|
||||
req: PayloadRequestWithData
|
||||
},
|
||||
) => Promise<T>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { PaginatedDocs } from 'payload/database'
|
||||
import type { PayloadRequest, Where } from 'payload/types'
|
||||
import type { PayloadRequestWithData, Where } from 'payload/types'
|
||||
import type { Collection } from 'payload/types'
|
||||
|
||||
import { findOperation } from 'payload/operations'
|
||||
@@ -20,7 +20,7 @@ export type Resolver = (
|
||||
where?: Where
|
||||
},
|
||||
context: {
|
||||
req: PayloadRequest
|
||||
req: PayloadRequestWithData
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
) => Promise<PaginatedDocs<any>>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { GeneratedTypes } from 'payload'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
import type { Collection } from 'payload/types'
|
||||
|
||||
import { findByIDOperation } from 'payload/operations'
|
||||
@@ -16,7 +16,7 @@ export type Resolver<T> = (
|
||||
locale?: string
|
||||
},
|
||||
context: {
|
||||
req: PayloadRequest
|
||||
req: PayloadRequestWithData
|
||||
},
|
||||
) => Promise<T>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
import type { Collection, TypeWithID } from 'payload/types'
|
||||
import type { TypeWithVersion } from 'payload/versions'
|
||||
|
||||
@@ -16,7 +16,7 @@ export type Resolver<T extends TypeWithID = any> = (
|
||||
locale?: string
|
||||
},
|
||||
context: {
|
||||
req: PayloadRequest
|
||||
req: PayloadRequestWithData
|
||||
},
|
||||
) => Promise<TypeWithVersion<T>>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { PaginatedDocs } from 'payload/database'
|
||||
import type { PayloadRequest, Where } from 'payload/types'
|
||||
import type { PayloadRequestWithData, Where } from 'payload/types'
|
||||
import type { Collection } from 'payload/types'
|
||||
|
||||
import { findVersionsOperation } from 'payload/operations'
|
||||
@@ -18,7 +18,7 @@ export type Resolver = (
|
||||
where: Where
|
||||
},
|
||||
context: {
|
||||
req: PayloadRequest
|
||||
req: PayloadRequestWithData
|
||||
},
|
||||
) => Promise<PaginatedDocs<any>>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
import type { Collection } from 'payload/types'
|
||||
|
||||
import { restoreVersionOperation } from 'payload/operations'
|
||||
@@ -12,7 +12,7 @@ export type Resolver = (
|
||||
id: number | string
|
||||
},
|
||||
context: {
|
||||
req: PayloadRequest
|
||||
req: PayloadRequestWithData
|
||||
},
|
||||
) => Promise<Document>
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user