chore(db-mongodb): tsconfig uses strict: true and noUncheckedIndexedAccess: true (#11444)

Migrates the `db-mongodb` package to use `strict: true` and
`noUncheckedIndexedAccess: true` TSConfig properties.
This greatly improves code quality and prevents some runtime errors or
gives better error messages.
This commit is contained in:
Sasha
2025-03-01 00:17:24 +02:00
committed by GitHub
parent f7f5651004
commit 79a7b4ad02
52 changed files with 1370 additions and 961 deletions

View File

@@ -53,6 +53,9 @@
},
"devDependencies": {
"@payloadcms/eslint-config": "workspace:*",
"@types/mongoose-aggregate-paginate-v2": "1.0.12",
"@types/prompts": "^2.4.5",
"@types/uuid": "10.0.0",
"mongodb": "6.12.0",
"mongodb-memory-server": "^10",
"payload": "workspace:*"

View File

@@ -70,9 +70,15 @@ export const connect: Connect = async function connect(
await this.migrate({ migrations: this.prodMigrations })
}
} catch (err) {
let msg = `Error: cannot connect to MongoDB.`
if (typeof err === 'object' && err && 'message' in err && typeof err.message === 'string') {
msg = `${msg} Details: ${err.message}`
}
this.payload.logger.error({
err,
msg: `Error: cannot connect to MongoDB. Details: ${err.message}`,
msg,
})
process.exit(1)
}

View File

@@ -6,13 +6,15 @@ import { flattenWhereToOperators } from 'payload'
import type { MongooseAdapter } from './index.js'
import { buildQuery } from './queries/buildQuery.js'
import { getCollection } from './utilities/getEntity.js'
import { getSession } from './utilities/getSession.js'
export const count: Count = async function count(
this: MongooseAdapter,
{ collection, locale, req, where },
{ collection: collectionSlug, locale, req, where = {} },
) {
const Model = this.collections[collection]
const { collectionConfig, Model } = getCollection({ adapter: this, collectionSlug })
const options: CountOptions = {
session: await getSession(this, req),
}
@@ -26,8 +28,8 @@ export const count: Count = async function count(
const query = await buildQuery({
adapter: this,
collectionSlug: collection,
fields: this.payload.collections[collection].config.flattenedFields,
collectionSlug,
fields: collectionConfig.flattenedFields,
locale,
where,
})

View File

@@ -6,13 +6,15 @@ import { buildVersionGlobalFields, flattenWhereToOperators } from 'payload'
import type { MongooseAdapter } from './index.js'
import { buildQuery } from './queries/buildQuery.js'
import { getGlobal } from './utilities/getEntity.js'
import { getSession } from './utilities/getSession.js'
export const countGlobalVersions: CountGlobalVersions = async function countGlobalVersions(
this: MongooseAdapter,
{ global, locale, req, where },
{ global: globalSlug, locale, req, where = {} },
) {
const Model = this.versions[global]
const { globalConfig, Model } = getGlobal({ adapter: this, globalSlug, versions: true })
const options: CountOptions = {
session: await getSession(this, req),
}
@@ -26,11 +28,7 @@ export const countGlobalVersions: CountGlobalVersions = async function countGlob
const query = await buildQuery({
adapter: this,
fields: buildVersionGlobalFields(
this.payload.config,
this.payload.globals.config.find((each) => each.slug === global),
true,
),
fields: buildVersionGlobalFields(this.payload.config, globalConfig, true),
locale,
where,
})

View File

@@ -6,13 +6,19 @@ import { buildVersionCollectionFields, flattenWhereToOperators } from 'payload'
import type { MongooseAdapter } from './index.js'
import { buildQuery } from './queries/buildQuery.js'
import { getCollection } from './utilities/getEntity.js'
import { getSession } from './utilities/getSession.js'
export const countVersions: CountVersions = async function countVersions(
this: MongooseAdapter,
{ collection, locale, req, where },
{ collection: collectionSlug, locale, req, where = {} },
) {
const Model = this.versions[collection]
const { collectionConfig, Model } = getCollection({
adapter: this,
collectionSlug,
versions: true,
})
const options: CountOptions = {
session: await getSession(this, req),
}
@@ -26,11 +32,7 @@ export const countVersions: CountVersions = async function countVersions(
const query = await buildQuery({
adapter: this,
fields: buildVersionCollectionFields(
this.payload.config,
this.payload.collections[collection].config,
true,
),
fields: buildVersionCollectionFields(this.payload.config, collectionConfig, true),
locale,
where,
})

View File

@@ -3,15 +3,17 @@ import type { Create } from 'payload'
import type { MongooseAdapter } from './index.js'
import { getCollection } from './utilities/getEntity.js'
import { getSession } from './utilities/getSession.js'
import { handleError } from './utilities/handleError.js'
import { transform } from './utilities/transform.js'
export const create: Create = async function create(
this: MongooseAdapter,
{ collection, data, req, returning },
{ collection: collectionSlug, data, req, returning },
) {
const Model = this.collections[collection]
const { collectionConfig, customIDType, Model } = getCollection({ adapter: this, collectionSlug })
const options: CreateOptions = {
session: await getSession(this, req),
}
@@ -21,18 +23,18 @@ export const create: Create = async function create(
transform({
adapter: this,
data,
fields: this.payload.collections[collection].config.fields,
fields: collectionConfig.fields,
operation: 'write',
})
if (this.payload.collections[collection].customIDType) {
if (customIDType) {
data._id = data.id
}
try {
;[doc] = await Model.create([data], options)
} catch (error) {
handleError({ collection, error, req })
handleError({ collection: collectionSlug, error, req })
}
if (returning === false) {
return null
@@ -43,7 +45,7 @@ export const create: Create = async function create(
transform({
adapter: this,
data: doc,
fields: this.payload.collections[collection].config.fields,
fields: collectionConfig.fields,
operation: 'read',
})

View File

@@ -1,22 +1,24 @@
import type { CreateOptions } from 'mongoose'
import type { CreateGlobal } from 'payload'
import { type CreateGlobal } from 'payload'
import type { MongooseAdapter } from './index.js'
import { getGlobal } from './utilities/getEntity.js'
import { getSession } from './utilities/getSession.js'
import { transform } from './utilities/transform.js'
export const createGlobal: CreateGlobal = async function createGlobal(
this: MongooseAdapter,
{ slug, data, req, returning },
{ slug: globalSlug, data, req, returning },
) {
const Model = this.globals
const { globalConfig, Model } = getGlobal({ adapter: this, globalSlug })
transform({
adapter: this,
data,
fields: this.payload.config.globals.find((globalConfig) => globalConfig.slug === slug).fields,
globalSlug: slug,
fields: globalConfig.fields,
globalSlug,
operation: 'write',
})
@@ -34,7 +36,7 @@ export const createGlobal: CreateGlobal = async function createGlobal(
transform({
adapter: this,
data: result,
fields: this.payload.config.globals.find((globalConfig) => globalConfig.slug === slug).fields,
fields: globalConfig.fields,
operation: 'read',
})

View File

@@ -1,9 +1,8 @@
import type { CreateOptions } from 'mongoose'
import { buildVersionGlobalFields, type CreateGlobalVersion } from 'payload'
import type { MongooseAdapter } from './index.js'
import { getGlobal } from './utilities/getEntity.js'
import { getSession } from './utilities/getSession.js'
import { transform } from './utilities/transform.js'
@@ -22,8 +21,9 @@ export const createGlobalVersion: CreateGlobalVersion = async function createGlo
versionData,
},
) {
const VersionModel = this.versions[globalSlug]
const options: CreateOptions = {
const { globalConfig, Model } = getGlobal({ adapter: this, globalSlug, versions: true })
const options = {
session: await getSession(this, req),
}
@@ -38,10 +38,7 @@ export const createGlobalVersion: CreateGlobalVersion = async function createGlo
version: versionData,
}
const fields = buildVersionGlobalFields(
this.payload.config,
this.payload.config.globals.find((global) => global.slug === globalSlug),
)
const fields = buildVersionGlobalFields(this.payload.config, globalConfig)
transform({
adapter: this,
@@ -50,9 +47,9 @@ export const createGlobalVersion: CreateGlobalVersion = async function createGlo
operation: 'write',
})
let [doc] = await VersionModel.create([data], options, req)
let [doc] = await Model.create([data], options, req)
await VersionModel.updateMany(
await Model.updateMany(
{
$and: [
{

View File

@@ -42,8 +42,9 @@ export const createMigration: CreateMigration = async function createMigration({
const migrationFileContent = migrationTemplate(predefinedMigration)
const [yyymmdd, hhmmss] = new Date().toISOString().split('T')
const formattedDate = yyymmdd.replace(/\D/g, '')
const formattedTime = hhmmss.split('.')[0].replace(/\D/g, '')
const formattedDate = yyymmdd!.replace(/\D/g, '')
const formattedTime = hhmmss!.split('.')[0]!.replace(/\D/g, '')
const timestamp = `${formattedDate}_${formattedTime}`

View File

@@ -1,9 +1,8 @@
import type { CreateOptions } from 'mongoose'
import { buildVersionCollectionFields, type CreateVersion } from 'payload'
import type { MongooseAdapter } from './index.js'
import { getCollection } from './utilities/getEntity.js'
import { getSession } from './utilities/getSession.js'
import { transform } from './utilities/transform.js'
@@ -22,8 +21,13 @@ export const createVersion: CreateVersion = async function createVersion(
versionData,
},
) {
const VersionModel = this.versions[collectionSlug]
const options: CreateOptions = {
const { collectionConfig, Model } = getCollection({
adapter: this,
collectionSlug,
versions: true,
})
const options = {
session: await getSession(this, req),
}
@@ -38,10 +42,7 @@ export const createVersion: CreateVersion = async function createVersion(
version: versionData,
}
const fields = buildVersionCollectionFields(
this.payload.config,
this.payload.collections[collectionSlug].config,
)
const fields = buildVersionCollectionFields(this.payload.config, collectionConfig)
transform({
adapter: this,
@@ -50,7 +51,7 @@ export const createVersion: CreateVersion = async function createVersion(
operation: 'write',
})
let [doc] = await VersionModel.create([data], options, req)
let [doc] = await Model.create([data], options, req)
const parentQuery = {
$or: [
@@ -62,7 +63,7 @@ export const createVersion: CreateVersion = async function createVersion(
],
}
await VersionModel.updateMany(
await Model.updateMany(
{
$and: [
{

View File

@@ -1,24 +1,27 @@
import type { DeleteOptions } from 'mongodb'
import type { DeleteMany } from 'payload'
import { type DeleteMany } from 'payload'
import type { MongooseAdapter } from './index.js'
import { buildQuery } from './queries/buildQuery.js'
import { getCollection } from './utilities/getEntity.js'
import { getSession } from './utilities/getSession.js'
export const deleteMany: DeleteMany = async function deleteMany(
this: MongooseAdapter,
{ collection, req, where },
{ collection: collectionSlug, req, where },
) {
const Model = this.collections[collection]
const { collectionConfig, Model } = getCollection({ adapter: this, collectionSlug })
const options: DeleteOptions = {
session: await getSession(this, req),
}
const query = await buildQuery({
adapter: this,
collectionSlug: collection,
fields: this.payload.collections[collection].config.flattenedFields,
collectionSlug,
fields: collectionConfig.flattenedFields,
where,
})

View File

@@ -5,18 +5,20 @@ import type { MongooseAdapter } from './index.js'
import { buildQuery } from './queries/buildQuery.js'
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
import { getCollection } from './utilities/getEntity.js'
import { getSession } from './utilities/getSession.js'
import { transform } from './utilities/transform.js'
export const deleteOne: DeleteOne = async function deleteOne(
this: MongooseAdapter,
{ collection, req, returning, select, where },
{ collection: collectionSlug, req, returning, select, where },
) {
const Model = this.collections[collection]
const { collectionConfig, Model } = getCollection({ adapter: this, collectionSlug })
const options: MongooseUpdateQueryOptions = {
projection: buildProjectionFromSelect({
adapter: this,
fields: this.payload.collections[collection].config.flattenedFields,
fields: collectionConfig.flattenedFields,
select,
}),
session: await getSession(this, req),
@@ -24,8 +26,8 @@ export const deleteOne: DeleteOne = async function deleteOne(
const query = await buildQuery({
adapter: this,
collectionSlug: collection,
fields: this.payload.collections[collection].config.flattenedFields,
collectionSlug,
fields: collectionConfig.flattenedFields,
where,
})
@@ -43,7 +45,7 @@ export const deleteOne: DeleteOne = async function deleteOne(
transform({
adapter: this,
data: doc,
fields: this.payload.collections[collection].config.fields,
fields: collectionConfig.fields,
operation: 'read',
})

View File

@@ -3,26 +3,27 @@ import { buildVersionCollectionFields, type DeleteVersions } from 'payload'
import type { MongooseAdapter } from './index.js'
import { buildQuery } from './queries/buildQuery.js'
import { getCollection } from './utilities/getEntity.js'
import { getSession } from './utilities/getSession.js'
export const deleteVersions: DeleteVersions = async function deleteVersions(
this: MongooseAdapter,
{ collection, locale, req, where },
{ collection: collectionSlug, locale, req, where },
) {
const VersionsModel = this.versions[collection]
const { collectionConfig, Model } = getCollection({
adapter: this,
collectionSlug,
versions: true,
})
const session = await getSession(this, req)
const query = await buildQuery({
adapter: this,
fields: buildVersionCollectionFields(
this.payload.config,
this.payload.collections[collection].config,
true,
),
fields: buildVersionCollectionFields(this.payload.config, collectionConfig, true),
locale,
where,
})
await VersionsModel.deleteMany(query, { session })
await Model.deleteMany(query, { session })
}

View File

@@ -10,13 +10,14 @@ import { buildSortParam } from './queries/buildSortParam.js'
import { aggregatePaginate } from './utilities/aggregatePaginate.js'
import { buildJoinAggregation } from './utilities/buildJoinAggregation.js'
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
import { getCollection } from './utilities/getEntity.js'
import { getSession } from './utilities/getSession.js'
import { transform } from './utilities/transform.js'
export const find: Find = async function find(
this: MongooseAdapter,
{
collection,
collection: collectionSlug,
joins = {},
limit = 0,
locale,
@@ -26,11 +27,10 @@ export const find: Find = async function find(
req,
select,
sort: sortArg,
where,
where = {},
},
) {
const Model = this.collections[collection]
const collectionConfig = this.payload.collections[collection].config
const { collectionConfig, Model } = getCollection({ adapter: this, collectionSlug })
const session = await getSession(this, req)
@@ -54,8 +54,8 @@ export const find: Find = async function find(
const query = await buildQuery({
adapter: this,
collectionSlug: collection,
fields: this.payload.collections[collection].config.flattenedFields,
collectionSlug,
fields: collectionConfig.flattenedFields,
locale,
where,
})
@@ -109,7 +109,8 @@ export const find: Find = async function find(
if (limit >= 0) {
paginationOptions.limit = limit
// limit must also be set here, it's ignored when pagination is false
paginationOptions.options.limit = limit
paginationOptions.options!.limit = limit
// Disable pagination if limit is 0
if (limit === 0) {
@@ -121,7 +122,7 @@ export const find: Find = async function find(
const aggregate = await buildJoinAggregation({
adapter: this,
collection,
collection: collectionSlug,
collectionConfig,
joins,
locale,
@@ -139,7 +140,7 @@ export const find: Find = async function find(
pagination: paginationOptions.pagination,
projection: paginationOptions.projection,
query,
session: paginationOptions.options?.session,
session: paginationOptions.options?.session ?? undefined,
sort: paginationOptions.sort as object,
useEstimatedCount: paginationOptions.useEstimatedCount,
})
@@ -150,7 +151,7 @@ export const find: Find = async function find(
transform({
adapter: this,
data: result.docs,
fields: this.payload.collections[collection].config.fields,
fields: collectionConfig.fields,
operation: 'read',
})

View File

@@ -7,15 +7,16 @@ import type { MongooseAdapter } from './index.js'
import { buildQuery } from './queries/buildQuery.js'
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
import { getGlobal } from './utilities/getEntity.js'
import { getSession } from './utilities/getSession.js'
import { transform } from './utilities/transform.js'
export const findGlobal: FindGlobal = async function findGlobal(
this: MongooseAdapter,
{ slug, locale, req, select, where },
{ slug: globalSlug, locale, req, select, where = {} },
) {
const Model = this.globals
const globalConfig = this.payload.globals.config.find((each) => each.slug === slug)
const { globalConfig, Model } = getGlobal({ adapter: this, globalSlug })
const fields = globalConfig.flattenedFields
const options: QueryOptions = {
lean: true,
@@ -30,12 +31,12 @@ export const findGlobal: FindGlobal = async function findGlobal(
const query = await buildQuery({
adapter: this,
fields,
globalSlug: slug,
globalSlug,
locale,
where: combineQueries({ globalType: { equals: slug } }, where),
where: combineQueries({ globalType: { equals: globalSlug } }, where),
})
const doc = (await Model.findOne(query, {}, options)) as any
const doc: any = await Model.findOne(query, {}, options)
if (!doc) {
return null

View File

@@ -1,22 +1,34 @@
import type { PaginateOptions, QueryOptions } from 'mongoose'
import type { FindGlobalVersions } from 'payload'
import { buildVersionGlobalFields, flattenWhereToOperators } from 'payload'
import { APIError, buildVersionGlobalFields, flattenWhereToOperators } from 'payload'
import type { MongooseAdapter } from './index.js'
import { buildQuery } from './queries/buildQuery.js'
import { buildSortParam } from './queries/buildSortParam.js'
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
import { getGlobal } from './utilities/getEntity.js'
import { getSession } from './utilities/getSession.js'
import { transform } from './utilities/transform.js'
export const findGlobalVersions: FindGlobalVersions = async function findGlobalVersions(
this: MongooseAdapter,
{ global, limit, locale, page, pagination, req, select, skip, sort: sortArg, where },
{
global: globalSlug,
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 { globalConfig, Model } = getGlobal({ adapter: this, globalSlug, versions: true })
const versionFields = buildVersionGlobalFields(this.payload.config, globalConfig, true)
const session = await getSession(this, req)
@@ -88,10 +100,11 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV
}
}
if (limit >= 0) {
if (limit && limit >= 0) {
paginationOptions.limit = limit
// limit must also be set here, it's ignored when pagination is false
paginationOptions.options.limit = limit
paginationOptions.options!.limit = limit
// Disable pagination if limit is 0
if (limit === 0) {

View File

@@ -1,5 +1,6 @@
import type { AggregateOptions, QueryOptions } from 'mongoose'
import type { FindOne } from 'payload'
import { type FindOne } from 'payload'
import type { MongooseAdapter } from './index.js'
@@ -7,15 +8,16 @@ import { buildQuery } from './queries/buildQuery.js'
import { aggregatePaginate } from './utilities/aggregatePaginate.js'
import { buildJoinAggregation } from './utilities/buildJoinAggregation.js'
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
import { getCollection } from './utilities/getEntity.js'
import { getSession } from './utilities/getSession.js'
import { transform } from './utilities/transform.js'
export const findOne: FindOne = async function findOne(
this: MongooseAdapter,
{ collection, joins, locale, req, select, where },
{ collection: collectionSlug, joins, locale, req, select, where = {} },
) {
const Model = this.collections[collection]
const collectionConfig = this.payload.collections[collection].config
const { collectionConfig, Model } = getCollection({ adapter: this, collectionSlug })
const session = await getSession(this, req)
const options: AggregateOptions & QueryOptions = {
lean: true,
@@ -24,7 +26,7 @@ export const findOne: FindOne = async function findOne(
const query = await buildQuery({
adapter: this,
collectionSlug: collection,
collectionSlug,
fields: collectionConfig.flattenedFields,
locale,
where,
@@ -38,7 +40,7 @@ export const findOne: FindOne = async function findOne(
const aggregate = await buildJoinAggregation({
adapter: this,
collection,
collection: collectionSlug,
collectionConfig,
joins,
locale,

View File

@@ -8,15 +8,31 @@ import type { MongooseAdapter } from './index.js'
import { buildQuery } from './queries/buildQuery.js'
import { buildSortParam } from './queries/buildSortParam.js'
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
import { getCollection } from './utilities/getEntity.js'
import { getSession } from './utilities/getSession.js'
import { transform } from './utilities/transform.js'
export const findVersions: FindVersions = async function findVersions(
this: MongooseAdapter,
{ collection, limit, locale, page, pagination, req = {}, select, skip, sort: sortArg, where },
{
collection: collectionSlug,
limit,
locale,
page,
pagination,
req = {},
select,
skip,
sort: sortArg,
where = {},
},
) {
const Model = this.versions[collection]
const collectionConfig = this.payload.collections[collection].config
const { collectionConfig, Model } = getCollection({
adapter: this,
collectionSlug,
versions: true,
})
const session = await getSession(this, req)
const options: QueryOptions = {
limit,
@@ -92,10 +108,11 @@ export const findVersions: FindVersions = async function findVersions(
}
}
if (limit >= 0) {
if (limit && limit >= 0) {
paginationOptions.limit = limit
// limit must also be set here, it's ignored when pagination is false
paginationOptions.options.limit = limit
paginationOptions.options!.limit = limit
// Disable pagination if limit is 0
if (limit === 0) {

View File

@@ -11,6 +11,7 @@ import type {
BaseDatabaseAdapter,
CollectionSlug,
DatabaseAdapterObj,
Migration,
Payload,
TypeWithID,
TypeWithVersion,
@@ -110,11 +111,7 @@ export interface Args {
* typed as any to avoid dependency
*/
mongoMemoryServer?: MongoMemoryReplSet
prodMigrations?: {
down: (args: MigrateDownArgs) => Promise<void>
name: string
up: (args: MigrateUpArgs) => Promise<void>
}[]
prodMigrations?: Migration[]
transactionOptions?: false | TransactionOptions
/** The URL to connect to MongoDB or false to start payload and prevent connecting */
@@ -181,7 +178,7 @@ export function mongooseAdapter({
collectionsSchemaOptions = {},
connectOptions,
disableIndexHints = false,
ensureIndexes,
ensureIndexes = false,
migrationDir: migrationDirArg,
mongoMemoryServer,
prodMigrations,
@@ -198,11 +195,14 @@ export function mongooseAdapter({
// Mongoose-specific
autoPluralization,
collections: {},
// @ts-expect-error initialize without a connection
connection: undefined,
connectOptions: connectOptions || {},
disableIndexHints,
ensureIndexes,
// @ts-expect-error don't have globals model yet
globals: undefined,
// @ts-expect-error Should not be required
mongoMemoryServer,
sessions: {},
transactionOptions: transactionOptions === false ? undefined : transactionOptions,

View File

@@ -6,7 +6,7 @@ import paginate from 'mongoose-paginate-v2'
import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload'
import type { MongooseAdapter } from './index.js'
import type { CollectionModel } from './types.js'
import type { CollectionModel, GlobalModel } from './types.js'
import { buildCollectionSchema } from './models/buildCollectionSchema.js'
import { buildGlobalModel } from './models/buildGlobalModel.js'
@@ -16,7 +16,7 @@ import { getDBName } from './utilities/getDBName.js'
export const init: Init = function init(this: MongooseAdapter) {
this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => {
const schemaOptions = this.collectionsSchemaOptions[collection.slug]
const schemaOptions = this.collectionsSchemaOptions?.[collection.slug]
const schema = buildCollectionSchema(collection, this.payload, schemaOptions)
@@ -68,7 +68,7 @@ export const init: Init = function init(this: MongooseAdapter) {
) as CollectionModel
})
this.globals = buildGlobalModel(this.payload)
this.globals = buildGlobalModel(this.payload) as GlobalModel
this.payload.config.globals.forEach((global) => {
if (global.versions) {

View File

@@ -1,5 +1,3 @@
import type { PayloadRequest } from 'payload'
import { commitTransaction, initTransaction, killTransaction, readMigrationFiles } from 'payload'
import prompts from 'prompts'

View File

@@ -28,7 +28,7 @@ export const buildCollectionSchema = (
})
if (Array.isArray(collection.upload.filenameCompoundIndex)) {
const indexDefinition: Record<string, 1> = collection.upload.filenameCompoundIndex.reduce(
const indexDefinition = collection.upload.filenameCompoundIndex.reduce<Record<string, 1>>(
(acc, index) => {
acc[index] = 1
return acc

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +1,16 @@
import type { ClientSession, Model } from 'mongoose'
import type { Field, PayloadRequest, SanitizedConfig } from 'payload'
import type { Field, PayloadRequest } from 'payload'
import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload'
import type { MongooseAdapter } from '../index.js'
import { getCollection, getGlobal } from '../utilities/getEntity.js'
import { getSession } from '../utilities/getSession.js'
import { transform } from '../utilities/transform.js'
const migrateModelWithBatching = async ({
batchSize,
config,
db,
fields,
Model,
@@ -18,12 +18,11 @@ const migrateModelWithBatching = async ({
session,
}: {
batchSize: number
config: SanitizedConfig
db: MongooseAdapter
fields: Field[]
Model: Model<any>
parentIsLocalized: boolean
session: ClientSession
session?: ClientSession
}): Promise<void> => {
let hasNext = true
let skip = 0
@@ -55,6 +54,7 @@ const migrateModelWithBatching = async ({
}
await Model.collection.bulkWrite(
// @ts-expect-error bulkWrite has a weird type, insertOne, updateMany etc are required here as well.
docs.map((doc) => ({
updateOne: {
filter: { _id: doc._id },
@@ -123,12 +123,13 @@ export async function migrateRelationshipsV2_V3({
if (hasRelationshipOrUploadField(collection)) {
payload.logger.info(`Migrating collection "${collection.slug}"`)
const { Model } = getCollection({ adapter: db, collectionSlug: collection.slug })
await migrateModelWithBatching({
batchSize,
config,
db,
fields: collection.fields,
Model: db.collections[collection.slug],
Model,
parentIsLocalized: false,
session,
})
@@ -139,12 +140,17 @@ export async function migrateRelationshipsV2_V3({
if (collection.versions) {
payload.logger.info(`Migrating collection versions "${collection.slug}"`)
const { Model } = getCollection({
adapter: db,
collectionSlug: collection.slug,
versions: true,
})
await migrateModelWithBatching({
batchSize,
config,
db,
fields: buildVersionCollectionFields(config, collection),
Model: db.versions[collection.slug],
Model,
parentIsLocalized: false,
session,
})
@@ -193,12 +199,13 @@ export async function migrateRelationshipsV2_V3({
if (global.versions) {
payload.logger.info(`Migrating global versions "${global.slug}"`)
const { Model } = getGlobal({ adapter: db, globalSlug: global.slug, versions: true })
await migrateModelWithBatching({
batchSize,
config,
db,
fields: buildVersionGlobalFields(config, global),
Model: db.versions[global.slug],
Model,
parentIsLocalized: false,
session,
})

View File

@@ -3,18 +3,20 @@ import type { Payload, PayloadRequest } from 'payload'
import type { MongooseAdapter } from '../index.js'
import { getCollection, getGlobal } from '../utilities/getEntity.js'
import { getSession } from '../utilities/getSession.js'
export async function migrateVersionsV1_V2({ req }: { req: PayloadRequest }) {
const { payload } = req
const session = await getSession(payload.db as MongooseAdapter, req)
const adapter = payload.db as MongooseAdapter
const session = await getSession(adapter, req)
// For each collection
for (const { slug, versions } of payload.config.collections) {
if (versions?.drafts) {
await migrateCollectionDocs({ slug, payload, session })
await migrateCollectionDocs({ slug, adapter, payload, session })
payload.logger.info(`Migrated the "${slug}" collection.`)
}
@@ -23,9 +25,13 @@ export async function migrateVersionsV1_V2({ req }: { req: PayloadRequest }) {
// For each global
for (const { slug, versions } of payload.config.globals) {
if (versions) {
const VersionsModel = payload.db.versions[slug]
const { Model } = getGlobal({
adapter,
globalSlug: slug,
versions: true,
})
await VersionsModel.findOneAndUpdate(
await Model.findOneAndUpdate(
{},
{ latest: true },
{
@@ -41,17 +47,23 @@ export async function migrateVersionsV1_V2({ req }: { req: PayloadRequest }) {
async function migrateCollectionDocs({
slug,
adapter,
docsAtATime = 100,
payload,
session,
}: {
adapter: MongooseAdapter
docsAtATime?: number
payload: Payload
session: ClientSession
session?: ClientSession
slug: string
}) {
const VersionsModel = payload.db.versions[slug]
const remainingDocs = await VersionsModel.aggregate(
const { Model } = getCollection({
adapter,
collectionSlug: slug,
versions: true,
})
const remainingDocs = await Model.aggregate(
[
// Sort so that newest are first
{
@@ -87,7 +99,7 @@ async function migrateCollectionDocs({
).exec()
if (!remainingDocs || remainingDocs.length === 0) {
const newVersions = await VersionsModel.find(
const newVersions = await Model.find(
{
latest: {
$eq: true,
@@ -108,7 +120,7 @@ async function migrateCollectionDocs({
const remainingDocIDs = remainingDocs.map((doc) => doc._versionID)
await VersionsModel.updateMany(
await Model.updateMany(
{
_id: {
$in: remainingDocIDs,
@@ -122,5 +134,5 @@ async function migrateCollectionDocs({
},
)
await migrateCollectionDocs({ slug, payload, session })
await migrateCollectionDocs({ slug, adapter, payload, session })
}

View File

@@ -1,11 +1,14 @@
import type { FilterQuery } from 'mongoose'
import type { FlattenedField, Operator, PathToQuery, Payload } from 'payload'
import { Types } from 'mongoose'
import { getLocalizedPaths } from 'payload'
import { APIError, getLocalizedPaths } from 'payload'
import { validOperatorSet } from 'payload/shared'
import type { MongooseAdapter } from '../index.js'
import type { OperatorMapKey } from './operatorMap.js'
import { getCollection } from '../utilities/getEntity.js'
import { operatorMap } from './operatorMap.js'
import { sanitizeQueryValue } from './sanitizeQueryValue.js'
@@ -43,7 +46,7 @@ export async function buildSearchParam({
parentIsLocalized: boolean
payload: Payload
val: unknown
}): Promise<SearchParam> {
}): Promise<SearchParam | undefined> {
// Replace GraphQL nested field double underscore formatting
let sanitizedPath = incomingPath.replace(/__/g, '.')
if (sanitizedPath === 'id') {
@@ -55,7 +58,9 @@ export async function buildSearchParam({
let hasCustomID = false
if (sanitizedPath === '_id') {
const customIDFieldType = payload.collections[collectionSlug]?.customIDType
const customIDFieldType = collectionSlug
? payload.collections[collectionSlug]?.customIDType
: undefined
let idFieldType: 'number' | 'text' = 'text'
@@ -71,7 +76,7 @@ export async function buildSearchParam({
name: 'id',
type: idFieldType,
} as FlattenedField,
parentIsLocalized,
parentIsLocalized: parentIsLocalized ?? false,
path: '_id',
})
} else {
@@ -86,6 +91,10 @@ export async function buildSearchParam({
})
}
if (!paths[0]) {
return undefined
}
const [{ field, path }] = paths
if (path) {
const sanitizedQueryValue = sanitizeQueryValue({
@@ -109,6 +118,10 @@ export async function buildSearchParam({
return { value: rawQuery }
}
if (!formattedOperator) {
return undefined
}
// If there are multiple collections to search through,
// Recursively build up a list of query constraints
if (paths.length > 1) {
@@ -116,84 +129,86 @@ export async function buildSearchParam({
// to work backwards from top
const pathsToQuery = paths.slice(1).reverse()
const initialRelationshipQuery = {
let relationshipQuery: SearchParam = {
value: {},
} as SearchParam
}
const relationshipQuery = await pathsToQuery.reduce(
async (priorQuery, { collectionSlug: slug, path: subPath }, i) => {
const priorQueryResult = await priorQuery
for (const [i, { collectionSlug, path: subPath }] of pathsToQuery.entries()) {
if (!collectionSlug) {
throw new APIError(`Collection with the slug ${collectionSlug} was not found.`)
}
const SubModel = (payload.db as MongooseAdapter).collections[slug]
const { Model: SubModel } = getCollection({
adapter: payload.db as MongooseAdapter,
collectionSlug,
})
// On the "deepest" collection,
// Search on the value passed through the query
if (i === 0) {
const subQuery = await SubModel.buildQuery({
locale,
payload,
where: {
[subPath]: {
[formattedOperator]: val,
},
if (i === 0) {
const subQuery = await SubModel.buildQuery({
locale,
payload,
where: {
[subPath]: {
[formattedOperator]: val,
},
})
},
})
const result = await SubModel.find(subQuery, subQueryOptions)
const $in: unknown[] = []
result.forEach((doc) => {
const stringID = doc._id.toString()
$in.push(stringID)
if (Types.ObjectId.isValid(stringID)) {
$in.push(doc._id)
}
})
if (pathsToQuery.length === 1) {
return {
path,
value: { $in },
}
}
const nextSubPath = pathsToQuery[i + 1].path
return {
value: { [nextSubPath]: { $in } },
}
}
const subQuery = priorQueryResult.value
const result = await SubModel.find(subQuery, subQueryOptions)
const $in = result.map((doc) => doc._id)
const $in: unknown[] = []
// If it is the last recursion
// then pass through the search param
if (i + 1 === pathsToQuery.length) {
result.forEach((doc) => {
const stringID = doc._id.toString()
$in.push(stringID)
if (Types.ObjectId.isValid(stringID)) {
$in.push(doc._id)
}
})
if (pathsToQuery.length === 1) {
return {
path,
value: { $in },
}
}
return {
const nextSubPath = pathsToQuery[i + 1]?.path
if (nextSubPath) {
relationshipQuery = { value: { [nextSubPath]: $in } }
}
continue
}
const subQuery = relationshipQuery.value as FilterQuery<any>
const result = await SubModel.find(subQuery, subQueryOptions)
const $in = result.map((doc) => doc._id)
// If it is the last recursion
// then pass through the search param
if (i + 1 === pathsToQuery.length) {
relationshipQuery = {
path,
value: { $in },
}
} else {
relationshipQuery = {
value: {
_id: { $in },
},
}
},
Promise.resolve(initialRelationshipQuery),
)
}
}
return relationshipQuery
}
if (formattedOperator && validOperatorSet.has(formattedOperator as Operator)) {
const operatorKey = operatorMap[formattedOperator]
const operatorKey = operatorMap[formattedOperator as OperatorMapKey]
if (field.type === 'relationship' || field.type === 'upload') {
let hasNumberIDRelation
@@ -210,7 +225,7 @@ export async function buildSearchParam({
if (typeof formattedValue === 'string') {
if (Types.ObjectId.isValid(formattedValue)) {
result.value[multiIDCondition].push({
result.value[multiIDCondition]?.push({
[path]: { [operatorKey]: new Types.ObjectId(formattedValue) },
})
} else {
@@ -226,14 +241,16 @@ export async function buildSearchParam({
)
if (hasNumberIDRelation) {
result.value[multiIDCondition].push({
result.value[multiIDCondition]?.push({
[path]: { [operatorKey]: parseFloat(formattedValue) },
})
}
}
}
if (result.value[multiIDCondition].length > 1) {
const length = result.value[multiIDCondition]?.length
if (typeof length === 'number' && length > 1) {
return result
}
}

View File

@@ -1,4 +1,3 @@
import type { PaginateOptions } from 'mongoose'
import type { FlattenedField, SanitizedConfig, Sort } from 'payload'
import { getLocalizedSortProperty } from './getLocalizedSortProperty.js'
@@ -6,7 +5,7 @@ import { getLocalizedSortProperty } from './getLocalizedSortProperty.js'
type Args = {
config: SanitizedConfig
fields: FlattenedField[]
locale: string
locale?: string
parentIsLocalized?: boolean
sort: Sort
timestamps: boolean
@@ -23,10 +22,10 @@ export const buildSortParam = ({
config,
fields,
locale,
parentIsLocalized,
parentIsLocalized = false,
sort,
timestamps,
}: Args): PaginateOptions['sort'] => {
}: Args): Record<string, string> => {
if (!sort) {
if (timestamps) {
sort = '-createdAt'
@@ -39,7 +38,7 @@ export const buildSortParam = ({
sort = [sort]
}
const sorting = sort.reduce<PaginateOptions['sort']>((acc, item) => {
const sorting = sort.reduce<Record<string, string>>((acc, item) => {
let sortProperty: string
let sortDirection: SortDirection
if (item.indexOf('-') === 0) {

View File

@@ -1,6 +1,6 @@
import type { FlattenedField, Payload, Where } from 'payload'
import { QueryError } from 'payload'
import { APIError } from 'payload'
import { parseParams } from './parseParams.js'
@@ -23,7 +23,7 @@ export const getBuildQueryPlugin = ({
collectionSlug,
versionsFields,
}: GetBuildQueryPluginArgs = {}) => {
return function buildQueryPlugin(schema) {
return function buildQueryPlugin(schema: any) {
const modifiedSchema = schema
async function schemaBuildQuery({
globalSlug,
@@ -31,19 +31,35 @@ export const getBuildQueryPlugin = ({
payload,
where,
}: BuildQueryArgs): Promise<Record<string, unknown>> {
let fields = versionsFields
if (!fields) {
let fields: FlattenedField[] | null = null
if (versionsFields) {
fields = versionsFields
} else {
if (globalSlug) {
const globalConfig = payload.globals.config.find(({ slug }) => slug === globalSlug)
if (!globalConfig) {
throw new APIError(`Global with the slug ${globalSlug} was not found`)
}
fields = globalConfig.flattenedFields
}
if (collectionSlug) {
const collectionConfig = payload.collections[collectionSlug].config
const collectionConfig = payload.collections[collectionSlug]?.config
if (!collectionConfig) {
throw new APIError(`Collection with the slug ${globalSlug} was not found`)
}
fields = collectionConfig.flattenedFields
}
}
const errors = []
if (fields === null) {
throw new APIError('Fields are not initialized.')
}
const result = await parseParams({
collectionSlug,
fields,
@@ -54,10 +70,6 @@ export const getBuildQueryPlugin = ({
where,
})
if (errors.length > 0) {
throw new QueryError(errors)
}
return result
}
modifiedSchema.statics.buildQuery = schemaBuildQuery

View File

@@ -19,6 +19,7 @@ describe('get localized sort property', () => {
it('passes through a non-localized sort property', () => {
const result = getLocalizedSortProperty({
config,
parentIsLocalized: false,
fields: [
{
name: 'title',
@@ -35,6 +36,7 @@ describe('get localized sort property', () => {
it('properly localizes an un-localized sort property', () => {
const result = getLocalizedSortProperty({
config,
parentIsLocalized: false,
fields: [
{
name: 'title',
@@ -52,6 +54,7 @@ describe('get localized sort property', () => {
it('keeps specifically asked-for localized sort properties', () => {
const result = getLocalizedSortProperty({
config,
parentIsLocalized: false,
fields: [
{
name: 'title',
@@ -69,6 +72,7 @@ describe('get localized sort property', () => {
it('properly localizes nested sort properties', () => {
const result = getLocalizedSortProperty({
config,
parentIsLocalized: false,
fields: flattenAllFields({
fields: [
{
@@ -94,6 +98,7 @@ describe('get localized sort property', () => {
it('keeps requested locale with nested sort properties', () => {
const result = getLocalizedSortProperty({
config,
parentIsLocalized: false,
fields: flattenAllFields({
fields: [
{
@@ -119,6 +124,7 @@ describe('get localized sort property', () => {
it('properly localizes field within row', () => {
const result = getLocalizedSortProperty({
config,
parentIsLocalized: false,
fields: flattenAllFields({
fields: [
{
@@ -143,6 +149,7 @@ describe('get localized sort property', () => {
it('properly localizes field within named tab', () => {
const result = getLocalizedSortProperty({
config,
parentIsLocalized: false,
fields: flattenAllFields({
fields: [
{
@@ -172,6 +179,7 @@ describe('get localized sort property', () => {
it('properly localizes field within unnamed tab', () => {
const result = getLocalizedSortProperty({
config,
parentIsLocalized: false,
fields: flattenAllFields({
fields: [
{

View File

@@ -5,7 +5,7 @@ import { fieldAffectsData, fieldIsPresentationalOnly, fieldShouldBeLocalized } f
type Args = {
config: SanitizedConfig
fields: FlattenedField[]
locale: string
locale?: string
parentIsLocalized: boolean
result?: string
segments: string[]
@@ -36,14 +36,16 @@ export const getLocalizedSortProperty = ({
)
if (matchedField && !fieldIsPresentationalOnly(matchedField)) {
let nextFields: FlattenedField[]
let nextFields: FlattenedField[] | null = null
let nextParentIsLocalized = parentIsLocalized
const remainingSegments = [...segments]
let localizedSegment = matchedField.name
if (fieldShouldBeLocalized({ field: matchedField, parentIsLocalized })) {
if (
fieldShouldBeLocalized({ field: matchedField, parentIsLocalized: parentIsLocalized ?? false })
) {
// Check to see if next segment is a locale
if (segments.length > 0) {
if (segments.length > 0 && remainingSegments[0]) {
const nextSegmentIsLocale = config.localization.localeCodes.includes(remainingSegments[0])
// If next segment is locale, remove it from remaining segments
@@ -66,16 +68,21 @@ export const getLocalizedSortProperty = ({
) {
nextFields = matchedField.flattenedFields
if (!nextParentIsLocalized) {
nextParentIsLocalized = matchedField.localized
nextParentIsLocalized = matchedField.localized ?? false
}
}
if (matchedField.type === 'blocks') {
nextFields = (matchedField.blockReferences ?? matchedField.blocks).reduce(
nextFields = (matchedField.blockReferences ?? matchedField.blocks).reduce<FlattenedField[]>(
(flattenedBlockFields, _block) => {
// TODO: iterate over blocks mapped to block slug in v4, or pass through payload.blocks
const block =
typeof _block === 'string' ? config.blocks.find((b) => b.slug === _block) : _block
typeof _block === 'string' ? config.blocks?.find((b) => b.slug === _block) : _block
if (!block) {
return [...flattenedBlockFields]
}
return [
...flattenedBlockFields,
...block.flattenedFields.filter(
@@ -93,7 +100,7 @@ export const getLocalizedSortProperty = ({
const result = incomingResult ? `${incomingResult}.${localizedSegment}` : localizedSegment
if (nextFields) {
if (nextFields !== null) {
return getLocalizedSortProperty({
config,
fields: nextFields,

View File

@@ -1,3 +1,5 @@
export type OperatorMapKey = keyof typeof operatorMap
export const operatorMap = {
all: '$all',
equals: '$eq',

View File

@@ -19,7 +19,7 @@ export async function parseParams({
collectionSlug?: string
fields: FlattenedField[]
globalSlug?: string
locale: string
locale?: string
parentIsLocalized: boolean
payload: Payload
where: Where
@@ -30,7 +30,7 @@ export async function parseParams({
// We need to determine if the whereKey is an AND, OR, or a schema path
for (const relationOrPath of Object.keys(where)) {
const condition = where[relationOrPath]
let conditionOperator: '$and' | '$or'
let conditionOperator: '$and' | '$or' | null = null
if (relationOrPath.toLowerCase() === 'and') {
conditionOperator = '$and'
} else if (relationOrPath.toLowerCase() === 'or') {
@@ -46,7 +46,7 @@ export async function parseParams({
payload,
where: condition,
})
if (builtConditions.length > 0) {
if (builtConditions.length > 0 && conditionOperator !== null) {
result[conditionOperator] = builtConditions
}
} else {
@@ -58,6 +58,7 @@ export async function parseParams({
const validOperators = Object.keys(pathOperators).filter((operator) =>
validOperatorSet.has(operator as Operator),
)
for (const operator of validOperators) {
const searchParam = await buildSearchParam({
collectionSlug,
@@ -68,7 +69,7 @@ export async function parseParams({
operator,
parentIsLocalized,
payload,
val: pathOperators[operator],
val: (pathOperators as Record<string, Where>)[operator],
})
if (searchParam?.value && searchParam?.path) {
@@ -83,7 +84,7 @@ export async function parseParams({
result[searchParam.path] = searchParam.value
}
} else if (typeof searchParam?.value === 'object') {
result = deepMergeWithCombinedArrays(result, searchParam.value, {
result = deepMergeWithCombinedArrays(result, searchParam.value ?? {}, {
// dont clone Types.ObjectIDs
clone: false,
})

View File

@@ -21,7 +21,7 @@ type SanitizeQueryValueArgs = {
val: any
}
const buildExistsQuery = (formattedValue, path, treatEmptyString = true) => {
const buildExistsQuery = (formattedValue: unknown, path: string, treatEmptyString = true) => {
if (formattedValue) {
return {
rawQuery: {
@@ -54,14 +54,17 @@ const getFieldFromSegments = ({
field: FlattenedBlock | FlattenedField
payload: Payload
segments: string[]
}) => {
}): FlattenedField | undefined => {
if ('blocks' in field || 'blockReferences' in field) {
const _field: FlattenedBlocksField = field as FlattenedBlocksField
for (const _block of _field.blockReferences ?? _field.blocks) {
const block: FlattenedBlock = typeof _block === 'string' ? payload.blocks[_block] : _block
const field = getFieldFromSegments({ field: block, payload, segments })
if (field) {
return field
const block: FlattenedBlock | undefined =
typeof _block === 'string' ? payload.blocks[_block] : _block
if (block) {
const field = getFieldFromSegments({ field: block, payload, segments })
if (field) {
return field
}
}
}
}
@@ -93,11 +96,13 @@ export const sanitizeQueryValue = ({
path,
payload,
val,
}: SanitizeQueryValueArgs): {
operator?: string
rawQuery?: unknown
val?: unknown
} => {
}: SanitizeQueryValueArgs):
| {
operator?: string
rawQuery?: unknown
val?: unknown
}
| undefined => {
let formattedValue = val
let formattedOperator = operator
if (['array', 'blocks', 'group', 'tab'].includes(field.type) && path.includes('.')) {
@@ -141,24 +146,26 @@ export const sanitizeQueryValue = ({
formattedValue = createArrayFromCommaDelineated(val)
}
formattedValue = formattedValue.reduce((formattedValues, inVal) => {
if (!hasCustomID) {
if (Types.ObjectId.isValid(inVal)) {
formattedValues.push(new Types.ObjectId(inVal))
if (Array.isArray(formattedValue)) {
formattedValue = formattedValue.reduce<unknown[]>((formattedValues, inVal) => {
if (!hasCustomID) {
if (Types.ObjectId.isValid(inVal)) {
formattedValues.push(new Types.ObjectId(inVal))
}
}
}
if (field.type === 'number') {
const parsedNumber = parseFloat(inVal)
if (!Number.isNaN(parsedNumber)) {
formattedValues.push(parsedNumber)
if (field.type === 'number') {
const parsedNumber = parseFloat(inVal)
if (!Number.isNaN(parsedNumber)) {
formattedValues.push(parsedNumber)
}
} else {
formattedValues.push(inVal)
}
} else {
formattedValues.push(inVal)
}
return formattedValues
}, [])
return formattedValues
}, [])
}
}
}
@@ -175,7 +182,7 @@ export const sanitizeQueryValue = ({
if (['all', 'in', 'not_in'].includes(operator) && typeof formattedValue === 'string') {
formattedValue = createArrayFromCommaDelineated(formattedValue)
if (field.type === 'number') {
if (field.type === 'number' && Array.isArray(formattedValue)) {
formattedValue = formattedValue.map((arrayVal) => parseFloat(arrayVal))
}
}
@@ -264,7 +271,7 @@ export const sanitizeQueryValue = ({
return formattedValues
}
if (typeof relationTo === 'string' && payload.collections[relationTo].customIDType) {
if (typeof relationTo === 'string' && payload.collections[relationTo]?.customIDType) {
if (payload.collections[relationTo].customIDType === 'number') {
const parsedNumber = parseFloat(inVal)
if (!Number.isNaN(parsedNumber)) {
@@ -279,7 +286,7 @@ export const sanitizeQueryValue = ({
if (
Array.isArray(relationTo) &&
relationTo.some((relationTo) => !!payload.collections[relationTo].customIDType)
relationTo.some((relationTo) => !!payload.collections[relationTo]?.customIDType)
) {
if (Types.ObjectId.isValid(inVal.toString())) {
formattedValues.push(new Types.ObjectId(inVal))
@@ -302,7 +309,7 @@ export const sanitizeQueryValue = ({
(!Array.isArray(relationTo) || !path.endsWith('.relationTo'))
) {
if (typeof relationTo === 'string') {
const customIDType = payload.collections[relationTo].customIDType
const customIDType = payload.collections[relationTo]?.customIDType
if (customIDType) {
if (customIDType === 'number') {
@@ -320,7 +327,7 @@ export const sanitizeQueryValue = ({
}
} else {
const hasCustomIDType = relationTo.some(
(relationTo) => !!payload.collections[relationTo].customIDType,
(relationTo) => !!payload.collections[relationTo]?.customIDType,
)
if (hasCustomIDType) {

View File

@@ -10,15 +10,31 @@ import { buildSortParam } from './queries/buildSortParam.js'
import { aggregatePaginate } from './utilities/aggregatePaginate.js'
import { buildJoinAggregation } from './utilities/buildJoinAggregation.js'
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
import { getCollection } from './utilities/getEntity.js'
import { getSession } from './utilities/getSession.js'
import { transform } from './utilities/transform.js'
export const queryDrafts: QueryDrafts = async function queryDrafts(
this: MongooseAdapter,
{ collection, joins, limit, locale, page, pagination, req, select, sort: sortArg, where },
{
collection: collectionSlug,
joins,
limit,
locale,
page,
pagination,
req,
select,
sort: sortArg,
where = {},
},
) {
const VersionModel = this.versions[collection]
const collectionConfig = this.payload.collections[collection].config
const { collectionConfig, Model } = getCollection({
adapter: this,
collectionSlug,
versions: true,
})
const options: QueryOptions = {
session: await getSession(this, req),
}
@@ -89,24 +105,25 @@ export const queryDrafts: QueryDrafts = async function queryDrafts(
// the correct indexed field
paginationOptions.useCustomCountFn = () => {
return Promise.resolve(
VersionModel.countDocuments(versionQuery, {
Model.countDocuments(versionQuery, {
hint: { _id: 1 },
}),
)
}
}
if (limit > 0) {
if (limit && limit > 0) {
paginationOptions.limit = limit
// limit must also be set here, it's ignored when pagination is false
paginationOptions.options.limit = limit
paginationOptions.options!.limit = limit
}
let result
const aggregate = await buildJoinAggregation({
adapter: this,
collection,
collection: collectionSlug,
collectionConfig,
joins,
locale,
@@ -122,17 +139,17 @@ export const queryDrafts: QueryDrafts = async function queryDrafts(
collation: paginationOptions.collation,
joinAggregation: aggregate,
limit: paginationOptions.limit,
Model: VersionModel,
Model,
page: paginationOptions.page,
pagination: paginationOptions.pagination,
projection: paginationOptions.projection,
query: versionQuery,
session: paginationOptions.options?.session,
session: paginationOptions.options?.session ?? undefined,
sort: paginationOptions.sort as object,
useEstimatedCount: paginationOptions.useEstimatedCount,
})
} else {
result = await VersionModel.paginate(versionQuery, paginationOptions)
result = await Model.paginate(versionQuery, paginationOptions)
}
transform({

View File

@@ -7,6 +7,7 @@ import { v4 as uuid } from 'uuid'
import type { MongooseAdapter } from '../index.js'
// Needs await to fulfill the interface
// @ts-expect-error TransactionOptions isn't compatible with BeginTransaction of the DatabaseAdapter interface.
// eslint-disable-next-line @typescript-eslint/require-await
export const beginTransaction: BeginTransaction = async function beginTransaction(
this: MongooseAdapter,
@@ -20,12 +21,13 @@ export const beginTransaction: BeginTransaction = async function beginTransactio
const id = uuid()
if (!this.sessions[id]) {
// @ts-expect-error BaseDatabaseAdapter and MongoosAdapter (that extends Base) sessions aren't compatible.
this.sessions[id] = client.startSession()
}
if (this.sessions[id].inTransaction()) {
if (this.sessions[id]?.inTransaction()) {
this.payload.logger.warn('beginTransaction called while transaction already exists')
} else {
this.sessions[id].startTransaction(options || (this.transactionOptions as TransactionOptions))
this.sessions[id]?.startTransaction(options || (this.transactionOptions as TransactionOptions))
}
return id

View File

@@ -1,6 +1,11 @@
import type { CommitTransaction } from 'payload'
export const commitTransaction: CommitTransaction = async function commitTransaction(id) {
import type { MongooseAdapter } from '../index.js'
export const commitTransaction: CommitTransaction = async function commitTransaction(
this: MongooseAdapter,
id,
) {
if (id instanceof Promise) {
return
}
@@ -12,7 +17,7 @@ export const commitTransaction: CommitTransaction = async function commitTransac
await this.sessions[id].commitTransaction()
try {
await this.sessions[id].endSession()
} catch (error) {
} catch (_) {
// ending sessions is only best effort and won't impact anything if it fails since the transaction was committed
}
delete this.sessions[id]

View File

@@ -1,6 +1,9 @@
import type { RollbackTransaction } from 'payload'
import type { MongooseAdapter } from '../index.js'
export const rollbackTransaction: RollbackTransaction = async function rollbackTransaction(
this: MongooseAdapter,
incomingID = '',
) {
let transactionID: number | string
@@ -18,7 +21,7 @@ export const rollbackTransaction: RollbackTransaction = async function rollbackT
}
// when session exists but is not inTransaction something unexpected is happening to the session
if (!this.sessions[transactionID].inTransaction()) {
if (!this.sessions[transactionID]?.inTransaction()) {
this.payload.logger.warn('rollbackTransaction called when no transaction exists')
delete this.sessions[transactionID]
return
@@ -26,8 +29,8 @@ export const rollbackTransaction: RollbackTransaction = async function rollbackT
// the first call for rollback should be aborted and deleted causing any other operations with the same transaction to fail
try {
await this.sessions[transactionID].abortTransaction()
await this.sessions[transactionID].endSession()
await this.sessions[transactionID]?.abortTransaction()
await this.sessions[transactionID]?.endSession()
} catch (error) {
// ignore the error as it is likely a race condition from multiple errors
}

View File

@@ -4,15 +4,17 @@ import type { UpdateGlobal } from 'payload'
import type { MongooseAdapter } from './index.js'
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
import { getGlobal } from './utilities/getEntity.js'
import { getSession } from './utilities/getSession.js'
import { transform } from './utilities/transform.js'
export const updateGlobal: UpdateGlobal = async function updateGlobal(
this: MongooseAdapter,
{ slug, data, options: optionsArgs = {}, req, returning, select },
{ slug: globalSlug, data, options: optionsArgs = {}, req, returning, select },
) {
const Model = this.globals
const fields = this.payload.config.globals.find((global) => global.slug === slug).fields
const { globalConfig, Model } = getGlobal({ adapter: this, globalSlug })
const fields = globalConfig.fields
const options: MongooseUpdateQueryOptions = {
...optionsArgs,
@@ -20,22 +22,22 @@ export const updateGlobal: UpdateGlobal = async function updateGlobal(
new: true,
projection: buildProjectionFromSelect({
adapter: this,
fields: this.payload.config.globals.find((global) => global.slug === slug).flattenedFields,
fields: globalConfig.flattenedFields,
select,
}),
session: await getSession(this, req),
}
transform({ adapter: this, data, fields, globalSlug: slug, operation: 'write' })
transform({ adapter: this, data, fields, globalSlug, operation: 'write' })
if (returning === false) {
await Model.updateOne({ globalType: slug }, data, options)
await Model.updateOne({ globalType: globalSlug }, data, options)
return null
}
const result: any = await Model.findOneAndUpdate({ globalType: slug }, data, options)
const result: any = await Model.findOneAndUpdate({ globalType: globalSlug }, data, options)
transform({ adapter: this, data: result, fields, globalSlug: slug, operation: 'read' })
transform({ adapter: this, data: result, fields, globalSlug, operation: 'read' })
return result
}

View File

@@ -6,6 +6,7 @@ import type { MongooseAdapter } from './index.js'
import { buildQuery } from './queries/buildQuery.js'
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
import { getGlobal } from './utilities/getEntity.js'
import { getSession } from './utilities/getSession.js'
import { transform } from './utilities/transform.js'
@@ -23,12 +24,11 @@ export async function updateGlobalVersion<T extends TypeWithID>(
where,
}: UpdateGlobalVersionArgs<T>,
) {
const VersionModel = this.versions[globalSlug]
const { globalConfig, Model } = getGlobal({ adapter: this, globalSlug, versions: true })
const whereToUse = where || { id: { equals: id } }
const currentGlobal = this.payload.config.globals.find((global) => global.slug === globalSlug)
const fields = buildVersionGlobalFields(this.payload.config, currentGlobal)
const flattenedFields = buildVersionGlobalFields(this.payload.config, currentGlobal, true)
const fields = buildVersionGlobalFields(this.payload.config, globalConfig)
const flattenedFields = buildVersionGlobalFields(this.payload.config, globalConfig, true)
const options: MongooseUpdateQueryOptions = {
...optionsArgs,
lean: true,
@@ -51,11 +51,11 @@ export async function updateGlobalVersion<T extends TypeWithID>(
transform({ adapter: this, data: versionData, fields, operation: 'write' })
if (returning === false) {
await VersionModel.updateOne(query, versionData, options)
await Model.updateOne(query, versionData, options)
return null
}
const doc = await VersionModel.findOneAndUpdate(query, versionData, options)
const doc = await Model.findOneAndUpdate(query, versionData, options)
if (!doc) {
return null

View File

@@ -5,16 +5,25 @@ import type { MongooseAdapter } from './index.js'
import { buildQuery } from './queries/buildQuery.js'
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
import { getCollection } from './utilities/getEntity.js'
import { getSession } from './utilities/getSession.js'
import { handleError } from './utilities/handleError.js'
import { transform } from './utilities/transform.js'
export const updateMany: UpdateMany = async function updateMany(
this: MongooseAdapter,
{ collection, data, locale, options: optionsArgs = {}, req, returning, select, where },
{
collection: collectionSlug,
data,
locale,
options: optionsArgs = {},
req,
returning,
select,
where,
},
) {
const Model = this.collections[collection]
const fields = this.payload.collections[collection].config.fields
const { collectionConfig, Model } = getCollection({ adapter: this, collectionSlug })
const options: MongooseUpdateQueryOptions = {
...optionsArgs,
@@ -22,7 +31,7 @@ export const updateMany: UpdateMany = async function updateMany(
new: true,
projection: buildProjectionFromSelect({
adapter: this,
fields: this.payload.collections[collection].config.flattenedFields,
fields: collectionConfig.flattenedFields,
select,
}),
session: await getSession(this, req),
@@ -30,18 +39,18 @@ export const updateMany: UpdateMany = async function updateMany(
const query = await buildQuery({
adapter: this,
collectionSlug: collection,
fields: this.payload.collections[collection].config.flattenedFields,
collectionSlug,
fields: collectionConfig.flattenedFields,
locale,
where,
})
transform({ adapter: this, data, fields, operation: 'write' })
transform({ adapter: this, data, fields: collectionConfig.fields, operation: 'write' })
try {
await Model.updateMany(query, data, options)
} catch (error) {
handleError({ collection, error, req })
handleError({ collection: collectionSlug, error, req })
}
if (returning === false) {
@@ -53,7 +62,7 @@ export const updateMany: UpdateMany = async function updateMany(
transform({
adapter: this,
data: result,
fields,
fields: collectionConfig.fields,
operation: 'read',
})

View File

@@ -5,6 +5,7 @@ import type { MongooseAdapter } from './index.js'
import { buildQuery } from './queries/buildQuery.js'
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
import { getCollection } from './utilities/getEntity.js'
import { getSession } from './utilities/getSession.js'
import { handleError } from './utilities/handleError.js'
import { transform } from './utilities/transform.js'
@@ -13,26 +14,27 @@ export const updateOne: UpdateOne = async function updateOne(
this: MongooseAdapter,
{
id,
collection,
collection: collectionSlug,
data,
locale,
options: optionsArgs = {},
req,
returning,
select,
where: whereArg,
where: whereArg = {},
},
) {
const { collectionConfig, Model } = getCollection({ adapter: this, collectionSlug })
const where = id ? { id: { equals: id } } : whereArg
const Model = this.collections[collection]
const fields = this.payload.collections[collection].config.fields
const fields = collectionConfig.fields
const options: MongooseUpdateQueryOptions = {
...optionsArgs,
lean: true,
new: true,
projection: buildProjectionFromSelect({
adapter: this,
fields: this.payload.collections[collection].config.flattenedFields,
fields: collectionConfig.flattenedFields,
select,
}),
session: await getSession(this, req),
@@ -40,8 +42,8 @@ export const updateOne: UpdateOne = async function updateOne(
const query = await buildQuery({
adapter: this,
collectionSlug: collection,
fields: this.payload.collections[collection].config.flattenedFields,
collectionSlug,
fields: collectionConfig.flattenedFields,
locale,
where,
})
@@ -58,7 +60,7 @@ export const updateOne: UpdateOne = async function updateOne(
result = await Model.findOneAndUpdate(query, data, options)
}
} catch (error) {
handleError({ collection, error, req })
handleError({ collection: collectionSlug, error, req })
}
if (!result) {

View File

@@ -6,25 +6,34 @@ import type { MongooseAdapter } from './index.js'
import { buildQuery } from './queries/buildQuery.js'
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
import { getCollection } from './utilities/getEntity.js'
import { getSession } from './utilities/getSession.js'
import { transform } from './utilities/transform.js'
export const updateVersion: UpdateVersion = async function updateVersion(
this: MongooseAdapter,
{ id, collection, locale, options: optionsArgs = {}, req, returning, select, versionData, where },
{
id,
collection: collectionSlug,
locale,
options: optionsArgs = {},
req,
returning,
select,
versionData,
where,
},
) {
const VersionModel = this.versions[collection]
const whereToUse = where || { id: { equals: id } }
const fields = buildVersionCollectionFields(
this.payload.config,
this.payload.collections[collection].config,
)
const { collectionConfig, Model } = getCollection({
adapter: this,
collectionSlug,
versions: true,
})
const flattenedFields = buildVersionCollectionFields(
this.payload.config,
this.payload.collections[collection].config,
true,
)
const whereToUse = where || { id: { equals: id } }
const fields = buildVersionCollectionFields(this.payload.config, collectionConfig)
const flattenedFields = buildVersionCollectionFields(this.payload.config, collectionConfig, true)
const options: MongooseUpdateQueryOptions = {
...optionsArgs,
@@ -48,11 +57,11 @@ export const updateVersion: UpdateVersion = async function updateVersion(
transform({ adapter: this, data: versionData, fields, operation: 'write' })
if (returning === false) {
await VersionModel.updateOne(query, versionData, options)
await Model.updateOne(query, versionData, options)
return null
}
const doc = await VersionModel.findOneAndUpdate(query, versionData, options)
const doc = await Model.findOneAndUpdate(query, versionData, options)
if (!doc) {
return null

View File

@@ -82,16 +82,18 @@ export const aggregatePaginate = async ({
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 hasPrevPage = typeof page === 'number' && pagination !== false && page > 1
const hasNextPage = typeof page === 'number' && pagination !== false && totalPages > page
const pagingCounter =
pagination !== false && typeof limit === 'number' ? (page - 1) * limit + 1 : 1
typeof page === 'number' && pagination !== false && typeof limit === 'number'
? (page - 1) * limit + 1
: 1
const result: PaginatedDocs = {
docs,
hasNextPage,
hasPrevPage,
limit,
limit: limit ?? 0,
nextPage: hasNextPage ? page + 1 : null,
page,
pagingCounter,

View File

@@ -1,28 +1,29 @@
import type { PipelineStage } from 'mongoose'
import type {
CollectionSlug,
FlattenedField,
JoinQuery,
SanitizedCollectionConfig,
Where,
} from 'payload'
import {
APIError,
type CollectionSlug,
type FlattenedField,
type JoinQuery,
type SanitizedCollectionConfig,
} from 'payload'
import { fieldShouldBeLocalized } from 'payload/shared'
import type { MongooseAdapter } from '../index.js'
import { buildQuery } from '../queries/buildQuery.js'
import { buildSortParam } from '../queries/buildSortParam.js'
import { getCollection } from './getEntity.js'
type BuildJoinAggregationArgs = {
adapter: MongooseAdapter
collection: CollectionSlug
collectionConfig: SanitizedCollectionConfig
joins: JoinQuery
locale: string
joins?: JoinQuery
locale?: string
projection?: Record<string, true>
// the where clause for the top collection
query?: Where
query?: Record<string, unknown>
/** whether the query is from drafts */
versions?: boolean
}
@@ -44,9 +45,18 @@ export const buildJoinAggregation = async ({
return
}
const joinConfig = adapter.payload.collections[collection].config.joins
const joinConfig = adapter.payload.collections[collection]?.config?.joins
if (!joinConfig) {
throw new APIError(`Could not retrieve sanitized join config for ${collection}.`)
}
const aggregate: PipelineStage[] = []
const polymorphicJoinsConfig = adapter.payload.collections[collection].config.polymorphicJoins
const polymorphicJoinsConfig = adapter.payload.collections[collection]?.config?.polymorphicJoins
if (!polymorphicJoinsConfig) {
throw new APIError(`Could not retrieve sanitized polymorphic joins config for ${collection}.`)
}
for (const join of polymorphicJoinsConfig) {
if (projection && !projection[join.joinPath]) {
@@ -62,12 +72,14 @@ export const buildJoinAggregation = async ({
limit: limitJoin = join.field.defaultLimit ?? 10,
page,
sort: sortJoin = join.field.defaultSort || collectionConfig.defaultSort,
where: whereJoin,
where: whereJoin = {},
} = joins?.[join.joinPath] || {}
const aggregatedFields: FlattenedField[] = []
for (const collectionSlug of join.field.collection) {
for (const field of adapter.payload.collections[collectionSlug].config.flattenedFields) {
const { collectionConfig } = getCollection({ adapter, collectionSlug })
for (const field of collectionConfig.flattenedFields) {
if (!aggregatedFields.some((eachField) => eachField.name === field.name)) {
aggregatedFields.push(field)
}
@@ -89,7 +101,7 @@ export const buildJoinAggregation = async ({
where: whereJoin,
})
const sortProperty = Object.keys(sort)[0]
const sortProperty = Object.keys(sort)[0]! // assert because buildSortParam always returns at least 1 key.
const sortDirection = sort[sortProperty] === 'asc' ? 1 : -1
const projectSort = sortProperty !== '_id' && sortProperty !== 'relationTo'
@@ -124,10 +136,12 @@ export const buildJoinAggregation = async ({
},
]
const { Model: JoinModel } = getCollection({ adapter, collectionSlug })
aggregate.push({
$lookup: {
as: alias,
from: adapter.collections[collectionSlug].collection.name,
from: JoinModel.collection.name,
let: {
root_id_: '$_id',
},
@@ -159,7 +173,7 @@ export const buildJoinAggregation = async ({
aggregate.push({
$lookup: {
as: `${as}.totalDocs.${alias}`,
from: adapter.collections[collectionSlug].collection.name,
from: JoinModel.collection.name,
let: {
root_id_: '$_id',
},
@@ -232,7 +246,13 @@ export const buildJoinAggregation = async ({
}
for (const slug of Object.keys(joinConfig)) {
for (const join of joinConfig[slug]) {
const joinsList = joinConfig[slug]
if (!joinsList) {
throw new APIError(`Failed to retrieve array of joins for ${slug} in collectio ${collection}`)
}
for (const join of joinsList) {
if (projection && !projection[join.joinPath]) {
continue
}
@@ -241,31 +261,34 @@ export const buildJoinAggregation = async ({
continue
}
const { collectionConfig, Model: JoinModel } = getCollection({
adapter,
collectionSlug: join.field.collection as string,
})
const {
count,
limit: limitJoin = join.field.defaultLimit ?? 10,
page,
sort: sortJoin = join.field.defaultSort || collectionConfig.defaultSort,
where: whereJoin,
where: whereJoin = {},
} = joins?.[join.joinPath] || {}
if (Array.isArray(join.field.collection)) {
throw new Error('Unreachable')
}
const joinModel = adapter.collections[join.field.collection]
const sort = buildSortParam({
config: adapter.payload.config,
fields: adapter.payload.collections[slug].config.flattenedFields,
fields: collectionConfig.flattenedFields,
locale,
sort: sortJoin,
timestamps: true,
})
const sortProperty = Object.keys(sort)[0]
const sortProperty = Object.keys(sort)[0]!
const sortDirection = sort[sortProperty] === 'asc' ? 1 : -1
const $match = await joinModel.buildQuery({
const $match = await JoinModel.buildQuery({
locale,
payload: adapter.payload,
where: whereJoin,
@@ -301,7 +324,7 @@ export const buildJoinAggregation = async ({
$lookup: {
as: `${as}.totalDocs`,
foreignField,
from: adapter.collections[slug].collection.name,
from: JoinModel.collection.name,
localField: versions ? 'parent' : '_id',
pipeline: [
{
@@ -329,7 +352,7 @@ export const buildJoinAggregation = async ({
$lookup: {
as: `${as}.docs`,
foreignField: `${join.field.on}${code}${polymorphicSuffix}`,
from: adapter.collections[slug].collection.name,
from: JoinModel.collection.name,
localField: versions ? 'parent' : '_id',
pipeline,
},
@@ -390,7 +413,7 @@ export const buildJoinAggregation = async ({
$lookup: {
as: `${as}.docs`,
foreignField,
from: adapter.collections[slug].collection.name,
from: JoinModel.collection.name,
localField: versions ? 'parent' : '_id',
pipeline,
},

View File

@@ -1,4 +1,10 @@
import type { FieldAffectingData, FlattenedField, SelectMode, SelectType } from 'payload'
import type {
FieldAffectingData,
FlattenedField,
SelectIncludeType,
SelectMode,
SelectType,
} from 'payload'
import {
deepCopyObjectSimple,
@@ -107,7 +113,7 @@ const traverseFields = ({
const fieldSelect = select[field.name] as SelectType
if (field.type === 'array' && selectMode === 'include') {
fieldSelect['id'] = true
fieldSelect.id = true
}
traverseFields({
@@ -128,6 +134,11 @@ const traverseFields = ({
for (const _block of field.blockReferences ?? field.blocks) {
const block = typeof _block === 'string' ? adapter.payload.blocks[_block] : _block
if (!block) {
continue
}
if (
(selectMode === 'include' && blocksSelect[block.slug] === true) ||
(selectMode === 'exclude' && typeof blocksSelect[block.slug] === 'undefined')
@@ -155,9 +166,10 @@ const traverseFields = ({
blocksSelect[block.slug] = {}
}
if (blockSelectMode === 'include') {
blocksSelect[block.slug]['id'] = true
blocksSelect[block.slug]['blockType'] = true
if (blockSelectMode === 'include' && typeof blocksSelect[block.slug] === 'object') {
const blockSelect = blocksSelect[block.slug] as SelectIncludeType
blockSelect.id = true
blockSelect.blockType = true
}
traverseFields({

View File

@@ -1,4 +1,4 @@
import type { DBIdentifierName } from 'payload'
import { APIError, type DBIdentifierName } from 'payload'
type Args = {
config: {
@@ -22,7 +22,7 @@ export const getDBName = ({
target = 'dbName',
versions = false,
}: Args): string => {
let result: string
let result: null | string = null
let custom = config[target]
if (!custom && target === 'enumName') {
@@ -32,12 +32,16 @@ export const getDBName = ({
if (custom) {
result = typeof custom === 'function' ? custom({}) : custom
} else {
result = name ?? slug
result = name ?? slug ?? null
}
if (versions) {
result = `_${result}_versions`
}
if (!result) {
throw new APIError(`Assertion for DB name of ${name} ${slug} was failed.`)
}
return result
}

View File

@@ -0,0 +1,91 @@
import type { Collection, SanitizedCollectionConfig, SanitizedGlobalConfig } from 'payload'
import { APIError } from 'payload'
import type { MongooseAdapter } from '../index.js'
import type { CollectionModel, GlobalModel } from '../types.js'
export const getCollection = ({
adapter,
collectionSlug,
versions = false,
}: {
adapter: MongooseAdapter
collectionSlug: string
versions?: boolean
}): {
collectionConfig: SanitizedCollectionConfig
customIDType: Collection['customIDType']
Model: CollectionModel
} => {
const collection = adapter.payload.collections[collectionSlug]
if (!collection) {
throw new APIError(
`ERROR: Failed to retrieve collection with the slug "${collectionSlug}". Does not exist.`,
)
}
if (versions) {
const Model = adapter.versions[collectionSlug]
if (!Model) {
throw new APIError(
`ERROR: Failed to retrieve collection version model with the slug "${collectionSlug}". Does not exist.`,
)
}
return { collectionConfig: collection.config, customIDType: collection.customIDType, Model }
}
const Model = adapter.collections[collectionSlug]
if (!Model) {
throw new APIError(
`ERROR: Failed to retrieve collection model with the slug "${collectionSlug}". Does not exist.`,
)
}
return { collectionConfig: collection.config, customIDType: collection.customIDType, Model }
}
type BaseGetGlobalArgs = {
adapter: MongooseAdapter
globalSlug: string
}
interface GetGlobal {
(args: { versions?: false | undefined } & BaseGetGlobalArgs): {
globalConfig: SanitizedGlobalConfig
Model: GlobalModel
}
(args: { versions?: true } & BaseGetGlobalArgs): {
globalConfig: SanitizedGlobalConfig
Model: CollectionModel
}
}
export const getGlobal: GetGlobal = ({ adapter, globalSlug, versions = false }) => {
const globalConfig = adapter.payload.config.globals.find((each) => each.slug === globalSlug)
if (!globalConfig) {
throw new APIError(
`ERROR: Failed to retrieve global with the slug "${globalSlug}". Does not exist.`,
)
}
if (versions) {
const Model = adapter.versions[globalSlug]
if (!Model) {
throw new APIError(
`ERROR: Failed to retrieve global version model with the slug "${globalSlug}". Does not exist.`,
)
}
return { globalConfig, Model }
}
return { globalConfig, Model: adapter.globals } as any
}

View File

@@ -9,7 +9,7 @@ export const handleError = ({
req,
}: {
collection?: string
error: Error
error: unknown
global?: string
req?: Partial<PayloadRequest>
}) => {
@@ -18,14 +18,20 @@ export const handleError = ({
}
// Handle uniqueness error from MongoDB
if ('code' in error && error.code === 11000 && 'keyValue' in error && error.keyValue) {
if (
'code' in error &&
error.code === 11000 &&
'keyValue' in error &&
error.keyValue &&
typeof error.keyValue === 'object'
) {
throw new ValidationError(
{
collection,
errors: [
{
message: req?.t ? req.t('error:valueMustBeUnique') : 'Value must be unique',
path: Object.keys(error.keyValue)[0],
path: Object.keys(error.keyValue)[0] ?? '',
},
],
global,
@@ -34,5 +40,6 @@ export const handleError = ({
)
}
// eslint-disable-next-line @typescript-eslint/only-throw-error
throw error
}

View File

@@ -234,12 +234,12 @@ export const transform = ({
fields,
globalSlug,
operation,
parentIsLocalized,
parentIsLocalized = false,
validateRelationships = true,
}: Args) => {
if (Array.isArray(data)) {
for (let i = 0; i < data.length; i++) {
transform({ adapter, data: data[i], fields, globalSlug, operation, validateRelationships })
for (const item of data) {
transform({ adapter, data: item, fields, globalSlug, operation, validateRelationships })
}
return
}
@@ -262,14 +262,16 @@ export const transform = ({
data.globalType = globalSlug
}
const sanitize: TraverseFieldsCallback = ({ field, ref }) => {
if (!ref || typeof ref !== 'object') {
const sanitize: TraverseFieldsCallback = ({ field, ref: incomingRef }) => {
if (!incomingRef || typeof incomingRef !== 'object') {
return
}
if (field.type === 'date' && operation === 'read' && ref[field.name]) {
const ref = incomingRef as Record<string, unknown>
if (field.type === 'date' && operation === 'read' && field.name in ref && ref[field.name]) {
if (config.localization && fieldShouldBeLocalized({ field, parentIsLocalized })) {
const fieldRef = ref[field.name]
const fieldRef = ref[field.name] as Record<string, unknown>
if (!fieldRef || typeof fieldRef !== 'object') {
return
}
@@ -284,7 +286,7 @@ export const transform = ({
} else {
sanitizeDate({
field,
ref: ref as Record<string, unknown>,
ref,
value: ref[field.name],
})
}
@@ -302,13 +304,13 @@ export const transform = ({
// handle localized relationships
if (config.localization && fieldShouldBeLocalized({ field, parentIsLocalized })) {
const locales = config.localization.locales
const fieldRef = ref[field.name]
const fieldRef = ref[field.name] as Record<string, unknown>
if (typeof fieldRef !== 'object') {
return
}
for (const { code } of locales) {
const value = ref[field.name][code]
const value = fieldRef[code]
if (value) {
sanitizeRelationship({
config,
@@ -328,7 +330,7 @@ export const transform = ({
field,
locale: undefined,
operation,
ref: ref as Record<string, unknown>,
ref,
validateRelationships,
value: ref[field.name],
})

View File

@@ -1,9 +1,4 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
/* TODO: remove the following lines */
"strict": false,
"noUncheckedIndexedAccess": false,
},
"references": [{ "path": "../payload" }, { "path": "../translations" }]
}

26
pnpm-lock.yaml generated
View File

@@ -264,6 +264,15 @@ importers:
'@payloadcms/eslint-config':
specifier: workspace:*
version: link:../eslint-config
'@types/mongoose-aggregate-paginate-v2':
specifier: 1.0.12
version: 1.0.12(@aws-sdk/credential-providers@3.687.0(@aws-sdk/client-sso-oidc@3.687.0(@aws-sdk/client-sts@3.687.0)))(socks@2.8.3)
'@types/prompts':
specifier: ^2.4.5
version: 2.4.9
'@types/uuid':
specifier: 10.0.0
version: 10.0.0
mongodb:
specifier: 6.12.0
version: 6.12.0(@aws-sdk/credential-providers@3.687.0(@aws-sdk/client-sso-oidc@3.687.0(@aws-sdk/client-sts@3.687.0)))(socks@2.8.3)
@@ -5349,6 +5358,9 @@ packages:
'@types/minimist@1.2.5':
resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==}
'@types/mongoose-aggregate-paginate-v2@1.0.12':
resolution: {integrity: sha512-wL8pgJQxqJagv5f5mR7aI8WgUu22nS6rVLoJm71W2Uu+iKfS8jgph2rRLfXrjo+dFt1s7ik5Zl+uGZ4f5GM6Vw==}
'@types/ms@0.7.34':
resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==}
@@ -14795,6 +14807,20 @@ snapshots:
'@types/minimist@1.2.5': {}
'@types/mongoose-aggregate-paginate-v2@1.0.12(@aws-sdk/credential-providers@3.687.0(@aws-sdk/client-sso-oidc@3.687.0(@aws-sdk/client-sts@3.687.0)))(socks@2.8.3)':
dependencies:
'@types/node': 22.5.4
mongoose: 8.9.5(@aws-sdk/credential-providers@3.687.0(@aws-sdk/client-sso-oidc@3.687.0(@aws-sdk/client-sts@3.687.0)))(socks@2.8.3)
transitivePeerDependencies:
- '@aws-sdk/credential-providers'
- '@mongodb-js/zstd'
- gcp-metadata
- kerberos
- mongodb-client-encryption
- snappy
- socks
- supports-color
'@types/ms@0.7.34': {}
'@types/mysql@2.15.26':

View File

@@ -1129,7 +1129,6 @@ describe('Queues', () => {
expect(jobAfterRun.input.amountTask1Retried).toBe(0)
})
it('ensure jobs can be cancelled using payload.jobs.cancelByID', async () => {
payload.config.jobs.deleteJobOnComplete = false