perf(db-mongodb): remove JSON.parse(JSON.stringify) copying of results (#11293)
Improves performance and optimizes memory usage for mongodb adapter by cutting down copying of results via `JSON.parse(JSON.stringify())`. Instead, `transform` does necessary transformations (`ObjectID` -> `string,` `Date` -> `string`) without any copying
This commit is contained in:
@@ -5,7 +5,7 @@ import type { MongooseAdapter } from './index.js'
|
|||||||
|
|
||||||
import { getSession } from './utilities/getSession.js'
|
import { getSession } from './utilities/getSession.js'
|
||||||
import { handleError } from './utilities/handleError.js'
|
import { handleError } from './utilities/handleError.js'
|
||||||
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'
|
import { transform } from './utilities/transform.js'
|
||||||
|
|
||||||
export const create: Create = async function create(
|
export const create: Create = async function create(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
@@ -18,31 +18,31 @@ export const create: Create = async function create(
|
|||||||
|
|
||||||
let doc
|
let doc
|
||||||
|
|
||||||
const sanitizedData = sanitizeRelationshipIDs({
|
transform({
|
||||||
config: this.payload.config,
|
adapter: this,
|
||||||
data,
|
data,
|
||||||
fields: this.payload.collections[collection].config.fields,
|
fields: this.payload.collections[collection].config.fields,
|
||||||
|
operation: 'write',
|
||||||
})
|
})
|
||||||
|
|
||||||
if (this.payload.collections[collection].customIDType) {
|
if (this.payload.collections[collection].customIDType) {
|
||||||
sanitizedData._id = sanitizedData.id
|
data._id = data.id
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
;[doc] = await Model.create([sanitizedData], options)
|
;[doc] = await Model.create([data], options)
|
||||||
} 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
|
doc = doc.toObject()
|
||||||
const result: Document = JSON.parse(JSON.stringify(doc))
|
|
||||||
const verificationToken = doc._verificationToken
|
|
||||||
|
|
||||||
// custom id type reset
|
transform({
|
||||||
result.id = result._id
|
adapter: this,
|
||||||
if (verificationToken) {
|
data: doc,
|
||||||
result._verificationToken = verificationToken
|
fields: this.payload.collections[collection].config.fields,
|
||||||
}
|
operation: 'read',
|
||||||
|
})
|
||||||
|
|
||||||
return result
|
return doc
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ import type { CreateGlobal } from 'payload'
|
|||||||
import type { MongooseAdapter } from './index.js'
|
import type { MongooseAdapter } from './index.js'
|
||||||
|
|
||||||
import { getSession } from './utilities/getSession.js'
|
import { getSession } from './utilities/getSession.js'
|
||||||
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
import { transform } from './utilities/transform.js'
|
||||||
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'
|
|
||||||
|
|
||||||
export const createGlobal: CreateGlobal = async function createGlobal(
|
export const createGlobal: CreateGlobal = async function createGlobal(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
@@ -13,26 +12,28 @@ export const createGlobal: CreateGlobal = async function createGlobal(
|
|||||||
) {
|
) {
|
||||||
const Model = this.globals
|
const Model = this.globals
|
||||||
|
|
||||||
const global = sanitizeRelationshipIDs({
|
transform({
|
||||||
config: this.payload.config,
|
adapter: this,
|
||||||
data: {
|
data,
|
||||||
globalType: slug,
|
|
||||||
...data,
|
|
||||||
},
|
|
||||||
fields: this.payload.config.globals.find((globalConfig) => globalConfig.slug === slug).fields,
|
fields: this.payload.config.globals.find((globalConfig) => globalConfig.slug === slug).fields,
|
||||||
|
globalSlug: slug,
|
||||||
|
operation: 'write',
|
||||||
})
|
})
|
||||||
|
|
||||||
const options: CreateOptions = {
|
const options: CreateOptions = {
|
||||||
session: await getSession(this, req),
|
session: await getSession(this, req),
|
||||||
}
|
}
|
||||||
|
|
||||||
let [result] = (await Model.create([global], options)) as any
|
let [result] = (await Model.create([data], options)) as any
|
||||||
|
|
||||||
result = JSON.parse(JSON.stringify(result))
|
result = result.toObject()
|
||||||
|
|
||||||
// custom id type reset
|
transform({
|
||||||
result.id = result._id
|
adapter: this,
|
||||||
result = sanitizeInternalFields(result)
|
data: result,
|
||||||
|
fields: this.payload.config.globals.find((globalConfig) => globalConfig.slug === slug).fields,
|
||||||
|
operation: 'read',
|
||||||
|
})
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import type { CreateOptions } from 'mongoose'
|
import type { CreateOptions } from 'mongoose'
|
||||||
|
|
||||||
import { buildVersionGlobalFields, type CreateGlobalVersion, type Document } from 'payload'
|
import { buildVersionGlobalFields, type CreateGlobalVersion } from 'payload'
|
||||||
|
|
||||||
import type { MongooseAdapter } from './index.js'
|
import type { MongooseAdapter } from './index.js'
|
||||||
|
|
||||||
import { getSession } from './utilities/getSession.js'
|
import { getSession } from './utilities/getSession.js'
|
||||||
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'
|
import { transform } from './utilities/transform.js'
|
||||||
|
|
||||||
export const createGlobalVersion: CreateGlobalVersion = async function createGlobalVersion(
|
export const createGlobalVersion: CreateGlobalVersion = async function createGlobalVersion(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
@@ -26,25 +26,30 @@ export const createGlobalVersion: CreateGlobalVersion = async function createGlo
|
|||||||
session: await getSession(this, req),
|
session: await getSession(this, req),
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = sanitizeRelationshipIDs({
|
const data = {
|
||||||
config: this.payload.config,
|
autosave,
|
||||||
data: {
|
createdAt,
|
||||||
autosave,
|
latest: true,
|
||||||
createdAt,
|
parent,
|
||||||
latest: true,
|
publishedLocale,
|
||||||
parent,
|
snapshot,
|
||||||
publishedLocale,
|
updatedAt,
|
||||||
snapshot,
|
version: versionData,
|
||||||
updatedAt,
|
}
|
||||||
version: versionData,
|
|
||||||
},
|
const fields = buildVersionGlobalFields(
|
||||||
fields: buildVersionGlobalFields(
|
this.payload.config,
|
||||||
this.payload.config,
|
this.payload.config.globals.find((global) => global.slug === globalSlug),
|
||||||
this.payload.config.globals.find((global) => global.slug === globalSlug),
|
)
|
||||||
),
|
|
||||||
|
transform({
|
||||||
|
adapter: this,
|
||||||
|
data,
|
||||||
|
fields,
|
||||||
|
operation: 'write',
|
||||||
})
|
})
|
||||||
|
|
||||||
const [doc] = await VersionModel.create([data], options, req)
|
let [doc] = await VersionModel.create([data], options, req)
|
||||||
|
|
||||||
await VersionModel.updateMany(
|
await VersionModel.updateMany(
|
||||||
{
|
{
|
||||||
@@ -70,13 +75,14 @@ export const createGlobalVersion: CreateGlobalVersion = async function createGlo
|
|||||||
options,
|
options,
|
||||||
)
|
)
|
||||||
|
|
||||||
const result: Document = JSON.parse(JSON.stringify(doc))
|
doc = doc.toObject()
|
||||||
const verificationToken = doc._verificationToken
|
|
||||||
|
|
||||||
// custom id type reset
|
transform({
|
||||||
result.id = result._id
|
adapter: this,
|
||||||
if (verificationToken) {
|
data: doc,
|
||||||
result._verificationToken = verificationToken
|
fields,
|
||||||
}
|
operation: 'read',
|
||||||
return result
|
})
|
||||||
|
|
||||||
|
return doc
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import type { CreateOptions } from 'mongoose'
|
import type { CreateOptions } 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 { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'
|
import { transform } from './utilities/transform.js'
|
||||||
|
|
||||||
export const createVersion: CreateVersion = async function createVersion(
|
export const createVersion: CreateVersion = async function createVersion(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
@@ -27,25 +26,30 @@ export const createVersion: CreateVersion = async function createVersion(
|
|||||||
session: await getSession(this, req),
|
session: await getSession(this, req),
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = sanitizeRelationshipIDs({
|
const data = {
|
||||||
config: this.payload.config,
|
autosave,
|
||||||
data: {
|
createdAt,
|
||||||
autosave,
|
latest: true,
|
||||||
createdAt,
|
parent,
|
||||||
latest: true,
|
publishedLocale,
|
||||||
parent,
|
snapshot,
|
||||||
publishedLocale,
|
updatedAt,
|
||||||
snapshot,
|
version: versionData,
|
||||||
updatedAt,
|
}
|
||||||
version: versionData,
|
|
||||||
},
|
const fields = buildVersionCollectionFields(
|
||||||
fields: buildVersionCollectionFields(
|
this.payload.config,
|
||||||
this.payload.config,
|
this.payload.collections[collectionSlug].config,
|
||||||
this.payload.collections[collectionSlug].config,
|
)
|
||||||
),
|
|
||||||
|
transform({
|
||||||
|
adapter: this,
|
||||||
|
data,
|
||||||
|
fields,
|
||||||
|
operation: 'write',
|
||||||
})
|
})
|
||||||
|
|
||||||
const [doc] = await VersionModel.create([data], options, req)
|
let [doc] = await VersionModel.create([data], options, req)
|
||||||
|
|
||||||
const parentQuery = {
|
const parentQuery = {
|
||||||
$or: [
|
$or: [
|
||||||
@@ -56,13 +60,6 @@ export const createVersion: CreateVersion = async function createVersion(
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
if (data.parent instanceof Types.ObjectId) {
|
|
||||||
parentQuery.$or.push({
|
|
||||||
parent: {
|
|
||||||
$eq: data.parent.toString(),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
await VersionModel.updateMany(
|
await VersionModel.updateMany(
|
||||||
{
|
{
|
||||||
@@ -89,13 +86,14 @@ export const createVersion: CreateVersion = async function createVersion(
|
|||||||
options,
|
options,
|
||||||
)
|
)
|
||||||
|
|
||||||
const result: Document = JSON.parse(JSON.stringify(doc))
|
doc = doc.toObject()
|
||||||
const verificationToken = doc._verificationToken
|
|
||||||
|
|
||||||
// custom id type reset
|
transform({
|
||||||
result.id = result._id
|
adapter: this,
|
||||||
if (verificationToken) {
|
data: doc,
|
||||||
result._verificationToken = verificationToken
|
fields,
|
||||||
}
|
operation: 'read',
|
||||||
return result
|
})
|
||||||
|
|
||||||
|
return doc
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import type { QueryOptions } from 'mongoose'
|
import type { QueryOptions } from 'mongoose'
|
||||||
import type { DeleteOne, Document } from 'payload'
|
import type { DeleteOne } from 'payload'
|
||||||
|
|
||||||
import type { MongooseAdapter } from './index.js'
|
import type { MongooseAdapter } from './index.js'
|
||||||
|
|
||||||
import { buildQuery } from './queries/buildQuery.js'
|
import { buildQuery } from './queries/buildQuery.js'
|
||||||
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
||||||
import { getSession } from './utilities/getSession.js'
|
import { getSession } from './utilities/getSession.js'
|
||||||
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
import { transform } from './utilities/transform.js'
|
||||||
|
|
||||||
export const deleteOne: DeleteOne = async function deleteOne(
|
export const deleteOne: DeleteOne = async function deleteOne(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
@@ -35,11 +35,12 @@ export const deleteOne: DeleteOne = async function deleteOne(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
let result: Document = JSON.parse(JSON.stringify(doc))
|
transform({
|
||||||
|
adapter: this,
|
||||||
|
data: doc,
|
||||||
|
fields: this.payload.collections[collection].config.fields,
|
||||||
|
operation: 'read',
|
||||||
|
})
|
||||||
|
|
||||||
// custom id type reset
|
return doc
|
||||||
result.id = result._id
|
|
||||||
result = sanitizeInternalFields(result)
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ 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 { getSession } from './utilities/getSession.js'
|
import { getSession } from './utilities/getSession.js'
|
||||||
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
import { transform } from './utilities/transform.js'
|
||||||
|
|
||||||
export const find: Find = async function find(
|
export const find: Find = async function find(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
@@ -133,13 +133,12 @@ export const find: Find = async function find(
|
|||||||
result = await Model.paginate(query, paginationOptions)
|
result = await Model.paginate(query, paginationOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
const docs = JSON.parse(JSON.stringify(result.docs))
|
transform({
|
||||||
|
adapter: this,
|
||||||
|
data: result.docs,
|
||||||
|
fields: this.payload.collections[collection].config.fields,
|
||||||
|
operation: 'read',
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return result
|
||||||
...result,
|
|
||||||
docs: docs.map((doc) => {
|
|
||||||
doc.id = doc._id
|
|
||||||
return sanitizeInternalFields(doc)
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,14 +8,15 @@ import type { MongooseAdapter } from './index.js'
|
|||||||
import { buildQuery } from './queries/buildQuery.js'
|
import { buildQuery } from './queries/buildQuery.js'
|
||||||
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
||||||
import { getSession } from './utilities/getSession.js'
|
import { getSession } from './utilities/getSession.js'
|
||||||
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
import { transform } from './utilities/transform.js'
|
||||||
|
|
||||||
export const findGlobal: FindGlobal = async function findGlobal(
|
export const findGlobal: FindGlobal = async function findGlobal(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
{ slug, locale, req, select, where },
|
{ slug, locale, req, select, where },
|
||||||
) {
|
) {
|
||||||
const Model = this.globals
|
const Model = this.globals
|
||||||
const fields = this.payload.globals.config.find((each) => each.slug === slug).flattenedFields
|
const globalConfig = this.payload.globals.config.find((each) => each.slug === slug)
|
||||||
|
const fields = globalConfig.flattenedFields
|
||||||
const options: QueryOptions = {
|
const options: QueryOptions = {
|
||||||
lean: true,
|
lean: true,
|
||||||
select: buildProjectionFromSelect({
|
select: buildProjectionFromSelect({
|
||||||
@@ -34,18 +35,18 @@ export const findGlobal: FindGlobal = async function findGlobal(
|
|||||||
where: combineQueries({ globalType: { equals: slug } }, where),
|
where: combineQueries({ globalType: { equals: slug } }, where),
|
||||||
})
|
})
|
||||||
|
|
||||||
let doc = (await Model.findOne(query, {}, options)) as any
|
const doc = (await Model.findOne(query, {}, options)) as any
|
||||||
|
|
||||||
if (!doc) {
|
if (!doc) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
if (doc._id) {
|
|
||||||
doc.id = doc._id
|
|
||||||
delete doc._id
|
|
||||||
}
|
|
||||||
|
|
||||||
doc = JSON.parse(JSON.stringify(doc))
|
transform({
|
||||||
doc = sanitizeInternalFields(doc)
|
adapter: this,
|
||||||
|
data: doc,
|
||||||
|
fields: globalConfig.fields,
|
||||||
|
operation: 'read',
|
||||||
|
})
|
||||||
|
|
||||||
return doc
|
return doc
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,18 +9,15 @@ import { buildQuery } from './queries/buildQuery.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 { getSession } from './utilities/getSession.js'
|
import { getSession } from './utilities/getSession.js'
|
||||||
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
import { transform } from './utilities/transform.js'
|
||||||
|
|
||||||
export const findGlobalVersions: FindGlobalVersions = async function findGlobalVersions(
|
export const findGlobalVersions: FindGlobalVersions = async function findGlobalVersions(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
{ global, limit, locale, page, pagination, req, select, skip, sort: sortArg, where },
|
{ global, limit, locale, page, pagination, req, select, skip, sort: sortArg, where },
|
||||||
) {
|
) {
|
||||||
|
const globalConfig = this.payload.globals.config.find(({ slug }) => slug === global)
|
||||||
const Model = this.versions[global]
|
const Model = this.versions[global]
|
||||||
const versionFields = buildVersionGlobalFields(
|
const versionFields = buildVersionGlobalFields(this.payload.config, globalConfig, true)
|
||||||
this.payload.config,
|
|
||||||
this.payload.globals.config.find(({ slug }) => slug === global),
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
|
|
||||||
const session = await getSession(this, req)
|
const session = await getSession(this, req)
|
||||||
const options: QueryOptions = {
|
const options: QueryOptions = {
|
||||||
@@ -103,13 +100,13 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = await Model.paginate(query, paginationOptions)
|
const result = await Model.paginate(query, paginationOptions)
|
||||||
const docs = JSON.parse(JSON.stringify(result.docs))
|
|
||||||
|
|
||||||
return {
|
transform({
|
||||||
...result,
|
adapter: this,
|
||||||
docs: docs.map((doc) => {
|
data: result.docs,
|
||||||
doc.id = doc._id
|
fields: buildVersionGlobalFields(this.payload.config, globalConfig),
|
||||||
return sanitizeInternalFields(doc)
|
operation: 'read',
|
||||||
}),
|
})
|
||||||
}
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { AggregateOptions, QueryOptions } from 'mongoose'
|
import type { AggregateOptions, QueryOptions } from 'mongoose'
|
||||||
import type { Document, FindOne } from 'payload'
|
import type { FindOne } from 'payload'
|
||||||
|
|
||||||
import type { MongooseAdapter } from './index.js'
|
import type { MongooseAdapter } from './index.js'
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ import { buildQuery } from './queries/buildQuery.js'
|
|||||||
import { buildJoinAggregation } from './utilities/buildJoinAggregation.js'
|
import { buildJoinAggregation } from './utilities/buildJoinAggregation.js'
|
||||||
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
||||||
import { getSession } from './utilities/getSession.js'
|
import { getSession } from './utilities/getSession.js'
|
||||||
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
import { transform } from './utilities/transform.js'
|
||||||
|
|
||||||
export const findOne: FindOne = async function findOne(
|
export const findOne: FindOne = async function findOne(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
@@ -58,11 +58,7 @@ export const findOne: FindOne = async function findOne(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
let result: Document = JSON.parse(JSON.stringify(doc))
|
transform({ adapter: this, data: doc, fields: collectionConfig.fields, operation: 'read' })
|
||||||
|
|
||||||
// custom id type reset
|
return doc
|
||||||
result.id = result._id
|
|
||||||
result = sanitizeInternalFields(result)
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { buildQuery } from './queries/buildQuery.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 { getSession } from './utilities/getSession.js'
|
import { getSession } from './utilities/getSession.js'
|
||||||
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
import { transform } from './utilities/transform.js'
|
||||||
|
|
||||||
export const findVersions: FindVersions = async function findVersions(
|
export const findVersions: FindVersions = async function findVersions(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
@@ -104,13 +104,13 @@ export const findVersions: FindVersions = async function findVersions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = await Model.paginate(query, paginationOptions)
|
const result = await Model.paginate(query, paginationOptions)
|
||||||
const docs = JSON.parse(JSON.stringify(result.docs))
|
|
||||||
|
|
||||||
return {
|
transform({
|
||||||
...result,
|
adapter: this,
|
||||||
docs: docs.map((doc) => {
|
data: result.docs,
|
||||||
doc.id = doc._id
|
fields: buildVersionCollectionFields(this.payload.config, collectionConfig),
|
||||||
return sanitizeInternalFields(doc)
|
operation: 'read',
|
||||||
}),
|
})
|
||||||
}
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -476,6 +476,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
|||||||
|
|
||||||
if (fieldShouldBeLocalized({ field, parentIsLocalized }) && payload.config.localization) {
|
if (fieldShouldBeLocalized({ field, parentIsLocalized }) && payload.config.localization) {
|
||||||
schemaToReturn = {
|
schemaToReturn = {
|
||||||
|
_id: false,
|
||||||
type: payload.config.localization.localeCodes.reduce((locales, locale) => {
|
type: payload.config.localization.localeCodes.reduce((locales, locale) => {
|
||||||
let localeSchema: { [key: string]: any } = {}
|
let localeSchema: { [key: string]: any } = {}
|
||||||
|
|
||||||
@@ -698,6 +699,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
|||||||
|
|
||||||
if (fieldShouldBeLocalized({ field, parentIsLocalized }) && payload.config.localization) {
|
if (fieldShouldBeLocalized({ field, parentIsLocalized }) && payload.config.localization) {
|
||||||
schemaToReturn = {
|
schemaToReturn = {
|
||||||
|
_id: false,
|
||||||
type: payload.config.localization.localeCodes.reduce((locales, locale) => {
|
type: payload.config.localization.localeCodes.reduce((locales, locale) => {
|
||||||
let localeSchema: { [key: string]: any } = {}
|
let localeSchema: { [key: string]: any } = {}
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload'
|
|||||||
import type { MongooseAdapter } from '../index.js'
|
import type { MongooseAdapter } from '../index.js'
|
||||||
|
|
||||||
import { getSession } from '../utilities/getSession.js'
|
import { getSession } from '../utilities/getSession.js'
|
||||||
import { sanitizeRelationshipIDs } from '../utilities/sanitizeRelationshipIDs.js'
|
import { transform } from '../utilities/transform.js'
|
||||||
|
|
||||||
const migrateModelWithBatching = async ({
|
const migrateModelWithBatching = async ({
|
||||||
batchSize,
|
batchSize,
|
||||||
config,
|
config,
|
||||||
|
db,
|
||||||
fields,
|
fields,
|
||||||
Model,
|
Model,
|
||||||
parentIsLocalized,
|
parentIsLocalized,
|
||||||
@@ -18,6 +19,7 @@ const migrateModelWithBatching = async ({
|
|||||||
}: {
|
}: {
|
||||||
batchSize: number
|
batchSize: number
|
||||||
config: SanitizedConfig
|
config: SanitizedConfig
|
||||||
|
db: MongooseAdapter
|
||||||
fields: Field[]
|
fields: Field[]
|
||||||
Model: Model<any>
|
Model: Model<any>
|
||||||
parentIsLocalized: boolean
|
parentIsLocalized: boolean
|
||||||
@@ -49,7 +51,7 @@ const migrateModelWithBatching = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const doc of docs) {
|
for (const doc of docs) {
|
||||||
sanitizeRelationshipIDs({ config, data: doc, fields, parentIsLocalized })
|
transform({ adapter: db, data: doc, fields, operation: 'write', parentIsLocalized })
|
||||||
}
|
}
|
||||||
|
|
||||||
await Model.collection.bulkWrite(
|
await Model.collection.bulkWrite(
|
||||||
@@ -124,6 +126,7 @@ export async function migrateRelationshipsV2_V3({
|
|||||||
await migrateModelWithBatching({
|
await migrateModelWithBatching({
|
||||||
batchSize,
|
batchSize,
|
||||||
config,
|
config,
|
||||||
|
db,
|
||||||
fields: collection.fields,
|
fields: collection.fields,
|
||||||
Model: db.collections[collection.slug],
|
Model: db.collections[collection.slug],
|
||||||
parentIsLocalized: false,
|
parentIsLocalized: false,
|
||||||
@@ -139,6 +142,7 @@ export async function migrateRelationshipsV2_V3({
|
|||||||
await migrateModelWithBatching({
|
await migrateModelWithBatching({
|
||||||
batchSize,
|
batchSize,
|
||||||
config,
|
config,
|
||||||
|
db,
|
||||||
fields: buildVersionCollectionFields(config, collection),
|
fields: buildVersionCollectionFields(config, collection),
|
||||||
Model: db.versions[collection.slug],
|
Model: db.versions[collection.slug],
|
||||||
parentIsLocalized: false,
|
parentIsLocalized: false,
|
||||||
@@ -167,10 +171,11 @@ export async function migrateRelationshipsV2_V3({
|
|||||||
|
|
||||||
// in case if the global doesn't exist in the database yet (not saved)
|
// in case if the global doesn't exist in the database yet (not saved)
|
||||||
if (doc) {
|
if (doc) {
|
||||||
sanitizeRelationshipIDs({
|
transform({
|
||||||
config,
|
adapter: db,
|
||||||
data: doc,
|
data: doc,
|
||||||
fields: global.fields,
|
fields: global.fields,
|
||||||
|
operation: 'write',
|
||||||
})
|
})
|
||||||
|
|
||||||
await GlobalsModel.collection.updateOne(
|
await GlobalsModel.collection.updateOne(
|
||||||
@@ -191,6 +196,7 @@ export async function migrateRelationshipsV2_V3({
|
|||||||
await migrateModelWithBatching({
|
await migrateModelWithBatching({
|
||||||
batchSize,
|
batchSize,
|
||||||
config,
|
config,
|
||||||
|
db,
|
||||||
fields: buildVersionGlobalFields(config, global),
|
fields: buildVersionGlobalFields(config, global),
|
||||||
Model: db.versions[global.slug],
|
Model: db.versions[global.slug],
|
||||||
parentIsLocalized: false,
|
parentIsLocalized: false,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ 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 { getSession } from './utilities/getSession.js'
|
import { getSession } from './utilities/getSession.js'
|
||||||
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
import { transform } from './utilities/transform.js'
|
||||||
|
|
||||||
export const queryDrafts: QueryDrafts = async function queryDrafts(
|
export const queryDrafts: QueryDrafts = async function queryDrafts(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
@@ -124,18 +124,18 @@ export const queryDrafts: QueryDrafts = async function queryDrafts(
|
|||||||
result = await VersionModel.paginate(versionQuery, paginationOptions)
|
result = await VersionModel.paginate(versionQuery, paginationOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
const docs = JSON.parse(JSON.stringify(result.docs))
|
transform({
|
||||||
|
adapter: this,
|
||||||
|
data: result.docs,
|
||||||
|
fields: buildVersionCollectionFields(this.payload.config, collectionConfig),
|
||||||
|
operation: 'read',
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
for (let i = 0; i < result.docs.length; i++) {
|
||||||
...result,
|
const id = result.docs[i].parent
|
||||||
docs: docs.map((doc) => {
|
result.docs[i] = result.docs[i].version
|
||||||
doc = {
|
result.docs[i].id = id
|
||||||
_id: doc.parent,
|
|
||||||
id: doc.parent,
|
|
||||||
...doc.version,
|
|
||||||
}
|
|
||||||
|
|
||||||
return sanitizeInternalFields(doc)
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ import type { MongooseAdapter } from './index.js'
|
|||||||
|
|
||||||
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
||||||
import { getSession } from './utilities/getSession.js'
|
import { getSession } from './utilities/getSession.js'
|
||||||
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
import { transform } from './utilities/transform.js'
|
||||||
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'
|
|
||||||
|
|
||||||
export const updateGlobal: UpdateGlobal = async function updateGlobal(
|
export const updateGlobal: UpdateGlobal = async function updateGlobal(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
@@ -27,25 +26,11 @@ export const updateGlobal: UpdateGlobal = async function updateGlobal(
|
|||||||
session: await getSession(this, req),
|
session: await getSession(this, req),
|
||||||
}
|
}
|
||||||
|
|
||||||
let result
|
transform({ adapter: this, data, fields, globalSlug: slug, operation: 'write' })
|
||||||
|
|
||||||
const sanitizedData = sanitizeRelationshipIDs({
|
const result: any = await Model.findOneAndUpdate({ globalType: slug }, data, options)
|
||||||
config: this.payload.config,
|
|
||||||
data,
|
|
||||||
fields,
|
|
||||||
})
|
|
||||||
|
|
||||||
result = await Model.findOneAndUpdate({ globalType: slug }, sanitizedData, options)
|
transform({ adapter: this, data: result, fields, globalSlug: slug, operation: 'read' })
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
result = JSON.parse(JSON.stringify(result))
|
|
||||||
|
|
||||||
// custom id type reset
|
|
||||||
result.id = result._id
|
|
||||||
result = sanitizeInternalFields(result)
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import type { MongooseAdapter } from './index.js'
|
|||||||
import { buildQuery } from './queries/buildQuery.js'
|
import { buildQuery } from './queries/buildQuery.js'
|
||||||
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
||||||
import { getSession } from './utilities/getSession.js'
|
import { getSession } from './utilities/getSession.js'
|
||||||
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'
|
import { transform } from './utilities/transform.js'
|
||||||
|
|
||||||
export async function updateGlobalVersion<T extends TypeWithID>(
|
export async function updateGlobalVersion<T extends TypeWithID>(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
@@ -47,26 +47,15 @@ export async function updateGlobalVersion<T extends TypeWithID>(
|
|||||||
where: whereToUse,
|
where: whereToUse,
|
||||||
})
|
})
|
||||||
|
|
||||||
const sanitizedData = sanitizeRelationshipIDs({
|
transform({ adapter: this, data: versionData, fields, operation: 'write' })
|
||||||
config: this.payload.config,
|
|
||||||
data: versionData,
|
|
||||||
fields,
|
|
||||||
})
|
|
||||||
|
|
||||||
const doc = await VersionModel.findOneAndUpdate(query, sanitizedData, options)
|
const doc = await VersionModel.findOneAndUpdate(query, versionData, options)
|
||||||
|
|
||||||
if (!doc) {
|
if (!doc) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = JSON.parse(JSON.stringify(doc))
|
transform({ adapter: this, data: doc, fields, operation: 'read' })
|
||||||
|
|
||||||
const verificationToken = doc._verificationToken
|
return doc
|
||||||
|
|
||||||
// custom id type reset
|
|
||||||
result.id = result._id
|
|
||||||
if (verificationToken) {
|
|
||||||
result._verificationToken = verificationToken
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ import { buildQuery } from './queries/buildQuery.js'
|
|||||||
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
||||||
import { getSession } from './utilities/getSession.js'
|
import { getSession } from './utilities/getSession.js'
|
||||||
import { handleError } from './utilities/handleError.js'
|
import { handleError } from './utilities/handleError.js'
|
||||||
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
import { transform } from './utilities/transform.js'
|
||||||
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'
|
|
||||||
|
|
||||||
export const updateOne: UpdateOne = async function updateOne(
|
export const updateOne: UpdateOne = async function updateOne(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
@@ -39,14 +38,10 @@ export const updateOne: UpdateOne = async function updateOne(
|
|||||||
|
|
||||||
let result
|
let result
|
||||||
|
|
||||||
const sanitizedData = sanitizeRelationshipIDs({
|
transform({ adapter: this, data, fields, operation: 'write' })
|
||||||
config: this.payload.config,
|
|
||||||
data,
|
|
||||||
fields,
|
|
||||||
})
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
result = await Model.findOneAndUpdate(query, sanitizedData, options)
|
result = await Model.findOneAndUpdate(query, data, options)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError({ collection, error, req })
|
handleError({ collection, error, req })
|
||||||
}
|
}
|
||||||
@@ -55,9 +50,7 @@ export const updateOne: UpdateOne = async function updateOne(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
result = JSON.parse(JSON.stringify(result))
|
transform({ adapter: this, data: result, fields, operation: 'read' })
|
||||||
result.id = result._id
|
|
||||||
result = sanitizeInternalFields(result)
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import type { MongooseAdapter } from './index.js'
|
|||||||
import { buildQuery } from './queries/buildQuery.js'
|
import { buildQuery } from './queries/buildQuery.js'
|
||||||
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
||||||
import { getSession } from './utilities/getSession.js'
|
import { getSession } from './utilities/getSession.js'
|
||||||
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'
|
import { transform } from './utilities/transform.js'
|
||||||
|
|
||||||
export const updateVersion: UpdateVersion = async function updateVersion(
|
export const updateVersion: UpdateVersion = async function updateVersion(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
@@ -45,26 +45,15 @@ export const updateVersion: UpdateVersion = async function updateVersion(
|
|||||||
where: whereToUse,
|
where: whereToUse,
|
||||||
})
|
})
|
||||||
|
|
||||||
const sanitizedData = sanitizeRelationshipIDs({
|
transform({ adapter: this, data: versionData, fields, operation: 'write' })
|
||||||
config: this.payload.config,
|
|
||||||
data: versionData,
|
|
||||||
fields,
|
|
||||||
})
|
|
||||||
|
|
||||||
const doc = await VersionModel.findOneAndUpdate(query, sanitizedData, options)
|
const doc = await VersionModel.findOneAndUpdate(query, versionData, options)
|
||||||
|
|
||||||
if (!doc) {
|
if (!doc) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = JSON.parse(JSON.stringify(doc))
|
transform({ adapter: this, data: doc, fields, operation: 'write' })
|
||||||
|
|
||||||
const verificationToken = doc._verificationToken
|
return doc
|
||||||
|
|
||||||
// custom id type reset
|
|
||||||
result.id = result._id
|
|
||||||
if (verificationToken) {
|
|
||||||
result._verificationToken = verificationToken
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
const internalFields = ['__v']
|
|
||||||
|
|
||||||
export const sanitizeInternalFields = <T extends Record<string, unknown>>(incomingDoc: T): T =>
|
|
||||||
Object.entries(incomingDoc).reduce((newDoc, [key, val]): T => {
|
|
||||||
if (key === '_id') {
|
|
||||||
return {
|
|
||||||
...newDoc,
|
|
||||||
id: val,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (internalFields.indexOf(key) > -1) {
|
|
||||||
return newDoc
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...newDoc,
|
|
||||||
[key]: val,
|
|
||||||
}
|
|
||||||
}, {} as T)
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
import type { CollectionConfig, Field, SanitizedConfig, TraverseFieldsCallback } from 'payload'
|
|
||||||
|
|
||||||
import { Types } from 'mongoose'
|
|
||||||
import { traverseFields } from 'payload'
|
|
||||||
import { fieldAffectsData, fieldShouldBeLocalized } from 'payload/shared'
|
|
||||||
|
|
||||||
type Args = {
|
|
||||||
config: SanitizedConfig
|
|
||||||
data: Record<string, unknown>
|
|
||||||
fields: Field[]
|
|
||||||
parentIsLocalized?: 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 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,
|
|
||||||
parentIsLocalized,
|
|
||||||
}: 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 && fieldShouldBeLocalized({ field, parentIsLocalized })) {
|
|
||||||
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,
|
|
||||||
config,
|
|
||||||
fields,
|
|
||||||
fillEmpty: false,
|
|
||||||
parentIsLocalized,
|
|
||||||
ref: data,
|
|
||||||
})
|
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,8 @@ import { flattenAllFields, type Field, type SanitizedConfig } from 'payload'
|
|||||||
|
|
||||||
import { Types } from 'mongoose'
|
import { Types } from 'mongoose'
|
||||||
|
|
||||||
import { sanitizeRelationshipIDs } from './sanitizeRelationshipIDs.js'
|
import { transform } from './transform.js'
|
||||||
|
import type { MongooseAdapter } from '../index.js'
|
||||||
|
|
||||||
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(
|
||||||
@@ -297,7 +298,7 @@ const relsData = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('sanitizeRelationshipIDs', () => {
|
describe('transform', () => {
|
||||||
it('should sanitize relationships', () => {
|
it('should sanitize relationships', () => {
|
||||||
const data = {
|
const data = {
|
||||||
...relsData,
|
...relsData,
|
||||||
@@ -382,7 +383,18 @@ describe('sanitizeRelationshipIDs', () => {
|
|||||||
}
|
}
|
||||||
const flattenValuesBefore = Object.values(flattenRelationshipValues(data))
|
const flattenValuesBefore = Object.values(flattenRelationshipValues(data))
|
||||||
|
|
||||||
sanitizeRelationshipIDs({ config, data, fields: config.collections[0].fields })
|
const mockAdapter = {
|
||||||
|
payload: {
|
||||||
|
config,
|
||||||
|
},
|
||||||
|
} as MongooseAdapter
|
||||||
|
|
||||||
|
transform({
|
||||||
|
adapter: mockAdapter,
|
||||||
|
operation: 'write',
|
||||||
|
data,
|
||||||
|
fields: config.collections[0].fields,
|
||||||
|
})
|
||||||
const flattenValuesAfter = Object.values(flattenRelationshipValues(data))
|
const flattenValuesAfter = Object.values(flattenRelationshipValues(data))
|
||||||
|
|
||||||
flattenValuesAfter.forEach((value, i) => {
|
flattenValuesAfter.forEach((value, i) => {
|
||||||
347
packages/db-mongodb/src/utilities/transform.ts
Normal file
347
packages/db-mongodb/src/utilities/transform.ts
Normal file
@@ -0,0 +1,347 @@
|
|||||||
|
import type {
|
||||||
|
CollectionConfig,
|
||||||
|
DateField,
|
||||||
|
Field,
|
||||||
|
JoinField,
|
||||||
|
RelationshipField,
|
||||||
|
SanitizedConfig,
|
||||||
|
TraverseFieldsCallback,
|
||||||
|
UploadField,
|
||||||
|
} from 'payload'
|
||||||
|
|
||||||
|
import { Types } from 'mongoose'
|
||||||
|
import { traverseFields } from 'payload'
|
||||||
|
import { fieldAffectsData, fieldShouldBeLocalized } from 'payload/shared'
|
||||||
|
|
||||||
|
import type { MongooseAdapter } from '../index.js'
|
||||||
|
|
||||||
|
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()
|
||||||
|
} else if (Array.isArray(field.collection) && item) {
|
||||||
|
// Fields here for polymorphic joins cannot be determinted, JSON.parse needed
|
||||||
|
value.docs[i] = JSON.parse(JSON.stringify(value.docs[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sanitizeDate = ({
|
||||||
|
field,
|
||||||
|
locale,
|
||||||
|
ref,
|
||||||
|
value,
|
||||||
|
}: {
|
||||||
|
field: DateField
|
||||||
|
locale?: string
|
||||||
|
ref: Record<string, unknown>
|
||||||
|
value: unknown
|
||||||
|
}) => {
|
||||||
|
if (!value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value instanceof Date) {
|
||||||
|
value = value.toISOString()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (locale) {
|
||||||
|
ref[locale] = value
|
||||||
|
} else {
|
||||||
|
ref[field.name] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Args = {
|
||||||
|
/** instance of the adapter */
|
||||||
|
adapter: MongooseAdapter
|
||||||
|
/** data to transform, can be an array of documents or a single document */
|
||||||
|
data: Record<string, unknown> | Record<string, unknown>[]
|
||||||
|
/** fields accossiated with the data */
|
||||||
|
fields: Field[]
|
||||||
|
/** slug of the global, pass only when the operation is `write` */
|
||||||
|
globalSlug?: string
|
||||||
|
/**
|
||||||
|
* Type of the operation
|
||||||
|
* read - sanitizes ObjectIDs, Date to strings.
|
||||||
|
* write - sanitizes string relationships to ObjectIDs.
|
||||||
|
*/
|
||||||
|
operation: 'read' | 'write'
|
||||||
|
parentIsLocalized?: boolean
|
||||||
|
/**
|
||||||
|
* Throw errors on invalid relationships
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
validateRelationships?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const transform = ({
|
||||||
|
adapter,
|
||||||
|
data,
|
||||||
|
fields,
|
||||||
|
globalSlug,
|
||||||
|
operation,
|
||||||
|
parentIsLocalized,
|
||||||
|
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 === 'write' && globalSlug) {
|
||||||
|
data.globalType = globalSlug
|
||||||
|
}
|
||||||
|
|
||||||
|
const sanitize: TraverseFieldsCallback = ({ field, ref }) => {
|
||||||
|
if (!ref || typeof ref !== 'object') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field.type === 'date' && operation === 'read' && ref[field.name]) {
|
||||||
|
if (config.localization && fieldShouldBeLocalized({ field, parentIsLocalized })) {
|
||||||
|
const fieldRef = ref[field.name]
|
||||||
|
if (!fieldRef || typeof fieldRef !== 'object') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const locale of config.localization.localeCodes) {
|
||||||
|
sanitizeDate({
|
||||||
|
field,
|
||||||
|
ref: fieldRef,
|
||||||
|
value: fieldRef[locale],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sanitizeDate({
|
||||||
|
field,
|
||||||
|
ref: ref as Record<string, unknown>,
|
||||||
|
value: ref[field.name],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
field.type === 'relationship' ||
|
||||||
|
field.type === 'upload' ||
|
||||||
|
(operation === 'read' && field.type === 'join')
|
||||||
|
) {
|
||||||
|
if (!ref[field.name]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle localized relationships
|
||||||
|
if (config.localization && fieldShouldBeLocalized({ field, parentIsLocalized })) {
|
||||||
|
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,
|
||||||
|
config,
|
||||||
|
fields,
|
||||||
|
fillEmpty: false,
|
||||||
|
parentIsLocalized,
|
||||||
|
ref: data,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -175,7 +175,10 @@ describe('Joins Field', () => {
|
|||||||
collection: categoriesSlug,
|
collection: categoriesSlug,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(Object.keys(categoryWithPosts)).toStrictEqual(['id', 'group'])
|
expect(categoryWithPosts).toStrictEqual({
|
||||||
|
id: categoryWithPosts.id,
|
||||||
|
group: categoryWithPosts.group,
|
||||||
|
})
|
||||||
|
|
||||||
expect(categoryWithPosts.group.relatedPosts.docs).toHaveLength(10)
|
expect(categoryWithPosts.group.relatedPosts.docs).toHaveLength(10)
|
||||||
expect(categoryWithPosts.group.relatedPosts.docs[0]).toHaveProperty('id')
|
expect(categoryWithPosts.group.relatedPosts.docs[0]).toHaveProperty('id')
|
||||||
|
|||||||
@@ -1648,7 +1648,10 @@ describe('Select', () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(Object.keys(res)).toStrictEqual(['id', 'text'])
|
expect(res).toStrictEqual({
|
||||||
|
id: res.id,
|
||||||
|
text: res.text,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should apply select with updateByID', async () => {
|
it('should apply select with updateByID', async () => {
|
||||||
@@ -1661,7 +1664,10 @@ describe('Select', () => {
|
|||||||
select: { text: true },
|
select: { text: true },
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(Object.keys(res)).toStrictEqual(['id', 'text'])
|
expect(res).toStrictEqual({
|
||||||
|
id: res.id,
|
||||||
|
text: res.text,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should apply select with updateBulk', async () => {
|
it('should apply select with updateBulk', async () => {
|
||||||
@@ -1680,7 +1686,10 @@ describe('Select', () => {
|
|||||||
|
|
||||||
assert(res.docs[0])
|
assert(res.docs[0])
|
||||||
|
|
||||||
expect(Object.keys(res.docs[0])).toStrictEqual(['id', 'text'])
|
expect(res.docs[0]).toStrictEqual({
|
||||||
|
id: res.docs[0].id,
|
||||||
|
text: res.docs[0].text,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should apply select with deleteByID', async () => {
|
it('should apply select with deleteByID', async () => {
|
||||||
@@ -1692,7 +1701,10 @@ describe('Select', () => {
|
|||||||
select: { text: true },
|
select: { text: true },
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(Object.keys(res)).toStrictEqual(['id', 'text'])
|
expect(res).toStrictEqual({
|
||||||
|
id: res.id,
|
||||||
|
text: res.text,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should apply select with deleteBulk', async () => {
|
it('should apply select with deleteBulk', async () => {
|
||||||
@@ -1710,7 +1722,10 @@ describe('Select', () => {
|
|||||||
|
|
||||||
assert(res.docs[0])
|
assert(res.docs[0])
|
||||||
|
|
||||||
expect(Object.keys(res.docs[0])).toStrictEqual(['id', 'text'])
|
expect(res.docs[0]).toStrictEqual({
|
||||||
|
id: res.docs[0].id,
|
||||||
|
text: res.docs[0].text,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should apply select with duplicate', async () => {
|
it('should apply select with duplicate', async () => {
|
||||||
@@ -1722,7 +1737,10 @@ describe('Select', () => {
|
|||||||
select: { text: true },
|
select: { text: true },
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(Object.keys(res)).toStrictEqual(['id', 'text'])
|
expect(res).toStrictEqual({
|
||||||
|
id: res.id,
|
||||||
|
text: res.text,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user