Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

When using replaceOne, the createdAt timestamp is being updated like the updatedAt #14309

Closed
2 tasks done
Cefalu129 opened this issue Jan 30, 2024 · 2 comments · Fixed by #14337
Closed
2 tasks done

When using replaceOne, the createdAt timestamp is being updated like the updatedAt #14309

Cefalu129 opened this issue Jan 30, 2024 · 2 comments · Fixed by #14337
Labels
docs This issue is due to a mistake or omission in the mongoosejs.com documentation

Comments

@Cefalu129
Copy link

Cefalu129 commented Jan 30, 2024

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Mongoose version

7.6.4

Node.js version

18.16.14

MongoDB server version

7.0.2

Typescript version (if applicable)

5.0.4

Description

When using replaceOne to update a document, only the updatedAt timestamp should be modified to reflect the time of the update. However, the createdAt timestamp, which should remain constant to preserve the original creation time of the document, is also being updated to the same timestamp as updatedAt.

The docs says:

Mongoose will then set createdAt when the document is first inserted, and update updatedAt whenever you update the document using save(), updateOne(), updateMany(), findOneAndUpdate(), update(), replaceOne(), or bulkWrite().

The createdAt property is immutable

So I assume that createdAt timestamp should not be updated when using replaceOne.
It should preserve the original createdAt timestamp of the document.

Steps to Reproduce

Sorry for any inaccuracies, this is my first public issue.

Define a ProductModel:

const productSchema = new Schema<Product>({
  name: {
    type: String,
    required: true,
  },
  price: {
    type: Number,
    required: true,
    min: 0,
  },
}, {
  timestamps: true
});

const ProductModel = model<ProductDocument>('Product', productSchema);

Now, let's create a document and try to replace with some updated data.

const createdProduct = await ProductModel.create({ name: 'Product', price: 18 }); 
await ProductModel.replaceOne({ _id: createdProduct.id}, { name: 'Changed Product' });

Expected Behavior

I expect the create to create a document with the following data:

name: 'Product',
price: 19,
createdAt: *dateOfCreation*
updatedAt: *dateOfCreation*

While this is the expected replace result:

name: 'Changed Product',
createdAt: *dateOfUpdate*
updatedAt: *dateOfUpdate*

But this should be the actual replace behavior:

name: 'Changed Product',
createdAt: *dateOfCreation*
updatedAt: *dateOfUpdate*
@IslandRhythms IslandRhythms added the confirmed-bug We've confirmed this is a bug in Mongoose and will fix it. label Jan 31, 2024
@IslandRhythms
Copy link
Collaborator

const mongoose = require('mongoose');

const { Schema } = mongoose;

const productSchema = new Schema({
  name: {
    type: String,
    required: true,
  },
  price: {
    type: Number,
    required: true,
    min: 0,
  },
}, {
  timestamps: true
});

const ProductModel = mongoose.model('Product', productSchema);

async function run() {
  await mongoose.connect('mongodb://localhost:27017');
  await mongoose.connection.dropDatabase();

  const createdProduct = await ProductModel.create({ name: 'Product', price: 18 });
  console.log('what is createdProduct', createdProduct);

  const res = await ProductModel.replaceOne({ _id: createdProduct.id}, { name: 'Changed Product' });
  console.log('what is res', res);
  console.log(await ProductModel.findOne());
}

run();

@vkarpov15
Copy link
Collaborator

This is expected behavior, but something we need to make more clear in the docs.

Unfortunately MongoDB's replaceOne() means replace the whole document except _id, and there's no way to preserve createdAt when updating.

For preserving createdAt with replaceOne(), your best bet is to use the "pull the timestamp from the _id" trick:

schema.virtual('createdAt').get(function() {
  return this._id.getTimestamp();
});

This approach works presuming you always use the default _id

@vkarpov15 vkarpov15 added docs This issue is due to a mistake or omission in the mongoosejs.com documentation and removed confirmed-bug We've confirmed this is a bug in Mongoose and will fix it. labels Feb 6, 2024
vkarpov15 added a commit that referenced this issue Feb 6, 2024
vkarpov15 added a commit that referenced this issue Feb 7, 2024
docs(timestamps): clarify that `replaceOne()` and `findOneAndReplace()` overwrite timestamps
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs This issue is due to a mistake or omission in the mongoosejs.com documentation
Projects
None yet
3 participants