mongoose icon indicating copy to clipboard operation
mongoose copied to clipboard

Feature: Soft-Delete

Open mhombach opened this issue 6 years ago • 10 comments

Do you want to request a feature or report a bug? feature

What is the current behavior? Deleting a document in mongoose really deletes it from the database.

What is the expected behavior? We should be able to enable an option to let documents be "soft-deleted". Meaning, there should be an attribute like _deleted on each document and deleting a document by the normal mongoose way will jsut mark the document as deleted. Find-queries will, by default, all skip/exclude documents that are marked as "deleted". By options, one can still hard-delete documents or find-query including or only deleted documents. There are 2-3 plugins for that but they are not maintained in any way, nor do they have a considerable amount of downloads and their last commit is over 3 years old. I, and many others that will be working with mongoose in professional environments, will love that feature.

Please mention your node.js, mongoose and MongoDB version. All latest versions

mhombach avatar Dec 06 '18 14:12 mhombach

How about an official plugin? Like the ones on http://plugins.mongoosejs.io

vkarpov15 avatar Dec 12 '18 14:12 vkarpov15

https://github.com/Automattic/mongoose/issues/7304#issuecomment-446616920: Is it under development or what? 'Cause I don't see it in the provided link.

msrumon avatar Jan 21 '21 02:01 msrumon

@msrumon we don't have an official plugin yet, nor do we have plans to build one currently.

vkarpov15 avatar Jan 26 '21 23:01 vkarpov15

Hello,

I've tried writing this plugin to fit our internal use. I've something working but I've questions that are TypeScript related. We have extended the Document type to match the new contract with soft deletion with the following code:

type TWithSoftDeleted = {
  isDeleted: boolean;
  deletedAt: Date | null;
};

type TDocument = TWithSoftDeleted & Document;

Our goal is then to use this document type to validate the procedure made in the plugin but we have errors in the typing. I've tried to use the documentation here related to TypeScript, but it was not 100% clear what the expected types are 😕

const excludeDeletedInFindQueries = async function (
    this: Query<any, TDocument>, // is this correct ?
    next: HookNextFunction
  ) {
    // Property 'includeSoftDeleted' does not exist on type 'QueryOptions'. How to make it available ?
    const { includeSoftDeleted } = this.getOptions();

    if (includeSoftDeleted !== true) {
      this.where({ isDeleted: { $in: [null, false] } });
    }

    next();
  };

Do you have any insight regarding this?

axelvaindal avatar Jun 11 '21 12:06 axelvaindal

@axelvaindal unfortunately right now you would have to manually cast:

const { includeSoftDeleted } = q.getOptions() as { includeSoftDeleted?: boolean };

We'll figure out a workaround for this for future versions.

vkarpov15 avatar Jun 14 '21 18:06 vkarpov15

Hello @vkarpov15 👋

I'm trying to update my code to use mongoose v6, but it seems typings have changed from the previous 5.X version. I've removed the @types/mongoose and tried to rely only on internal typings from now on.

Is there a way to find relevant typings for plugins apart from reading index.d.ts in the repository? More specifically, I'm trying to apply pre hooks to these queries:

  const findQueries = [
    "count",
    "find",
    "findOne",
    "findOneAndDelete",
    "findOneAndRemove",
    "findOneAndUpdate",
    "update",
    "updateOne",
    "updateMany",
  ];

Then, I try to exclude deleted in these queries but I can't manage to correctly type the hook, and the previous HookNextFunction type is no longer functioning. Could you help me correct the typings here? Thanks in advance for your help 🙏

const excludeDeletedInQueries = async function (
    this: FilterQuery<any>, // is this correct?
    next: any // what should be used here?
  ) {
    const { includeSoftDeleted } = this.getOptions();

    if (includeSoftDeleted !== true) {
      this.where({ isDeleted: { $in: [null, false] } });
    }

    next();
  };
  
  const excludeDeletedInAggregateMiddleware = async function (
    this: Aggregate<any>, // is this correct?
    next: any // what should be used here?
  ) {
    this.pipeline().unshift({ $match: { isDeleted: { $in: [null, false] } } });
    next();
  };


softDeletedExcludedQueries.forEach((query) => {
    schema.pre(query, excludeDeletedInQueries);
  });
  
  schema.pre("aggregate", excludeDeletedInAggregateMiddleware);

axelvaindal avatar Nov 07 '21 16:11 axelvaindal

HookNextFunction looks like this:

  interface HookNextFunction {
    (error?: Error): any;
  }

We removed it in Mongoose 6 because it isn't terribly useful. next: (error?: Error) => any should work.

Your plugin implementation looks correct :+1:

vkarpov15 avatar Nov 11 '21 19:11 vkarpov15

Soft delete plugin => https://www.npmjs.com/package/soft-delete-mongoose-plugin

A simple and friendly soft delete plugin for mongoose,implementation using TS. Methods were added and overridden on mongoose model to realize soft deletion logic.

GO-DIE avatar Apr 07 '22 02:04 GO-DIE

Would be good if mongoose have something like https://sequelize.org/docs/v6/core-concepts/paranoid, so we can do:

const userSchema = new mongoose.Schema(
  {
    // schema
  },
  {
    timestamps: true,
    paranoid: true
  }
);

And whenever we perform any kinds of delete, i.e., deleteMany, deleteOne, findByIdAndDelete, findOneAndDelete, etc, it will use deletedAt field, and when we query, we have the option to either include or exclude documents with deletedAt -- should default to exclude!

aprilmintacpineda avatar Apr 21 '23 04:04 aprilmintacpineda

user land ?

sibelius avatar Jan 18 '24 18:01 sibelius