Compare commits

...

17 Commits

Author SHA1 Message Date
Elliot DeNolf
87a1d698b2 chore(release): payload/2.14.0 [skip ci] 2024-04-24 15:40:04 -04:00
Dan Ribbens
c11600aac3 fix: bulk publish (#6006) 2024-04-24 15:04:50 -04:00
Elliot DeNolf
ad01c6784d fix: header filters (#5997) 2024-04-24 11:01:24 -04:00
Elliot DeNolf
62601c54a7 chore: update .vscode/settings.json 2024-04-24 08:59:45 -04:00
Elliot DeNolf
4a144ddc44 ci: register pr-title workflow 2024-04-23 23:31:13 -04:00
Patrik
9152a238d2 fix(db-postgres): row table names were not being built properly - v2 (#5961) 2024-04-22 16:56:03 -04:00
Mike Keefe
fc8b835264 docs: fix typo in admin custom components docs (#5944) 2024-04-21 20:39:32 -04:00
Elliot DeNolf
28ee5e34c3 chore(readme): add 3.0 beta announcement [skip ci] 2024-04-21 16:54:26 -04:00
Ricardo Domingues
e25886649f fix(db-postgres): Fixes nested groups inside nested blocks (#5882)
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2024-04-21 00:18:25 -04:00
Rafał Nawojczyk
985796be54 fix: min/max attributes missing from number input (#5779) 2024-04-20 23:36:32 -04:00
Dan Ribbens
bd8b5123b0 fix(db-postgres): extra version suffix added to table names (#5939) 2024-04-20 23:23:31 -04:00
Ritsu
c380deee4a feat: add count operation to collections (#5936) 2024-04-20 23:05:37 -04:00
Elliot DeNolf
0b12aac895 ci: bump actions node version (#5103) 2024-04-20 08:54:12 -04:00
Christian Gil
90d3f178ab feat(live-preview-vue): Vue Hook for Live Preview (#5925) 2024-04-20 06:45:18 -04:00
Patrik
a8c9625cde fix: removes equals & not_equals operators from fields with hasMany (#5885) 2024-04-19 11:41:39 -04:00
Elliot DeNolf
938d069523 chore(release): plugin-seo/2.3.1 [skip ci] 2024-04-19 11:38:41 -04:00
Elliot DeNolf
1a337ec223 chore(release): richtext-lexical/0.9.0 [skip ci] 2024-04-19 11:38:09 -04:00
60 changed files with 2080 additions and 1195 deletions

View File

@@ -52,14 +52,14 @@ jobs:
# https://github.com/actions/virtual-environments/issues/1187 # https://github.com/actions/virtual-environments/issues/1187
- name: tune linux network - name: tune linux network
run: sudo ethtool -K eth0 tx off rx off run: sudo ethtool -K eth0 tx off rx off
- name: Use Node.js 18 - name: Use Node.js 20
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 20
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@v2 uses: pnpm/action-setup@v3
with: with:
version: 8 version: 8
run_install: false run_install: false
@@ -69,7 +69,7 @@ jobs:
run: | run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v3 - uses: actions/cache@v4
name: Setup pnpm cache name: Setup pnpm cache
with: with:
path: ${{ env.STORE_PATH }} path: ${{ env.STORE_PATH }}
@@ -82,7 +82,7 @@ jobs:
- run: pnpm run build - run: pnpm run build
- name: Cache build - name: Cache build
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: ./* path: ./*
key: ${{ github.sha }}-${{ github.run_number }} key: ${{ github.sha }}-${{ github.run_number }}
@@ -108,19 +108,19 @@ jobs:
- name: tune linux network - name: tune linux network
run: sudo ethtool -K eth0 tx off rx off run: sudo ethtool -K eth0 tx off rx off
- name: Use Node.js 18 - name: Use Node.js 20
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 20
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@v2 uses: pnpm/action-setup@v3
with: with:
version: 8 version: 8
run_install: false run_install: false
- name: Restore build - name: Restore build
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: ./* path: ./*
key: ${{ github.sha }}-${{ github.run_number }} key: ${{ github.sha }}-${{ github.run_number }}
@@ -193,19 +193,19 @@ jobs:
- name: tune linux network - name: tune linux network
run: sudo ethtool -K eth0 tx off rx off run: sudo ethtool -K eth0 tx off rx off
- name: Use Node.js 18 - name: Use Node.js 20
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 20
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@v2 uses: pnpm/action-setup@v3
with: with:
version: 8 version: 8
run_install: false run_install: false
- name: Restore build - name: Restore build
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: ./* path: ./*
key: ${{ github.sha }}-${{ github.run_number }} key: ${{ github.sha }}-${{ github.run_number }}
@@ -234,19 +234,19 @@ jobs:
- name: tune linux network - name: tune linux network
run: sudo ethtool -K eth0 tx off rx off run: sudo ethtool -K eth0 tx off rx off
- name: Use Node.js 18 - name: Use Node.js 20
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 20
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@v2 uses: pnpm/action-setup@v3
with: with:
version: 8 version: 8
run_install: false run_install: false
- name: Restore build - name: Restore build
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: ./* path: ./*
key: ${{ github.sha }}-${{ github.run_number }} key: ${{ github.sha }}-${{ github.run_number }}
@@ -278,19 +278,19 @@ jobs:
- name: tune linux network - name: tune linux network
run: sudo ethtool -K eth0 tx off rx off run: sudo ethtool -K eth0 tx off rx off
- name: Use Node.js 18 - name: Use Node.js 20
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 20
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@v2 uses: pnpm/action-setup@v3
with: with:
version: 8 version: 8
run_install: false run_install: false
- name: Restore build - name: Restore build
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: ./* path: ./*
key: ${{ github.sha }}-${{ github.run_number }} key: ${{ github.sha }}-${{ github.run_number }}
@@ -319,19 +319,19 @@ jobs:
- name: tune linux network - name: tune linux network
run: sudo ethtool -K eth0 tx off rx off run: sudo ethtool -K eth0 tx off rx off
- name: Use Node.js 18 - name: Use Node.js 20
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 20
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@v2 uses: pnpm/action-setup@v3
with: with:
version: 8 version: 8
run_install: false run_install: false
- name: Restore build - name: Restore build
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: ./* path: ./*
key: ${{ github.sha }}-${{ github.run_number }} key: ${{ github.sha }}-${{ github.run_number }}
@@ -361,10 +361,10 @@ jobs:
- name: tune linux network - name: tune linux network
run: sudo ethtool -K eth0 tx off rx off run: sudo ethtool -K eth0 tx off rx off
- name: Use Node.js 18 - name: Use Node.js 20
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 20
- name: Start MongoDB - name: Start MongoDB
uses: supercharge/mongodb-github-action@1.10.0 uses: supercharge/mongodb-github-action@1.10.0

11
.github/workflows/pr-title.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
name: pr-title
on:
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Echo
run: echo "Register pr-title workflow"

View File

@@ -5,21 +5,21 @@
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": true "source.fixAll.eslint": "explicit"
} }
}, },
"[typescriptreact]": { "[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": true "source.fixAll.eslint": "explicit"
} }
}, },
"[javascript]": { "[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": true "source.fixAll.eslint": "explicit"
} }
}, },
"[json]": { "[json]": {

View File

@@ -1,3 +1,20 @@
## [2.14.0](https://github.com/payloadcms/payload/compare/v2.13.0...v2.14.0) (2024-04-24)
### Features
* add count operation to collections ([#5936](https://github.com/payloadcms/payload/issues/5936)) ([c380dee](https://github.com/payloadcms/payload/commit/c380deee4a1db82bce9fea264060000957a53eee))
### Bug Fixes
* bulk publish ([#6006](https://github.com/payloadcms/payload/issues/6006)) ([c11600a](https://github.com/payloadcms/payload/commit/c11600aac38cd67019765faf2a41e62df13e50cc))
* **db-postgres:** extra version suffix added to table names ([#5939](https://github.com/payloadcms/payload/issues/5939)) ([bd8b512](https://github.com/payloadcms/payload/commit/bd8b5123b0991e53eb209315897dbca10d14d45e))
* **db-postgres:** Fixes nested groups inside nested blocks ([#5882](https://github.com/payloadcms/payload/issues/5882)) ([e258866](https://github.com/payloadcms/payload/commit/e25886649fce414d5d47918f35ba2d4d2ba59174))
* **db-postgres:** row table names were not being built properly - v2 ([#5961](https://github.com/payloadcms/payload/issues/5961)) ([9152a23](https://github.com/payloadcms/payload/commit/9152a238d2982503e7f509350651b0ba3f83b1ec))
* header filters ([#5997](https://github.com/payloadcms/payload/issues/5997)) ([ad01c67](https://github.com/payloadcms/payload/commit/ad01c6784d283386dc819dfcd47455cad5accfaa))
* min/max attributes missing from number input ([#5779](https://github.com/payloadcms/payload/issues/5779)) ([985796b](https://github.com/payloadcms/payload/commit/985796be54b593af0a4934685ab8621b9badda10))
* removes `equals` & `not_equals` operators from fields with `hasMany` ([#5885](https://github.com/payloadcms/payload/issues/5885)) ([a8c9625](https://github.com/payloadcms/payload/commit/a8c9625cdec33476a5da87bcd9f010f9d7fb9a94))
## [2.13.0](https://github.com/payloadcms/payload/compare/v2.12.1...v2.13.0) (2024-04-19) ## [2.13.0](https://github.com/payloadcms/payload/compare/v2.12.1...v2.13.0) (2024-04-19)

View File

@@ -17,7 +17,7 @@
<hr/> <hr/>
> [!IMPORTANT] > [!IMPORTANT]
> 🎉 <strong>Payload 2.0 is now available!</strong> Read more in the <a target="_blank" href="https://payloadcms.com/blog/payload-2-0" rel="dofollow"><strong>announcement post</strong></a>. > 🎉 <strong>Payload 3.0 beta announced!</strong> Read more in the <a target="_blank" href="https://payloadcms.com/blog/30-beta-install-payload-into-any-nextjs-app-with-one-line" rel="dofollow"><strong>announcement post</strong></a>.
<h3>Benefits over a regular CMS</h3> <h3>Benefits over a regular CMS</h3>
<ul> <ul>

View File

@@ -657,7 +657,7 @@ As your admin customizations gets more complex you may want to share state betwe
### Styling Custom Components ### Styling Custom Components
Payload exports its SCSS variables and mixins for reuse in your own custom components. This is helpful in cases where you might want to style a custom input similarly to Payload's built-ini styling, so it blends more thoroughly into the existing admin UI. Payload exports its SCSS variables and mixins for reuse in your own custom components. This is helpful in cases where you might want to style a custom input similarly to Payload's built-in styling, so it blends more thoroughly into the existing admin UI.
To make use of Payload SCSS variables / mixins to use directly in your own components, you can import them as follows: To make use of Payload SCSS variables / mixins to use directly in your own components, you can import them as follows:

View File

@@ -43,11 +43,12 @@ export const PublicUser: CollectionConfig = {
**Payload will automatically open up the following queries:** **Payload will automatically open up the following queries:**
| Query Name | Operation | | Query Name | Operation |
| ------------------ | ------------------- | | ------------------ | ------------------- |
| **`PublicUser`** | `findByID` | | **`PublicUser`** | `findByID` |
| **`PublicUsers`** | `find` | | **`PublicUsers`** | `find` |
| **`mePublicUser`** | `me` auth operation | | **`countPublicUsers`** | `count` |
| **`mePublicUser`** | `me` auth operation |
**And the following mutations:** **And the following mutations:**

View File

@@ -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. 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: By default, all hooks accept the following args:
@@ -32,6 +32,10 @@ And return the following values:
If your front-end is tightly coupled to required fields, you should ensure that your UI does not break when these fields are removed. For example, if you are rendering something like `data.relatedPosts[0].title`, your page will break once you remove the first related post. To get around this, use conditional logic, optional chaining, or default values in your UI where needed. For example, `data?.relatedPosts?.[0]?.title`. If your front-end is tightly coupled to required fields, you should ensure that your UI does not break when these fields are removed. For example, if you are rendering something like `data.relatedPosts[0].title`, your page will break once you remove the first related post. To get around this, use conditional logic, optional chaining, or default values in your UI where needed. For example, `data?.relatedPosts?.[0]?.title`.
</Banner> </Banner>
<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>
### React ### React
If your front-end application is built with React or Next.js, you can use the `useLivePreview` hook that Payload provides. If your front-end application is built with React or Next.js, you can use the `useLivePreview` hook that Payload provides.
@@ -69,9 +73,40 @@ export const PageClient: React.FC<{
} }
``` ```
<Banner type="info"> ### Vue
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> 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 ## Building your own hook

View File

@@ -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 #### Update by ID
```js ```js

View File

@@ -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", operation: "Create",
method: "POST", method: "POST",

View File

@@ -0,0 +1,49 @@
import type { QueryOptions } from 'mongoose'
import type { Count } from 'payload/database'
import type { PayloadRequest } from 'payload/types'
import { flattenWhereToOperators } from 'payload/database'
import type { MongooseAdapter } from '.'
import { withSession } from './withSession'
export const count: Count = async function count(
this: MongooseAdapter,
{ collection, locale, req = {} as PayloadRequest, 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,
}
}

View File

@@ -11,6 +11,7 @@ import { createDatabaseAdapter } from 'payload/database'
import type { CollectionModel, GlobalModel } from './types' import type { CollectionModel, GlobalModel } from './types'
import { connect } from './connect' import { connect } from './connect'
import { count } from './count'
import { create } from './create' import { create } from './create'
import { createGlobal } from './createGlobal' import { createGlobal } from './createGlobal'
import { createGlobalVersion } from './createGlobalVersion' import { createGlobalVersion } from './createGlobalVersion'
@@ -108,6 +109,7 @@ export function mongooseAdapter({
collections: {}, collections: {},
connectOptions: connectOptions || {}, connectOptions: connectOptions || {},
connection: undefined, connection: undefined,
count,
disableIndexHints, disableIndexHints,
globals: undefined, globals: undefined,
mongoMemoryServer: undefined, mongoMemoryServer: undefined,
@@ -115,7 +117,6 @@ export function mongooseAdapter({
transactionOptions: transactionOptions === false ? undefined : transactionOptions, transactionOptions: transactionOptions === false ? undefined : transactionOptions,
url, url,
versions: {}, versions: {},
// DatabaseAdapter // DatabaseAdapter
beginTransaction: transactionOptions ? beginTransaction : undefined, beginTransaction: transactionOptions ? beginTransaction : undefined,
commitTransaction, commitTransaction,

View File

@@ -0,0 +1,65 @@
import type { Count } from 'payload/database'
import type { SanitizedCollectionConfig } from 'payload/types'
import { sql } from 'drizzle-orm'
import type { ChainedMethods } from './find/chainMethods'
import type { PostgresAdapter } from './types'
import { chainMethods } from './find/chainMethods'
import buildQuery from './queries/buildQuery'
import { getTableName } from './schema/getTableName'
export const count: Count = async function count(
this: PostgresAdapter,
{ collection, locale, req, where: whereArg },
) {
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
const tableName = getTableName({
adapter: this,
config: collectionConfig,
})
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) }
}

View File

@@ -7,6 +7,7 @@ import { createDatabaseAdapter } from 'payload/database'
import type { Args, PostgresAdapter, PostgresAdapterResult } from './types' import type { Args, PostgresAdapter, PostgresAdapterResult } from './types'
import { connect } from './connect' import { connect } from './connect'
import { count } from './count'
import { create } from './create' import { create } from './create'
import { createGlobal } from './createGlobal' import { createGlobal } from './createGlobal'
import { createGlobalVersion } from './createGlobalVersion' import { createGlobalVersion } from './createGlobalVersion'
@@ -70,6 +71,7 @@ export function postgresAdapter(args: Args): PostgresAdapterResult {
beginTransaction, beginTransaction,
commitTransaction, commitTransaction,
connect, connect,
count,
create, create,
createGlobal, createGlobal,
createGlobalVersion, createGlobalVersion,

View File

@@ -228,7 +228,6 @@ export const traverseFields = ({
prefix: `enum_${newTableName}_`, prefix: `enum_${newTableName}_`,
target: 'enumName', target: 'enumName',
throwValidationError, throwValidationError,
versions,
}) })
adapter.enums[enumName] = pgEnum( adapter.enums[enumName] = pgEnum(
@@ -249,7 +248,6 @@ export const traverseFields = ({
parentTableName: newTableName, parentTableName: newTableName,
prefix: `${newTableName}_`, prefix: `${newTableName}_`,
throwValidationError, throwValidationError,
versions,
}) })
const baseColumns: Record<string, PgColumnBuilder> = { const baseColumns: Record<string, PgColumnBuilder> = {
order: integer('order').notNull(), order: integer('order').notNull(),
@@ -659,7 +657,7 @@ export const traverseFields = ({
indexes, indexes,
localesColumns, localesColumns,
localesIndexes, localesIndexes,
newTableName: parentTableName, newTableName,
parentTableName, parentTableName,
relationsToBuild, relationsToBuild,
relationships, relationships,

View File

@@ -28,7 +28,7 @@ const getFlattenedFieldNames = (
} }
if (fieldHasSubFields(field)) { 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)] return [...fieldsToUse, ...getFlattenedFieldNames(field.fields, fieldPrefix)]
} }
@@ -36,7 +36,7 @@ const getFlattenedFieldNames = (
return [ return [
...fieldsToUse, ...fieldsToUse,
...field.tabs.reduce((tabFields, tab) => { ...field.tabs.reduce((tabFields, tab) => {
fieldPrefix = 'name' in tab ? `${prefix}.${tab.name}` : prefix fieldPrefix = 'name' in tab ? `${prefix}_${tab.name}` : prefix
return [ return [
...tabFields, ...tabFields,
...(tabHasName(tab) ...(tabHasName(tab)
@@ -51,7 +51,7 @@ const getFlattenedFieldNames = (
return [ return [
...fieldsToUse, ...fieldsToUse,
{ {
name: `${fieldPrefix?.replace('.', '_') || ''}${field.name}`, name: `${fieldPrefix}${field.name}`,
localized: field.localized, localized: field.localized,
}, },
] ]
@@ -84,7 +84,11 @@ export const validateExistingBlockIsIdentical = ({
if (missingField) { if (missingField) {
throw new InvalidConfiguration( 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.`,
) )
} }

View File

@@ -0,0 +1,10 @@
.tmp
**/.git
**/.hg
**/.pnp.*
**/.svn
**/.yarn/**
**/build
**/dist/**
**/node_modules
**/temp

View File

@@ -0,0 +1,37 @@
/** @type {import('prettier').Config} */
module.exports = {
extends: ['@payloadcms'],
overrides: [
{
extends: ['plugin:@typescript-eslint/disable-type-checked'],
files: ['*.js', '*.cjs', '*.json', '*.md', '*.yml', '*.yaml'],
},
{
files: ['package.json', 'tsconfig.json'],
rules: {
'perfectionist/sort-array-includes': 'off',
'perfectionist/sort-astro-attributes': 'off',
'perfectionist/sort-classes': 'off',
'perfectionist/sort-enums': 'off',
'perfectionist/sort-exports': 'off',
'perfectionist/sort-imports': 'off',
'perfectionist/sort-interfaces': 'off',
'perfectionist/sort-jsx-props': 'off',
'perfectionist/sort-keys': 'off',
'perfectionist/sort-maps': 'off',
'perfectionist/sort-named-exports': 'off',
'perfectionist/sort-named-imports': 'off',
'perfectionist/sort-object-types': 'off',
'perfectionist/sort-objects': 'off',
'perfectionist/sort-svelte-attributes': 'off',
'perfectionist/sort-union-types': 'off',
'perfectionist/sort-vue-attributes': 'off',
},
},
],
parserOptions: {
project: ['./tsconfig.json'],
tsconfigRootDir: __dirname,
},
root: true,
}

View File

@@ -0,0 +1,10 @@
.tmp
**/.git
**/.hg
**/.pnp.*
**/.svn
**/.yarn/**
**/build
**/dist/**
**/node_modules
**/temp

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://json.schemastore.org/swcrc",
"sourceMaps": "inline",
"jsc": {
"target": "esnext",
"parser": {
"syntax": "typescript",
"tsx": true,
"dts": true
}
},
"module": {
"type": "commonjs"
}
}

View File

@@ -0,0 +1,49 @@
{
"name": "@payloadcms/live-preview-vue",
"version": "0.1.0",
"description": "The official live preview Vue SDK for Payload",
"repository": {
"type": "git",
"url": "https://github.com/payloadcms/payload.git",
"directory": "packages/live-preview-vue"
},
"license": "MIT",
"homepage": "https://payloadcms.com",
"author": "Payload CMS, Inc.",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"build": "pnpm copyfiles && pnpm build:swc && pnpm build:types",
"build:swc": "swc ./src -d ./dist --config-file .swcrc",
"build:types": "tsc --emitDeclarationOnly --outDir dist",
"clean": "rimraf {dist,*.tsbuildinfo}",
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
"prepublishOnly": "pnpm clean && pnpm build"
},
"dependencies": {
"@payloadcms/live-preview": "workspace:^0.x"
},
"devDependencies": {
"@payloadcms/eslint-config": "workspace:*",
"vue": "^3.0.0",
"payload": "workspace:*"
},
"peerDependencies": {
"vue": "^3.0.0"
},
"exports": {
".": {
"default": "./src/index.ts",
"types": "./src/index.ts"
}
},
"publishConfig": {
"exports": null,
"main": "./dist/index.js",
"registry": "https://registry.npmjs.org/",
"types": "./dist/index.d.ts"
},
"files": [
"dist"
]
}

View File

@@ -0,0 +1,58 @@
import type { Ref } from 'vue'
import { ready, subscribe, unsubscribe } from '@payloadcms/live-preview'
import { onMounted, onUnmounted, ref } from 'vue'
/**
* Vue composable to implement Payload CMS Live Preview.
*
* {@link https://payloadcms.com/docs/live-preview/frontend View the documentation}
*/
export const useLivePreview = <T>(props: {
apiRoute?: string
depth?: number
initialData: T
serverURL: string
}): {
data: Ref<T>
isLoading: Ref<boolean>
} => {
const { apiRoute, depth, initialData, serverURL } = props
const data = ref(initialData) as Ref<T>
const isLoading = ref(true)
const hasSentReadyMessage = ref(false)
const onChange = (mergedData: T) => {
data.value = mergedData
isLoading.value = false
}
let subscription: (event: MessageEvent) => void
onMounted(() => {
subscription = subscribe({
apiRoute,
callback: onChange,
depth,
initialData,
serverURL,
})
if (!hasSentReadyMessage.value) {
hasSentReadyMessage.value = true
ready({
serverURL,
})
}
})
onUnmounted(() => {
unsubscribe(subscription)
})
return {
data,
isLoading,
}
}

View File

@@ -0,0 +1,25 @@
{
"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. */,
"jsx": "react"
},
"exclude": [
"dist",
"build",
"tests",
"test",
"node_modules",
".eslintrc.js",
"src/**/*.spec.js",
"src/**/*.spec.jsx",
"src/**/*.spec.ts",
"src/**/*.spec.tsx"
],
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.d.ts", "src/**/*.json"],
"references": [{ "path": "../payload" }] // db-mongodb depends on payload
}

View File

@@ -1,6 +1,6 @@
{ {
"name": "payload", "name": "payload",
"version": "2.13.0", "version": "2.14.0",
"description": "Node, React and MongoDB Headless CMS and Application Framework", "description": "Node, React and MongoDB Headless CMS and Application Framework",
"license": "MIT", "license": "MIT",
"main": "./dist/index.js", "main": "./dist/index.js",

View File

@@ -77,6 +77,15 @@ const contains = {
value: 'contains', value: 'contains',
} }
const filterOperators = (operators, hasMany = false) => {
if (hasMany) {
return operators.filter(
(operator) => operator.value !== 'equals' && operator.value !== 'not_equals',
)
}
return operators
}
const fieldTypeConditions = { const fieldTypeConditions = {
checkbox: { checkbox: {
component: 'Text', component: 'Text',
@@ -100,7 +109,7 @@ const fieldTypeConditions = {
}, },
number: { number: {
component: 'Number', component: 'Number',
operators: [...base, ...numeric], operators: (hasMany) => filterOperators([...base, ...numeric], hasMany),
}, },
point: { point: {
component: 'Point', component: 'Point',
@@ -120,11 +129,11 @@ const fieldTypeConditions = {
}, },
select: { select: {
component: 'Select', component: 'Select',
operators: [...base], operators: (hasMany) => filterOperators([...base], hasMany),
}, },
text: { text: {
component: 'Text', component: 'Text',
operators: [...base, like, contains], operators: (hasMany) => filterOperators([...base, like, contains], hasMany),
}, },
textarea: { textarea: {
component: 'Text', component: 'Text',

View File

@@ -22,36 +22,44 @@ const baseClass = 'where-builder'
const reduceFields = (fields, i18n) => const reduceFields = (fields, i18n) =>
flattenTopLevelFields(fields).reduce((reduced, field) => { flattenTopLevelFields(fields).reduce((reduced, field) => {
let operators = []
if (typeof fieldTypes[field.type] === 'object') { if (typeof fieldTypes[field.type] === 'object') {
const operatorKeys = new Set() if (typeof fieldTypes[field.type].operators === 'function') {
const operators = fieldTypes[field.type].operators.reduce((acc, operator) => { operators = fieldTypes[field.type].operators(
if (!operatorKeys.has(operator.value)) { 'hasMany' in field && field.hasMany ? true : false,
operatorKeys.add(operator.value) )
return [ } else {
...acc, operators = fieldTypes[field.type].operators
{
...operator,
label: i18n.t(`operators:${operator.label}`),
},
]
}
return acc
}, [])
const formattedField = {
label: getTranslation(field.label || field.name, i18n),
value: field.name,
...fieldTypes[field.type],
operators,
props: {
...field,
},
} }
return [...reduced, formattedField]
} }
return reduced const operatorKeys = new Set()
const filteredOperators = operators.reduce((acc, operator) => {
if (!operatorKeys.has(operator.value)) {
operatorKeys.add(operator.value)
return [
...acc,
{
...operator,
label: i18n.t(`operators:${operator.label}`),
},
]
}
return acc
}, [])
const formattedField = {
label: getTranslation(field.label || field.name, i18n),
value: field.name,
...fieldTypes[field.type],
operators: filteredOperators,
props: {
...field,
},
}
return [...reduced, formattedField]
}, []) }, [])
/** /**
@@ -185,7 +193,7 @@ const WhereBuilder: React.FC<Props> = (props) => {
iconStyle="with-border" iconStyle="with-border"
onClick={() => { onClick={() => {
if (reducedFields.length > 0) if (reducedFields.length > 0)
dispatchConditions({ field: reducedFields[0].value, type: 'add' }) dispatchConditions({ type: 'add', field: reducedFields[0].value })
}} }}
> >
{t('or')} {t('or')}
@@ -203,7 +211,7 @@ const WhereBuilder: React.FC<Props> = (props) => {
iconStyle="with-border" iconStyle="with-border"
onClick={() => { onClick={() => {
if (reducedFields.length > 0) if (reducedFields.length > 0)
dispatchConditions({ field: reducedFields[0].value, type: 'add' }) dispatchConditions({ type: 'add', field: reducedFields[0].value })
}} }}
> >
{t('addFilter')} {t('addFilter')}

View File

@@ -166,6 +166,8 @@ const NumberField: React.FC<Props> = (props) => {
<input <input
disabled={readOnly} disabled={readOnly}
id={`field-${path.replace(/\./g, '__')}`} id={`field-${path.replace(/\./g, '__')}`}
max={max}
min={min}
name={path} name={path}
onChange={handleChange} onChange={handleChange}
onWheel={(e) => { onWheel={(e) => {

View File

@@ -11,6 +11,7 @@ import registerFirstUserHandler from '../auth/requestHandlers/registerFirstUser'
import resetPassword from '../auth/requestHandlers/resetPassword' import resetPassword from '../auth/requestHandlers/resetPassword'
import unlock from '../auth/requestHandlers/unlock' import unlock from '../auth/requestHandlers/unlock'
import verifyEmail from '../auth/requestHandlers/verifyEmail' import verifyEmail from '../auth/requestHandlers/verifyEmail'
import count from './requestHandlers/count'
import create from './requestHandlers/create' import create from './requestHandlers/create'
import deleteHandler from './requestHandlers/delete' import deleteHandler from './requestHandlers/delete'
import deleteByID from './requestHandlers/deleteByID' import deleteByID from './requestHandlers/deleteByID'
@@ -124,6 +125,11 @@ const buildEndpoints = (collection: SanitizedCollectionConfig): Endpoint[] => {
method: 'post', method: 'post',
path: '/', path: '/',
}, },
{
handler: count,
method: 'get',
path: '/count',
},
{ {
handler: docAccessRequestHandler, handler: docAccessRequestHandler,
method: 'get', method: 'get',

View File

@@ -30,6 +30,7 @@ import type { AfterOperationArg, AfterOperationMap } from '../operations/utils'
export type HookOperationType = export type HookOperationType =
| 'autosave' | 'autosave'
| 'count'
| 'create' | 'create'
| 'delete' | 'delete'
| 'forgotPassword' | 'forgotPassword'
@@ -465,6 +466,7 @@ export type Collection = {
config: SanitizedCollectionConfig config: SanitizedCollectionConfig
graphQL?: { graphQL?: {
JWT: GraphQLObjectType JWT: GraphQLObjectType
countType: GraphQLObjectType
mutationInputType: GraphQLNonNull<any> mutationInputType: GraphQLNonNull<any>
paginatedType: GraphQLObjectType paginatedType: GraphQLObjectType
type: GraphQLObjectType type: GraphQLObjectType

View File

@@ -30,8 +30,10 @@ import buildPaginatedListType from '../../graphql/schema/buildPaginatedListType'
import { buildPolicyType } from '../../graphql/schema/buildPoliciesType' import { buildPolicyType } from '../../graphql/schema/buildPoliciesType'
import buildWhereInputType from '../../graphql/schema/buildWhereInputType' import buildWhereInputType from '../../graphql/schema/buildWhereInputType'
import formatName from '../../graphql/utilities/formatName' import formatName from '../../graphql/utilities/formatName'
import flattenFields from '../../utilities/flattenTopLevelFields'
import { formatNames, toWords } from '../../utilities/formatLabels' import { formatNames, toWords } from '../../utilities/formatLabels'
import { buildVersionCollectionFields } from '../../versions/buildCollectionFields' import { buildVersionCollectionFields } from '../../versions/buildCollectionFields'
import countResolver from './resolvers/count'
import createResolver from './resolvers/create' import createResolver from './resolvers/create'
import getDeleteResolver from './resolvers/delete' import getDeleteResolver from './resolvers/delete'
import { docAccessResolver } from './resolvers/docAccess' import { docAccessResolver } from './resolvers/docAccess'
@@ -41,7 +43,6 @@ import findVersionByIDResolver from './resolvers/findVersionByID'
import findVersionsResolver from './resolvers/findVersions' import findVersionsResolver from './resolvers/findVersions'
import restoreVersionResolver from './resolvers/restoreVersion' import restoreVersionResolver from './resolvers/restoreVersion'
import updateResolver from './resolvers/update' import updateResolver from './resolvers/update'
import flattenFields from '../../utilities/flattenTopLevelFields'
function initCollectionsGraphQL(payload: Payload): void { function initCollectionsGraphQL(payload: Payload): void {
Object.keys(payload.collections).forEach((slug) => { Object.keys(payload.collections).forEach((slug) => {
@@ -118,9 +119,9 @@ function initCollectionsGraphQL(payload: Payload): void {
if (config.auth && !config.auth.disableLocalStrategy) { if (config.auth && !config.auth.disableLocalStrategy) {
fields.push({ fields.push({
name: 'password', name: 'password',
type: 'text',
label: 'Password', label: 'Password',
required: true, required: true,
type: 'text',
}) })
} }
@@ -146,6 +147,7 @@ function initCollectionsGraphQL(payload: Payload): void {
} }
payload.Query.fields[singularName] = { payload.Query.fields[singularName] = {
type: collection.graphQL.type,
args: { args: {
id: { type: new GraphQLNonNull(idType) }, id: { type: new GraphQLNonNull(idType) },
draft: { type: GraphQLBoolean }, draft: { type: GraphQLBoolean },
@@ -157,10 +159,10 @@ function initCollectionsGraphQL(payload: Payload): void {
: {}), : {}),
}, },
resolve: findByIDResolver(collection), resolve: findByIDResolver(collection),
type: collection.graphQL.type,
} }
payload.Query.fields[pluralName] = { payload.Query.fields[pluralName] = {
type: buildPaginatedListType(pluralName, collection.graphQL.type),
args: { args: {
draft: { type: GraphQLBoolean }, draft: { type: GraphQLBoolean },
where: { type: collection.graphQL.whereInputType }, where: { type: collection.graphQL.whereInputType },
@@ -175,23 +177,42 @@ function initCollectionsGraphQL(payload: Payload): void {
sort: { type: GraphQLString }, sort: { type: GraphQLString },
}, },
resolve: findResolver(collection), resolve: findResolver(collection),
type: buildPaginatedListType(pluralName, collection.graphQL.type), }
payload.Query.fields[`count${pluralName}`] = {
type: new GraphQLObjectType({
name: `count${pluralName}`,
fields: {
totalDocs: { type: GraphQLInt },
},
}),
args: {
draft: { type: GraphQLBoolean },
where: { type: collection.graphQL.whereInputType },
...(payload.config.localization
? {
locale: { type: payload.types.localeInputType },
}
: {}),
},
resolve: countResolver(collection),
} }
payload.Query.fields[`docAccess${singularName}`] = { payload.Query.fields[`docAccess${singularName}`] = {
type: buildPolicyType({
type: 'collection',
entity: config,
scope: 'docAccess',
typeSuffix: 'DocAccess',
}),
args: { args: {
id: { type: new GraphQLNonNull(idType) }, id: { type: new GraphQLNonNull(idType) },
}, },
resolve: docAccessResolver(), resolve: docAccessResolver(),
type: buildPolicyType({
entity: config,
scope: 'docAccess',
type: 'collection',
typeSuffix: 'DocAccess',
}),
} }
payload.Mutation.fields[`create${singularName}`] = { payload.Mutation.fields[`create${singularName}`] = {
type: collection.graphQL.type,
args: { args: {
...(createMutationInputType ...(createMutationInputType
? { data: { type: collection.graphQL.mutationInputType } } ? { data: { type: collection.graphQL.mutationInputType } }
@@ -204,10 +225,10 @@ function initCollectionsGraphQL(payload: Payload): void {
: {}), : {}),
}, },
resolve: createResolver(collection), resolve: createResolver(collection),
type: collection.graphQL.type,
} }
payload.Mutation.fields[`update${singularName}`] = { payload.Mutation.fields[`update${singularName}`] = {
type: collection.graphQL.type,
args: { args: {
id: { type: new GraphQLNonNull(idType) }, id: { type: new GraphQLNonNull(idType) },
autosave: { type: GraphQLBoolean }, autosave: { type: GraphQLBoolean },
@@ -222,15 +243,14 @@ function initCollectionsGraphQL(payload: Payload): void {
: {}), : {}),
}, },
resolve: updateResolver(collection), resolve: updateResolver(collection),
type: collection.graphQL.type,
} }
payload.Mutation.fields[`delete${singularName}`] = { payload.Mutation.fields[`delete${singularName}`] = {
type: collection.graphQL.type,
args: { args: {
id: { type: new GraphQLNonNull(idType) }, id: { type: new GraphQLNonNull(idType) },
}, },
resolve: getDeleteResolver(collection), resolve: getDeleteResolver(collection),
type: collection.graphQL.type,
} }
if (config.versions) { if (config.versions) {
@@ -243,13 +263,13 @@ function initCollectionsGraphQL(payload: Payload): void {
}, },
{ {
name: 'createdAt', name: 'createdAt',
label: 'Created At',
type: 'date', type: 'date',
label: 'Created At',
}, },
{ {
name: 'updatedAt', name: 'updatedAt',
label: 'Updated At',
type: 'date', type: 'date',
label: 'Updated At',
}, },
] ]
@@ -262,6 +282,7 @@ function initCollectionsGraphQL(payload: Payload): void {
}) })
payload.Query.fields[`version${formatName(singularName)}`] = { payload.Query.fields[`version${formatName(singularName)}`] = {
type: collection.graphQL.versionType,
args: { args: {
id: { type: versionIDType }, id: { type: versionIDType },
...(payload.config.localization ...(payload.config.localization
@@ -272,9 +293,12 @@ function initCollectionsGraphQL(payload: Payload): void {
: {}), : {}),
}, },
resolve: findVersionByIDResolver(collection), resolve: findVersionByIDResolver(collection),
type: collection.graphQL.versionType,
} }
payload.Query.fields[`versions${pluralName}`] = { payload.Query.fields[`versions${pluralName}`] = {
type: buildPaginatedListType(
`versions${formatName(pluralName)}`,
collection.graphQL.versionType,
),
args: { args: {
where: { where: {
type: buildWhereInputType({ type: buildWhereInputType({
@@ -295,17 +319,13 @@ function initCollectionsGraphQL(payload: Payload): void {
sort: { type: GraphQLString }, sort: { type: GraphQLString },
}, },
resolve: findVersionsResolver(collection), resolve: findVersionsResolver(collection),
type: buildPaginatedListType(
`versions${formatName(pluralName)}`,
collection.graphQL.versionType,
),
} }
payload.Mutation.fields[`restoreVersion${formatName(singularName)}`] = { payload.Mutation.fields[`restoreVersion${formatName(singularName)}`] = {
type: collection.graphQL.type,
args: { args: {
id: { type: versionIDType }, id: { type: versionIDType },
}, },
resolve: restoreVersionResolver(collection), resolve: restoreVersionResolver(collection),
type: collection.graphQL.type,
} }
} }
@@ -315,8 +335,8 @@ function initCollectionsGraphQL(payload: Payload): void {
: [ : [
{ {
name: 'email', name: 'email',
required: true,
type: 'email', type: 'email',
required: true,
}, },
] ]
collection.graphQL.JWT = buildObjectType({ collection.graphQL.JWT = buildObjectType({
@@ -326,8 +346,8 @@ function initCollectionsGraphQL(payload: Payload): void {
...authFields, ...authFields,
{ {
name: 'collection', name: 'collection',
required: true,
type: 'text', type: 'text',
required: true,
}, },
], ],
parentName: formatName(`${slug}JWT`), parentName: formatName(`${slug}JWT`),
@@ -335,7 +355,6 @@ function initCollectionsGraphQL(payload: Payload): void {
}) })
payload.Query.fields[`me${singularName}`] = { payload.Query.fields[`me${singularName}`] = {
resolve: me(collection),
type: new GraphQLObjectType({ type: new GraphQLObjectType({
name: formatName(`${slug}Me`), name: formatName(`${slug}Me`),
fields: { fields: {
@@ -353,18 +372,15 @@ function initCollectionsGraphQL(payload: Payload): void {
}, },
}, },
}), }),
resolve: me(collection),
} }
payload.Query.fields[`initialized${singularName}`] = { payload.Query.fields[`initialized${singularName}`] = {
resolve: init(collection.config.slug),
type: GraphQLBoolean, type: GraphQLBoolean,
resolve: init(collection.config.slug),
} }
payload.Mutation.fields[`refreshToken${singularName}`] = { payload.Mutation.fields[`refreshToken${singularName}`] = {
args: {
token: { type: GraphQLString },
},
resolve: refresh(collection),
type: new GraphQLObjectType({ type: new GraphQLObjectType({
name: formatName(`${slug}Refreshed${singularName}`), name: formatName(`${slug}Refreshed${singularName}`),
fields: { fields: {
@@ -379,30 +395,29 @@ function initCollectionsGraphQL(payload: Payload): void {
}, },
}, },
}), }),
args: {
token: { type: GraphQLString },
},
resolve: refresh(collection),
} }
payload.Mutation.fields[`logout${singularName}`] = { payload.Mutation.fields[`logout${singularName}`] = {
resolve: logout(collection),
type: GraphQLString, type: GraphQLString,
resolve: logout(collection),
} }
if (!config.auth.disableLocalStrategy) { if (!config.auth.disableLocalStrategy) {
if (config.auth.maxLoginAttempts > 0) { if (config.auth.maxLoginAttempts > 0) {
payload.Mutation.fields[`unlock${singularName}`] = { payload.Mutation.fields[`unlock${singularName}`] = {
type: new GraphQLNonNull(GraphQLBoolean),
args: { args: {
email: { type: new GraphQLNonNull(GraphQLString) }, email: { type: new GraphQLNonNull(GraphQLString) },
}, },
resolve: unlock(collection), resolve: unlock(collection),
type: new GraphQLNonNull(GraphQLBoolean),
} }
} }
payload.Mutation.fields[`login${singularName}`] = { payload.Mutation.fields[`login${singularName}`] = {
args: {
email: { type: GraphQLString },
password: { type: GraphQLString },
},
resolve: login(collection),
type: new GraphQLObjectType({ type: new GraphQLObjectType({
name: formatName(`${slug}LoginResult`), name: formatName(`${slug}LoginResult`),
fields: { fields: {
@@ -417,24 +432,24 @@ function initCollectionsGraphQL(payload: Payload): void {
}, },
}, },
}), }),
args: {
email: { type: GraphQLString },
password: { type: GraphQLString },
},
resolve: login(collection),
} }
payload.Mutation.fields[`forgotPassword${singularName}`] = { payload.Mutation.fields[`forgotPassword${singularName}`] = {
type: new GraphQLNonNull(GraphQLBoolean),
args: { args: {
disableEmail: { type: GraphQLBoolean }, disableEmail: { type: GraphQLBoolean },
email: { type: new GraphQLNonNull(GraphQLString) }, email: { type: new GraphQLNonNull(GraphQLString) },
expiration: { type: GraphQLInt }, expiration: { type: GraphQLInt },
}, },
resolve: forgotPassword(collection), resolve: forgotPassword(collection),
type: new GraphQLNonNull(GraphQLBoolean),
} }
payload.Mutation.fields[`resetPassword${singularName}`] = { payload.Mutation.fields[`resetPassword${singularName}`] = {
args: {
password: { type: GraphQLString },
token: { type: GraphQLString },
},
resolve: resetPassword(collection),
type: new GraphQLObjectType({ type: new GraphQLObjectType({
name: formatName(`${slug}ResetPassword`), name: formatName(`${slug}ResetPassword`),
fields: { fields: {
@@ -442,14 +457,19 @@ function initCollectionsGraphQL(payload: Payload): void {
user: { type: collection.graphQL.type }, user: { type: collection.graphQL.type },
}, },
}), }),
args: {
password: { type: GraphQLString },
token: { type: GraphQLString },
},
resolve: resetPassword(collection),
} }
payload.Mutation.fields[`verifyEmail${singularName}`] = { payload.Mutation.fields[`verifyEmail${singularName}`] = {
type: GraphQLBoolean,
args: { args: {
token: { type: GraphQLString }, token: { type: GraphQLString },
}, },
resolve: verifyEmail(collection), resolve: verifyEmail(collection),
type: GraphQLBoolean,
} }
} }
} }

View File

@@ -0,0 +1,40 @@
import type { PayloadRequest } from '../../../express/types'
import type { Where } from '../../../types'
import type { Collection } from '../../config/types'
import isolateObjectProperty from '../../../utilities/isolateObjectProperty'
import count from '../../operations/count'
export type Resolver = (
_: unknown,
args: {
data: Record<string, unknown>
locale?: string
where?: Where
},
context: {
req: PayloadRequest
res: Response
},
) => Promise<{ totalDocs: number }>
export default function findResolver(collection: Collection): Resolver {
return async function resolver(_, args, 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<PayloadRequest>(req, 'transactionID'),
where: args.where,
}
const results = await count(options)
return results
}
}

View File

@@ -0,0 +1,113 @@
import type { AccessResult } from '../../config/types'
import type { PayloadRequest, Where } from '../../types/index'
import type { Collection, TypeWithID } from '../config/types'
import executeAccess from '../../auth/executeAccess'
import { combineQueries } from '../../database/combineQueries'
import { validateQueryPaths } from '../../database/queryValidation/validateQueryPaths'
import { commitTransaction } from '../../utilities/commitTransaction'
import { initTransaction } from '../../utilities/initTransaction'
import { killTransaction } from '../../utilities/killTransaction'
import { buildAfterOperation } from './utils'
export type Arguments = {
collection: Collection
disableErrors?: boolean
overrideAccess?: boolean
req?: PayloadRequest
where?: Where
}
async function count<T extends TypeWithID & Record<string, unknown>>(
incomingArgs: Arguments,
): Promise<{ totalDocs: number }> {
let args = incomingArgs
try {
const shouldCommit = await initTransaction(args.req)
// /////////////////////////////////////
// beforeOperation - Collection
// /////////////////////////////////////
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
await priorHook
args =
(await hook({
args,
collection: args.collection.config,
context: args.req.context,
operation: 'count',
req: args.req,
})) || args
}, Promise.resolve())
const {
collection: { config: collectionConfig },
disableErrors,
overrideAccess,
req: { payload },
req,
where,
} = args
// /////////////////////////////////////
// Access
// /////////////////////////////////////
let accessResult: AccessResult
if (!overrideAccess) {
accessResult = await executeAccess({ disableErrors, req }, collectionConfig.access.read)
// If errors are disabled, and access returns false, return empty results
if (accessResult === false) {
return {
totalDocs: 0,
}
}
}
let result: { totalDocs: number }
const fullWhere = combineQueries(where, accessResult)
await validateQueryPaths({
collectionConfig,
overrideAccess,
req,
where,
})
result = await payload.db.count({
collection: collectionConfig.slug,
req,
where: fullWhere,
})
// /////////////////////////////////////
// afterOperation - Collection
// /////////////////////////////////////
result = await buildAfterOperation<T>({
args,
collection: collectionConfig,
operation: 'count',
result,
})
// /////////////////////////////////////
// Return results
// /////////////////////////////////////
if (shouldCommit) await commitTransaction(req)
return result
} catch (error: unknown) {
await killTransaction(args.req)
throw error
}
}
export default count

View File

@@ -0,0 +1,47 @@
import type { GeneratedTypes } from '../../../'
import type { PayloadRequest, RequestContext } from '../../../express/types'
import type { Payload } from '../../../payload'
import type { Document, Where } from '../../../types'
import { APIError } from '../../../errors'
import { createLocalReq } from '../../../utilities/createLocalReq'
import count from '../count'
export type Options<T extends keyof GeneratedTypes['collections']> = {
collection: T
/**
* context, which will then be passed to req.context, which can be read by hooks
*/
context?: RequestContext
disableErrors?: boolean
locale?: string
overrideAccess?: boolean
req?: PayloadRequest
user?: Document
where?: Where
}
export default async function countLocal<T extends keyof GeneratedTypes['collections']>(
payload: Payload,
options: Options<T>,
): Promise<{ totalDocs: number }> {
const { collection: collectionSlug, disableErrors, overrideAccess = true, where } = options
const collection = payload.collections[collectionSlug]
if (!collection) {
throw new APIError(
`The collection with slug ${String(collectionSlug)} can't be found. Find Operation.`,
)
}
const req = createLocalReq(options, payload)
return count<GeneratedTypes['collections'][T]>({
collection,
disableErrors,
overrideAccess,
req,
where,
})
}

View File

@@ -1,4 +1,5 @@
import auth from '../../../auth/operations/local' import auth from '../../../auth/operations/local'
import count from './count'
import create from './create' import create from './create'
import deleteLocal from './delete' import deleteLocal from './delete'
import find from './find' import find from './find'
@@ -10,6 +11,7 @@ import update from './update'
export default { export default {
auth, auth,
count,
create, create,
deleteLocal, deleteLocal,
find, find,

View File

@@ -276,7 +276,7 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
// Update // Update
// ///////////////////////////////////// // /////////////////////////////////////
if (!shouldSaveDraft) { if (!shouldSaveDraft || data._status === 'published') {
result = await req.payload.db.updateOne({ result = await req.payload.db.updateOne({
id, id,
collection: collectionConfig.slug, collection: collectionConfig.slug,

View File

@@ -262,7 +262,7 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
// Update // Update
// ///////////////////////////////////// // /////////////////////////////////////
if (!shouldSaveDraft) { if (!shouldSaveDraft || data._status === 'published') {
result = await req.payload.db.updateOne({ result = await req.payload.db.updateOne({
id, id,
collection: collectionConfig.slug, collection: collectionConfig.slug,

View File

@@ -3,6 +3,7 @@ import type login from '../../auth/operations/login'
import type refresh from '../../auth/operations/refresh' import type refresh from '../../auth/operations/refresh'
import type { PayloadRequest } from '../../express/types' import type { PayloadRequest } from '../../express/types'
import type { AfterOperationHook, SanitizedCollectionConfig, TypeWithID } from '../config/types' import type { AfterOperationHook, SanitizedCollectionConfig, TypeWithID } from '../config/types'
import type countOperation from './count'
import type create from './create' import type create from './create'
import type deleteOperation from './delete' import type deleteOperation from './delete'
import type deleteByID from './deleteByID' import type deleteByID from './deleteByID'
@@ -12,6 +13,7 @@ import type update from './update'
import type updateByID from './updateByID' import type updateByID from './updateByID'
export type AfterOperationMap<T extends TypeWithID> = { export type AfterOperationMap<T extends TypeWithID> = {
count: typeof countOperation
create: typeof create // todo: pass correct generic create: typeof create // todo: pass correct generic
delete: typeof deleteOperation // todo: pass correct generic delete: typeof deleteOperation // todo: pass correct generic
deleteByID: typeof deleteByID // todo: pass correct generic deleteByID: typeof deleteByID // todo: pass correct generic
@@ -28,6 +30,11 @@ export type AfterOperationArg<T extends TypeWithID> = {
collection: SanitizedCollectionConfig collection: SanitizedCollectionConfig
req: PayloadRequest req: PayloadRequest
} & ( } & (
| {
args: Parameters<AfterOperationMap<T>['count']>[0]
operation: 'count'
result: Awaited<ReturnType<AfterOperationMap<T>['count']>>
}
| { | {
args: Parameters<AfterOperationMap<T>['create']>[0] args: Parameters<AfterOperationMap<T>['create']>[0]
operation: 'create' operation: 'create'

View File

@@ -0,0 +1,26 @@
import type { NextFunction, Response } from 'express'
import httpStatus from 'http-status'
import type { PayloadRequest } from '../../express/types'
import type { Where } from '../../types'
import count from '../operations/count'
export default async function countHandler(
req: PayloadRequest,
res: Response,
next: NextFunction,
): Promise<Response<{ totalDocs: number }> | void> {
try {
const result = await count({
collection: req.collection,
req,
where: req.query.where as Where, // This is a little shady
})
return res.status(httpStatus.OK).json(result)
} catch (error) {
return next(error)
}
}

View File

@@ -22,6 +22,8 @@ export interface BaseDatabaseAdapter {
*/ */
connect?: Connect connect?: Connect
count: Count
create: Create create: Create
createGlobal: CreateGlobal createGlobal: CreateGlobal
@@ -197,6 +199,15 @@ export type FindArgs = {
export type Find = <T = TypeWithID>(args: FindArgs) => Promise<PaginatedDocs<T>> export type Find = <T = TypeWithID>(args: FindArgs) => Promise<PaginatedDocs<T>>
export type CountArgs = {
collection: string
locale?: string
req: PayloadRequest
where?: Where
}
export type Count = (args: CountArgs) => Promise<{ totalDocs: number }>
type BaseVersionArgs = { type BaseVersionArgs = {
limit?: number limit?: number
locale?: string locale?: string

View File

@@ -0,0 +1,18 @@
import type { TFunction } from 'i18next'
import httpStatus from 'http-status'
import APIError from './APIError'
class FileRetrievalError extends APIError {
constructor(t?: TFunction, message?: string) {
let msg = t ? t('error:problemUploadingFile') : 'There was a problem while retrieving the file.'
if (message) {
msg += ` ${message}`
}
super(msg, httpStatus.BAD_REQUEST)
}
}
export default FileRetrievalError

View File

@@ -3,6 +3,8 @@ export {
BeginTransaction, BeginTransaction,
CommitTransaction, CommitTransaction,
Connect, Connect,
Count,
CountArgs,
Create, Create,
CreateArgs, CreateArgs,
CreateGlobal, CreateGlobal,

View File

@@ -18,6 +18,7 @@ import type { Options as VerifyEmailOptions } from './auth/operations/local/veri
import type { Result as LoginResult } from './auth/operations/login' import type { Result as LoginResult } from './auth/operations/login'
import type { Result as ResetPasswordResult } from './auth/operations/resetPassword' import type { Result as ResetPasswordResult } from './auth/operations/resetPassword'
import type { BulkOperationResult, Collection } from './collections/config/types' import type { BulkOperationResult, Collection } from './collections/config/types'
import type { Options as CountOptions } from './collections/operations/local/count'
import type { Options as CreateOptions } from './collections/operations/local/create' import type { Options as CreateOptions } from './collections/operations/local/create'
import type { import type {
ByIDOptions as DeleteByIDOptions, ByIDOptions as DeleteByIDOptions,
@@ -74,6 +75,18 @@ export class BasePayload<TGeneratedTypes extends GeneratedTypes> {
config: SanitizedConfig config: SanitizedConfig
/**
* @description Performs count operation
* @param options
* @returns count of documents satisfying query
*/
count = async <T extends keyof TGeneratedTypes['collections']>(
options: CountOptions<T>,
): Promise<{ totalDocs: number }> => {
const { count } = localOperations
return count(this, options)
}
/** /**
* @description Performs create operation * @description Performs create operation
* @param options * @param options

View File

@@ -14,6 +14,7 @@ import type { PayloadRequest } from '../express/types'
import type { FileData, FileToSave, ProbedImageSize } from './types' import type { FileData, FileToSave, ProbedImageSize } from './types'
import { FileUploadError, MissingFile } from '../errors' import { FileUploadError, MissingFile } from '../errors'
import FileRetrievalError from '../errors/FileRetrievalError'
import canResizeImage from './canResizeImage' import canResizeImage from './canResizeImage'
import cropImage from './cropImage' import cropImage from './cropImage'
import { getExternalFile } from './getExternalFile' import { getExternalFile } from './getExternalFile'
@@ -80,8 +81,10 @@ export const generateFileData = async <T>({
})) as UploadedFile })) as UploadedFile
overwriteExistingFiles = true overwriteExistingFiles = true
} }
} catch (err) { } catch (err: unknown) {
throw new FileUploadError(req.t) if (err instanceof Error) {
throw new FileRetrievalError(req.t, err.message)
}
} }
} }

View File

@@ -1,4 +1,5 @@
import type { Request } from 'express' import type { Request } from 'express'
import type { IncomingHttpHeaders } from 'http'
import type { File, FileData, IncomingUploadType } from './types' import type { File, FileData, IncomingUploadType } from './types'
@@ -21,20 +22,15 @@ export const getExternalFile = async ({ data, req, uploadConfig }: Args): Promis
const { default: fetch } = (await import('node-fetch')) as any const { default: fetch } = (await import('node-fetch')) as any
// Convert headers
const convertedHeaders: Record<string, string> = headersToObject(req.headers)
const headers = uploadConfig.externalFileHeaderFilter const headers = uploadConfig.externalFileHeaderFilter
? uploadConfig.externalFileHeaderFilter(convertedHeaders) ? uploadConfig.externalFileHeaderFilter(headersToObject(req.headers))
: { : {
cookie: req.headers['cookie'], cookie: req.headers['cookie'],
} }
const res = await fetch(fileURL, { const res = await fetch(fileURL, {
credentials: 'include', credentials: 'include',
headers: { headers,
headers,
},
method: 'GET', method: 'GET',
}) })
@@ -53,15 +49,17 @@ export const getExternalFile = async ({ data, req, uploadConfig }: Args): Promis
throw new APIError('Invalid file url', 400) throw new APIError('Invalid file url', 400)
} }
function headersToObject(headers) { function headersToObject(headers: IncomingHttpHeaders) {
const headersObj = {} return Object.entries(headers).reduce(
headers.forEach((value, key) => { (acc, [key, value]) => {
// If the header value is an array, join its elements into a single string if (Array.isArray(value)) {
if (Array.isArray(value)) { acc[key] = value.join(',')
headersObj[key] = value.join(', ') } else {
} else { acc[key] = value
headersObj[key] = value }
}
}) return acc
return headersObj },
{} as Record<string, string>,
)
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@payloadcms/plugin-seo", "name": "@payloadcms/plugin-seo",
"version": "2.3.0", "version": "2.3.1",
"homepage:": "https://payloadcms.com", "homepage:": "https://payloadcms.com",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@payloadcms/richtext-lexical", "name": "@payloadcms/richtext-lexical",
"version": "0.8.0", "version": "0.9.0",
"description": "The officially supported Lexical richtext adapter for Payload", "description": "The officially supported Lexical richtext adapter for Payload",
"repository": { "repository": {
"type": "git", "type": "git",

1221
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@ import type { FieldAccess } from '../../packages/payload/src/fields/config/types
import { buildConfigWithDefaults } from '../buildConfigWithDefaults' import { buildConfigWithDefaults } from '../buildConfigWithDefaults'
import { devUser } from '../credentials' import { devUser } from '../credentials'
import { firstArrayText, secondArrayText } from './shared' import { firstArrayText, hiddenAccessCountSlug, secondArrayText } from './shared'
import { import {
docLevelAccessSlug, docLevelAccessSlug,
hiddenAccessSlug, hiddenAccessSlug,
@@ -390,6 +390,32 @@ export default buildConfigWithDefaults({
}, },
], ],
}, },
{
slug: hiddenAccessCountSlug,
access: {
read: ({ req: { user } }) => {
if (user) return true
return {
hidden: {
not_equals: true,
},
}
},
},
fields: [
{
name: 'title',
type: 'text',
required: true,
},
{
name: 'hidden',
type: 'checkbox',
hidden: true,
},
],
},
], ],
onInit: async (payload) => { onInit: async (payload) => {
await payload.create({ await payload.create({

View File

@@ -7,6 +7,7 @@ import { initPayloadTest } from '../helpers/configHelpers'
import { requestHeaders } from './config' import { requestHeaders } from './config'
import { import {
firstArrayText, firstArrayText,
hiddenAccessCountSlug,
hiddenAccessSlug, hiddenAccessSlug,
hiddenFieldsSlug, hiddenFieldsSlug,
relyOnRequestHeadersSlug, relyOnRequestHeadersSlug,
@@ -417,6 +418,30 @@ describe('Access Control', () => {
expect(docs).toHaveLength(1) expect(docs).toHaveLength(1)
}) })
it('should respect query constraint using hidden field on count', async () => {
await payload.create({
collection: hiddenAccessCountSlug,
data: {
title: 'hello',
},
})
await payload.create({
collection: hiddenAccessCountSlug,
data: {
title: 'hello',
hidden: true,
},
})
const { totalDocs } = await payload.count({
collection: hiddenAccessCountSlug,
overrideAccess: false,
})
expect(totalDocs).toBe(1)
})
it('should respect query constraint using hidden field on versions', async () => { it('should respect query constraint using hidden field on versions', async () => {
await payload.create({ await payload.create({
collection: restrictedVersionsSlug, collection: restrictedVersionsSlug,

View File

@@ -20,11 +20,16 @@ export interface Config {
'doc-level-access': DocLevelAccess 'doc-level-access': DocLevelAccess
'hidden-fields': HiddenField 'hidden-fields': HiddenField
'hidden-access': HiddenAccess 'hidden-access': HiddenAccess
'hidden-access-count': HiddenAccessCount
'payload-preferences': PayloadPreference 'payload-preferences': PayloadPreference
'payload-migrations': PayloadMigration 'payload-migrations': PayloadMigration
} }
globals: {} globals: {}
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users".
*/
export interface User { export interface User {
id: string id: string
roles?: ('admin' | 'user')[] | null roles?: ('admin' | 'user')[] | null
@@ -39,6 +44,10 @@ export interface User {
lockUntil?: string | null lockUntil?: string | null
password: string | null password: string | null
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "posts".
*/
export interface Post { export interface Post {
id: string id: string
restrictedField?: string | null restrictedField?: string | null
@@ -50,6 +59,10 @@ export interface Post {
updatedAt: string updatedAt: string
createdAt: string createdAt: string
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "unrestricted".
*/
export interface Unrestricted { export interface Unrestricted {
id: string id: string
name?: string | null name?: string | null
@@ -57,24 +70,40 @@ export interface Unrestricted {
updatedAt: string updatedAt: string
createdAt: string createdAt: string
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "user-restricted".
*/
export interface UserRestricted { export interface UserRestricted {
id: string id: string
name?: string | null name?: string | null
updatedAt: string updatedAt: string
createdAt: string createdAt: string
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "restricted".
*/
export interface Restricted { export interface Restricted {
id: string id: string
name?: string | null name?: string | null
updatedAt: string updatedAt: string
createdAt: string createdAt: string
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "read-only-collection".
*/
export interface ReadOnlyCollection { export interface ReadOnlyCollection {
id: string id: string
name?: string | null name?: string | null
updatedAt: string updatedAt: string
createdAt: string createdAt: string
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "restricted-versions".
*/
export interface RestrictedVersion { export interface RestrictedVersion {
id: string id: string
name?: string | null name?: string | null
@@ -82,6 +111,10 @@ export interface RestrictedVersion {
updatedAt: string updatedAt: string
createdAt: string createdAt: string
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "sibling-data".
*/
export interface SiblingDatum { export interface SiblingDatum {
id: string id: string
array?: array?:
@@ -94,12 +127,20 @@ export interface SiblingDatum {
updatedAt: string updatedAt: string
createdAt: string createdAt: string
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "rely-on-request-headers".
*/
export interface RelyOnRequestHeader { export interface RelyOnRequestHeader {
id: string id: string
name?: string | null name?: string | null
updatedAt: string updatedAt: string
createdAt: string createdAt: string
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "doc-level-access".
*/
export interface DocLevelAccess { export interface DocLevelAccess {
id: string id: string
approvedForRemoval?: boolean | null approvedForRemoval?: boolean | null
@@ -108,6 +149,10 @@ export interface DocLevelAccess {
updatedAt: string updatedAt: string
createdAt: string createdAt: string
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "hidden-fields".
*/
export interface HiddenField { export interface HiddenField {
id: string id: string
title?: string | null title?: string | null
@@ -126,6 +171,10 @@ export interface HiddenField {
updatedAt: string updatedAt: string
createdAt: string createdAt: string
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "hidden-access".
*/
export interface HiddenAccess { export interface HiddenAccess {
id: string id: string
title: string title: string
@@ -133,6 +182,21 @@ export interface HiddenAccess {
updatedAt: string updatedAt: string
createdAt: string createdAt: string
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "hidden-access-count".
*/
export interface HiddenAccessCount {
id: string
title: string
hidden?: boolean | null
updatedAt: string
createdAt: string
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-preferences".
*/
export interface PayloadPreference { export interface PayloadPreference {
id: string id: string
user: { user: {
@@ -152,6 +216,10 @@ export interface PayloadPreference {
updatedAt: string updatedAt: string
createdAt: string createdAt: string
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-migrations".
*/
export interface PayloadMigration { export interface PayloadMigration {
id: string id: string
name?: string | null name?: string | null

View File

@@ -12,5 +12,5 @@ export const siblingDataSlug = 'sibling-data'
export const relyOnRequestHeadersSlug = 'rely-on-request-headers' export const relyOnRequestHeadersSlug = 'rely-on-request-headers'
export const docLevelAccessSlug = 'doc-level-access' export const docLevelAccessSlug = 'doc-level-access'
export const hiddenFieldsSlug = 'hidden-fields' export const hiddenFieldsSlug = 'hidden-fields'
export const hiddenAccessCountSlug = 'hidden-access-count'
export const hiddenAccessSlug = 'hidden-access' export const hiddenAccessSlug = 'hidden-access'

View File

@@ -102,6 +102,18 @@ describe('collections-graphql', () => {
expect(docs).toContainEqual(expect.objectContaining({ id: existingDoc.id })) expect(docs).toContainEqual(expect.objectContaining({ id: existingDoc.id }))
}) })
it('should count', async () => {
const query = `query {
countPosts {
totalDocs
}
}`
const response = await client.request<{ countPosts: { totalDocs: number } }>(query)
const { totalDocs } = response.countPosts
expect(typeof totalDocs).toBe('number')
})
it('should read using multiple queries', async () => { it('should read using multiple queries', async () => {
const query = `query { const query = `query {
postIDs: Posts { postIDs: Posts {

View File

@@ -1,32 +1,49 @@
type Query { type Query {
User(id: String!, draft: Boolean): User User(id: String!, draft: Boolean): User
Users(draft: Boolean, where: User_where, limit: Int, page: Int, sort: String): Users Users(draft: Boolean, where: User_where, limit: Int, page: Int, sort: String): Users
countUsers(draft: Boolean, where: User_where): countUsers
docAccessUser(id: String!): usersDocAccess docAccessUser(id: String!): usersDocAccess
meUser: usersMe meUser: usersMe
initializedUser: Boolean initializedUser: Boolean
Point(id: String!, draft: Boolean): Point Point(id: String!, draft: Boolean): Point
Points(draft: Boolean, where: Point_where, limit: Int, page: Int, sort: String): Points Points(draft: Boolean, where: Point_where, limit: Int, page: Int, sort: String): Points
countPoints(draft: Boolean, where: Point_where): countPoints
docAccessPoint(id: String!): pointDocAccess docAccessPoint(id: String!): pointDocAccess
Post(id: String!, draft: Boolean): Post Post(id: String!, draft: Boolean): Post
Posts(draft: Boolean, where: Post_where, limit: Int, page: Int, sort: String): Posts Posts(draft: Boolean, where: Post_where, limit: Int, page: Int, sort: String): Posts
countPosts(draft: Boolean, where: Post_where): countPosts
docAccessPost(id: String!): postsDocAccess docAccessPost(id: String!): postsDocAccess
CustomId(id: Int!, draft: Boolean): CustomId CustomId(id: Int!, draft: Boolean): CustomId
CustomIds(draft: Boolean, where: CustomId_where, limit: Int, page: Int, sort: String): CustomIds CustomIds(draft: Boolean, where: CustomId_where, limit: Int, page: Int, sort: String): CustomIds
countCustomIds(draft: Boolean, where: CustomId_where): countCustomIds
docAccessCustomId(id: Int!): custom_idsDocAccess docAccessCustomId(id: Int!): custom_idsDocAccess
Relation(id: String!, draft: Boolean): Relation Relation(id: String!, draft: Boolean): Relation
Relations(draft: Boolean, where: Relation_where, limit: Int, page: Int, sort: String): Relations Relations(draft: Boolean, where: Relation_where, limit: Int, page: Int, sort: String): Relations
countRelations(draft: Boolean, where: Relation_where): countRelations
docAccessRelation(id: String!): relationDocAccess docAccessRelation(id: String!): relationDocAccess
Dummy(id: String!, draft: Boolean): Dummy Dummy(id: String!, draft: Boolean): Dummy
Dummies(draft: Boolean, where: Dummy_where, limit: Int, page: Int, sort: String): Dummies Dummies(draft: Boolean, where: Dummy_where, limit: Int, page: Int, sort: String): Dummies
countDummies(draft: Boolean, where: Dummy_where): countDummies
docAccessDummy(id: String!): dummyDocAccess docAccessDummy(id: String!): dummyDocAccess
ErrorOnHook(id: String!, draft: Boolean): ErrorOnHook
ErrorOnHooks(draft: Boolean, where: ErrorOnHook_where, limit: Int, page: Int, sort: String): ErrorOnHooks
countErrorOnHooks(draft: Boolean, where: ErrorOnHook_where): countErrorOnHooks
docAccessErrorOnHook(id: String!): error_on_hooksDocAccess
PayloadApiTestOne(id: String!, draft: Boolean): PayloadApiTestOne PayloadApiTestOne(id: String!, draft: Boolean): PayloadApiTestOne
PayloadApiTestOnes(draft: Boolean, where: PayloadApiTestOne_where, limit: Int, page: Int, sort: String): PayloadApiTestOnes PayloadApiTestOnes(draft: Boolean, where: PayloadApiTestOne_where, limit: Int, page: Int, sort: String): PayloadApiTestOnes
countPayloadApiTestOnes(draft: Boolean, where: PayloadApiTestOne_where): countPayloadApiTestOnes
docAccessPayloadApiTestOne(id: String!): payload_api_test_onesDocAccess docAccessPayloadApiTestOne(id: String!): payload_api_test_onesDocAccess
PayloadApiTestTwo(id: String!, draft: Boolean): PayloadApiTestTwo PayloadApiTestTwo(id: String!, draft: Boolean): PayloadApiTestTwo
PayloadApiTestTwos(draft: Boolean, where: PayloadApiTestTwo_where, limit: Int, page: Int, sort: String): PayloadApiTestTwos PayloadApiTestTwos(draft: Boolean, where: PayloadApiTestTwo_where, limit: Int, page: Int, sort: String): PayloadApiTestTwos
countPayloadApiTestTwos(draft: Boolean, where: PayloadApiTestTwo_where): countPayloadApiTestTwos
docAccessPayloadApiTestTwo(id: String!): payload_api_test_twosDocAccess docAccessPayloadApiTestTwo(id: String!): payload_api_test_twosDocAccess
ContentType(id: String!, draft: Boolean): ContentType
ContentTypes(draft: Boolean, where: ContentType_where, limit: Int, page: Int, sort: String): ContentTypes
countContentTypes(draft: Boolean, where: ContentType_where): countContentTypes
docAccessContentType(id: String!): content_typeDocAccess
PayloadPreference(id: String!, draft: Boolean): PayloadPreference PayloadPreference(id: String!, draft: Boolean): PayloadPreference
PayloadPreferences(draft: Boolean, where: PayloadPreference_where, limit: Int, page: Int, sort: String): PayloadPreferences PayloadPreferences(draft: Boolean, where: PayloadPreference_where, limit: Int, page: Int, sort: String): PayloadPreferences
countPayloadPreferences(draft: Boolean, where: PayloadPreference_where): countPayloadPreferences
docAccessPayloadPreference(id: String!): payload_preferencesDocAccess docAccessPayloadPreference(id: String!): payload_preferencesDocAccess
Access: Access Access: Access
QueryWithInternalError: QueryWithInternalError QueryWithInternalError: QueryWithInternalError
@@ -140,6 +157,10 @@ input User_where_or {
OR: [User_where_or] OR: [User_where_or]
} }
type countUsers {
totalDocs: Int
}
type usersDocAccess { type usersDocAccess {
fields: UsersDocAccessFields fields: UsersDocAccessFields
create: UsersCreateDocAccess create: UsersCreateDocAccess
@@ -389,6 +410,10 @@ input Point_where_or {
OR: [Point_where_or] OR: [Point_where_or]
} }
type countPoints {
totalDocs: Int
}
type pointDocAccess { type pointDocAccess {
fields: PointDocAccessFields fields: PointDocAccessFields
create: PointCreateDocAccess create: PointCreateDocAccess
@@ -842,6 +867,10 @@ input Post_where_or {
OR: [Post_where_or] OR: [Post_where_or]
} }
type countPosts {
totalDocs: Int
}
type postsDocAccess { type postsDocAccess {
fields: PostsDocAccessFields fields: PostsDocAccessFields
create: PostsCreateDocAccess create: PostsCreateDocAccess
@@ -1509,6 +1538,10 @@ input CustomId_where_or {
OR: [CustomId_where_or] OR: [CustomId_where_or]
} }
type countCustomIds {
totalDocs: Int
}
type custom_idsDocAccess { type custom_idsDocAccess {
fields: CustomIdsDocAccessFields fields: CustomIdsDocAccessFields
create: CustomIdsCreateDocAccess create: CustomIdsCreateDocAccess
@@ -1721,6 +1754,10 @@ input Relation_where_or {
OR: [Relation_where_or] OR: [Relation_where_or]
} }
type countRelations {
totalDocs: Int
}
type relationDocAccess { type relationDocAccess {
fields: RelationDocAccessFields fields: RelationDocAccessFields
create: RelationCreateDocAccess create: RelationCreateDocAccess
@@ -1909,6 +1946,10 @@ input Dummy_where_or {
OR: [Dummy_where_or] OR: [Dummy_where_or]
} }
type countDummies {
totalDocs: Int
}
type dummyDocAccess { type dummyDocAccess {
fields: DummyDocAccessFields fields: DummyDocAccessFields
create: DummyCreateDocAccess create: DummyCreateDocAccess
@@ -2012,6 +2053,239 @@ type DummyDeleteDocAccess {
where: JSONObject where: JSONObject
} }
type ErrorOnHook {
id: String
title: String
errorBeforeChange: Boolean
updatedAt: DateTime
createdAt: DateTime
}
type ErrorOnHooks {
docs: [ErrorOnHook]
hasNextPage: Boolean
hasPrevPage: Boolean
limit: Int
nextPage: Int
offset: Int
page: Int
pagingCounter: Int
prevPage: Int
totalDocs: Int
totalPages: Int
}
input ErrorOnHook_where {
title: ErrorOnHook_title_operator
errorBeforeChange: ErrorOnHook_errorBeforeChange_operator
updatedAt: ErrorOnHook_updatedAt_operator
createdAt: ErrorOnHook_createdAt_operator
id: ErrorOnHook_id_operator
AND: [ErrorOnHook_where_and]
OR: [ErrorOnHook_where_or]
}
input ErrorOnHook_title_operator {
equals: String
not_equals: String
like: String
contains: String
in: [String]
not_in: [String]
all: [String]
exists: Boolean
}
input ErrorOnHook_errorBeforeChange_operator {
equals: Boolean
not_equals: Boolean
exists: Boolean
}
input ErrorOnHook_updatedAt_operator {
equals: DateTime
not_equals: DateTime
greater_than_equal: DateTime
greater_than: DateTime
less_than_equal: DateTime
less_than: DateTime
like: DateTime
exists: Boolean
}
input ErrorOnHook_createdAt_operator {
equals: DateTime
not_equals: DateTime
greater_than_equal: DateTime
greater_than: DateTime
less_than_equal: DateTime
less_than: DateTime
like: DateTime
exists: Boolean
}
input ErrorOnHook_id_operator {
equals: String
not_equals: String
like: String
contains: String
in: [String]
not_in: [String]
all: [String]
exists: Boolean
}
input ErrorOnHook_where_and {
title: ErrorOnHook_title_operator
errorBeforeChange: ErrorOnHook_errorBeforeChange_operator
updatedAt: ErrorOnHook_updatedAt_operator
createdAt: ErrorOnHook_createdAt_operator
id: ErrorOnHook_id_operator
AND: [ErrorOnHook_where_and]
OR: [ErrorOnHook_where_or]
}
input ErrorOnHook_where_or {
title: ErrorOnHook_title_operator
errorBeforeChange: ErrorOnHook_errorBeforeChange_operator
updatedAt: ErrorOnHook_updatedAt_operator
createdAt: ErrorOnHook_createdAt_operator
id: ErrorOnHook_id_operator
AND: [ErrorOnHook_where_and]
OR: [ErrorOnHook_where_or]
}
type countErrorOnHooks {
totalDocs: Int
}
type error_on_hooksDocAccess {
fields: ErrorOnHooksDocAccessFields
create: ErrorOnHooksCreateDocAccess
read: ErrorOnHooksReadDocAccess
update: ErrorOnHooksUpdateDocAccess
delete: ErrorOnHooksDeleteDocAccess
}
type ErrorOnHooksDocAccessFields {
title: ErrorOnHooksDocAccessFields_title
errorBeforeChange: ErrorOnHooksDocAccessFields_errorBeforeChange
updatedAt: ErrorOnHooksDocAccessFields_updatedAt
createdAt: ErrorOnHooksDocAccessFields_createdAt
}
type ErrorOnHooksDocAccessFields_title {
create: ErrorOnHooksDocAccessFields_title_Create
read: ErrorOnHooksDocAccessFields_title_Read
update: ErrorOnHooksDocAccessFields_title_Update
delete: ErrorOnHooksDocAccessFields_title_Delete
}
type ErrorOnHooksDocAccessFields_title_Create {
permission: Boolean!
}
type ErrorOnHooksDocAccessFields_title_Read {
permission: Boolean!
}
type ErrorOnHooksDocAccessFields_title_Update {
permission: Boolean!
}
type ErrorOnHooksDocAccessFields_title_Delete {
permission: Boolean!
}
type ErrorOnHooksDocAccessFields_errorBeforeChange {
create: ErrorOnHooksDocAccessFields_errorBeforeChange_Create
read: ErrorOnHooksDocAccessFields_errorBeforeChange_Read
update: ErrorOnHooksDocAccessFields_errorBeforeChange_Update
delete: ErrorOnHooksDocAccessFields_errorBeforeChange_Delete
}
type ErrorOnHooksDocAccessFields_errorBeforeChange_Create {
permission: Boolean!
}
type ErrorOnHooksDocAccessFields_errorBeforeChange_Read {
permission: Boolean!
}
type ErrorOnHooksDocAccessFields_errorBeforeChange_Update {
permission: Boolean!
}
type ErrorOnHooksDocAccessFields_errorBeforeChange_Delete {
permission: Boolean!
}
type ErrorOnHooksDocAccessFields_updatedAt {
create: ErrorOnHooksDocAccessFields_updatedAt_Create
read: ErrorOnHooksDocAccessFields_updatedAt_Read
update: ErrorOnHooksDocAccessFields_updatedAt_Update
delete: ErrorOnHooksDocAccessFields_updatedAt_Delete
}
type ErrorOnHooksDocAccessFields_updatedAt_Create {
permission: Boolean!
}
type ErrorOnHooksDocAccessFields_updatedAt_Read {
permission: Boolean!
}
type ErrorOnHooksDocAccessFields_updatedAt_Update {
permission: Boolean!
}
type ErrorOnHooksDocAccessFields_updatedAt_Delete {
permission: Boolean!
}
type ErrorOnHooksDocAccessFields_createdAt {
create: ErrorOnHooksDocAccessFields_createdAt_Create
read: ErrorOnHooksDocAccessFields_createdAt_Read
update: ErrorOnHooksDocAccessFields_createdAt_Update
delete: ErrorOnHooksDocAccessFields_createdAt_Delete
}
type ErrorOnHooksDocAccessFields_createdAt_Create {
permission: Boolean!
}
type ErrorOnHooksDocAccessFields_createdAt_Read {
permission: Boolean!
}
type ErrorOnHooksDocAccessFields_createdAt_Update {
permission: Boolean!
}
type ErrorOnHooksDocAccessFields_createdAt_Delete {
permission: Boolean!
}
type ErrorOnHooksCreateDocAccess {
permission: Boolean!
where: JSONObject
}
type ErrorOnHooksReadDocAccess {
permission: Boolean!
where: JSONObject
}
type ErrorOnHooksUpdateDocAccess {
permission: Boolean!
where: JSONObject
}
type ErrorOnHooksDeleteDocAccess {
permission: Boolean!
where: JSONObject
}
type PayloadApiTestOne { type PayloadApiTestOne {
id: String id: String
payloadAPI: String payloadAPI: String
@@ -2104,6 +2378,10 @@ input PayloadApiTestOne_where_or {
OR: [PayloadApiTestOne_where_or] OR: [PayloadApiTestOne_where_or]
} }
type countPayloadApiTestOnes {
totalDocs: Int
}
type payload_api_test_onesDocAccess { type payload_api_test_onesDocAccess {
fields: PayloadApiTestOnesDocAccessFields fields: PayloadApiTestOnesDocAccessFields
create: PayloadApiTestOnesCreateDocAccess create: PayloadApiTestOnesCreateDocAccess
@@ -2312,6 +2590,10 @@ input PayloadApiTestTwo_where_or {
OR: [PayloadApiTestTwo_where_or] OR: [PayloadApiTestTwo_where_or]
} }
type countPayloadApiTestTwos {
totalDocs: Int
}
type payload_api_test_twosDocAccess { type payload_api_test_twosDocAccess {
fields: PayloadApiTestTwosDocAccessFields fields: PayloadApiTestTwosDocAccessFields
create: PayloadApiTestTwosCreateDocAccess create: PayloadApiTestTwosCreateDocAccess
@@ -2439,6 +2721,205 @@ type PayloadApiTestTwosDeleteDocAccess {
where: JSONObject where: JSONObject
} }
type ContentType {
id: String
contentType: String
updatedAt: DateTime
createdAt: DateTime
}
type ContentTypes {
docs: [ContentType]
hasNextPage: Boolean
hasPrevPage: Boolean
limit: Int
nextPage: Int
offset: Int
page: Int
pagingCounter: Int
prevPage: Int
totalDocs: Int
totalPages: Int
}
input ContentType_where {
contentType: ContentType_contentType_operator
updatedAt: ContentType_updatedAt_operator
createdAt: ContentType_createdAt_operator
id: ContentType_id_operator
AND: [ContentType_where_and]
OR: [ContentType_where_or]
}
input ContentType_contentType_operator {
equals: String
not_equals: String
like: String
contains: String
in: [String]
not_in: [String]
all: [String]
exists: Boolean
}
input ContentType_updatedAt_operator {
equals: DateTime
not_equals: DateTime
greater_than_equal: DateTime
greater_than: DateTime
less_than_equal: DateTime
less_than: DateTime
like: DateTime
exists: Boolean
}
input ContentType_createdAt_operator {
equals: DateTime
not_equals: DateTime
greater_than_equal: DateTime
greater_than: DateTime
less_than_equal: DateTime
less_than: DateTime
like: DateTime
exists: Boolean
}
input ContentType_id_operator {
equals: String
not_equals: String
like: String
contains: String
in: [String]
not_in: [String]
all: [String]
exists: Boolean
}
input ContentType_where_and {
contentType: ContentType_contentType_operator
updatedAt: ContentType_updatedAt_operator
createdAt: ContentType_createdAt_operator
id: ContentType_id_operator
AND: [ContentType_where_and]
OR: [ContentType_where_or]
}
input ContentType_where_or {
contentType: ContentType_contentType_operator
updatedAt: ContentType_updatedAt_operator
createdAt: ContentType_createdAt_operator
id: ContentType_id_operator
AND: [ContentType_where_and]
OR: [ContentType_where_or]
}
type countContentTypes {
totalDocs: Int
}
type content_typeDocAccess {
fields: ContentTypeDocAccessFields
create: ContentTypeCreateDocAccess
read: ContentTypeReadDocAccess
update: ContentTypeUpdateDocAccess
delete: ContentTypeDeleteDocAccess
}
type ContentTypeDocAccessFields {
contentType: ContentTypeDocAccessFields_contentType
updatedAt: ContentTypeDocAccessFields_updatedAt
createdAt: ContentTypeDocAccessFields_createdAt
}
type ContentTypeDocAccessFields_contentType {
create: ContentTypeDocAccessFields_contentType_Create
read: ContentTypeDocAccessFields_contentType_Read
update: ContentTypeDocAccessFields_contentType_Update
delete: ContentTypeDocAccessFields_contentType_Delete
}
type ContentTypeDocAccessFields_contentType_Create {
permission: Boolean!
}
type ContentTypeDocAccessFields_contentType_Read {
permission: Boolean!
}
type ContentTypeDocAccessFields_contentType_Update {
permission: Boolean!
}
type ContentTypeDocAccessFields_contentType_Delete {
permission: Boolean!
}
type ContentTypeDocAccessFields_updatedAt {
create: ContentTypeDocAccessFields_updatedAt_Create
read: ContentTypeDocAccessFields_updatedAt_Read
update: ContentTypeDocAccessFields_updatedAt_Update
delete: ContentTypeDocAccessFields_updatedAt_Delete
}
type ContentTypeDocAccessFields_updatedAt_Create {
permission: Boolean!
}
type ContentTypeDocAccessFields_updatedAt_Read {
permission: Boolean!
}
type ContentTypeDocAccessFields_updatedAt_Update {
permission: Boolean!
}
type ContentTypeDocAccessFields_updatedAt_Delete {
permission: Boolean!
}
type ContentTypeDocAccessFields_createdAt {
create: ContentTypeDocAccessFields_createdAt_Create
read: ContentTypeDocAccessFields_createdAt_Read
update: ContentTypeDocAccessFields_createdAt_Update
delete: ContentTypeDocAccessFields_createdAt_Delete
}
type ContentTypeDocAccessFields_createdAt_Create {
permission: Boolean!
}
type ContentTypeDocAccessFields_createdAt_Read {
permission: Boolean!
}
type ContentTypeDocAccessFields_createdAt_Update {
permission: Boolean!
}
type ContentTypeDocAccessFields_createdAt_Delete {
permission: Boolean!
}
type ContentTypeCreateDocAccess {
permission: Boolean!
where: JSONObject
}
type ContentTypeReadDocAccess {
permission: Boolean!
where: JSONObject
}
type ContentTypeUpdateDocAccess {
permission: Boolean!
where: JSONObject
}
type ContentTypeDeleteDocAccess {
permission: Boolean!
where: JSONObject
}
type PayloadPreference { type PayloadPreference {
id: String id: String
user: PayloadPreference_User_Relationship! user: PayloadPreference_User_Relationship!
@@ -2569,6 +3050,10 @@ input PayloadPreference_where_or {
OR: [PayloadPreference_where_or] OR: [PayloadPreference_where_or]
} }
type countPayloadPreferences {
totalDocs: Int
}
type payload_preferencesDocAccess { type payload_preferencesDocAccess {
fields: PayloadPreferencesDocAccessFields fields: PayloadPreferencesDocAccessFields
create: PayloadPreferencesCreateDocAccess create: PayloadPreferencesCreateDocAccess
@@ -2728,8 +3213,10 @@ type Access {
custom_ids: custom_idsAccess custom_ids: custom_idsAccess
relation: relationAccess relation: relationAccess
dummy: dummyAccess dummy: dummyAccess
error_on_hooks: error_on_hooksAccess
payload_api_test_ones: payload_api_test_onesAccess payload_api_test_ones: payload_api_test_onesAccess
payload_api_test_twos: payload_api_test_twosAccess payload_api_test_twos: payload_api_test_twosAccess
content_type: content_typeAccess
payload_preferences: payload_preferencesAccess payload_preferences: payload_preferencesAccess
} }
@@ -3885,6 +4372,133 @@ type DummyDeleteAccess {
where: JSONObject where: JSONObject
} }
type error_on_hooksAccess {
fields: ErrorOnHooksFields
create: ErrorOnHooksCreateAccess
read: ErrorOnHooksReadAccess
update: ErrorOnHooksUpdateAccess
delete: ErrorOnHooksDeleteAccess
}
type ErrorOnHooksFields {
title: ErrorOnHooksFields_title
errorBeforeChange: ErrorOnHooksFields_errorBeforeChange
updatedAt: ErrorOnHooksFields_updatedAt
createdAt: ErrorOnHooksFields_createdAt
}
type ErrorOnHooksFields_title {
create: ErrorOnHooksFields_title_Create
read: ErrorOnHooksFields_title_Read
update: ErrorOnHooksFields_title_Update
delete: ErrorOnHooksFields_title_Delete
}
type ErrorOnHooksFields_title_Create {
permission: Boolean!
}
type ErrorOnHooksFields_title_Read {
permission: Boolean!
}
type ErrorOnHooksFields_title_Update {
permission: Boolean!
}
type ErrorOnHooksFields_title_Delete {
permission: Boolean!
}
type ErrorOnHooksFields_errorBeforeChange {
create: ErrorOnHooksFields_errorBeforeChange_Create
read: ErrorOnHooksFields_errorBeforeChange_Read
update: ErrorOnHooksFields_errorBeforeChange_Update
delete: ErrorOnHooksFields_errorBeforeChange_Delete
}
type ErrorOnHooksFields_errorBeforeChange_Create {
permission: Boolean!
}
type ErrorOnHooksFields_errorBeforeChange_Read {
permission: Boolean!
}
type ErrorOnHooksFields_errorBeforeChange_Update {
permission: Boolean!
}
type ErrorOnHooksFields_errorBeforeChange_Delete {
permission: Boolean!
}
type ErrorOnHooksFields_updatedAt {
create: ErrorOnHooksFields_updatedAt_Create
read: ErrorOnHooksFields_updatedAt_Read
update: ErrorOnHooksFields_updatedAt_Update
delete: ErrorOnHooksFields_updatedAt_Delete
}
type ErrorOnHooksFields_updatedAt_Create {
permission: Boolean!
}
type ErrorOnHooksFields_updatedAt_Read {
permission: Boolean!
}
type ErrorOnHooksFields_updatedAt_Update {
permission: Boolean!
}
type ErrorOnHooksFields_updatedAt_Delete {
permission: Boolean!
}
type ErrorOnHooksFields_createdAt {
create: ErrorOnHooksFields_createdAt_Create
read: ErrorOnHooksFields_createdAt_Read
update: ErrorOnHooksFields_createdAt_Update
delete: ErrorOnHooksFields_createdAt_Delete
}
type ErrorOnHooksFields_createdAt_Create {
permission: Boolean!
}
type ErrorOnHooksFields_createdAt_Read {
permission: Boolean!
}
type ErrorOnHooksFields_createdAt_Update {
permission: Boolean!
}
type ErrorOnHooksFields_createdAt_Delete {
permission: Boolean!
}
type ErrorOnHooksCreateAccess {
permission: Boolean!
where: JSONObject
}
type ErrorOnHooksReadAccess {
permission: Boolean!
where: JSONObject
}
type ErrorOnHooksUpdateAccess {
permission: Boolean!
where: JSONObject
}
type ErrorOnHooksDeleteAccess {
permission: Boolean!
where: JSONObject
}
type payload_api_test_onesAccess { type payload_api_test_onesAccess {
fields: PayloadApiTestOnesFields fields: PayloadApiTestOnesFields
create: PayloadApiTestOnesCreateAccess create: PayloadApiTestOnesCreateAccess
@@ -4115,6 +4729,109 @@ type PayloadApiTestTwosDeleteAccess {
where: JSONObject where: JSONObject
} }
type content_typeAccess {
fields: ContentTypeFields
create: ContentTypeCreateAccess
read: ContentTypeReadAccess
update: ContentTypeUpdateAccess
delete: ContentTypeDeleteAccess
}
type ContentTypeFields {
contentType: ContentTypeFields_contentType
updatedAt: ContentTypeFields_updatedAt
createdAt: ContentTypeFields_createdAt
}
type ContentTypeFields_contentType {
create: ContentTypeFields_contentType_Create
read: ContentTypeFields_contentType_Read
update: ContentTypeFields_contentType_Update
delete: ContentTypeFields_contentType_Delete
}
type ContentTypeFields_contentType_Create {
permission: Boolean!
}
type ContentTypeFields_contentType_Read {
permission: Boolean!
}
type ContentTypeFields_contentType_Update {
permission: Boolean!
}
type ContentTypeFields_contentType_Delete {
permission: Boolean!
}
type ContentTypeFields_updatedAt {
create: ContentTypeFields_updatedAt_Create
read: ContentTypeFields_updatedAt_Read
update: ContentTypeFields_updatedAt_Update
delete: ContentTypeFields_updatedAt_Delete
}
type ContentTypeFields_updatedAt_Create {
permission: Boolean!
}
type ContentTypeFields_updatedAt_Read {
permission: Boolean!
}
type ContentTypeFields_updatedAt_Update {
permission: Boolean!
}
type ContentTypeFields_updatedAt_Delete {
permission: Boolean!
}
type ContentTypeFields_createdAt {
create: ContentTypeFields_createdAt_Create
read: ContentTypeFields_createdAt_Read
update: ContentTypeFields_createdAt_Update
delete: ContentTypeFields_createdAt_Delete
}
type ContentTypeFields_createdAt_Create {
permission: Boolean!
}
type ContentTypeFields_createdAt_Read {
permission: Boolean!
}
type ContentTypeFields_createdAt_Update {
permission: Boolean!
}
type ContentTypeFields_createdAt_Delete {
permission: Boolean!
}
type ContentTypeCreateAccess {
permission: Boolean!
where: JSONObject
}
type ContentTypeReadAccess {
permission: Boolean!
where: JSONObject
}
type ContentTypeUpdateAccess {
permission: Boolean!
where: JSONObject
}
type ContentTypeDeleteAccess {
permission: Boolean!
where: JSONObject
}
type payload_preferencesAccess { type payload_preferencesAccess {
fields: PayloadPreferencesFields fields: PayloadPreferencesFields
create: PayloadPreferencesCreateAccess create: PayloadPreferencesCreateAccess
@@ -4296,12 +5013,18 @@ type Mutation {
createDummy(data: mutationDummyInput!, draft: Boolean): Dummy createDummy(data: mutationDummyInput!, draft: Boolean): Dummy
updateDummy(id: String!, autosave: Boolean, data: mutationDummyUpdateInput!, draft: Boolean): Dummy updateDummy(id: String!, autosave: Boolean, data: mutationDummyUpdateInput!, draft: Boolean): Dummy
deleteDummy(id: String!): Dummy deleteDummy(id: String!): Dummy
createErrorOnHook(data: mutationErrorOnHookInput!, draft: Boolean): ErrorOnHook
updateErrorOnHook(id: String!, autosave: Boolean, data: mutationErrorOnHookUpdateInput!, draft: Boolean): ErrorOnHook
deleteErrorOnHook(id: String!): ErrorOnHook
createPayloadApiTestOne(data: mutationPayloadApiTestOneInput!, draft: Boolean): PayloadApiTestOne createPayloadApiTestOne(data: mutationPayloadApiTestOneInput!, draft: Boolean): PayloadApiTestOne
updatePayloadApiTestOne(id: String!, autosave: Boolean, data: mutationPayloadApiTestOneUpdateInput!, draft: Boolean): PayloadApiTestOne updatePayloadApiTestOne(id: String!, autosave: Boolean, data: mutationPayloadApiTestOneUpdateInput!, draft: Boolean): PayloadApiTestOne
deletePayloadApiTestOne(id: String!): PayloadApiTestOne deletePayloadApiTestOne(id: String!): PayloadApiTestOne
createPayloadApiTestTwo(data: mutationPayloadApiTestTwoInput!, draft: Boolean): PayloadApiTestTwo createPayloadApiTestTwo(data: mutationPayloadApiTestTwoInput!, draft: Boolean): PayloadApiTestTwo
updatePayloadApiTestTwo(id: String!, autosave: Boolean, data: mutationPayloadApiTestTwoUpdateInput!, draft: Boolean): PayloadApiTestTwo updatePayloadApiTestTwo(id: String!, autosave: Boolean, data: mutationPayloadApiTestTwoUpdateInput!, draft: Boolean): PayloadApiTestTwo
deletePayloadApiTestTwo(id: String!): PayloadApiTestTwo deletePayloadApiTestTwo(id: String!): PayloadApiTestTwo
createContentType(data: mutationContentTypeInput!, draft: Boolean): ContentType
updateContentType(id: String!, autosave: Boolean, data: mutationContentTypeUpdateInput!, draft: Boolean): ContentType
deleteContentType(id: String!): ContentType
createPayloadPreference(data: mutationPayloadPreferenceInput!, draft: Boolean): PayloadPreference createPayloadPreference(data: mutationPayloadPreferenceInput!, draft: Boolean): PayloadPreference
updatePayloadPreference(id: String!, autosave: Boolean, data: mutationPayloadPreferenceUpdateInput!, draft: Boolean): PayloadPreference updatePayloadPreference(id: String!, autosave: Boolean, data: mutationPayloadPreferenceUpdateInput!, draft: Boolean): PayloadPreference
deletePayloadPreference(id: String!): PayloadPreference deletePayloadPreference(id: String!): PayloadPreference
@@ -4538,6 +5261,20 @@ input mutationDummyUpdateInput {
createdAt: String createdAt: String
} }
input mutationErrorOnHookInput {
title: String
errorBeforeChange: Boolean
updatedAt: String
createdAt: String
}
input mutationErrorOnHookUpdateInput {
title: String
errorBeforeChange: Boolean
updatedAt: String
createdAt: String
}
input mutationPayloadApiTestOneInput { input mutationPayloadApiTestOneInput {
payloadAPI: String payloadAPI: String
updatedAt: String updatedAt: String
@@ -4564,6 +5301,18 @@ input mutationPayloadApiTestTwoUpdateInput {
createdAt: String createdAt: String
} }
input mutationContentTypeInput {
contentType: String
updatedAt: String
createdAt: String
}
input mutationContentTypeUpdateInput {
contentType: String
updatedAt: String
createdAt: String
}
input mutationPayloadPreferenceInput { input mutationPayloadPreferenceInput {
user: PayloadPreference_UserRelationshipInput user: PayloadPreference_UserRelationshipInput
key: String key: String

View File

@@ -67,6 +67,15 @@ describe('collections-rest', () => {
expect(result.docs).toEqual(expect.arrayContaining(expectedDocs)) expect(result.docs).toEqual(expect.arrayContaining(expectedDocs))
}) })
it('should count', async () => {
await createPost()
await createPost()
const { result, status } = await client.count()
expect(status).toEqual(200)
expect(result).toEqual({ totalDocs: 2 })
})
it('should find where id', async () => { it('should find where id', async () => {
const post1 = await createPost() const post1 = await createPost()
await createPost() await createPost()

View File

@@ -142,6 +142,21 @@ const TabsFields: CollectionConfig = {
type: 'text', type: 'text',
defaultValue: namedTabDefaultValue, defaultValue: namedTabDefaultValue,
}, },
{
type: 'row',
fields: [
{
name: 'arrayInRow',
type: 'array',
fields: [
{
name: 'textInArrayInRow',
type: 'text',
},
],
},
],
},
], ],
}, },
{ {

View File

@@ -35,6 +35,11 @@ export const tabsDoc: Partial<TabsField> = {
}, },
], ],
text: namedTabText, text: namedTabText,
arrayInRow: [
{
text: "Hello, I'm some text in an array in a row",
},
],
}, },
localizedTab: { localizedTab: {
text: localizedTextValue, text: localizedTextValue,

View File

@@ -355,6 +355,29 @@ describe('fields', () => {
await saveDocAndAssert(page) await saveDocAndAssert(page)
await expect(field.locator('.rs__value-container')).toContainText('One') await expect(field.locator('.rs__value-container')).toContainText('One')
}) })
test('should not allow filtering by hasMany field / equals / not equals', async () => {
await page.goto(url.list)
await page.locator('.list-controls__toggle-columns').click()
await page.locator('.list-controls__toggle-where').click()
await page.waitForSelector('.list-controls__where.rah-static--height-auto')
await page.locator('.where-builder__add-first-filter').click()
const conditionField = page.locator('.condition__field')
await conditionField.click()
const dropdownFieldOptions = conditionField.locator('.rs__option')
await dropdownFieldOptions.locator('text=Select Has Many').nth(0).click()
const operatorField = page.locator('.condition__operator')
await operatorField.click()
const dropdownOperatorOptions = operatorField.locator('.rs__option')
await expect(dropdownOperatorOptions.locator('text=equals')).toBeHidden()
await expect(dropdownOperatorOptions.locator('text=not equals')).toBeHidden()
})
}) })
describe('point', () => { describe('point', () => {

View File

@@ -1083,6 +1083,10 @@ export interface TabsField {
}[] }[]
text?: string | null text?: string | null
defaultValue?: string | null defaultValue?: string | null
arrayInRow?: {
text: string
id?: string | null
}[]
} }
namedTabWithDefaultValue: { namedTabWithDefaultValue: {
defaultValue?: string | null defaultValue?: string | null

View File

@@ -30,6 +30,12 @@ type CreateArgs<T = any> = {
slug?: string slug?: string
} }
type CountArgs = {
auth?: boolean
query?: Where
slug?: string
}
type FindArgs = { type FindArgs = {
auth?: boolean auth?: boolean
depth?: number depth?: number
@@ -112,6 +118,13 @@ type QueryResponse<T> = {
status: number status: number
} }
type CountResponse = {
result: {
totalDocs: number
}
status: number
}
export class RESTClient { export class RESTClient {
private readonly config: Config private readonly config: Config
@@ -127,6 +140,32 @@ export class RESTClient {
this.defaultSlug = args.defaultSlug this.defaultSlug = args.defaultSlug
} }
async count<T = any>(args?: CountArgs): Promise<CountResponse> {
const options = {
headers: { ...headers },
}
if (args?.auth !== false && this.token) {
options.headers.Authorization = `JWT ${this.token}`
}
const whereQuery = qs.stringify(
{
...(args?.query ? { where: args.query } : {}),
},
{
addQueryPrefix: true,
},
)
const slug = args?.slug || this.defaultSlug
const response = await fetch(`${this.serverURL}/api/${slug}/count${whereQuery}`, options)
const { status } = response
const result = await response.json()
if (result.errors) throw new Error(result.errors[0].message)
return { result, status }
}
async create<T = any>(args: CreateArgs): Promise<DocResponse<T>> { async create<T = any>(args: CreateArgs): Promise<DocResponse<T>> {
const options = { const options = {
body: args.file ? args.data : JSON.stringify(args.data), body: args.file ? args.data : JSON.stringify(args.data),

View File

@@ -437,6 +437,7 @@ describe('Versions', () => {
collection, collection,
data: { data: {
title: patchedTitle, title: patchedTitle,
_status: 'draft',
}, },
draft: true, draft: true,
locale: 'en', locale: 'en',
@@ -450,6 +451,7 @@ describe('Versions', () => {
collection, collection,
data: { data: {
title: spanishTitle, title: spanishTitle,
_status: 'draft',
}, },
draft: true, draft: true,
locale: 'es', locale: 'es',
@@ -529,9 +531,11 @@ describe('Versions', () => {
}, },
}) })
// bulk publish
const updated = await payload.update({ const updated = await payload.update({
collection: draftCollectionSlug, collection: draftCollectionSlug,
data: { data: {
_status: 'published',
description: 'updated description', description: 'updated description',
}, },
draft: true, draft: true,
@@ -544,8 +548,20 @@ describe('Versions', () => {
const updatedDoc = updated.docs?.[0] const updatedDoc = updated.docs?.[0]
// get the published doc
const findResult = await payload.find({
collection: draftCollectionSlug,
where: {
id: { equals: doc.id },
},
})
const findDoc = findResult.docs?.[0]
expect(updatedDoc.description).toStrictEqual('updated description') expect(updatedDoc.description).toStrictEqual('updated description')
expect(updatedDoc.title).toStrictEqual('updated title') // probably will fail expect(updatedDoc.title).toStrictEqual('updated title')
expect(findDoc.title).toStrictEqual('updated title')
expect(findDoc.description).toStrictEqual('updated description')
}) })
}) })
@@ -1236,6 +1252,7 @@ describe('Versions', () => {
await payload.updateGlobal({ await payload.updateGlobal({
slug: globalSlug, slug: globalSlug,
data: { data: {
_status: 'draft',
title: updatedTitle2, title: updatedTitle2,
}, },
draft: true, draft: true,
@@ -1245,6 +1262,7 @@ describe('Versions', () => {
await payload.updateGlobal({ await payload.updateGlobal({
slug: globalSlug, slug: globalSlug,
data: { data: {
_status: 'draft',
title: updatedTitle2, title: updatedTitle2,
}, },
draft: true, draft: true,