diff --git a/docs/local-api/overview.mdx b/docs/local-api/overview.mdx
index eca5e20a07..666be3dcee 100644
--- a/docs/local-api/overview.mdx
+++ b/docs/local-api/overview.mdx
@@ -77,21 +77,22 @@ Both options function in exactly the same way outside of one having HMR support
You can specify more options within the Local API vs. REST or GraphQL due to the server-only context that they are executed in.
-| Local Option | Description |
-|-----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| `collection` | Required for Collection operations. Specifies the Collection slug to operate against. |
-| `data` | The data to use within the operation. Required for `create`, `update`. |
-| `depth` | [Control auto-population](../queries/depth) of nested relationship and upload fields. |
-| `locale` | Specify [locale](/docs/configuration/localization) for any returned documents. |
-| `fallbackLocale` | Specify a [fallback locale](/docs/configuration/localization) to use for any returned documents. |
-| `overrideAccess` | Skip access control. By default, this property is set to true within all Local API operations. |
-| `overrideLock` | By default, document locks are ignored (`true`). Set to `false` to enforce locks and prevent operations when a document is locked by another user. [More details](../admin/locked-documents). |
-| `user` | If you set `overrideAccess` to `false`, you can pass a user to use against the access control checks. |
-| `showHiddenFields` | Opt-in to receiving hidden fields. By default, they are hidden from returned documents in accordance to your config. |
-| `pagination` | Set to false to return all documents and avoid querying for document counts. |
-| `context` | [Context](/docs/hooks/context), which will then be passed to `context` and `req.context`, which can be read by hooks. Useful if you want to pass additional information to the hooks which shouldn't be necessarily part of the document, for example a `triggerBeforeChange` option which can be read by the BeforeChange hook to determine if it should run or not. |
-| `disableErrors` | When set to `true`, errors will not be thrown. Instead, the `findByID` operation will return `null`, and the `find` operation will return an empty documents array. |
-| `disableTransaction` | When set to `true`, a [database transactions](../database/transactions) will not be initialized. |
+| Local Option | Description |
+| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `collection` | Required for Collection operations. Specifies the Collection slug to operate against. |
+| `data` | The data to use within the operation. Required for `create`, `update`. |
+| `depth` | [Control auto-population](../queries/depth) of nested relationship and upload fields. |
+| `locale` | Specify [locale](/docs/configuration/localization) for any returned documents. |
+| `select` | Specify [select](../queries/select) to control which fields to include to the result. |
+| `fallbackLocale` | Specify a [fallback locale](/docs/configuration/localization) to use for any returned documents. |
+| `overrideAccess` | Skip access control. By default, this property is set to true within all Local API operations. |
+| `overrideLock` | By default, document locks are ignored (`true`). Set to `false` to enforce locks and prevent operations when a document is locked by another user. [More details](../admin/locked-documents). |
+| `user` | If you set `overrideAccess` to `false`, you can pass a user to use against the access control checks. |
+| `showHiddenFields` | Opt-in to receiving hidden fields. By default, they are hidden from returned documents in accordance to your config. |
+| `pagination` | Set to false to return all documents and avoid querying for document counts. |
+| `context` | [Context](/docs/hooks/context), which will then be passed to `context` and `req.context`, which can be read by hooks. Useful if you want to pass additional information to the hooks which shouldn't be necessarily part of the document, for example a `triggerBeforeChange` option which can be read by the BeforeChange hook to determine if it should run or not. |
+| `disableErrors` | When set to `true`, errors will not be thrown. Instead, the `findByID` operation will return `null`, and the `find` operation will return an empty documents array. |
+| `disableTransaction` | When set to `true`, a [database transactions](../database/transactions) will not be initialized. |
_There are more options available on an operation by operation basis outlined below._
diff --git a/docs/queries/select.mdx b/docs/queries/select.mdx
new file mode 100644
index 0000000000..3bd4fe694a
--- /dev/null
+++ b/docs/queries/select.mdx
@@ -0,0 +1,99 @@
+---
+title: Select
+label: Select
+order: 30
+desc: Payload select determines which fields are selected to the result.
+keywords: query, documents, pagination, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
+---
+
+You may not need the full data from your Local API / REST queries, but only some specific fields. The select fields API can help you to optimize those cases.
+
+## Local API
+
+To specify select in the [Local API](../local-api/overview), you can use the `select` option in your query:
+
+```ts
+// Include mode
+const getPosts = async () => {
+ const posts = await payload.find({
+ collection: 'posts',
+ select: {
+ text: true,
+ // select a specific field from group
+ group: {
+ number: true
+ },
+ // select all fields from array
+ array: true,
+ }, // highlight-line
+ })
+
+ return posts
+}
+
+// Exclude mode
+const getPosts = async () => {
+ const posts = await payload.find({
+ collection: 'posts',
+ // Select everything except for array and group.number
+ select: {
+ array: false,
+ group: {
+ number: false
+ }
+ }, // highlight-line
+ })
+
+ return posts
+}
+```
+
+
+
+ Important:
+ To perform querying with `select` efficiently, it works on the database level. Because of that, your `beforeRead` and `afterRead` hooks may not receive the full `doc`.
+
+
+
+## REST API
+
+To specify select in the [REST API](../rest-api/overview), you can use the `select` parameter in your query:
+
+```ts
+fetch('https://localhost:3000/api/posts?select[color]=true&select[group][number]=true') // highlight-line
+ .then((res) => res.json())
+ .then((data) => console.log(data))
+```
+
+To understand the syntax, you need to understand that complex URL search strings are parsed into a JSON object. This one isn't too bad, but more complex queries get unavoidably more difficult to write.
+
+For this reason, we recommend to use the extremely helpful and ubiquitous [`qs`](https://www.npmjs.com/package/qs) package to parse your JSON / object-formatted queries into query strings:
+
+```ts
+import { stringify } from 'qs-esm'
+
+const select = {
+ text: true,
+ group: {
+ number: true
+ }
+ // This query could be much more complex
+ // and QS would handle it beautifully
+}
+
+const getPosts = async () => {
+ const stringifiedQuery = stringify(
+ {
+ select, // ensure that `qs` adds the `select` property, too!
+ },
+ { addQueryPrefix: true },
+ )
+
+ const response = await fetch(`http://localhost:3000/api/posts${stringifiedQuery}`)
+ // Continue to handle the response below...
+}
+
+
+ Reminder:
+ This is the same for [Globals](../configuration/globals) using the `/api/globals` endpoint.
+
diff --git a/docs/rest-api/overview.mdx b/docs/rest-api/overview.mdx
index a41f40deab..2f3003f0e6 100644
--- a/docs/rest-api/overview.mdx
+++ b/docs/rest-api/overview.mdx
@@ -18,6 +18,7 @@ All Payload API routes are mounted and prefixed to your config's `routes.api` UR
- [depth](../queries/depth) - automatically populates relationships and uploads
- [locale](/docs/configuration/localization#retrieving-localized-docs) - retrieves document(s) in a specific locale
- [fallback-locale](/docs/configuration/localization#retrieving-localized-docs) - specifies a fallback locale if no locale value exists
+- [select](../queries/select) - speicifes which fields to include to the result
## Collections
diff --git a/packages/db-mongodb/src/deleteOne.ts b/packages/db-mongodb/src/deleteOne.ts
index 5cf4895b49..889b4a8e75 100644
--- a/packages/db-mongodb/src/deleteOne.ts
+++ b/packages/db-mongodb/src/deleteOne.ts
@@ -2,12 +2,13 @@ import type { DeleteOne, Document, PayloadRequest } from 'payload'
import type { MongooseAdapter } from './index.js'
+import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
import { withSession } from './withSession.js'
export const deleteOne: DeleteOne = async function deleteOne(
this: MongooseAdapter,
- { collection, req = {} as PayloadRequest, where },
+ { collection, req = {} as PayloadRequest, select, where },
) {
const Model = this.collections[collection]
const options = await withSession(this, req)
@@ -17,7 +18,14 @@ export const deleteOne: DeleteOne = async function deleteOne(
where,
})
- const doc = await Model.findOneAndDelete(query, options).lean()
+ const doc = await Model.findOneAndDelete(query, {
+ ...options,
+ projection: buildProjectionFromSelect({
+ adapter: this,
+ fields: this.payload.collections[collection].config.fields,
+ select,
+ }),
+ }).lean()
let result: Document = JSON.parse(JSON.stringify(doc))
diff --git a/packages/db-mongodb/src/find.ts b/packages/db-mongodb/src/find.ts
index b88f66c784..16529d25c4 100644
--- a/packages/db-mongodb/src/find.ts
+++ b/packages/db-mongodb/src/find.ts
@@ -7,6 +7,7 @@ import type { MongooseAdapter } from './index.js'
import { buildSortParam } from './queries/buildSortParam.js'
import { buildJoinAggregation } from './utilities/buildJoinAggregation.js'
+import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
import { withSession } from './withSession.js'
@@ -21,6 +22,7 @@ export const find: Find = async function find(
pagination,
projection,
req = {} as PayloadRequest,
+ select,
sort: sortArg,
where,
},
@@ -67,6 +69,14 @@ export const find: Find = async function find(
useEstimatedCount,
}
+ if (select) {
+ paginationOptions.projection = buildProjectionFromSelect({
+ adapter: this,
+ fields: collectionConfig.fields,
+ select,
+ })
+ }
+
if (this.collation) {
const defaultLocale = 'en'
paginationOptions.collation = {
diff --git a/packages/db-mongodb/src/findGlobal.ts b/packages/db-mongodb/src/findGlobal.ts
index 0473674dc4..a32c8ca729 100644
--- a/packages/db-mongodb/src/findGlobal.ts
+++ b/packages/db-mongodb/src/findGlobal.ts
@@ -4,17 +4,23 @@ import { combineQueries } from 'payload'
import type { MongooseAdapter } from './index.js'
+import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
import { withSession } from './withSession.js'
export const findGlobal: FindGlobal = async function findGlobal(
this: MongooseAdapter,
- { slug, locale, req = {} as PayloadRequest, where },
+ { slug, locale, req = {} as PayloadRequest, select, where },
) {
const Model = this.globals
const options = {
...(await withSession(this, req)),
lean: true,
+ select: buildProjectionFromSelect({
+ adapter: this,
+ fields: this.payload.globals.config.find((each) => each.slug === slug).fields,
+ select,
+ }),
}
const query = await Model.buildQuery({
diff --git a/packages/db-mongodb/src/findGlobalVersions.ts b/packages/db-mongodb/src/findGlobalVersions.ts
index 61c6cbcf2f..5a82355ef1 100644
--- a/packages/db-mongodb/src/findGlobalVersions.ts
+++ b/packages/db-mongodb/src/findGlobalVersions.ts
@@ -6,6 +6,7 @@ import { buildVersionGlobalFields, flattenWhereToOperators } from 'payload'
import type { MongooseAdapter } from './index.js'
import { buildSortParam } from './queries/buildSortParam.js'
+import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
import { withSession } from './withSession.js'
@@ -18,6 +19,7 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV
page,
pagination,
req = {} as PayloadRequest,
+ select,
skip,
sort: sortArg,
where,
@@ -69,6 +71,7 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV
options,
page,
pagination,
+ projection: buildProjectionFromSelect({ adapter: this, fields: versionFields, select }),
sort,
useEstimatedCount,
}
diff --git a/packages/db-mongodb/src/findOne.ts b/packages/db-mongodb/src/findOne.ts
index d2be516567..47f5615b90 100644
--- a/packages/db-mongodb/src/findOne.ts
+++ b/packages/db-mongodb/src/findOne.ts
@@ -1,15 +1,16 @@
-import type { MongooseQueryOptions } from 'mongoose'
+import type { MongooseQueryOptions, QueryOptions } from 'mongoose'
import type { Document, FindOne, PayloadRequest } from 'payload'
import type { MongooseAdapter } from './index.js'
import { buildJoinAggregation } from './utilities/buildJoinAggregation.js'
+import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
import { withSession } from './withSession.js'
export const findOne: FindOne = async function findOne(
this: MongooseAdapter,
- { collection, joins, locale, req = {} as PayloadRequest, where },
+ { collection, joins, locale, req = {} as PayloadRequest, select, where },
) {
const Model = this.collections[collection]
const collectionConfig = this.payload.collections[collection].config
@@ -24,6 +25,12 @@ export const findOne: FindOne = async function findOne(
where,
})
+ const projection = buildProjectionFromSelect({
+ adapter: this,
+ fields: collectionConfig.fields,
+ select,
+ })
+
const aggregate = await buildJoinAggregation({
adapter: this,
collection,
@@ -31,6 +38,7 @@ export const findOne: FindOne = async function findOne(
joins,
limit: 1,
locale,
+ projection,
query,
})
@@ -38,6 +46,7 @@ export const findOne: FindOne = async function findOne(
if (aggregate) {
;[doc] = await Model.aggregate(aggregate, options)
} else {
+ ;(options as Record).projection = projection
doc = await Model.findOne(query, {}, options)
}
diff --git a/packages/db-mongodb/src/findVersions.ts b/packages/db-mongodb/src/findVersions.ts
index 186d938214..1e66303c13 100644
--- a/packages/db-mongodb/src/findVersions.ts
+++ b/packages/db-mongodb/src/findVersions.ts
@@ -1,11 +1,12 @@
import type { PaginateOptions } from 'mongoose'
import type { FindVersions, PayloadRequest } from 'payload'
-import { flattenWhereToOperators } from 'payload'
+import { buildVersionCollectionFields, flattenWhereToOperators } from 'payload'
import type { MongooseAdapter } from './index.js'
import { buildSortParam } from './queries/buildSortParam.js'
+import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
import { withSession } from './withSession.js'
@@ -18,6 +19,7 @@ export const findVersions: FindVersions = async function findVersions(
page,
pagination,
req = {} as PayloadRequest,
+ select,
skip,
sort: sortArg,
where,
@@ -65,6 +67,11 @@ export const findVersions: FindVersions = async function findVersions(
options,
page,
pagination,
+ projection: buildProjectionFromSelect({
+ adapter: this,
+ fields: buildVersionCollectionFields(this.payload.config, collectionConfig),
+ select,
+ }),
sort,
useEstimatedCount,
}
diff --git a/packages/db-mongodb/src/queryDrafts.ts b/packages/db-mongodb/src/queryDrafts.ts
index fca0ec353d..b3ce18767a 100644
--- a/packages/db-mongodb/src/queryDrafts.ts
+++ b/packages/db-mongodb/src/queryDrafts.ts
@@ -1,12 +1,13 @@
import type { PaginateOptions } from 'mongoose'
import type { PayloadRequest, QueryDrafts } from 'payload'
-import { combineQueries, flattenWhereToOperators } from 'payload'
+import { buildVersionCollectionFields, combineQueries, flattenWhereToOperators } from 'payload'
import type { MongooseAdapter } from './index.js'
import { buildSortParam } from './queries/buildSortParam.js'
import { buildJoinAggregation } from './utilities/buildJoinAggregation.js'
+import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
import { withSession } from './withSession.js'
@@ -20,6 +21,7 @@ export const queryDrafts: QueryDrafts = async function queryDrafts(
page,
pagination,
req = {} as PayloadRequest,
+ select,
sort: sortArg,
where,
},
@@ -54,6 +56,11 @@ export const queryDrafts: QueryDrafts = async function queryDrafts(
where: combinedWhere,
})
+ const projection = buildProjectionFromSelect({
+ adapter: this,
+ fields: buildVersionCollectionFields(this.payload.config, collectionConfig),
+ select,
+ })
// 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 || !versionQuery || Object.keys(versionQuery).length === 0
@@ -64,6 +71,7 @@ export const queryDrafts: QueryDrafts = async function queryDrafts(
options,
page,
pagination,
+ projection,
sort,
useEstimatedCount,
}
@@ -109,6 +117,7 @@ export const queryDrafts: QueryDrafts = async function queryDrafts(
joins,
limit,
locale,
+ projection,
query: versionQuery,
versions: true,
})
diff --git a/packages/db-mongodb/src/updateGlobal.ts b/packages/db-mongodb/src/updateGlobal.ts
index 66bcdd4aff..4016a33ee1 100644
--- a/packages/db-mongodb/src/updateGlobal.ts
+++ b/packages/db-mongodb/src/updateGlobal.ts
@@ -2,19 +2,23 @@ import type { PayloadRequest, UpdateGlobal } from 'payload'
import type { MongooseAdapter } from './index.js'
+import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'
import { withSession } from './withSession.js'
export const updateGlobal: UpdateGlobal = async function updateGlobal(
this: MongooseAdapter,
- { slug, data, req = {} as PayloadRequest },
+ { slug, data, req = {} as PayloadRequest, select },
) {
const Model = this.globals
+ const fields = this.payload.config.globals.find((global) => global.slug === slug).fields
+
const options = {
...(await withSession(this, req)),
lean: true,
new: true,
+ projection: buildProjectionFromSelect({ adapter: this, fields, select }),
}
let result
@@ -22,7 +26,7 @@ export const updateGlobal: UpdateGlobal = async function updateGlobal(
const sanitizedData = sanitizeRelationshipIDs({
config: this.payload.config,
data,
- fields: this.payload.config.globals.find((global) => global.slug === slug).fields,
+ fields,
})
result = await Model.findOneAndUpdate({ globalType: slug }, sanitizedData, options)
diff --git a/packages/db-mongodb/src/updateGlobalVersion.ts b/packages/db-mongodb/src/updateGlobalVersion.ts
index 10618033d4..4588ef5951 100644
--- a/packages/db-mongodb/src/updateGlobalVersion.ts
+++ b/packages/db-mongodb/src/updateGlobalVersion.ts
@@ -7,6 +7,7 @@ import {
import type { MongooseAdapter } from './index.js'
+import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'
import { withSession } from './withSession.js'
@@ -17,16 +18,23 @@ export async function updateGlobalVersion(
global: globalSlug,
locale,
req = {} as PayloadRequest,
+ select,
versionData,
where,
}: UpdateGlobalVersionArgs,
) {
const VersionModel = this.versions[globalSlug]
const whereToUse = where || { id: { equals: id } }
+ const fields = buildVersionGlobalFields(
+ this.payload.config,
+ this.payload.config.globals.find((global) => global.slug === globalSlug),
+ )
+
const options = {
...(await withSession(this, req)),
lean: true,
new: true,
+ projection: buildProjectionFromSelect({ adapter: this, fields, select }),
}
const query = await VersionModel.buildQuery({
@@ -38,10 +46,7 @@ export async function updateGlobalVersion(
const sanitizedData = sanitizeRelationshipIDs({
config: this.payload.config,
data: versionData,
- fields: buildVersionGlobalFields(
- this.payload.config,
- this.payload.config.globals.find((global) => global.slug === globalSlug),
- ),
+ fields,
})
const doc = await VersionModel.findOneAndUpdate(query, sanitizedData, options)
diff --git a/packages/db-mongodb/src/updateOne.ts b/packages/db-mongodb/src/updateOne.ts
index c456b57d26..ce6d04ecac 100644
--- a/packages/db-mongodb/src/updateOne.ts
+++ b/packages/db-mongodb/src/updateOne.ts
@@ -3,6 +3,7 @@ import type { PayloadRequest, UpdateOne } from 'payload'
import type { MongooseAdapter } from './index.js'
+import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
import { handleError } from './utilities/handleError.js'
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'
@@ -17,16 +18,19 @@ export const updateOne: UpdateOne = async function updateOne(
locale,
options: optionsArgs = {},
req = {} as PayloadRequest,
+ select,
where: whereArg,
},
) {
const where = id ? { id: { equals: id } } : whereArg
const Model = this.collections[collection]
+ const fields = this.payload.collections[collection].config.fields
const options: QueryOptions = {
...optionsArgs,
...(await withSession(this, req)),
lean: true,
new: true,
+ projection: buildProjectionFromSelect({ adapter: this, fields, select }),
}
const query = await Model.buildQuery({
@@ -40,7 +44,7 @@ export const updateOne: UpdateOne = async function updateOne(
const sanitizedData = sanitizeRelationshipIDs({
config: this.payload.config,
data,
- fields: this.payload.collections[collection].config.fields,
+ fields,
})
try {
diff --git a/packages/db-mongodb/src/updateVersion.ts b/packages/db-mongodb/src/updateVersion.ts
index da45bbb60b..f199d4c7f1 100644
--- a/packages/db-mongodb/src/updateVersion.ts
+++ b/packages/db-mongodb/src/updateVersion.ts
@@ -2,19 +2,26 @@ import { buildVersionCollectionFields, type PayloadRequest, type UpdateVersion }
import type { MongooseAdapter } from './index.js'
+import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'
import { withSession } from './withSession.js'
export const updateVersion: UpdateVersion = async function updateVersion(
this: MongooseAdapter,
- { id, collection, locale, req = {} as PayloadRequest, versionData, where },
+ { id, collection, locale, req = {} as PayloadRequest, select, versionData, where },
) {
const VersionModel = this.versions[collection]
const whereToUse = where || { id: { equals: id } }
+ const fields = buildVersionCollectionFields(
+ this.payload.config,
+ this.payload.collections[collection].config,
+ )
+
const options = {
...(await withSession(this, req)),
lean: true,
new: true,
+ projection: buildProjectionFromSelect({ adapter: this, fields, select }),
}
const query = await VersionModel.buildQuery({
@@ -26,10 +33,7 @@ export const updateVersion: UpdateVersion = async function updateVersion(
const sanitizedData = sanitizeRelationshipIDs({
config: this.payload.config,
data: versionData,
- fields: buildVersionCollectionFields(
- this.payload.config,
- this.payload.collections[collection].config,
- ),
+ fields,
})
const doc = await VersionModel.findOneAndUpdate(query, sanitizedData, options)
diff --git a/packages/db-mongodb/src/upsert.ts b/packages/db-mongodb/src/upsert.ts
index c4d4376e19..8f846c4a23 100644
--- a/packages/db-mongodb/src/upsert.ts
+++ b/packages/db-mongodb/src/upsert.ts
@@ -4,7 +4,7 @@ import type { MongooseAdapter } from './index.js'
export const upsert: Upsert = async function upsert(
this: MongooseAdapter,
- { collection, data, locale, req = {} as PayloadRequest, where },
+ { collection, data, locale, req = {} as PayloadRequest, select, where },
) {
- return this.updateOne({ collection, data, locale, options: { upsert: true }, req, where })
+ return this.updateOne({ collection, data, locale, options: { upsert: true }, req, select, where })
}
diff --git a/packages/db-mongodb/src/utilities/buildJoinAggregation.ts b/packages/db-mongodb/src/utilities/buildJoinAggregation.ts
index 2ff570a25c..ab0654c596 100644
--- a/packages/db-mongodb/src/utilities/buildJoinAggregation.ts
+++ b/packages/db-mongodb/src/utilities/buildJoinAggregation.ts
@@ -13,6 +13,7 @@ type BuildJoinAggregationArgs = {
// the number of docs to get at the top collection level
limit?: number
locale: string
+ projection?: Record
// the where clause for the top collection
query?: Where
/** whether the query is from drafts */
@@ -26,6 +27,7 @@ export const buildJoinAggregation = async ({
joins,
limit,
locale,
+ projection,
query,
versions,
}: BuildJoinAggregationArgs): Promise => {
@@ -56,6 +58,10 @@ export const buildJoinAggregation = async ({
for (const join of joinConfig[slug]) {
const joinModel = adapter.collections[join.field.collection]
+ if (projection && !projection[join.schemaPath]) {
+ continue
+ }
+
const {
limit: limitJoin = join.field.defaultLimit ?? 10,
sort: sortJoin = join.field.defaultSort || collectionConfig.defaultSort,
@@ -174,5 +180,9 @@ export const buildJoinAggregation = async ({
}
}
+ if (projection) {
+ aggregate.push({ $project: projection })
+ }
+
return aggregate
}
diff --git a/packages/db-mongodb/src/utilities/buildProjectionFromSelect.ts b/packages/db-mongodb/src/utilities/buildProjectionFromSelect.ts
new file mode 100644
index 0000000000..1bb2bb7fea
--- /dev/null
+++ b/packages/db-mongodb/src/utilities/buildProjectionFromSelect.ts
@@ -0,0 +1,234 @@
+import {
+ deepCopyObjectSimple,
+ type Field,
+ type FieldAffectingData,
+ type SelectMode,
+ type SelectType,
+ type TabAsField,
+} from 'payload'
+import { fieldAffectsData, getSelectMode } from 'payload/shared'
+
+import type { MongooseAdapter } from '../index.js'
+
+const addFieldToProjection = ({
+ adapter,
+ databaseSchemaPath,
+ field,
+ projection,
+ withinLocalizedField,
+}: {
+ adapter: MongooseAdapter
+ databaseSchemaPath: string
+ field: FieldAffectingData
+ projection: Record
+ withinLocalizedField: boolean
+}) => {
+ const { config } = adapter.payload
+
+ if (withinLocalizedField && config.localization) {
+ for (const locale of config.localization.localeCodes) {
+ const localeDatabaseSchemaPath = databaseSchemaPath.replace('', locale)
+ projection[`${localeDatabaseSchemaPath}${field.name}`] = true
+ }
+ } else {
+ projection[`${databaseSchemaPath}${field.name}`] = true
+ }
+}
+
+const traverseFields = ({
+ adapter,
+ databaseSchemaPath = '',
+ fields,
+ projection,
+ select,
+ selectAllOnCurrentLevel = false,
+ selectMode,
+ withinLocalizedField = false,
+}: {
+ adapter: MongooseAdapter
+ databaseSchemaPath?: string
+ fields: (Field | TabAsField)[]
+ projection: Record
+ select: SelectType
+ selectAllOnCurrentLevel?: boolean
+ selectMode: SelectMode
+ withinLocalizedField?: boolean
+}) => {
+ for (const field of fields) {
+ if (fieldAffectsData(field)) {
+ if (selectMode === 'include') {
+ if (select[field.name] === true || selectAllOnCurrentLevel) {
+ addFieldToProjection({
+ adapter,
+ databaseSchemaPath,
+ field,
+ projection,
+ withinLocalizedField,
+ })
+ continue
+ }
+
+ if (!select[field.name]) {
+ continue
+ }
+ }
+
+ if (selectMode === 'exclude') {
+ if (typeof select[field.name] === 'undefined') {
+ addFieldToProjection({
+ adapter,
+ databaseSchemaPath,
+ field,
+ projection,
+ withinLocalizedField,
+ })
+ continue
+ }
+
+ if (select[field.name] === false) {
+ continue
+ }
+ }
+ }
+
+ let fieldDatabaseSchemaPath = databaseSchemaPath
+ let fieldWithinLocalizedField = withinLocalizedField
+
+ if (fieldAffectsData(field)) {
+ fieldDatabaseSchemaPath = `${databaseSchemaPath}${field.name}.`
+
+ if (field.localized) {
+ fieldDatabaseSchemaPath = `${fieldDatabaseSchemaPath}.`
+ fieldWithinLocalizedField = true
+ }
+ }
+
+ switch (field.type) {
+ case 'collapsible':
+ case 'row':
+ traverseFields({
+ adapter,
+ databaseSchemaPath,
+ fields: field.fields,
+ projection,
+ select,
+ selectMode,
+ withinLocalizedField,
+ })
+ break
+
+ case 'tabs':
+ traverseFields({
+ adapter,
+ databaseSchemaPath,
+ fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
+ projection,
+ select,
+ selectMode,
+ withinLocalizedField,
+ })
+ break
+
+ case 'group':
+ case 'tab':
+ case 'array':
+ if (field.type === 'array' && selectMode === 'include') {
+ select[field.name]['id'] = true
+ }
+
+ traverseFields({
+ adapter,
+ databaseSchemaPath: fieldDatabaseSchemaPath,
+ fields: field.fields,
+ projection,
+ select: select[field.name] as SelectType,
+ selectMode,
+ withinLocalizedField: fieldWithinLocalizedField,
+ })
+
+ break
+
+ case 'blocks': {
+ const blocksSelect = select[field.name] as SelectType
+
+ for (const block of field.blocks) {
+ if (
+ (selectMode === 'include' && blocksSelect[block.slug] === true) ||
+ (selectMode === 'exclude' && typeof blocksSelect[block.slug] === 'undefined')
+ ) {
+ traverseFields({
+ adapter,
+ databaseSchemaPath: fieldDatabaseSchemaPath,
+ fields: block.fields,
+ projection,
+ select: {},
+ selectAllOnCurrentLevel: true,
+ selectMode: 'include',
+ withinLocalizedField: fieldWithinLocalizedField,
+ })
+ continue
+ }
+
+ let blockSelectMode = selectMode
+
+ if (selectMode === 'exclude' && blocksSelect[block.slug] === false) {
+ blockSelectMode = 'include'
+ }
+
+ if (typeof blocksSelect[block.slug] !== 'object') {
+ blocksSelect[block.slug] = {}
+ }
+
+ if (blockSelectMode === 'include') {
+ blocksSelect[block.slug]['id'] = true
+ blocksSelect[block.slug]['blockType'] = true
+ }
+
+ traverseFields({
+ adapter,
+ databaseSchemaPath: fieldDatabaseSchemaPath,
+ fields: block.fields,
+ projection,
+ select: blocksSelect[block.slug] as SelectType,
+ selectMode: blockSelectMode,
+ withinLocalizedField: fieldWithinLocalizedField,
+ })
+ }
+
+ break
+ }
+
+ default:
+ break
+ }
+ }
+}
+
+export const buildProjectionFromSelect = ({
+ adapter,
+ fields,
+ select,
+}: {
+ adapter: MongooseAdapter
+ fields: Field[]
+ select?: SelectType
+}): Record | undefined => {
+ if (!select) {
+ return
+ }
+
+ const projection: Record = {
+ _id: true,
+ }
+
+ traverseFields({
+ adapter,
+ fields,
+ projection,
+ // Clone to safely mutate it later
+ select: deepCopyObjectSimple(select),
+ selectMode: getSelectMode(select),
+ })
+
+ return projection
+}
diff --git a/packages/drizzle/src/create.ts b/packages/drizzle/src/create.ts
index 7ab4679aec..9d40afb01c 100644
--- a/packages/drizzle/src/create.ts
+++ b/packages/drizzle/src/create.ts
@@ -8,7 +8,7 @@ import { upsertRow } from './upsertRow/index.js'
export const create: Create = async function create(
this: DrizzleAdapter,
- { collection: collectionSlug, data, req },
+ { collection: collectionSlug, data, req, select },
) {
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
const collection = this.payload.collections[collectionSlug].config
@@ -22,6 +22,7 @@ export const create: Create = async function create(
fields: collection.fields,
operation: 'create',
req,
+ select,
tableName,
})
diff --git a/packages/drizzle/src/createGlobalVersion.ts b/packages/drizzle/src/createGlobalVersion.ts
index d4c72db4bd..999cfb60f8 100644
--- a/packages/drizzle/src/createGlobalVersion.ts
+++ b/packages/drizzle/src/createGlobalVersion.ts
@@ -16,6 +16,7 @@ export async function createGlobalVersion(
globalSlug,
publishedLocale,
req = {} as PayloadRequest,
+ select,
snapshot,
updatedAt,
versionData,
@@ -41,6 +42,7 @@ export async function createGlobalVersion(
fields: buildVersionGlobalFields(this.payload.config, global),
operation: 'create',
req,
+ select,
tableName,
})
diff --git a/packages/drizzle/src/createVersion.ts b/packages/drizzle/src/createVersion.ts
index 0eda45ffcb..4f41aa7aca 100644
--- a/packages/drizzle/src/createVersion.ts
+++ b/packages/drizzle/src/createVersion.ts
@@ -17,6 +17,7 @@ export async function createVersion(
parent,
publishedLocale,
req = {} as PayloadRequest,
+ select,
snapshot,
updatedAt,
versionData,
@@ -51,6 +52,7 @@ export async function createVersion(
fields: buildVersionCollectionFields(this.payload.config, collection),
operation: 'create',
req,
+ select,
tableName,
})
diff --git a/packages/drizzle/src/deleteOne.ts b/packages/drizzle/src/deleteOne.ts
index 5b49a75538..ca9e330441 100644
--- a/packages/drizzle/src/deleteOne.ts
+++ b/packages/drizzle/src/deleteOne.ts
@@ -12,7 +12,13 @@ import { transform } from './transform/read/index.js'
export const deleteOne: DeleteOne = async function deleteOne(
this: DrizzleAdapter,
- { collection: collectionSlug, joins: joinQuery, req = {} as PayloadRequest, where: whereArg },
+ {
+ collection: collectionSlug,
+ joins: joinQuery,
+ req = {} as PayloadRequest,
+ select,
+ where: whereArg,
+ },
) {
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
const collection = this.payload.collections[collectionSlug].config
@@ -49,6 +55,7 @@ export const deleteOne: DeleteOne = async function deleteOne(
depth: 0,
fields: collection.fields,
joinQuery,
+ select,
tableName,
})
diff --git a/packages/drizzle/src/find.ts b/packages/drizzle/src/find.ts
index 9ec5bcf692..66b596ddba 100644
--- a/packages/drizzle/src/find.ts
+++ b/packages/drizzle/src/find.ts
@@ -16,6 +16,7 @@ export const find: Find = async function find(
page = 1,
pagination,
req = {} as PayloadRequest,
+ select,
sort: sortArg,
where,
},
@@ -34,6 +35,7 @@ export const find: Find = async function find(
page,
pagination,
req,
+ select,
sort,
tableName,
where,
diff --git a/packages/drizzle/src/find/buildFindManyArgs.ts b/packages/drizzle/src/find/buildFindManyArgs.ts
index 418a3da3ba..3614aa84fd 100644
--- a/packages/drizzle/src/find/buildFindManyArgs.ts
+++ b/packages/drizzle/src/find/buildFindManyArgs.ts
@@ -1,5 +1,7 @@
import type { DBQueryConfig } from 'drizzle-orm'
-import type { Field, JoinQuery } from 'payload'
+import type { Field, JoinQuery, SelectType } from 'payload'
+
+import { getSelectMode } from 'payload/shared'
import type { BuildQueryJoinAliases, DrizzleAdapter } from '../types.js'
@@ -15,6 +17,7 @@ type BuildFindQueryArgs = {
*/
joins?: BuildQueryJoinAliases
locale?: string
+ select?: SelectType
tableName: string
versions?: boolean
}
@@ -34,6 +37,7 @@ export const buildFindManyArgs = ({
joinQuery,
joins = [],
locale,
+ select,
tableName,
versions,
}: BuildFindQueryArgs): Record => {
@@ -42,48 +46,30 @@ export const buildFindManyArgs = ({
with: {},
}
+ if (select) {
+ result.columns = {
+ id: true,
+ }
+ }
+
const _locales: Result = {
- columns: {
- id: false,
- _parentID: false,
- },
+ columns: select
+ ? { _locale: true }
+ : {
+ id: false,
+ _parentID: false,
+ },
extras: {},
with: {},
}
- if (adapter.tables[`${tableName}_texts`]) {
- result.with._texts = {
- columns: {
- id: false,
- parent: false,
- },
- orderBy: ({ order }, { asc: ASC }) => [ASC(order)],
- }
- }
-
- if (adapter.tables[`${tableName}_numbers`]) {
- result.with._numbers = {
- columns: {
- id: false,
- parent: false,
- },
- orderBy: ({ order }, { asc: ASC }) => [ASC(order)],
- }
- }
-
- if (adapter.tables[`${tableName}${adapter.relationshipsSuffix}`]) {
- result.with._rels = {
- columns: {
- id: false,
- parent: false,
- },
- orderBy: ({ order }, { asc: ASC }) => [ASC(order)],
- }
- }
-
- if (adapter.tables[`${tableName}${adapter.localesSuffix}`]) {
- result.with._locales = _locales
- }
+ const withTabledFields = select
+ ? {}
+ : {
+ numbers: true,
+ rels: true,
+ texts: true,
+ }
traverseFields({
_locales,
@@ -96,11 +82,51 @@ export const buildFindManyArgs = ({
joins,
locale,
path: '',
+ select,
+ selectMode: select ? getSelectMode(select) : undefined,
tablePath: '',
topLevelArgs: result,
topLevelTableName: tableName,
versions,
+ withTabledFields,
})
+ if (adapter.tables[`${tableName}_texts`] && withTabledFields.texts) {
+ result.with._texts = {
+ columns: {
+ id: false,
+ parent: false,
+ },
+ orderBy: ({ order }, { asc: ASC }) => [ASC(order)],
+ }
+ }
+
+ if (adapter.tables[`${tableName}_numbers`] && withTabledFields.numbers) {
+ result.with._numbers = {
+ columns: {
+ id: false,
+ parent: false,
+ },
+ orderBy: ({ order }, { asc: ASC }) => [ASC(order)],
+ }
+ }
+
+ if (adapter.tables[`${tableName}${adapter.relationshipsSuffix}`] && withTabledFields.rels) {
+ result.with._rels = {
+ columns: {
+ id: false,
+ parent: false,
+ },
+ orderBy: ({ order }, { asc: ASC }) => [ASC(order)],
+ }
+ }
+
+ if (
+ adapter.tables[`${tableName}${adapter.localesSuffix}`] &&
+ (!select || Object.keys(_locales.columns).length > 1)
+ ) {
+ result.with._locales = _locales
+ }
+
return result
}
diff --git a/packages/drizzle/src/find/findMany.ts b/packages/drizzle/src/find/findMany.ts
index 442af7fd77..84491adc97 100644
--- a/packages/drizzle/src/find/findMany.ts
+++ b/packages/drizzle/src/find/findMany.ts
@@ -26,6 +26,7 @@ export const findMany = async function find({
page = 1,
pagination,
req = {} as PayloadRequest,
+ select,
skip,
sort,
tableName,
@@ -72,6 +73,7 @@ export const findMany = async function find({
fields,
joinQuery,
joins,
+ select,
tableName,
versions,
})
diff --git a/packages/drizzle/src/find/traverseFields.ts b/packages/drizzle/src/find/traverseFields.ts
index 558341179a..7badc56871 100644
--- a/packages/drizzle/src/find/traverseFields.ts
+++ b/packages/drizzle/src/find/traverseFields.ts
@@ -1,5 +1,5 @@
import type { LibSQLDatabase } from 'drizzle-orm/libsql'
-import type { Field, JoinQuery } from 'payload'
+import type { Field, JoinQuery, SelectMode, SelectType } from 'payload'
import { and, eq, sql } from 'drizzle-orm'
import { fieldAffectsData, fieldIsVirtual, tabHasName } from 'payload/shared'
@@ -22,10 +22,19 @@ type TraverseFieldArgs = {
joins?: BuildQueryJoinAliases
locale?: string
path: string
+ select?: SelectType
+ selectAllOnCurrentLevel?: boolean
+ selectMode?: SelectMode
tablePath: string
topLevelArgs: Record
topLevelTableName: string
versions?: boolean
+ withinLocalizedField?: boolean
+ withTabledFields: {
+ numbers?: boolean
+ rels?: boolean
+ texts?: boolean
+ }
}
export const traverseFields = ({
@@ -39,10 +48,15 @@ export const traverseFields = ({
joins,
locale,
path,
+ select,
+ selectAllOnCurrentLevel = false,
+ selectMode,
tablePath,
topLevelArgs,
topLevelTableName,
versions,
+ withinLocalizedField = false,
+ withTabledFields,
}: TraverseFieldArgs) => {
fields.forEach((field) => {
if (fieldIsVirtual(field)) {
@@ -74,9 +88,12 @@ export const traverseFields = ({
joinQuery,
joins,
path,
+ select,
+ selectMode,
tablePath,
topLevelArgs,
topLevelTableName,
+ withTabledFields,
})
return
@@ -87,6 +104,20 @@ export const traverseFields = ({
const tabPath = tabHasName(tab) ? `${path}${tab.name}_` : path
const tabTablePath = tabHasName(tab) ? `${tablePath}${toSnakeCase(tab.name)}_` : tablePath
+ const tabSelect = tabHasName(tab) ? select?.[tab.name] : select
+
+ if (tabSelect === false) {
+ return
+ }
+
+ let tabSelectAllOnCurrentLevel = selectAllOnCurrentLevel
+
+ if (tabHasName(tab) && select && !tabSelectAllOnCurrentLevel) {
+ tabSelectAllOnCurrentLevel =
+ select[tab.name] === true ||
+ (selectMode === 'exclude' && typeof select[tab.name] === 'undefined')
+ }
+
traverseFields({
_locales,
adapter,
@@ -97,10 +128,14 @@ export const traverseFields = ({
joinQuery,
joins,
path: tabPath,
+ select: typeof tabSelect === 'object' ? tabSelect : undefined,
+ selectAllOnCurrentLevel: tabSelectAllOnCurrentLevel,
+ selectMode,
tablePath: tabTablePath,
topLevelArgs,
topLevelTableName,
versions,
+ withTabledFields,
})
})
@@ -110,10 +145,27 @@ export const traverseFields = ({
if (fieldAffectsData(field)) {
switch (field.type) {
case 'array': {
+ const arraySelect = selectAllOnCurrentLevel ? true : select?.[field.name]
+
+ if (select) {
+ if (
+ (selectMode === 'include' && typeof arraySelect === 'undefined') ||
+ (selectMode === 'exclude' && arraySelect === false)
+ ) {
+ break
+ }
+ }
+
const withArray: Result = {
- columns: {
- _parentID: false,
- },
+ columns:
+ typeof arraySelect === 'object'
+ ? {
+ id: true,
+ _order: true,
+ }
+ : {
+ _parentID: false,
+ },
orderBy: ({ _order }, { asc }) => [asc(_order)],
with: {},
}
@@ -122,17 +174,33 @@ export const traverseFields = ({
`${currentTableName}_${tablePath}${toSnakeCase(field.name)}`,
)
+ if (typeof arraySelect === 'object') {
+ if (adapter.tables[arrayTableName]._locale) {
+ withArray.columns._locale = true
+ }
+
+ if (adapter.tables[arrayTableName]._uuid) {
+ withArray.columns._uuid = true
+ }
+ }
+
const arrayTableNameWithLocales = `${arrayTableName}${adapter.localesSuffix}`
if (adapter.tables[arrayTableNameWithLocales]) {
withArray.with._locales = {
- columns: {
- id: false,
- _parentID: false,
- },
+ columns:
+ typeof arraySelect === 'object'
+ ? {
+ _locale: true,
+ }
+ : {
+ id: false,
+ _parentID: false,
+ },
with: {},
}
}
+
currentArgs.with[`${path}${field.name}`] = withArray
traverseFields({
@@ -144,16 +212,37 @@ export const traverseFields = ({
fields: field.fields,
joinQuery,
path: '',
+ select: typeof arraySelect === 'object' ? arraySelect : undefined,
+ selectMode,
tablePath: '',
topLevelArgs,
topLevelTableName,
+ withinLocalizedField: withinLocalizedField || field.localized,
+ withTabledFields,
})
+ if (
+ typeof arraySelect === 'object' &&
+ withArray.with._locales &&
+ Object.keys(withArray.with._locales).length === 1
+ ) {
+ delete withArray.with._locales
+ }
+
break
}
case 'select': {
if (field.hasMany) {
+ if (select) {
+ if (
+ (selectMode === 'include' && !select[field.name]) ||
+ (selectMode === 'exclude' && select[field.name] === false)
+ ) {
+ break
+ }
+ }
+
const withSelect: Result = {
columns: {
id: false,
@@ -169,15 +258,55 @@ export const traverseFields = ({
break
}
- case 'blocks':
+ case 'blocks': {
+ const blocksSelect = selectAllOnCurrentLevel ? true : select?.[field.name]
+
+ if (select) {
+ if (
+ (selectMode === 'include' && !blocksSelect) ||
+ (selectMode === 'exclude' && blocksSelect === false)
+ ) {
+ break
+ }
+ }
+
field.blocks.forEach((block) => {
const blockKey = `_blocks_${block.slug}`
+ let blockSelect: boolean | SelectType | undefined
+
+ let blockSelectMode = selectMode
+
+ if (selectMode === 'include' && blocksSelect === true) {
+ blockSelect = true
+ }
+
+ if (typeof blocksSelect === 'object') {
+ if (typeof blocksSelect[block.slug] === 'object') {
+ blockSelect = blocksSelect[block.slug]
+ } else if (
+ (selectMode === 'include' && typeof blocksSelect[block.slug] === 'undefined') ||
+ (selectMode === 'exclude' && blocksSelect[block.slug] === false)
+ ) {
+ blockSelect = {}
+ blockSelectMode = 'include'
+ } else if (selectMode === 'include' && blocksSelect[block.slug] === true) {
+ blockSelect = true
+ }
+ }
+
if (!topLevelArgs[blockKey]) {
const withBlock: Result = {
- columns: {
- _parentID: false,
- },
+ columns:
+ typeof blockSelect === 'object'
+ ? {
+ id: true,
+ _order: true,
+ _path: true,
+ }
+ : {
+ _parentID: false,
+ },
orderBy: ({ _order }, { asc }) => [asc(_order)],
with: {},
}
@@ -186,10 +315,26 @@ export const traverseFields = ({
`${topLevelTableName}_blocks_${toSnakeCase(block.slug)}`,
)
+ if (typeof blockSelect === 'object') {
+ if (adapter.tables[tableName]._locale) {
+ withBlock.columns._locale = true
+ }
+
+ if (adapter.tables[tableName]._uuid) {
+ withBlock.columns._uuid = true
+ }
+ }
+
if (adapter.tables[`${tableName}${adapter.localesSuffix}`]) {
withBlock.with._locales = {
with: {},
}
+
+ if (typeof blockSelect === 'object') {
+ withBlock.with._locales.columns = {
+ _locale: true,
+ }
+ }
}
topLevelArgs.with[blockKey] = withBlock
@@ -202,16 +347,35 @@ export const traverseFields = ({
fields: block.fields,
joinQuery,
path: '',
+ select: typeof blockSelect === 'object' ? blockSelect : undefined,
+ selectMode: blockSelectMode,
tablePath: '',
topLevelArgs,
topLevelTableName,
+ withinLocalizedField: withinLocalizedField || field.localized,
+ withTabledFields,
})
+
+ if (
+ typeof blockSelect === 'object' &&
+ withBlock.with._locales &&
+ Object.keys(withBlock.with._locales.columns).length === 1
+ ) {
+ delete withBlock.with._locales
+ }
}
})
break
+ }
case 'group': {
+ const groupSelect = select?.[field.name]
+
+ if (groupSelect === false) {
+ break
+ }
+
traverseFields({
_locales,
adapter,
@@ -222,10 +386,18 @@ export const traverseFields = ({
joinQuery,
joins,
path: `${path}${field.name}_`,
+ select: typeof groupSelect === 'object' ? groupSelect : undefined,
+ selectAllOnCurrentLevel:
+ selectAllOnCurrentLevel ||
+ groupSelect === true ||
+ (selectMode === 'exclude' && typeof groupSelect === 'undefined'),
+ selectMode,
tablePath: `${tablePath}${toSnakeCase(field.name)}_`,
topLevelArgs,
topLevelTableName,
versions,
+ withinLocalizedField: withinLocalizedField || field.localized,
+ withTabledFields,
})
break
@@ -237,6 +409,13 @@ export const traverseFields = ({
break
}
+ if (
+ (select && selectMode === 'include' && !select[field.name]) ||
+ (selectMode === 'exclude' && select[field.name] === false)
+ ) {
+ break
+ }
+
const {
limit: limitArg = field.defaultLimit ?? 10,
sort = field.defaultSort,
@@ -410,6 +589,40 @@ export const traverseFields = ({
}
default: {
+ if (!select && !selectAllOnCurrentLevel) {
+ break
+ }
+
+ if (
+ selectAllOnCurrentLevel ||
+ (selectMode === 'include' && select[field.name] === true) ||
+ (selectMode === 'exclude' && typeof select[field.name] === 'undefined')
+ ) {
+ const fieldPath = `${path}${field.name}`
+
+ if ((field.localized || withinLocalizedField) && _locales) {
+ _locales.columns[fieldPath] = true
+ } else if (adapter.tables[currentTableName]?.[fieldPath]) {
+ currentArgs.columns[fieldPath] = true
+ }
+
+ if (
+ !withTabledFields.rels &&
+ field.type === 'relationship' &&
+ (field.hasMany || Array.isArray(field.relationTo))
+ ) {
+ withTabledFields.rels = true
+ }
+
+ if (!withTabledFields.numbers && field.type === 'number' && field.hasMany) {
+ withTabledFields.numbers = true
+ }
+
+ if (!withTabledFields.texts && field.type === 'text' && field.hasMany) {
+ withTabledFields.texts = true
+ }
+ }
+
break
}
}
diff --git a/packages/drizzle/src/findGlobal.ts b/packages/drizzle/src/findGlobal.ts
index c624a34b2b..f2873cd68a 100644
--- a/packages/drizzle/src/findGlobal.ts
+++ b/packages/drizzle/src/findGlobal.ts
@@ -8,7 +8,7 @@ import { findMany } from './find/findMany.js'
export const findGlobal: FindGlobal = async function findGlobal(
this: DrizzleAdapter,
- { slug, locale, req, where },
+ { slug, locale, req, select, where },
) {
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug)
@@ -23,6 +23,7 @@ export const findGlobal: FindGlobal = async function findGlobal(
locale,
pagination: false,
req,
+ select,
tableName,
where,
})
diff --git a/packages/drizzle/src/findGlobalVersions.ts b/packages/drizzle/src/findGlobalVersions.ts
index bb55ae3604..7e47c62d0c 100644
--- a/packages/drizzle/src/findGlobalVersions.ts
+++ b/packages/drizzle/src/findGlobalVersions.ts
@@ -16,6 +16,7 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV
page,
pagination,
req = {} as PayloadRequest,
+ select,
skip,
sort: sortArg,
where,
@@ -40,6 +41,7 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV
page,
pagination,
req,
+ select,
skip,
sort,
tableName,
diff --git a/packages/drizzle/src/findOne.ts b/packages/drizzle/src/findOne.ts
index 78da901a5c..fe8357bc13 100644
--- a/packages/drizzle/src/findOne.ts
+++ b/packages/drizzle/src/findOne.ts
@@ -8,7 +8,7 @@ import { findMany } from './find/findMany.js'
export async function findOne(
this: DrizzleAdapter,
- { collection, joins, locale, req = {} as PayloadRequest, where }: FindOneArgs,
+ { collection, joins, locale, req = {} as PayloadRequest, select, where }: FindOneArgs,
): Promise {
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
@@ -23,6 +23,7 @@ export async function findOne(
page: 1,
pagination: false,
req,
+ select,
sort: undefined,
tableName,
where,
diff --git a/packages/drizzle/src/findVersions.ts b/packages/drizzle/src/findVersions.ts
index ae0e280370..3c86547342 100644
--- a/packages/drizzle/src/findVersions.ts
+++ b/packages/drizzle/src/findVersions.ts
@@ -16,6 +16,7 @@ export const findVersions: FindVersions = async function findVersions(
page,
pagination,
req = {} as PayloadRequest,
+ select,
skip,
sort: sortArg,
where,
@@ -38,6 +39,7 @@ export const findVersions: FindVersions = async function findVersions(
page,
pagination,
req,
+ select,
skip,
sort,
tableName,
diff --git a/packages/drizzle/src/queryDrafts.ts b/packages/drizzle/src/queryDrafts.ts
index 9c32f1fbd7..654f9eaaf3 100644
--- a/packages/drizzle/src/queryDrafts.ts
+++ b/packages/drizzle/src/queryDrafts.ts
@@ -1,4 +1,4 @@
-import type { JoinQuery, PayloadRequest, QueryDrafts, SanitizedCollectionConfig } from 'payload'
+import type { PayloadRequest, QueryDrafts, SanitizedCollectionConfig } from 'payload'
import { buildVersionCollectionFields, combineQueries } from 'payload'
import toSnakeCase from 'to-snake-case'
@@ -17,6 +17,7 @@ export const queryDrafts: QueryDrafts = async function queryDrafts(
page = 1,
pagination,
req = {} as PayloadRequest,
+ select,
sort,
where,
},
@@ -38,6 +39,7 @@ export const queryDrafts: QueryDrafts = async function queryDrafts(
page,
pagination,
req,
+ select,
sort,
tableName,
versions: true,
diff --git a/packages/drizzle/src/update.ts b/packages/drizzle/src/update.ts
index 0526b78b57..9450564202 100644
--- a/packages/drizzle/src/update.ts
+++ b/packages/drizzle/src/update.ts
@@ -10,7 +10,7 @@ import { upsertRow } from './upsertRow/index.js'
export const updateOne: UpdateOne = async function updateOne(
this: DrizzleAdapter,
- { id, collection: collectionSlug, data, draft, joins: joinQuery, locale, req, where: whereArg },
+ { id, collection: collectionSlug, data, joins: joinQuery, locale, req, select, where: whereArg },
) {
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
const collection = this.payload.collections[collectionSlug].config
@@ -49,6 +49,7 @@ export const updateOne: UpdateOne = async function updateOne(
joinQuery,
operation: 'update',
req,
+ select,
tableName,
})
diff --git a/packages/drizzle/src/updateGlobal.ts b/packages/drizzle/src/updateGlobal.ts
index ccb13d47a6..18dfe83fc4 100644
--- a/packages/drizzle/src/updateGlobal.ts
+++ b/packages/drizzle/src/updateGlobal.ts
@@ -8,7 +8,7 @@ import { upsertRow } from './upsertRow/index.js'
export async function updateGlobal>(
this: DrizzleAdapter,
- { slug, data, req = {} as PayloadRequest }: UpdateGlobalArgs,
+ { slug, data, req = {} as PayloadRequest, select }: UpdateGlobalArgs,
): Promise {
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug)
@@ -23,6 +23,7 @@ export async function updateGlobal>(
db,
fields: globalConfig.fields,
req,
+ select,
tableName,
})
diff --git a/packages/drizzle/src/updateGlobalVersion.ts b/packages/drizzle/src/updateGlobalVersion.ts
index 3353d885aa..3b61fa57bf 100644
--- a/packages/drizzle/src/updateGlobalVersion.ts
+++ b/packages/drizzle/src/updateGlobalVersion.ts
@@ -21,6 +21,7 @@ export async function updateGlobalVersion(
global,
locale,
req = {} as PayloadRequest,
+ select,
versionData,
where: whereArg,
}: UpdateGlobalVersionArgs,
@@ -53,6 +54,7 @@ export async function updateGlobalVersion(
fields,
operation: 'update',
req,
+ select,
tableName,
where,
})
diff --git a/packages/drizzle/src/updateVersion.ts b/packages/drizzle/src/updateVersion.ts
index 25e285f907..935623bce9 100644
--- a/packages/drizzle/src/updateVersion.ts
+++ b/packages/drizzle/src/updateVersion.ts
@@ -21,6 +21,7 @@ export async function updateVersion(
collection,
locale,
req = {} as PayloadRequest,
+ select,
versionData,
where: whereArg,
}: UpdateVersionArgs,
@@ -50,6 +51,7 @@ export async function updateVersion(
fields,
operation: 'update',
req,
+ select,
tableName,
where,
})
diff --git a/packages/drizzle/src/upsertRow/index.ts b/packages/drizzle/src/upsertRow/index.ts
index 722fe88e9d..77c3448c00 100644
--- a/packages/drizzle/src/upsertRow/index.ts
+++ b/packages/drizzle/src/upsertRow/index.ts
@@ -24,6 +24,7 @@ export const upsertRow = async | TypeWithID>(
operation,
path = '',
req,
+ select,
tableName,
upsertTarget,
where,
@@ -415,6 +416,7 @@ export const upsertRow = async | TypeWithID>(
depth: 0,
fields,
joinQuery,
+ select,
tableName,
})
diff --git a/packages/drizzle/src/upsertRow/types.ts b/packages/drizzle/src/upsertRow/types.ts
index de29ff0423..81f0aa701e 100644
--- a/packages/drizzle/src/upsertRow/types.ts
+++ b/packages/drizzle/src/upsertRow/types.ts
@@ -1,5 +1,5 @@
import type { SQL } from 'drizzle-orm'
-import type { Field, JoinQuery, PayloadRequest } from 'payload'
+import type { Field, JoinQuery, PayloadRequest, SelectType } from 'payload'
import type { DrizzleAdapter, DrizzleTransaction, GenericColumn } from '../types.js'
@@ -23,6 +23,7 @@ type CreateArgs = {
id?: never
joinQuery?: never
operation: 'create'
+ select?: SelectType
upsertTarget?: never
where?: never
} & BaseArgs
@@ -31,6 +32,7 @@ type UpdateArgs = {
id?: number | string
joinQuery?: JoinQuery
operation: 'update'
+ select?: SelectType
upsertTarget?: GenericColumn
where?: SQL
} & BaseArgs
diff --git a/packages/graphql/src/resolvers/globals/update.ts b/packages/graphql/src/resolvers/globals/update.ts
index 1656356dc5..22678d7807 100644
--- a/packages/graphql/src/resolvers/globals/update.ts
+++ b/packages/graphql/src/resolvers/globals/update.ts
@@ -1,4 +1,10 @@
-import type { DataFromGlobalSlug, GlobalSlug, PayloadRequest, SanitizedGlobalConfig } from 'payload'
+import type {
+ DataFromGlobalSlug,
+ GlobalSlug,
+ PayloadRequest,
+ SanitizedGlobalConfig,
+ SelectType,
+} from 'payload'
import type { DeepPartial } from 'ts-essentials'
import { isolateObjectProperty, updateOperationGlobal } from 'payload'
@@ -40,7 +46,7 @@ export function update(
req: isolateObjectProperty(context.req, 'transactionID'),
}
- const result = await updateOperationGlobal(options)
+ const result = await updateOperationGlobal(options)
return result
}
}
diff --git a/packages/next/src/routes/rest/collections/create.ts b/packages/next/src/routes/rest/collections/create.ts
index 191d438548..e388d8c5a0 100644
--- a/packages/next/src/routes/rest/collections/create.ts
+++ b/packages/next/src/routes/rest/collections/create.ts
@@ -6,6 +6,7 @@ import { isNumber } from 'payload/shared'
import type { CollectionRouteHandler } from '../types.js'
import { headersWithCors } from '../../../utilities/headersWithCors.js'
+import { sanitizeSelect } from '../utilities/sanitizeSelect.js'
export const create: CollectionRouteHandler = async ({ collection, req }) => {
const { searchParams } = req
@@ -20,6 +21,7 @@ export const create: CollectionRouteHandler = async ({ collection, req }) => {
depth: isNumber(depth) ? depth : undefined,
draft,
req,
+ select: sanitizeSelect(req.query.select),
})
return Response.json(
diff --git a/packages/next/src/routes/rest/collections/delete.ts b/packages/next/src/routes/rest/collections/delete.ts
index c005019fa5..5d5db732bc 100644
--- a/packages/next/src/routes/rest/collections/delete.ts
+++ b/packages/next/src/routes/rest/collections/delete.ts
@@ -8,11 +8,13 @@ import { isNumber } from 'payload/shared'
import type { CollectionRouteHandler } from '../types.js'
import { headersWithCors } from '../../../utilities/headersWithCors.js'
+import { sanitizeSelect } from '../utilities/sanitizeSelect.js'
export const deleteDoc: CollectionRouteHandler = async ({ collection, req }) => {
- const { depth, overrideLock, where } = req.query as {
+ const { depth, overrideLock, select, where } = req.query as {
depth?: string
overrideLock?: string
+ select?: Record
where?: Where
}
@@ -21,6 +23,7 @@ export const deleteDoc: CollectionRouteHandler = async ({ collection, req }) =>
depth: isNumber(depth) ? Number(depth) : undefined,
overrideLock: Boolean(overrideLock === 'true'),
req,
+ select: sanitizeSelect(select),
where,
})
diff --git a/packages/next/src/routes/rest/collections/deleteByID.ts b/packages/next/src/routes/rest/collections/deleteByID.ts
index 2aa1508934..9f4869a6d9 100644
--- a/packages/next/src/routes/rest/collections/deleteByID.ts
+++ b/packages/next/src/routes/rest/collections/deleteByID.ts
@@ -6,6 +6,7 @@ import type { CollectionRouteHandlerWithID } from '../types.js'
import { headersWithCors } from '../../../utilities/headersWithCors.js'
import { sanitizeCollectionID } from '../utilities/sanitizeCollectionID.js'
+import { sanitizeSelect } from '../utilities/sanitizeSelect.js'
export const deleteByID: CollectionRouteHandlerWithID = async ({
id: incomingID,
@@ -28,6 +29,7 @@ export const deleteByID: CollectionRouteHandlerWithID = async ({
depth: isNumber(depth) ? depth : undefined,
overrideLock: Boolean(overrideLock === 'true'),
req,
+ select: sanitizeSelect(req.query.select),
})
const headers = headersWithCors({
diff --git a/packages/next/src/routes/rest/collections/duplicate.ts b/packages/next/src/routes/rest/collections/duplicate.ts
index dddb4d16db..891af1794f 100644
--- a/packages/next/src/routes/rest/collections/duplicate.ts
+++ b/packages/next/src/routes/rest/collections/duplicate.ts
@@ -7,6 +7,7 @@ import type { CollectionRouteHandlerWithID } from '../types.js'
import { headersWithCors } from '../../../utilities/headersWithCors.js'
import { sanitizeCollectionID } from '../utilities/sanitizeCollectionID.js'
+import { sanitizeSelect } from '../utilities/sanitizeSelect.js'
export const duplicate: CollectionRouteHandlerWithID = async ({
id: incomingID,
@@ -30,6 +31,7 @@ export const duplicate: CollectionRouteHandlerWithID = async ({
depth: isNumber(depth) ? Number(depth) : undefined,
draft,
req,
+ select: sanitizeSelect(req.query.select),
})
const message = req.t('general:successfullyDuplicated', {
diff --git a/packages/next/src/routes/rest/collections/find.ts b/packages/next/src/routes/rest/collections/find.ts
index ffa3eb19d9..29fbcc21f1 100644
--- a/packages/next/src/routes/rest/collections/find.ts
+++ b/packages/next/src/routes/rest/collections/find.ts
@@ -8,14 +8,16 @@ import type { CollectionRouteHandler } from '../types.js'
import { headersWithCors } from '../../../utilities/headersWithCors.js'
import { sanitizeJoinParams } from '../utilities/sanitizeJoinParams.js'
+import { sanitizeSelect } from '../utilities/sanitizeSelect.js'
export const find: CollectionRouteHandler = async ({ collection, req }) => {
- const { depth, draft, joins, limit, page, sort, where } = req.query as {
+ const { depth, draft, joins, limit, page, select, sort, where } = req.query as {
depth?: string
draft?: string
joins?: JoinQuery
limit?: string
page?: string
+ select?: Record
sort?: string
where?: Where
}
@@ -28,6 +30,7 @@ export const find: CollectionRouteHandler = async ({ collection, req }) => {
limit: isNumber(limit) ? Number(limit) : undefined,
page: isNumber(page) ? Number(page) : undefined,
req,
+ select: sanitizeSelect(select),
sort: typeof sort === 'string' ? sort.split(',') : undefined,
where,
})
diff --git a/packages/next/src/routes/rest/collections/findByID.ts b/packages/next/src/routes/rest/collections/findByID.ts
index 55eebedd65..4faeace3c4 100644
--- a/packages/next/src/routes/rest/collections/findByID.ts
+++ b/packages/next/src/routes/rest/collections/findByID.ts
@@ -9,6 +9,7 @@ import type { CollectionRouteHandlerWithID } from '../types.js'
import { headersWithCors } from '../../../utilities/headersWithCors.js'
import { sanitizeCollectionID } from '../utilities/sanitizeCollectionID.js'
import { sanitizeJoinParams } from '../utilities/sanitizeJoinParams.js'
+import { sanitizeSelect } from '../utilities/sanitizeSelect.js'
export const findByID: CollectionRouteHandlerWithID = async ({
id: incomingID,
@@ -31,6 +32,7 @@ export const findByID: CollectionRouteHandlerWithID = async ({
draft: searchParams.get('draft') === 'true',
joins: sanitizeJoinParams(req.query.joins as JoinQuery),
req,
+ select: sanitizeSelect(req.query.select),
})
return Response.json(result, {
diff --git a/packages/next/src/routes/rest/collections/findVersionByID.ts b/packages/next/src/routes/rest/collections/findVersionByID.ts
index dee8567346..7ad31fc93a 100644
--- a/packages/next/src/routes/rest/collections/findVersionByID.ts
+++ b/packages/next/src/routes/rest/collections/findVersionByID.ts
@@ -6,6 +6,7 @@ import type { CollectionRouteHandlerWithID } from '../types.js'
import { headersWithCors } from '../../../utilities/headersWithCors.js'
import { sanitizeCollectionID } from '../utilities/sanitizeCollectionID.js'
+import { sanitizeSelect } from '../utilities/sanitizeSelect.js'
export const findVersionByID: CollectionRouteHandlerWithID = async ({
id: incomingID,
@@ -26,6 +27,7 @@ export const findVersionByID: CollectionRouteHandlerWithID = async ({
collection,
depth: isNumber(depth) ? Number(depth) : undefined,
req,
+ select: sanitizeSelect(req.query.select),
})
return Response.json(result, {
diff --git a/packages/next/src/routes/rest/collections/findVersions.ts b/packages/next/src/routes/rest/collections/findVersions.ts
index 25cf18d241..358c3aacc0 100644
--- a/packages/next/src/routes/rest/collections/findVersions.ts
+++ b/packages/next/src/routes/rest/collections/findVersions.ts
@@ -7,12 +7,14 @@ import { isNumber } from 'payload/shared'
import type { CollectionRouteHandler } from '../types.js'
import { headersWithCors } from '../../../utilities/headersWithCors.js'
+import { sanitizeSelect } from '../utilities/sanitizeSelect.js'
export const findVersions: CollectionRouteHandler = async ({ collection, req }) => {
- const { depth, limit, page, sort, where } = req.query as {
+ const { depth, limit, page, select, sort, where } = req.query as {
depth?: string
limit?: string
page?: string
+ select?: Record
sort?: string
where?: Where
}
@@ -23,6 +25,7 @@ export const findVersions: CollectionRouteHandler = async ({ collection, req })
limit: isNumber(limit) ? Number(limit) : undefined,
page: isNumber(page) ? Number(page) : undefined,
req,
+ select: sanitizeSelect(select),
sort: typeof sort === 'string' ? sort.split(',') : undefined,
where,
})
diff --git a/packages/next/src/routes/rest/collections/update.ts b/packages/next/src/routes/rest/collections/update.ts
index 1ebc070051..70e4366fb9 100644
--- a/packages/next/src/routes/rest/collections/update.ts
+++ b/packages/next/src/routes/rest/collections/update.ts
@@ -8,13 +8,15 @@ import { isNumber } from 'payload/shared'
import type { CollectionRouteHandler } from '../types.js'
import { headersWithCors } from '../../../utilities/headersWithCors.js'
+import { sanitizeSelect } from '../utilities/sanitizeSelect.js'
export const update: CollectionRouteHandler = async ({ collection, req }) => {
- const { depth, draft, limit, overrideLock, where } = req.query as {
+ const { depth, draft, limit, overrideLock, select, where } = req.query as {
depth?: string
draft?: string
limit?: string
overrideLock?: string
+ select?: Record
where?: Where
}
@@ -26,6 +28,7 @@ export const update: CollectionRouteHandler = async ({ collection, req }) => {
limit: isNumber(limit) ? Number(limit) : undefined,
overrideLock: Boolean(overrideLock === 'true'),
req,
+ select: sanitizeSelect(select),
where,
})
diff --git a/packages/next/src/routes/rest/collections/updateByID.ts b/packages/next/src/routes/rest/collections/updateByID.ts
index cf7ca62597..7d4b50fdd7 100644
--- a/packages/next/src/routes/rest/collections/updateByID.ts
+++ b/packages/next/src/routes/rest/collections/updateByID.ts
@@ -6,6 +6,7 @@ import type { CollectionRouteHandlerWithID } from '../types.js'
import { headersWithCors } from '../../../utilities/headersWithCors.js'
import { sanitizeCollectionID } from '../utilities/sanitizeCollectionID.js'
+import { sanitizeSelect } from '../utilities/sanitizeSelect.js'
export const updateByID: CollectionRouteHandlerWithID = async ({
id: incomingID,
@@ -35,6 +36,7 @@ export const updateByID: CollectionRouteHandlerWithID = async ({
overrideLock: Boolean(overrideLock === 'true'),
publishSpecificLocale,
req,
+ select: sanitizeSelect(req.query.select),
})
let message = req.t('general:updatedSuccessfully')
diff --git a/packages/next/src/routes/rest/globals/findOne.ts b/packages/next/src/routes/rest/globals/findOne.ts
index e7d15a453c..115200f90e 100644
--- a/packages/next/src/routes/rest/globals/findOne.ts
+++ b/packages/next/src/routes/rest/globals/findOne.ts
@@ -5,6 +5,7 @@ import { isNumber } from 'payload/shared'
import type { GlobalRouteHandler } from '../types.js'
import { headersWithCors } from '../../../utilities/headersWithCors.js'
+import { sanitizeSelect } from '../utilities/sanitizeSelect.js'
export const findOne: GlobalRouteHandler = async ({ globalConfig, req }) => {
const { searchParams } = req
@@ -16,6 +17,7 @@ export const findOne: GlobalRouteHandler = async ({ globalConfig, req }) => {
draft: searchParams.get('draft') === 'true',
globalConfig,
req,
+ select: sanitizeSelect(req.query.select),
})
return Response.json(result, {
diff --git a/packages/next/src/routes/rest/globals/findVersionByID.ts b/packages/next/src/routes/rest/globals/findVersionByID.ts
index d3c0b34cae..8fd3fd7735 100644
--- a/packages/next/src/routes/rest/globals/findVersionByID.ts
+++ b/packages/next/src/routes/rest/globals/findVersionByID.ts
@@ -5,6 +5,7 @@ import { isNumber } from 'payload/shared'
import type { GlobalRouteHandlerWithID } from '../types.js'
import { headersWithCors } from '../../../utilities/headersWithCors.js'
+import { sanitizeSelect } from '../utilities/sanitizeSelect.js'
export const findVersionByID: GlobalRouteHandlerWithID = async ({ id, globalConfig, req }) => {
const { searchParams } = req
@@ -15,6 +16,7 @@ export const findVersionByID: GlobalRouteHandlerWithID = async ({ id, globalConf
depth: isNumber(depth) ? Number(depth) : undefined,
globalConfig,
req,
+ select: sanitizeSelect(req.query.select),
})
return Response.json(result, {
diff --git a/packages/next/src/routes/rest/globals/findVersions.ts b/packages/next/src/routes/rest/globals/findVersions.ts
index d868cd44f3..095cf99d3f 100644
--- a/packages/next/src/routes/rest/globals/findVersions.ts
+++ b/packages/next/src/routes/rest/globals/findVersions.ts
@@ -7,12 +7,14 @@ import { isNumber } from 'payload/shared'
import type { GlobalRouteHandler } from '../types.js'
import { headersWithCors } from '../../../utilities/headersWithCors.js'
+import { sanitizeSelect } from '../utilities/sanitizeSelect.js'
export const findVersions: GlobalRouteHandler = async ({ globalConfig, req }) => {
- const { depth, limit, page, sort, where } = req.query as {
+ const { depth, limit, page, select, sort, where } = req.query as {
depth?: string
limit?: string
page?: string
+ select?: Record
sort?: string
where?: Where
}
@@ -23,6 +25,7 @@ export const findVersions: GlobalRouteHandler = async ({ globalConfig, req }) =>
limit: isNumber(limit) ? Number(limit) : undefined,
page: isNumber(page) ? Number(page) : undefined,
req,
+ select: sanitizeSelect(select),
sort: typeof sort === 'string' ? sort.split(',') : undefined,
where,
})
diff --git a/packages/next/src/routes/rest/globals/update.ts b/packages/next/src/routes/rest/globals/update.ts
index afc8e70add..5007de78a1 100644
--- a/packages/next/src/routes/rest/globals/update.ts
+++ b/packages/next/src/routes/rest/globals/update.ts
@@ -5,6 +5,7 @@ import { isNumber } from 'payload/shared'
import type { GlobalRouteHandler } from '../types.js'
import { headersWithCors } from '../../../utilities/headersWithCors.js'
+import { sanitizeSelect } from '../utilities/sanitizeSelect.js'
export const update: GlobalRouteHandler = async ({ globalConfig, req }) => {
const { searchParams } = req
@@ -22,6 +23,7 @@ export const update: GlobalRouteHandler = async ({ globalConfig, req }) => {
globalConfig,
publishSpecificLocale,
req,
+ select: sanitizeSelect(req.query.select),
})
let message = req.t('general:updatedSuccessfully')
diff --git a/packages/next/src/routes/rest/utilities/sanitizeSelect.ts b/packages/next/src/routes/rest/utilities/sanitizeSelect.ts
new file mode 100644
index 0000000000..a837ffc6d1
--- /dev/null
+++ b/packages/next/src/routes/rest/utilities/sanitizeSelect.ts
@@ -0,0 +1,20 @@
+import type { SelectType } from 'payload'
+
+/**
+ * Sanitizes REST select query to SelectType
+ */
+export const sanitizeSelect = (unsanitizedSelect: unknown): SelectType | undefined => {
+ if (unsanitizedSelect && typeof unsanitizedSelect === 'object') {
+ for (const k in unsanitizedSelect) {
+ if (unsanitizedSelect[k] === 'true') {
+ unsanitizedSelect[k] = true
+ } else if (unsanitizedSelect[k] === 'false') {
+ unsanitizedSelect[k] = false
+ } else if (typeof unsanitizedSelect[k] === 'object') {
+ sanitizeSelect(unsanitizedSelect[k])
+ }
+ }
+ }
+
+ return unsanitizedSelect as SelectType
+}
diff --git a/packages/payload/src/auth/operations/registerFirstUser.ts b/packages/payload/src/auth/operations/registerFirstUser.ts
index 66ce4d7275..2030726e3c 100644
--- a/packages/payload/src/auth/operations/registerFirstUser.ts
+++ b/packages/payload/src/auth/operations/registerFirstUser.ts
@@ -5,7 +5,7 @@ import type {
RequiredDataFromCollectionSlug,
} from '../../collections/config/types.js'
import type { CollectionSlug } from '../../index.js'
-import type { PayloadRequest } from '../../types/index.js'
+import type { PayloadRequest, SelectType } from '../../types/index.js'
import { Forbidden } from '../../errors/index.js'
import { commitTransaction } from '../../utilities/commitTransaction.js'
@@ -66,7 +66,7 @@ export const registerFirstUserOperation = async (
// Register first user
// /////////////////////////////////////
- const result = await payload.create({
+ const result = await payload.create({
collection: slug as TSlug,
data,
overrideAccess: true,
diff --git a/packages/payload/src/auth/strategies/local/register.ts b/packages/payload/src/auth/strategies/local/register.ts
index 1ea35110fd..75b2603c10 100644
--- a/packages/payload/src/auth/strategies/local/register.ts
+++ b/packages/payload/src/auth/strategies/local/register.ts
@@ -1,6 +1,6 @@
import type { SanitizedCollectionConfig } from '../../../collections/config/types.js'
import type { JsonObject, Payload } from '../../../index.js'
-import type { PayloadRequest, Where } from '../../../types/index.js'
+import type { PayloadRequest, SelectType, Where } from '../../../types/index.js'
import { ValidationError } from '../../../errors/index.js'
import { generatePasswordSaltHash } from './generatePasswordSaltHash.js'
@@ -11,6 +11,7 @@ type Args = {
password: string
payload: Payload
req: PayloadRequest
+ select?: SelectType
}
export const registerLocalStrategy = async ({
@@ -19,6 +20,7 @@ export const registerLocalStrategy = async ({
password,
payload,
req,
+ select,
}: Args): Promise> => {
const loginWithUsername = collection?.auth?.loginWithUsername
@@ -90,5 +92,6 @@ export const registerLocalStrategy = async ({
salt,
},
req,
+ select,
})
}
diff --git a/packages/payload/src/bin/generateTypes.ts b/packages/payload/src/bin/generateTypes.ts
index dc367ecff1..d2d56035b7 100644
--- a/packages/payload/src/bin/generateTypes.ts
+++ b/packages/payload/src/bin/generateTypes.ts
@@ -3,6 +3,7 @@ import { compile } from 'json-schema-to-typescript'
import type { SanitizedConfig } from '../config/types.js'
+import { addSelectGenericsToGeneratedTypes } from '../utilities/addSelectGenericsToGeneretedTypes.js'
import { configToJSONSchema } from '../utilities/configToJSONSchema.js'
import { getLogger } from '../utilities/logger.js'
@@ -36,6 +37,8 @@ export async function generateTypes(
unreachableDefinitions: true,
})
+ compiled = addSelectGenericsToGeneratedTypes({ compiledGeneratedTypes: compiled })
+
if (config.typescript.declare !== false) {
if (config.typescript.declare?.ignoreTSError) {
compiled += `\n\n${declareWithTSIgnoreError}`
diff --git a/packages/payload/src/collections/config/types.ts b/packages/payload/src/collections/config/types.ts
index b0e1723d5b..4a83e98749 100644
--- a/packages/payload/src/collections/config/types.ts
+++ b/packages/payload/src/collections/config/types.ts
@@ -37,8 +37,15 @@ import type {
JsonObject,
TypedAuthOperations,
TypedCollection,
+ TypedCollectionSelect,
} from '../../index.js'
-import type { PayloadRequest, RequestContext, Sort } from '../../types/index.js'
+import type {
+ PayloadRequest,
+ RequestContext,
+ SelectType,
+ Sort,
+ TransformCollectionWithSelect,
+} from '../../types/index.js'
import type { SanitizedUploadConfig, UploadConfig } from '../../uploads/types.js'
import type {
IncomingCollectionVersions,
@@ -47,6 +54,9 @@ import type {
import type { AfterOperationArg, AfterOperationMap } from '../operations/utils.js'
export type DataFromCollectionSlug = TypedCollection[TSlug]
+
+export type SelectFromCollectionSlug = TypedCollectionSelect[TSlug]
+
export type AuthOperationsFromCollectionSlug =
TypedAuthOperations[TSlug]
@@ -523,8 +533,8 @@ export type Collection = {
}
}
-export type BulkOperationResult = {
- docs: DataFromCollectionSlug[]
+export type BulkOperationResult = {
+ docs: TransformCollectionWithSelect[]
errors: {
id: DataFromCollectionSlug['id']
message: string
diff --git a/packages/payload/src/collections/operations/create.ts b/packages/payload/src/collections/operations/create.ts
index bb223a100d..ede23e3316 100644
--- a/packages/payload/src/collections/operations/create.ts
+++ b/packages/payload/src/collections/operations/create.ts
@@ -1,14 +1,19 @@
import crypto from 'crypto'
import type { CollectionSlug, JsonObject } from '../../index.js'
-import type { Document, PayloadRequest } from '../../types/index.js'
+import type {
+ Document,
+ PayloadRequest,
+ SelectType,
+ TransformCollectionWithSelect,
+} from '../../types/index.js'
import type {
AfterChangeHook,
BeforeOperationHook,
BeforeValidateHook,
Collection,
- DataFromCollectionSlug,
RequiredDataFromCollectionSlug,
+ SelectFromCollectionSlug,
} from '../config/types.js'
import { ensureUsernameOrEmail } from '../../auth/ensureUsernameOrEmail.js'
@@ -40,12 +45,16 @@ export type Arguments = {
overrideAccess?: boolean
overwriteExistingFiles?: boolean
req: PayloadRequest
+ select?: SelectType
showHiddenFields?: boolean
}
-export const createOperation = async (
+export const createOperation = async <
+ TSlug extends CollectionSlug,
+ TSelect extends SelectFromCollectionSlug,
+>(
incomingArgs: Arguments,
-): Promise> => {
+): Promise> => {
let args = incomingArgs
try {
@@ -95,6 +104,7 @@ export const createOperation = async (
payload: { config, email },
},
req,
+ select,
showHiddenFields,
} = args
@@ -235,12 +245,14 @@ export const createOperation = async (
password: data.password as string,
payload: req.payload,
req,
+ select,
})
} else {
doc = await payload.db.create({
collection: collectionConfig.slug,
data: resultWithLocales,
req,
+ select,
})
}
@@ -293,6 +305,7 @@ export const createOperation = async (
locale,
overrideAccess,
req,
+ select,
showHiddenFields,
})
diff --git a/packages/payload/src/collections/operations/delete.ts b/packages/payload/src/collections/operations/delete.ts
index 9eabf12f7a..569a7f635c 100644
--- a/packages/payload/src/collections/operations/delete.ts
+++ b/packages/payload/src/collections/operations/delete.ts
@@ -2,8 +2,14 @@ import httpStatus from 'http-status'
import type { AccessResult } from '../../config/types.js'
import type { CollectionSlug } from '../../index.js'
-import type { PayloadRequest, Where } from '../../types/index.js'
-import type { BeforeOperationHook, Collection, DataFromCollectionSlug } from '../config/types.js'
+import type { PayloadRequest, SelectType, Where } from '../../types/index.js'
+import type {
+ BeforeOperationHook,
+ BulkOperationResult,
+ Collection,
+ DataFromCollectionSlug,
+ SelectFromCollectionSlug,
+} from '../config/types.js'
import executeAccess from '../../auth/executeAccess.js'
import { combineQueries } from '../../database/combineQueries.js'
@@ -26,19 +32,17 @@ export type Arguments = {
overrideAccess?: boolean
overrideLock?: boolean
req: PayloadRequest
+ select?: SelectType
showHiddenFields?: boolean
where: Where
}
-export const deleteOperation = async (
+export const deleteOperation = async <
+ TSlug extends CollectionSlug,
+ TSelect extends SelectFromCollectionSlug,
+>(
incomingArgs: Arguments,
-): Promise<{
- docs: DataFromCollectionSlug[]
- errors: {
- id: DataFromCollectionSlug['id']
- message: string
- }[]
-}> => {
+): Promise> => {
let args = incomingArgs
try {
@@ -75,6 +79,7 @@ export const deleteOperation = async (
payload,
},
req,
+ select,
showHiddenFields,
where,
} = args
@@ -110,6 +115,7 @@ export const deleteOperation = async (
collection: collectionConfig.slug,
locale,
req,
+ select,
where: fullWhere,
})
@@ -198,6 +204,7 @@ export const deleteOperation = async (
locale,
overrideAccess,
req,
+ select,
showHiddenFields,
})
diff --git a/packages/payload/src/collections/operations/deleteByID.ts b/packages/payload/src/collections/operations/deleteByID.ts
index eced5be71b..25be137b7e 100644
--- a/packages/payload/src/collections/operations/deleteByID.ts
+++ b/packages/payload/src/collections/operations/deleteByID.ts
@@ -1,5 +1,9 @@
import type { CollectionSlug } from '../../index.js'
-import type { PayloadRequest } from '../../types/index.js'
+import type {
+ PayloadRequest,
+ SelectType,
+ TransformCollectionWithSelect,
+} from '../../types/index.js'
import type { BeforeOperationHook, Collection, DataFromCollectionSlug } from '../config/types.js'
import executeAccess from '../../auth/executeAccess.js'
@@ -24,12 +28,13 @@ export type Arguments = {
overrideAccess?: boolean
overrideLock?: boolean
req: PayloadRequest
+ select?: SelectType
showHiddenFields?: boolean
}
-export const deleteByIDOperation = async (
+export const deleteByIDOperation = async (
incomingArgs: Arguments,
-): Promise> => {
+): Promise> => {
let args = incomingArgs
try {
@@ -68,6 +73,7 @@ export const deleteByIDOperation = async (
payload,
},
req,
+ select,
showHiddenFields,
} = args
@@ -153,6 +159,7 @@ export const deleteByIDOperation = async (
let result: DataFromCollectionSlug = await req.payload.db.deleteOne({
collection: collectionConfig.slug,
req,
+ select,
where: { id: { equals: id } },
})
@@ -182,6 +189,7 @@ export const deleteByIDOperation = async (
locale,
overrideAccess,
req,
+ select,
showHiddenFields,
})
@@ -237,7 +245,7 @@ export const deleteByIDOperation = async (
await commitTransaction(req)
}
- return result
+ return result as TransformCollectionWithSelect
} catch (error: unknown) {
await killTransaction(args.req)
throw error
diff --git a/packages/payload/src/collections/operations/duplicate.ts b/packages/payload/src/collections/operations/duplicate.ts
index 4fe5e4c588..66891db00b 100644
--- a/packages/payload/src/collections/operations/duplicate.ts
+++ b/packages/payload/src/collections/operations/duplicate.ts
@@ -4,8 +4,16 @@ import httpStatus from 'http-status'
import type { FindOneArgs } from '../../database/types.js'
import type { CollectionSlug } from '../../index.js'
-import type { PayloadRequest } from '../../types/index.js'
-import type { Collection, DataFromCollectionSlug } from '../config/types.js'
+import type {
+ PayloadRequest,
+ SelectType,
+ TransformCollectionWithSelect,
+} from '../../types/index.js'
+import type {
+ Collection,
+ DataFromCollectionSlug,
+ SelectFromCollectionSlug,
+} from '../config/types.js'
import executeAccess from '../../auth/executeAccess.js'
import { hasWhereAccessResult } from '../../auth/types.js'
@@ -21,6 +29,7 @@ import { uploadFiles } from '../../uploads/uploadFiles.js'
import { commitTransaction } from '../../utilities/commitTransaction.js'
import { initTransaction } from '../../utilities/initTransaction.js'
import { killTransaction } from '../../utilities/killTransaction.js'
+import sanitizeInternalFields from '../../utilities/sanitizeInternalFields.js'
import { getLatestCollectionVersion } from '../../versions/getLatestCollectionVersion.js'
import { saveVersion } from '../../versions/saveVersion.js'
import { buildAfterOperation } from './utils.js'
@@ -33,12 +42,16 @@ export type Arguments = {
id: number | string
overrideAccess?: boolean
req: PayloadRequest
+ select?: SelectType
showHiddenFields?: boolean
}
-export const duplicateOperation = async (
+export const duplicateOperation = async <
+ TSlug extends CollectionSlug,
+ TSelect extends SelectFromCollectionSlug,
+>(
incomingArgs: Arguments,
-): Promise> => {
+): Promise> => {
let args = incomingArgs
const operation = 'create'
@@ -70,6 +83,7 @@ export const duplicateOperation = async (
overrideAccess,
req: { fallbackLocale, locale: localeArg, payload },
req,
+ select,
showHiddenFields,
} = args
@@ -254,12 +268,15 @@ export const duplicateOperation = async (
await uploadFiles(payload, filesToUpload, req)
}
- const versionDoc = await payload.db.create({
+ let versionDoc = await payload.db.create({
collection: collectionConfig.slug,
data: result,
req,
+ select,
})
+ versionDoc = sanitizeInternalFields(versionDoc)
+
// /////////////////////////////////////
// Create version
// /////////////////////////////////////
@@ -290,6 +307,7 @@ export const duplicateOperation = async (
locale: localeArg,
overrideAccess,
req,
+ select,
showHiddenFields,
})
diff --git a/packages/payload/src/collections/operations/find.ts b/packages/payload/src/collections/operations/find.ts
index 140d9af3eb..b7735f0754 100644
--- a/packages/payload/src/collections/operations/find.ts
+++ b/packages/payload/src/collections/operations/find.ts
@@ -1,8 +1,18 @@
import type { AccessResult } from '../../config/types.js'
import type { PaginatedDocs } from '../../database/types.js'
import type { CollectionSlug, JoinQuery } from '../../index.js'
-import type { PayloadRequest, Sort, Where } from '../../types/index.js'
-import type { Collection, DataFromCollectionSlug } from '../config/types.js'
+import type {
+ PayloadRequest,
+ SelectType,
+ Sort,
+ TransformCollectionWithSelect,
+ Where,
+} from '../../types/index.js'
+import type {
+ Collection,
+ DataFromCollectionSlug,
+ SelectFromCollectionSlug,
+} from '../config/types.js'
import executeAccess from '../../auth/executeAccess.js'
import { combineQueries } from '../../database/combineQueries.js'
@@ -11,6 +21,7 @@ import { afterRead } from '../../fields/hooks/afterRead/index.js'
import { killTransaction } from '../../utilities/killTransaction.js'
import { buildVersionCollectionFields } from '../../versions/buildCollectionFields.js'
import { appendVersionToQueryKey } from '../../versions/drafts/appendVersionToQueryKey.js'
+import { getQueryDraftsSelect } from '../../versions/drafts/getQueryDraftsSelect.js'
import { getQueryDraftsSort } from '../../versions/drafts/getQueryDraftsSort.js'
import { buildAfterOperation } from './utils.js'
@@ -27,14 +38,18 @@ export type Arguments = {
page?: number
pagination?: boolean
req?: PayloadRequest
+ select?: SelectType
showHiddenFields?: boolean
sort?: Sort
where?: Where
}
-export const findOperation = async (
+export const findOperation = async <
+ TSlug extends CollectionSlug,
+ TSelect extends SelectFromCollectionSlug,
+>(
incomingArgs: Arguments,
-): Promise>> => {
+): Promise>> => {
let args = incomingArgs
try {
@@ -70,6 +85,7 @@ export const findOperation = async (
pagination = true,
req: { fallbackLocale, locale, payload },
req,
+ select,
showHiddenFields,
sort,
where,
@@ -132,6 +148,7 @@ export const findOperation = async (
page: sanitizedPage,
pagination: usePagination,
req,
+ select: getQueryDraftsSelect({ select }),
sort: getQueryDraftsSort({ collectionConfig, sort }),
where: fullWhere,
})
@@ -151,6 +168,7 @@ export const findOperation = async (
page: sanitizedPage,
pagination,
req,
+ select,
sort,
where: fullWhere,
})
@@ -268,6 +286,7 @@ export const findOperation = async (
locale,
overrideAccess,
req,
+ select,
showHiddenFields,
}),
),
@@ -318,7 +337,7 @@ export const findOperation = async (
// Return results
// /////////////////////////////////////
- return result
+ return result as PaginatedDocs>
} catch (error: unknown) {
await killTransaction(args.req)
throw error
diff --git a/packages/payload/src/collections/operations/findByID.ts b/packages/payload/src/collections/operations/findByID.ts
index cfb74de5d1..0f8394d43a 100644
--- a/packages/payload/src/collections/operations/findByID.ts
+++ b/packages/payload/src/collections/operations/findByID.ts
@@ -1,7 +1,16 @@
import type { FindOneArgs } from '../../database/types.js'
import type { CollectionSlug, JoinQuery } from '../../index.js'
-import type { PayloadRequest } from '../../types/index.js'
-import type { Collection, DataFromCollectionSlug } from '../config/types.js'
+import type {
+ ApplyDisableErrors,
+ PayloadRequest,
+ SelectType,
+ TransformCollectionWithSelect,
+} from '../../types/index.js'
+import type {
+ Collection,
+ DataFromCollectionSlug,
+ SelectFromCollectionSlug,
+} from '../config/types.js'
import executeAccess from '../../auth/executeAccess.js'
import { combineQueries } from '../../database/combineQueries.js'
@@ -22,12 +31,17 @@ export type Arguments = {
joins?: JoinQuery
overrideAccess?: boolean
req: PayloadRequest
+ select?: SelectType
showHiddenFields?: boolean
}
-export const findByIDOperation = async (
+export const findByIDOperation = async <
+ TSlug extends CollectionSlug,
+ TDisableErrors extends boolean,
+ TSelect extends SelectFromCollectionSlug,
+>(
incomingArgs: Arguments,
-): Promise> => {
+): Promise, TDisableErrors>> => {
let args = incomingArgs
try {
@@ -60,6 +74,7 @@ export const findByIDOperation = async (
overrideAccess = false,
req: { fallbackLocale, locale, t },
req,
+ select,
showHiddenFields,
} = args
@@ -83,6 +98,7 @@ export const findByIDOperation = async (
req: {
transactionID: req.transactionID,
} as PayloadRequest,
+ select,
where: combineQueries({ id: { equals: id } }, accessResult),
}
@@ -170,6 +186,7 @@ export const findByIDOperation = async (
entityType: 'collection',
overrideAccess,
req,
+ select,
})
}
@@ -206,6 +223,7 @@ export const findByIDOperation = async (
locale,
overrideAccess,
req,
+ select,
showHiddenFields,
})
@@ -241,7 +259,10 @@ export const findByIDOperation = async (
// Return results
// /////////////////////////////////////
- return result
+ return result as ApplyDisableErrors<
+ TransformCollectionWithSelect,
+ TDisableErrors
+ >
} catch (error: unknown) {
await killTransaction(args.req)
throw error
diff --git a/packages/payload/src/collections/operations/findVersionByID.ts b/packages/payload/src/collections/operations/findVersionByID.ts
index b08b795c13..89de00d6c2 100644
--- a/packages/payload/src/collections/operations/findVersionByID.ts
+++ b/packages/payload/src/collections/operations/findVersionByID.ts
@@ -1,6 +1,6 @@
import httpStatus from 'http-status'
-import type { PayloadRequest } from '../../types/index.js'
+import type { PayloadRequest, SelectType } from '../../types/index.js'
import type { TypeWithVersion } from '../../versions/types.js'
import type { Collection, TypeWithID } from '../config/types.js'
@@ -18,6 +18,7 @@ export type Arguments = {
id: number | string
overrideAccess?: boolean
req: PayloadRequest
+ select?: SelectType
showHiddenFields?: boolean
}
@@ -33,6 +34,7 @@ export const findVersionByIDOperation = async (
overrideAccess,
req: { fallbackLocale, locale, payload },
req,
+ select,
showHiddenFields,
} = args
@@ -68,6 +70,7 @@ export const findVersionByIDOperation = async (
locale,
pagination: false,
req,
+ select,
where: fullWhere,
})
@@ -119,6 +122,7 @@ export const findVersionByIDOperation = async (
locale,
overrideAccess,
req,
+ select: typeof select?.version === 'object' ? select.version : undefined,
showHiddenFields,
})
diff --git a/packages/payload/src/collections/operations/findVersions.ts b/packages/payload/src/collections/operations/findVersions.ts
index e0ee8d0b56..beef8e9ca8 100644
--- a/packages/payload/src/collections/operations/findVersions.ts
+++ b/packages/payload/src/collections/operations/findVersions.ts
@@ -1,5 +1,5 @@
import type { PaginatedDocs } from '../../database/types.js'
-import type { PayloadRequest, Sort, Where } from '../../types/index.js'
+import type { PayloadRequest, SelectType, Sort, Where } from '../../types/index.js'
import type { TypeWithVersion } from '../../versions/types.js'
import type { Collection } from '../config/types.js'
@@ -19,6 +19,7 @@ export type Arguments = {
page?: number
pagination?: boolean
req?: PayloadRequest
+ select?: SelectType
showHiddenFields?: boolean
sort?: Sort
where?: Where
@@ -36,6 +37,7 @@ export const findVersionsOperation = async
pagination = true,
req: { fallbackLocale, locale, payload },
req,
+ select,
showHiddenFields,
sort,
where,
@@ -75,6 +77,7 @@ export const findVersionsOperation = async
page: page || 1,
pagination,
req,
+ select,
sort,
where: fullWhere,
})
@@ -127,6 +130,7 @@ export const findVersionsOperation = async
locale,
overrideAccess,
req,
+ select: typeof select?.version === 'object' ? select.version : undefined,
showHiddenFields,
}),
})),
diff --git a/packages/payload/src/collections/operations/local/create.ts b/packages/payload/src/collections/operations/local/create.ts
index 7a63c327c8..978c08e265 100644
--- a/packages/payload/src/collections/operations/local/create.ts
+++ b/packages/payload/src/collections/operations/local/create.ts
@@ -1,14 +1,23 @@
import type { CollectionSlug, Payload, TypedLocale } from '../../../index.js'
-import type { Document, PayloadRequest, RequestContext } from '../../../types/index.js'
+import type {
+ Document,
+ PayloadRequest,
+ RequestContext,
+ SelectType,
+ TransformCollectionWithSelect,
+} from '../../../types/index.js'
import type { File } from '../../../uploads/types.js'
-import type { DataFromCollectionSlug, RequiredDataFromCollectionSlug } from '../../config/types.js'
+import type {
+ RequiredDataFromCollectionSlug,
+ SelectFromCollectionSlug,
+} from '../../config/types.js'
import { APIError } from '../../../errors/index.js'
import { getFileByPath } from '../../../uploads/getFileByPath.js'
import { createLocalReq } from '../../../utilities/createLocalReq.js'
import { createOperation } from '../create.js'
-export type Options = {
+export type Options = {
collection: TSlug
/**
* context, which will then be passed to req.context, which can be read by hooks
@@ -26,15 +35,19 @@ export type Options = {
overrideAccess?: boolean
overwriteExistingFiles?: boolean
req?: PayloadRequest
+ select?: TSelect
showHiddenFields?: boolean
user?: Document
}
// eslint-disable-next-line no-restricted-exports
-export default async function createLocal(
+export default async function createLocal<
+ TSlug extends CollectionSlug,
+ TSelect extends SelectFromCollectionSlug,
+>(
payload: Payload,
- options: Options,
-): Promise> {
+ options: Options,
+): Promise> {
const {
collection: collectionSlug,
data,
@@ -46,6 +59,7 @@ export default async function createLocal(
filePath,
overrideAccess = true,
overwriteExistingFiles = false,
+ select,
showHiddenFields,
} = options
const collection = payload.collections[collectionSlug]
@@ -59,7 +73,7 @@ export default async function createLocal(
const req = await createLocalReq(options, payload)
req.file = file ?? (await getFileByPath(filePath))
- return createOperation({
+ return createOperation({
collection,
data,
depth,
@@ -69,6 +83,7 @@ export default async function createLocal(
overrideAccess,
overwriteExistingFiles,
req,
+ select,
showHiddenFields,
})
}
diff --git a/packages/payload/src/collections/operations/local/delete.ts b/packages/payload/src/collections/operations/local/delete.ts
index 8d9191faad..c57278cb27 100644
--- a/packages/payload/src/collections/operations/local/delete.ts
+++ b/packages/payload/src/collections/operations/local/delete.ts
@@ -1,13 +1,20 @@
import type { CollectionSlug, Payload, TypedLocale } from '../../../index.js'
-import type { Document, PayloadRequest, RequestContext, Where } from '../../../types/index.js'
-import type { BulkOperationResult, DataFromCollectionSlug } from '../../config/types.js'
+import type {
+ Document,
+ PayloadRequest,
+ RequestContext,
+ SelectType,
+ TransformCollectionWithSelect,
+ Where,
+} from '../../../types/index.js'
+import type { BulkOperationResult, SelectFromCollectionSlug } from '../../config/types.js'
import { APIError } from '../../../errors/index.js'
import { createLocalReq } from '../../../utilities/createLocalReq.js'
import { deleteOperation } from '../delete.js'
import { deleteByIDOperation } from '../deleteByID.js'
-export type BaseOptions = {
+export type BaseOptions = {
collection: TSlug
/**
* context, which will then be passed to req.context, which can be read by hooks
@@ -20,38 +27,60 @@ export type BaseOptions = {
overrideAccess?: boolean
overrideLock?: boolean
req?: PayloadRequest
+ select?: TSelect
showHiddenFields?: boolean
user?: Document
}
-export type ByIDOptions = {
+export type ByIDOptions<
+ TSlug extends CollectionSlug,
+ TSelect extends SelectFromCollectionSlug,
+> = {
id: number | string
where?: never
-} & BaseOptions
+} & BaseOptions
-export type ManyOptions = {
+export type ManyOptions<
+ TSlug extends CollectionSlug,
+ TSelect extends SelectFromCollectionSlug,
+> = {
id?: never
where: Where
-} & BaseOptions
+} & BaseOptions
-export type Options = ByIDOptions | ManyOptions
+export type Options<
+ TSlug extends CollectionSlug,
+ TSelect extends SelectFromCollectionSlug,
+> = ByIDOptions | ManyOptions
-async function deleteLocal(
+async function deleteLocal<
+ TSlug extends CollectionSlug,
+ TSelect extends SelectFromCollectionSlug,
+>(
payload: Payload,
- options: ByIDOptions,
-): Promise>
-async function deleteLocal(
+ options: ByIDOptions,
+): Promise>
+async function deleteLocal<
+ TSlug extends CollectionSlug,
+ TSelect extends SelectFromCollectionSlug,
+>(
payload: Payload,
- options: ManyOptions,
-): Promise>
-async function deleteLocal(
+ options: ManyOptions,
+): Promise>
+async function deleteLocal<
+ TSlug extends CollectionSlug,
+ TSelect extends SelectFromCollectionSlug,
+>(
payload: Payload,
- options: Options,
-): Promise | DataFromCollectionSlug>
-async function deleteLocal(
+ options: Options,
+): Promise | TransformCollectionWithSelect>
+async function deleteLocal<
+ TSlug extends CollectionSlug,
+ TSelect extends SelectFromCollectionSlug,
+>(
payload: Payload,
- options: Options,
-): Promise | DataFromCollectionSlug> {
+ options: Options,
+): Promise | TransformCollectionWithSelect> {
const {
id,
collection: collectionSlug,
@@ -59,6 +88,7 @@ async function deleteLocal(
disableTransaction,
overrideAccess = true,
overrideLock,
+ select,
showHiddenFields,
where,
} = options
@@ -79,14 +109,15 @@ async function deleteLocal(
overrideAccess,
overrideLock,
req: await createLocalReq(options, payload),
+ select,
showHiddenFields,
where,
}
if (options.id) {
- return deleteByIDOperation(args)
+ return deleteByIDOperation(args)
}
- return deleteOperation(args)
+ return deleteOperation(args)
}
export default deleteLocal
diff --git a/packages/payload/src/collections/operations/local/duplicate.ts b/packages/payload/src/collections/operations/local/duplicate.ts
index 5e7097f6ab..c79381b08f 100644
--- a/packages/payload/src/collections/operations/local/duplicate.ts
+++ b/packages/payload/src/collections/operations/local/duplicate.ts
@@ -1,13 +1,19 @@
import type { CollectionSlug, TypedLocale } from '../../..//index.js'
import type { Payload } from '../../../index.js'
-import type { Document, PayloadRequest, RequestContext } from '../../../types/index.js'
-import type { DataFromCollectionSlug } from '../../config/types.js'
+import type {
+ Document,
+ PayloadRequest,
+ RequestContext,
+ SelectType,
+ TransformCollectionWithSelect,
+} from '../../../types/index.js'
+import type { DataFromCollectionSlug, SelectFromCollectionSlug } from '../../config/types.js'
import { APIError } from '../../../errors/index.js'
import { createLocalReq } from '../../../utilities/createLocalReq.js'
import { duplicateOperation } from '../duplicate.js'
-export type Options = {
+export type Options = {
collection: TSlug
/**
* context, which will then be passed to req.context, which can be read by hooks
@@ -21,14 +27,18 @@ export type Options = {
locale?: TypedLocale
overrideAccess?: boolean
req?: PayloadRequest
+ select?: TSelect
showHiddenFields?: boolean
user?: Document
}
-export async function duplicate(
+export async function duplicate<
+ TSlug extends CollectionSlug,
+ TSelect extends SelectFromCollectionSlug,
+>(
payload: Payload,
- options: Options,
-): Promise> {
+ options: Options,
+): Promise> {
const {
id,
collection: collectionSlug,
@@ -36,6 +46,7 @@ export async function duplicate(
disableTransaction,
draft,
overrideAccess = true,
+ select,
showHiddenFields,
} = options
const collection = payload.collections[collectionSlug]
@@ -55,7 +66,7 @@ export async function duplicate(
const req = await createLocalReq(options, payload)
- return duplicateOperation({
+ return duplicateOperation({
id,
collection,
depth,
@@ -63,6 +74,7 @@ export async function duplicate(
draft,
overrideAccess,
req,
+ select,
showHiddenFields,
})
}
diff --git a/packages/payload/src/collections/operations/local/find.ts b/packages/payload/src/collections/operations/local/find.ts
index 8f5d545f8e..b6a75fba7f 100644
--- a/packages/payload/src/collections/operations/local/find.ts
+++ b/packages/payload/src/collections/operations/local/find.ts
@@ -1,13 +1,21 @@
import type { PaginatedDocs } from '../../../database/types.js'
import type { CollectionSlug, JoinQuery, Payload, TypedLocale } from '../../../index.js'
-import type { Document, PayloadRequest, RequestContext, Sort, Where } from '../../../types/index.js'
-import type { DataFromCollectionSlug } from '../../config/types.js'
+import type {
+ Document,
+ PayloadRequest,
+ RequestContext,
+ SelectType,
+ Sort,
+ TransformCollectionWithSelect,
+ Where,
+} from '../../../types/index.js'
+import type { SelectFromCollectionSlug } from '../../config/types.js'
import { APIError } from '../../../errors/index.js'
import { createLocalReq } from '../../../utilities/createLocalReq.js'
import { findOperation } from '../find.js'
-export type Options = {
+export type Options = {
collection: TSlug
/**
* context, which will then be passed to req.context, which can be read by hooks
@@ -26,16 +34,20 @@ export type Options = {
page?: number
pagination?: boolean
req?: PayloadRequest
+ select?: TSelect
showHiddenFields?: boolean
sort?: Sort
user?: Document
where?: Where
}
-export async function findLocal(
+export async function findLocal<
+ TSlug extends CollectionSlug,
+ TSelect extends SelectFromCollectionSlug,
+>(
payload: Payload,
- options: Options,
-): Promise>> {
+ options: Options,
+): Promise>> {
const {
collection: collectionSlug,
currentDepth,
@@ -48,6 +60,8 @@ export async function findLocal(
overrideAccess = true,
page,
pagination = true,
+ select,
+ // select,
showHiddenFields,
sort,
where,
@@ -61,7 +75,7 @@ export async function findLocal(
)
}
- return findOperation({
+ return findOperation({
collection,
currentDepth,
depth,
@@ -74,6 +88,7 @@ export async function findLocal(
page,
pagination,
req: await createLocalReq(options, payload),
+ select,
showHiddenFields,
sort,
where,
diff --git a/packages/payload/src/collections/operations/local/findByID.ts b/packages/payload/src/collections/operations/local/findByID.ts
index 2c330bc736..20978c08f7 100644
--- a/packages/payload/src/collections/operations/local/findByID.ts
+++ b/packages/payload/src/collections/operations/local/findByID.ts
@@ -1,12 +1,23 @@
-import type { CollectionSlug, JoinQuery, Payload, TypedLocale } from '../../../index.js'
-import type { Document, PayloadRequest, RequestContext } from '../../../types/index.js'
-import type { DataFromCollectionSlug } from '../../config/types.js'
+/* eslint-disable no-restricted-exports */
+import type { CollectionSlug, JoinQuery, Payload, SelectType, TypedLocale } from '../../../index.js'
+import type {
+ ApplyDisableErrors,
+ Document,
+ PayloadRequest,
+ RequestContext,
+ TransformCollectionWithSelect,
+} from '../../../types/index.js'
+import type { SelectFromCollectionSlug } from '../../config/types.js'
import { APIError } from '../../../errors/index.js'
import { createLocalReq } from '../../../utilities/createLocalReq.js'
import { findByIDOperation } from '../findByID.js'
-export type Options = {
+export type Options<
+ TSlug extends CollectionSlug,
+ TDisableErrors extends boolean,
+ TSelect extends SelectType,
+> = {
collection: TSlug
/**
* context, which will then be passed to req.context, which can be read by hooks
@@ -14,7 +25,7 @@ export type Options = {
context?: RequestContext
currentDepth?: number
depth?: number
- disableErrors?: boolean
+ disableErrors?: TDisableErrors
draft?: boolean
fallbackLocale?: TypedLocale
id: number | string
@@ -23,18 +34,19 @@ export type Options = {
locale?: 'all' | TypedLocale
overrideAccess?: boolean
req?: PayloadRequest
+ select?: TSelect
showHiddenFields?: boolean
user?: Document
}
-export default async function findByIDLocal(
+export default async function findByIDLocal<
+ TSlug extends CollectionSlug,
+ TDisableErrors extends boolean,
+ TSelect extends SelectFromCollectionSlug,
+>(
payload: Payload,
- options: TOptions,
-): Promise<
- TOptions['disableErrors'] extends true
- ? DataFromCollectionSlug | null
- : DataFromCollectionSlug
-> {
+ options: Options,
+): Promise, TDisableErrors>> {
const {
id,
collection: collectionSlug,
@@ -45,6 +57,7 @@ export default async function findByIDLocal(
includeLockStatus,
joins,
overrideAccess = true,
+ select,
showHiddenFields,
} = options
@@ -56,7 +69,7 @@ export default async function findByIDLocal(
)
}
- return findByIDOperation({
+ return findByIDOperation({
id,
collection,
currentDepth,
@@ -67,6 +80,7 @@ export default async function findByIDLocal(
joins,
overrideAccess,
req: await createLocalReq(options, payload),
+ select,
showHiddenFields,
})
}
diff --git a/packages/payload/src/collections/operations/local/findVersionByID.ts b/packages/payload/src/collections/operations/local/findVersionByID.ts
index 53de77b650..e672d2a578 100644
--- a/packages/payload/src/collections/operations/local/findVersionByID.ts
+++ b/packages/payload/src/collections/operations/local/findVersionByID.ts
@@ -1,5 +1,5 @@
import type { CollectionSlug, Payload, TypedLocale } from '../../../index.js'
-import type { Document, PayloadRequest, RequestContext } from '../../../types/index.js'
+import type { Document, PayloadRequest, RequestContext, SelectType } from '../../../types/index.js'
import type { TypeWithVersion } from '../../../versions/types.js'
import type { DataFromCollectionSlug } from '../../config/types.js'
@@ -21,6 +21,7 @@ export type Options = {
locale?: 'all' | TypedLocale
overrideAccess?: boolean
req?: PayloadRequest
+ select?: SelectType
showHiddenFields?: boolean
user?: Document
}
@@ -35,6 +36,7 @@ export default async function findVersionByIDLocal
depth,
disableErrors = false,
overrideAccess = true,
+ select,
showHiddenFields,
} = options
@@ -55,6 +57,7 @@ export default async function findVersionByIDLocal
disableErrors,
overrideAccess,
req: await createLocalReq(options, payload),
+ select,
showHiddenFields,
})
}
diff --git a/packages/payload/src/collections/operations/local/findVersions.ts b/packages/payload/src/collections/operations/local/findVersions.ts
index f5cbbe5a96..e6ab0048d5 100644
--- a/packages/payload/src/collections/operations/local/findVersions.ts
+++ b/packages/payload/src/collections/operations/local/findVersions.ts
@@ -1,6 +1,13 @@
import type { PaginatedDocs } from '../../../database/types.js'
import type { CollectionSlug, Payload, TypedLocale } from '../../../index.js'
-import type { Document, PayloadRequest, RequestContext, Sort, Where } from '../../../types/index.js'
+import type {
+ Document,
+ PayloadRequest,
+ RequestContext,
+ SelectType,
+ Sort,
+ Where,
+} from '../../../types/index.js'
import type { TypeWithVersion } from '../../../versions/types.js'
import type { DataFromCollectionSlug } from '../../config/types.js'
@@ -22,6 +29,7 @@ export type Options = {
overrideAccess?: boolean
page?: number
req?: PayloadRequest
+ select?: SelectType
showHiddenFields?: boolean
sort?: Sort
user?: Document
@@ -38,6 +46,7 @@ export default async function findVersionsLocal(
limit,
overrideAccess = true,
page,
+ select,
showHiddenFields,
sort,
where,
@@ -58,6 +67,7 @@ export default async function findVersionsLocal(
overrideAccess,
page,
req: await createLocalReq(options, payload),
+ select,
showHiddenFields,
sort,
where,
diff --git a/packages/payload/src/collections/operations/local/index.ts b/packages/payload/src/collections/operations/local/index.ts
index 24081a222d..c8e878a249 100644
--- a/packages/payload/src/collections/operations/local/index.ts
+++ b/packages/payload/src/collections/operations/local/index.ts
@@ -1,3 +1,4 @@
+/* eslint-disable no-restricted-exports */
import auth from '../../../auth/operations/local/index.js'
import count from './count.js'
import create from './create.js'
diff --git a/packages/payload/src/collections/operations/local/restoreVersion.ts b/packages/payload/src/collections/operations/local/restoreVersion.ts
index fc2b6bfe0a..6e671dbf89 100644
--- a/packages/payload/src/collections/operations/local/restoreVersion.ts
+++ b/packages/payload/src/collections/operations/local/restoreVersion.ts
@@ -1,5 +1,5 @@
import type { CollectionSlug, Payload, TypedLocale } from '../../../index.js'
-import type { Document, PayloadRequest, RequestContext } from '../../../types/index.js'
+import type { Document, PayloadRequest, RequestContext, SelectType } from '../../../types/index.js'
import type { DataFromCollectionSlug } from '../../config/types.js'
import { APIError } from '../../../errors/index.js'
@@ -19,6 +19,7 @@ export type Options = {
locale?: TypedLocale
overrideAccess?: boolean
req?: PayloadRequest
+ select?: SelectType
showHiddenFields?: boolean
user?: Document
}
@@ -27,7 +28,14 @@ export default async function restoreVersionLocal(
payload: Payload,
options: Options,
): Promise> {
- const { id, collection: collectionSlug, depth, overrideAccess = true, showHiddenFields } = options
+ const {
+ id,
+ collection: collectionSlug,
+ depth,
+ overrideAccess = true,
+ select,
+ showHiddenFields,
+ } = options
const collection = payload.collections[collectionSlug]
@@ -46,6 +54,7 @@ export default async function restoreVersionLocal(
overrideAccess,
payload,
req: await createLocalReq(options, payload),
+ select,
showHiddenFields,
}
diff --git a/packages/payload/src/collections/operations/local/update.ts b/packages/payload/src/collections/operations/local/update.ts
index 502ab87fdb..2f13f33593 100644
--- a/packages/payload/src/collections/operations/local/update.ts
+++ b/packages/payload/src/collections/operations/local/update.ts
@@ -1,12 +1,19 @@
import type { DeepPartial } from 'ts-essentials'
import type { CollectionSlug, Payload, TypedLocale } from '../../../index.js'
-import type { Document, PayloadRequest, RequestContext, Where } from '../../../types/index.js'
+import type {
+ Document,
+ PayloadRequest,
+ RequestContext,
+ SelectType,
+ TransformCollectionWithSelect,
+ Where,
+} from '../../../types/index.js'
import type { File } from '../../../uploads/types.js'
import type {
BulkOperationResult,
- DataFromCollectionSlug,
RequiredDataFromCollectionSlug,
+ SelectFromCollectionSlug,
} from '../../config/types.js'
import { APIError } from '../../../errors/index.js'
@@ -15,7 +22,7 @@ import { createLocalReq } from '../../../utilities/createLocalReq.js'
import { updateOperation } from '../update.js'
import { updateByIDOperation } from '../updateByID.js'
-export type BaseOptions = {
+export type BaseOptions = {
autosave?: boolean
collection: TSlug
/**
@@ -35,40 +42,62 @@ export type BaseOptions = {
overwriteExistingFiles?: boolean
publishSpecificLocale?: string
req?: PayloadRequest
+ select?: TSelect
showHiddenFields?: boolean
user?: Document
}
-export type ByIDOptions = {
+export type ByIDOptions<
+ TSlug extends CollectionSlug,
+ TSelect extends SelectFromCollectionSlug,
+> = {
id: number | string
limit?: never
where?: never
-} & BaseOptions
+} & BaseOptions
-export type ManyOptions = {
+export type ManyOptions<
+ TSlug extends CollectionSlug,
+ TSelect extends SelectFromCollectionSlug,
+> = {
id?: never
limit?: number
where: Where
-} & BaseOptions
+} & BaseOptions
-export type Options = ByIDOptions | ManyOptions
+export type Options<
+ TSlug extends CollectionSlug,
+ TSelect extends SelectFromCollectionSlug,
+> = ByIDOptions | ManyOptions
-async function updateLocal(
+async function updateLocal<
+ TSlug extends CollectionSlug,
+ TSelect extends SelectFromCollectionSlug,
+>(
payload: Payload,
- options: ByIDOptions,
-): Promise>
-async function updateLocal(
+ options: ByIDOptions,
+): Promise