Skip to content

Commit 7de11b2

Browse files
authored
fix: coerce schedulePublish doc.value to collection ID type before update (#17238)
3.x backport of #17236. Fixes #17229 ### What happened `input.doc.value` on a `schedulePublish` job is always stored as a string (#10481, which fixed job querying against numeric Postgres IDs by casting the stored value to string). `getSchedulePublishTask`'s handler passed that string straight into `payload.update({ id: input.doc.value, ... })` without coercing it back to the collection's real ID type. That's harmless on its own, but it leaks into any self-referencing relationship field recomputed during the update — e.g. the `breadcrumbs.doc` field added by `@payloadcms/plugin-nested-docs` — and fails that field's relationship-ID-type validation (`isValidID` requires `typeof value === 'number'` for numeric ID collections). Full repro/root-cause writeup is in #17229. ### The fix Same as #17236, adapted to this branch's version of `job.ts` (which still uses `publishSpecificLocale` rather than the simplified `locale` param on `main`): compute the target collection's real ID type and coerce `input.doc.value` to a `number` when needed, before passing it as `id` to `payload.update()`. ### Test plan Added the same regression test as #17236 in `test/plugin-nested-docs/int.spec.ts` (enabled `schedulePublish: true` on that project's `pages` fixture collection, already wired into `nestedDocsPlugin`). The test explicitly does `value: String(draft.id)` when queuing the job, matching what the real schedule-publish UI sends via client-side relationship-picker state. Verified locally against Postgres on this branch specifically: - Reverting just the `job.ts` fix reproduces the exact reported error: `Error: The following field is invalid: Breadcrumbs 1 > Doc` - With the fix, all 12 tests in `test/plugin-nested-docs/int.spec.ts` pass ``` PAYLOAD_DATABASE=postgres npx vitest run --project int test/plugin-nested-docs/int.spec.ts ```
1 parent 2061859 commit 7de11b2

3 files changed

Lines changed: 59 additions & 2 deletions

File tree

packages/payload/src/versions/schedule/job.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,15 @@ export const getSchedulePublishTask = ({
4646
}
4747

4848
if (input.doc) {
49+
// input.doc.value is always a string (#10481); coerce back to the real ID type.
50+
const idType =
51+
req.payload.collections[input.doc.relationTo]?.customIDType ??
52+
req.payload.db?.defaultIDType ??
53+
'text'
54+
const id = idType === 'number' ? Number(input.doc.value) : input.doc.value
55+
4956
await req.payload.update({
50-
id: input.doc.value,
57+
id,
5158
collection: input.doc.relationTo,
5259
data: {
5360
_status,

test/plugin-nested-docs/collections/Pages.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ export const Pages: CollectionConfig = {
1212
useAsTitle: 'fullTitle',
1313
},
1414
versions: {
15-
drafts: true,
15+
drafts: {
16+
schedulePublish: true,
17+
},
1618
},
1719
access: {
1820
read: () => true,

test/plugin-nested-docs/int.spec.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ArrayField, Payload, RelationshipField } from 'payload'
22

33
import path from 'path'
4+
import { wait } from 'payload/shared'
45
import { fileURLToPath } from 'url'
56
import { afterAll, afterEach, beforeAll, describe, expect, it } from 'vitest'
67

@@ -392,6 +393,53 @@ describe('@payloadcms/plugin-nested-docs', () => {
392393
})
393394
})
394395

396+
describe('scheduled publish', () => {
397+
it('should allow scheduled publish on a collection with a nested-docs breadcrumbs field', async () => {
398+
const draft = await payload.create({
399+
collection: 'pages',
400+
data: {
401+
title: 'Scheduled Page',
402+
slug: 'scheduled-page',
403+
},
404+
draft: true,
405+
})
406+
407+
expect(draft._status).toBe('draft')
408+
409+
const currentDate = new Date()
410+
411+
await payload.jobs.queue({
412+
input: {
413+
doc: {
414+
relationTo: 'pages',
415+
// The real schedule-publish UI always sends this as a string
416+
// (it comes from client-side form/relationship-picker state),
417+
// regardless of the collection's actual ID type. Cast explicitly
418+
// here to reproduce that instead of relying on the incidental
419+
// JS type of `draft.id` in a Local API call.
420+
value: String(draft.id),
421+
},
422+
},
423+
task: 'schedulePublish',
424+
waitUntil: new Date(currentDate.getTime() + 3000),
425+
})
426+
427+
await wait(4000)
428+
429+
await payload.jobs.run()
430+
431+
const retrieved = await payload.findByID({
432+
id: draft.id,
433+
collection: 'pages',
434+
draft: false,
435+
})
436+
437+
expect(retrieved._status).toBe('published')
438+
439+
await payload.delete({ collection: 'pages', id: draft.id })
440+
})
441+
})
442+
395443
describe('overrides', () => {
396444
let collection
397445
beforeAll(() => {

0 commit comments

Comments
 (0)