Files
payloadcms/test/queues/cli.int.spec.ts
Alessio Gravili 13c24afa63 feat: allow multiple, different payload instances using getPayload in same process (#13603)
Fixes https://github.com/payloadcms/payload/issues/13433. Testing
release: `3.54.0-internal.90cf7d5`

Previously, when calling `getPayload`, you would always use the same,
cached payload instance within a single process, regardless of the
arguments passed to the `getPayload` function. This resulted in the
following issues - both are fixed by this PR:

- If, in your frontend you're calling `getPayload` without `cron: true`,
and you're hosting the Payload Admin Panel in the same process, crons
will not be enabled even if you visit the admin panel which calls
`getPayload` with `cron: true`. This will break jobs autorun depending
on which page you visit first - admin panel or frontend
- Within the same process, you are unable to use `getPayload` twice for
different instances of payload with different Payload Configs.
On postgres, you can get around this by manually calling new
`BasePayload()` which skips the cache. This did not work on mongoose
though, as mongoose was caching the models on a global singleton (this
PR addresses this).
In order to bust the cache for different Payload Config, this PR
introduces a new, optional `key` property to `getPayload`.


## Mongoose - disable using global singleton

This PR refactors the Payload Mongoose adapter to stop relying on the
global mongoose singleton. Instead, each adapter instance now creates
and manages its own scoped Connection object.

### Motivation

Previously, calling `getPayload()` more than once in the same process
would throw `Cannot overwrite model` errors because models were compiled
into the global singleton. This prevented running multiple Payload
instances side-by-side, even when pointing at different databases.

### Changes
- Replace usage of `mongoose.connect()` / `mongoose.model()` with
instance-scoped `createConnection()` and `connection.model()`.
- Ensure models, globals, and versions are compiled per connection, not
globally.
- Added proper `close()` handling on `this.connection` instead of
`mongoose.disconnect()`.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211114366468745
2025-08-27 10:24:37 -07:00

86 lines
2.3 KiB
TypeScript

import path from 'path'
import {
_internal_jobSystemGlobals,
_internal_resetJobSystemGlobals,
getPayload,
migrateCLI,
type SanitizedConfig,
} from 'payload'
import { wait } from 'payload/shared'
import { fileURLToPath } from 'url'
import { initPayloadInt } from '../helpers/initPayloadInt.js'
import { waitUntilAutorunIsDone } from './utilities.js'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
describe('Queues - CLI', () => {
let config: SanitizedConfig
beforeAll(async () => {
;({ config } = await initPayloadInt(dirname, undefined, false))
})
it('ensure consecutive getPayload call with cron: true will autorun jobs', async () => {
const payload = await getPayload({
config,
})
await payload.jobs.queue({
workflow: 'inlineTaskTest',
queue: 'autorunSecond',
input: {
message: 'hello!',
},
})
process.env.PAYLOAD_DROP_DATABASE = 'false'
// Second instance of payload with the only purpose of running cron jobs
const _payload2 = await getPayload({
config,
cron: true,
})
await waitUntilAutorunIsDone({
payload,
queue: 'autorunSecond',
})
const allSimples = await payload.find({
collection: 'simple',
limit: 100,
})
expect(allSimples.totalDocs).toBe(1)
expect(allSimples?.docs?.[0]?.title).toBe('hello!')
// Shut down safely:
// Ensure no new crons are scheduled
_internal_jobSystemGlobals.shouldAutoRun = false
_internal_jobSystemGlobals.shouldAutoSchedule = false
// Wait 3 seconds to ensure all currently-running crons are done. If we shut down the db while a function is running, it can cause issues
// Cron function runs may persist after a test has finished
await wait(3000)
// Now we can destroy the payload instance
await _payload2.destroy()
await payload.destroy()
_internal_resetJobSystemGlobals()
})
it('can run migrate CLI without jobs attempting to run', async () => {
await migrateCLI({
config,
parsedArgs: {
_: ['migrate'],
},
})
// Wait 3 seconds to let potential autorun crons trigger
await new Promise((resolve) => setTimeout(resolve, 3000))
// Expect no errors. Previously, this would throw an "error: relation "payload_jobs" does not exist" error
expect(true).toBe(true)
})
})