fix: ensure updates to createdAt and updatedAt are respected (#13335)
Previously, when manually setting `createdAt` or `updatedAt` in a
`payload.db.*` or `payload.*` operation, the value may have been
ignored. In some cases it was _impossible_ to change the `updatedAt`
value, even when using direct db adapter calls. On top of that, this
behavior sometimes differed between db adapters. For example, mongodb
did accept `updatedAt` when calling `payload.db.updateVersion` -
postgres ignored it.
This PR changes this behavior to consistently respect `createdAt` and
`updatedAt` values for `payload.db.*` operations.
For `payload.*` operations, this also works with the following
exception:
- update operations do no respect `updatedAt`, as updates are commonly
performed by spreading the old data, e.g. `payload.update({ data:
{...oldData} })` - in these cases, we usually still want the `updatedAt`
to be updated. If you need to get around this, you can use the
`payload.db.updateOne` operation instead.
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1210919646303994
This commit is contained in:
@@ -36,6 +36,16 @@ export const getConfig: () => Partial<Config> = () => ({
|
||||
},
|
||||
},
|
||||
collections: [
|
||||
{
|
||||
slug: 'noTimeStamps',
|
||||
timestamps: false,
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'title',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: 'categories',
|
||||
versions: { drafts: true },
|
||||
|
||||
@@ -49,6 +49,8 @@ const collection = postsSlug
|
||||
const title = 'title'
|
||||
process.env.PAYLOAD_CONFIG_PATH = path.join(dirname, 'config.ts')
|
||||
|
||||
const itMongo = process.env.PAYLOAD_DATABASE?.startsWith('mongodb') ? it : it.skip
|
||||
|
||||
describe('database', () => {
|
||||
beforeAll(async () => {
|
||||
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
||||
@@ -224,6 +226,12 @@ describe('database', () => {
|
||||
const createdAtDate = new Date(result.createdAt)
|
||||
|
||||
expect(createdAtDate.getMilliseconds()).toBeDefined()
|
||||
|
||||
// Cleanup, as this test suite does not use clearAndSeedEverything
|
||||
await payload.db.deleteMany({
|
||||
collection: postsSlug,
|
||||
where: {},
|
||||
})
|
||||
})
|
||||
|
||||
it('should allow createdAt to be set in create', async () => {
|
||||
@@ -243,9 +251,15 @@ describe('database', () => {
|
||||
|
||||
expect(result.createdAt).toStrictEqual(createdAt)
|
||||
expect(doc.createdAt).toStrictEqual(createdAt)
|
||||
|
||||
// Cleanup, as this test suite does not use clearAndSeedEverything
|
||||
await payload.db.deleteMany({
|
||||
collection: postsSlug,
|
||||
where: {},
|
||||
})
|
||||
})
|
||||
|
||||
it('updatedAt cannot be set in create', async () => {
|
||||
it('should allow updatedAt to be set in create', async () => {
|
||||
const updatedAt = new Date('2022-01-01T00:00:00.000Z').toISOString()
|
||||
const result = await payload.create({
|
||||
collection: postsSlug,
|
||||
@@ -255,8 +269,302 @@ describe('database', () => {
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.updatedAt).not.toStrictEqual(updatedAt)
|
||||
expect(result.updatedAt).toStrictEqual(updatedAt)
|
||||
|
||||
// Cleanup, as this test suite does not use clearAndSeedEverything
|
||||
await payload.db.deleteMany({
|
||||
collection: postsSlug,
|
||||
where: {},
|
||||
})
|
||||
})
|
||||
it('should allow createdAt to be set in update', async () => {
|
||||
const post = await payload.create({
|
||||
collection: postsSlug,
|
||||
data: {
|
||||
title: 'hello',
|
||||
},
|
||||
})
|
||||
const createdAt = new Date('2021-01-01T00:00:00.000Z').toISOString()
|
||||
|
||||
const result: any = await payload.db.updateOne({
|
||||
collection: postsSlug,
|
||||
id: post.id,
|
||||
data: {
|
||||
createdAt,
|
||||
},
|
||||
})
|
||||
|
||||
const doc = await payload.findByID({
|
||||
id: result.id,
|
||||
collection: postsSlug,
|
||||
})
|
||||
|
||||
expect(doc.createdAt).toStrictEqual(createdAt)
|
||||
|
||||
// Cleanup, as this test suite does not use clearAndSeedEverything
|
||||
await payload.db.deleteMany({
|
||||
collection: postsSlug,
|
||||
where: {},
|
||||
})
|
||||
})
|
||||
|
||||
it('should allow updatedAt to be set in update', async () => {
|
||||
const post = await payload.create({
|
||||
collection: postsSlug,
|
||||
data: {
|
||||
title: 'hello',
|
||||
},
|
||||
})
|
||||
const updatedAt = new Date('2021-01-01T00:00:00.000Z').toISOString()
|
||||
|
||||
const result: any = await payload.db.updateOne({
|
||||
collection: postsSlug,
|
||||
id: post.id,
|
||||
data: {
|
||||
updatedAt,
|
||||
},
|
||||
})
|
||||
|
||||
const doc = await payload.findByID({
|
||||
id: result.id,
|
||||
collection: postsSlug,
|
||||
})
|
||||
|
||||
expect(doc.updatedAt).toStrictEqual(updatedAt)
|
||||
|
||||
// Cleanup, as this test suite does not use clearAndSeedEverything
|
||||
await payload.db.deleteMany({
|
||||
collection: postsSlug,
|
||||
where: {},
|
||||
})
|
||||
})
|
||||
|
||||
it('should allow createdAt to be set in updateVersion', async () => {
|
||||
const category = await payload.create({
|
||||
collection: 'categories',
|
||||
data: {
|
||||
title: 'hello',
|
||||
},
|
||||
})
|
||||
await payload.update({
|
||||
collection: 'categories',
|
||||
id: category.id,
|
||||
data: {
|
||||
title: 'hello2',
|
||||
},
|
||||
})
|
||||
const versions = await payload.findVersions({
|
||||
collection: 'categories',
|
||||
depth: 0,
|
||||
sort: '-createdAt',
|
||||
})
|
||||
const createdAt = new Date('2021-01-01T00:00:00.000Z').toISOString()
|
||||
|
||||
for (const version of versions.docs) {
|
||||
await payload.db.updateVersion({
|
||||
id: version.id,
|
||||
collection: 'categories',
|
||||
versionData: {
|
||||
...version.version,
|
||||
createdAt,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const updatedVersions = await payload.findVersions({
|
||||
collection: 'categories',
|
||||
depth: 0,
|
||||
sort: '-createdAt',
|
||||
})
|
||||
expect(updatedVersions.docs).toHaveLength(2)
|
||||
for (const version of updatedVersions.docs) {
|
||||
expect(version.createdAt).toStrictEqual(createdAt)
|
||||
}
|
||||
|
||||
// Cleanup, as this test suite does not use clearAndSeedEverything
|
||||
await payload.db.deleteMany({
|
||||
collection: 'categories',
|
||||
where: {},
|
||||
})
|
||||
await payload.db.deleteVersions({
|
||||
collection: 'categories',
|
||||
where: {},
|
||||
})
|
||||
})
|
||||
|
||||
it('should allow updatedAt to be set in updateVersion', async () => {
|
||||
const category = await payload.create({
|
||||
collection: 'categories',
|
||||
data: {
|
||||
title: 'hello',
|
||||
},
|
||||
})
|
||||
await payload.update({
|
||||
collection: 'categories',
|
||||
id: category.id,
|
||||
data: {
|
||||
title: 'hello2',
|
||||
},
|
||||
})
|
||||
const versions = await payload.findVersions({
|
||||
collection: 'categories',
|
||||
depth: 0,
|
||||
sort: '-createdAt',
|
||||
})
|
||||
const updatedAt = new Date('2021-01-01T00:00:00.000Z').toISOString()
|
||||
|
||||
for (const version of versions.docs) {
|
||||
await payload.db.updateVersion({
|
||||
id: version.id,
|
||||
collection: 'categories',
|
||||
versionData: {
|
||||
...version.version,
|
||||
updatedAt,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const updatedVersions = await payload.findVersions({
|
||||
collection: 'categories',
|
||||
depth: 0,
|
||||
sort: '-updatedAt',
|
||||
})
|
||||
expect(updatedVersions.docs).toHaveLength(2)
|
||||
for (const version of updatedVersions.docs) {
|
||||
expect(version.updatedAt).toStrictEqual(updatedAt)
|
||||
}
|
||||
|
||||
// Cleanup, as this test suite does not use clearAndSeedEverything
|
||||
await payload.db.deleteMany({
|
||||
collection: 'categories',
|
||||
where: {},
|
||||
})
|
||||
await payload.db.deleteVersions({
|
||||
collection: 'categories',
|
||||
where: {},
|
||||
})
|
||||
})
|
||||
|
||||
async function noTimestampsTestLocalAPI() {
|
||||
const createdDoc: any = await payload.create({
|
||||
collection: 'noTimeStamps',
|
||||
data: {
|
||||
title: 'hello',
|
||||
},
|
||||
})
|
||||
expect(createdDoc.createdAt).toBeUndefined()
|
||||
expect(createdDoc.updatedAt).toBeUndefined()
|
||||
|
||||
const updated: any = await payload.update({
|
||||
collection: 'noTimeStamps',
|
||||
id: createdDoc.id,
|
||||
data: {
|
||||
title: 'updated',
|
||||
},
|
||||
})
|
||||
expect(updated.createdAt).toBeUndefined()
|
||||
expect(updated.updatedAt).toBeUndefined()
|
||||
|
||||
const date = new Date('2021-01-01T00:00:00.000Z').toISOString()
|
||||
const createdDocWithTimestamps: any = await payload.create({
|
||||
collection: 'noTimeStamps',
|
||||
data: {
|
||||
title: 'hello',
|
||||
createdAt: date,
|
||||
updatedAt: date,
|
||||
},
|
||||
})
|
||||
expect(createdDocWithTimestamps.createdAt).toBeUndefined()
|
||||
expect(createdDocWithTimestamps.updatedAt).toBeUndefined()
|
||||
|
||||
const updatedDocWithTimestamps: any = await payload.update({
|
||||
collection: 'noTimeStamps',
|
||||
id: createdDocWithTimestamps.id,
|
||||
data: {
|
||||
title: 'updated',
|
||||
createdAt: date,
|
||||
updatedAt: date,
|
||||
},
|
||||
})
|
||||
expect(updatedDocWithTimestamps.createdAt).toBeUndefined()
|
||||
expect(updatedDocWithTimestamps.updatedAt).toBeUndefined()
|
||||
}
|
||||
|
||||
async function noTimestampsTestDB(aa) {
|
||||
const createdDoc: any = await payload.db.create({
|
||||
collection: 'noTimeStamps',
|
||||
data: {
|
||||
title: 'hello',
|
||||
},
|
||||
})
|
||||
expect(createdDoc.createdAt).toBeUndefined()
|
||||
expect(createdDoc.updatedAt).toBeUndefined()
|
||||
|
||||
const updated: any = await payload.db.updateOne({
|
||||
collection: 'noTimeStamps',
|
||||
id: createdDoc.id,
|
||||
data: {
|
||||
title: 'updated',
|
||||
},
|
||||
})
|
||||
expect(updated.createdAt).toBeUndefined()
|
||||
expect(updated.updatedAt).toBeUndefined()
|
||||
|
||||
const date = new Date('2021-01-01T00:00:00.000Z').toISOString()
|
||||
const createdDocWithTimestamps: any = await payload.db.create({
|
||||
collection: 'noTimeStamps',
|
||||
data: {
|
||||
title: 'hello',
|
||||
createdAt: date,
|
||||
updatedAt: date,
|
||||
},
|
||||
})
|
||||
expect(createdDocWithTimestamps.createdAt).toBeUndefined()
|
||||
expect(createdDocWithTimestamps.updatedAt).toBeUndefined()
|
||||
|
||||
const updatedDocWithTimestamps: any = await payload.db.updateOne({
|
||||
collection: 'noTimeStamps',
|
||||
id: createdDocWithTimestamps.id,
|
||||
data: {
|
||||
title: 'updated',
|
||||
createdAt: date,
|
||||
updatedAt: date,
|
||||
},
|
||||
})
|
||||
expect(updatedDocWithTimestamps.createdAt).toBeUndefined()
|
||||
expect(updatedDocWithTimestamps.updatedAt).toBeUndefined()
|
||||
}
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('ensure timestamps are not created in update or create when timestamps are disabled', async () => {
|
||||
await noTimestampsTestLocalAPI()
|
||||
})
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('ensure timestamps are not created in db adapter update or create when timestamps are disabled', async () => {
|
||||
await noTimestampsTestDB(true)
|
||||
})
|
||||
|
||||
itMongo(
|
||||
'ensure timestamps are not created in update or create when timestamps are disabled even with allowAdditionalKeys true',
|
||||
async () => {
|
||||
const originalAllowAdditionalKeys = payload.db.allowAdditionalKeys
|
||||
payload.db.allowAdditionalKeys = true
|
||||
await noTimestampsTestLocalAPI()
|
||||
payload.db.allowAdditionalKeys = originalAllowAdditionalKeys
|
||||
},
|
||||
)
|
||||
|
||||
itMongo(
|
||||
'ensure timestamps are not created in db adapter update or create when timestamps are disabled even with allowAdditionalKeys true',
|
||||
async () => {
|
||||
const originalAllowAdditionalKeys = payload.db.allowAdditionalKeys
|
||||
payload.db.allowAdditionalKeys = true
|
||||
await noTimestampsTestDB()
|
||||
|
||||
payload.db.allowAdditionalKeys = originalAllowAdditionalKeys
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
describe('Data strictness', () => {
|
||||
|
||||
@@ -67,6 +67,7 @@ export interface Config {
|
||||
};
|
||||
blocks: {};
|
||||
collections: {
|
||||
noTimeStamps: NoTimeStamp;
|
||||
categories: Category;
|
||||
simple: Simple;
|
||||
'categories-custom-id': CategoriesCustomId;
|
||||
@@ -94,6 +95,7 @@ export interface Config {
|
||||
};
|
||||
collectionsJoins: {};
|
||||
collectionsSelect: {
|
||||
noTimeStamps: NoTimeStampsSelect<false> | NoTimeStampsSelect<true>;
|
||||
categories: CategoriesSelect<false> | CategoriesSelect<true>;
|
||||
simple: SimpleSelect<false> | SimpleSelect<true>;
|
||||
'categories-custom-id': CategoriesCustomIdSelect<false> | CategoriesCustomIdSelect<true>;
|
||||
@@ -163,6 +165,14 @@ export interface UserAuthOperations {
|
||||
password: string;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "noTimeStamps".
|
||||
*/
|
||||
export interface NoTimeStamp {
|
||||
id: string;
|
||||
title?: string | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "categories".
|
||||
@@ -617,6 +627,10 @@ export interface User {
|
||||
export interface PayloadLockedDocument {
|
||||
id: string;
|
||||
document?:
|
||||
| ({
|
||||
relationTo: 'noTimeStamps';
|
||||
value: string | NoTimeStamp;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'categories';
|
||||
value: string | Category;
|
||||
@@ -743,6 +757,13 @@ export interface PayloadMigration {
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "noTimeStamps_select".
|
||||
*/
|
||||
export interface NoTimeStampsSelect<T extends boolean = true> {
|
||||
title?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "categories_select".
|
||||
|
||||
Reference in New Issue
Block a user