Skip to content

Recipe: Soft delete

Mondel supports opt-in soft delete on delete helpers.

Behavior

typescript
// Hard delete (default) — document removed
await db.users.deleteOne({ email: "a@b.com" });

// Soft delete — sets deletedAt: Date
// If schema timestamps enabled, also bumps updatedAt
await db.users.deleteOne({ email: "a@b.com" }, { soft: true });
await db.users.deleteMany({ role: "GUEST" }, { soft: true });
await db.users.deleteById(id, { soft: true });

deletedCount on the result reflects matched documents for soft deletes (how many docs were targeted by the filter).

Schema tip

Optionally model the field:

typescript
export const userSchema = defineSchema("users", {
  timestamps: true,
  fields: {
    email: s.string().required(),
    deletedAt: s.date(), // optional
  },
});

Choosing the field name

deletedAt is the default. Point Mondel at an existing column instead — useful when migrating a collection that already uses another name — with softDelete:

typescript
export const userSchema = defineSchema("users", {
  timestamps: true,
  softDelete: { field: "removedAt" },
  fields: {
    email: s.string().required(),
    removedAt: s.date(),
  },
});

// stamps removedAt, not deletedAt
await db.users.deleteOne({ email: "a@b.com" }, { soft: true });

The field is stamped whether or not you declare it in fields — Mondel does not require the schema to model it.

Always filter soft-deleted rows

Soft delete does not change default reads:

typescript
// Active only
await db.users.findMany({ deletedAt: { $exists: false } });

// Or explicit null
await db.users.findMany({ deletedAt: null });

Helper pattern:

typescript
const notDeleted = { deletedAt: { $exists: false } } as const;

await db.users.findMany({ ...notDeleted, role: "USER" });

Restore

typescript
await db.users.updateOne(
  { email: "a@b.com" },
  { $unset: { deletedAt: "" } }
);

$unset is not Zod-validated (operator passthrough).

Indexes

Partial index for active emails:

typescript
indexes: [
  {
    fields: { email: 1 },
    options: {
      unique: true,
      partialFilterExpression: { deletedAt: { $exists: false } },
    },
  },
],

Released under the MIT License. · llms.txt