Skip to content

Commit 957867f

Browse files
authored
fix: ensures generated IDs persist on create (#10089)
IDs that are supplied directly through the API, such as client-side generated IDs when adding new blocks and array rows, are overwritten on create. This is because when adding blocks or array rows on the client, their IDs are generated first before being sent to the server for processing. Then when the server receives this data, it incorrectly overrides them to ensure they are unique when using relational DBs. But this only needs to happen when no ID was supplied on create, or specifically when duplicating documents via the `beforeDuplicate` hook.
1 parent 4e95353 commit 957867f

File tree

7 files changed

+291
-402
lines changed

7 files changed

+291
-402
lines changed

packages/payload/src/fields/baseFields/baseIDField.ts

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,8 @@ export const baseIDField: TextField = {
1313
},
1414
defaultValue: () => new ObjectId().toHexString(),
1515
hooks: {
16-
beforeChange: [
17-
({ operation, value }) => {
18-
// If creating new doc, need to disregard any
19-
// ids that have been passed in because they will cause
20-
// primary key unique conflicts in relational DBs
21-
if (!value || (operation === 'create' && value)) {
22-
return new ObjectId().toHexString()
23-
}
24-
25-
return value
26-
},
27-
],
28-
beforeDuplicate: [
29-
() => {
30-
return new ObjectId().toHexString()
31-
},
32-
],
16+
beforeChange: [({ value }) => value || new ObjectId().toHexString()],
17+
beforeDuplicate: [() => new ObjectId().toHexString()],
3318
},
3419
label: 'ID',
3520
}

test/database/config.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,31 @@ export default buildConfigWithDefaults({
5454
],
5555
},
5656
},
57+
{
58+
name: 'arrayWithIDs',
59+
type: 'array',
60+
fields: [
61+
{
62+
name: 'text',
63+
type: 'text',
64+
},
65+
],
66+
},
67+
{
68+
name: 'blocksWithIDs',
69+
type: 'blocks',
70+
blocks: [
71+
{
72+
slug: 'block',
73+
fields: [
74+
{
75+
name: 'text',
76+
type: 'text',
77+
},
78+
],
79+
},
80+
],
81+
},
5782
],
5883
hooks: {
5984
beforeOperation: [

test/database/int.spec.ts

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
migrateRelationshipsV2_V3,
88
migrateVersionsV1_V2,
99
} from '@payloadcms/db-mongodb/migration-utils'
10-
import { desc, type Table } from 'drizzle-orm'
10+
import { type Table } from 'drizzle-orm'
1111
import * as drizzlePg from 'drizzle-orm/pg-core'
1212
import * as drizzleSqlite from 'drizzle-orm/sqlite-core'
1313
import fs from 'fs'
@@ -136,6 +136,63 @@ describe('database', () => {
136136
it('should not accidentally treat nested id fields as custom id', () => {
137137
expect(payload.collections['fake-custom-ids'].customIDType).toBeUndefined()
138138
})
139+
140+
it('should not overwrite supplied block and array row IDs on create', async () => {
141+
const arrayRowID = '67648ed5c72f13be6eacf24e'
142+
const blockID = '6764de9af79a863575c5f58c'
143+
144+
const doc = await payload.create({
145+
collection: 'posts',
146+
data: {
147+
title: 'test',
148+
arrayWithIDs: [
149+
{
150+
id: arrayRowID,
151+
},
152+
],
153+
blocksWithIDs: [
154+
{
155+
blockType: 'block',
156+
id: blockID,
157+
},
158+
],
159+
},
160+
})
161+
162+
expect(doc.arrayWithIDs[0].id).toStrictEqual(arrayRowID)
163+
expect(doc.blocksWithIDs[0].id).toStrictEqual(blockID)
164+
})
165+
166+
it('should overwrite supplied block and array row IDs on duplicate', async () => {
167+
const arrayRowID = '6764deb5201e9e36aeba3b6c'
168+
const blockID = '6764dec58c68f337a758180c'
169+
170+
const doc = await payload.create({
171+
collection: 'posts',
172+
data: {
173+
title: 'test',
174+
arrayWithIDs: [
175+
{
176+
id: arrayRowID,
177+
},
178+
],
179+
blocksWithIDs: [
180+
{
181+
blockType: 'block',
182+
id: blockID,
183+
},
184+
],
185+
},
186+
})
187+
188+
const duplicate = await payload.duplicate({
189+
collection: 'posts',
190+
id: doc.id,
191+
})
192+
193+
expect(duplicate.arrayWithIDs[0].id).not.toStrictEqual(arrayRowID)
194+
expect(duplicate.blocksWithIDs[0].id).not.toStrictEqual(blockID)
195+
})
139196
})
140197

141198
describe('timestamps', () => {

test/database/payload-types.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,20 @@ export interface Post {
9090
title: string;
9191
hasTransaction?: boolean | null;
9292
throwAfterChange?: boolean | null;
93+
arrayWithIDs?:
94+
| {
95+
text?: string | null;
96+
id?: string | null;
97+
}[]
98+
| null;
99+
blocksWithIDs?:
100+
| {
101+
text?: string | null;
102+
id?: string | null;
103+
blockName?: string | null;
104+
blockType: 'block';
105+
}[]
106+
| null;
93107
updatedAt: string;
94108
createdAt: string;
95109
}
@@ -428,6 +442,23 @@ export interface PostsSelect<T extends boolean = true> {
428442
title?: T;
429443
hasTransaction?: T;
430444
throwAfterChange?: T;
445+
arrayWithIDs?:
446+
| T
447+
| {
448+
text?: T;
449+
id?: T;
450+
};
451+
blocksWithIDs?:
452+
| T
453+
| {
454+
block?:
455+
| T
456+
| {
457+
text?: T;
458+
id?: T;
459+
blockName?: T;
460+
};
461+
};
431462
updatedAt?: T;
432463
createdAt?: T;
433464
}

test/fields/int.spec.ts

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2285,23 +2285,6 @@ describe('Fields', () => {
22852285
expect(blockFieldsFail.docs).toHaveLength(0)
22862286
})
22872287

2288-
it('should create when existing block ids are used', async () => {
2289-
const blockFields = await payload.find({
2290-
collection: 'block-fields',
2291-
limit: 1,
2292-
})
2293-
const [doc] = blockFields.docs
2294-
2295-
const result = await payload.create({
2296-
collection: 'block-fields',
2297-
data: {
2298-
...doc,
2299-
},
2300-
})
2301-
2302-
expect(result.id).toBeDefined()
2303-
})
2304-
23052288
it('should filter based on nested block fields', async () => {
23062289
await payload.create({
23072290
collection: 'block-fields',

0 commit comments

Comments
 (0)