diff --git a/packages/db-mongodb/src/index.ts b/packages/db-mongodb/src/index.ts index b828e7dac..d69359b73 100644 --- a/packages/db-mongodb/src/index.ts +++ b/packages/db-mongodb/src/index.ts @@ -17,7 +17,6 @@ import type { TypeWithVersion, UpdateGlobalArgs, UpdateGlobalVersionArgs, - UpdateManyArgs, UpdateOneArgs, UpdateVersionArgs, } from 'payload' @@ -55,6 +54,7 @@ import { commitTransaction } from './transactions/commitTransaction.js' import { rollbackTransaction } from './transactions/rollbackTransaction.js' import { updateGlobal } from './updateGlobal.js' import { updateGlobalVersion } from './updateGlobalVersion.js' +import { updateJobs } from './updateJobs.js' import { updateMany } from './updateMany.js' import { updateOne } from './updateOne.js' import { updateVersion } from './updateVersion.js' @@ -227,6 +227,7 @@ export function mongooseAdapter({ mongoMemoryServer, sessions: {}, transactionOptions: transactionOptions === false ? undefined : transactionOptions, + updateJobs, updateMany, url, versions: {}, diff --git a/packages/db-mongodb/src/updateJobs.ts b/packages/db-mongodb/src/updateJobs.ts new file mode 100644 index 000000000..51dd7c027 --- /dev/null +++ b/packages/db-mongodb/src/updateJobs.ts @@ -0,0 +1,83 @@ +import type { MongooseUpdateQueryOptions } from 'mongoose' +import type { BaseJob, UpdateJobs, Where } 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' +import { handleError } from './utilities/handleError.js' +import { transform } from './utilities/transform.js' + +export const updateJobs: UpdateJobs = async function updateMany( + this: MongooseAdapter, + { id, data, limit, req, returning, where: whereArg }, +) { + const where = id ? { id: { equals: id } } : (whereArg as Where) + + const { collectionConfig, Model } = getCollection({ + adapter: this, + collectionSlug: 'payload-jobs', + }) + + const options: MongooseUpdateQueryOptions = { + lean: true, + new: true, + session: await getSession(this, req), + } + + let query = await buildQuery({ + adapter: this, + collectionSlug: collectionConfig.slug, + fields: collectionConfig.flattenedFields, + where, + }) + + transform({ adapter: this, data, fields: collectionConfig.fields, operation: 'write' }) + + let result: BaseJob[] = [] + + try { + if (id) { + if (returning === false) { + await Model.updateOne(query, data, options) + return null + } else { + const doc = await Model.findOneAndUpdate(query, data, options) + result = doc ? [doc] : [] + } + } else { + if (typeof limit === 'number' && limit > 0) { + const documentsToUpdate = await Model.find( + query, + {}, + { ...options, limit, projection: { _id: 1 } }, + ) + if (documentsToUpdate.length === 0) { + return null + } + + query = { _id: { $in: documentsToUpdate.map((doc) => doc._id) } } + } + + await Model.updateMany(query, data, options) + + if (returning === false) { + return null + } + + result = await Model.find(query, {}, options) + } + } catch (error) { + handleError({ collection: collectionConfig.slug, error, req }) + } + + transform({ + adapter: this, + data: result, + fields: collectionConfig.fields, + operation: 'read', + }) + + return result +} diff --git a/packages/db-postgres/src/index.ts b/packages/db-postgres/src/index.ts index 26ec09a11..4df602621 100644 --- a/packages/db-postgres/src/index.ts +++ b/packages/db-postgres/src/index.ts @@ -33,6 +33,7 @@ import { rollbackTransaction, updateGlobal, updateGlobalVersion, + updateJobs, updateMany, updateOne, updateVersion, @@ -172,6 +173,7 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj find, findGlobal, findGlobalVersions, + updateJobs, // @ts-expect-error - vestiges of when tsconfig was not strict. Feel free to improve findOne, findVersions, diff --git a/packages/db-sqlite/src/index.ts b/packages/db-sqlite/src/index.ts index f4ab2d633..86b15302d 100644 --- a/packages/db-sqlite/src/index.ts +++ b/packages/db-sqlite/src/index.ts @@ -34,6 +34,7 @@ import { rollbackTransaction, updateGlobal, updateGlobalVersion, + updateJobs, updateMany, updateOne, updateVersion, @@ -127,6 +128,7 @@ export function sqliteAdapter(args: Args): DatabaseAdapterObj { tables: {}, // @ts-expect-error - vestiges of when tsconfig was not strict. Feel free to improve transactionOptions: args.transactionOptions || undefined, + updateJobs, updateMany, versionsSuffix: args.versionsSuffix || '_v', diff --git a/packages/db-vercel-postgres/src/index.ts b/packages/db-vercel-postgres/src/index.ts index a96892560..410c7ca9a 100644 --- a/packages/db-vercel-postgres/src/index.ts +++ b/packages/db-vercel-postgres/src/index.ts @@ -34,6 +34,7 @@ import { rollbackTransaction, updateGlobal, updateGlobalVersion, + updateJobs, updateMany, updateOne, updateVersion, @@ -138,6 +139,7 @@ export function vercelPostgresAdapter(args: Args = {}): DatabaseAdapterObj( | 'migrateReset' | 'migrateStatus' | 'migrationDir' + | 'updateJobs' >, ): T { return { @@ -45,6 +47,7 @@ export function createDatabaseAdapter( migrateReset, migrateStatus, rollbackTransaction, + updateJobs: defaultUpdateJobs, ...args, // Ensure migrationDir is set diff --git a/packages/payload/src/database/defaultUpdateJobs.ts b/packages/payload/src/database/defaultUpdateJobs.ts new file mode 100644 index 000000000..cc66f6a2f --- /dev/null +++ b/packages/payload/src/database/defaultUpdateJobs.ts @@ -0,0 +1,53 @@ +import type { BaseJob, DatabaseAdapter } from '../index.js' +import type { UpdateJobs } from './types.js' + +import { jobsCollectionSlug } from '../queues/config/index.js' +import { sanitizeUpdateData } from '../queues/utilities/sanitizeUpdateData.js' + +export const defaultUpdateJobs: UpdateJobs = async function updateMany( + this: DatabaseAdapter, + { id, data, limit, req, returning, where }, +) { + const updatedJobs: BaseJob[] | null = [] + + const jobsToUpdate: BaseJob[] = ( + id + ? [ + await this.findOne({ + collection: jobsCollectionSlug, + req, + where: { id: { equals: id } }, + }), + ] + : ( + await this.find({ + collection: jobsCollectionSlug, + limit, + pagination: false, + req, + where, + }) + ).docs + ).filter(Boolean) as BaseJob[] + + if (!jobsToUpdate) { + return null + } + + for (const job of jobsToUpdate) { + const updateData = { + ...job, + ...data, + } + const updatedJob = await this.updateOne({ + id: job.id, + collection: jobsCollectionSlug, + data: sanitizeUpdateData({ data: updateData }), + req, + returning, + }) + updatedJobs.push(updatedJob) + } + + return updatedJobs +} diff --git a/packages/payload/src/database/types.ts b/packages/payload/src/database/types.ts index d978e9d1f..55c907b94 100644 --- a/packages/payload/src/database/types.ts +++ b/packages/payload/src/database/types.ts @@ -1,5 +1,5 @@ import type { TypeWithID } from '../collections/config/types.js' -import type { CollectionSlug, GlobalSlug } from '../index.js' +import type { BaseJob, CollectionSlug, GlobalSlug } from '../index.js' import type { Document, JoinQuery, @@ -147,6 +147,8 @@ export interface BaseDatabaseAdapter { updateGlobalVersion: UpdateGlobalVersion + updateJobs: UpdateJobs + updateMany: UpdateMany updateOne: UpdateOne @@ -540,6 +542,32 @@ export type UpdateManyArgs = { export type UpdateMany = (args: UpdateManyArgs) => Promise +export type UpdateJobsArgs = { + data: Record + req?: Partial + /** + * If true, returns the updated documents + * + * @default true + */ + returning?: boolean +} & ( + | { + id: number | string + limit?: never + sort?: never + where?: never + } + | { + id?: never + limit?: number + sort?: Sort + where: Where + } +) + +export type UpdateJobs = (args: UpdateJobsArgs) => Promise + export type UpsertArgs = { collection: CollectionSlug data: Record diff --git a/packages/payload/src/index.ts b/packages/payload/src/index.ts index 61c24caa9..ad401c8cb 100644 --- a/packages/payload/src/index.ts +++ b/packages/payload/src/index.ts @@ -1160,6 +1160,8 @@ export type { UpdateGlobalArgs, UpdateGlobalVersion, UpdateGlobalVersionArgs, + UpdateJobs, + UpdateJobsArgs, UpdateMany, UpdateManyArgs, UpdateOne, diff --git a/packages/payload/src/queues/utilities/updateJob.ts b/packages/payload/src/queues/utilities/updateJob.ts index 90867620c..113f4d8a3 100644 --- a/packages/payload/src/queues/utilities/updateJob.ts +++ b/packages/payload/src/queues/utilities/updateJob.ts @@ -1,5 +1,6 @@ import type { ManyOptions } from '../../collections/operations/local/update.js' -import type { PayloadRequest, Where } from '../../types/index.js' +import type { UpdateJobsArgs } from '../../database/types.js' +import type { PayloadRequest, Sort, Where } from '../../types/index.js' import type { BaseJob } from '../config/types/workflowTypes.js' import { jobAfterRead, jobsCollectionSlug } from '../config/index.js' @@ -17,32 +18,29 @@ type BaseArgs = { type ArgsByID = { id: number | string limit?: never + sort?: never where?: never } +type ArgsWhere = { + id?: never + limit?: number + sort?: Sort + where: Where +} + +type RunJobsArgs = (ArgsByID | ArgsWhere) & BaseArgs + /** * Convenience method for updateJobs by id */ export async function updateJob(args: ArgsByID & BaseArgs) { - const result = await updateJobs({ - ...args, - id: undefined, - limit: 1, - where: { id: { equals: args.id } }, - }) + const result = await updateJobs(args) if (result) { return result[0] } } -type ArgsWhere = { - id?: never | undefined - limit?: number - where: Where -} - -type RunJobsArgs = (ArgsByID | ArgsWhere) & BaseArgs - export async function updateJobs({ id, data, @@ -51,9 +49,12 @@ export async function updateJobs({ limit: limitArg, req, returning, - where, + sort, + where: whereArg, }: RunJobsArgs): Promise { const limit = id ? 1 : limitArg + const where = id ? { id: { equals: id } } : whereArg + if (depth || req.payload.config?.jobs?.runHooks) { const result = await req.payload.update({ id, @@ -71,36 +72,24 @@ export async function updateJobs({ return result.docs as BaseJob[] } - const updatedJobs = [] + const args: UpdateJobsArgs = id + ? { + id, + data: sanitizeUpdateData({ data }), + req: disableTransaction === true ? undefined : req, + returning, + } + : { + data: sanitizeUpdateData({ data }), + limit, + req: disableTransaction === true ? undefined : req, + returning, + sort, + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + where: where as Where, + } - // TODO: this can be optimized in the future - partial updates are supported in mongodb. In postgres, - // we can support this by manually constructing the sql query. We should use req.payload.db.updateMany instead - // of req.payload.db.updateOne once this is supported - const jobsToUpdate = await req.payload.db.find({ - collection: jobsCollectionSlug, - limit, - pagination: false, - req: disableTransaction === true ? undefined : req, - where, - }) - if (!jobsToUpdate?.docs) { - return null - } - - for (const job of jobsToUpdate.docs) { - const updateData = { - ...job, - ...data, - } - const updatedJob = await req.payload.db.updateOne({ - id: job.id, - collection: jobsCollectionSlug, - data: sanitizeUpdateData({ data: updateData }), - req: disableTransaction === true ? undefined : req, - returning, - }) - updatedJobs.push(updatedJob) - } + const updatedJobs: BaseJob[] | null = await req.payload.db.updateJobs(args) if (returning === false || !updatedJobs?.length) { return null