fix(db-mongodb): mongodb optimizations (#10120)
There are few issues introduced in #9594 that we need to look into with these changes.
This commit is contained in:
@@ -1,9 +1,10 @@
|
|||||||
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(
|
||||||
@@ -11,37 +12,41 @@ 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 session = await getSession(this, req)
|
const options: CountOptions = {
|
||||||
|
session: await getSession(this, req),
|
||||||
|
}
|
||||||
|
|
||||||
const hasNearConstraint = getHasNearConstraint(where)
|
let hasNearConstraint = false
|
||||||
|
|
||||||
|
if (where) {
|
||||||
|
const constraints = flattenWhereToOperators(where)
|
||||||
|
hasNearConstraint = constraints.some((prop) => Object.keys(prop).some((key) => key === 'near'))
|
||||||
|
}
|
||||||
|
|
||||||
const query = await Model.buildQuery({
|
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.collection.estimatedDocumentCount()
|
result = await Model.estimatedDocumentCount({ session: options.session })
|
||||||
} else {
|
} else {
|
||||||
const options: CountOptions = { session }
|
result = await Model.countDocuments(query, options)
|
||||||
|
|
||||||
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,9 +1,10 @@
|
|||||||
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(
|
||||||
@@ -11,37 +12,41 @@ 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 session = await getSession(this, req)
|
const options: CountOptions = {
|
||||||
|
session: await getSession(this, req),
|
||||||
|
}
|
||||||
|
|
||||||
const hasNearConstraint = getHasNearConstraint(where)
|
let hasNearConstraint = false
|
||||||
|
|
||||||
|
if (where) {
|
||||||
|
const constraints = flattenWhereToOperators(where)
|
||||||
|
hasNearConstraint = constraints.some((prop) => Object.keys(prop).some((key) => key === 'near'))
|
||||||
|
}
|
||||||
|
|
||||||
const query = await Model.buildQuery({
|
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.collection.estimatedDocumentCount()
|
result = await Model.estimatedDocumentCount({ session: options.session })
|
||||||
} else {
|
} else {
|
||||||
const options: CountOptions = { session }
|
result = await Model.countDocuments(query, options)
|
||||||
|
|
||||||
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,9 +1,10 @@
|
|||||||
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(
|
||||||
@@ -11,37 +12,41 @@ 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 session = await getSession(this, req)
|
const options: CountOptions = {
|
||||||
|
session: await getSession(this, req),
|
||||||
|
}
|
||||||
|
|
||||||
const hasNearConstraint = getHasNearConstraint(where)
|
let hasNearConstraint = false
|
||||||
|
|
||||||
|
if (where) {
|
||||||
|
const constraints = flattenWhereToOperators(where)
|
||||||
|
hasNearConstraint = constraints.some((prop) => Object.keys(prop).some((key) => key === 'near'))
|
||||||
|
}
|
||||||
|
|
||||||
const query = await Model.buildQuery({
|
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.collection.estimatedDocumentCount()
|
result = await Model.estimatedDocumentCount({ session: options.session })
|
||||||
} else {
|
} else {
|
||||||
const options: CountOptions = { session }
|
result = await Model.countDocuments(query, options)
|
||||||
|
|
||||||
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,44 +1,48 @@
|
|||||||
import type { Create } from 'payload'
|
import type { CreateOptions } from 'mongoose'
|
||||||
|
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 { transform } from './utilities/transform.js'
|
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.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 session = await getSession(this, req)
|
const options: CreateOptions = {
|
||||||
|
session: await getSession(this, req),
|
||||||
const fields = this.payload.collections[collection].config.flattenedFields
|
|
||||||
|
|
||||||
if (this.payload.collections[collection].customIDType) {
|
|
||||||
data._id = data.id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
transform({
|
let doc
|
||||||
adapter: this,
|
|
||||||
|
const sanitizedData = sanitizeRelationshipIDs({
|
||||||
|
config: this.payload.config,
|
||||||
data,
|
data,
|
||||||
fields,
|
fields: this.payload.collections[collection].config.fields,
|
||||||
operation: 'create',
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (this.payload.collections[collection].customIDType) {
|
||||||
|
sanitizedData._id = sanitizedData.id
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { insertedId: insertedID } = await Model.collection.insertOne(data, { session })
|
;[doc] = await Model.create([sanitizedData], options)
|
||||||
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,9 +1,11 @@
|
|||||||
|
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 { transform } from './utilities/transform.js'
|
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.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,
|
||||||
@@ -11,29 +13,26 @@ export const createGlobal: CreateGlobal = async function createGlobal(
|
|||||||
) {
|
) {
|
||||||
const Model = this.globals
|
const Model = this.globals
|
||||||
|
|
||||||
const fields = this.payload.config.globals.find(
|
const global = sanitizeRelationshipIDs({
|
||||||
(globalConfig) => globalConfig.slug === slug,
|
config: this.payload.config,
|
||||||
).flattenedFields
|
data: {
|
||||||
|
globalType: slug,
|
||||||
transform({
|
...data,
|
||||||
adapter: this,
|
},
|
||||||
data,
|
fields: this.payload.config.globals.find((globalConfig) => globalConfig.slug === slug).fields,
|
||||||
fields,
|
|
||||||
globalSlug: slug,
|
|
||||||
operation: 'create',
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const session = await getSession(this, req)
|
const options: CreateOptions = {
|
||||||
|
session: await getSession(this, req),
|
||||||
|
}
|
||||||
|
|
||||||
const { insertedId: insertedID } = await Model.collection.insertOne(data, { session })
|
let [result] = (await Model.create([global], options)) as any
|
||||||
;(data as any)._id = insertedID
|
|
||||||
|
|
||||||
transform({
|
result = JSON.parse(JSON.stringify(result))
|
||||||
adapter: this,
|
|
||||||
data,
|
|
||||||
fields,
|
|
||||||
operation: 'read',
|
|
||||||
})
|
|
||||||
|
|
||||||
return data
|
// custom id type reset
|
||||||
|
result.id = result._id
|
||||||
|
result = sanitizeInternalFields(result)
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { buildVersionGlobalFields, type CreateGlobalVersion } from 'payload'
|
import type { CreateOptions } from 'mongoose'
|
||||||
|
|
||||||
|
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 { transform } from './utilities/transform.js'
|
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'
|
||||||
|
|
||||||
export const createGlobalVersion: CreateGlobalVersion = async function createGlobalVersion(
|
export const createGlobalVersion: CreateGlobalVersion = async function createGlobalVersion(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
@@ -20,41 +22,36 @@ export const createGlobalVersion: CreateGlobalVersion = async function createGlo
|
|||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const VersionModel = this.versions[globalSlug]
|
const VersionModel = this.versions[globalSlug]
|
||||||
const session = await getSession(this, req)
|
const options: CreateOptions = {
|
||||||
|
session: await getSession(this, req),
|
||||||
const data = {
|
|
||||||
autosave,
|
|
||||||
createdAt,
|
|
||||||
latest: true,
|
|
||||||
parent,
|
|
||||||
publishedLocale,
|
|
||||||
snapshot,
|
|
||||||
updatedAt,
|
|
||||||
version: versionData,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const fields = buildVersionGlobalFields(
|
const data = sanitizeRelationshipIDs({
|
||||||
this.payload.config,
|
config: this.payload.config,
|
||||||
this.payload.config.globals.find((global) => global.slug === globalSlug),
|
data: {
|
||||||
true,
|
autosave,
|
||||||
)
|
createdAt,
|
||||||
|
latest: true,
|
||||||
transform({
|
parent,
|
||||||
adapter: this,
|
publishedLocale,
|
||||||
data,
|
snapshot,
|
||||||
fields,
|
updatedAt,
|
||||||
operation: 'create',
|
version: versionData,
|
||||||
|
},
|
||||||
|
fields: buildVersionGlobalFields(
|
||||||
|
this.payload.config,
|
||||||
|
this.payload.config.globals.find((global) => global.slug === globalSlug),
|
||||||
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
const { insertedId: insertedID } = await VersionModel.collection.insertOne(data, { session })
|
const [doc] = await VersionModel.create([data], options, req)
|
||||||
;(data as any)._id = insertedID
|
|
||||||
|
|
||||||
await VersionModel.collection.updateMany(
|
await VersionModel.updateMany(
|
||||||
{
|
{
|
||||||
$and: [
|
$and: [
|
||||||
{
|
{
|
||||||
_id: {
|
_id: {
|
||||||
$ne: insertedID,
|
$ne: doc._id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -70,15 +67,16 @@ export const createGlobalVersion: CreateGlobalVersion = async function createGlo
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{ $unset: { latest: 1 } },
|
{ $unset: { latest: 1 } },
|
||||||
{ session },
|
options,
|
||||||
)
|
)
|
||||||
|
|
||||||
transform({
|
const result: Document = JSON.parse(JSON.stringify(doc))
|
||||||
adapter: this,
|
const verificationToken = doc._verificationToken
|
||||||
data,
|
|
||||||
fields,
|
|
||||||
operation: 'read',
|
|
||||||
})
|
|
||||||
|
|
||||||
return data as any
|
// custom id type reset
|
||||||
|
result.id = result._id
|
||||||
|
if (verificationToken) {
|
||||||
|
result._verificationToken = verificationToken
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
|
import type { CreateOptions } from 'mongoose'
|
||||||
|
|
||||||
import { Types } from 'mongoose'
|
import { Types } from 'mongoose'
|
||||||
import { buildVersionCollectionFields, type CreateVersion } from 'payload'
|
import { buildVersionCollectionFields, type CreateVersion, 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 { transform } from './utilities/transform.js'
|
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'
|
||||||
|
|
||||||
export const createVersion: CreateVersion = async function createVersion(
|
export const createVersion: CreateVersion = async function createVersion(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
@@ -21,34 +23,29 @@ export const createVersion: CreateVersion = async function createVersion(
|
|||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const VersionModel = this.versions[collectionSlug]
|
const VersionModel = this.versions[collectionSlug]
|
||||||
const session = await getSession(this, req)
|
const options: CreateOptions = {
|
||||||
|
session: await getSession(this, req),
|
||||||
const data: any = {
|
|
||||||
autosave,
|
|
||||||
createdAt,
|
|
||||||
latest: true,
|
|
||||||
parent,
|
|
||||||
publishedLocale,
|
|
||||||
snapshot,
|
|
||||||
updatedAt,
|
|
||||||
version: versionData,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const fields = buildVersionCollectionFields(
|
const data = sanitizeRelationshipIDs({
|
||||||
this.payload.config,
|
config: this.payload.config,
|
||||||
this.payload.collections[collectionSlug].config,
|
data: {
|
||||||
true,
|
autosave,
|
||||||
)
|
createdAt,
|
||||||
|
latest: true,
|
||||||
transform({
|
parent,
|
||||||
adapter: this,
|
publishedLocale,
|
||||||
data,
|
snapshot,
|
||||||
fields,
|
updatedAt,
|
||||||
operation: 'create',
|
version: versionData,
|
||||||
|
},
|
||||||
|
fields: buildVersionCollectionFields(
|
||||||
|
this.payload.config,
|
||||||
|
this.payload.collections[collectionSlug].config,
|
||||||
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
const { insertedId: insertedID } = await VersionModel.collection.insertOne(data, { session })
|
const [doc] = await VersionModel.create([data], options, req)
|
||||||
data._id = insertedID
|
|
||||||
|
|
||||||
const parentQuery = {
|
const parentQuery = {
|
||||||
$or: [
|
$or: [
|
||||||
@@ -59,7 +56,7 @@ export const createVersion: CreateVersion = async function createVersion(
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
if ((data.parent as unknown) instanceof Types.ObjectId) {
|
if (data.parent instanceof Types.ObjectId) {
|
||||||
parentQuery.$or.push({
|
parentQuery.$or.push({
|
||||||
parent: {
|
parent: {
|
||||||
$eq: data.parent.toString(),
|
$eq: data.parent.toString(),
|
||||||
@@ -67,12 +64,12 @@ export const createVersion: CreateVersion = async function createVersion(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
await VersionModel.collection.updateMany(
|
await VersionModel.updateMany(
|
||||||
{
|
{
|
||||||
$and: [
|
$and: [
|
||||||
{
|
{
|
||||||
_id: {
|
_id: {
|
||||||
$ne: insertedID,
|
$ne: doc._id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
parentQuery,
|
parentQuery,
|
||||||
@@ -83,21 +80,22 @@ export const createVersion: CreateVersion = async function createVersion(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
updatedAt: {
|
updatedAt: {
|
||||||
$lt: new Date(data.updatedAt),
|
$lt: new Date(doc.updatedAt),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{ $unset: { latest: 1 } },
|
{ $unset: { latest: 1 } },
|
||||||
{ session },
|
options,
|
||||||
)
|
)
|
||||||
|
|
||||||
transform({
|
const result: Document = JSON.parse(JSON.stringify(doc))
|
||||||
adapter: this,
|
const verificationToken = doc._verificationToken
|
||||||
data,
|
|
||||||
fields,
|
|
||||||
operation: 'read',
|
|
||||||
})
|
|
||||||
|
|
||||||
return data
|
// custom id type reset
|
||||||
|
result.id = result._id
|
||||||
|
if (verificationToken) {
|
||||||
|
result._verificationToken = verificationToken
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
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'
|
||||||
@@ -9,16 +10,14 @@ 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 = {
|
||||||
const session = await getSession(this, req)
|
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.collection.deleteMany(query, {
|
await Model.deleteMany(query, options)
|
||||||
session,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,41 +1,38 @@
|
|||||||
import type { DeleteOne } from 'payload'
|
import type { QueryOptions } from 'mongoose'
|
||||||
|
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 { transform } from './utilities/transform.js'
|
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.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 session = await getSession(this, req)
|
const options: QueryOptions = {
|
||||||
|
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 fields = this.payload.collections[collection].config.flattenedFields
|
const doc = await Model.findOneAndDelete(query, options).lean()
|
||||||
|
|
||||||
const doc = await Model.collection.findOneAndDelete(query, {
|
let result: Document = JSON.parse(JSON.stringify(doc))
|
||||||
projection: buildProjectionFromSelect({
|
|
||||||
adapter: this,
|
|
||||||
fields,
|
|
||||||
select,
|
|
||||||
}),
|
|
||||||
session,
|
|
||||||
})
|
|
||||||
|
|
||||||
transform({
|
// custom id type reset
|
||||||
adapter: this,
|
result.id = result._id
|
||||||
data: doc,
|
result = sanitizeInternalFields(result)
|
||||||
fields,
|
|
||||||
operation: 'read',
|
|
||||||
})
|
|
||||||
|
|
||||||
return doc
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,11 +15,8 @@ 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.collection.deleteMany(query, {
|
await VersionsModel.deleteMany(query, { session })
|
||||||
session,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import type { CollationOptions } from 'mongodb'
|
import type { PaginateOptions } from 'mongoose'
|
||||||
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 { transform } from './utilities/transform.js'
|
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
||||||
|
|
||||||
export const find: Find = async function find(
|
export const find: Find = async function find(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
@@ -20,6 +20,7 @@ export const find: Find = async function find(
|
|||||||
locale,
|
locale,
|
||||||
page,
|
page,
|
||||||
pagination,
|
pagination,
|
||||||
|
projection,
|
||||||
req,
|
req,
|
||||||
select,
|
select,
|
||||||
sort: sortArg,
|
sort: sortArg,
|
||||||
@@ -28,17 +29,21 @@ 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)
|
||||||
|
|
||||||
const hasNearConstraint = getHasNearConstraint(where)
|
let hasNearConstraint = false
|
||||||
|
|
||||||
const fields = collectionConfig.flattenedFields
|
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) {
|
||||||
sort = buildSortParam({
|
sort = buildSortParam({
|
||||||
config: this.payload.config,
|
config: this.payload.config,
|
||||||
fields,
|
fields: collectionConfig.flattenedFields,
|
||||||
locale,
|
locale,
|
||||||
sort: sortArg || collectionConfig.defaultSort,
|
sort: sortArg || collectionConfig.defaultSort,
|
||||||
timestamps: true,
|
timestamps: true,
|
||||||
@@ -48,51 +53,90 @@ 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,
|
||||||
|
}
|
||||||
|
|
||||||
const projection = buildProjectionFromSelect({
|
if (select) {
|
||||||
adapter: this,
|
paginationOptions.projection = buildProjectionFromSelect({
|
||||||
fields,
|
adapter: this,
|
||||||
select,
|
fields: collectionConfig.flattenedFields,
|
||||||
})
|
select,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const collation: CollationOptions | undefined = this.collation
|
if (this.collation) {
|
||||||
? {
|
const defaultLocale = 'en'
|
||||||
locale: locale && locale !== 'all' && locale !== '*' ? locale : 'en',
|
paginationOptions.collation = {
|
||||||
...this.collation,
|
locale: locale && locale !== 'all' && locale !== '*' ? locale : defaultLocale,
|
||||||
}
|
...this.collation,
|
||||||
: undefined
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const joinAgreggation = await buildJoinAggregation({
|
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
|
||||||
|
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,
|
||||||
session,
|
|
||||||
})
|
|
||||||
|
|
||||||
const result = await findMany({
|
|
||||||
adapter: this,
|
|
||||||
collation,
|
|
||||||
collection: Model.collection,
|
|
||||||
joinAgreggation,
|
|
||||||
limit,
|
|
||||||
page,
|
|
||||||
pagination,
|
|
||||||
projection,
|
|
||||||
query,
|
query,
|
||||||
session,
|
|
||||||
sort,
|
|
||||||
useEstimatedCount,
|
|
||||||
})
|
})
|
||||||
|
// build join aggregation
|
||||||
|
if (aggregate) {
|
||||||
|
result = await Model.aggregatePaginate(Model.aggregate(aggregate), paginationOptions)
|
||||||
|
} else {
|
||||||
|
result = await Model.paginate(query, paginationOptions)
|
||||||
|
}
|
||||||
|
|
||||||
transform({ adapter: this, data: result.docs, fields, operation: 'read' })
|
const docs = JSON.parse(JSON.stringify(result.docs))
|
||||||
|
|
||||||
return result
|
return {
|
||||||
|
...result,
|
||||||
|
docs: docs.map((doc) => {
|
||||||
|
doc.id = doc._id
|
||||||
|
return sanitizeInternalFields(doc)
|
||||||
|
}),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { QueryOptions } from 'mongoose'
|
||||||
import type { FindGlobal } from 'payload'
|
import type { FindGlobal } from 'payload'
|
||||||
|
|
||||||
import { combineQueries } from 'payload'
|
import { combineQueries } from 'payload'
|
||||||
@@ -6,40 +7,42 @@ 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 { transform } from './utilities/transform.js'
|
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.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 = {
|
||||||
const session = await getSession(this, req)
|
lean: true,
|
||||||
|
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),
|
||||||
})
|
})
|
||||||
|
|
||||||
const fields = this.payload.globals.config.find((each) => each.slug === slug).flattenedFields
|
let doc = (await Model.findOne(query, {}, options)) as any
|
||||||
|
|
||||||
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
|
||||||
|
}
|
||||||
|
|
||||||
transform({ adapter: this, data: doc, fields, operation: 'read' })
|
doc = JSON.parse(JSON.stringify(doc))
|
||||||
|
doc = sanitizeInternalFields(doc)
|
||||||
|
|
||||||
return doc as any
|
return doc
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import type { CollationOptions } from 'mongodb'
|
import type { PaginateOptions, QueryOptions } from 'mongoose'
|
||||||
import type { FindGlobalVersions } from 'payload'
|
import type { FindGlobalVersions } from 'payload'
|
||||||
|
|
||||||
import { buildVersionGlobalFields } from 'payload'
|
import { buildVersionGlobalFields, 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 { 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 { transform } from './utilities/transform.js'
|
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
||||||
|
|
||||||
export const findGlobalVersions: FindGlobalVersions = async function findGlobalVersions(
|
export const findGlobalVersions: FindGlobalVersions = async function findGlobalVersions(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
@@ -23,7 +21,19 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV
|
|||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
|
|
||||||
const hasNearConstraint = getHasNearConstraint(where)
|
const session = await getSession(this, req)
|
||||||
|
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) {
|
||||||
@@ -36,49 +46,69 @@ 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 = {
|
||||||
const projection = buildProjectionFromSelect({ adapter: this, fields: versionFields, select })
|
lean: true,
|
||||||
|
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,
|
projection: buildProjectionFromSelect({ adapter: this, fields: versionFields, select }),
|
||||||
query,
|
|
||||||
session,
|
|
||||||
skip,
|
|
||||||
sort,
|
sort,
|
||||||
useEstimatedCount,
|
useEstimatedCount,
|
||||||
})
|
}
|
||||||
|
|
||||||
transform({
|
if (this.collation) {
|
||||||
adapter: this,
|
const defaultLocale = 'en'
|
||||||
data: result.docs,
|
paginationOptions.collation = {
|
||||||
fields: versionFields,
|
locale: locale && locale !== 'all' && locale !== '*' ? locale : defaultLocale,
|
||||||
operation: 'read',
|
...this.collation,
|
||||||
})
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result
|
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
|
||||||
|
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,11 +1,12 @@
|
|||||||
import type { FindOne } from 'payload'
|
import type { AggregateOptions, QueryOptions } from 'mongoose'
|
||||||
|
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 { transform } from './utilities/transform.js'
|
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
||||||
|
|
||||||
export const findOne: FindOne = async function findOne(
|
export const findOne: FindOne = async function findOne(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
@@ -13,64 +14,52 @@ 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,
|
fields: collectionConfig.flattenedFields,
|
||||||
select,
|
select,
|
||||||
})
|
})
|
||||||
|
|
||||||
const joinAggregation = await buildJoinAggregation({
|
const aggregate = await buildJoinAggregation({
|
||||||
adapter: this,
|
adapter: this,
|
||||||
collection,
|
collection,
|
||||||
collectionConfig,
|
collectionConfig,
|
||||||
joins,
|
joins,
|
||||||
|
limit: 1,
|
||||||
locale,
|
locale,
|
||||||
projection,
|
projection,
|
||||||
session,
|
query,
|
||||||
})
|
})
|
||||||
|
|
||||||
let doc
|
let doc
|
||||||
if (joinAggregation) {
|
if (aggregate) {
|
||||||
const aggregation = Model.collection.aggregate(
|
;[doc] = await Model.aggregate(aggregate, { session })
|
||||||
[
|
|
||||||
{
|
|
||||||
$match: query,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
{ session },
|
|
||||||
)
|
|
||||||
aggregation.limit(1)
|
|
||||||
for (const stage of joinAggregation) {
|
|
||||||
aggregation.addStage(stage)
|
|
||||||
}
|
|
||||||
|
|
||||||
;[doc] = await aggregation.toArray()
|
|
||||||
} else {
|
} else {
|
||||||
doc = await Model.collection.findOne(query, { projection, session })
|
;(options as Record<string, unknown>).projection = projection
|
||||||
|
doc = await Model.findOne(query, {}, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!doc) {
|
if (!doc) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
transform({
|
let result: Document = JSON.parse(JSON.stringify(doc))
|
||||||
adapter: this,
|
|
||||||
data: doc,
|
|
||||||
fields,
|
|
||||||
operation: 'read',
|
|
||||||
})
|
|
||||||
|
|
||||||
return doc
|
// custom id type reset
|
||||||
|
result.id = result._id
|
||||||
|
result = sanitizeInternalFields(result)
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import type { CollationOptions } from 'mongodb'
|
import type { PaginateOptions, QueryOptions } from 'mongoose'
|
||||||
import type { FindVersions } from 'payload'
|
import type { FindVersions } from 'payload'
|
||||||
|
|
||||||
import { buildVersionCollectionFields } from 'payload'
|
import { buildVersionCollectionFields, 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 { 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 { transform } from './utilities/transform.js'
|
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
||||||
|
|
||||||
export const findVersions: FindVersions = async function findVersions(
|
export const findVersions: FindVersions = async function findVersions(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
@@ -18,10 +16,19 @@ 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,
|
||||||
|
}
|
||||||
|
|
||||||
const hasNearConstraint = getHasNearConstraint(where)
|
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) {
|
||||||
@@ -37,48 +44,69 @@ 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 = {
|
||||||
const projection = buildProjectionFromSelect({
|
lean: true,
|
||||||
adapter: this,
|
leanWithId: true,
|
||||||
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,
|
projection: buildProjectionFromSelect({
|
||||||
query,
|
adapter: this,
|
||||||
session,
|
fields: buildVersionCollectionFields(this.payload.config, collectionConfig, true),
|
||||||
skip,
|
select,
|
||||||
|
}),
|
||||||
sort,
|
sort,
|
||||||
useEstimatedCount,
|
useEstimatedCount,
|
||||||
})
|
}
|
||||||
|
|
||||||
transform({
|
if (this.collation) {
|
||||||
adapter: this,
|
const defaultLocale = 'en'
|
||||||
data: result.docs,
|
paginationOptions.collation = {
|
||||||
fields: versionFields,
|
locale: locale && locale !== 'all' && locale !== '*' ? locale : defaultLocale,
|
||||||
operation: 'read',
|
...this.collation,
|
||||||
})
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result
|
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
|
||||||
|
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,8 +59,6 @@ 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,3 +1,5 @@
|
|||||||
|
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, FlattenedField, PayloadRequest } from 'payload'
|
import type { Field, PayloadRequest, SanitizedConfig } 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 { transform } from '../utilities/transform.js'
|
import { sanitizeRelationshipIDs } from '../utilities/sanitizeRelationshipIDs.js'
|
||||||
|
|
||||||
const migrateModelWithBatching = async ({
|
const migrateModelWithBatching = async ({
|
||||||
adapter,
|
|
||||||
batchSize,
|
batchSize,
|
||||||
|
config,
|
||||||
fields,
|
fields,
|
||||||
Model,
|
Model,
|
||||||
session,
|
session,
|
||||||
}: {
|
}: {
|
||||||
adapter: MongooseAdapter
|
|
||||||
batchSize: number
|
batchSize: number
|
||||||
fields: FlattenedField[]
|
config: SanitizedConfig
|
||||||
|
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) {
|
||||||
transform({ adapter, data: doc, fields, operation: 'update', validateRelationships: false })
|
sanitizeRelationshipIDs({ config, data: doc, fields })
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
fields: collection.flattenedFields,
|
config,
|
||||||
|
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,
|
||||||
fields: buildVersionCollectionFields(config, collection, true),
|
config,
|
||||||
|
fields: buildVersionCollectionFields(config, collection),
|
||||||
Model: db.versions[collection.slug],
|
Model: db.versions[collection.slug],
|
||||||
session,
|
session,
|
||||||
})
|
})
|
||||||
@@ -156,13 +156,7 @@ 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) {
|
||||||
transform({
|
sanitizeRelationshipIDs({ config, data: doc, fields: global.fields })
|
||||||
adapter: db,
|
|
||||||
data: doc,
|
|
||||||
fields: global.flattenedFields,
|
|
||||||
operation: 'update',
|
|
||||||
validateRelationships: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
await GlobalsModel.collection.updateOne(
|
await GlobalsModel.collection.updateOne(
|
||||||
{
|
{
|
||||||
@@ -179,9 +173,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,
|
||||||
fields: buildVersionGlobalFields(config, global, true),
|
config,
|
||||||
|
fields: buildVersionGlobalFields(config, global),
|
||||||
Model: db.versions[global.slug],
|
Model: db.versions[global.slug],
|
||||||
session,
|
session,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
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'
|
||||||
@@ -9,7 +8,6 @@ export async function buildAndOrConditions({
|
|||||||
globalSlug,
|
globalSlug,
|
||||||
locale,
|
locale,
|
||||||
payload,
|
payload,
|
||||||
session,
|
|
||||||
where,
|
where,
|
||||||
}: {
|
}: {
|
||||||
collectionSlug?: string
|
collectionSlug?: string
|
||||||
@@ -17,7 +15,6 @@ 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 = []
|
||||||
@@ -33,7 +30,6 @@ 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,6 +1,7 @@
|
|||||||
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 = {
|
||||||
@@ -12,7 +13,6 @@ export type BuildQueryArgs = {
|
|||||||
globalSlug?: string
|
globalSlug?: string
|
||||||
locale?: string
|
locale?: string
|
||||||
payload: Payload
|
payload: Payload
|
||||||
session?: ClientSession
|
|
||||||
where: Where
|
where: Where
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,7 +28,6 @@ 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
|
||||||
@@ -42,17 +41,20 @@ 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,4 +1,3 @@
|
|||||||
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'
|
||||||
@@ -16,11 +15,9 @@ type SearchParam = {
|
|||||||
value?: unknown
|
value?: unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
const subQueryOptions: FindOptions = {
|
const subQueryOptions = {
|
||||||
|
lean: true,
|
||||||
limit: 50,
|
limit: 50,
|
||||||
projection: {
|
|
||||||
_id: true,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,7 +31,6 @@ export async function buildSearchParam({
|
|||||||
locale,
|
locale,
|
||||||
operator,
|
operator,
|
||||||
payload,
|
payload,
|
||||||
session,
|
|
||||||
val,
|
val,
|
||||||
}: {
|
}: {
|
||||||
collectionSlug?: string
|
collectionSlug?: string
|
||||||
@@ -44,7 +40,6 @@ 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
|
||||||
@@ -139,14 +134,17 @@ export async function buildSearchParam({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const result = await SubModel.collection
|
const result = await SubModel.find(subQuery, subQueryOptions)
|
||||||
.find(subQuery, { session, ...subQueryOptions })
|
|
||||||
.toArray()
|
|
||||||
|
|
||||||
const $in: unknown[] = []
|
const $in: unknown[] = []
|
||||||
|
|
||||||
result.forEach((doc) => {
|
result.forEach((doc) => {
|
||||||
$in.push(doc._id)
|
const stringID = doc._id.toString()
|
||||||
|
$in.push(stringID)
|
||||||
|
|
||||||
|
if (Types.ObjectId.isValid(stringID)) {
|
||||||
|
$in.push(doc._id)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (pathsToQuery.length === 1) {
|
if (pathsToQuery.length === 1) {
|
||||||
@@ -164,9 +162,7 @@ export async function buildSearchParam({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const subQuery = priorQueryResult.value
|
const subQuery = priorQueryResult.value
|
||||||
const result = await SubModel.collection
|
const result = await SubModel.find(subQuery, subQueryOptions)
|
||||||
.find(subQuery, { session, ...subQueryOptions })
|
|
||||||
.toArray()
|
|
||||||
|
|
||||||
const $in = result.map((doc) => doc._id)
|
const $in = result.map((doc) => doc._id)
|
||||||
|
|
||||||
|
|||||||
@@ -11,13 +11,20 @@ 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): Record<string, -1 | 1> => {
|
}: Args): PaginateOptions['sort'] => {
|
||||||
if (!sort) {
|
if (!sort) {
|
||||||
if (timestamps) {
|
if (timestamps) {
|
||||||
sort = '-createdAt'
|
sort = '-createdAt'
|
||||||
@@ -30,15 +37,15 @@ export const buildSortParam = ({
|
|||||||
sort = [sort]
|
sort = [sort]
|
||||||
}
|
}
|
||||||
|
|
||||||
const sorting = sort.reduce<Record<string, -1 | 1>>((acc, item) => {
|
const sorting = sort.reduce<PaginateOptions['sort']>((acc, item) => {
|
||||||
let sortProperty: string
|
let sortProperty: string
|
||||||
let sortDirection: -1 | 1
|
let sortDirection: SortDirection
|
||||||
if (item.indexOf('-') === 0) {
|
if (item.indexOf('-') === 0) {
|
||||||
sortProperty = item.substring(1)
|
sortProperty = item.substring(1)
|
||||||
sortDirection = -1
|
sortDirection = 'desc'
|
||||||
} else {
|
} else {
|
||||||
sortProperty = item
|
sortProperty = item
|
||||||
sortDirection = 1
|
sortDirection = 'asc'
|
||||||
}
|
}
|
||||||
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 } from 'payload/shared'
|
import { fieldAffectsData, fieldIsPresentationalOnly } 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) {
|
if (matchedField && !fieldIsPresentationalOnly(matchedField)) {
|
||||||
let nextFields: FlattenedField[]
|
let nextFields: FlattenedField[]
|
||||||
const remainingSegments = [...segments]
|
const remainingSegments = [...segments]
|
||||||
let localizedSegment = matchedField.name
|
let localizedSegment = matchedField.name
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
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'
|
||||||
|
|
||||||
@@ -14,7 +13,6 @@ export async function parseParams({
|
|||||||
globalSlug,
|
globalSlug,
|
||||||
locale,
|
locale,
|
||||||
payload,
|
payload,
|
||||||
session,
|
|
||||||
where,
|
where,
|
||||||
}: {
|
}: {
|
||||||
collectionSlug?: string
|
collectionSlug?: string
|
||||||
@@ -22,7 +20,6 @@ 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>
|
||||||
@@ -65,7 +62,6 @@ export async function parseParams({
|
|||||||
locale,
|
locale,
|
||||||
operator,
|
operator,
|
||||||
payload,
|
payload,
|
||||||
session,
|
|
||||||
val: pathOperators[operator],
|
val: pathOperators[operator],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -37,22 +37,6 @@ 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,
|
||||||
@@ -375,14 +359,6 @@ 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,17 +1,15 @@
|
|||||||
import type { CollationOptions } from 'mongodb'
|
import type { PaginateOptions, QueryOptions } from 'mongoose'
|
||||||
import type { QueryDrafts } from 'payload'
|
import type { QueryDrafts } from 'payload'
|
||||||
|
|
||||||
import { buildVersionCollectionFields, combineQueries } from 'payload'
|
import { buildVersionCollectionFields, combineQueries, 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 { transform } from './utilities/transform.js'
|
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
||||||
|
|
||||||
export const queryDrafts: QueryDrafts = async function queryDrafts(
|
export const queryDrafts: QueryDrafts = async function queryDrafts(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
@@ -19,11 +17,18 @@ 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 session = await getSession(this, req)
|
const options: QueryOptions = {
|
||||||
|
session: await getSession(this, req),
|
||||||
|
}
|
||||||
|
|
||||||
const hasNearConstraint = getHasNearConstraint(where)
|
let hasNearConstraint
|
||||||
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,
|
||||||
@@ -39,65 +44,95 @@ 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: versionFields,
|
fields: buildVersionCollectionFields(this.payload.config, collectionConfig, true),
|
||||||
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,
|
||||||
|
}
|
||||||
|
|
||||||
const collation: CollationOptions | undefined = this.collation
|
if (this.collation) {
|
||||||
? {
|
const defaultLocale = 'en'
|
||||||
locale: locale && locale !== 'all' && locale !== '*' ? locale : 'en',
|
paginationOptions.collation = {
|
||||||
...this.collation,
|
locale: locale && locale !== 'all' && locale !== '*' ? locale : defaultLocale,
|
||||||
}
|
...this.collation,
|
||||||
: undefined
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const joinAgreggation = await buildJoinAggregation({
|
if (
|
||||||
|
!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,
|
||||||
session,
|
query: versionQuery,
|
||||||
versions: true,
|
versions: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
const result = await findMany({
|
// build join aggregation
|
||||||
adapter: this,
|
if (aggregate) {
|
||||||
collation,
|
result = await VersionModel.aggregatePaginate(
|
||||||
collection: VersionModel.collection,
|
VersionModel.aggregate(aggregate),
|
||||||
joinAgreggation,
|
paginationOptions,
|
||||||
limit,
|
)
|
||||||
page,
|
} else {
|
||||||
pagination,
|
result = await VersionModel.paginate(versionQuery, paginationOptions)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
const docs = JSON.parse(JSON.stringify(result.docs))
|
||||||
|
|
||||||
|
return {
|
||||||
|
...result,
|
||||||
|
docs: docs.map((doc) => {
|
||||||
|
doc = {
|
||||||
|
_id: doc.parent,
|
||||||
|
id: doc.parent,
|
||||||
|
...doc.version,
|
||||||
|
}
|
||||||
|
|
||||||
|
return sanitizeInternalFields(doc)
|
||||||
|
}),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +1,47 @@
|
|||||||
|
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 { transform } from './utilities/transform.js'
|
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.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).flattenedFields
|
const fields = this.payload.config.globals.find((global) => global.slug === slug).fields
|
||||||
|
|
||||||
const session = await getSession(this, req)
|
const options: QueryOptions = {
|
||||||
|
...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),
|
||||||
|
}
|
||||||
|
|
||||||
transform({
|
let result
|
||||||
adapter: this,
|
|
||||||
|
const sanitizedData = sanitizeRelationshipIDs({
|
||||||
|
config: this.payload.config,
|
||||||
data,
|
data,
|
||||||
fields,
|
fields,
|
||||||
operation: 'update',
|
|
||||||
timestamps: optionsArgs.timestamps !== false,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const result: any = await Model.collection.findOneAndUpdate(
|
result = await Model.findOneAndUpdate({ globalType: slug }, sanitizedData, options)
|
||||||
{ globalType: slug },
|
|
||||||
{ $set: data },
|
|
||||||
{
|
|
||||||
...optionsArgs,
|
|
||||||
projection: buildProjectionFromSelect({ adapter: this, fields, select }),
|
|
||||||
returnDocument: 'after',
|
|
||||||
session,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
transform({
|
result = JSON.parse(JSON.stringify(result))
|
||||||
adapter: this,
|
|
||||||
data: result,
|
// custom id type reset
|
||||||
fields,
|
result.id = result._id
|
||||||
operation: 'read',
|
result = sanitizeInternalFields(result)
|
||||||
})
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
|
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 { transform } from './utilities/transform.js'
|
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'
|
||||||
|
|
||||||
export async function updateGlobalVersion<T extends TypeWithID>(
|
export async function updateGlobalVersion<T extends TypeWithID>(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
@@ -21,50 +23,44 @@ 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 session = await getSession(this, req)
|
const currentGlobal = this.payload.config.globals.find((global) => global.slug === globalSlug)
|
||||||
|
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,
|
||||||
})
|
})
|
||||||
|
|
||||||
transform({
|
const sanitizedData = sanitizeRelationshipIDs({
|
||||||
adapter: this,
|
config: this.payload.config,
|
||||||
data: versionData,
|
data: versionData,
|
||||||
fields,
|
fields,
|
||||||
operation: 'update',
|
|
||||||
timestamps: optionsArgs.timestamps !== false,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const doc: any = await VersionModel.collection.findOneAndUpdate(
|
const doc = await VersionModel.findOneAndUpdate(query, sanitizedData, options)
|
||||||
query,
|
|
||||||
{ $set: versionData },
|
|
||||||
{
|
|
||||||
...optionsArgs,
|
|
||||||
projection: buildProjectionFromSelect({
|
|
||||||
adapter: this,
|
|
||||||
fields,
|
|
||||||
select,
|
|
||||||
}),
|
|
||||||
returnDocument: 'after',
|
|
||||||
session,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
transform({
|
const result = JSON.parse(JSON.stringify(doc))
|
||||||
adapter: this,
|
|
||||||
data: doc,
|
|
||||||
fields,
|
|
||||||
operation: 'read',
|
|
||||||
})
|
|
||||||
|
|
||||||
return doc
|
const verificationToken = doc._verificationToken
|
||||||
|
|
||||||
|
// custom id type reset
|
||||||
|
result.id = result._id
|
||||||
|
if (verificationToken) {
|
||||||
|
result._verificationToken = verificationToken
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
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'
|
||||||
@@ -5,7 +6,8 @@ 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 { transform } from './utilities/transform.js'
|
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.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,
|
||||||
@@ -13,45 +15,42 @@ 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.flattenedFields
|
const fields = this.payload.collections[collection].config.fields
|
||||||
|
const options: QueryOptions = {
|
||||||
const session = await getSession(this, req)
|
...optionsArgs,
|
||||||
|
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,
|
||||||
})
|
})
|
||||||
|
|
||||||
transform({
|
let result
|
||||||
adapter: this,
|
|
||||||
|
const sanitizedData = sanitizeRelationshipIDs({
|
||||||
|
config: this.payload.config,
|
||||||
data,
|
data,
|
||||||
fields,
|
fields,
|
||||||
operation: 'update',
|
|
||||||
timestamps: optionsArgs.timestamps !== false,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await Model.collection.findOneAndUpdate(
|
result = await Model.findOneAndUpdate(query, sanitizedData, options)
|
||||||
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,10 +1,12 @@
|
|||||||
|
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 { transform } from './utilities/transform.js'
|
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'
|
||||||
|
|
||||||
export const updateVersion: UpdateVersion = async function updateVersion(
|
export const updateVersion: UpdateVersion = async function updateVersion(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
@@ -15,51 +17,46 @@ 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 session = await getSession(this, req)
|
const options: QueryOptions = {
|
||||||
|
...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,
|
||||||
})
|
})
|
||||||
|
|
||||||
transform({
|
const sanitizedData = sanitizeRelationshipIDs({
|
||||||
adapter: this,
|
config: this.payload.config,
|
||||||
data: versionData,
|
data: versionData,
|
||||||
fields,
|
fields,
|
||||||
operation: 'update',
|
|
||||||
timestamps: optionsArgs.timestamps !== false,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const doc = await VersionModel.collection.findOneAndUpdate(
|
const doc = await VersionModel.findOneAndUpdate(query, sanitizedData, options)
|
||||||
query,
|
|
||||||
{ $set: versionData },
|
|
||||||
{
|
|
||||||
...optionsArgs,
|
|
||||||
projection: buildProjectionFromSelect({
|
|
||||||
adapter: this,
|
|
||||||
fields: buildVersionCollectionFields(
|
|
||||||
this.payload.config,
|
|
||||||
this.payload.collections[collection].config,
|
|
||||||
true,
|
|
||||||
),
|
|
||||||
select,
|
|
||||||
}),
|
|
||||||
returnDocument: 'after',
|
|
||||||
session,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
transform({
|
const result = JSON.parse(JSON.stringify(doc))
|
||||||
adapter: this,
|
|
||||||
data: doc,
|
|
||||||
fields,
|
|
||||||
operation: 'read',
|
|
||||||
})
|
|
||||||
|
|
||||||
return doc as any
|
const verificationToken = doc._verificationToken
|
||||||
|
|
||||||
|
// custom id type reset
|
||||||
|
result.id = result._id
|
||||||
|
if (verificationToken) {
|
||||||
|
result._verificationToken = verificationToken
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
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'
|
||||||
|
|
||||||
@@ -11,9 +10,12 @@ 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>
|
||||||
session?: ClientSession
|
// the where clause for the top collection
|
||||||
|
query?: Where
|
||||||
/** whether the query is from drafts */
|
/** whether the query is from drafts */
|
||||||
versions?: boolean
|
versions?: boolean
|
||||||
}
|
}
|
||||||
@@ -23,9 +25,10 @@ export const buildJoinAggregation = async ({
|
|||||||
collection,
|
collection,
|
||||||
collectionConfig,
|
collectionConfig,
|
||||||
joins,
|
joins,
|
||||||
|
limit,
|
||||||
locale,
|
locale,
|
||||||
projection,
|
projection,
|
||||||
session,
|
query,
|
||||||
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) {
|
||||||
@@ -33,7 +36,23 @@ 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]) {
|
||||||
@@ -53,25 +72,26 @@ 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,
|
$sort: { [sortProperty]: sortDirection },
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -169,8 +189,8 @@ export const buildJoinAggregation = async ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!aggregate.length) {
|
if (projection) {
|
||||||
return
|
aggregate.push({ $project: projection })
|
||||||
}
|
}
|
||||||
|
|
||||||
return aggregate
|
return aggregate
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Field, FieldAffectingData, FlattenedField, SelectMode, SelectType } from 'payload'
|
import type { FieldAffectingData, FlattenedField, SelectMode, SelectType } from 'payload'
|
||||||
|
|
||||||
import { deepCopyObjectSimple, fieldAffectsData, getSelectMode } from 'payload/shared'
|
import { deepCopyObjectSimple, fieldAffectsData, getSelectMode } from 'payload/shared'
|
||||||
|
|
||||||
@@ -29,11 +29,6 @@ const addFieldToProjection = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const blockTypeField: Field = {
|
|
||||||
name: 'blockType',
|
|
||||||
type: 'text',
|
|
||||||
}
|
|
||||||
|
|
||||||
const traverseFields = ({
|
const traverseFields = ({
|
||||||
adapter,
|
adapter,
|
||||||
databaseSchemaPath = '',
|
databaseSchemaPath = '',
|
||||||
@@ -133,14 +128,6 @@ 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,
|
||||||
@@ -166,13 +153,7 @@ const traverseFields = ({
|
|||||||
|
|
||||||
if (blockSelectMode === 'include') {
|
if (blockSelectMode === 'include') {
|
||||||
blocksSelect[block.slug]['id'] = true
|
blocksSelect[block.slug]['id'] = true
|
||||||
addFieldToProjection({
|
blocksSelect[block.slug]['blockType'] = true
|
||||||
adapter,
|
|
||||||
databaseSchemaPath: fieldDatabaseSchemaPath,
|
|
||||||
field: blockTypeField,
|
|
||||||
projection,
|
|
||||||
withinLocalizedField: fieldWithinLocalizedField,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
traverseFields({
|
traverseFields({
|
||||||
|
|||||||
@@ -1,128 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
20
packages/db-mongodb/src/utilities/sanitizeInternalFields.ts
Normal file
20
packages/db-mongodb/src/utilities/sanitizeInternalFields.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
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,9 +1,8 @@
|
|||||||
import { flattenAllFields, type Field, type SanitizedConfig } from 'payload'
|
import type { Field, SanitizedConfig } from 'payload'
|
||||||
|
|
||||||
import { Types } from 'mongoose'
|
import { Types } from 'mongoose'
|
||||||
|
|
||||||
import { transform } from './transform.js'
|
import { sanitizeRelationshipIDs } from './sanitizeRelationshipIDs.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(
|
||||||
@@ -272,8 +271,8 @@ const relsData = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('transform', () => {
|
describe('sanitizeRelationshipIDs', () => {
|
||||||
it('should sanitize relationships with transform write', () => {
|
it('should sanitize relationships', () => {
|
||||||
const data = {
|
const data = {
|
||||||
...relsData,
|
...relsData,
|
||||||
array: [
|
array: [
|
||||||
@@ -349,19 +348,12 @@ describe('transform', () => {
|
|||||||
}
|
}
|
||||||
const flattenValuesBefore = Object.values(flattenRelationshipValues(data))
|
const flattenValuesBefore = Object.values(flattenRelationshipValues(data))
|
||||||
|
|
||||||
const mockAdapter = { payload: { config } } as MongooseAdapter
|
sanitizeRelationshipIDs({ config, data, fields: config.collections[0].fields })
|
||||||
|
|
||||||
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 })
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
156
packages/db-mongodb/src/utilities/sanitizeRelationshipIDs.ts
Normal file
156
packages/db-mongodb/src/utilities/sanitizeRelationshipIDs.ts
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
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,385 +0,0 @@
|
|||||||
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,11 +126,6 @@ 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,12 +875,7 @@ 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,10 +1362,7 @@ 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 {
|
export type { TraverseFieldsCallback } from './utilities/traverseFields.js'
|
||||||
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,12 +1,4 @@
|
|||||||
import type {
|
import type { ArrayField, BlocksField, Field, TabAsField } from '../fields/config/types.js'
|
||||||
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'
|
||||||
|
|
||||||
@@ -20,7 +12,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) {
|
||||||
@@ -36,23 +28,20 @@ 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) as FlattenedBlock
|
const block = field.blocks.find((block) => block.slug === ref.blockType)
|
||||||
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 (flattenedFields || fields) {
|
if (fields) {
|
||||||
traverseFields({ callback, fields, fillEmpty, flattenedFields, parentRef, ref })
|
traverseFields({ callback, fields, fillEmpty, parentRef, ref })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type TraverseFieldsCallbackArgs = {
|
export type TraverseFieldsCallback = (args: {
|
||||||
/**
|
/**
|
||||||
* The current field
|
* The current field
|
||||||
*/
|
*/
|
||||||
@@ -69,45 +58,12 @@ type TraverseFieldsCallbackArgs = {
|
|||||||
* 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 | FlattenedField | TabAsField)[]
|
fields: (Field | 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
|
||||||
}
|
}
|
||||||
@@ -125,11 +81,10 @@ export const traverseFields = ({
|
|||||||
callback,
|
callback,
|
||||||
fields,
|
fields,
|
||||||
fillEmpty = true,
|
fillEmpty = true,
|
||||||
flattenedFields,
|
|
||||||
parentRef = {},
|
parentRef = {},
|
||||||
ref = {},
|
ref = {},
|
||||||
}: TraverseFieldsArgs | TraverseFlattenedFieldsArgs): void => {
|
}: TraverseFieldsArgs): void => {
|
||||||
;(flattenedFields ?? fields).some((field) => {
|
fields.some((field) => {
|
||||||
let skip = false
|
let skip = false
|
||||||
const next = () => {
|
const next = () => {
|
||||||
skip = true
|
skip = true
|
||||||
@@ -139,16 +94,7 @@ export const traverseFields = ({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (callback && callback({ field, next, parentRef, ref })) {
|
||||||
callback &&
|
|
||||||
callback({
|
|
||||||
// @ts-expect-error compatibillity Field | FlattenedField
|
|
||||||
field,
|
|
||||||
next,
|
|
||||||
parentRef,
|
|
||||||
ref,
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,7 +139,6 @@ 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,
|
||||||
@@ -215,15 +160,12 @@ export const traverseFields = ({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (field.type !== 'tab' && (fieldHasSubFields(field) || field.type === 'blocks')) {
|
||||||
(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' || field.type === 'tab') {
|
if (field.type === 'group') {
|
||||||
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) {
|
||||||
@@ -240,7 +182,7 @@ export const traverseFields = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(field.type === 'group' || field.type === 'tab') &&
|
field.type === 'group' &&
|
||||||
field.localized &&
|
field.localized &&
|
||||||
currentRef &&
|
currentRef &&
|
||||||
typeof currentRef === 'object'
|
typeof currentRef === 'object'
|
||||||
@@ -251,10 +193,9 @@ 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
|
||||||
@@ -298,7 +239,6 @@ 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,12 +53,10 @@ describe('Relationship Fields', () => {
|
|||||||
collection: versionedRelationshipFieldSlug,
|
collection: versionedRelationshipFieldSlug,
|
||||||
data: {
|
data: {
|
||||||
title: 'Version 1 Title',
|
title: 'Version 1 Title',
|
||||||
relationshipField: [
|
relationshipField: {
|
||||||
{
|
value: relatedDoc.id,
|
||||||
value: relatedDoc.id,
|
relationTo: collection1Slug,
|
||||||
relationTo: collection1Slug,
|
},
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1134,30 +1134,6 @@ 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', () => {
|
||||||
|
|||||||
@@ -174,10 +174,7 @@ describe('Joins Field', () => {
|
|||||||
collection: categoriesSlug,
|
collection: categoriesSlug,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(categoryWithPosts).toStrictEqual({
|
expect(Object.keys(categoryWithPosts)).toStrictEqual(['id', 'group'])
|
||||||
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,10 +1633,7 @@ describe('Select', () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(res).toStrictEqual({
|
expect(Object.keys(res)).toStrictEqual(['id', 'text'])
|
||||||
id: res.id,
|
|
||||||
text: res.text,
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should apply select with updateByID', async () => {
|
it('should apply select with updateByID', async () => {
|
||||||
@@ -1649,18 +1646,13 @@ describe('Select', () => {
|
|||||||
select: { text: true },
|
select: { text: true },
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(res).toStrictEqual({
|
expect(Object.keys(res)).toStrictEqual(['id', 'text'])
|
||||||
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 {
|
const res = await payload.update({
|
||||||
docs: [res],
|
|
||||||
} = await payload.update({
|
|
||||||
collection: 'posts',
|
collection: 'posts',
|
||||||
where: {
|
where: {
|
||||||
id: {
|
id: {
|
||||||
@@ -1671,10 +1663,7 @@ describe('Select', () => {
|
|||||||
select: { text: true },
|
select: { text: true },
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(res).toStrictEqual({
|
expect(Object.keys(res.docs[0])).toStrictEqual(['id', 'text'])
|
||||||
id: res.id,
|
|
||||||
text: res.text,
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should apply select with deleteByID', async () => {
|
it('should apply select with deleteByID', async () => {
|
||||||
@@ -1686,18 +1675,13 @@ describe('Select', () => {
|
|||||||
select: { text: true },
|
select: { text: true },
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(res).toStrictEqual({
|
expect(Object.keys(res)).toStrictEqual(['id', 'text'])
|
||||||
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 {
|
const res = await payload.delete({
|
||||||
docs: [res],
|
|
||||||
} = await payload.delete({
|
|
||||||
collection: 'posts',
|
collection: 'posts',
|
||||||
where: {
|
where: {
|
||||||
id: {
|
id: {
|
||||||
@@ -1707,10 +1691,7 @@ describe('Select', () => {
|
|||||||
select: { text: true },
|
select: { text: true },
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(res).toStrictEqual({
|
expect(Object.keys(res.docs[0])).toStrictEqual(['id', 'text'])
|
||||||
id: res.id,
|
|
||||||
text: res.text,
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should apply select with duplicate', async () => {
|
it('should apply select with duplicate', async () => {
|
||||||
@@ -1722,10 +1703,7 @@ describe('Select', () => {
|
|||||||
select: { text: true },
|
select: { text: true },
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(res).toStrictEqual({
|
expect(Object.keys(res)).toStrictEqual(['id', 'text'])
|
||||||
id: res.id,
|
|
||||||
text: res.text,
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user