Skip to content

✨ feat(uploads): idempotent remove + age-graced sweep of multi-path-unreferenced GridFS blobs #4013

Description

@PierreBrisorgueil

What — two additions to the uploads module, promoting a pattern proven in a downstream consumer:

  1. remove() becomes idempotent: a lookup that matches no record (e.g. a filename already deleted by a previous pass) is a debug-logged NO-OP returning { deletedCount: 0, notFound: true } — not a thrown error. A genuine GridFS bucket failure on an existing file still throws.
  2. New sweepUnreferenced(kind, collection, paths, minAgeMs): sweep GridFS blobs of a given kind that are unreferenced by ANY of several reference paths on another collection (scalar or array-of-subdocuments paths, normalised before the OR check), respecting a minimum-age grace window so a blob written just before its referencing document persists is never swept.

Why — retention/cleanup jobs that re-run over the same window need remove() to treat "already gone" as success (otherwise every re-pass logs phantom errors). And purge() cannot express "referenced by path A OR B": its $lookup equality join is right for exactly one reference key; extending it would either lose the indexed join for existing callers or reintroduce per-row sub-queries. Multi-path referencing is generic (any consumer storing one blob referenced from several fields).

Scopemodules/uploads/repositories/uploads.repository.js (+ service wiring if the repo layer is not exported directly):

  • remove() no-op semantics as above (logger.debug, not warn).
  • sweepUnreferenced: ONE streaming pass over the referencing collection building an in-memory Set of referenced filenames across all paths, then one streaming pass over age-eligible candidate uploads checking Set membership. Deliberately a separate function from purge() (different, non-interchangeable query strategies — document that in the JSDoc).
  • Unit tests: no-op remove (missing record) · genuine failure still throws · sweep respects the grace window · scalar path · array-of-subdocs path · blob referenced by only ONE of several paths is KEPT (the data-loss guard).

Scope: validated 2026-08-04

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions