ADR-0013 — Soft-delete operations are enabled from config, not ORM metadata
Status: accepted
Context
Soft delete resolves per entity: an entity carrying a delete-marker column (@DeleteDateColumn, or a column named by softDelete.field) is soft-deletable, everything else is hard-deleted. That decision needs entity metadata, which exists only at bootstrap.
Route generation, however, runs at class-decoration time, where no ORM metadata exists (ADR-0012) — and it generates one route per enabled registry entry, from a registry built by the same createOperationRegistry the engine uses. If enablement of restoreOne and purgeOne depended on metadata, the two registry builds would disagree: the engine would enable an operation the router never mapped. Making @kavo/nest read TypeORM metadata to close the gap is exactly the boundary ADR-0002 forbids.
Decision
restoreOne and purgeOne are enabled from entity config alone — the one input both registry builds share:
restoreOneis on when the config declares soft delete for the entity:softDelete: { strategy: "soft" }, or an explicitsoftDelete.field. Inheriting the built-instrategy: "auto"is not a declaration —autois answered by metadata, which decoration time cannot see.purgeOneis off until named:operations: { purgeOne: true }. Permanently destroying data is not a default.- Either may also be switched with
operations.<id>: true | falseor{ enabled }.
createCrud — the first moment config and metadata are both known — rejects the mismatch: enabling a soft-delete operation on an entity that resolves to a hard delete strategy is a ConfigurationException naming the fix.
The strategy itself stays metadata-driven. An entity with a @DeleteDateColumn and no config still soft-deletes, and its reads still exclude deleted rows; only the extra routes wait on the declaration.
Consequences
- Both registry builds reach the same answer from the same input; the route table and the service surface cannot drift.
@kavo/nestneeds no ORM knowledge, and the generator itself needed no change to support soft delete — restore/purge appeared by enabling entries.- Cost: zero-config soft delete is not entirely zero-config. An entity gets soft deletes and exclusion for free, but its restore route takes one line (
softDelete: { strategy: "soft" }). Stating it is also the honest signal that un-deleting is now part of the entity's public API. - If a later change moves route generation behind a bootstrap-time registration (a DX option), this rule can relax to pure metadata detection without changing any config that exists today — declaring soft delete stays valid either way.