perf(db-mongodb): improve performance of all operations, up to 50% faster (#9594)
This PR improves speed and memory efficiency across all operations with the Mongoose adapter. ### How? - Removes Mongoose layer from all database calls, instead uses MongoDB directly. (this doesn't remove building mongoose schema since it's still needed for indexes + users in theory can use it) - Replaces deep copying of read results using `JSON.parse(JSON.stringify(data))` with the `transform` `operation: 'read'` function which converts Date's, ObjectID's in relationships / joins to strings. As before, it also handles transformations for write operations. - Faster `hasNearConstraint` for potentially large `where`'s - `traverseFields` now can accept `flattenedFields` which we use in `transform`. Less recursive calls with tabs/rows/collapsible Additional fixes - Uses current transaction for querying nested relationships properties in `buildQuery`, previously it wasn't used which could've led to wrong results - Allows to clear not required point fields with passing `null` from the Local API. Previously it didn't work in both, MongoDB and Postgres Benchmarks using this file https://github.com/payloadcms/payload/blob/chore/db-benchmark/test/_community/int.spec.ts ### Small Dataset Performance | Metric | Before Optimization | After Optimization | Improvement (%) | |---------------------------|---------------------|--------------------|-----------------| | Average FULL (ms) | 1170 | 844 | 27.86% | | `payload.db.create` (ms) | 1413 | 691 | 51.12% | | `payload.db.find` (ms) | 2856 | 2204 | 22.83% | | `payload.db.deleteMany` (ms) | 15206 | 8439 | 44.53% | | `payload.db.updateOne` (ms) | 21444 | 12162 | 43.30% | | `payload.db.findOne` (ms) | 159 | 112 | 29.56% | | `payload.db.deleteOne` (ms) | 3729 | 2578 | 30.89% | | DB small FULL (ms) | 64473 | 46451 | 27.93% | --- ### Medium Dataset Performance | Metric | Before Optimization | After Optimization | Improvement (%) | |---------------------------|---------------------|--------------------|-----------------| | Average FULL (ms) | 9407 | 6210 | 33.99% | | `payload.db.create` (ms) | 10270 | 4321 | 57.93% | | `payload.db.find` (ms) | 20814 | 16036 | 22.93% | | `payload.db.deleteMany` (ms) | 126351 | 61789 | 51.11% | | `payload.db.updateOne` (ms) | 201782 | 99943 | 50.49% | | `payload.db.findOne` (ms) | 1081 | 817 | 24.43% | | `payload.db.deleteOne` (ms) | 28534 | 23363 | 18.12% | | DB medium FULL (ms) | 519518 | 342194 | 34.13% | --- ### Large Dataset Performance | Metric | Before Optimization | After Optimization | Improvement (%) | |---------------------------|---------------------|--------------------|-----------------| | Average FULL (ms) | 26575 | 17509 | 34.14% | | `payload.db.create` (ms) | 29085 | 12196 | 58.08% | | `payload.db.find` (ms) | 58497 | 43838 | 25.04% | | `payload.db.deleteMany` (ms) | 372195 | 173218 | 53.47% | | `payload.db.updateOne` (ms) | 544089 | 288350 | 47.00% | | `payload.db.findOne` (ms) | 3058 | 2197 | 28.14% | | `payload.db.deleteOne` (ms) | 82444 | 64730 | 21.49% | | DB large FULL (ms) | 1461097 | 969714 | 33.62% |
This commit is contained in:
@@ -1,10 +1,9 @@
|
|||||||
import type { CountOptions } from 'mongodb'
|
import type { CountOptions } from 'mongodb'
|
||||||
import type { Count } from 'payload'
|
import type { Count } from 'payload'
|
||||||
|
|
||||||
import { flattenWhereToOperators } from 'payload'
|
|
||||||
|
|
||||||
import type { MongooseAdapter } from './index.js'
|
import type { MongooseAdapter } from './index.js'
|
||||||
|
|
||||||
|
import { getHasNearConstraint } from './utilities/getHasNearConstraint.js'
|
||||||
import { getSession } from './utilities/getSession.js'
|
import { getSession } from './utilities/getSession.js'
|
||||||
|
|
||||||
export const count: Count = async function count(
|
export const count: Count = async function count(
|
||||||
@@ -12,41 +11,37 @@ export const count: Count = async function count(
|
|||||||
{ collection, locale, req, where },
|
{ collection, locale, req, where },
|
||||||
) {
|
) {
|
||||||
const Model = this.collections[collection]
|
const Model = this.collections[collection]
|
||||||
const options: CountOptions = {
|
const session = await getSession(this, req)
|
||||||
session: await getSession(this, req),
|
|
||||||
}
|
|
||||||
|
|
||||||
let hasNearConstraint = false
|
const hasNearConstraint = getHasNearConstraint(where)
|
||||||
|
|
||||||
if (where) {
|
|
||||||
const constraints = flattenWhereToOperators(where)
|
|
||||||
hasNearConstraint = constraints.some((prop) => Object.keys(prop).some((key) => key === 'near'))
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = await Model.buildQuery({
|
const query = await Model.buildQuery({
|
||||||
locale,
|
locale,
|
||||||
payload: this.payload,
|
payload: this.payload,
|
||||||
|
session,
|
||||||
where,
|
where,
|
||||||
})
|
})
|
||||||
|
|
||||||
// useEstimatedCount is faster, but not accurate, as it ignores any filters. It is thus set to true if there are no filters.
|
// useEstimatedCount is faster, but not accurate, as it ignores any filters. It is thus set to true if there are no filters.
|
||||||
const useEstimatedCount = hasNearConstraint || !query || Object.keys(query).length === 0
|
const useEstimatedCount = hasNearConstraint || !query || Object.keys(query).length === 0
|
||||||
|
|
||||||
if (!useEstimatedCount && Object.keys(query).length === 0 && this.disableIndexHints !== true) {
|
|
||||||
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
|
|
||||||
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
|
|
||||||
// which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses
|
|
||||||
// the correct indexed field
|
|
||||||
options.hint = {
|
|
||||||
_id: 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let result: number
|
let result: number
|
||||||
if (useEstimatedCount) {
|
if (useEstimatedCount) {
|
||||||
result = await Model.estimatedDocumentCount({ session: options.session })
|
result = await Model.collection.estimatedDocumentCount()
|
||||||
} else {
|
} else {
|
||||||
result = await Model.countDocuments(query, options)
|
const options: CountOptions = { session }
|
||||||
|
|
||||||
|
if (this.disableIndexHints !== true) {
|
||||||
|
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
|
||||||
|
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
|
||||||
|
// which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses
|
||||||
|
// the correct indexed field
|
||||||
|
options.hint = {
|
||||||
|
_id: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = await Model.collection.countDocuments(query, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import type { CountOptions } from 'mongodb'
|
import type { CountOptions } from 'mongodb'
|
||||||
import type { CountGlobalVersions } from 'payload'
|
import type { CountGlobalVersions } from 'payload'
|
||||||
|
|
||||||
import { flattenWhereToOperators } from 'payload'
|
|
||||||
|
|
||||||
import type { MongooseAdapter } from './index.js'
|
import type { MongooseAdapter } from './index.js'
|
||||||
|
|
||||||
|
import { getHasNearConstraint } from './utilities/getHasNearConstraint.js'
|
||||||
import { getSession } from './utilities/getSession.js'
|
import { getSession } from './utilities/getSession.js'
|
||||||
|
|
||||||
export const countGlobalVersions: CountGlobalVersions = async function countGlobalVersions(
|
export const countGlobalVersions: CountGlobalVersions = async function countGlobalVersions(
|
||||||
@@ -12,41 +11,37 @@ export const countGlobalVersions: CountGlobalVersions = async function countGlob
|
|||||||
{ global, locale, req, where },
|
{ global, locale, req, where },
|
||||||
) {
|
) {
|
||||||
const Model = this.versions[global]
|
const Model = this.versions[global]
|
||||||
const options: CountOptions = {
|
const session = await getSession(this, req)
|
||||||
session: await getSession(this, req),
|
|
||||||
}
|
|
||||||
|
|
||||||
let hasNearConstraint = false
|
const hasNearConstraint = getHasNearConstraint(where)
|
||||||
|
|
||||||
if (where) {
|
|
||||||
const constraints = flattenWhereToOperators(where)
|
|
||||||
hasNearConstraint = constraints.some((prop) => Object.keys(prop).some((key) => key === 'near'))
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = await Model.buildQuery({
|
const query = await Model.buildQuery({
|
||||||
locale,
|
locale,
|
||||||
payload: this.payload,
|
payload: this.payload,
|
||||||
|
session,
|
||||||
where,
|
where,
|
||||||
})
|
})
|
||||||
|
|
||||||
// useEstimatedCount is faster, but not accurate, as it ignores any filters. It is thus set to true if there are no filters.
|
// useEstimatedCount is faster, but not accurate, as it ignores any filters. It is thus set to true if there are no filters.
|
||||||
const useEstimatedCount = hasNearConstraint || !query || Object.keys(query).length === 0
|
const useEstimatedCount = hasNearConstraint || !query || Object.keys(query).length === 0
|
||||||
|
|
||||||
if (!useEstimatedCount && Object.keys(query).length === 0 && this.disableIndexHints !== true) {
|
|
||||||
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
|
|
||||||
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
|
|
||||||
// which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses
|
|
||||||
// the correct indexed field
|
|
||||||
options.hint = {
|
|
||||||
_id: 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let result: number
|
let result: number
|
||||||
if (useEstimatedCount) {
|
if (useEstimatedCount) {
|
||||||
result = await Model.estimatedDocumentCount({ session: options.session })
|
result = await Model.collection.estimatedDocumentCount()
|
||||||
} else {
|
} else {
|
||||||
result = await Model.countDocuments(query, options)
|
const options: CountOptions = { session }
|
||||||
|
|
||||||
|
if (Object.keys(query).length === 0 && this.disableIndexHints !== true) {
|
||||||
|
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
|
||||||
|
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
|
||||||
|
// which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses
|
||||||
|
// the correct indexed field
|
||||||
|
options.hint = {
|
||||||
|
_id: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = await Model.collection.countDocuments(query, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import type { CountOptions } from 'mongodb'
|
import type { CountOptions } from 'mongodb'
|
||||||
import type { CountVersions } from 'payload'
|
import type { CountVersions } from 'payload'
|
||||||
|
|
||||||
import { flattenWhereToOperators } from 'payload'
|
|
||||||
|
|
||||||
import type { MongooseAdapter } from './index.js'
|
import type { MongooseAdapter } from './index.js'
|
||||||
|
|
||||||
|
import { getHasNearConstraint } from './utilities/getHasNearConstraint.js'
|
||||||
import { getSession } from './utilities/getSession.js'
|
import { getSession } from './utilities/getSession.js'
|
||||||
|
|
||||||
export const countVersions: CountVersions = async function countVersions(
|
export const countVersions: CountVersions = async function countVersions(
|
||||||
@@ -12,41 +11,37 @@ export const countVersions: CountVersions = async function countVersions(
|
|||||||
{ collection, locale, req, where },
|
{ collection, locale, req, where },
|
||||||
) {
|
) {
|
||||||
const Model = this.versions[collection]
|
const Model = this.versions[collection]
|
||||||
const options: CountOptions = {
|
const session = await getSession(this, req)
|
||||||
session: await getSession(this, req),
|
|
||||||
}
|
|
||||||
|
|
||||||
let hasNearConstraint = false
|
const hasNearConstraint = getHasNearConstraint(where)
|
||||||
|
|
||||||
if (where) {
|
|
||||||
const constraints = flattenWhereToOperators(where)
|
|
||||||
hasNearConstraint = constraints.some((prop) => Object.keys(prop).some((key) => key === 'near'))
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = await Model.buildQuery({
|
const query = await Model.buildQuery({
|
||||||
locale,
|
locale,
|
||||||
payload: this.payload,
|
payload: this.payload,
|
||||||
|
session,
|
||||||
where,
|
where,
|
||||||
})
|
})
|
||||||
|
|
||||||
// useEstimatedCount is faster, but not accurate, as it ignores any filters. It is thus set to true if there are no filters.
|
// useEstimatedCount is faster, but not accurate, as it ignores any filters. It is thus set to true if there are no filters.
|
||||||
const useEstimatedCount = hasNearConstraint || !query || Object.keys(query).length === 0
|
const useEstimatedCount = hasNearConstraint || !query || Object.keys(query).length === 0
|
||||||
|
|
||||||
if (!useEstimatedCount && Object.keys(query).length === 0 && this.disableIndexHints !== true) {
|
|
||||||
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
|
|
||||||
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
|
|
||||||
// which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses
|
|
||||||
// the correct indexed field
|
|
||||||
options.hint = {
|
|
||||||
_id: 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let result: number
|
let result: number
|
||||||
if (useEstimatedCount) {
|
if (useEstimatedCount) {
|
||||||
result = await Model.estimatedDocumentCount({ session: options.session })
|
result = await Model.collection.estimatedDocumentCount()
|
||||||
} else {
|
} else {
|
||||||
result = await Model.countDocuments(query, options)
|
const options: CountOptions = { session }
|
||||||
|
|
||||||
|
if (this.disableIndexHints !== true) {
|
||||||
|
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
|
||||||
|
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
|
||||||
|
// which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses
|
||||||
|
// the correct indexed field
|
||||||
|
options.hint = {
|
||||||
|
_id: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = await Model.collection.countDocuments(query, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,48 +1,44 @@
|
|||||||
import type { CreateOptions } from 'mongoose'
|
import type { Create } from 'payload'
|
||||||
import type { Create, Document } from 'payload'
|
|
||||||
|
|
||||||
import type { MongooseAdapter } from './index.js'
|
import type { MongooseAdapter } from './index.js'
|
||||||
|
|
||||||
import { getSession } from './utilities/getSession.js'
|
import { getSession } from './utilities/getSession.js'
|
||||||
import { handleError } from './utilities/handleError.js'
|
import { handleError } from './utilities/handleError.js'
|
||||||
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'
|
import { transform } from './utilities/transform.js'
|
||||||
|
|
||||||
export const create: Create = async function create(
|
export const create: Create = async function create(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
{ collection, data, req },
|
{ collection, data, req },
|
||||||
) {
|
) {
|
||||||
const Model = this.collections[collection]
|
const Model = this.collections[collection]
|
||||||
const options: CreateOptions = {
|
const session = await getSession(this, req)
|
||||||
session: await getSession(this, req),
|
|
||||||
}
|
|
||||||
|
|
||||||
let doc
|
const fields = this.payload.collections[collection].config.flattenedFields
|
||||||
|
|
||||||
const sanitizedData = sanitizeRelationshipIDs({
|
|
||||||
config: this.payload.config,
|
|
||||||
data,
|
|
||||||
fields: this.payload.collections[collection].config.fields,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (this.payload.collections[collection].customIDType) {
|
if (this.payload.collections[collection].customIDType) {
|
||||||
sanitizedData._id = sanitizedData.id
|
data._id = data.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transform({
|
||||||
|
adapter: this,
|
||||||
|
data,
|
||||||
|
fields,
|
||||||
|
operation: 'create',
|
||||||
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
;[doc] = await Model.create([sanitizedData], options)
|
const { insertedId } = await Model.collection.insertOne(data, { session })
|
||||||
|
data._id = insertedId
|
||||||
|
|
||||||
|
transform({
|
||||||
|
adapter: this,
|
||||||
|
data,
|
||||||
|
fields,
|
||||||
|
operation: 'read',
|
||||||
|
})
|
||||||
|
|
||||||
|
return data
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError({ collection, error, req })
|
handleError({ collection, error, req })
|
||||||
}
|
}
|
||||||
|
|
||||||
// doc.toJSON does not do stuff like converting ObjectIds to string, or date strings to date objects. That's why we use JSON.parse/stringify here
|
|
||||||
const result: Document = JSON.parse(JSON.stringify(doc))
|
|
||||||
const verificationToken = doc._verificationToken
|
|
||||||
|
|
||||||
// custom id type reset
|
|
||||||
result.id = result._id
|
|
||||||
if (verificationToken) {
|
|
||||||
result._verificationToken = verificationToken
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import type { CreateOptions } from 'mongoose'
|
|
||||||
import type { CreateGlobal } from 'payload'
|
import type { CreateGlobal } from 'payload'
|
||||||
|
|
||||||
import type { MongooseAdapter } from './index.js'
|
import type { MongooseAdapter } from './index.js'
|
||||||
|
|
||||||
import { getSession } from './utilities/getSession.js'
|
import { getSession } from './utilities/getSession.js'
|
||||||
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
import { transform } from './utilities/transform.js'
|
||||||
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'
|
|
||||||
|
|
||||||
export const createGlobal: CreateGlobal = async function createGlobal(
|
export const createGlobal: CreateGlobal = async function createGlobal(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
@@ -13,26 +11,29 @@ export const createGlobal: CreateGlobal = async function createGlobal(
|
|||||||
) {
|
) {
|
||||||
const Model = this.globals
|
const Model = this.globals
|
||||||
|
|
||||||
const global = sanitizeRelationshipIDs({
|
const fields = this.payload.config.globals.find(
|
||||||
config: this.payload.config,
|
(globalConfig) => globalConfig.slug === slug,
|
||||||
data: {
|
).flattenedFields
|
||||||
globalType: slug,
|
|
||||||
...data,
|
transform({
|
||||||
},
|
adapter: this,
|
||||||
fields: this.payload.config.globals.find((globalConfig) => globalConfig.slug === slug).fields,
|
data,
|
||||||
|
fields,
|
||||||
|
globalSlug: slug,
|
||||||
|
operation: 'create',
|
||||||
})
|
})
|
||||||
|
|
||||||
const options: CreateOptions = {
|
const session = await getSession(this, req)
|
||||||
session: await getSession(this, req),
|
|
||||||
}
|
|
||||||
|
|
||||||
let [result] = (await Model.create([global], options)) as any
|
const { insertedId } = await Model.collection.insertOne(data, { session })
|
||||||
|
;(data as any)._id = insertedId
|
||||||
|
|
||||||
result = JSON.parse(JSON.stringify(result))
|
transform({
|
||||||
|
adapter: this,
|
||||||
|
data,
|
||||||
|
fields,
|
||||||
|
operation: 'read',
|
||||||
|
})
|
||||||
|
|
||||||
// custom id type reset
|
return data
|
||||||
result.id = result._id
|
|
||||||
result = sanitizeInternalFields(result)
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import type { CreateOptions } from 'mongoose'
|
import { buildVersionGlobalFields, type CreateGlobalVersion } from 'payload'
|
||||||
|
|
||||||
import { buildVersionGlobalFields, type CreateGlobalVersion, type Document } from 'payload'
|
|
||||||
|
|
||||||
import type { MongooseAdapter } from './index.js'
|
import type { MongooseAdapter } from './index.js'
|
||||||
|
|
||||||
import { getSession } from './utilities/getSession.js'
|
import { getSession } from './utilities/getSession.js'
|
||||||
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'
|
import { transform } from './utilities/transform.js'
|
||||||
|
|
||||||
export const createGlobalVersion: CreateGlobalVersion = async function createGlobalVersion(
|
export const createGlobalVersion: CreateGlobalVersion = async function createGlobalVersion(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
@@ -22,36 +20,41 @@ export const createGlobalVersion: CreateGlobalVersion = async function createGlo
|
|||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const VersionModel = this.versions[globalSlug]
|
const VersionModel = this.versions[globalSlug]
|
||||||
const options: CreateOptions = {
|
const session = await getSession(this, req)
|
||||||
session: await getSession(this, req),
|
|
||||||
|
const data = {
|
||||||
|
autosave,
|
||||||
|
createdAt,
|
||||||
|
latest: true,
|
||||||
|
parent,
|
||||||
|
publishedLocale,
|
||||||
|
snapshot,
|
||||||
|
updatedAt,
|
||||||
|
version: versionData,
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = sanitizeRelationshipIDs({
|
const fields = buildVersionGlobalFields(
|
||||||
config: this.payload.config,
|
this.payload.config,
|
||||||
data: {
|
this.payload.config.globals.find((global) => global.slug === globalSlug),
|
||||||
autosave,
|
true,
|
||||||
createdAt,
|
)
|
||||||
latest: true,
|
|
||||||
parent,
|
transform({
|
||||||
publishedLocale,
|
adapter: this,
|
||||||
snapshot,
|
data,
|
||||||
updatedAt,
|
fields,
|
||||||
version: versionData,
|
operation: 'create',
|
||||||
},
|
|
||||||
fields: buildVersionGlobalFields(
|
|
||||||
this.payload.config,
|
|
||||||
this.payload.config.globals.find((global) => global.slug === globalSlug),
|
|
||||||
),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const [doc] = await VersionModel.create([data], options, req)
|
const { insertedId } = await VersionModel.collection.insertOne(data, { session })
|
||||||
|
;(data as any)._id = insertedId
|
||||||
|
|
||||||
await VersionModel.updateMany(
|
await VersionModel.collection.updateMany(
|
||||||
{
|
{
|
||||||
$and: [
|
$and: [
|
||||||
{
|
{
|
||||||
_id: {
|
_id: {
|
||||||
$ne: doc._id,
|
$ne: insertedId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -67,16 +70,15 @@ export const createGlobalVersion: CreateGlobalVersion = async function createGlo
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{ $unset: { latest: 1 } },
|
{ $unset: { latest: 1 } },
|
||||||
options,
|
{ session },
|
||||||
)
|
)
|
||||||
|
|
||||||
const result: Document = JSON.parse(JSON.stringify(doc))
|
transform({
|
||||||
const verificationToken = doc._verificationToken
|
adapter: this,
|
||||||
|
data,
|
||||||
|
fields,
|
||||||
|
operation: 'read',
|
||||||
|
})
|
||||||
|
|
||||||
// custom id type reset
|
return data as any
|
||||||
result.id = result._id
|
|
||||||
if (verificationToken) {
|
|
||||||
result._verificationToken = verificationToken
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import type { CreateOptions } from 'mongoose'
|
|
||||||
|
|
||||||
import { Types } from 'mongoose'
|
import { Types } from 'mongoose'
|
||||||
import { buildVersionCollectionFields, type CreateVersion, type Document } from 'payload'
|
import { buildVersionCollectionFields, type CreateVersion } from 'payload'
|
||||||
|
|
||||||
import type { MongooseAdapter } from './index.js'
|
import type { MongooseAdapter } from './index.js'
|
||||||
|
|
||||||
import { getSession } from './utilities/getSession.js'
|
import { getSession } from './utilities/getSession.js'
|
||||||
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'
|
import { transform } from './utilities/transform.js'
|
||||||
|
|
||||||
export const createVersion: CreateVersion = async function createVersion(
|
export const createVersion: CreateVersion = async function createVersion(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
@@ -23,29 +21,34 @@ export const createVersion: CreateVersion = async function createVersion(
|
|||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const VersionModel = this.versions[collectionSlug]
|
const VersionModel = this.versions[collectionSlug]
|
||||||
const options: CreateOptions = {
|
const session = await getSession(this, req)
|
||||||
session: await getSession(this, req),
|
|
||||||
|
const data: any = {
|
||||||
|
autosave,
|
||||||
|
createdAt,
|
||||||
|
latest: true,
|
||||||
|
parent,
|
||||||
|
publishedLocale,
|
||||||
|
snapshot,
|
||||||
|
updatedAt,
|
||||||
|
version: versionData,
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = sanitizeRelationshipIDs({
|
const fields = buildVersionCollectionFields(
|
||||||
config: this.payload.config,
|
this.payload.config,
|
||||||
data: {
|
this.payload.collections[collectionSlug].config,
|
||||||
autosave,
|
true,
|
||||||
createdAt,
|
)
|
||||||
latest: true,
|
|
||||||
parent,
|
transform({
|
||||||
publishedLocale,
|
adapter: this,
|
||||||
snapshot,
|
data,
|
||||||
updatedAt,
|
fields,
|
||||||
version: versionData,
|
operation: 'create',
|
||||||
},
|
|
||||||
fields: buildVersionCollectionFields(
|
|
||||||
this.payload.config,
|
|
||||||
this.payload.collections[collectionSlug].config,
|
|
||||||
),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const [doc] = await VersionModel.create([data], options, req)
|
const { insertedId } = await VersionModel.collection.insertOne(data, { session })
|
||||||
|
data._id = insertedId
|
||||||
|
|
||||||
const parentQuery = {
|
const parentQuery = {
|
||||||
$or: [
|
$or: [
|
||||||
@@ -56,7 +59,7 @@ export const createVersion: CreateVersion = async function createVersion(
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
if (data.parent instanceof Types.ObjectId) {
|
if ((data.parent as unknown) instanceof Types.ObjectId) {
|
||||||
parentQuery.$or.push({
|
parentQuery.$or.push({
|
||||||
parent: {
|
parent: {
|
||||||
$eq: data.parent.toString(),
|
$eq: data.parent.toString(),
|
||||||
@@ -64,12 +67,12 @@ export const createVersion: CreateVersion = async function createVersion(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
await VersionModel.updateMany(
|
await VersionModel.collection.updateMany(
|
||||||
{
|
{
|
||||||
$and: [
|
$and: [
|
||||||
{
|
{
|
||||||
_id: {
|
_id: {
|
||||||
$ne: doc._id,
|
$ne: insertedId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
parentQuery,
|
parentQuery,
|
||||||
@@ -80,22 +83,21 @@ export const createVersion: CreateVersion = async function createVersion(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
updatedAt: {
|
updatedAt: {
|
||||||
$lt: new Date(doc.updatedAt),
|
$lt: new Date(data.updatedAt),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{ $unset: { latest: 1 } },
|
{ $unset: { latest: 1 } },
|
||||||
options,
|
{ session },
|
||||||
)
|
)
|
||||||
|
|
||||||
const result: Document = JSON.parse(JSON.stringify(doc))
|
transform({
|
||||||
const verificationToken = doc._verificationToken
|
adapter: this,
|
||||||
|
data,
|
||||||
|
fields,
|
||||||
|
operation: 'read',
|
||||||
|
})
|
||||||
|
|
||||||
// custom id type reset
|
return data
|
||||||
result.id = result._id
|
|
||||||
if (verificationToken) {
|
|
||||||
result._verificationToken = verificationToken
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import type { DeleteOptions } from 'mongodb'
|
|
||||||
import type { DeleteMany } from 'payload'
|
import type { DeleteMany } from 'payload'
|
||||||
|
|
||||||
import type { MongooseAdapter } from './index.js'
|
import type { MongooseAdapter } from './index.js'
|
||||||
@@ -10,14 +9,16 @@ export const deleteMany: DeleteMany = async function deleteMany(
|
|||||||
{ collection, req, where },
|
{ collection, req, where },
|
||||||
) {
|
) {
|
||||||
const Model = this.collections[collection]
|
const Model = this.collections[collection]
|
||||||
const options: DeleteOptions = {
|
|
||||||
session: await getSession(this, req),
|
const session = await getSession(this, req)
|
||||||
}
|
|
||||||
|
|
||||||
const query = await Model.buildQuery({
|
const query = await Model.buildQuery({
|
||||||
payload: this.payload,
|
payload: this.payload,
|
||||||
|
session,
|
||||||
where,
|
where,
|
||||||
})
|
})
|
||||||
|
|
||||||
await Model.deleteMany(query, options)
|
await Model.collection.deleteMany(query, {
|
||||||
|
session,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,41 @@
|
|||||||
import type { QueryOptions } from 'mongoose'
|
import type { DeleteOne } from 'payload'
|
||||||
import type { DeleteOne, Document } from 'payload'
|
|
||||||
|
|
||||||
import type { MongooseAdapter } from './index.js'
|
import type { MongooseAdapter } from './index.js'
|
||||||
|
|
||||||
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
||||||
import { getSession } from './utilities/getSession.js'
|
import { getSession } from './utilities/getSession.js'
|
||||||
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
import { transform } from './utilities/transform.js'
|
||||||
|
|
||||||
export const deleteOne: DeleteOne = async function deleteOne(
|
export const deleteOne: DeleteOne = async function deleteOne(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
{ collection, req, select, where },
|
{ collection, req, select, where },
|
||||||
) {
|
) {
|
||||||
const Model = this.collections[collection]
|
const Model = this.collections[collection]
|
||||||
const options: QueryOptions = {
|
const session = await getSession(this, req)
|
||||||
projection: buildProjectionFromSelect({
|
|
||||||
adapter: this,
|
|
||||||
fields: this.payload.collections[collection].config.flattenedFields,
|
|
||||||
select,
|
|
||||||
}),
|
|
||||||
session: await getSession(this, req),
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = await Model.buildQuery({
|
const query = await Model.buildQuery({
|
||||||
payload: this.payload,
|
payload: this.payload,
|
||||||
|
session,
|
||||||
where,
|
where,
|
||||||
})
|
})
|
||||||
|
|
||||||
const doc = await Model.findOneAndDelete(query, options).lean()
|
const fields = this.payload.collections[collection].config.flattenedFields
|
||||||
|
|
||||||
let result: Document = JSON.parse(JSON.stringify(doc))
|
const doc = await Model.collection.findOneAndDelete(query, {
|
||||||
|
projection: buildProjectionFromSelect({
|
||||||
|
adapter: this,
|
||||||
|
fields,
|
||||||
|
select,
|
||||||
|
}),
|
||||||
|
session,
|
||||||
|
})
|
||||||
|
|
||||||
// custom id type reset
|
transform({
|
||||||
result.id = result._id
|
adapter: this,
|
||||||
result = sanitizeInternalFields(result)
|
data: doc,
|
||||||
|
fields,
|
||||||
|
operation: 'read',
|
||||||
|
})
|
||||||
|
|
||||||
return result
|
return doc
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,11 @@ export const deleteVersions: DeleteVersions = async function deleteVersions(
|
|||||||
const query = await VersionsModel.buildQuery({
|
const query = await VersionsModel.buildQuery({
|
||||||
locale,
|
locale,
|
||||||
payload: this.payload,
|
payload: this.payload,
|
||||||
|
session,
|
||||||
where,
|
where,
|
||||||
})
|
})
|
||||||
|
|
||||||
await VersionsModel.deleteMany(query, { session })
|
await VersionsModel.collection.deleteMany(query, {
|
||||||
|
session,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import type { PaginateOptions } from 'mongoose'
|
import type { CollationOptions } from 'mongodb'
|
||||||
import type { Find } from 'payload'
|
import type { Find } from 'payload'
|
||||||
|
|
||||||
import { flattenWhereToOperators } from 'payload'
|
|
||||||
|
|
||||||
import type { MongooseAdapter } from './index.js'
|
import type { MongooseAdapter } from './index.js'
|
||||||
|
|
||||||
import { buildSortParam } from './queries/buildSortParam.js'
|
import { buildSortParam } from './queries/buildSortParam.js'
|
||||||
import { buildJoinAggregation } from './utilities/buildJoinAggregation.js'
|
import { buildJoinAggregation } from './utilities/buildJoinAggregation.js'
|
||||||
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
||||||
|
import { findMany } from './utilities/findMany.js'
|
||||||
|
import { getHasNearConstraint } from './utilities/getHasNearConstraint.js'
|
||||||
import { getSession } from './utilities/getSession.js'
|
import { getSession } from './utilities/getSession.js'
|
||||||
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
import { transform } from './utilities/transform.js'
|
||||||
|
|
||||||
export const find: Find = async function find(
|
export const find: Find = async function find(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
@@ -20,7 +20,6 @@ export const find: Find = async function find(
|
|||||||
locale,
|
locale,
|
||||||
page,
|
page,
|
||||||
pagination,
|
pagination,
|
||||||
projection,
|
|
||||||
req,
|
req,
|
||||||
select,
|
select,
|
||||||
sort: sortArg,
|
sort: sortArg,
|
||||||
@@ -29,21 +28,17 @@ export const find: Find = async function find(
|
|||||||
) {
|
) {
|
||||||
const Model = this.collections[collection]
|
const Model = this.collections[collection]
|
||||||
const collectionConfig = this.payload.collections[collection].config
|
const collectionConfig = this.payload.collections[collection].config
|
||||||
|
|
||||||
const session = await getSession(this, req)
|
const session = await getSession(this, req)
|
||||||
|
|
||||||
let hasNearConstraint = false
|
const hasNearConstraint = getHasNearConstraint(where)
|
||||||
|
|
||||||
if (where) {
|
const fields = collectionConfig.flattenedFields
|
||||||
const constraints = flattenWhereToOperators(where)
|
|
||||||
hasNearConstraint = constraints.some((prop) => Object.keys(prop).some((key) => key === 'near'))
|
|
||||||
}
|
|
||||||
|
|
||||||
let sort
|
let sort
|
||||||
if (!hasNearConstraint) {
|
if (!hasNearConstraint) {
|
||||||
sort = buildSortParam({
|
sort = buildSortParam({
|
||||||
config: this.payload.config,
|
config: this.payload.config,
|
||||||
fields: collectionConfig.flattenedFields,
|
fields,
|
||||||
locale,
|
locale,
|
||||||
sort: sortArg || collectionConfig.defaultSort,
|
sort: sortArg || collectionConfig.defaultSort,
|
||||||
timestamps: true,
|
timestamps: true,
|
||||||
@@ -53,90 +48,51 @@ export const find: Find = async function find(
|
|||||||
const query = await Model.buildQuery({
|
const query = await Model.buildQuery({
|
||||||
locale,
|
locale,
|
||||||
payload: this.payload,
|
payload: this.payload,
|
||||||
|
session,
|
||||||
where,
|
where,
|
||||||
})
|
})
|
||||||
|
|
||||||
// useEstimatedCount is faster, but not accurate, as it ignores any filters. It is thus set to true if there are no filters.
|
// useEstimatedCount is faster, but not accurate, as it ignores any filters. It is thus set to true if there are no filters.
|
||||||
const useEstimatedCount = hasNearConstraint || !query || Object.keys(query).length === 0
|
const useEstimatedCount = hasNearConstraint || !query || Object.keys(query).length === 0
|
||||||
const paginationOptions: PaginateOptions = {
|
|
||||||
lean: true,
|
|
||||||
leanWithId: true,
|
|
||||||
options: {
|
|
||||||
session,
|
|
||||||
},
|
|
||||||
page,
|
|
||||||
pagination,
|
|
||||||
projection,
|
|
||||||
sort,
|
|
||||||
useEstimatedCount,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (select) {
|
const projection = buildProjectionFromSelect({
|
||||||
paginationOptions.projection = buildProjectionFromSelect({
|
adapter: this,
|
||||||
adapter: this,
|
fields,
|
||||||
fields: collectionConfig.flattenedFields,
|
select,
|
||||||
select,
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.collation) {
|
const collation: CollationOptions | undefined = this.collation
|
||||||
const defaultLocale = 'en'
|
? {
|
||||||
paginationOptions.collation = {
|
locale: locale && locale !== 'all' && locale !== '*' ? locale : 'en',
|
||||||
locale: locale && locale !== 'all' && locale !== '*' ? locale : defaultLocale,
|
...this.collation,
|
||||||
...this.collation,
|
}
|
||||||
}
|
: undefined
|
||||||
}
|
|
||||||
|
|
||||||
if (!useEstimatedCount && Object.keys(query).length === 0 && this.disableIndexHints !== true) {
|
const joinAgreggation = await buildJoinAggregation({
|
||||||
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
|
|
||||||
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
|
|
||||||
// which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses
|
|
||||||
// the correct indexed field
|
|
||||||
paginationOptions.useCustomCountFn = () => {
|
|
||||||
return Promise.resolve(
|
|
||||||
Model.countDocuments(query, {
|
|
||||||
hint: { _id: 1 },
|
|
||||||
session,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (limit >= 0) {
|
|
||||||
paginationOptions.limit = limit
|
|
||||||
// limit must also be set here, it's ignored when pagination is false
|
|
||||||
paginationOptions.options.limit = limit
|
|
||||||
|
|
||||||
// Disable pagination if limit is 0
|
|
||||||
if (limit === 0) {
|
|
||||||
paginationOptions.pagination = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let result
|
|
||||||
|
|
||||||
const aggregate = await buildJoinAggregation({
|
|
||||||
adapter: this,
|
adapter: this,
|
||||||
collection,
|
collection,
|
||||||
collectionConfig,
|
collectionConfig,
|
||||||
joins,
|
joins,
|
||||||
locale,
|
locale,
|
||||||
query,
|
session,
|
||||||
})
|
})
|
||||||
// build join aggregation
|
|
||||||
if (aggregate) {
|
|
||||||
result = await Model.aggregatePaginate(Model.aggregate(aggregate), paginationOptions)
|
|
||||||
} else {
|
|
||||||
result = await Model.paginate(query, paginationOptions)
|
|
||||||
}
|
|
||||||
|
|
||||||
const docs = JSON.parse(JSON.stringify(result.docs))
|
const result = await findMany({
|
||||||
|
adapter: this,
|
||||||
|
collation,
|
||||||
|
collection: Model.collection,
|
||||||
|
joinAgreggation,
|
||||||
|
limit,
|
||||||
|
page,
|
||||||
|
pagination,
|
||||||
|
projection,
|
||||||
|
query,
|
||||||
|
session,
|
||||||
|
sort,
|
||||||
|
useEstimatedCount,
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
transform({ adapter: this, data: result.docs, fields, operation: 'read' })
|
||||||
...result,
|
|
||||||
docs: docs.map((doc) => {
|
return result
|
||||||
doc.id = doc._id
|
|
||||||
return sanitizeInternalFields(doc)
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import type { QueryOptions } from 'mongoose'
|
|
||||||
import type { FindGlobal } from 'payload'
|
import type { FindGlobal } from 'payload'
|
||||||
|
|
||||||
import { combineQueries } from 'payload'
|
import { combineQueries } from 'payload'
|
||||||
@@ -7,42 +6,40 @@ import type { MongooseAdapter } from './index.js'
|
|||||||
|
|
||||||
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
||||||
import { getSession } from './utilities/getSession.js'
|
import { getSession } from './utilities/getSession.js'
|
||||||
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
import { transform } from './utilities/transform.js'
|
||||||
|
|
||||||
export const findGlobal: FindGlobal = async function findGlobal(
|
export const findGlobal: FindGlobal = async function findGlobal(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
{ slug, locale, req, select, where },
|
{ slug, locale, req, select, where },
|
||||||
) {
|
) {
|
||||||
const Model = this.globals
|
const Model = this.globals
|
||||||
const options: QueryOptions = {
|
|
||||||
lean: true,
|
const session = await getSession(this, req)
|
||||||
select: buildProjectionFromSelect({
|
|
||||||
adapter: this,
|
|
||||||
fields: this.payload.globals.config.find((each) => each.slug === slug).flattenedFields,
|
|
||||||
select,
|
|
||||||
}),
|
|
||||||
session: await getSession(this, req),
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = await Model.buildQuery({
|
const query = await Model.buildQuery({
|
||||||
globalSlug: slug,
|
globalSlug: slug,
|
||||||
locale,
|
locale,
|
||||||
payload: this.payload,
|
payload: this.payload,
|
||||||
|
session,
|
||||||
where: combineQueries({ globalType: { equals: slug } }, where),
|
where: combineQueries({ globalType: { equals: slug } }, where),
|
||||||
})
|
})
|
||||||
|
|
||||||
let doc = (await Model.findOne(query, {}, options)) as any
|
const fields = this.payload.globals.config.find((each) => each.slug === slug).flattenedFields
|
||||||
|
|
||||||
|
const doc = await Model.collection.findOne(query, {
|
||||||
|
projection: buildProjectionFromSelect({
|
||||||
|
adapter: this,
|
||||||
|
fields,
|
||||||
|
select,
|
||||||
|
}),
|
||||||
|
session: await getSession(this, req),
|
||||||
|
})
|
||||||
|
|
||||||
if (!doc) {
|
if (!doc) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
if (doc._id) {
|
|
||||||
doc.id = doc._id
|
|
||||||
delete doc._id
|
|
||||||
}
|
|
||||||
|
|
||||||
doc = JSON.parse(JSON.stringify(doc))
|
transform({ adapter: this, data: doc, fields, operation: 'read' })
|
||||||
doc = sanitizeInternalFields(doc)
|
|
||||||
|
|
||||||
return doc
|
return doc as any
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import type { PaginateOptions, QueryOptions } from 'mongoose'
|
import type { CollationOptions } from 'mongodb'
|
||||||
import type { FindGlobalVersions } from 'payload'
|
import type { FindGlobalVersions } from 'payload'
|
||||||
|
|
||||||
import { buildVersionGlobalFields, flattenWhereToOperators } from 'payload'
|
import { buildVersionGlobalFields } from 'payload'
|
||||||
|
|
||||||
import type { MongooseAdapter } from './index.js'
|
import type { MongooseAdapter } from './index.js'
|
||||||
|
|
||||||
import { buildSortParam } from './queries/buildSortParam.js'
|
import { buildSortParam } from './queries/buildSortParam.js'
|
||||||
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
||||||
|
import { findMany } from './utilities/findMany.js'
|
||||||
|
import { getHasNearConstraint } from './utilities/getHasNearConstraint.js'
|
||||||
import { getSession } from './utilities/getSession.js'
|
import { getSession } from './utilities/getSession.js'
|
||||||
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
import { transform } from './utilities/transform.js'
|
||||||
|
|
||||||
export const findGlobalVersions: FindGlobalVersions = async function findGlobalVersions(
|
export const findGlobalVersions: FindGlobalVersions = async function findGlobalVersions(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
@@ -21,19 +23,7 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV
|
|||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
|
|
||||||
const session = await getSession(this, req)
|
const hasNearConstraint = getHasNearConstraint(where)
|
||||||
const options: QueryOptions = {
|
|
||||||
limit,
|
|
||||||
session,
|
|
||||||
skip,
|
|
||||||
}
|
|
||||||
|
|
||||||
let hasNearConstraint = false
|
|
||||||
|
|
||||||
if (where) {
|
|
||||||
const constraints = flattenWhereToOperators(where)
|
|
||||||
hasNearConstraint = constraints.some((prop) => Object.keys(prop).some((key) => key === 'near'))
|
|
||||||
}
|
|
||||||
|
|
||||||
let sort
|
let sort
|
||||||
if (!hasNearConstraint) {
|
if (!hasNearConstraint) {
|
||||||
@@ -46,69 +36,49 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const session = await getSession(this, req)
|
||||||
|
|
||||||
const query = await Model.buildQuery({
|
const query = await Model.buildQuery({
|
||||||
globalSlug: global,
|
globalSlug: global,
|
||||||
locale,
|
locale,
|
||||||
payload: this.payload,
|
payload: this.payload,
|
||||||
|
session,
|
||||||
where,
|
where,
|
||||||
})
|
})
|
||||||
|
|
||||||
// useEstimatedCount is faster, but not accurate, as it ignores any filters. It is thus set to true if there are no filters.
|
// useEstimatedCount is faster, but not accurate, as it ignores any filters. It is thus set to true if there are no filters.
|
||||||
const useEstimatedCount = hasNearConstraint || !query || Object.keys(query).length === 0
|
const useEstimatedCount = hasNearConstraint || !query || Object.keys(query).length === 0
|
||||||
const paginationOptions: PaginateOptions = {
|
|
||||||
lean: true,
|
const projection = buildProjectionFromSelect({ adapter: this, fields: versionFields, select })
|
||||||
leanWithId: true,
|
|
||||||
|
const collation: CollationOptions | undefined = this.collation
|
||||||
|
? {
|
||||||
|
locale: locale && locale !== 'all' && locale !== '*' ? locale : 'en',
|
||||||
|
...this.collation,
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
const result = await findMany({
|
||||||
|
adapter: this,
|
||||||
|
collation,
|
||||||
|
collection: Model.collection,
|
||||||
limit,
|
limit,
|
||||||
options,
|
|
||||||
page,
|
page,
|
||||||
pagination,
|
pagination,
|
||||||
projection: buildProjectionFromSelect({ adapter: this, fields: versionFields, select }),
|
projection,
|
||||||
|
query,
|
||||||
|
session,
|
||||||
|
skip,
|
||||||
sort,
|
sort,
|
||||||
useEstimatedCount,
|
useEstimatedCount,
|
||||||
}
|
})
|
||||||
|
|
||||||
if (this.collation) {
|
transform({
|
||||||
const defaultLocale = 'en'
|
adapter: this,
|
||||||
paginationOptions.collation = {
|
data: result.docs,
|
||||||
locale: locale && locale !== 'all' && locale !== '*' ? locale : defaultLocale,
|
fields: versionFields,
|
||||||
...this.collation,
|
operation: 'read',
|
||||||
}
|
})
|
||||||
}
|
|
||||||
|
|
||||||
if (!useEstimatedCount && Object.keys(query).length === 0 && this.disableIndexHints !== true) {
|
return result
|
||||||
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
|
|
||||||
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
|
|
||||||
// which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses
|
|
||||||
// the correct indexed field
|
|
||||||
paginationOptions.useCustomCountFn = () => {
|
|
||||||
return Promise.resolve(
|
|
||||||
Model.countDocuments(query, {
|
|
||||||
hint: { _id: 1 },
|
|
||||||
session,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (limit >= 0) {
|
|
||||||
paginationOptions.limit = limit
|
|
||||||
// limit must also be set here, it's ignored when pagination is false
|
|
||||||
paginationOptions.options.limit = limit
|
|
||||||
|
|
||||||
// Disable pagination if limit is 0
|
|
||||||
if (limit === 0) {
|
|
||||||
paginationOptions.pagination = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await Model.paginate(query, paginationOptions)
|
|
||||||
const docs = JSON.parse(JSON.stringify(result.docs))
|
|
||||||
|
|
||||||
return {
|
|
||||||
...result,
|
|
||||||
docs: docs.map((doc) => {
|
|
||||||
doc.id = doc._id
|
|
||||||
return sanitizeInternalFields(doc)
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import type { AggregateOptions, QueryOptions } from 'mongoose'
|
import type { FindOne } from 'payload'
|
||||||
import type { Document, FindOne } from 'payload'
|
|
||||||
|
|
||||||
import type { MongooseAdapter } from './index.js'
|
import type { MongooseAdapter } from './index.js'
|
||||||
|
|
||||||
import { buildJoinAggregation } from './utilities/buildJoinAggregation.js'
|
import { buildJoinAggregation } from './utilities/buildJoinAggregation.js'
|
||||||
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
||||||
import { getSession } from './utilities/getSession.js'
|
import { getSession } from './utilities/getSession.js'
|
||||||
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
import { transform } from './utilities/transform.js'
|
||||||
|
|
||||||
export const findOne: FindOne = async function findOne(
|
export const findOne: FindOne = async function findOne(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
@@ -14,52 +13,64 @@ export const findOne: FindOne = async function findOne(
|
|||||||
) {
|
) {
|
||||||
const Model = this.collections[collection]
|
const Model = this.collections[collection]
|
||||||
const collectionConfig = this.payload.collections[collection].config
|
const collectionConfig = this.payload.collections[collection].config
|
||||||
|
|
||||||
const session = await getSession(this, req)
|
const session = await getSession(this, req)
|
||||||
const options: AggregateOptions & QueryOptions = {
|
|
||||||
lean: true,
|
|
||||||
session,
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = await Model.buildQuery({
|
const query = await Model.buildQuery({
|
||||||
locale,
|
locale,
|
||||||
payload: this.payload,
|
payload: this.payload,
|
||||||
|
session,
|
||||||
where,
|
where,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const fields = collectionConfig.flattenedFields
|
||||||
|
|
||||||
const projection = buildProjectionFromSelect({
|
const projection = buildProjectionFromSelect({
|
||||||
adapter: this,
|
adapter: this,
|
||||||
fields: collectionConfig.flattenedFields,
|
fields,
|
||||||
select,
|
select,
|
||||||
})
|
})
|
||||||
|
|
||||||
const aggregate = await buildJoinAggregation({
|
const joinAggregation = await buildJoinAggregation({
|
||||||
adapter: this,
|
adapter: this,
|
||||||
collection,
|
collection,
|
||||||
collectionConfig,
|
collectionConfig,
|
||||||
joins,
|
joins,
|
||||||
limit: 1,
|
|
||||||
locale,
|
locale,
|
||||||
projection,
|
projection,
|
||||||
query,
|
session,
|
||||||
})
|
})
|
||||||
|
|
||||||
let doc
|
let doc
|
||||||
if (aggregate) {
|
if (joinAggregation) {
|
||||||
;[doc] = await Model.aggregate(aggregate, { session })
|
const aggregation = Model.collection.aggregate(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
$match: query,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{ session },
|
||||||
|
)
|
||||||
|
aggregation.limit(1)
|
||||||
|
for (const stage of joinAggregation) {
|
||||||
|
aggregation.addStage(stage)
|
||||||
|
}
|
||||||
|
|
||||||
|
;[doc] = await aggregation.toArray()
|
||||||
} else {
|
} else {
|
||||||
;(options as Record<string, unknown>).projection = projection
|
doc = await Model.collection.findOne(query, { projection, session })
|
||||||
doc = await Model.findOne(query, {}, options)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!doc) {
|
if (!doc) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
let result: Document = JSON.parse(JSON.stringify(doc))
|
transform({
|
||||||
|
adapter: this,
|
||||||
|
data: doc,
|
||||||
|
fields,
|
||||||
|
operation: 'read',
|
||||||
|
})
|
||||||
|
|
||||||
// custom id type reset
|
return doc
|
||||||
result.id = result._id
|
|
||||||
result = sanitizeInternalFields(result)
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import type { PaginateOptions, QueryOptions } from 'mongoose'
|
import type { CollationOptions } from 'mongodb'
|
||||||
import type { FindVersions } from 'payload'
|
import type { FindVersions } from 'payload'
|
||||||
|
|
||||||
import { buildVersionCollectionFields, flattenWhereToOperators } from 'payload'
|
import { buildVersionCollectionFields } from 'payload'
|
||||||
|
|
||||||
import type { MongooseAdapter } from './index.js'
|
import type { MongooseAdapter } from './index.js'
|
||||||
|
|
||||||
import { buildSortParam } from './queries/buildSortParam.js'
|
import { buildSortParam } from './queries/buildSortParam.js'
|
||||||
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
||||||
|
import { findMany } from './utilities/findMany.js'
|
||||||
|
import { getHasNearConstraint } from './utilities/getHasNearConstraint.js'
|
||||||
import { getSession } from './utilities/getSession.js'
|
import { getSession } from './utilities/getSession.js'
|
||||||
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
import { transform } from './utilities/transform.js'
|
||||||
|
|
||||||
export const findVersions: FindVersions = async function findVersions(
|
export const findVersions: FindVersions = async function findVersions(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
@@ -16,19 +18,10 @@ export const findVersions: FindVersions = async function findVersions(
|
|||||||
) {
|
) {
|
||||||
const Model = this.versions[collection]
|
const Model = this.versions[collection]
|
||||||
const collectionConfig = this.payload.collections[collection].config
|
const collectionConfig = this.payload.collections[collection].config
|
||||||
|
|
||||||
const session = await getSession(this, req)
|
const session = await getSession(this, req)
|
||||||
const options: QueryOptions = {
|
|
||||||
limit,
|
|
||||||
session,
|
|
||||||
skip,
|
|
||||||
}
|
|
||||||
|
|
||||||
let hasNearConstraint = false
|
const hasNearConstraint = getHasNearConstraint(where)
|
||||||
|
|
||||||
if (where) {
|
|
||||||
const constraints = flattenWhereToOperators(where)
|
|
||||||
hasNearConstraint = constraints.some((prop) => Object.keys(prop).some((key) => key === 'near'))
|
|
||||||
}
|
|
||||||
|
|
||||||
let sort
|
let sort
|
||||||
if (!hasNearConstraint) {
|
if (!hasNearConstraint) {
|
||||||
@@ -44,69 +37,48 @@ export const findVersions: FindVersions = async function findVersions(
|
|||||||
const query = await Model.buildQuery({
|
const query = await Model.buildQuery({
|
||||||
locale,
|
locale,
|
||||||
payload: this.payload,
|
payload: this.payload,
|
||||||
|
session,
|
||||||
where,
|
where,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const versionFields = buildVersionCollectionFields(this.payload.config, collectionConfig, true)
|
||||||
// useEstimatedCount is faster, but not accurate, as it ignores any filters. It is thus set to true if there are no filters.
|
// useEstimatedCount is faster, but not accurate, as it ignores any filters. It is thus set to true if there are no filters.
|
||||||
const useEstimatedCount = hasNearConstraint || !query || Object.keys(query).length === 0
|
const useEstimatedCount = hasNearConstraint || !query || Object.keys(query).length === 0
|
||||||
const paginationOptions: PaginateOptions = {
|
|
||||||
lean: true,
|
const projection = buildProjectionFromSelect({
|
||||||
leanWithId: true,
|
adapter: this,
|
||||||
|
fields: versionFields,
|
||||||
|
select,
|
||||||
|
})
|
||||||
|
|
||||||
|
const collation: CollationOptions | undefined = this.collation
|
||||||
|
? {
|
||||||
|
locale: locale && locale !== 'all' && locale !== '*' ? locale : 'en',
|
||||||
|
...this.collation,
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
const result = await findMany({
|
||||||
|
adapter: this,
|
||||||
|
collation,
|
||||||
|
collection: Model.collection,
|
||||||
limit,
|
limit,
|
||||||
options,
|
|
||||||
page,
|
page,
|
||||||
pagination,
|
pagination,
|
||||||
projection: buildProjectionFromSelect({
|
projection,
|
||||||
adapter: this,
|
query,
|
||||||
fields: buildVersionCollectionFields(this.payload.config, collectionConfig, true),
|
session,
|
||||||
select,
|
skip,
|
||||||
}),
|
|
||||||
sort,
|
sort,
|
||||||
useEstimatedCount,
|
useEstimatedCount,
|
||||||
}
|
})
|
||||||
|
|
||||||
if (this.collation) {
|
transform({
|
||||||
const defaultLocale = 'en'
|
adapter: this,
|
||||||
paginationOptions.collation = {
|
data: result.docs,
|
||||||
locale: locale && locale !== 'all' && locale !== '*' ? locale : defaultLocale,
|
fields: versionFields,
|
||||||
...this.collation,
|
operation: 'read',
|
||||||
}
|
})
|
||||||
}
|
|
||||||
|
|
||||||
if (!useEstimatedCount && Object.keys(query).length === 0 && this.disableIndexHints !== true) {
|
return result
|
||||||
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
|
|
||||||
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
|
|
||||||
// which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses
|
|
||||||
// the correct indexed field
|
|
||||||
paginationOptions.useCustomCountFn = () => {
|
|
||||||
return Promise.resolve(
|
|
||||||
Model.countDocuments(query, {
|
|
||||||
hint: { _id: 1 },
|
|
||||||
session,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (limit >= 0) {
|
|
||||||
paginationOptions.limit = limit
|
|
||||||
// limit must also be set here, it's ignored when pagination is false
|
|
||||||
paginationOptions.options.limit = limit
|
|
||||||
|
|
||||||
// Disable pagination if limit is 0
|
|
||||||
if (limit === 0) {
|
|
||||||
paginationOptions.pagination = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await Model.paginate(query, paginationOptions)
|
|
||||||
const docs = JSON.parse(JSON.stringify(result.docs))
|
|
||||||
|
|
||||||
return {
|
|
||||||
...result,
|
|
||||||
docs: docs.map((doc) => {
|
|
||||||
doc.id = doc._id
|
|
||||||
return sanitizeInternalFields(doc)
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ import { upsert } from './upsert.js'
|
|||||||
|
|
||||||
export type { MigrateDownArgs, MigrateUpArgs } from './types.js'
|
export type { MigrateDownArgs, MigrateUpArgs } from './types.js'
|
||||||
|
|
||||||
|
export { transform } from './utilities/transform.js'
|
||||||
|
|
||||||
export interface Args {
|
export interface Args {
|
||||||
/** Set to false to disable auto-pluralization of collection names, Defaults to true */
|
/** Set to false to disable auto-pluralization of collection names, Defaults to true */
|
||||||
autoPluralization?: boolean
|
autoPluralization?: boolean
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import type { PayloadRequest } from 'payload'
|
|
||||||
|
|
||||||
import { commitTransaction, initTransaction, killTransaction, readMigrationFiles } from 'payload'
|
import { commitTransaction, initTransaction, killTransaction, readMigrationFiles } from 'payload'
|
||||||
import prompts from 'prompts'
|
import prompts from 'prompts'
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
import type { ClientSession, Model } from 'mongoose'
|
import type { ClientSession, Model } from 'mongoose'
|
||||||
import type { Field, PayloadRequest, SanitizedConfig } from 'payload'
|
import type { Field, FlattenedField, PayloadRequest } from 'payload'
|
||||||
|
|
||||||
import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload'
|
import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload'
|
||||||
|
|
||||||
import type { MongooseAdapter } from '../index.js'
|
import type { MongooseAdapter } from '../index.js'
|
||||||
|
|
||||||
import { getSession } from '../utilities/getSession.js'
|
import { getSession } from '../utilities/getSession.js'
|
||||||
import { sanitizeRelationshipIDs } from '../utilities/sanitizeRelationshipIDs.js'
|
import { transform } from '../utilities/transform.js'
|
||||||
|
|
||||||
const migrateModelWithBatching = async ({
|
const migrateModelWithBatching = async ({
|
||||||
|
adapter,
|
||||||
batchSize,
|
batchSize,
|
||||||
config,
|
|
||||||
fields,
|
fields,
|
||||||
Model,
|
Model,
|
||||||
session,
|
session,
|
||||||
}: {
|
}: {
|
||||||
|
adapter: MongooseAdapter
|
||||||
batchSize: number
|
batchSize: number
|
||||||
config: SanitizedConfig
|
fields: FlattenedField[]
|
||||||
fields: Field[]
|
|
||||||
Model: Model<any>
|
Model: Model<any>
|
||||||
session: ClientSession
|
session: ClientSession
|
||||||
}): Promise<void> => {
|
}): Promise<void> => {
|
||||||
@@ -47,7 +47,7 @@ const migrateModelWithBatching = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const doc of docs) {
|
for (const doc of docs) {
|
||||||
sanitizeRelationshipIDs({ config, data: doc, fields })
|
transform({ adapter, data: doc, fields, operation: 'update', validateRelationships: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
await Model.collection.bulkWrite(
|
await Model.collection.bulkWrite(
|
||||||
@@ -115,9 +115,9 @@ export async function migrateRelationshipsV2_V3({
|
|||||||
payload.logger.info(`Migrating collection "${collection.slug}"`)
|
payload.logger.info(`Migrating collection "${collection.slug}"`)
|
||||||
|
|
||||||
await migrateModelWithBatching({
|
await migrateModelWithBatching({
|
||||||
|
adapter: db,
|
||||||
batchSize,
|
batchSize,
|
||||||
config,
|
fields: collection.flattenedFields,
|
||||||
fields: collection.fields,
|
|
||||||
Model: db.collections[collection.slug],
|
Model: db.collections[collection.slug],
|
||||||
session,
|
session,
|
||||||
})
|
})
|
||||||
@@ -128,9 +128,9 @@ export async function migrateRelationshipsV2_V3({
|
|||||||
payload.logger.info(`Migrating collection versions "${collection.slug}"`)
|
payload.logger.info(`Migrating collection versions "${collection.slug}"`)
|
||||||
|
|
||||||
await migrateModelWithBatching({
|
await migrateModelWithBatching({
|
||||||
|
adapter: db,
|
||||||
batchSize,
|
batchSize,
|
||||||
config,
|
fields: buildVersionCollectionFields(config, collection, true),
|
||||||
fields: buildVersionCollectionFields(config, collection),
|
|
||||||
Model: db.versions[collection.slug],
|
Model: db.versions[collection.slug],
|
||||||
session,
|
session,
|
||||||
})
|
})
|
||||||
@@ -156,7 +156,13 @@ export async function migrateRelationshipsV2_V3({
|
|||||||
|
|
||||||
// in case if the global doesn't exist in the database yet (not saved)
|
// in case if the global doesn't exist in the database yet (not saved)
|
||||||
if (doc) {
|
if (doc) {
|
||||||
sanitizeRelationshipIDs({ config, data: doc, fields: global.fields })
|
transform({
|
||||||
|
adapter: db,
|
||||||
|
data: doc,
|
||||||
|
fields: global.flattenedFields,
|
||||||
|
operation: 'update',
|
||||||
|
validateRelationships: false,
|
||||||
|
})
|
||||||
|
|
||||||
await GlobalsModel.collection.updateOne(
|
await GlobalsModel.collection.updateOne(
|
||||||
{
|
{
|
||||||
@@ -173,9 +179,9 @@ export async function migrateRelationshipsV2_V3({
|
|||||||
payload.logger.info(`Migrating global versions "${global.slug}"`)
|
payload.logger.info(`Migrating global versions "${global.slug}"`)
|
||||||
|
|
||||||
await migrateModelWithBatching({
|
await migrateModelWithBatching({
|
||||||
|
adapter: db,
|
||||||
batchSize,
|
batchSize,
|
||||||
config,
|
fields: buildVersionGlobalFields(config, global, true),
|
||||||
fields: buildVersionGlobalFields(config, global),
|
|
||||||
Model: db.versions[global.slug],
|
Model: db.versions[global.slug],
|
||||||
session,
|
session,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { ClientSession } from 'mongodb'
|
||||||
import type { FlattenedField, Payload, Where } from 'payload'
|
import type { FlattenedField, Payload, Where } from 'payload'
|
||||||
|
|
||||||
import { parseParams } from './parseParams.js'
|
import { parseParams } from './parseParams.js'
|
||||||
@@ -8,6 +9,7 @@ export async function buildAndOrConditions({
|
|||||||
globalSlug,
|
globalSlug,
|
||||||
locale,
|
locale,
|
||||||
payload,
|
payload,
|
||||||
|
session,
|
||||||
where,
|
where,
|
||||||
}: {
|
}: {
|
||||||
collectionSlug?: string
|
collectionSlug?: string
|
||||||
@@ -15,6 +17,7 @@ export async function buildAndOrConditions({
|
|||||||
globalSlug?: string
|
globalSlug?: string
|
||||||
locale?: string
|
locale?: string
|
||||||
payload: Payload
|
payload: Payload
|
||||||
|
session?: ClientSession
|
||||||
where: Where[]
|
where: Where[]
|
||||||
}): Promise<Record<string, unknown>[]> {
|
}): Promise<Record<string, unknown>[]> {
|
||||||
const completedConditions = []
|
const completedConditions = []
|
||||||
@@ -30,6 +33,7 @@ export async function buildAndOrConditions({
|
|||||||
globalSlug,
|
globalSlug,
|
||||||
locale,
|
locale,
|
||||||
payload,
|
payload,
|
||||||
|
session,
|
||||||
where: condition,
|
where: condition,
|
||||||
})
|
})
|
||||||
if (Object.keys(result).length > 0) {
|
if (Object.keys(result).length > 0) {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
import type { ClientSession } from 'mongodb'
|
||||||
import type { FlattenedField, Payload, Where } from 'payload'
|
import type { FlattenedField, Payload, Where } from 'payload'
|
||||||
|
|
||||||
import { QueryError } from 'payload'
|
|
||||||
|
|
||||||
import { parseParams } from './parseParams.js'
|
import { parseParams } from './parseParams.js'
|
||||||
|
|
||||||
type GetBuildQueryPluginArgs = {
|
type GetBuildQueryPluginArgs = {
|
||||||
@@ -13,6 +12,7 @@ export type BuildQueryArgs = {
|
|||||||
globalSlug?: string
|
globalSlug?: string
|
||||||
locale?: string
|
locale?: string
|
||||||
payload: Payload
|
payload: Payload
|
||||||
|
session?: ClientSession
|
||||||
where: Where
|
where: Where
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,6 +28,7 @@ export const getBuildQueryPlugin = ({
|
|||||||
globalSlug,
|
globalSlug,
|
||||||
locale,
|
locale,
|
||||||
payload,
|
payload,
|
||||||
|
session,
|
||||||
where,
|
where,
|
||||||
}: BuildQueryArgs): Promise<Record<string, unknown>> {
|
}: BuildQueryArgs): Promise<Record<string, unknown>> {
|
||||||
let fields = versionsFields
|
let fields = versionsFields
|
||||||
@@ -41,20 +42,17 @@ export const getBuildQueryPlugin = ({
|
|||||||
fields = collectionConfig.flattenedFields
|
fields = collectionConfig.flattenedFields
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const errors = []
|
|
||||||
const result = await parseParams({
|
const result = await parseParams({
|
||||||
collectionSlug,
|
collectionSlug,
|
||||||
fields,
|
fields,
|
||||||
globalSlug,
|
globalSlug,
|
||||||
locale,
|
locale,
|
||||||
payload,
|
payload,
|
||||||
|
session,
|
||||||
where,
|
where,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (errors.length > 0) {
|
|
||||||
throw new QueryError(errors)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
modifiedSchema.statics.buildQuery = buildQuery
|
modifiedSchema.statics.buildQuery = buildQuery
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { ClientSession, FindOptions } from 'mongodb'
|
||||||
import type { FlattenedField, Operator, PathToQuery, Payload } from 'payload'
|
import type { FlattenedField, Operator, PathToQuery, Payload } from 'payload'
|
||||||
|
|
||||||
import { Types } from 'mongoose'
|
import { Types } from 'mongoose'
|
||||||
@@ -15,9 +16,11 @@ type SearchParam = {
|
|||||||
value?: unknown
|
value?: unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
const subQueryOptions = {
|
const subQueryOptions: FindOptions = {
|
||||||
lean: true,
|
|
||||||
limit: 50,
|
limit: 50,
|
||||||
|
projection: {
|
||||||
|
_id: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -31,6 +34,7 @@ export async function buildSearchParam({
|
|||||||
locale,
|
locale,
|
||||||
operator,
|
operator,
|
||||||
payload,
|
payload,
|
||||||
|
session,
|
||||||
val,
|
val,
|
||||||
}: {
|
}: {
|
||||||
collectionSlug?: string
|
collectionSlug?: string
|
||||||
@@ -40,6 +44,7 @@ export async function buildSearchParam({
|
|||||||
locale?: string
|
locale?: string
|
||||||
operator: string
|
operator: string
|
||||||
payload: Payload
|
payload: Payload
|
||||||
|
session?: ClientSession
|
||||||
val: unknown
|
val: unknown
|
||||||
}): Promise<SearchParam> {
|
}): Promise<SearchParam> {
|
||||||
// Replace GraphQL nested field double underscore formatting
|
// Replace GraphQL nested field double underscore formatting
|
||||||
@@ -134,17 +139,14 @@ export async function buildSearchParam({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const result = await SubModel.find(subQuery, subQueryOptions)
|
const result = await SubModel.collection
|
||||||
|
.find(subQuery, { session, ...subQueryOptions })
|
||||||
|
.toArray()
|
||||||
|
|
||||||
const $in: unknown[] = []
|
const $in: unknown[] = []
|
||||||
|
|
||||||
result.forEach((doc) => {
|
result.forEach((doc) => {
|
||||||
const stringID = doc._id.toString()
|
$in.push(doc._id)
|
||||||
$in.push(stringID)
|
|
||||||
|
|
||||||
if (Types.ObjectId.isValid(stringID)) {
|
|
||||||
$in.push(doc._id)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if (pathsToQuery.length === 1) {
|
if (pathsToQuery.length === 1) {
|
||||||
@@ -162,7 +164,9 @@ export async function buildSearchParam({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const subQuery = priorQueryResult.value
|
const subQuery = priorQueryResult.value
|
||||||
const result = await SubModel.find(subQuery, subQueryOptions)
|
const result = await SubModel.collection
|
||||||
|
.find(subQuery, { session, ...subQueryOptions })
|
||||||
|
.toArray()
|
||||||
|
|
||||||
const $in = result.map((doc) => doc._id)
|
const $in = result.map((doc) => doc._id)
|
||||||
|
|
||||||
|
|||||||
@@ -11,20 +11,13 @@ type Args = {
|
|||||||
timestamps: boolean
|
timestamps: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SortArgs = {
|
|
||||||
direction: SortDirection
|
|
||||||
property: string
|
|
||||||
}[]
|
|
||||||
|
|
||||||
export type SortDirection = 'asc' | 'desc'
|
|
||||||
|
|
||||||
export const buildSortParam = ({
|
export const buildSortParam = ({
|
||||||
config,
|
config,
|
||||||
fields,
|
fields,
|
||||||
locale,
|
locale,
|
||||||
sort,
|
sort,
|
||||||
timestamps,
|
timestamps,
|
||||||
}: Args): PaginateOptions['sort'] => {
|
}: Args): Record<string, -1 | 1> => {
|
||||||
if (!sort) {
|
if (!sort) {
|
||||||
if (timestamps) {
|
if (timestamps) {
|
||||||
sort = '-createdAt'
|
sort = '-createdAt'
|
||||||
@@ -37,15 +30,15 @@ export const buildSortParam = ({
|
|||||||
sort = [sort]
|
sort = [sort]
|
||||||
}
|
}
|
||||||
|
|
||||||
const sorting = sort.reduce<PaginateOptions['sort']>((acc, item) => {
|
const sorting = sort.reduce<Record<string, -1 | 1>>((acc, item) => {
|
||||||
let sortProperty: string
|
let sortProperty: string
|
||||||
let sortDirection: SortDirection
|
let sortDirection: -1 | 1
|
||||||
if (item.indexOf('-') === 0) {
|
if (item.indexOf('-') === 0) {
|
||||||
sortProperty = item.substring(1)
|
sortProperty = item.substring(1)
|
||||||
sortDirection = 'desc'
|
sortDirection = -1
|
||||||
} else {
|
} else {
|
||||||
sortProperty = item
|
sortProperty = item
|
||||||
sortDirection = 'asc'
|
sortDirection = 1
|
||||||
}
|
}
|
||||||
if (sortProperty === 'id') {
|
if (sortProperty === 'id') {
|
||||||
acc['_id'] = sortDirection
|
acc['_id'] = sortDirection
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { FlattenedField, SanitizedConfig } from 'payload'
|
import type { FlattenedField, SanitizedConfig } from 'payload'
|
||||||
|
|
||||||
import { fieldAffectsData, fieldIsPresentationalOnly } from 'payload/shared'
|
import { fieldAffectsData } from 'payload/shared'
|
||||||
|
|
||||||
type Args = {
|
type Args = {
|
||||||
config: SanitizedConfig
|
config: SanitizedConfig
|
||||||
@@ -33,7 +33,7 @@ export const getLocalizedSortProperty = ({
|
|||||||
(field) => fieldAffectsData(field) && field.name === firstSegment,
|
(field) => fieldAffectsData(field) && field.name === firstSegment,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (matchedField && !fieldIsPresentationalOnly(matchedField)) {
|
if (matchedField) {
|
||||||
let nextFields: FlattenedField[]
|
let nextFields: FlattenedField[]
|
||||||
const remainingSegments = [...segments]
|
const remainingSegments = [...segments]
|
||||||
let localizedSegment = matchedField.name
|
let localizedSegment = matchedField.name
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { ClientSession } from 'mongodb'
|
||||||
import type { FilterQuery } from 'mongoose'
|
import type { FilterQuery } from 'mongoose'
|
||||||
import type { FlattenedField, Operator, Payload, Where } from 'payload'
|
import type { FlattenedField, Operator, Payload, Where } from 'payload'
|
||||||
|
|
||||||
@@ -13,6 +14,7 @@ export async function parseParams({
|
|||||||
globalSlug,
|
globalSlug,
|
||||||
locale,
|
locale,
|
||||||
payload,
|
payload,
|
||||||
|
session,
|
||||||
where,
|
where,
|
||||||
}: {
|
}: {
|
||||||
collectionSlug?: string
|
collectionSlug?: string
|
||||||
@@ -20,6 +22,7 @@ export async function parseParams({
|
|||||||
globalSlug?: string
|
globalSlug?: string
|
||||||
locale: string
|
locale: string
|
||||||
payload: Payload
|
payload: Payload
|
||||||
|
session?: ClientSession
|
||||||
where: Where
|
where: Where
|
||||||
}): Promise<Record<string, unknown>> {
|
}): Promise<Record<string, unknown>> {
|
||||||
let result = {} as FilterQuery<any>
|
let result = {} as FilterQuery<any>
|
||||||
@@ -62,6 +65,7 @@ export async function parseParams({
|
|||||||
locale,
|
locale,
|
||||||
operator,
|
operator,
|
||||||
payload,
|
payload,
|
||||||
|
session,
|
||||||
val: pathOperators[operator],
|
val: pathOperators[operator],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,22 @@ const buildExistsQuery = (formattedValue, path, treatEmptyString = true) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sanitizeCoordinates = (coordinates: unknown[]): unknown[] => {
|
||||||
|
const result: unknown[] = []
|
||||||
|
|
||||||
|
for (const value of coordinates) {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
result.push(Number(value))
|
||||||
|
} else if (Array.isArray(value)) {
|
||||||
|
result.push(sanitizeCoordinates(value))
|
||||||
|
} else {
|
||||||
|
result.push(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// returns nestedField Field object from blocks.nestedField path because getLocalizedPaths splits them only for relationships
|
// returns nestedField Field object from blocks.nestedField path because getLocalizedPaths splits them only for relationships
|
||||||
const getFieldFromSegments = ({
|
const getFieldFromSegments = ({
|
||||||
field,
|
field,
|
||||||
@@ -359,6 +375,14 @@ export const sanitizeQueryValue = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (operator === 'within' || operator === 'intersects') {
|
if (operator === 'within' || operator === 'intersects') {
|
||||||
|
if (
|
||||||
|
formattedValue &&
|
||||||
|
typeof formattedValue === 'object' &&
|
||||||
|
Array.isArray(formattedValue.coordinates)
|
||||||
|
) {
|
||||||
|
formattedValue.coordinates = sanitizeCoordinates(formattedValue.coordinates)
|
||||||
|
}
|
||||||
|
|
||||||
formattedValue = {
|
formattedValue = {
|
||||||
$geometry: formattedValue,
|
$geometry: formattedValue,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
import type { PaginateOptions, QueryOptions } from 'mongoose'
|
import type { CollationOptions } from 'mongodb'
|
||||||
import type { QueryDrafts } from 'payload'
|
import type { QueryDrafts } from 'payload'
|
||||||
|
|
||||||
import { buildVersionCollectionFields, combineQueries, flattenWhereToOperators } from 'payload'
|
import { buildVersionCollectionFields, combineQueries } from 'payload'
|
||||||
|
|
||||||
import type { MongooseAdapter } from './index.js'
|
import type { MongooseAdapter } from './index.js'
|
||||||
|
|
||||||
import { buildSortParam } from './queries/buildSortParam.js'
|
import { buildSortParam } from './queries/buildSortParam.js'
|
||||||
import { buildJoinAggregation } from './utilities/buildJoinAggregation.js'
|
import { buildJoinAggregation } from './utilities/buildJoinAggregation.js'
|
||||||
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
||||||
|
import { findMany } from './utilities/findMany.js'
|
||||||
|
import { getHasNearConstraint } from './utilities/getHasNearConstraint.js'
|
||||||
import { getSession } from './utilities/getSession.js'
|
import { getSession } from './utilities/getSession.js'
|
||||||
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
import { transform } from './utilities/transform.js'
|
||||||
|
|
||||||
export const queryDrafts: QueryDrafts = async function queryDrafts(
|
export const queryDrafts: QueryDrafts = async function queryDrafts(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
@@ -17,18 +19,11 @@ export const queryDrafts: QueryDrafts = async function queryDrafts(
|
|||||||
) {
|
) {
|
||||||
const VersionModel = this.versions[collection]
|
const VersionModel = this.versions[collection]
|
||||||
const collectionConfig = this.payload.collections[collection].config
|
const collectionConfig = this.payload.collections[collection].config
|
||||||
const options: QueryOptions = {
|
const session = await getSession(this, req)
|
||||||
session: await getSession(this, req),
|
|
||||||
}
|
|
||||||
|
|
||||||
let hasNearConstraint
|
const hasNearConstraint = getHasNearConstraint(where)
|
||||||
let sort
|
let sort
|
||||||
|
|
||||||
if (where) {
|
|
||||||
const constraints = flattenWhereToOperators(where)
|
|
||||||
hasNearConstraint = constraints.some((prop) => Object.keys(prop).some((key) => key === 'near'))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasNearConstraint) {
|
if (!hasNearConstraint) {
|
||||||
sort = buildSortParam({
|
sort = buildSortParam({
|
||||||
config: this.payload.config,
|
config: this.payload.config,
|
||||||
@@ -44,95 +39,65 @@ export const queryDrafts: QueryDrafts = async function queryDrafts(
|
|||||||
const versionQuery = await VersionModel.buildQuery({
|
const versionQuery = await VersionModel.buildQuery({
|
||||||
locale,
|
locale,
|
||||||
payload: this.payload,
|
payload: this.payload,
|
||||||
|
session,
|
||||||
where: combinedWhere,
|
where: combinedWhere,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const versionFields = buildVersionCollectionFields(this.payload.config, collectionConfig, true)
|
||||||
const projection = buildProjectionFromSelect({
|
const projection = buildProjectionFromSelect({
|
||||||
adapter: this,
|
adapter: this,
|
||||||
fields: buildVersionCollectionFields(this.payload.config, collectionConfig, true),
|
fields: versionFields,
|
||||||
select,
|
select,
|
||||||
})
|
})
|
||||||
// useEstimatedCount is faster, but not accurate, as it ignores any filters. It is thus set to true if there are no filters.
|
// useEstimatedCount is faster, but not accurate, as it ignores any filters. It is thus set to true if there are no filters.
|
||||||
const useEstimatedCount =
|
const useEstimatedCount =
|
||||||
hasNearConstraint || !versionQuery || Object.keys(versionQuery).length === 0
|
hasNearConstraint || !versionQuery || Object.keys(versionQuery).length === 0
|
||||||
const paginationOptions: PaginateOptions = {
|
|
||||||
lean: true,
|
|
||||||
leanWithId: true,
|
|
||||||
options,
|
|
||||||
page,
|
|
||||||
pagination,
|
|
||||||
projection,
|
|
||||||
sort,
|
|
||||||
useEstimatedCount,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.collation) {
|
const collation: CollationOptions | undefined = this.collation
|
||||||
const defaultLocale = 'en'
|
? {
|
||||||
paginationOptions.collation = {
|
locale: locale && locale !== 'all' && locale !== '*' ? locale : 'en',
|
||||||
locale: locale && locale !== 'all' && locale !== '*' ? locale : defaultLocale,
|
...this.collation,
|
||||||
...this.collation,
|
}
|
||||||
}
|
: undefined
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
const joinAgreggation = await buildJoinAggregation({
|
||||||
!useEstimatedCount &&
|
|
||||||
Object.keys(versionQuery).length === 0 &&
|
|
||||||
this.disableIndexHints !== true
|
|
||||||
) {
|
|
||||||
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
|
|
||||||
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
|
|
||||||
// which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses
|
|
||||||
// the correct indexed field
|
|
||||||
paginationOptions.useCustomCountFn = () => {
|
|
||||||
return Promise.resolve(
|
|
||||||
VersionModel.countDocuments(versionQuery, {
|
|
||||||
hint: { _id: 1 },
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (limit > 0) {
|
|
||||||
paginationOptions.limit = limit
|
|
||||||
// limit must also be set here, it's ignored when pagination is false
|
|
||||||
paginationOptions.options.limit = limit
|
|
||||||
}
|
|
||||||
|
|
||||||
let result
|
|
||||||
|
|
||||||
const aggregate = await buildJoinAggregation({
|
|
||||||
adapter: this,
|
adapter: this,
|
||||||
collection,
|
collection,
|
||||||
collectionConfig,
|
collectionConfig,
|
||||||
joins,
|
joins,
|
||||||
locale,
|
locale,
|
||||||
projection,
|
projection,
|
||||||
query: versionQuery,
|
session,
|
||||||
versions: true,
|
versions: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
// build join aggregation
|
const result = await findMany({
|
||||||
if (aggregate) {
|
adapter: this,
|
||||||
result = await VersionModel.aggregatePaginate(
|
collation,
|
||||||
VersionModel.aggregate(aggregate),
|
collection: VersionModel.collection,
|
||||||
paginationOptions,
|
joinAgreggation,
|
||||||
)
|
limit,
|
||||||
} else {
|
page,
|
||||||
result = await VersionModel.paginate(versionQuery, paginationOptions)
|
pagination,
|
||||||
|
projection,
|
||||||
|
query: versionQuery,
|
||||||
|
session,
|
||||||
|
sort,
|
||||||
|
useEstimatedCount,
|
||||||
|
})
|
||||||
|
|
||||||
|
transform({
|
||||||
|
adapter: this,
|
||||||
|
data: result.docs,
|
||||||
|
fields: versionFields,
|
||||||
|
operation: 'read',
|
||||||
|
})
|
||||||
|
|
||||||
|
for (let i = 0; i < result.docs.length; i++) {
|
||||||
|
const id = result.docs[i].parent
|
||||||
|
result.docs[i] = result.docs[i].version
|
||||||
|
result.docs[i].id = id
|
||||||
}
|
}
|
||||||
|
|
||||||
const docs = JSON.parse(JSON.stringify(result.docs))
|
return result
|
||||||
|
|
||||||
return {
|
|
||||||
...result,
|
|
||||||
docs: docs.map((doc) => {
|
|
||||||
doc = {
|
|
||||||
_id: doc.parent,
|
|
||||||
id: doc.parent,
|
|
||||||
...doc.version,
|
|
||||||
}
|
|
||||||
|
|
||||||
return sanitizeInternalFields(doc)
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +1,45 @@
|
|||||||
import type { QueryOptions } from 'mongoose'
|
|
||||||
import type { UpdateGlobal } from 'payload'
|
import type { UpdateGlobal } from 'payload'
|
||||||
|
|
||||||
import type { MongooseAdapter } from './index.js'
|
import type { MongooseAdapter } from './index.js'
|
||||||
|
|
||||||
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
||||||
import { getSession } from './utilities/getSession.js'
|
import { getSession } from './utilities/getSession.js'
|
||||||
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
import { transform } from './utilities/transform.js'
|
||||||
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'
|
|
||||||
|
|
||||||
export const updateGlobal: UpdateGlobal = async function updateGlobal(
|
export const updateGlobal: UpdateGlobal = async function updateGlobal(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
{ slug, data, options: optionsArgs = {}, req, select },
|
{ slug, data, options: optionsArgs = {}, req, select },
|
||||||
) {
|
) {
|
||||||
const Model = this.globals
|
const Model = this.globals
|
||||||
const fields = this.payload.config.globals.find((global) => global.slug === slug).fields
|
const fields = this.payload.config.globals.find((global) => global.slug === slug).flattenedFields
|
||||||
|
|
||||||
const options: QueryOptions = {
|
const session = await getSession(this, req)
|
||||||
...optionsArgs,
|
|
||||||
lean: true,
|
|
||||||
new: true,
|
|
||||||
projection: buildProjectionFromSelect({
|
|
||||||
adapter: this,
|
|
||||||
fields: this.payload.config.globals.find((global) => global.slug === slug).flattenedFields,
|
|
||||||
select,
|
|
||||||
}),
|
|
||||||
session: await getSession(this, req),
|
|
||||||
}
|
|
||||||
|
|
||||||
let result
|
transform({
|
||||||
|
adapter: this,
|
||||||
const sanitizedData = sanitizeRelationshipIDs({
|
|
||||||
config: this.payload.config,
|
|
||||||
data,
|
data,
|
||||||
fields,
|
fields,
|
||||||
|
operation: 'update',
|
||||||
|
timestamps: optionsArgs.timestamps !== false,
|
||||||
})
|
})
|
||||||
|
|
||||||
result = await Model.findOneAndUpdate({ globalType: slug }, sanitizedData, options)
|
const result: any = await Model.collection.findOneAndUpdate(
|
||||||
|
{ globalType: slug },
|
||||||
|
{ $set: data },
|
||||||
|
{
|
||||||
|
...optionsArgs,
|
||||||
|
projection: buildProjectionFromSelect({ adapter: this, fields, select }),
|
||||||
|
returnDocument: 'after',
|
||||||
|
session,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
result = JSON.parse(JSON.stringify(result))
|
transform({
|
||||||
|
adapter: this,
|
||||||
// custom id type reset
|
data: result,
|
||||||
result.id = result._id
|
fields,
|
||||||
result = sanitizeInternalFields(result)
|
operation: 'read',
|
||||||
|
})
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import type { QueryOptions } from 'mongoose'
|
|
||||||
|
|
||||||
import { buildVersionGlobalFields, type TypeWithID, type UpdateGlobalVersionArgs } from 'payload'
|
import { buildVersionGlobalFields, type TypeWithID, type UpdateGlobalVersionArgs } from 'payload'
|
||||||
|
|
||||||
import type { MongooseAdapter } from './index.js'
|
import type { MongooseAdapter } from './index.js'
|
||||||
|
|
||||||
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
||||||
import { getSession } from './utilities/getSession.js'
|
import { getSession } from './utilities/getSession.js'
|
||||||
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'
|
import { transform } from './utilities/transform.js'
|
||||||
|
|
||||||
export async function updateGlobalVersion<T extends TypeWithID>(
|
export async function updateGlobalVersion<T extends TypeWithID>(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
@@ -23,44 +21,50 @@ export async function updateGlobalVersion<T extends TypeWithID>(
|
|||||||
) {
|
) {
|
||||||
const VersionModel = this.versions[globalSlug]
|
const VersionModel = this.versions[globalSlug]
|
||||||
const whereToUse = where || { id: { equals: id } }
|
const whereToUse = where || { id: { equals: id } }
|
||||||
|
const fields = buildVersionGlobalFields(
|
||||||
|
this.payload.config,
|
||||||
|
this.payload.config.globals.find((global) => global.slug === globalSlug),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
|
||||||
const currentGlobal = this.payload.config.globals.find((global) => global.slug === globalSlug)
|
const session = await getSession(this, req)
|
||||||
const fields = buildVersionGlobalFields(this.payload.config, currentGlobal)
|
|
||||||
|
|
||||||
const options: QueryOptions = {
|
|
||||||
...optionsArgs,
|
|
||||||
lean: true,
|
|
||||||
new: true,
|
|
||||||
projection: buildProjectionFromSelect({
|
|
||||||
adapter: this,
|
|
||||||
fields: buildVersionGlobalFields(this.payload.config, currentGlobal, true),
|
|
||||||
select,
|
|
||||||
}),
|
|
||||||
session: await getSession(this, req),
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = await VersionModel.buildQuery({
|
const query = await VersionModel.buildQuery({
|
||||||
locale,
|
locale,
|
||||||
payload: this.payload,
|
payload: this.payload,
|
||||||
|
session,
|
||||||
where: whereToUse,
|
where: whereToUse,
|
||||||
})
|
})
|
||||||
|
|
||||||
const sanitizedData = sanitizeRelationshipIDs({
|
transform({
|
||||||
config: this.payload.config,
|
adapter: this,
|
||||||
data: versionData,
|
data: versionData,
|
||||||
fields,
|
fields,
|
||||||
|
operation: 'update',
|
||||||
|
timestamps: optionsArgs.timestamps !== false,
|
||||||
})
|
})
|
||||||
|
|
||||||
const doc = await VersionModel.findOneAndUpdate(query, sanitizedData, options)
|
const doc: any = await VersionModel.collection.findOneAndUpdate(
|
||||||
|
query,
|
||||||
|
{ $set: versionData },
|
||||||
|
{
|
||||||
|
...optionsArgs,
|
||||||
|
projection: buildProjectionFromSelect({
|
||||||
|
adapter: this,
|
||||||
|
fields,
|
||||||
|
select,
|
||||||
|
}),
|
||||||
|
returnDocument: 'after',
|
||||||
|
session,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
const result = JSON.parse(JSON.stringify(doc))
|
transform({
|
||||||
|
adapter: this,
|
||||||
|
data: doc,
|
||||||
|
fields,
|
||||||
|
operation: 'read',
|
||||||
|
})
|
||||||
|
|
||||||
const verificationToken = doc._verificationToken
|
return doc
|
||||||
|
|
||||||
// custom id type reset
|
|
||||||
result.id = result._id
|
|
||||||
if (verificationToken) {
|
|
||||||
result._verificationToken = verificationToken
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import type { QueryOptions } from 'mongoose'
|
|
||||||
import type { UpdateOne } from 'payload'
|
import type { UpdateOne } from 'payload'
|
||||||
|
|
||||||
import type { MongooseAdapter } from './index.js'
|
import type { MongooseAdapter } from './index.js'
|
||||||
@@ -6,8 +5,7 @@ import type { MongooseAdapter } from './index.js'
|
|||||||
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
||||||
import { getSession } from './utilities/getSession.js'
|
import { getSession } from './utilities/getSession.js'
|
||||||
import { handleError } from './utilities/handleError.js'
|
import { handleError } from './utilities/handleError.js'
|
||||||
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
import { transform } from './utilities/transform.js'
|
||||||
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'
|
|
||||||
|
|
||||||
export const updateOne: UpdateOne = async function updateOne(
|
export const updateOne: UpdateOne = async function updateOne(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
@@ -15,42 +13,45 @@ export const updateOne: UpdateOne = async function updateOne(
|
|||||||
) {
|
) {
|
||||||
const where = id ? { id: { equals: id } } : whereArg
|
const where = id ? { id: { equals: id } } : whereArg
|
||||||
const Model = this.collections[collection]
|
const Model = this.collections[collection]
|
||||||
const fields = this.payload.collections[collection].config.fields
|
const fields = this.payload.collections[collection].config.flattenedFields
|
||||||
const options: QueryOptions = {
|
|
||||||
...optionsArgs,
|
const session = await getSession(this, req)
|
||||||
lean: true,
|
|
||||||
new: true,
|
|
||||||
projection: buildProjectionFromSelect({
|
|
||||||
adapter: this,
|
|
||||||
fields: this.payload.collections[collection].config.flattenedFields,
|
|
||||||
select,
|
|
||||||
}),
|
|
||||||
session: await getSession(this, req),
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = await Model.buildQuery({
|
const query = await Model.buildQuery({
|
||||||
locale,
|
locale,
|
||||||
payload: this.payload,
|
payload: this.payload,
|
||||||
|
session,
|
||||||
where,
|
where,
|
||||||
})
|
})
|
||||||
|
|
||||||
let result
|
transform({
|
||||||
|
adapter: this,
|
||||||
const sanitizedData = sanitizeRelationshipIDs({
|
|
||||||
config: this.payload.config,
|
|
||||||
data,
|
data,
|
||||||
fields,
|
fields,
|
||||||
|
operation: 'update',
|
||||||
|
timestamps: optionsArgs.timestamps !== false,
|
||||||
})
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
result = await Model.findOneAndUpdate(query, sanitizedData, options)
|
const result = await Model.collection.findOneAndUpdate(
|
||||||
|
query,
|
||||||
|
{ $set: data },
|
||||||
|
{
|
||||||
|
...optionsArgs,
|
||||||
|
projection: buildProjectionFromSelect({ adapter: this, fields, select }),
|
||||||
|
returnDocument: 'after',
|
||||||
|
session,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
transform({
|
||||||
|
adapter: this,
|
||||||
|
data: result,
|
||||||
|
fields,
|
||||||
|
operation: 'read',
|
||||||
|
})
|
||||||
|
return result
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError({ collection, error, req })
|
handleError({ collection, error, req })
|
||||||
}
|
}
|
||||||
|
|
||||||
result = JSON.parse(JSON.stringify(result))
|
|
||||||
result.id = result._id
|
|
||||||
result = sanitizeInternalFields(result)
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import type { QueryOptions } from 'mongoose'
|
|
||||||
|
|
||||||
import { buildVersionCollectionFields, type UpdateVersion } from 'payload'
|
import { buildVersionCollectionFields, type UpdateVersion } from 'payload'
|
||||||
|
|
||||||
import type { MongooseAdapter } from './index.js'
|
import type { MongooseAdapter } from './index.js'
|
||||||
|
|
||||||
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
||||||
import { getSession } from './utilities/getSession.js'
|
import { getSession } from './utilities/getSession.js'
|
||||||
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'
|
import { transform } from './utilities/transform.js'
|
||||||
|
|
||||||
export const updateVersion: UpdateVersion = async function updateVersion(
|
export const updateVersion: UpdateVersion = async function updateVersion(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
@@ -17,46 +15,51 @@ export const updateVersion: UpdateVersion = async function updateVersion(
|
|||||||
const fields = buildVersionCollectionFields(
|
const fields = buildVersionCollectionFields(
|
||||||
this.payload.config,
|
this.payload.config,
|
||||||
this.payload.collections[collection].config,
|
this.payload.collections[collection].config,
|
||||||
|
true,
|
||||||
)
|
)
|
||||||
|
|
||||||
const options: QueryOptions = {
|
const session = await getSession(this, req)
|
||||||
...optionsArgs,
|
|
||||||
lean: true,
|
|
||||||
new: true,
|
|
||||||
projection: buildProjectionFromSelect({
|
|
||||||
adapter: this,
|
|
||||||
fields: buildVersionCollectionFields(
|
|
||||||
this.payload.config,
|
|
||||||
this.payload.collections[collection].config,
|
|
||||||
true,
|
|
||||||
),
|
|
||||||
select,
|
|
||||||
}),
|
|
||||||
session: await getSession(this, req),
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = await VersionModel.buildQuery({
|
const query = await VersionModel.buildQuery({
|
||||||
locale,
|
locale,
|
||||||
payload: this.payload,
|
payload: this.payload,
|
||||||
|
session,
|
||||||
where: whereToUse,
|
where: whereToUse,
|
||||||
})
|
})
|
||||||
|
|
||||||
const sanitizedData = sanitizeRelationshipIDs({
|
transform({
|
||||||
config: this.payload.config,
|
adapter: this,
|
||||||
data: versionData,
|
data: versionData,
|
||||||
fields,
|
fields,
|
||||||
|
operation: 'update',
|
||||||
|
timestamps: optionsArgs.timestamps !== false,
|
||||||
})
|
})
|
||||||
|
|
||||||
const doc = await VersionModel.findOneAndUpdate(query, sanitizedData, options)
|
const doc = await VersionModel.collection.findOneAndUpdate(
|
||||||
|
query,
|
||||||
|
{ $set: versionData },
|
||||||
|
{
|
||||||
|
...optionsArgs,
|
||||||
|
projection: buildProjectionFromSelect({
|
||||||
|
adapter: this,
|
||||||
|
fields: buildVersionCollectionFields(
|
||||||
|
this.payload.config,
|
||||||
|
this.payload.collections[collection].config,
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
select,
|
||||||
|
}),
|
||||||
|
returnDocument: 'after',
|
||||||
|
session,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
const result = JSON.parse(JSON.stringify(doc))
|
transform({
|
||||||
|
adapter: this,
|
||||||
|
data: doc,
|
||||||
|
fields,
|
||||||
|
operation: 'read',
|
||||||
|
})
|
||||||
|
|
||||||
const verificationToken = doc._verificationToken
|
return doc as any
|
||||||
|
|
||||||
// custom id type reset
|
|
||||||
result.id = result._id
|
|
||||||
if (verificationToken) {
|
|
||||||
result._verificationToken = verificationToken
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { ClientSession } from 'mongodb'
|
||||||
import type { PipelineStage } from 'mongoose'
|
import type { PipelineStage } from 'mongoose'
|
||||||
import type { CollectionSlug, JoinQuery, SanitizedCollectionConfig, Where } from 'payload'
|
import type { CollectionSlug, JoinQuery, SanitizedCollectionConfig, Where } from 'payload'
|
||||||
|
|
||||||
@@ -10,12 +11,9 @@ type BuildJoinAggregationArgs = {
|
|||||||
collection: CollectionSlug
|
collection: CollectionSlug
|
||||||
collectionConfig: SanitizedCollectionConfig
|
collectionConfig: SanitizedCollectionConfig
|
||||||
joins: JoinQuery
|
joins: JoinQuery
|
||||||
// the number of docs to get at the top collection level
|
|
||||||
limit?: number
|
|
||||||
locale: string
|
locale: string
|
||||||
projection?: Record<string, true>
|
projection?: Record<string, true>
|
||||||
// the where clause for the top collection
|
session?: ClientSession
|
||||||
query?: Where
|
|
||||||
/** whether the query is from drafts */
|
/** whether the query is from drafts */
|
||||||
versions?: boolean
|
versions?: boolean
|
||||||
}
|
}
|
||||||
@@ -25,10 +23,9 @@ export const buildJoinAggregation = async ({
|
|||||||
collection,
|
collection,
|
||||||
collectionConfig,
|
collectionConfig,
|
||||||
joins,
|
joins,
|
||||||
limit,
|
|
||||||
locale,
|
locale,
|
||||||
projection,
|
projection,
|
||||||
query,
|
session,
|
||||||
versions,
|
versions,
|
||||||
}: BuildJoinAggregationArgs): Promise<PipelineStage[] | undefined> => {
|
}: BuildJoinAggregationArgs): Promise<PipelineStage[] | undefined> => {
|
||||||
if (Object.keys(collectionConfig.joins).length === 0 || joins === false) {
|
if (Object.keys(collectionConfig.joins).length === 0 || joins === false) {
|
||||||
@@ -36,23 +33,7 @@ export const buildJoinAggregation = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const joinConfig = adapter.payload.collections[collection].config.joins
|
const joinConfig = adapter.payload.collections[collection].config.joins
|
||||||
const aggregate: PipelineStage[] = [
|
const aggregate: PipelineStage[] = []
|
||||||
{
|
|
||||||
$sort: { createdAt: -1 },
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
if (query) {
|
|
||||||
aggregate.push({
|
|
||||||
$match: query,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (limit) {
|
|
||||||
aggregate.push({
|
|
||||||
$limit: limit,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const slug of Object.keys(joinConfig)) {
|
for (const slug of Object.keys(joinConfig)) {
|
||||||
for (const join of joinConfig[slug]) {
|
for (const join of joinConfig[slug]) {
|
||||||
@@ -72,26 +53,25 @@ export const buildJoinAggregation = async ({
|
|||||||
where: whereJoin,
|
where: whereJoin,
|
||||||
} = joins?.[join.joinPath] || {}
|
} = joins?.[join.joinPath] || {}
|
||||||
|
|
||||||
const sort = buildSortParam({
|
const $sort = buildSortParam({
|
||||||
config: adapter.payload.config,
|
config: adapter.payload.config,
|
||||||
fields: adapter.payload.collections[slug].config.flattenedFields,
|
fields: adapter.payload.collections[slug].config.flattenedFields,
|
||||||
locale,
|
locale,
|
||||||
sort: sortJoin,
|
sort: sortJoin,
|
||||||
timestamps: true,
|
timestamps: true,
|
||||||
})
|
})
|
||||||
const sortProperty = Object.keys(sort)[0]
|
|
||||||
const sortDirection = sort[sortProperty] === 'asc' ? 1 : -1
|
|
||||||
|
|
||||||
const $match = await joinModel.buildQuery({
|
const $match = await joinModel.buildQuery({
|
||||||
locale,
|
locale,
|
||||||
payload: adapter.payload,
|
payload: adapter.payload,
|
||||||
|
session,
|
||||||
where: whereJoin,
|
where: whereJoin,
|
||||||
})
|
})
|
||||||
|
|
||||||
const pipeline: Exclude<PipelineStage, PipelineStage.Merge | PipelineStage.Out>[] = [
|
const pipeline: Exclude<PipelineStage, PipelineStage.Merge | PipelineStage.Out>[] = [
|
||||||
{ $match },
|
{ $match },
|
||||||
{
|
{
|
||||||
$sort: { [sortProperty]: sortDirection },
|
$sort,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -184,8 +164,8 @@ export const buildJoinAggregation = async ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (projection) {
|
if (!aggregate.length) {
|
||||||
aggregate.push({ $project: projection })
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return aggregate
|
return aggregate
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { FieldAffectingData, FlattenedField, SelectMode, SelectType } from 'payload'
|
import type { Field, FieldAffectingData, FlattenedField, SelectMode, SelectType } from 'payload'
|
||||||
|
|
||||||
import { deepCopyObjectSimple, fieldAffectsData, getSelectMode } from 'payload/shared'
|
import { deepCopyObjectSimple, fieldAffectsData, getSelectMode } from 'payload/shared'
|
||||||
|
|
||||||
@@ -29,6 +29,11 @@ const addFieldToProjection = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const blockTypeField: Field = {
|
||||||
|
name: 'blockType',
|
||||||
|
type: 'text',
|
||||||
|
}
|
||||||
|
|
||||||
const traverseFields = ({
|
const traverseFields = ({
|
||||||
adapter,
|
adapter,
|
||||||
databaseSchemaPath = '',
|
databaseSchemaPath = '',
|
||||||
@@ -128,6 +133,14 @@ const traverseFields = ({
|
|||||||
(selectMode === 'include' && blocksSelect[block.slug] === true) ||
|
(selectMode === 'include' && blocksSelect[block.slug] === true) ||
|
||||||
(selectMode === 'exclude' && typeof blocksSelect[block.slug] === 'undefined')
|
(selectMode === 'exclude' && typeof blocksSelect[block.slug] === 'undefined')
|
||||||
) {
|
) {
|
||||||
|
addFieldToProjection({
|
||||||
|
adapter,
|
||||||
|
databaseSchemaPath: fieldDatabaseSchemaPath,
|
||||||
|
field: blockTypeField,
|
||||||
|
projection,
|
||||||
|
withinLocalizedField: fieldWithinLocalizedField,
|
||||||
|
})
|
||||||
|
|
||||||
traverseFields({
|
traverseFields({
|
||||||
adapter,
|
adapter,
|
||||||
databaseSchemaPath: fieldDatabaseSchemaPath,
|
databaseSchemaPath: fieldDatabaseSchemaPath,
|
||||||
@@ -153,7 +166,13 @@ const traverseFields = ({
|
|||||||
|
|
||||||
if (blockSelectMode === 'include') {
|
if (blockSelectMode === 'include') {
|
||||||
blocksSelect[block.slug]['id'] = true
|
blocksSelect[block.slug]['id'] = true
|
||||||
blocksSelect[block.slug]['blockType'] = true
|
addFieldToProjection({
|
||||||
|
adapter,
|
||||||
|
databaseSchemaPath: fieldDatabaseSchemaPath,
|
||||||
|
field: blockTypeField,
|
||||||
|
projection,
|
||||||
|
withinLocalizedField: fieldWithinLocalizedField,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
traverseFields({
|
traverseFields({
|
||||||
|
|||||||
128
packages/db-mongodb/src/utilities/findMany.ts
Normal file
128
packages/db-mongodb/src/utilities/findMany.ts
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import type { ClientSession, CollationOptions, Collection, Document } from 'mongodb'
|
||||||
|
import type { PipelineStage } from 'mongoose'
|
||||||
|
import type { PaginatedDocs } from 'payload'
|
||||||
|
|
||||||
|
import type { MongooseAdapter } from '../index.js'
|
||||||
|
|
||||||
|
export const findMany = async ({
|
||||||
|
adapter,
|
||||||
|
collation,
|
||||||
|
collection,
|
||||||
|
joinAgreggation,
|
||||||
|
limit,
|
||||||
|
page = 1,
|
||||||
|
pagination,
|
||||||
|
projection,
|
||||||
|
query = {},
|
||||||
|
session,
|
||||||
|
skip,
|
||||||
|
sort,
|
||||||
|
useEstimatedCount,
|
||||||
|
}: {
|
||||||
|
adapter: MongooseAdapter
|
||||||
|
collation?: CollationOptions
|
||||||
|
collection: Collection
|
||||||
|
joinAgreggation?: PipelineStage[]
|
||||||
|
limit?: number
|
||||||
|
page?: number
|
||||||
|
pagination?: boolean
|
||||||
|
projection?: Record<string, unknown>
|
||||||
|
query?: Record<string, unknown>
|
||||||
|
session?: ClientSession
|
||||||
|
skip?: number
|
||||||
|
sort?: Record<string, -1 | 1>
|
||||||
|
useEstimatedCount?: boolean
|
||||||
|
}): Promise<PaginatedDocs> => {
|
||||||
|
if (!skip) {
|
||||||
|
skip = (page - 1) * (limit ?? 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
let docsPromise: Promise<Document[]>
|
||||||
|
let countPromise: Promise<null | number> = Promise.resolve(null)
|
||||||
|
|
||||||
|
if (joinAgreggation) {
|
||||||
|
const aggregation = collection.aggregate(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
$match: query,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{ collation, session },
|
||||||
|
)
|
||||||
|
|
||||||
|
if (sort) {
|
||||||
|
aggregation.sort(sort)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skip) {
|
||||||
|
aggregation.skip(skip)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (limit) {
|
||||||
|
aggregation.limit(limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const stage of joinAgreggation) {
|
||||||
|
aggregation.addStage(stage)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (projection) {
|
||||||
|
aggregation.project(projection)
|
||||||
|
}
|
||||||
|
|
||||||
|
docsPromise = aggregation.toArray()
|
||||||
|
} else {
|
||||||
|
docsPromise = collection
|
||||||
|
.find(query, {
|
||||||
|
collation,
|
||||||
|
limit,
|
||||||
|
projection,
|
||||||
|
session,
|
||||||
|
skip,
|
||||||
|
sort,
|
||||||
|
})
|
||||||
|
.toArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pagination !== false && limit) {
|
||||||
|
if (useEstimatedCount) {
|
||||||
|
countPromise = collection.estimatedDocumentCount()
|
||||||
|
} else {
|
||||||
|
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
|
||||||
|
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
|
||||||
|
// which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses
|
||||||
|
// the correct indexed field
|
||||||
|
|
||||||
|
const hint = adapter.disableIndexHints !== true ? { _id: 1 } : undefined
|
||||||
|
|
||||||
|
countPromise = collection.countDocuments(query, { collation, hint, session })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [docs, countResult] = await Promise.all([docsPromise, countPromise])
|
||||||
|
|
||||||
|
const count = countResult === null ? docs.length : countResult
|
||||||
|
|
||||||
|
const totalPages =
|
||||||
|
pagination !== false && typeof limit === 'number' && limit !== 0 ? Math.ceil(count / limit) : 1
|
||||||
|
|
||||||
|
const hasPrevPage = pagination !== false && page > 1
|
||||||
|
const hasNextPage = pagination !== false && totalPages > page
|
||||||
|
const pagingCounter =
|
||||||
|
pagination !== false && typeof limit === 'number' ? (page - 1) * limit + 1 : 1
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
docs,
|
||||||
|
hasNextPage,
|
||||||
|
hasPrevPage,
|
||||||
|
limit,
|
||||||
|
nextPage: hasNextPage ? page + 1 : null,
|
||||||
|
page,
|
||||||
|
pagingCounter,
|
||||||
|
prevPage: hasPrevPage ? page - 1 : null,
|
||||||
|
totalDocs: count,
|
||||||
|
totalPages,
|
||||||
|
} as PaginatedDocs<Record<string, unknown>>
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
27
packages/db-mongodb/src/utilities/getHasNearConstraint.ts
Normal file
27
packages/db-mongodb/src/utilities/getHasNearConstraint.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import type { Where } from 'payload'
|
||||||
|
|
||||||
|
export const getHasNearConstraint = (where?: Where): boolean => {
|
||||||
|
if (!where) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key in where) {
|
||||||
|
const value = where[key]
|
||||||
|
|
||||||
|
if (Array.isArray(value) && ['AND', 'OR'].includes(key.toUpperCase())) {
|
||||||
|
for (const where of value) {
|
||||||
|
if (getHasNearConstraint(where)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key in value) {
|
||||||
|
if (key === 'near') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
const internalFields = ['__v']
|
|
||||||
|
|
||||||
export const sanitizeInternalFields = <T extends Record<string, unknown>>(incomingDoc: T): T =>
|
|
||||||
Object.entries(incomingDoc).reduce((newDoc, [key, val]): T => {
|
|
||||||
if (key === '_id') {
|
|
||||||
return {
|
|
||||||
...newDoc,
|
|
||||||
id: val,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (internalFields.indexOf(key) > -1) {
|
|
||||||
return newDoc
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...newDoc,
|
|
||||||
[key]: val,
|
|
||||||
}
|
|
||||||
}, {} as T)
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
import type { CollectionConfig, Field, SanitizedConfig, TraverseFieldsCallback } from 'payload'
|
|
||||||
|
|
||||||
import { Types } from 'mongoose'
|
|
||||||
import { APIError, traverseFields } from 'payload'
|
|
||||||
import { fieldAffectsData } from 'payload/shared'
|
|
||||||
|
|
||||||
type Args = {
|
|
||||||
config: SanitizedConfig
|
|
||||||
data: Record<string, unknown>
|
|
||||||
fields: Field[]
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RelationObject {
|
|
||||||
relationTo: string
|
|
||||||
value: number | string
|
|
||||||
}
|
|
||||||
|
|
||||||
function isValidRelationObject(value: unknown): value is RelationObject {
|
|
||||||
return typeof value === 'object' && value !== null && 'relationTo' in value && 'value' in value
|
|
||||||
}
|
|
||||||
|
|
||||||
const convertValue = ({
|
|
||||||
relatedCollection,
|
|
||||||
value,
|
|
||||||
}: {
|
|
||||||
relatedCollection: CollectionConfig
|
|
||||||
value: number | string
|
|
||||||
}): number | string | Types.ObjectId => {
|
|
||||||
const customIDField = relatedCollection.fields.find(
|
|
||||||
(field) => fieldAffectsData(field) && field.name === 'id',
|
|
||||||
)
|
|
||||||
|
|
||||||
if (customIDField) {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return new Types.ObjectId(value)
|
|
||||||
} catch {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const sanitizeRelationship = ({ config, field, locale, ref, value }) => {
|
|
||||||
let relatedCollection: CollectionConfig | undefined
|
|
||||||
let result = value
|
|
||||||
|
|
||||||
const hasManyRelations = typeof field.relationTo !== 'string'
|
|
||||||
|
|
||||||
if (!hasManyRelations) {
|
|
||||||
relatedCollection = config.collections?.find(({ slug }) => slug === field.relationTo)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
result = value.map((val) => {
|
|
||||||
// Handle has many
|
|
||||||
if (relatedCollection && val && (typeof val === 'string' || typeof val === 'number')) {
|
|
||||||
return convertValue({
|
|
||||||
relatedCollection,
|
|
||||||
value: val,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle has many - polymorphic
|
|
||||||
if (isValidRelationObject(val)) {
|
|
||||||
const relatedCollectionForSingleValue = config.collections?.find(
|
|
||||||
({ slug }) => slug === val.relationTo,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (relatedCollectionForSingleValue) {
|
|
||||||
return {
|
|
||||||
relationTo: val.relationTo,
|
|
||||||
value: convertValue({
|
|
||||||
relatedCollection: relatedCollectionForSingleValue,
|
|
||||||
value: val.value,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return val
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle has one - polymorphic
|
|
||||||
if (isValidRelationObject(value)) {
|
|
||||||
relatedCollection = config.collections?.find(({ slug }) => slug === value.relationTo)
|
|
||||||
|
|
||||||
if (relatedCollection) {
|
|
||||||
result = {
|
|
||||||
relationTo: value.relationTo,
|
|
||||||
value: convertValue({ relatedCollection, value: value.value }),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle has one
|
|
||||||
if (relatedCollection && value && (typeof value === 'string' || typeof value === 'number')) {
|
|
||||||
result = convertValue({
|
|
||||||
relatedCollection,
|
|
||||||
value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (locale) {
|
|
||||||
ref[locale] = result
|
|
||||||
} else {
|
|
||||||
ref[field.name] = result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const sanitizeRelationshipIDs = ({
|
|
||||||
config,
|
|
||||||
data,
|
|
||||||
fields,
|
|
||||||
}: Args): Record<string, unknown> => {
|
|
||||||
const sanitize: TraverseFieldsCallback = ({ field, ref }) => {
|
|
||||||
if (!ref || typeof ref !== 'object') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (field.type === 'relationship' || field.type === 'upload') {
|
|
||||||
if (!ref[field.name]) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle localized relationships
|
|
||||||
if (config.localization && field.localized) {
|
|
||||||
const locales = config.localization.locales
|
|
||||||
const fieldRef = ref[field.name]
|
|
||||||
if (typeof fieldRef !== 'object') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const { code } of locales) {
|
|
||||||
const value = ref[field.name][code]
|
|
||||||
if (value) {
|
|
||||||
sanitizeRelationship({ config, field, locale: code, ref: fieldRef, value })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// handle non-localized relationships
|
|
||||||
sanitizeRelationship({
|
|
||||||
config,
|
|
||||||
field,
|
|
||||||
locale: undefined,
|
|
||||||
ref,
|
|
||||||
value: ref[field.name],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
traverseFields({ callback: sanitize, fields, fillEmpty: false, ref: data })
|
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import type { Field, SanitizedConfig } from 'payload'
|
import { flattenAllFields, type Field, type SanitizedConfig } from 'payload'
|
||||||
|
|
||||||
import { Types } from 'mongoose'
|
import { Types } from 'mongoose'
|
||||||
|
|
||||||
import { sanitizeRelationshipIDs } from './sanitizeRelationshipIDs.js'
|
import { transform } from './transform.js'
|
||||||
|
import { MongooseAdapter } from '..'
|
||||||
|
|
||||||
const flattenRelationshipValues = (obj: Record<string, any>, prefix = ''): Record<string, any> => {
|
const flattenRelationshipValues = (obj: Record<string, any>, prefix = ''): Record<string, any> => {
|
||||||
return Object.keys(obj).reduce(
|
return Object.keys(obj).reduce(
|
||||||
@@ -271,8 +272,8 @@ const relsData = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('sanitizeRelationshipIDs', () => {
|
describe('transform', () => {
|
||||||
it('should sanitize relationships', () => {
|
it('should sanitize relationships with transform write', () => {
|
||||||
const data = {
|
const data = {
|
||||||
...relsData,
|
...relsData,
|
||||||
array: [
|
array: [
|
||||||
@@ -348,12 +349,19 @@ describe('sanitizeRelationshipIDs', () => {
|
|||||||
}
|
}
|
||||||
const flattenValuesBefore = Object.values(flattenRelationshipValues(data))
|
const flattenValuesBefore = Object.values(flattenRelationshipValues(data))
|
||||||
|
|
||||||
sanitizeRelationshipIDs({ config, data, fields: config.collections[0].fields })
|
const mockAdapter = { payload: { config } } as MongooseAdapter
|
||||||
|
|
||||||
|
const fields = flattenAllFields({ fields: config.collections[0].fields })
|
||||||
|
|
||||||
|
transform({ type: 'write', adapter: mockAdapter, data, fields })
|
||||||
|
|
||||||
const flattenValuesAfter = Object.values(flattenRelationshipValues(data))
|
const flattenValuesAfter = Object.values(flattenRelationshipValues(data))
|
||||||
|
|
||||||
flattenValuesAfter.forEach((value, i) => {
|
flattenValuesAfter.forEach((value, i) => {
|
||||||
expect(value).toBeInstanceOf(Types.ObjectId)
|
expect(value).toBeInstanceOf(Types.ObjectId)
|
||||||
expect(flattenValuesBefore[i]).toBe(value.toHexString())
|
expect(flattenValuesBefore[i]).toBe(value.toHexString())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
transform({ type: 'read', adapter: mockAdapter, data, fields })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
385
packages/db-mongodb/src/utilities/transform.ts
Normal file
385
packages/db-mongodb/src/utilities/transform.ts
Normal file
@@ -0,0 +1,385 @@
|
|||||||
|
import type {
|
||||||
|
CollectionConfig,
|
||||||
|
DateField,
|
||||||
|
FlattenedField,
|
||||||
|
JoinField,
|
||||||
|
RelationshipField,
|
||||||
|
SanitizedConfig,
|
||||||
|
TraverseFlattenedFieldsCallback,
|
||||||
|
UploadField,
|
||||||
|
} from 'payload'
|
||||||
|
|
||||||
|
import { Types } from 'mongoose'
|
||||||
|
import { traverseFields } from 'payload'
|
||||||
|
import { fieldAffectsData, fieldIsVirtual } from 'payload/shared'
|
||||||
|
|
||||||
|
import type { MongooseAdapter } from '../index.js'
|
||||||
|
|
||||||
|
type Args = {
|
||||||
|
adapter: MongooseAdapter
|
||||||
|
data: Record<string, unknown> | Record<string, unknown>[]
|
||||||
|
fields: FlattenedField[]
|
||||||
|
globalSlug?: string
|
||||||
|
operation: 'create' | 'read' | 'update'
|
||||||
|
/**
|
||||||
|
* Set updatedAt and createdAt
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
timestamps?: boolean
|
||||||
|
/**
|
||||||
|
* Throw errors on invalid relationships
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
validateRelationships?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RelationObject {
|
||||||
|
relationTo: string
|
||||||
|
value: number | string
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidRelationObject(value: unknown): value is RelationObject {
|
||||||
|
return typeof value === 'object' && value !== null && 'relationTo' in value && 'value' in value
|
||||||
|
}
|
||||||
|
|
||||||
|
const convertRelationshipValue = ({
|
||||||
|
operation,
|
||||||
|
relatedCollection,
|
||||||
|
validateRelationships,
|
||||||
|
value,
|
||||||
|
}: {
|
||||||
|
operation: Args['operation']
|
||||||
|
relatedCollection: CollectionConfig
|
||||||
|
validateRelationships?: boolean
|
||||||
|
value: unknown
|
||||||
|
}) => {
|
||||||
|
const customIDField = relatedCollection.fields.find(
|
||||||
|
(field) => fieldAffectsData(field) && field.name === 'id',
|
||||||
|
)
|
||||||
|
|
||||||
|
if (operation === 'read') {
|
||||||
|
if (value instanceof Types.ObjectId) {
|
||||||
|
return value.toHexString()
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
if (customIDField) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
try {
|
||||||
|
return new Types.ObjectId(value)
|
||||||
|
} catch (e) {
|
||||||
|
if (validateRelationships) {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
const sanitizeRelationship = ({
|
||||||
|
config,
|
||||||
|
field,
|
||||||
|
locale,
|
||||||
|
operation,
|
||||||
|
ref,
|
||||||
|
validateRelationships,
|
||||||
|
value,
|
||||||
|
}: {
|
||||||
|
config: SanitizedConfig
|
||||||
|
field: JoinField | RelationshipField | UploadField
|
||||||
|
locale?: string
|
||||||
|
operation: Args['operation']
|
||||||
|
ref: Record<string, unknown>
|
||||||
|
validateRelationships?: boolean
|
||||||
|
value?: unknown
|
||||||
|
}) => {
|
||||||
|
if (field.type === 'join') {
|
||||||
|
if (
|
||||||
|
operation === 'read' &&
|
||||||
|
value &&
|
||||||
|
typeof value === 'object' &&
|
||||||
|
'docs' in value &&
|
||||||
|
Array.isArray(value.docs)
|
||||||
|
) {
|
||||||
|
for (let i = 0; i < value.docs.length; i++) {
|
||||||
|
const item = value.docs[i]
|
||||||
|
if (item instanceof Types.ObjectId) {
|
||||||
|
value.docs[i] = item.toHexString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
let relatedCollection: CollectionConfig | undefined
|
||||||
|
let result = value
|
||||||
|
|
||||||
|
const hasManyRelations = typeof field.relationTo !== 'string'
|
||||||
|
|
||||||
|
if (!hasManyRelations) {
|
||||||
|
relatedCollection = config.collections?.find(({ slug }) => slug === field.relationTo)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
result = value.map((val) => {
|
||||||
|
// Handle has many - polymorphic
|
||||||
|
if (isValidRelationObject(val)) {
|
||||||
|
const relatedCollectionForSingleValue = config.collections?.find(
|
||||||
|
({ slug }) => slug === val.relationTo,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (relatedCollectionForSingleValue) {
|
||||||
|
return {
|
||||||
|
relationTo: val.relationTo,
|
||||||
|
value: convertRelationshipValue({
|
||||||
|
operation,
|
||||||
|
relatedCollection: relatedCollectionForSingleValue,
|
||||||
|
validateRelationships,
|
||||||
|
value: val.value,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relatedCollection) {
|
||||||
|
return convertRelationshipValue({
|
||||||
|
operation,
|
||||||
|
relatedCollection,
|
||||||
|
validateRelationships,
|
||||||
|
value: val,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return val
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// Handle has one - polymorphic
|
||||||
|
else if (isValidRelationObject(value)) {
|
||||||
|
relatedCollection = config.collections?.find(({ slug }) => slug === value.relationTo)
|
||||||
|
|
||||||
|
if (relatedCollection) {
|
||||||
|
result = {
|
||||||
|
relationTo: value.relationTo,
|
||||||
|
value: convertRelationshipValue({
|
||||||
|
operation,
|
||||||
|
relatedCollection,
|
||||||
|
validateRelationships,
|
||||||
|
value: value.value,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Handle has one
|
||||||
|
else if (relatedCollection) {
|
||||||
|
result = convertRelationshipValue({
|
||||||
|
operation,
|
||||||
|
relatedCollection,
|
||||||
|
validateRelationships,
|
||||||
|
value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (locale) {
|
||||||
|
ref[locale] = result
|
||||||
|
} else {
|
||||||
|
ref[field.name] = result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When sending data to Payload - convert Date to string.
|
||||||
|
* Vice versa when sending data to MongoDB so dates are stored properly.
|
||||||
|
*/
|
||||||
|
const sanitizeDate = ({
|
||||||
|
field,
|
||||||
|
locale,
|
||||||
|
operation,
|
||||||
|
ref,
|
||||||
|
value,
|
||||||
|
}: {
|
||||||
|
field: DateField
|
||||||
|
locale?: string
|
||||||
|
operation: Args['operation']
|
||||||
|
ref: Record<string, unknown>
|
||||||
|
value: unknown
|
||||||
|
}) => {
|
||||||
|
if (!value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation === 'read') {
|
||||||
|
if (value instanceof Date) {
|
||||||
|
value = value.toISOString()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
value = new Date(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (locale) {
|
||||||
|
ref[locale] = value
|
||||||
|
} else {
|
||||||
|
ref[field.name] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @experimental This API can be changed without a major version bump.
|
||||||
|
*/
|
||||||
|
export const transform = ({
|
||||||
|
adapter,
|
||||||
|
data,
|
||||||
|
fields,
|
||||||
|
globalSlug,
|
||||||
|
operation,
|
||||||
|
timestamps = true,
|
||||||
|
validateRelationships = true,
|
||||||
|
}: Args) => {
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
transform({ adapter, data: data[i], fields, globalSlug, operation, validateRelationships })
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
payload: { config },
|
||||||
|
} = adapter
|
||||||
|
|
||||||
|
if (operation === 'read') {
|
||||||
|
delete data['__v']
|
||||||
|
data.id = data._id
|
||||||
|
delete data['_id']
|
||||||
|
|
||||||
|
if (data.id instanceof Types.ObjectId) {
|
||||||
|
data.id = data.id.toHexString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation !== 'read') {
|
||||||
|
if (timestamps) {
|
||||||
|
if (operation === 'create' && !data.createdAt) {
|
||||||
|
data.createdAt = new Date()
|
||||||
|
}
|
||||||
|
|
||||||
|
data.updatedAt = new Date()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (globalSlug) {
|
||||||
|
data.globalType = globalSlug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sanitize: TraverseFlattenedFieldsCallback = ({ field, ref }) => {
|
||||||
|
if (!ref || typeof ref !== 'object') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation !== 'read') {
|
||||||
|
if (
|
||||||
|
typeof ref[field.name] === 'undefined' &&
|
||||||
|
typeof field.defaultValue !== 'undefined' &&
|
||||||
|
typeof field.defaultValue !== 'function'
|
||||||
|
) {
|
||||||
|
if (field.type === 'point') {
|
||||||
|
ref[field.name] = {
|
||||||
|
type: 'Point',
|
||||||
|
coordinates: field.defaultValue,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ref[field.name] = field.defaultValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldIsVirtual(field)) {
|
||||||
|
delete ref[field.name]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field.type === 'date') {
|
||||||
|
if (config.localization && field.localized) {
|
||||||
|
const fieldRef = ref[field.name]
|
||||||
|
if (!fieldRef || typeof fieldRef !== 'object') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const locale of config.localization.localeCodes) {
|
||||||
|
sanitizeDate({
|
||||||
|
field,
|
||||||
|
operation,
|
||||||
|
ref: fieldRef,
|
||||||
|
value: fieldRef[locale],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sanitizeDate({
|
||||||
|
field,
|
||||||
|
operation,
|
||||||
|
ref: ref as Record<string, unknown>,
|
||||||
|
value: ref[field.name],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
field.type === 'relationship' ||
|
||||||
|
field.type === 'upload' ||
|
||||||
|
(operation === 'read' && field.type === 'join')
|
||||||
|
) {
|
||||||
|
// sanitize passed undefined in objects to null
|
||||||
|
if (operation !== 'read' && field.name in ref && ref[field.name] === undefined) {
|
||||||
|
ref[field.name] = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ref[field.name]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle localized relationships
|
||||||
|
if (config.localization && field.localized) {
|
||||||
|
const locales = config.localization.locales
|
||||||
|
const fieldRef = ref[field.name]
|
||||||
|
if (typeof fieldRef !== 'object') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const { code } of locales) {
|
||||||
|
const value = ref[field.name][code]
|
||||||
|
if (value) {
|
||||||
|
sanitizeRelationship({
|
||||||
|
config,
|
||||||
|
field,
|
||||||
|
locale: code,
|
||||||
|
operation,
|
||||||
|
ref: fieldRef,
|
||||||
|
validateRelationships,
|
||||||
|
value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// handle non-localized relationships
|
||||||
|
sanitizeRelationship({
|
||||||
|
config,
|
||||||
|
field,
|
||||||
|
locale: undefined,
|
||||||
|
operation,
|
||||||
|
ref: ref as Record<string, unknown>,
|
||||||
|
validateRelationships,
|
||||||
|
value: ref[field.name],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
traverseFields({ callback: sanitize, fillEmpty: false, flattenedFields: fields, ref: data })
|
||||||
|
}
|
||||||
@@ -126,6 +126,11 @@ export const promise = async <T>({
|
|||||||
|
|
||||||
case 'point': {
|
case 'point': {
|
||||||
if (Array.isArray(siblingData[field.name])) {
|
if (Array.isArray(siblingData[field.name])) {
|
||||||
|
if ((siblingData[field.name] as string[]).some((val) => val === null || val === '')) {
|
||||||
|
siblingData[field.name] = null
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
siblingData[field.name] = (siblingData[field.name] as string[]).map((coordinate, i) => {
|
siblingData[field.name] = (siblingData[field.name] as string[]).map((coordinate, i) => {
|
||||||
if (typeof coordinate === 'string') {
|
if (typeof coordinate === 'string') {
|
||||||
const value = siblingData[field.name][i] as string
|
const value = siblingData[field.name][i] as string
|
||||||
|
|||||||
@@ -875,7 +875,12 @@ export type PointFieldValidation = Validate<
|
|||||||
PointField
|
PointField
|
||||||
>
|
>
|
||||||
|
|
||||||
export const point: PointFieldValidation = (value = ['', ''], { req: { t }, required }) => {
|
export const point: PointFieldValidation = (value, { req: { t }, required }) => {
|
||||||
|
// Allow to pass null to clear the field
|
||||||
|
if (!value) {
|
||||||
|
value = ['', '']
|
||||||
|
}
|
||||||
|
|
||||||
const lng = parseFloat(String(value[0]))
|
const lng = parseFloat(String(value[0]))
|
||||||
const lat = parseFloat(String(value[1]))
|
const lat = parseFloat(String(value[1]))
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -1362,7 +1362,10 @@ export { sanitizeJoinParams } from './utilities/sanitizeJoinParams.js'
|
|||||||
export { sanitizePopulateParam } from './utilities/sanitizePopulateParam.js'
|
export { sanitizePopulateParam } from './utilities/sanitizePopulateParam.js'
|
||||||
export { sanitizeSelectParam } from './utilities/sanitizeSelectParam.js'
|
export { sanitizeSelectParam } from './utilities/sanitizeSelectParam.js'
|
||||||
export { traverseFields } from './utilities/traverseFields.js'
|
export { traverseFields } from './utilities/traverseFields.js'
|
||||||
export type { TraverseFieldsCallback } from './utilities/traverseFields.js'
|
export type {
|
||||||
|
TraverseFieldsCallback,
|
||||||
|
TraverseFlattenedFieldsCallback,
|
||||||
|
} from './utilities/traverseFields.js'
|
||||||
export { buildVersionCollectionFields } from './versions/buildCollectionFields.js'
|
export { buildVersionCollectionFields } from './versions/buildCollectionFields.js'
|
||||||
export { buildVersionGlobalFields } from './versions/buildGlobalFields.js'
|
export { buildVersionGlobalFields } from './versions/buildGlobalFields.js'
|
||||||
export { versionDefaults } from './versions/defaults.js'
|
export { versionDefaults } from './versions/defaults.js'
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
import type { ArrayField, BlocksField, Field, TabAsField } from '../fields/config/types.js'
|
import type {
|
||||||
|
ArrayField,
|
||||||
|
BlocksField,
|
||||||
|
Field,
|
||||||
|
FlattenedArrayField,
|
||||||
|
FlattenedBlock,
|
||||||
|
FlattenedField,
|
||||||
|
TabAsField,
|
||||||
|
} from '../fields/config/types.js'
|
||||||
|
|
||||||
import { fieldHasSubFields } from '../fields/config/types.js'
|
import { fieldHasSubFields } from '../fields/config/types.js'
|
||||||
|
|
||||||
@@ -12,7 +20,7 @@ const traverseArrayOrBlocksField = ({
|
|||||||
callback: TraverseFieldsCallback
|
callback: TraverseFieldsCallback
|
||||||
data: Record<string, unknown>[]
|
data: Record<string, unknown>[]
|
||||||
field: ArrayField | BlocksField
|
field: ArrayField | BlocksField
|
||||||
fillEmpty: boolean
|
fillEmpty?: boolean
|
||||||
parentRef?: unknown
|
parentRef?: unknown
|
||||||
}) => {
|
}) => {
|
||||||
if (fillEmpty) {
|
if (fillEmpty) {
|
||||||
@@ -28,20 +36,23 @@ const traverseArrayOrBlocksField = ({
|
|||||||
}
|
}
|
||||||
for (const ref of data) {
|
for (const ref of data) {
|
||||||
let fields: Field[]
|
let fields: Field[]
|
||||||
|
let flattenedFields: FlattenedField[]
|
||||||
if (field.type === 'blocks' && typeof ref?.blockType === 'string') {
|
if (field.type === 'blocks' && typeof ref?.blockType === 'string') {
|
||||||
const block = field.blocks.find((block) => block.slug === ref.blockType)
|
const block = field.blocks.find((block) => block.slug === ref.blockType) as FlattenedBlock
|
||||||
fields = block?.fields
|
fields = block?.fields
|
||||||
|
flattenedFields = block?.flattenedFields
|
||||||
} else if (field.type === 'array') {
|
} else if (field.type === 'array') {
|
||||||
fields = field.fields
|
fields = field.fields
|
||||||
|
flattenedFields = (field as FlattenedArrayField)?.flattenedFields
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fields) {
|
if (flattenedFields || fields) {
|
||||||
traverseFields({ callback, fields, fillEmpty, parentRef, ref })
|
traverseFields({ callback, fields, fillEmpty, flattenedFields, parentRef, ref })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TraverseFieldsCallback = (args: {
|
type TraverseFieldsCallbackArgs = {
|
||||||
/**
|
/**
|
||||||
* The current field
|
* The current field
|
||||||
*/
|
*/
|
||||||
@@ -58,12 +69,45 @@ export type TraverseFieldsCallback = (args: {
|
|||||||
* The current reference object
|
* The current reference object
|
||||||
*/
|
*/
|
||||||
ref?: Record<string, unknown> | unknown
|
ref?: Record<string, unknown> | unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TraverseFieldsCallback = (args: TraverseFieldsCallbackArgs) => boolean | void
|
||||||
|
|
||||||
|
export type TraverseFlattenedFieldsCallback = (args: {
|
||||||
|
/**
|
||||||
|
* The current field
|
||||||
|
*/
|
||||||
|
field: FlattenedField
|
||||||
|
/**
|
||||||
|
* Function that when called will skip the current field and continue to the next
|
||||||
|
*/
|
||||||
|
next?: () => void
|
||||||
|
/**
|
||||||
|
* The parent reference object
|
||||||
|
*/
|
||||||
|
parentRef?: Record<string, unknown> | unknown
|
||||||
|
/**
|
||||||
|
* The current reference object
|
||||||
|
*/
|
||||||
|
ref?: Record<string, unknown> | unknown
|
||||||
}) => boolean | void
|
}) => boolean | void
|
||||||
|
|
||||||
|
type TraverseFlattenedFieldsArgs = {
|
||||||
|
callback: TraverseFlattenedFieldsCallback
|
||||||
|
fields?: Field[]
|
||||||
|
/** fill empty properties to use this without data */
|
||||||
|
fillEmpty?: boolean
|
||||||
|
flattenedFields: FlattenedField[]
|
||||||
|
parentRef?: Record<string, unknown> | unknown
|
||||||
|
ref?: Record<string, unknown> | unknown
|
||||||
|
}
|
||||||
|
|
||||||
type TraverseFieldsArgs = {
|
type TraverseFieldsArgs = {
|
||||||
callback: TraverseFieldsCallback
|
callback: TraverseFieldsCallback
|
||||||
fields: (Field | TabAsField)[]
|
fields: (Field | FlattenedField | TabAsField)[]
|
||||||
|
/** fill empty properties to use this without data */
|
||||||
fillEmpty?: boolean
|
fillEmpty?: boolean
|
||||||
|
flattenedFields?: FlattenedField[]
|
||||||
parentRef?: Record<string, unknown> | unknown
|
parentRef?: Record<string, unknown> | unknown
|
||||||
ref?: Record<string, unknown> | unknown
|
ref?: Record<string, unknown> | unknown
|
||||||
}
|
}
|
||||||
@@ -81,10 +125,11 @@ export const traverseFields = ({
|
|||||||
callback,
|
callback,
|
||||||
fields,
|
fields,
|
||||||
fillEmpty = true,
|
fillEmpty = true,
|
||||||
|
flattenedFields,
|
||||||
parentRef = {},
|
parentRef = {},
|
||||||
ref = {},
|
ref = {},
|
||||||
}: TraverseFieldsArgs): void => {
|
}: TraverseFieldsArgs | TraverseFlattenedFieldsArgs): void => {
|
||||||
fields.some((field) => {
|
;(flattenedFields ?? fields).some((field) => {
|
||||||
let skip = false
|
let skip = false
|
||||||
const next = () => {
|
const next = () => {
|
||||||
skip = true
|
skip = true
|
||||||
@@ -94,7 +139,16 @@ export const traverseFields = ({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (callback && callback({ field, next, parentRef, ref })) {
|
if (
|
||||||
|
callback &&
|
||||||
|
callback({
|
||||||
|
// @ts-expect-error compatibillity Field | FlattenedField
|
||||||
|
field,
|
||||||
|
next,
|
||||||
|
parentRef,
|
||||||
|
ref,
|
||||||
|
})
|
||||||
|
) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,6 +193,7 @@ export const traverseFields = ({
|
|||||||
if (
|
if (
|
||||||
callback &&
|
callback &&
|
||||||
callback({
|
callback({
|
||||||
|
// @ts-expect-error compatibillity Field | FlattenedField
|
||||||
field: { ...tab, type: 'tab' },
|
field: { ...tab, type: 'tab' },
|
||||||
next,
|
next,
|
||||||
parentRef: currentParentRef,
|
parentRef: currentParentRef,
|
||||||
@@ -160,12 +215,15 @@ export const traverseFields = ({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field.type !== 'tab' && (fieldHasSubFields(field) || field.type === 'blocks')) {
|
if (
|
||||||
|
(flattenedFields || field.type !== 'tab') &&
|
||||||
|
(fieldHasSubFields(field as Field) || field.type === 'tab' || field.type === 'blocks')
|
||||||
|
) {
|
||||||
if ('name' in field && field.name) {
|
if ('name' in field && field.name) {
|
||||||
currentParentRef = currentRef
|
currentParentRef = currentRef
|
||||||
if (!ref[field.name]) {
|
if (!ref[field.name]) {
|
||||||
if (fillEmpty) {
|
if (fillEmpty) {
|
||||||
if (field.type === 'group') {
|
if (field.type === 'group' || field.type === 'tab') {
|
||||||
ref[field.name] = {}
|
ref[field.name] = {}
|
||||||
} else if (field.type === 'array' || field.type === 'blocks') {
|
} else if (field.type === 'array' || field.type === 'blocks') {
|
||||||
if (field.localized) {
|
if (field.localized) {
|
||||||
@@ -182,7 +240,7 @@ export const traverseFields = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
field.type === 'group' &&
|
(field.type === 'group' || field.type === 'tab') &&
|
||||||
field.localized &&
|
field.localized &&
|
||||||
currentRef &&
|
currentRef &&
|
||||||
typeof currentRef === 'object'
|
typeof currentRef === 'object'
|
||||||
@@ -193,9 +251,10 @@ export const traverseFields = ({
|
|||||||
callback,
|
callback,
|
||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
fillEmpty,
|
fillEmpty,
|
||||||
|
flattenedFields: 'flattenedFields' in field ? field.flattenedFields : undefined,
|
||||||
parentRef: currentParentRef,
|
parentRef: currentParentRef,
|
||||||
ref: currentRef[key],
|
ref: currentRef[key],
|
||||||
})
|
} as TraverseFieldsArgs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -239,6 +298,7 @@ export const traverseFields = ({
|
|||||||
callback,
|
callback,
|
||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
fillEmpty,
|
fillEmpty,
|
||||||
|
flattenedFields: 'flattenedFields' in field ? field.flattenedFields : undefined,
|
||||||
parentRef: currentParentRef,
|
parentRef: currentParentRef,
|
||||||
ref: currentRef,
|
ref: currentRef,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -53,10 +53,12 @@ describe('Relationship Fields', () => {
|
|||||||
collection: versionedRelationshipFieldSlug,
|
collection: versionedRelationshipFieldSlug,
|
||||||
data: {
|
data: {
|
||||||
title: 'Version 1 Title',
|
title: 'Version 1 Title',
|
||||||
relationshipField: {
|
relationshipField: [
|
||||||
value: relatedDoc.id,
|
{
|
||||||
relationTo: collection1Slug,
|
value: relatedDoc.id,
|
||||||
},
|
relationTo: collection1Slug,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1134,6 +1134,30 @@ describe('Fields', () => {
|
|||||||
expect(doc.localized).toEqual(localized)
|
expect(doc.localized).toEqual(localized)
|
||||||
expect(doc.group).toMatchObject(group)
|
expect(doc.group).toMatchObject(group)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should clear a point field', async () => {
|
||||||
|
if (payload.db.name === 'sqlite') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const doc = await payload.create({
|
||||||
|
collection: 'point-fields',
|
||||||
|
data: {
|
||||||
|
point: [7, -7],
|
||||||
|
group: {
|
||||||
|
point: [7, -7],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = await payload.update({
|
||||||
|
collection: 'point-fields',
|
||||||
|
id: doc.id,
|
||||||
|
data: { group: { point: null } },
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(res.group.point).toBeFalsy()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('unique indexes', () => {
|
describe('unique indexes', () => {
|
||||||
|
|||||||
@@ -154,7 +154,10 @@ describe('Joins Field', () => {
|
|||||||
collection: categoriesSlug,
|
collection: categoriesSlug,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(Object.keys(categoryWithPosts)).toStrictEqual(['id', 'group'])
|
expect(categoryWithPosts).toStrictEqual({
|
||||||
|
id: categoryWithPosts.id,
|
||||||
|
group: categoryWithPosts.group,
|
||||||
|
})
|
||||||
|
|
||||||
expect(categoryWithPosts.group.relatedPosts.docs).toHaveLength(10)
|
expect(categoryWithPosts.group.relatedPosts.docs).toHaveLength(10)
|
||||||
expect(categoryWithPosts.group.relatedPosts.docs[0]).toHaveProperty('id')
|
expect(categoryWithPosts.group.relatedPosts.docs[0]).toHaveProperty('id')
|
||||||
|
|||||||
@@ -1633,7 +1633,10 @@ describe('Select', () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(Object.keys(res)).toStrictEqual(['id', 'text'])
|
expect(res).toStrictEqual({
|
||||||
|
id: res.id,
|
||||||
|
text: res.text,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should apply select with updateByID', async () => {
|
it('should apply select with updateByID', async () => {
|
||||||
@@ -1646,13 +1649,18 @@ describe('Select', () => {
|
|||||||
select: { text: true },
|
select: { text: true },
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(Object.keys(res)).toStrictEqual(['id', 'text'])
|
expect(res).toStrictEqual({
|
||||||
|
id: res.id,
|
||||||
|
text: res.text,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should apply select with updateBulk', async () => {
|
it('should apply select with updateBulk', async () => {
|
||||||
const post = await createPost()
|
const post = await createPost()
|
||||||
|
|
||||||
const res = await payload.update({
|
const {
|
||||||
|
docs: [res],
|
||||||
|
} = await payload.update({
|
||||||
collection: 'posts',
|
collection: 'posts',
|
||||||
where: {
|
where: {
|
||||||
id: {
|
id: {
|
||||||
@@ -1663,7 +1671,10 @@ describe('Select', () => {
|
|||||||
select: { text: true },
|
select: { text: true },
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(Object.keys(res.docs[0])).toStrictEqual(['id', 'text'])
|
expect(res).toStrictEqual({
|
||||||
|
id: res.id,
|
||||||
|
text: res.text,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should apply select with deleteByID', async () => {
|
it('should apply select with deleteByID', async () => {
|
||||||
@@ -1675,13 +1686,18 @@ describe('Select', () => {
|
|||||||
select: { text: true },
|
select: { text: true },
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(Object.keys(res)).toStrictEqual(['id', 'text'])
|
expect(res).toStrictEqual({
|
||||||
|
id: res.id,
|
||||||
|
text: res.text,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should apply select with deleteBulk', async () => {
|
it('should apply select with deleteBulk', async () => {
|
||||||
const post = await createPost()
|
const post = await createPost()
|
||||||
|
|
||||||
const res = await payload.delete({
|
const {
|
||||||
|
docs: [res],
|
||||||
|
} = await payload.delete({
|
||||||
collection: 'posts',
|
collection: 'posts',
|
||||||
where: {
|
where: {
|
||||||
id: {
|
id: {
|
||||||
@@ -1691,7 +1707,10 @@ describe('Select', () => {
|
|||||||
select: { text: true },
|
select: { text: true },
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(Object.keys(res.docs[0])).toStrictEqual(['id', 'text'])
|
expect(res).toStrictEqual({
|
||||||
|
id: res.id,
|
||||||
|
text: res.text,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should apply select with duplicate', async () => {
|
it('should apply select with duplicate', async () => {
|
||||||
@@ -1703,7 +1722,10 @@ describe('Select', () => {
|
|||||||
select: { text: true },
|
select: { text: true },
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(Object.keys(res)).toStrictEqual(['id', 'text'])
|
expect(res).toStrictEqual({
|
||||||
|
id: res.id,
|
||||||
|
text: res.text,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user