feat: select fields (#8550)

Adds `select` which is used to specify the field projection for local
and rest API calls. This is available as an optimization to reduce the
payload's of requests and make the database queries more efficient.

Includes:
- [x] generate types for the `select` property
- [x] infer the return type by `select` with 2 modes - include (`field:
true`) and exclude (`field: false`)
- [x] lots of integration tests, including deep fields / localization
etc
- [x] implement the property in db adapters
- [x] implement the property in the local api for most operations
- [x] implement the property in the rest api 
- [x] docs

---------

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
This commit is contained in:
Sasha
2024-10-29 23:47:18 +02:00
committed by GitHub
parent 6cdf141380
commit dae832c288
116 changed files with 5491 additions and 371 deletions

View File

@@ -78,11 +78,12 @@ 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. |
| `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). |

99
docs/queries/select.mdx Normal file
View File

@@ -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
}
```
<Banner type="warning">
<strong>Important:</strong>
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`.
</Banner>
## 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...
}
<Banner type="info">
<strong>Reminder:</strong>
This is the same for [Globals](../configuration/globals) using the `/api/globals` endpoint.
</Banner>

View File

@@ -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

View File

@@ -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))

View File

@@ -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 = {

View File

@@ -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({

View File

@@ -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,
}

View File

@@ -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<string, unknown>).projection = projection
doc = await Model.findOne(query, {}, options)
}

View File

@@ -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,
}

View File

@@ -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,
})

View File

@@ -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)

View File

@@ -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<T extends TypeWithID>(
global: globalSlug,
locale,
req = {} as PayloadRequest,
select,
versionData,
where,
}: UpdateGlobalVersionArgs<T>,
) {
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<T extends TypeWithID>(
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)

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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 })
}

View File

@@ -13,6 +13,7 @@ type BuildJoinAggregationArgs = {
// the number of docs to get at the top collection level
limit?: number
locale: string
projection?: Record<string, true>
// 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<PipelineStage[] | undefined> => {
@@ -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
}

View File

@@ -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<string, true>
withinLocalizedField: boolean
}) => {
const { config } = adapter.payload
if (withinLocalizedField && config.localization) {
for (const locale of config.localization.localeCodes) {
const localeDatabaseSchemaPath = databaseSchemaPath.replace('<locale>', 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<string, true>
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}<locale>.`
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<string, true> | undefined => {
if (!select) {
return
}
const projection: Record<string, true> = {
_id: true,
}
traverseFields({
adapter,
fields,
projection,
// Clone to safely mutate it later
select: deepCopyObjectSimple(select),
selectMode: getSelectMode(select),
})
return projection
}

View File

@@ -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,
})

View File

@@ -16,6 +16,7 @@ export async function createGlobalVersion<T extends TypeWithID>(
globalSlug,
publishedLocale,
req = {} as PayloadRequest,
select,
snapshot,
updatedAt,
versionData,
@@ -41,6 +42,7 @@ export async function createGlobalVersion<T extends TypeWithID>(
fields: buildVersionGlobalFields(this.payload.config, global),
operation: 'create',
req,
select,
tableName,
})

View File

@@ -17,6 +17,7 @@ export async function createVersion<T extends TypeWithID>(
parent,
publishedLocale,
req = {} as PayloadRequest,
select,
snapshot,
updatedAt,
versionData,
@@ -51,6 +52,7 @@ export async function createVersion<T extends TypeWithID>(
fields: buildVersionCollectionFields(this.payload.config, collection),
operation: 'create',
req,
select,
tableName,
})

View File

@@ -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,
})

View File

@@ -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,

View File

@@ -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<string, unknown> => {
@@ -42,8 +46,16 @@ export const buildFindManyArgs = ({
with: {},
}
if (select) {
result.columns = {
id: true,
}
}
const _locales: Result = {
columns: {
columns: select
? { _locale: true }
: {
id: false,
_parentID: false,
},
@@ -51,38 +63,12 @@ export const buildFindManyArgs = ({
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({
@@ -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
}

View File

@@ -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,
})

View File

@@ -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<string, unknown>
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,8 +145,25 @@ 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: {
columns:
typeof arraySelect === 'object'
? {
id: true,
_order: true,
}
: {
_parentID: false,
},
orderBy: ({ _order }, { asc }) => [asc(_order)],
@@ -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: {
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,13 +258,53 @@ 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: {
columns:
typeof blockSelect === 'object'
? {
id: true,
_order: true,
_path: true,
}
: {
_parentID: false,
},
orderBy: ({ _order }, { asc }) => [asc(_order)],
@@ -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
}
}

View File

@@ -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,
})

View File

@@ -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,

View File

@@ -8,7 +8,7 @@ import { findMany } from './find/findMany.js'
export async function findOne<T extends TypeWithID>(
this: DrizzleAdapter,
{ collection, joins, locale, req = {} as PayloadRequest, where }: FindOneArgs,
{ collection, joins, locale, req = {} as PayloadRequest, select, where }: FindOneArgs,
): Promise<T> {
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
@@ -23,6 +23,7 @@ export async function findOne<T extends TypeWithID>(
page: 1,
pagination: false,
req,
select,
sort: undefined,
tableName,
where,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,
})

View File

@@ -8,7 +8,7 @@ import { upsertRow } from './upsertRow/index.js'
export async function updateGlobal<T extends Record<string, unknown>>(
this: DrizzleAdapter,
{ slug, data, req = {} as PayloadRequest }: UpdateGlobalArgs,
{ slug, data, req = {} as PayloadRequest, select }: UpdateGlobalArgs,
): Promise<T> {
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<T extends Record<string, unknown>>(
db,
fields: globalConfig.fields,
req,
select,
tableName,
})

View File

@@ -21,6 +21,7 @@ export async function updateGlobalVersion<T extends TypeWithID>(
global,
locale,
req = {} as PayloadRequest,
select,
versionData,
where: whereArg,
}: UpdateGlobalVersionArgs<T>,
@@ -53,6 +54,7 @@ export async function updateGlobalVersion<T extends TypeWithID>(
fields,
operation: 'update',
req,
select,
tableName,
where,
})

View File

@@ -21,6 +21,7 @@ export async function updateVersion<T extends TypeWithID>(
collection,
locale,
req = {} as PayloadRequest,
select,
versionData,
where: whereArg,
}: UpdateVersionArgs<T>,
@@ -50,6 +51,7 @@ export async function updateVersion<T extends TypeWithID>(
fields,
operation: 'update',
req,
select,
tableName,
where,
})

View File

@@ -24,6 +24,7 @@ export const upsertRow = async <T extends Record<string, unknown> | TypeWithID>(
operation,
path = '',
req,
select,
tableName,
upsertTarget,
where,
@@ -415,6 +416,7 @@ export const upsertRow = async <T extends Record<string, unknown> | TypeWithID>(
depth: 0,
fields,
joinQuery,
select,
tableName,
})

View File

@@ -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<unknown>
} & BaseArgs

View File

@@ -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<TSlug extends GlobalSlug>(
req: isolateObjectProperty(context.req, 'transactionID'),
}
const result = await updateOperationGlobal<TSlug>(options)
const result = await updateOperationGlobal<TSlug, SelectType>(options)
return result
}
}

View File

@@ -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(

View File

@@ -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<string, unknown>
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,
})

View File

@@ -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({

View File

@@ -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', {

View File

@@ -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<string, unknown>
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,
})

View File

@@ -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, {

View File

@@ -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, {

View File

@@ -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<string, unknown>
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,
})

View File

@@ -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<string, unknown>
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,
})

View File

@@ -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')

View File

@@ -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, {

View File

@@ -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, {

View File

@@ -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<string, unknown>
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,
})

View File

@@ -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')

View File

@@ -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
}

View File

@@ -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 <TSlug extends CollectionSlug>(
// Register first user
// /////////////////////////////////////
const result = await payload.create<TSlug>({
const result = await payload.create<TSlug, SelectType>({
collection: slug as TSlug,
data,
overrideAccess: true,

View File

@@ -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<Record<string, unknown>> => {
const loginWithUsername = collection?.auth?.loginWithUsername
@@ -90,5 +92,6 @@ export const registerLocalStrategy = async ({
salt,
},
req,
select,
})
}

View File

@@ -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}`

View File

@@ -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<TSlug extends CollectionSlug> = TypedCollection[TSlug]
export type SelectFromCollectionSlug<TSlug extends CollectionSlug> = TypedCollectionSelect[TSlug]
export type AuthOperationsFromCollectionSlug<TSlug extends CollectionSlug> =
TypedAuthOperations[TSlug]
@@ -523,8 +533,8 @@ export type Collection = {
}
}
export type BulkOperationResult<TSlug extends CollectionSlug> = {
docs: DataFromCollectionSlug<TSlug>[]
export type BulkOperationResult<TSlug extends CollectionSlug, TSelect extends SelectType> = {
docs: TransformCollectionWithSelect<TSlug, TSelect>[]
errors: {
id: DataFromCollectionSlug<TSlug>['id']
message: string

View File

@@ -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<TSlug extends CollectionSlug> = {
overrideAccess?: boolean
overwriteExistingFiles?: boolean
req: PayloadRequest
select?: SelectType
showHiddenFields?: boolean
}
export const createOperation = async <TSlug extends CollectionSlug>(
export const createOperation = async <
TSlug extends CollectionSlug,
TSelect extends SelectFromCollectionSlug<TSlug>,
>(
incomingArgs: Arguments<TSlug>,
): Promise<DataFromCollectionSlug<TSlug>> => {
): Promise<TransformCollectionWithSelect<TSlug, TSelect>> => {
let args = incomingArgs
try {
@@ -95,6 +104,7 @@ export const createOperation = async <TSlug extends CollectionSlug>(
payload: { config, email },
},
req,
select,
showHiddenFields,
} = args
@@ -235,12 +245,14 @@ export const createOperation = async <TSlug extends CollectionSlug>(
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 <TSlug extends CollectionSlug>(
locale,
overrideAccess,
req,
select,
showHiddenFields,
})

View File

@@ -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 <TSlug extends CollectionSlug>(
export const deleteOperation = async <
TSlug extends CollectionSlug,
TSelect extends SelectFromCollectionSlug<TSlug>,
>(
incomingArgs: Arguments,
): Promise<{
docs: DataFromCollectionSlug<TSlug>[]
errors: {
id: DataFromCollectionSlug<TSlug>['id']
message: string
}[]
}> => {
): Promise<BulkOperationResult<TSlug, TSelect>> => {
let args = incomingArgs
try {
@@ -75,6 +79,7 @@ export const deleteOperation = async <TSlug extends CollectionSlug>(
payload,
},
req,
select,
showHiddenFields,
where,
} = args
@@ -110,6 +115,7 @@ export const deleteOperation = async <TSlug extends CollectionSlug>(
collection: collectionConfig.slug,
locale,
req,
select,
where: fullWhere,
})
@@ -198,6 +204,7 @@ export const deleteOperation = async <TSlug extends CollectionSlug>(
locale,
overrideAccess,
req,
select,
showHiddenFields,
})

View File

@@ -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 <TSlug extends CollectionSlug>(
export const deleteByIDOperation = async <TSlug extends CollectionSlug, TSelect extends SelectType>(
incomingArgs: Arguments,
): Promise<DataFromCollectionSlug<TSlug>> => {
): Promise<TransformCollectionWithSelect<TSlug, TSelect>> => {
let args = incomingArgs
try {
@@ -68,6 +73,7 @@ export const deleteByIDOperation = async <TSlug extends CollectionSlug>(
payload,
},
req,
select,
showHiddenFields,
} = args
@@ -153,6 +159,7 @@ export const deleteByIDOperation = async <TSlug extends CollectionSlug>(
let result: DataFromCollectionSlug<TSlug> = await req.payload.db.deleteOne({
collection: collectionConfig.slug,
req,
select,
where: { id: { equals: id } },
})
@@ -182,6 +189,7 @@ export const deleteByIDOperation = async <TSlug extends CollectionSlug>(
locale,
overrideAccess,
req,
select,
showHiddenFields,
})
@@ -237,7 +245,7 @@ export const deleteByIDOperation = async <TSlug extends CollectionSlug>(
await commitTransaction(req)
}
return result
return result as TransformCollectionWithSelect<TSlug, TSelect>
} catch (error: unknown) {
await killTransaction(args.req)
throw error

View File

@@ -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 <TSlug extends CollectionSlug>(
export const duplicateOperation = async <
TSlug extends CollectionSlug,
TSelect extends SelectFromCollectionSlug<TSlug>,
>(
incomingArgs: Arguments,
): Promise<DataFromCollectionSlug<TSlug>> => {
): Promise<TransformCollectionWithSelect<TSlug, TSelect>> => {
let args = incomingArgs
const operation = 'create'
@@ -70,6 +83,7 @@ export const duplicateOperation = async <TSlug extends CollectionSlug>(
overrideAccess,
req: { fallbackLocale, locale: localeArg, payload },
req,
select,
showHiddenFields,
} = args
@@ -254,12 +268,15 @@ export const duplicateOperation = async <TSlug extends CollectionSlug>(
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 <TSlug extends CollectionSlug>(
locale: localeArg,
overrideAccess,
req,
select,
showHiddenFields,
})

View File

@@ -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 <TSlug extends CollectionSlug>(
export const findOperation = async <
TSlug extends CollectionSlug,
TSelect extends SelectFromCollectionSlug<TSlug>,
>(
incomingArgs: Arguments,
): Promise<PaginatedDocs<DataFromCollectionSlug<TSlug>>> => {
): Promise<PaginatedDocs<TransformCollectionWithSelect<TSlug, TSelect>>> => {
let args = incomingArgs
try {
@@ -70,6 +85,7 @@ export const findOperation = async <TSlug extends CollectionSlug>(
pagination = true,
req: { fallbackLocale, locale, payload },
req,
select,
showHiddenFields,
sort,
where,
@@ -132,6 +148,7 @@ export const findOperation = async <TSlug extends CollectionSlug>(
page: sanitizedPage,
pagination: usePagination,
req,
select: getQueryDraftsSelect({ select }),
sort: getQueryDraftsSort({ collectionConfig, sort }),
where: fullWhere,
})
@@ -151,6 +168,7 @@ export const findOperation = async <TSlug extends CollectionSlug>(
page: sanitizedPage,
pagination,
req,
select,
sort,
where: fullWhere,
})
@@ -268,6 +286,7 @@ export const findOperation = async <TSlug extends CollectionSlug>(
locale,
overrideAccess,
req,
select,
showHiddenFields,
}),
),
@@ -318,7 +337,7 @@ export const findOperation = async <TSlug extends CollectionSlug>(
// Return results
// /////////////////////////////////////
return result
return result as PaginatedDocs<TransformCollectionWithSelect<TSlug, TSelect>>
} catch (error: unknown) {
await killTransaction(args.req)
throw error

View File

@@ -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 <TSlug extends CollectionSlug>(
export const findByIDOperation = async <
TSlug extends CollectionSlug,
TDisableErrors extends boolean,
TSelect extends SelectFromCollectionSlug<TSlug>,
>(
incomingArgs: Arguments,
): Promise<DataFromCollectionSlug<TSlug>> => {
): Promise<ApplyDisableErrors<TransformCollectionWithSelect<TSlug, TSelect>, TDisableErrors>> => {
let args = incomingArgs
try {
@@ -60,6 +74,7 @@ export const findByIDOperation = async <TSlug extends CollectionSlug>(
overrideAccess = false,
req: { fallbackLocale, locale, t },
req,
select,
showHiddenFields,
} = args
@@ -83,6 +98,7 @@ export const findByIDOperation = async <TSlug extends CollectionSlug>(
req: {
transactionID: req.transactionID,
} as PayloadRequest,
select,
where: combineQueries({ id: { equals: id } }, accessResult),
}
@@ -170,6 +186,7 @@ export const findByIDOperation = async <TSlug extends CollectionSlug>(
entityType: 'collection',
overrideAccess,
req,
select,
})
}
@@ -206,6 +223,7 @@ export const findByIDOperation = async <TSlug extends CollectionSlug>(
locale,
overrideAccess,
req,
select,
showHiddenFields,
})
@@ -241,7 +259,10 @@ export const findByIDOperation = async <TSlug extends CollectionSlug>(
// Return results
// /////////////////////////////////////
return result
return result as ApplyDisableErrors<
TransformCollectionWithSelect<TSlug, TSelect>,
TDisableErrors
>
} catch (error: unknown) {
await killTransaction(args.req)
throw error

View File

@@ -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 <TData extends TypeWithID = any>(
overrideAccess,
req: { fallbackLocale, locale, payload },
req,
select,
showHiddenFields,
} = args
@@ -68,6 +70,7 @@ export const findVersionByIDOperation = async <TData extends TypeWithID = any>(
locale,
pagination: false,
req,
select,
where: fullWhere,
})
@@ -119,6 +122,7 @@ export const findVersionByIDOperation = async <TData extends TypeWithID = any>(
locale,
overrideAccess,
req,
select: typeof select?.version === 'object' ? select.version : undefined,
showHiddenFields,
})

View File

@@ -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 <TData extends TypeWithVersion<TData>
pagination = true,
req: { fallbackLocale, locale, payload },
req,
select,
showHiddenFields,
sort,
where,
@@ -75,6 +77,7 @@ export const findVersionsOperation = async <TData extends TypeWithVersion<TData>
page: page || 1,
pagination,
req,
select,
sort,
where: fullWhere,
})
@@ -127,6 +130,7 @@ export const findVersionsOperation = async <TData extends TypeWithVersion<TData>
locale,
overrideAccess,
req,
select: typeof select?.version === 'object' ? select.version : undefined,
showHiddenFields,
}),
})),

View File

@@ -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<TSlug extends CollectionSlug> = {
export type Options<TSlug extends CollectionSlug, TSelect extends SelectType> = {
collection: TSlug
/**
* context, which will then be passed to req.context, which can be read by hooks
@@ -26,15 +35,19 @@ export type Options<TSlug extends CollectionSlug> = {
overrideAccess?: boolean
overwriteExistingFiles?: boolean
req?: PayloadRequest
select?: TSelect
showHiddenFields?: boolean
user?: Document
}
// eslint-disable-next-line no-restricted-exports
export default async function createLocal<TSlug extends CollectionSlug>(
export default async function createLocal<
TSlug extends CollectionSlug,
TSelect extends SelectFromCollectionSlug<TSlug>,
>(
payload: Payload,
options: Options<TSlug>,
): Promise<DataFromCollectionSlug<TSlug>> {
options: Options<TSlug, TSelect>,
): Promise<TransformCollectionWithSelect<TSlug, TSelect>> {
const {
collection: collectionSlug,
data,
@@ -46,6 +59,7 @@ export default async function createLocal<TSlug extends CollectionSlug>(
filePath,
overrideAccess = true,
overwriteExistingFiles = false,
select,
showHiddenFields,
} = options
const collection = payload.collections[collectionSlug]
@@ -59,7 +73,7 @@ export default async function createLocal<TSlug extends CollectionSlug>(
const req = await createLocalReq(options, payload)
req.file = file ?? (await getFileByPath(filePath))
return createOperation<TSlug>({
return createOperation<TSlug, TSelect>({
collection,
data,
depth,
@@ -69,6 +83,7 @@ export default async function createLocal<TSlug extends CollectionSlug>(
overrideAccess,
overwriteExistingFiles,
req,
select,
showHiddenFields,
})
}

View File

@@ -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<TSlug extends CollectionSlug> = {
export type BaseOptions<TSlug extends CollectionSlug, TSelect extends SelectType> = {
collection: TSlug
/**
* context, which will then be passed to req.context, which can be read by hooks
@@ -20,38 +27,60 @@ export type BaseOptions<TSlug extends CollectionSlug> = {
overrideAccess?: boolean
overrideLock?: boolean
req?: PayloadRequest
select?: TSelect
showHiddenFields?: boolean
user?: Document
}
export type ByIDOptions<TSlug extends CollectionSlug> = {
export type ByIDOptions<
TSlug extends CollectionSlug,
TSelect extends SelectFromCollectionSlug<TSlug>,
> = {
id: number | string
where?: never
} & BaseOptions<TSlug>
} & BaseOptions<TSlug, TSelect>
export type ManyOptions<TSlug extends CollectionSlug> = {
export type ManyOptions<
TSlug extends CollectionSlug,
TSelect extends SelectFromCollectionSlug<TSlug>,
> = {
id?: never
where: Where
} & BaseOptions<TSlug>
} & BaseOptions<TSlug, TSelect>
export type Options<TSlug extends CollectionSlug> = ByIDOptions<TSlug> | ManyOptions<TSlug>
export type Options<
TSlug extends CollectionSlug,
TSelect extends SelectFromCollectionSlug<TSlug>,
> = ByIDOptions<TSlug, TSelect> | ManyOptions<TSlug, TSelect>
async function deleteLocal<TSlug extends CollectionSlug>(
async function deleteLocal<
TSlug extends CollectionSlug,
TSelect extends SelectFromCollectionSlug<TSlug>,
>(
payload: Payload,
options: ByIDOptions<TSlug>,
): Promise<DataFromCollectionSlug<TSlug>>
async function deleteLocal<TSlug extends CollectionSlug>(
options: ByIDOptions<TSlug, TSelect>,
): Promise<TransformCollectionWithSelect<TSlug, TSelect>>
async function deleteLocal<
TSlug extends CollectionSlug,
TSelect extends SelectFromCollectionSlug<TSlug>,
>(
payload: Payload,
options: ManyOptions<TSlug>,
): Promise<BulkOperationResult<TSlug>>
async function deleteLocal<TSlug extends CollectionSlug>(
options: ManyOptions<TSlug, TSelect>,
): Promise<BulkOperationResult<TSlug, TSelect>>
async function deleteLocal<
TSlug extends CollectionSlug,
TSelect extends SelectFromCollectionSlug<TSlug>,
>(
payload: Payload,
options: Options<TSlug>,
): Promise<BulkOperationResult<TSlug> | DataFromCollectionSlug<TSlug>>
async function deleteLocal<TSlug extends CollectionSlug>(
options: Options<TSlug, TSelect>,
): Promise<BulkOperationResult<TSlug, TSelect> | TransformCollectionWithSelect<TSlug, TSelect>>
async function deleteLocal<
TSlug extends CollectionSlug,
TSelect extends SelectFromCollectionSlug<TSlug>,
>(
payload: Payload,
options: Options<TSlug>,
): Promise<BulkOperationResult<TSlug> | DataFromCollectionSlug<TSlug>> {
options: Options<TSlug, TSelect>,
): Promise<BulkOperationResult<TSlug, TSelect> | TransformCollectionWithSelect<TSlug, TSelect>> {
const {
id,
collection: collectionSlug,
@@ -59,6 +88,7 @@ async function deleteLocal<TSlug extends CollectionSlug>(
disableTransaction,
overrideAccess = true,
overrideLock,
select,
showHiddenFields,
where,
} = options
@@ -79,14 +109,15 @@ async function deleteLocal<TSlug extends CollectionSlug>(
overrideAccess,
overrideLock,
req: await createLocalReq(options, payload),
select,
showHiddenFields,
where,
}
if (options.id) {
return deleteByIDOperation<TSlug>(args)
return deleteByIDOperation<TSlug, TSelect>(args)
}
return deleteOperation<TSlug>(args)
return deleteOperation<TSlug, TSelect>(args)
}
export default deleteLocal

View File

@@ -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<TSlug extends CollectionSlug> = {
export type Options<TSlug extends CollectionSlug, TSelect extends SelectType> = {
collection: TSlug
/**
* context, which will then be passed to req.context, which can be read by hooks
@@ -21,14 +27,18 @@ export type Options<TSlug extends CollectionSlug> = {
locale?: TypedLocale
overrideAccess?: boolean
req?: PayloadRequest
select?: TSelect
showHiddenFields?: boolean
user?: Document
}
export async function duplicate<TSlug extends CollectionSlug>(
export async function duplicate<
TSlug extends CollectionSlug,
TSelect extends SelectFromCollectionSlug<TSlug>,
>(
payload: Payload,
options: Options<TSlug>,
): Promise<DataFromCollectionSlug<TSlug>> {
options: Options<TSlug, TSelect>,
): Promise<TransformCollectionWithSelect<TSlug, TSelect>> {
const {
id,
collection: collectionSlug,
@@ -36,6 +46,7 @@ export async function duplicate<TSlug extends CollectionSlug>(
disableTransaction,
draft,
overrideAccess = true,
select,
showHiddenFields,
} = options
const collection = payload.collections[collectionSlug]
@@ -55,7 +66,7 @@ export async function duplicate<TSlug extends CollectionSlug>(
const req = await createLocalReq(options, payload)
return duplicateOperation<TSlug>({
return duplicateOperation<TSlug, TSelect>({
id,
collection,
depth,
@@ -63,6 +74,7 @@ export async function duplicate<TSlug extends CollectionSlug>(
draft,
overrideAccess,
req,
select,
showHiddenFields,
})
}

View File

@@ -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<TSlug extends CollectionSlug> = {
export type Options<TSlug extends CollectionSlug, TSelect extends SelectType> = {
collection: TSlug
/**
* context, which will then be passed to req.context, which can be read by hooks
@@ -26,16 +34,20 @@ export type Options<TSlug extends CollectionSlug> = {
page?: number
pagination?: boolean
req?: PayloadRequest
select?: TSelect
showHiddenFields?: boolean
sort?: Sort
user?: Document
where?: Where
}
export async function findLocal<TSlug extends CollectionSlug>(
export async function findLocal<
TSlug extends CollectionSlug,
TSelect extends SelectFromCollectionSlug<TSlug>,
>(
payload: Payload,
options: Options<TSlug>,
): Promise<PaginatedDocs<DataFromCollectionSlug<TSlug>>> {
options: Options<TSlug, TSelect>,
): Promise<PaginatedDocs<TransformCollectionWithSelect<TSlug, TSelect>>> {
const {
collection: collectionSlug,
currentDepth,
@@ -48,6 +60,8 @@ export async function findLocal<TSlug extends CollectionSlug>(
overrideAccess = true,
page,
pagination = true,
select,
// select,
showHiddenFields,
sort,
where,
@@ -61,7 +75,7 @@ export async function findLocal<TSlug extends CollectionSlug>(
)
}
return findOperation<TSlug>({
return findOperation<TSlug, TSelect>({
collection,
currentDepth,
depth,
@@ -74,6 +88,7 @@ export async function findLocal<TSlug extends CollectionSlug>(
page,
pagination,
req: await createLocalReq(options, payload),
select,
showHiddenFields,
sort,
where,

View File

@@ -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<TSlug extends CollectionSlug = CollectionSlug> = {
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<TSlug extends CollectionSlug = CollectionSlug> = {
context?: RequestContext
currentDepth?: number
depth?: number
disableErrors?: boolean
disableErrors?: TDisableErrors
draft?: boolean
fallbackLocale?: TypedLocale
id: number | string
@@ -23,18 +34,19 @@ export type Options<TSlug extends CollectionSlug = CollectionSlug> = {
locale?: 'all' | TypedLocale
overrideAccess?: boolean
req?: PayloadRequest
select?: TSelect
showHiddenFields?: boolean
user?: Document
}
export default async function findByIDLocal<TOptions extends Options>(
export default async function findByIDLocal<
TSlug extends CollectionSlug,
TDisableErrors extends boolean,
TSelect extends SelectFromCollectionSlug<TSlug>,
>(
payload: Payload,
options: TOptions,
): Promise<
TOptions['disableErrors'] extends true
? DataFromCollectionSlug<TOptions['collection']> | null
: DataFromCollectionSlug<TOptions['collection']>
> {
options: Options<TSlug, TDisableErrors, TSelect>,
): Promise<ApplyDisableErrors<TransformCollectionWithSelect<TSlug, TSelect>, TDisableErrors>> {
const {
id,
collection: collectionSlug,
@@ -45,6 +57,7 @@ export default async function findByIDLocal<TOptions extends Options>(
includeLockStatus,
joins,
overrideAccess = true,
select,
showHiddenFields,
} = options
@@ -56,7 +69,7 @@ export default async function findByIDLocal<TOptions extends Options>(
)
}
return findByIDOperation<TOptions['collection']>({
return findByIDOperation<TSlug, TDisableErrors, TSelect>({
id,
collection,
currentDepth,
@@ -67,6 +80,7 @@ export default async function findByIDLocal<TOptions extends Options>(
joins,
overrideAccess,
req: await createLocalReq(options, payload),
select,
showHiddenFields,
})
}

View File

@@ -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<TSlug extends CollectionSlug> = {
locale?: 'all' | TypedLocale
overrideAccess?: boolean
req?: PayloadRequest
select?: SelectType
showHiddenFields?: boolean
user?: Document
}
@@ -35,6 +36,7 @@ export default async function findVersionByIDLocal<TSlug extends CollectionSlug>
depth,
disableErrors = false,
overrideAccess = true,
select,
showHiddenFields,
} = options
@@ -55,6 +57,7 @@ export default async function findVersionByIDLocal<TSlug extends CollectionSlug>
disableErrors,
overrideAccess,
req: await createLocalReq(options, payload),
select,
showHiddenFields,
})
}

View File

@@ -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<TSlug extends CollectionSlug> = {
overrideAccess?: boolean
page?: number
req?: PayloadRequest
select?: SelectType
showHiddenFields?: boolean
sort?: Sort
user?: Document
@@ -38,6 +46,7 @@ export default async function findVersionsLocal<TSlug extends CollectionSlug>(
limit,
overrideAccess = true,
page,
select,
showHiddenFields,
sort,
where,
@@ -58,6 +67,7 @@ export default async function findVersionsLocal<TSlug extends CollectionSlug>(
overrideAccess,
page,
req: await createLocalReq(options, payload),
select,
showHiddenFields,
sort,
where,

View File

@@ -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'

View File

@@ -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<TSlug extends CollectionSlug> = {
locale?: TypedLocale
overrideAccess?: boolean
req?: PayloadRequest
select?: SelectType
showHiddenFields?: boolean
user?: Document
}
@@ -27,7 +28,14 @@ export default async function restoreVersionLocal<TSlug extends CollectionSlug>(
payload: Payload,
options: Options<TSlug>,
): Promise<DataFromCollectionSlug<TSlug>> {
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<TSlug extends CollectionSlug>(
overrideAccess,
payload,
req: await createLocalReq(options, payload),
select,
showHiddenFields,
}

View File

@@ -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<TSlug extends CollectionSlug> = {
export type BaseOptions<TSlug extends CollectionSlug, TSelect extends SelectType> = {
autosave?: boolean
collection: TSlug
/**
@@ -35,40 +42,62 @@ export type BaseOptions<TSlug extends CollectionSlug> = {
overwriteExistingFiles?: boolean
publishSpecificLocale?: string
req?: PayloadRequest
select?: TSelect
showHiddenFields?: boolean
user?: Document
}
export type ByIDOptions<TSlug extends CollectionSlug> = {
export type ByIDOptions<
TSlug extends CollectionSlug,
TSelect extends SelectFromCollectionSlug<TSlug>,
> = {
id: number | string
limit?: never
where?: never
} & BaseOptions<TSlug>
} & BaseOptions<TSlug, TSelect>
export type ManyOptions<TSlug extends CollectionSlug> = {
export type ManyOptions<
TSlug extends CollectionSlug,
TSelect extends SelectFromCollectionSlug<TSlug>,
> = {
id?: never
limit?: number
where: Where
} & BaseOptions<TSlug>
} & BaseOptions<TSlug, TSelect>
export type Options<TSlug extends CollectionSlug> = ByIDOptions<TSlug> | ManyOptions<TSlug>
export type Options<
TSlug extends CollectionSlug,
TSelect extends SelectFromCollectionSlug<TSlug>,
> = ByIDOptions<TSlug, TSelect> | ManyOptions<TSlug, TSelect>
async function updateLocal<TSlug extends CollectionSlug>(
async function updateLocal<
TSlug extends CollectionSlug,
TSelect extends SelectFromCollectionSlug<TSlug>,
>(
payload: Payload,
options: ByIDOptions<TSlug>,
): Promise<DataFromCollectionSlug<TSlug>>
async function updateLocal<TSlug extends CollectionSlug>(
options: ByIDOptions<TSlug, TSelect>,
): Promise<TransformCollectionWithSelect<TSlug, TSelect>>
async function updateLocal<
TSlug extends CollectionSlug,
TSelect extends SelectFromCollectionSlug<TSlug>,
>(
payload: Payload,
options: ManyOptions<TSlug>,
): Promise<BulkOperationResult<TSlug>>
async function updateLocal<TSlug extends CollectionSlug>(
options: ManyOptions<TSlug, TSelect>,
): Promise<BulkOperationResult<TSlug, TSelect>>
async function updateLocal<
TSlug extends CollectionSlug,
TSelect extends SelectFromCollectionSlug<TSlug>,
>(
payload: Payload,
options: Options<TSlug>,
): Promise<BulkOperationResult<TSlug> | DataFromCollectionSlug<TSlug>>
async function updateLocal<TSlug extends CollectionSlug>(
options: Options<TSlug, TSelect>,
): Promise<BulkOperationResult<TSlug, TSelect> | TransformCollectionWithSelect<TSlug, TSelect>>
async function updateLocal<
TSlug extends CollectionSlug,
TSelect extends SelectFromCollectionSlug<TSlug>,
>(
payload: Payload,
options: Options<TSlug>,
): Promise<BulkOperationResult<TSlug> | DataFromCollectionSlug<TSlug>> {
options: Options<TSlug, TSelect>,
): Promise<BulkOperationResult<TSlug, TSelect> | TransformCollectionWithSelect<TSlug, TSelect>> {
const {
id,
autosave,
@@ -84,6 +113,7 @@ async function updateLocal<TSlug extends CollectionSlug>(
overrideLock,
overwriteExistingFiles = false,
publishSpecificLocale,
select,
showHiddenFields,
where,
} = options
@@ -114,14 +144,15 @@ async function updateLocal<TSlug extends CollectionSlug>(
payload,
publishSpecificLocale,
req,
select,
showHiddenFields,
where,
}
if (options.id) {
return updateByIDOperation<TSlug>(args)
return updateByIDOperation<TSlug, TSelect>(args)
}
return updateOperation<TSlug>(args)
return updateOperation<TSlug, TSelect>(args)
}
export default updateLocal

View File

@@ -1,7 +1,7 @@
import httpStatus from 'http-status'
import type { FindOneArgs } from '../../database/types.js'
import type { PayloadRequest } from '../../types/index.js'
import type { PayloadRequest, SelectType } from '../../types/index.js'
import type { Collection, TypeWithID } from '../config/types.js'
import executeAccess from '../../auth/executeAccess.js'
@@ -22,6 +22,7 @@ export type Arguments = {
id: number | string
overrideAccess?: boolean
req: PayloadRequest
select?: SelectType
showHiddenFields?: boolean
}
@@ -36,6 +37,7 @@ export const restoreVersionOperation = async <TData extends TypeWithID = any>(
overrideAccess = false,
req,
req: { fallbackLocale, locale, payload },
select,
showHiddenFields,
} = args
@@ -115,6 +117,7 @@ export const restoreVersionOperation = async <TData extends TypeWithID = any>(
collection: collectionConfig.slug,
data: rawVersion.version,
req,
select,
})
// /////////////////////////////////////
@@ -150,6 +153,7 @@ export const restoreVersionOperation = async <TData extends TypeWithID = any>(
locale,
overrideAccess,
req,
select,
showHiddenFields,
})

View File

@@ -4,12 +4,13 @@ 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 { PayloadRequest, SelectType, Where } from '../../types/index.js'
import type {
BulkOperationResult,
Collection,
DataFromCollectionSlug,
RequiredDataFromCollectionSlug,
SelectFromCollectionSlug,
} from '../config/types.js'
import { ensureUsernameOrEmail } from '../../auth/ensureUsernameOrEmail.js'
@@ -46,13 +47,17 @@ export type Arguments<TSlug extends CollectionSlug> = {
overrideLock?: boolean
overwriteExistingFiles?: boolean
req: PayloadRequest
select?: SelectType
showHiddenFields?: boolean
where: Where
}
export const updateOperation = async <TSlug extends CollectionSlug>(
export const updateOperation = async <
TSlug extends CollectionSlug,
TSelect extends SelectFromCollectionSlug<TSlug>,
>(
incomingArgs: Arguments<TSlug>,
): Promise<BulkOperationResult<TSlug>> => {
): Promise<BulkOperationResult<TSlug, TSelect>> => {
let args = incomingArgs
try {
@@ -91,6 +96,7 @@ export const updateOperation = async <TSlug extends CollectionSlug>(
payload,
},
req,
select,
showHiddenFields,
where,
} = args
@@ -322,6 +328,7 @@ export const updateOperation = async <TSlug extends CollectionSlug>(
data: result,
locale,
req,
select,
})
}
@@ -336,6 +343,7 @@ export const updateOperation = async <TSlug extends CollectionSlug>(
docWithLocales: result,
payload,
req,
select,
})
}
@@ -354,6 +362,7 @@ export const updateOperation = async <TSlug extends CollectionSlug>(
locale,
overrideAccess,
req,
select,
showHiddenFields,
})

View File

@@ -5,11 +5,16 @@ import httpStatus from 'http-status'
import type { FindOneArgs } from '../../database/types.js'
import type { Args } from '../../fields/hooks/beforeChange/index.js'
import type { CollectionSlug } from '../../index.js'
import type { PayloadRequest } from '../../types/index.js'
import type {
PayloadRequest,
SelectType,
TransformCollectionWithSelect,
} from '../../types/index.js'
import type {
Collection,
DataFromCollectionSlug,
RequiredDataFromCollectionSlug,
SelectFromCollectionSlug,
} from '../config/types.js'
import { ensureUsernameOrEmail } from '../../auth/ensureUsernameOrEmail.js'
@@ -48,12 +53,16 @@ export type Arguments<TSlug extends CollectionSlug> = {
overwriteExistingFiles?: boolean
publishSpecificLocale?: string
req: PayloadRequest
select?: SelectType
showHiddenFields?: boolean
}
export const updateByIDOperation = async <TSlug extends CollectionSlug>(
export const updateByIDOperation = async <
TSlug extends CollectionSlug,
TSelect extends SelectFromCollectionSlug<TSlug> = SelectType,
>(
incomingArgs: Arguments<TSlug>,
): Promise<DataFromCollectionSlug<TSlug>> => {
): Promise<TransformCollectionWithSelect<TSlug, TSelect>> => {
let args = incomingArgs
try {
@@ -98,6 +107,7 @@ export const updateByIDOperation = async <TSlug extends CollectionSlug>(
payload,
},
req,
select,
showHiddenFields,
} = args
@@ -345,6 +355,7 @@ export const updateByIDOperation = async <TSlug extends CollectionSlug>(
data: dataToUpdate,
locale,
req,
select,
})
}
@@ -362,6 +373,7 @@ export const updateByIDOperation = async <TSlug extends CollectionSlug>(
payload,
publishSpecificLocale,
req,
select,
snapshot: versionSnapshotResult,
})
}
@@ -381,6 +393,7 @@ export const updateByIDOperation = async <TSlug extends CollectionSlug>(
locale,
overrideAccess,
req,
select,
showHiddenFields,
})
@@ -458,7 +471,7 @@ export const updateByIDOperation = async <TSlug extends CollectionSlug>(
await commitTransaction(req)
}
return result
return result as TransformCollectionWithSelect<TSlug, TSelect>
} catch (error: unknown) {
await killTransaction(args.req)
throw error

View File

@@ -3,7 +3,11 @@ import type { loginOperation } from '../../auth/operations/login.js'
import type { refreshOperation } from '../../auth/operations/refresh.js'
import type { CollectionSlug } from '../../index.js'
import type { PayloadRequest } from '../../types/index.js'
import type { AfterOperationHook, SanitizedCollectionConfig } from '../config/types.js'
import type {
AfterOperationHook,
SanitizedCollectionConfig,
SelectFromCollectionSlug,
} from '../config/types.js'
import type { countOperation } from './count.js'
import type { createOperation } from './create.js'
import type { deleteOperation } from './delete.js'
@@ -15,16 +19,26 @@ import type { updateByIDOperation } from './updateByID.js'
export type AfterOperationMap<TOperationGeneric extends CollectionSlug> = {
count: typeof countOperation<TOperationGeneric>
create: typeof createOperation<TOperationGeneric>
delete: typeof deleteOperation<TOperationGeneric>
deleteByID: typeof deleteByIDOperation<TOperationGeneric>
find: typeof findOperation<TOperationGeneric>
findByID: typeof findByIDOperation<TOperationGeneric>
create: typeof createOperation<TOperationGeneric, SelectFromCollectionSlug<TOperationGeneric>>
delete: typeof deleteOperation<TOperationGeneric, SelectFromCollectionSlug<TOperationGeneric>>
deleteByID: typeof deleteByIDOperation<
TOperationGeneric,
SelectFromCollectionSlug<TOperationGeneric>
>
find: typeof findOperation<TOperationGeneric, SelectFromCollectionSlug<TOperationGeneric>>
findByID: typeof findByIDOperation<
TOperationGeneric,
boolean,
SelectFromCollectionSlug<TOperationGeneric>
>
forgotPassword: typeof forgotPasswordOperation
login: typeof loginOperation<TOperationGeneric>
refresh: typeof refreshOperation
update: typeof updateOperation<TOperationGeneric>
updateByID: typeof updateByIDOperation<TOperationGeneric>
update: typeof updateOperation<TOperationGeneric, SelectFromCollectionSlug<TOperationGeneric>>
updateByID: typeof updateByIDOperation<
TOperationGeneric,
SelectFromCollectionSlug<TOperationGeneric>
>
}
export type AfterOperationArg<TOperationGeneric extends CollectionSlug> = {

View File

@@ -1,5 +1,13 @@
import type { TypeWithID } from '../collections/config/types.js'
import type { Document, JoinQuery, Payload, PayloadRequest, Sort, Where } from '../types/index.js'
import type {
Document,
JoinQuery,
Payload,
PayloadRequest,
SelectType,
Sort,
Where,
} from '../types/index.js'
import type { TypeWithVersion } from '../versions/types.js'
export type { TypeWithVersion }
@@ -180,6 +188,7 @@ export type QueryDraftsArgs = {
page?: number
pagination?: boolean
req: PayloadRequest
select?: SelectType
sort?: Sort
where?: Where
}
@@ -191,6 +200,7 @@ export type FindOneArgs = {
joins?: JoinQuery
locale?: string
req: PayloadRequest
select?: SelectType
where?: Where
}
@@ -206,6 +216,7 @@ export type FindArgs = {
pagination?: boolean
projection?: Record<string, unknown>
req: PayloadRequest
select?: SelectType
skip?: number
sort?: Sort
versions?: boolean
@@ -229,6 +240,7 @@ type BaseVersionArgs = {
page?: number
pagination?: boolean
req: PayloadRequest
select?: SelectType
skip?: number
sort?: Sort
versions?: boolean
@@ -250,6 +262,7 @@ export type FindGlobalVersionsArgs = {
export type FindGlobalArgs = {
locale?: string
req: PayloadRequest
select?: SelectType
slug: string
where?: Where
}
@@ -258,6 +271,7 @@ export type UpdateGlobalVersionArgs<T = TypeWithID> = {
global: string
locale?: string
req: PayloadRequest
select?: SelectType
versionData: T
} & (
| {
@@ -290,6 +304,7 @@ export type CreateGlobal = <T extends Record<string, unknown> = any>(
export type UpdateGlobalArgs<T extends Record<string, unknown> = any> = {
data: T
req: PayloadRequest
select?: SelectType
slug: string
}
export type UpdateGlobal = <T extends Record<string, unknown> = any>(
@@ -319,6 +334,7 @@ export type CreateVersionArgs<T = TypeWithID> = {
parent: number | string
publishedLocale?: string
req: PayloadRequest
select?: SelectType
snapshot?: true
updatedAt: string
versionData: T
@@ -336,6 +352,7 @@ export type CreateGlobalVersionArgs<T = TypeWithID> = {
parent: number | string
publishedLocale?: string
req: PayloadRequest
select?: SelectType
snapshot?: true
updatedAt: string
versionData: T
@@ -351,6 +368,7 @@ export type UpdateVersionArgs<T = TypeWithID> = {
collection: string
locale?: string
req: PayloadRequest
select?: SelectType
versionData: T
} & (
| {
@@ -373,6 +391,7 @@ export type CreateArgs = {
draft?: boolean
locale?: string
req: PayloadRequest
select?: SelectType
}
export type Create = (args: CreateArgs) => Promise<Document>
@@ -388,6 +407,7 @@ export type UpdateOneArgs = {
*/
options?: Record<string, unknown>
req: PayloadRequest
select?: SelectType
} & (
| {
id: number | string
@@ -407,6 +427,7 @@ export type UpsertArgs = {
joins?: JoinQuery
locale?: string
req: PayloadRequest
select?: SelectType
where: Where
}
@@ -416,6 +437,7 @@ export type DeleteOneArgs = {
collection: string
joins?: JoinQuery
req: PayloadRequest
select?: SelectType
where: Where
}

View File

@@ -51,6 +51,8 @@ export {
export { fieldSchemaToJSON } from '../utilities/fieldSchemaToJSON.js'
export { getDataByPath } from '../utilities/getDataByPath.js'
export { getSelectMode } from '../utilities/getSelectMode.js'
export { getSiblingData } from '../utilities/getSiblingData.js'
export { getUniqueListBy } from '../utilities/getUniqueListBy.js'
@@ -70,9 +72,7 @@ export { setsAreEqual } from '../utilities/setsAreEqual.js'
export { default as toKebabCase } from '../utilities/toKebabCase.js'
export { unflatten } from '../utilities/unflatten.js'
export { wait } from '../utilities/wait.js'
export { default as wordBoundariesRegex } from '../utilities/wordBoundariesRegex.js'
export { versionDefaults } from '../versions/defaults.js'
export { deepMergeSimple } from '@payloadcms/translations/utilities'

View File

@@ -1,8 +1,14 @@
import type { SanitizedCollectionConfig } from '../../../collections/config/types.js'
import type { SanitizedGlobalConfig } from '../../../globals/config/types.js'
import type { JsonObject, PayloadRequest, RequestContext } from '../../../types/index.js'
import type {
JsonObject,
PayloadRequest,
RequestContext,
SelectType,
} from '../../../types/index.js'
import { deepCopyObjectSimple } from '../../../utilities/deepCopyObject.js'
import { getSelectMode } from '../../../utilities/getSelectMode.js'
import { traverseFields } from './traverseFields.js'
type Args<T extends JsonObject> = {
@@ -19,6 +25,7 @@ type Args<T extends JsonObject> = {
locale: string
overrideAccess: boolean
req: PayloadRequest
select?: SelectType
showHiddenFields: boolean
}
@@ -47,6 +54,7 @@ export async function afterRead<T extends JsonObject>(args: Args<T>): Promise<T>
locale,
overrideAccess,
req,
select,
showHiddenFields,
} = args
@@ -83,6 +91,8 @@ export async function afterRead<T extends JsonObject>(args: Args<T>): Promise<T>
populationPromises,
req,
schemaPath: [],
select,
selectMode: select ? getSelectMode(select) : undefined,
showHiddenFields,
siblingDoc: doc,
})

View File

@@ -1,7 +1,13 @@
import type { RichTextAdapter } from '../../../admin/RichText.js'
import type { SanitizedCollectionConfig } from '../../../collections/config/types.js'
import type { SanitizedGlobalConfig } from '../../../globals/config/types.js'
import type { JsonObject, PayloadRequest, RequestContext } from '../../../types/index.js'
import type {
JsonObject,
PayloadRequest,
RequestContext,
SelectMode,
SelectType,
} from '../../../types/index.js'
import type { Field, TabAsField } from '../../config/types.js'
import { MissingEditorProp } from '../../../errors/index.js'
@@ -39,6 +45,8 @@ type Args = {
parentSchemaPath: string[]
populationPromises: Promise<void>[]
req: PayloadRequest
select?: SelectType
selectMode?: SelectMode
showHiddenFields: boolean
siblingDoc: JsonObject
triggerAccessControl?: boolean
@@ -72,6 +80,8 @@ export const promise = async ({
parentSchemaPath,
populationPromises,
req,
select,
selectMode,
showHiddenFields,
siblingDoc,
triggerAccessControl = true,
@@ -92,6 +102,22 @@ export const promise = async ({
delete siblingDoc[field.name]
}
if (fieldAffectsData(field) && select && selectMode) {
if (selectMode === 'include') {
if (!select[field.name]) {
delete siblingDoc[field.name]
return
}
}
if (selectMode === 'exclude') {
if (select[field.name] === false) {
delete siblingDoc[field.name]
return
}
}
}
const shouldHoistLocalizedValue =
flattenLocales &&
fieldAffectsData(field) &&
@@ -317,6 +343,8 @@ export const promise = async ({
groupDoc = {}
}
const groupSelect = select?.[field.name]
traverseFields({
collection,
context,
@@ -336,6 +364,8 @@ export const promise = async ({
populationPromises,
req,
schemaPath: fieldSchemaPath,
select: typeof groupSelect === 'object' ? groupSelect : undefined,
selectMode,
showHiddenFields,
siblingDoc: groupDoc,
triggerAccessControl,
@@ -348,6 +378,12 @@ export const promise = async ({
case 'array': {
const rows = siblingDoc[field.name] as JsonObject
const arraySelect = select?.[field.name]
if (selectMode === 'include' && typeof arraySelect === 'object') {
arraySelect.id = true
}
if (Array.isArray(rows)) {
rows.forEach((row, i) => {
traverseFields({
@@ -369,6 +405,8 @@ export const promise = async ({
populationPromises,
req,
schemaPath: fieldSchemaPath,
select: typeof arraySelect === 'object' ? arraySelect : undefined,
selectMode,
showHiddenFields,
siblingDoc: row || {},
triggerAccessControl,
@@ -415,12 +453,38 @@ export const promise = async ({
case 'blocks': {
const rows = siblingDoc[field.name]
const blocksSelect = select?.[field.name]
if (Array.isArray(rows)) {
rows.forEach((row, i) => {
const block = field.blocks.find(
(blockType) => blockType.slug === (row as JsonObject).blockType,
)
let blockSelectMode = selectMode
if (typeof blocksSelect === 'object') {
// sanitize blocks: {cta: false} to blocks: {cta: {id: true, blockType: true}}
if (selectMode === 'exclude' && blocksSelect[block.slug] === false) {
blockSelectMode = 'include'
blocksSelect[block.slug] = {
id: true,
blockType: true,
}
} else if (selectMode === 'include') {
if (!blocksSelect[block.slug]) {
blocksSelect[block.slug] = {}
}
if (typeof blocksSelect[block.slug] === 'object') {
blocksSelect[block.slug]['id'] = true
blocksSelect[block.slug]['blockType'] = true
}
}
}
const blockSelect = blocksSelect?.[block.slug]
if (block) {
traverseFields({
collection,
@@ -441,6 +505,8 @@ export const promise = async ({
populationPromises,
req,
schemaPath: fieldSchemaPath,
select: typeof blockSelect === 'object' ? blockSelect : undefined,
selectMode: blockSelectMode,
showHiddenFields,
siblingDoc: (row as JsonObject) || {},
triggerAccessControl,
@@ -513,6 +579,8 @@ export const promise = async ({
populationPromises,
req,
schemaPath: fieldSchemaPath,
select,
selectMode,
showHiddenFields,
siblingDoc,
triggerAccessControl,
@@ -524,11 +592,18 @@ export const promise = async ({
case 'tab': {
let tabDoc = siblingDoc
let tabSelect: SelectType | undefined
if (tabHasName(field)) {
tabDoc = siblingDoc[field.name] as JsonObject
if (typeof siblingDoc[field.name] !== 'object') {
tabDoc = {}
}
if (typeof select?.[field.name] === 'object') {
tabSelect = select?.[field.name] as SelectType
}
} else {
tabSelect = select
}
traverseFields({
@@ -550,6 +625,8 @@ export const promise = async ({
populationPromises,
req,
schemaPath: fieldSchemaPath,
select: tabSelect,
selectMode,
showHiddenFields,
siblingDoc: tabDoc,
triggerAccessControl,
@@ -579,6 +656,8 @@ export const promise = async ({
populationPromises,
req,
schemaPath: fieldSchemaPath,
select,
selectMode,
showHiddenFields,
siblingDoc,
triggerAccessControl,

View File

@@ -1,6 +1,12 @@
import type { SanitizedCollectionConfig } from '../../../collections/config/types.js'
import type { SanitizedGlobalConfig } from '../../../globals/config/types.js'
import type { JsonObject, PayloadRequest, RequestContext } from '../../../types/index.js'
import type {
JsonObject,
PayloadRequest,
RequestContext,
SelectMode,
SelectType,
} from '../../../types/index.js'
import type { Field, TabAsField } from '../../config/types.js'
import { promise } from './promise.js'
@@ -27,6 +33,8 @@ type Args = {
populationPromises: Promise<void>[]
req: PayloadRequest
schemaPath: string[]
select?: SelectType
selectMode?: SelectMode
showHiddenFields: boolean
siblingDoc: JsonObject
triggerAccessControl?: boolean
@@ -52,6 +60,8 @@ export const traverseFields = ({
populationPromises,
req,
schemaPath,
select,
selectMode,
showHiddenFields,
siblingDoc,
triggerAccessControl = true,
@@ -78,6 +88,8 @@ export const traverseFields = ({
parentSchemaPath: schemaPath,
populationPromises,
req,
select,
selectMode,
showHiddenFields,
siblingDoc,
triggerAccessControl,

View File

@@ -19,12 +19,14 @@ import type {
} from '../../config/types.js'
import type { DBIdentifierName } from '../../database/types.js'
import type { Field } from '../../fields/config/types.js'
import type { GlobalSlug, TypedGlobal } from '../../index.js'
import type { GlobalSlug, TypedGlobal, TypedGlobalSelect } from '../../index.js'
import type { PayloadRequest, RequestContext, Where } from '../../types/index.js'
import type { IncomingGlobalVersions, SanitizedGlobalVersions } from '../../versions/types.js'
export type DataFromGlobalSlug<TSlug extends GlobalSlug> = TypedGlobal[TSlug]
export type SelectFromGlobalSlug<TSlug extends GlobalSlug> = TypedGlobalSelect[TSlug]
export type BeforeValidateHook = (args: {
context: RequestContext
data?: any

View File

@@ -1,9 +1,10 @@
import type { AccessResult } from '../../config/types.js'
import type { PayloadRequest, Where } from '../../types/index.js'
import type { PayloadRequest, SelectType, Where } from '../../types/index.js'
import type { SanitizedGlobalConfig } from '../config/types.js'
import executeAccess from '../../auth/executeAccess.js'
import { afterRead } from '../../fields/hooks/afterRead/index.js'
import { getSelectMode } from '../../utilities/getSelectMode.js'
import { killTransaction } from '../../utilities/killTransaction.js'
import replaceWithDraftIfAvailable from '../../versions/drafts/replaceWithDraftIfAvailable.js'
@@ -14,6 +15,7 @@ type Args = {
includeLockStatus?: boolean
overrideAccess?: boolean
req: PayloadRequest
select?: SelectType
showHiddenFields?: boolean
slug: string
}
@@ -30,6 +32,7 @@ export const findOneOperation = async <T extends Record<string, unknown>>(
overrideAccess = false,
req: { fallbackLocale, locale },
req,
select,
showHiddenFields,
} = args
@@ -52,6 +55,7 @@ export const findOneOperation = async <T extends Record<string, unknown>>(
slug,
locale,
req,
select,
where: overrideAccess ? undefined : (accessResult as Where),
})
if (!doc) {
@@ -103,6 +107,7 @@ export const findOneOperation = async <T extends Record<string, unknown>>(
entityType: 'global',
overrideAccess,
req,
select,
})
}
@@ -122,6 +127,19 @@ export const findOneOperation = async <T extends Record<string, unknown>>(
})) || doc
}, Promise.resolve())
// /////////////////////////////////////
// Execute globalType field if not selected
// /////////////////////////////////////
if (select && doc.globalType) {
const selectMode = getSelectMode(select)
if (
(selectMode === 'include' && !select['globalType']) ||
(selectMode === 'exclude' && select['globalType'] === false)
) {
delete doc['globalType']
}
}
// /////////////////////////////////////
// Execute field-level hooks and access
// /////////////////////////////////////
@@ -137,6 +155,7 @@ export const findOneOperation = async <T extends Record<string, unknown>>(
locale,
overrideAccess,
req,
select,
showHiddenFields,
})

View File

@@ -1,5 +1,5 @@
import type { FindGlobalVersionsArgs } from '../../database/types.js'
import type { PayloadRequest } from '../../types/index.js'
import type { PayloadRequest, SelectType } from '../../types/index.js'
import type { TypeWithVersion } from '../../versions/types.js'
import type { SanitizedGlobalConfig } from '../config/types.js'
@@ -8,6 +8,7 @@ import { combineQueries } from '../../database/combineQueries.js'
import { Forbidden, NotFound } from '../../errors/index.js'
import { afterRead } from '../../fields/hooks/afterRead/index.js'
import { deepCopyObjectSimple } from '../../utilities/deepCopyObject.js'
import { getSelectMode } from '../../utilities/getSelectMode.js'
import { killTransaction } from '../../utilities/killTransaction.js'
export type Arguments = {
@@ -18,6 +19,7 @@ export type Arguments = {
id: number | string
overrideAccess?: boolean
req: PayloadRequest
select?: SelectType
showHiddenFields?: boolean
}
@@ -33,6 +35,7 @@ export const findVersionByIDOperation = async <T extends TypeWithVersion<T> = an
overrideAccess,
req: { fallbackLocale, locale, payload },
req,
select,
showHiddenFields,
} = args
@@ -57,6 +60,7 @@ export const findVersionByIDOperation = async <T extends TypeWithVersion<T> = an
limit: 1,
locale,
req,
select,
where: combineQueries({ id: { equals: id } }, accessResults),
}
@@ -120,6 +124,7 @@ export const findVersionByIDOperation = async <T extends TypeWithVersion<T> = an
locale,
overrideAccess,
req,
select: typeof select?.version === 'object' ? select.version : undefined,
showHiddenFields,
})

View File

@@ -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 { SanitizedGlobalConfig } 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 <T extends TypeWithVersion<T>>(
pagination = true,
req: { fallbackLocale, locale, payload },
req,
select,
showHiddenFields,
sort,
where,
@@ -73,6 +75,7 @@ export const findVersionsOperation = async <T extends TypeWithVersion<T>>(
page: page || 1,
pagination,
req,
select,
sort,
where: fullWhere,
})
@@ -84,7 +87,8 @@ export const findVersionsOperation = async <T extends TypeWithVersion<T>>(
let result = {
...paginatedDocs,
docs: await Promise.all(
paginatedDocs.docs.map(async (data) => ({
paginatedDocs.docs.map(async (data) => {
return {
...data,
version: await afterRead<T>({
collection: null,
@@ -102,9 +106,11 @@ export const findVersionsOperation = async <T extends TypeWithVersion<T>>(
locale,
overrideAccess,
req,
select,
showHiddenFields,
}),
})),
}
}),
),
} as PaginatedDocs<T>

View File

@@ -1,12 +1,17 @@
import type { GlobalSlug, Payload, RequestContext, TypedLocale } from '../../../index.js'
import type { Document, PayloadRequest } from '../../../types/index.js'
import type { DataFromGlobalSlug } from '../../config/types.js'
import type {
Document,
PayloadRequest,
SelectType,
TransformGlobalWithSelect,
} from '../../../types/index.js'
import type { SelectFromGlobalSlug } from '../../config/types.js'
import { APIError } from '../../../errors/index.js'
import { createLocalReq } from '../../../utilities/createLocalReq.js'
import { findOneOperation } from '../findOne.js'
export type Options<TSlug extends GlobalSlug> = {
export type Options<TSlug extends GlobalSlug, TSelect extends SelectType> = {
context?: RequestContext
depth?: number
draft?: boolean
@@ -15,21 +20,26 @@ export type Options<TSlug extends GlobalSlug> = {
locale?: 'all' | TypedLocale
overrideAccess?: boolean
req?: PayloadRequest
select?: TSelect
showHiddenFields?: boolean
slug: TSlug
user?: Document
}
export default async function findOneLocal<TSlug extends GlobalSlug>(
export default async function findOneLocal<
TSlug extends GlobalSlug,
TSelect extends SelectFromGlobalSlug<TSlug>,
>(
payload: Payload,
options: Options<TSlug>,
): Promise<DataFromGlobalSlug<TSlug>> {
options: Options<TSlug, TSelect>,
): Promise<TransformGlobalWithSelect<TSlug, TSelect>> {
const {
slug: globalSlug,
depth,
draft = false,
includeLockStatus,
overrideAccess = true,
select,
showHiddenFields,
} = options
@@ -47,6 +57,7 @@ export default async function findOneLocal<TSlug extends GlobalSlug>(
includeLockStatus,
overrideAccess,
req: await createLocalReq(options, payload),
select,
showHiddenFields,
})
}

View File

@@ -1,5 +1,5 @@
import type { GlobalSlug, Payload, RequestContext, TypedLocale } from '../../../index.js'
import type { Document, PayloadRequest } from '../../../types/index.js'
import type { Document, PayloadRequest, SelectType } from '../../../types/index.js'
import type { TypeWithVersion } from '../../../versions/types.js'
import type { DataFromGlobalSlug } from '../../config/types.js'
@@ -16,6 +16,7 @@ export type Options<TSlug extends GlobalSlug> = {
locale?: 'all' | TypedLocale
overrideAccess?: boolean
req?: PayloadRequest
select?: SelectType
showHiddenFields?: boolean
slug: TSlug
user?: Document
@@ -32,6 +33,7 @@ export default async function findVersionByIDLocal<TSlug extends GlobalSlug>(
depth,
disableErrors = false,
overrideAccess = true,
select,
showHiddenFields,
} = options
@@ -48,6 +50,7 @@ export default async function findVersionByIDLocal<TSlug extends GlobalSlug>(
globalConfig,
overrideAccess,
req: await createLocalReq(options, payload),
select,
showHiddenFields,
})
}

View File

@@ -1,6 +1,7 @@
/* eslint-disable no-restricted-exports */
import type { PaginatedDocs } from '../../../database/types.js'
import type { GlobalSlug, Payload, RequestContext, TypedLocale } from '../../../index.js'
import type { Document, PayloadRequest, Sort, Where } from '../../../types/index.js'
import type { Document, PayloadRequest, SelectType, Sort, Where } from '../../../types/index.js'
import type { TypeWithVersion } from '../../../versions/types.js'
import type { DataFromGlobalSlug } from '../../config/types.js'
@@ -17,6 +18,7 @@ export type Options<TSlug extends GlobalSlug> = {
overrideAccess?: boolean
page?: number
req?: PayloadRequest
select?: SelectType
showHiddenFields?: boolean
slug: TSlug
sort?: Sort
@@ -34,6 +36,7 @@ export default async function findVersionsLocal<TSlug extends GlobalSlug>(
limit,
overrideAccess = true,
page,
select,
showHiddenFields,
sort,
where,
@@ -52,6 +55,7 @@ export default async function findVersionsLocal<TSlug extends GlobalSlug>(
overrideAccess,
page,
req: await createLocalReq(options, payload),
select,
showHiddenFields,
sort,
where,

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-restricted-exports */
import type { GlobalSlug, Payload, RequestContext, TypedLocale } from '../../../index.js'
import type { Document, PayloadRequest } from '../../../types/index.js'
import type { DataFromGlobalSlug } from '../../config/types.js'

View File

@@ -1,14 +1,19 @@
import type { DeepPartial } from 'ts-essentials'
import type { GlobalSlug, Payload, RequestContext, TypedLocale } from '../../../index.js'
import type { Document, PayloadRequest } from '../../../types/index.js'
import type { DataFromGlobalSlug } from '../../config/types.js'
import type {
Document,
PayloadRequest,
SelectType,
TransformGlobalWithSelect,
} from '../../../types/index.js'
import type { DataFromGlobalSlug, SelectFromGlobalSlug } from '../../config/types.js'
import { APIError } from '../../../errors/index.js'
import { createLocalReq } from '../../../utilities/createLocalReq.js'
import { updateOperation } from '../update.js'
export type Options<TSlug extends GlobalSlug> = {
export type Options<TSlug extends GlobalSlug, TSelect extends SelectType> = {
context?: RequestContext
data: DeepPartial<Omit<DataFromGlobalSlug<TSlug>, 'id'>>
depth?: number
@@ -19,15 +24,19 @@ export type Options<TSlug extends GlobalSlug> = {
overrideLock?: boolean
publishSpecificLocale?: TypedLocale
req?: PayloadRequest
select?: TSelect
showHiddenFields?: boolean
slug: TSlug
user?: Document
}
export default async function updateLocal<TSlug extends GlobalSlug>(
export default async function updateLocal<
TSlug extends GlobalSlug,
TSelect extends SelectFromGlobalSlug<TSlug>,
>(
payload: Payload,
options: Options<TSlug>,
): Promise<DataFromGlobalSlug<TSlug>> {
options: Options<TSlug, TSelect>,
): Promise<TransformGlobalWithSelect<TSlug, TSelect>> {
const {
slug: globalSlug,
data,
@@ -36,6 +45,7 @@ export default async function updateLocal<TSlug extends GlobalSlug>(
overrideAccess = true,
overrideLock,
publishSpecificLocale,
select,
showHiddenFields,
} = options
@@ -45,7 +55,7 @@ export default async function updateLocal<TSlug extends GlobalSlug>(
throw new APIError(`The global with slug ${String(globalSlug)} can't be found.`)
}
return updateOperation<TSlug>({
return updateOperation<TSlug, TSelect>({
slug: globalSlug as string,
data,
depth,
@@ -55,6 +65,7 @@ export default async function updateLocal<TSlug extends GlobalSlug>(
overrideLock,
publishSpecificLocale,
req: await createLocalReq(options, payload),
select,
showHiddenFields,
})
}

View File

@@ -1,8 +1,18 @@
import type { DeepPartial } from 'ts-essentials'
import type { GlobalSlug, JsonObject } from '../../index.js'
import type { Operation, PayloadRequest, Where } from '../../types/index.js'
import type { DataFromGlobalSlug, SanitizedGlobalConfig } from '../config/types.js'
import type {
Operation,
PayloadRequest,
SelectType,
TransformGlobalWithSelect,
Where,
} from '../../types/index.js'
import type {
DataFromGlobalSlug,
SanitizedGlobalConfig,
SelectFromGlobalSlug,
} from '../config/types.js'
import executeAccess from '../../auth/executeAccess.js'
import { afterChange } from '../../fields/hooks/afterChange/index.js'
@@ -12,6 +22,7 @@ import { beforeValidate } from '../../fields/hooks/beforeValidate/index.js'
import { deepCopyObjectSimple } from '../../index.js'
import { checkDocumentLockStatus } from '../../utilities/checkDocumentLockStatus.js'
import { commitTransaction } from '../../utilities/commitTransaction.js'
import { getSelectMode } from '../../utilities/getSelectMode.js'
import { initTransaction } from '../../utilities/initTransaction.js'
import { killTransaction } from '../../utilities/killTransaction.js'
import { getLatestGlobalVersion } from '../../versions/getLatestGlobalVersion.js'
@@ -28,13 +39,17 @@ type Args<TSlug extends GlobalSlug> = {
overrideLock?: boolean
publishSpecificLocale?: string
req: PayloadRequest
select?: SelectType
showHiddenFields?: boolean
slug: string
}
export const updateOperation = async <TSlug extends GlobalSlug>(
export const updateOperation = async <
TSlug extends GlobalSlug,
TSelect extends SelectFromGlobalSlug<TSlug>,
>(
args: Args<TSlug>,
): Promise<DataFromGlobalSlug<TSlug>> => {
): Promise<TransformGlobalWithSelect<TSlug, TSelect>> => {
if (args.publishSpecificLocale) {
args.req.locale = args.publishSpecificLocale
}
@@ -51,6 +66,7 @@ export const updateOperation = async <TSlug extends GlobalSlug>(
publishSpecificLocale,
req: { fallbackLocale, locale, payload },
req,
select,
showHiddenFields,
} = args
@@ -230,6 +246,7 @@ export const updateOperation = async <TSlug extends GlobalSlug>(
slug,
data: result,
req,
select,
})
} else {
result = await payload.db.createGlobal({
@@ -253,6 +270,7 @@ export const updateOperation = async <TSlug extends GlobalSlug>(
payload,
publishSpecificLocale,
req,
select,
snapshot: versionSnapshotResult,
})
@@ -262,6 +280,19 @@ export const updateOperation = async <TSlug extends GlobalSlug>(
}
}
// /////////////////////////////////////
// Execute globalType field if not selected
// /////////////////////////////////////
if (select && result.globalType) {
const selectMode = getSelectMode(select)
if (
(selectMode === 'include' && !select['globalType']) ||
(selectMode === 'exclude' && select['globalType'] === false)
) {
delete result['globalType']
}
}
// /////////////////////////////////////
// afterRead - Fields
// /////////////////////////////////////
@@ -277,6 +308,7 @@ export const updateOperation = async <TSlug extends GlobalSlug>(
locale,
overrideAccess,
req,
select,
showHiddenFields,
})
@@ -336,7 +368,7 @@ export const updateOperation = async <TSlug extends GlobalSlug>(
await commitTransaction(req)
}
return result
return result as TransformGlobalWithSelect<TSlug, TSelect>
} catch (error: unknown) {
await killTransaction(req)
throw error

View File

@@ -22,6 +22,7 @@ import type {
BulkOperationResult,
Collection,
DataFromCollectionSlug,
SelectFromCollectionSlug,
TypeWithID,
} from './collections/config/types.js'
export type * from './admin/types.js'
@@ -33,6 +34,7 @@ import type {
Options as DeleteOptions,
} from './collections/operations/local/delete.js'
export type { MappedView } from './admin/views/types.js'
import type { Options as DuplicateOptions } from './collections/operations/local/duplicate.js'
import type { Options as FindOptions } from './collections/operations/local/find.js'
import type { Options as FindByIDOptions } from './collections/operations/local/findByID.js'
@@ -47,13 +49,19 @@ import type {
import type { InitOptions, SanitizedConfig } from './config/types.js'
import type { BaseDatabaseAdapter, PaginatedDocs } from './database/types.js'
import type { InitializedEmailAdapter } from './email/types.js'
import type { DataFromGlobalSlug, Globals } from './globals/config/types.js'
import type { DataFromGlobalSlug, Globals, SelectFromGlobalSlug } from './globals/config/types.js'
import type { Options as FindGlobalOptions } from './globals/operations/local/findOne.js'
import type { Options as FindGlobalVersionByIDOptions } from './globals/operations/local/findVersionByID.js'
import type { Options as FindGlobalVersionsOptions } from './globals/operations/local/findVersions.js'
import type { Options as RestoreGlobalVersionOptions } from './globals/operations/local/restoreVersion.js'
import type { Options as UpdateGlobalOptions } from './globals/operations/local/update.js'
import type { JsonObject } from './types/index.js'
import type {
ApplyDisableErrors,
JsonObject,
SelectType,
TransformCollectionWithSelect,
TransformGlobalWithSelect,
} from './types/index.js'
import type { TraverseFieldsCallback } from './utilities/traverseFields.js'
import type { TypeWithVersion } from './versions/types.js'
@@ -88,12 +96,20 @@ export interface GeneratedTypes {
}
}
}
collectionsSelectUntyped: {
[slug: string]: SelectType
}
collectionsUntyped: {
[slug: string]: JsonObject & TypeWithID
}
dbUntyped: {
defaultIDType: number | string
}
globalsSelectUntyped: {
[slug: string]: SelectType
}
globalsUntyped: {
[slug: string]: JsonObject
}
@@ -106,13 +122,29 @@ type ResolveCollectionType<T> = 'collections' extends keyof T
? T['collections']
: // @ts-expect-error
T['collectionsUntyped']
// @ts-expect-error
type ResolveGlobalType<T> = 'globals' extends keyof T ? T['globals'] : T['globalsUntyped']
type ResolveCollectionSelectType<T> = 'collectionsSelect' extends keyof T
? T['collectionsSelect']
: // @ts-expect-error
T['collectionsSelectUntyped']
type ResolveGlobalType<T> = 'globals' extends keyof T
? T['globals']
: // @ts-expect-error
T['globalsUntyped']
type ResolveGlobalSelectType<T> = 'globalsSelect' extends keyof T
? T['globalsSelect']
: // @ts-expect-error
T['globalsSelectUntyped']
// Applying helper types to GeneratedTypes
export type TypedCollection = ResolveCollectionType<GeneratedTypes>
export type TypedCollectionSelect = ResolveCollectionSelectType<GeneratedTypes>
export type TypedGlobal = ResolveGlobalType<GeneratedTypes>
export type TypedGlobalSelect = ResolveGlobalSelectType<GeneratedTypes>
// Extract string keys from the type
type StringKeyOf<T> = Extract<keyof T, string>
@@ -184,21 +216,21 @@ export class BasePayload {
* @param options
* @returns created document
*/
create = async <TSlug extends CollectionSlug>(
options: CreateOptions<TSlug>,
): Promise<DataFromCollectionSlug<TSlug>> => {
create = async <TSlug extends CollectionSlug, TSelect extends SelectFromCollectionSlug<TSlug>>(
options: CreateOptions<TSlug, TSelect>,
): Promise<TransformCollectionWithSelect<TSlug, TSelect>> => {
const { create } = localOperations
return create<TSlug>(this, options)
return create<TSlug, TSelect>(this, options)
}
db: DatabaseAdapter
decrypt = decrypt
duplicate = async <TSlug extends CollectionSlug>(
options: DuplicateOptions<TSlug>,
): Promise<DataFromCollectionSlug<TSlug>> => {
duplicate = async <TSlug extends CollectionSlug, TSelect extends SelectFromCollectionSlug<TSlug>>(
options: DuplicateOptions<TSlug, TSelect>,
): Promise<TransformCollectionWithSelect<TSlug, TSelect>> => {
const { duplicate } = localOperations
return duplicate<TSlug>(this, options)
return duplicate<TSlug, TSelect>(this, options)
}
email: InitializedEmailAdapter
@@ -219,11 +251,11 @@ export class BasePayload {
* @param options
* @returns documents satisfying query
*/
find = async <TSlug extends CollectionSlug>(
options: FindOptions<TSlug>,
): Promise<PaginatedDocs<DataFromCollectionSlug<TSlug>>> => {
find = async <TSlug extends CollectionSlug, TSelect extends SelectFromCollectionSlug<TSlug>>(
options: FindOptions<TSlug, TSelect>,
): Promise<PaginatedDocs<TransformCollectionWithSelect<TSlug, TSelect>>> => {
const { find } = localOperations
return find<TSlug>(this, options)
return find<TSlug, TSelect>(this, options)
}
/**
@@ -231,22 +263,22 @@ export class BasePayload {
* @param options
* @returns document with specified ID
*/
findByID = async <TOptions extends FindByIDOptions>(
options: TOptions,
): Promise<
TOptions['disableErrors'] extends true
? DataFromCollectionSlug<TOptions['collection']> | null
: DataFromCollectionSlug<TOptions['collection']>
> => {
findByID = async <
TSlug extends CollectionSlug,
TDisableErrors extends boolean,
TSelect extends SelectFromCollectionSlug<TSlug>,
>(
options: FindByIDOptions<TSlug, TDisableErrors, TSelect>,
): Promise<ApplyDisableErrors<TransformCollectionWithSelect<TSlug, TSelect>, TDisableErrors>> => {
const { findByID } = localOperations
return findByID<TOptions>(this, options)
return findByID<TSlug, TDisableErrors, TSelect>(this, options)
}
findGlobal = async <TSlug extends GlobalSlug>(
options: FindGlobalOptions<TSlug>,
): Promise<DataFromGlobalSlug<TSlug>> => {
findGlobal = async <TSlug extends GlobalSlug, TSelect extends SelectFromGlobalSlug<TSlug>>(
options: FindGlobalOptions<TSlug, TSelect>,
): Promise<TransformGlobalWithSelect<TSlug, TSelect>> => {
const { findOne } = localGlobalOperations
return findOne<TSlug>(this, options)
return findOne<TSlug, TSelect>(this, options)
}
/**
@@ -375,11 +407,11 @@ export class BasePayload {
return unlock<TSlug>(this, options)
}
updateGlobal = async <TSlug extends GlobalSlug>(
options: UpdateGlobalOptions<TSlug>,
): Promise<DataFromGlobalSlug<TSlug>> => {
updateGlobal = async <TSlug extends GlobalSlug, TSelect extends SelectFromGlobalSlug<TSlug>>(
options: UpdateGlobalOptions<TSlug, TSelect>,
): Promise<TransformGlobalWithSelect<TSlug, TSelect>> => {
const { update } = localGlobalOperations
return update<TSlug>(this, options)
return update<TSlug, TSelect>(this, options)
}
validationRules: (args: OperationArgs<any>) => ValidationRule[]
@@ -425,19 +457,19 @@ export class BasePayload {
* @param options
* @returns Updated document(s)
*/
delete<TSlug extends CollectionSlug>(
options: DeleteByIDOptions<TSlug>,
): Promise<DataFromCollectionSlug<TSlug>>
delete<TSlug extends CollectionSlug, TSelect extends SelectFromCollectionSlug<TSlug>>(
options: DeleteByIDOptions<TSlug, TSelect>,
): Promise<TransformCollectionWithSelect<TSlug, TSelect>>
delete<TSlug extends CollectionSlug>(
options: DeleteManyOptions<TSlug>,
): Promise<BulkOperationResult<TSlug>>
delete<TSlug extends CollectionSlug, TSelect extends SelectFromCollectionSlug<TSlug>>(
options: DeleteManyOptions<TSlug, TSelect>,
): Promise<BulkOperationResult<TSlug, TSelect>>
delete<TSlug extends CollectionSlug>(
options: DeleteOptions<TSlug>,
): Promise<BulkOperationResult<TSlug> | DataFromCollectionSlug<TSlug>> {
delete<TSlug extends CollectionSlug, TSelect extends SelectFromCollectionSlug<TSlug>>(
options: DeleteOptions<TSlug, TSelect>,
): Promise<BulkOperationResult<TSlug, TSelect> | TransformCollectionWithSelect<TSlug, TSelect>> {
const { deleteLocal } = localOperations
return deleteLocal<TSlug>(this, options)
return deleteLocal<TSlug, TSelect>(this, options)
}
/**
@@ -593,24 +625,24 @@ export class BasePayload {
return this
}
update<TSlug extends CollectionSlug>(
options: UpdateManyOptions<TSlug>,
): Promise<BulkOperationResult<TSlug>>
update<TSlug extends CollectionSlug, TSelect extends SelectFromCollectionSlug<TSlug>>(
options: UpdateManyOptions<TSlug, TSelect>,
): Promise<BulkOperationResult<TSlug, TSelect>>
/**
* @description Update one or more documents
* @param options
* @returns Updated document(s)
*/
update<TSlug extends CollectionSlug>(
options: UpdateByIDOptions<TSlug>,
): Promise<DataFromCollectionSlug<TSlug>>
update<TSlug extends CollectionSlug, TSelect extends SelectFromCollectionSlug<TSlug>>(
options: UpdateByIDOptions<TSlug, TSelect>,
): Promise<TransformCollectionWithSelect<TSlug, TSelect>>
update<TSlug extends CollectionSlug>(
options: UpdateOptions<TSlug>,
): Promise<BulkOperationResult<TSlug> | DataFromCollectionSlug<TSlug>> {
update<TSlug extends CollectionSlug, TSelect extends SelectFromCollectionSlug<TSlug>>(
options: UpdateOptions<TSlug, TSelect>,
): Promise<BulkOperationResult<TSlug, TSelect> | TransformCollectionWithSelect<TSlug, TSelect>> {
const { update } = localOperations
return update<TSlug>(this, options)
return update<TSlug, TSelect>(this, options)
}
}

View File

@@ -2,9 +2,19 @@ import type { I18n, TFunction } from '@payloadcms/translations'
import type DataLoader from 'dataloader'
import type { URL } from 'url'
import type { TypeWithID, TypeWithTimestamps } from '../collections/config/types.js'
import type {
DataFromCollectionSlug,
TypeWithID,
TypeWithTimestamps,
} from '../collections/config/types.js'
import type payload from '../index.js'
import type { TypedLocale, TypedUser } from '../index.js'
import type {
CollectionSlug,
DataFromGlobalSlug,
GlobalSlug,
TypedLocale,
TypedUser,
} from '../index.js'
import type { validOperators } from './constants.js'
export type { Payload as Payload } from '../index.js'
@@ -140,3 +150,73 @@ export function docHasTimestamps(doc: any): doc is TypeWithTimestamps {
export type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N // This is a commonly used trick to detect 'any'
export type IsAny<T> = IfAny<T, true, false>
export type ReplaceAny<T, DefaultType> = IsAny<T> extends true ? DefaultType : T
export type SelectIncludeType = {
[k: string]: SelectIncludeType | true
}
export type SelectExcludeType = {
[k: string]: false | SelectExcludeType
}
export type SelectMode = 'exclude' | 'include'
export type SelectType = SelectExcludeType | SelectIncludeType
export type ApplyDisableErrors<T, DisableErrors extends boolean> = DisableErrors extends true
? null | T
: T
export type TransformDataWithSelect<
Data extends Record<string, any>,
Select extends SelectType,
> = Select extends never
? Data
: string extends keyof Select
? Data
: // START Handle types when they aren't generated
// For example in any package in this repository outside of tests / plugins
// This stil gives us autocomplete when using include select mode, i.e select: {title :true} returns type {title: any, id: string | number}
string extends keyof Omit<Data, 'id'>
? Select extends SelectIncludeType
? {
[K in Data extends TypeWithID ? 'id' | keyof Select : keyof Select]: K extends 'id'
? number | string
: unknown
}
: Data
: // END Handle types when they aren't generated
// Handle include mode
Select extends SelectIncludeType
? {
[K in keyof Data as K extends keyof Select
? Select[K] extends object | true
? K
: never
: // select 'id' always
K extends 'id'
? K
: never]: Data[K]
}
: // Handle exclude mode
{
[K in keyof Data as K extends keyof Select
? Select[K] extends object | undefined
? K
: never
: K]: Data[K]
}
export type TransformCollectionWithSelect<
TSlug extends CollectionSlug,
TSelect extends SelectType,
> = TSelect extends SelectType
? TransformDataWithSelect<DataFromCollectionSlug<TSlug>, TSelect>
: DataFromCollectionSlug<TSlug>
export type TransformGlobalWithSelect<
TSlug extends GlobalSlug,
TSelect extends SelectType,
> = TSelect extends SelectType
? TransformDataWithSelect<DataFromGlobalSlug<TSlug>, TSelect>
: DataFromGlobalSlug<TSlug>

View File

@@ -0,0 +1,564 @@
import { addSelectGenericsToGeneratedTypes } from './addSelectGenericsToGeneretedTypes.js'
const INPUT_AND_OUTPUT = [
{
input: `
/* tslint:disable */
/* eslint-disable */
/**
* This file was automatically generated by Payload.
* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,
* and re-run \`payload generate:types\` to regenerate this file.
*/
export interface Config {
auth: {
users: UserAuthOperations;
};
collections: {
posts: Post;
users: User;
'payload-locked-documents': PayloadLockedDocument;
'payload-preferences': PayloadPreference;
'payload-migrations': PayloadMigration;
};
collectionsSelect?: {
posts: PostsSelect;
users: UsersSelect;
'payload-locked-documents': PayloadLockedDocumentsSelect;
'payload-preferences': PayloadPreferencesSelect;
'payload-migrations': PayloadMigrationsSelect;
};
db: {
defaultIDType: string;
};
globals: {};
globalsSelect?: {};
locale: null;
user: User & {
collection: 'users';
};
}
export interface UserAuthOperations {
forgotPassword: {
email: string;
password: string;
};
login: {
email: string;
password: string;
};
registerFirstUser: {
email: string;
password: string;
};
unlock: {
email: string;
password: string;
};
}
/**
* This interface was referenced by \`Config\`'s JSON-Schema
* via the \`definition\` "posts".
*/
export interface Post {
id: string;
text?: string | null;
number?: number | null;
group?: {
text?: string | null;
number?: number | null;
};
array?:
| {
text?: string | null;
number?: number | null;
id?: string | null;
}[]
| null;
blocks?:
| (
| {
text?: string | null;
introText?: string | null;
id?: string | null;
blockName?: string | null;
blockType: 'intro';
}
| {
text?: string | null;
ctaText?: string | null;
id?: string | null;
blockName?: string | null;
blockType: 'cta';
}
)[]
| null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by \`Config\`'s JSON-Schema
* via the \`definition\` "users".
*/
export interface User {
id: string;
updatedAt: string;
createdAt: string;
email: string;
resetPasswordToken?: string | null;
resetPasswordExpiration?: string | null;
salt?: string | null;
hash?: string | null;
loginAttempts?: number | null;
lockUntil?: string | null;
password?: string | null;
}
/**
* This interface was referenced by \`Config\`'s JSON-Schema
* via the \`definition\` "payload-locked-documents".
*/
export interface PayloadLockedDocument {
id: string;
document?:
| ({
relationTo: 'posts';
value: string | Post;
} | null)
| ({
relationTo: 'users';
value: string | User;
} | null);
globalSlug?: string | null;
user: {
relationTo: 'users';
value: string | User;
};
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by \`Config\`'s JSON-Schema
* via the \`definition\` "payload-preferences".
*/
export interface PayloadPreference {
id: string;
user: {
relationTo: 'users';
value: string | User;
};
key?: string | null;
value?:
| {
[k: string]: unknown;
}
| unknown[]
| string
| number
| boolean
| null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by \`Config\`'s JSON-Schema
* via the \`definition\` "payload-migrations".
*/
export interface PayloadMigration {
id: string;
name?: string | null;
batch?: number | null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by \`Config\`'s JSON-Schema
* via the \`definition\` "posts_select".
*/
export interface PostsSelect {
text?: boolean;
number?: boolean;
group?:
| boolean
| {
text?: boolean;
number?: boolean;
};
array?:
| boolean
| {
text?: boolean;
number?: boolean;
id?: boolean;
};
blocks?:
| boolean
| {
intro?:
| boolean
| {
text?: boolean;
introText?: boolean;
id?: boolean;
blockName?: boolean;
};
cta?:
| boolean
| {
text?: boolean;
ctaText?: boolean;
id?: boolean;
blockName?: boolean;
};
};
updatedAt?: boolean;
createdAt?: boolean;
}
/**
* This interface was referenced by \`Config\`'s JSON-Schema
* via the \`definition\` "users_select".
*/
export interface UsersSelect {
updatedAt?: boolean;
createdAt?: boolean;
email?: boolean;
resetPasswordToken?: boolean;
resetPasswordExpiration?: boolean;
salt?: boolean;
hash?: boolean;
loginAttempts?: boolean;
lockUntil?: boolean;
}
/**
* This interface was referenced by \`Config\`'s JSON-Schema
* via the \`definition\` "payload-locked-documents_select".
*/
export interface PayloadLockedDocumentsSelect {
document?: boolean;
globalSlug?: boolean;
user?: boolean;
updatedAt?: boolean;
createdAt?: boolean;
}
/**
* This interface was referenced by \`Config\`'s JSON-Schema
* via the \`definition\` "payload-preferences_select".
*/
export interface PayloadPreferencesSelect {
user?: boolean;
key?: boolean;
value?: boolean;
updatedAt?: boolean;
createdAt?: boolean;
}
/**
* This interface was referenced by \`Config\`'s JSON-Schema
* via the \`definition\` "payload-migrations_select".
*/
export interface PayloadMigrationsSelect {
name?: boolean;
batch?: boolean;
updatedAt?: boolean;
createdAt?: boolean;
}
/**
* This interface was referenced by \`Config\`'s JSON-Schema
* via the \`definition\` "auth".
*/
export interface Auth {
[k: string]: unknown;
}
declare module 'payload' {
// @ts-ignore
export interface GeneratedTypes extends Config {}
}
`,
output: `
/* tslint:disable */
/* eslint-disable */
/**
* This file was automatically generated by Payload.
* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,
* and re-run \`payload generate:types\` to regenerate this file.
*/
export interface Config {
auth: {
users: UserAuthOperations;
};
collections: {
posts: Post;
users: User;
'payload-locked-documents': PayloadLockedDocument;
'payload-preferences': PayloadPreference;
'payload-migrations': PayloadMigration;
};
collectionsSelect?: {
posts: PostsSelect<false> | PostsSelect<true>;
users: UsersSelect<false> | UsersSelect<true>;
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
};
db: {
defaultIDType: string;
};
globals: {};
globalsSelect?: {};
locale: null;
user: User & {
collection: 'users';
};
}
export interface UserAuthOperations {
forgotPassword: {
email: string;
password: string;
};
login: {
email: string;
password: string;
};
registerFirstUser: {
email: string;
password: string;
};
unlock: {
email: string;
password: string;
};
}
/**
* This interface was referenced by \`Config\`'s JSON-Schema
* via the \`definition\` "posts".
*/
export interface Post {
id: string;
text?: string | null;
number?: number | null;
group?: {
text?: string | null;
number?: number | null;
};
array?:
| {
text?: string | null;
number?: number | null;
id?: string | null;
}[]
| null;
blocks?:
| (
| {
text?: string | null;
introText?: string | null;
id?: string | null;
blockName?: string | null;
blockType: 'intro';
}
| {
text?: string | null;
ctaText?: string | null;
id?: string | null;
blockName?: string | null;
blockType: 'cta';
}
)[]
| null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by \`Config\`'s JSON-Schema
* via the \`definition\` "users".
*/
export interface User {
id: string;
updatedAt: string;
createdAt: string;
email: string;
resetPasswordToken?: string | null;
resetPasswordExpiration?: string | null;
salt?: string | null;
hash?: string | null;
loginAttempts?: number | null;
lockUntil?: string | null;
password?: string | null;
}
/**
* This interface was referenced by \`Config\`'s JSON-Schema
* via the \`definition\` "payload-locked-documents".
*/
export interface PayloadLockedDocument {
id: string;
document?:
| ({
relationTo: 'posts';
value: string | Post;
} | null)
| ({
relationTo: 'users';
value: string | User;
} | null);
globalSlug?: string | null;
user: {
relationTo: 'users';
value: string | User;
};
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by \`Config\`'s JSON-Schema
* via the \`definition\` "payload-preferences".
*/
export interface PayloadPreference {
id: string;
user: {
relationTo: 'users';
value: string | User;
};
key?: string | null;
value?:
| {
[k: string]: unknown;
}
| unknown[]
| string
| number
| boolean
| null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by \`Config\`'s JSON-Schema
* via the \`definition\` "payload-migrations".
*/
export interface PayloadMigration {
id: string;
name?: string | null;
batch?: number | null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by \`Config\`'s JSON-Schema
* via the \`definition\` "posts_select".
*/
export interface PostsSelect<T extends boolean = true> {
text?: T;
number?: T;
group?:
| T
| {
text?: T;
number?: T;
};
array?:
| T
| {
text?: T;
number?: T;
id?: T;
};
blocks?:
| T
| {
intro?:
| T
| {
text?: T;
introText?: T;
id?: T;
blockName?: T;
};
cta?:
| T
| {
text?: T;
ctaText?: T;
id?: T;
blockName?: T;
};
};
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by \`Config\`'s JSON-Schema
* via the \`definition\` "users_select".
*/
export interface UsersSelect<T extends boolean = true> {
updatedAt?: T;
createdAt?: T;
email?: T;
resetPasswordToken?: T;
resetPasswordExpiration?: T;
salt?: T;
hash?: T;
loginAttempts?: T;
lockUntil?: T;
}
/**
* This interface was referenced by \`Config\`'s JSON-Schema
* via the \`definition\` "payload-locked-documents_select".
*/
export interface PayloadLockedDocumentsSelect<T extends boolean = true> {
document?: T;
globalSlug?: T;
user?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by \`Config\`'s JSON-Schema
* via the \`definition\` "payload-preferences_select".
*/
export interface PayloadPreferencesSelect<T extends boolean = true> {
user?: T;
key?: T;
value?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by \`Config\`'s JSON-Schema
* via the \`definition\` "payload-migrations_select".
*/
export interface PayloadMigrationsSelect<T extends boolean = true> {
name?: T;
batch?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by \`Config\`'s JSON-Schema
* via the \`definition\` "auth".
*/
export interface Auth {
[k: string]: unknown;
}
declare module 'payload' {
// @ts-ignore
export interface GeneratedTypes extends Config {}
}
`,
},
]
describe('addSelectGenericsToGeneratedTypes', () => {
it('should match return of given input with output', () => {
for (const { input, output } of INPUT_AND_OUTPUT) {
expect(
addSelectGenericsToGeneratedTypes({
compiledGeneratedTypes: input,
}),
).toStrictEqual(output)
}
})
})

View File

@@ -0,0 +1,51 @@
export const addSelectGenericsToGeneratedTypes = ({
compiledGeneratedTypes,
}: {
compiledGeneratedTypes: string
}) => {
const modifiedLines = []
let isCollectionsSelectToken = false
let isSelectTypeToken = false
for (const line of compiledGeneratedTypes.split('\n')) {
let newLine = line
if (line === ` collectionsSelect?: {` || line === ` globalsSelect?: {`) {
isCollectionsSelectToken = true
}
if (isCollectionsSelectToken) {
if (line === ' };') {
isCollectionsSelectToken = false
} else {
// replace <posts: PostsSelect;> with <posts: PostsSelect<true> | PostsSelect<false;>
newLine = line.replace(/(['"]?\w+['"]?):\s*(\w+);/g, (_, variable, type) => {
return `${variable}: ${type}<false> | ${type}<true>;`
})
}
}
// eslint-disable-next-line regexp/no-unused-capturing-group
if (line.match(/via the `definition` "([\w-]+_select)"/g)) {
isSelectTypeToken = true
}
if (isSelectTypeToken) {
if (line.startsWith('export interface')) {
// add generic to the interface
newLine = line.replace(/(export interface\s+\w+)(\s*\{)/g, '$1<T extends boolean = true>$2')
} else {
// replace booleans with T on the line
newLine = line.replace(/(?<!\?)\bboolean\b/g, 'T')
if (line === '}') {
isSelectTypeToken = false
}
}
}
modifiedLines.push(newLine)
}
return modifiedLines.join('\n')
}

View File

@@ -73,6 +73,25 @@ function generateEntitySchemas(
}
}
function generateEntitySelectSchemas(
entities: (SanitizedCollectionConfig | SanitizedGlobalConfig)[],
): JSONSchema4 {
const properties = [...entities].reduce((acc, { slug }) => {
acc[slug] = {
$ref: `#/definitions/${slug}_select`,
}
return acc
}, {})
return {
type: 'object',
additionalProperties: false,
properties,
required: Object.keys(properties),
}
}
function generateLocaleEntitySchemas(localization: SanitizedConfig['localization']): JSONSchema4 {
if (localization && 'locales' in localization && localization?.locales) {
const localesFromConfig = localization?.locales
@@ -633,6 +652,98 @@ export function entityToJSONSchema(
}
}
export function fieldsToSelectJSONSchema({ fields }: { fields: Field[] }): JSONSchema4 {
const schema: JSONSchema4 = {
type: 'object',
additionalProperties: false,
properties: {},
}
for (const field of fields) {
switch (field.type) {
case 'row':
case 'collapsible':
schema.properties = {
...schema.properties,
...fieldsToSelectJSONSchema({ fields: field.fields }).properties,
}
break
case 'array':
case 'group':
schema.properties[field.name] = {
oneOf: [
{
type: 'boolean',
},
fieldsToSelectJSONSchema({ fields: field.fields }),
],
}
break
case 'tabs':
for (const tab of field.tabs) {
if (tabHasName(tab)) {
schema.properties[tab.name] = {
oneOf: [
{
type: 'boolean',
},
fieldsToSelectJSONSchema({ fields: tab.fields }),
],
}
continue
}
schema.properties = {
...schema.properties,
...fieldsToSelectJSONSchema({ fields: tab.fields }).properties,
}
}
break
case 'blocks': {
const blocksSchema: JSONSchema4 = {
type: 'object',
additionalProperties: false,
properties: {},
}
for (const block of field.blocks) {
blocksSchema.properties[block.slug] = {
oneOf: [
{
type: 'boolean',
},
fieldsToSelectJSONSchema({ fields: block.fields }),
],
}
}
schema.properties[field.name] = {
oneOf: [
{
type: 'boolean',
},
blocksSchema,
],
}
break
}
default:
schema.properties[field.name] = {
type: 'boolean',
}
break
}
}
return schema
}
const fieldType: JSONSchema4 = {
type: 'string',
required: false,
@@ -803,13 +914,39 @@ export function configToJSONSchema(
// Collections and Globals have to be moved to the top-level definitions as well. Reason: The top-level type will be the `Config` type - we don't want all collection and global
// types to be inlined inside the `Config` type
const entityDefinitions: { [k: string]: JSONSchema4 } = [
...config.globals,
...config.collections,
].reduce((acc, entity) => {
const entities: {
entity: SanitizedCollectionConfig | SanitizedGlobalConfig
type: 'collection' | 'global'
}[] = [
...config.globals.map((global) => ({ type: 'global' as const, entity: global })),
...config.collections.map((collection) => ({
type: 'collection' as const,
entity: collection,
})),
]
const entityDefinitions: { [k: string]: JSONSchema4 } = entities.reduce(
(acc, { type, entity }) => {
acc[entity.slug] = entityToJSONSchema(config, entity, interfaceNameDefinitions, defaultIDType)
const select = fieldsToSelectJSONSchema({ fields: entity.fields })
if (type === 'global') {
select.properties.globalType = {
type: 'boolean',
}
}
acc[`${entity.slug}_select`] = {
type: 'object',
additionalProperties: false,
...select,
}
return acc
}, {})
},
{},
)
const authOperationDefinitions = [...config.collections]
.filter(({ auth }) => Boolean(auth))
@@ -833,8 +970,10 @@ export function configToJSONSchema(
properties: {
auth: generateAuthOperationSchemas(config.collections),
collections: generateEntitySchemas(config.collections || []),
collectionsSelect: generateEntitySelectSchemas(config.collections || []),
db: generateDbEntitySchema(config),
globals: generateEntitySchemas(config.globals || []),
globalsSelect: generateEntitySelectSchemas(config.globals || []),
locale: generateLocaleEntitySchemas(config.localization),
user: generateAuthEntitySchemas(config.collections),
},

View File

@@ -0,0 +1,16 @@
import type { SelectMode, SelectType } from '../types/index.js'
export const getSelectMode = (select: SelectType): SelectMode => {
for (const key in select) {
const selectValue = select[key]
if (selectValue === false) {
return 'exclude'
}
if (typeof selectValue === 'object') {
return getSelectMode(selectValue)
}
}
return 'include'
}

View File

@@ -0,0 +1,26 @@
import type { SelectType } from '../../types/index.js'
import { getSelectMode } from '../../utilities/getSelectMode.js'
export const getQueryDraftsSelect = ({
select,
}: {
select?: SelectType
}): SelectType | undefined => {
if (!select) {
return
}
const mode = getSelectMode(select)
if (mode === 'include') {
return {
parent: true,
version: select,
} as SelectType
}
return {
version: select,
} as SelectType
}

Some files were not shown because too many files have changed in this diff Show More