Policy Model
Policies are registered per entity and compiled before execution. That means typos and invalid shapes (e.g. dynamic anonymization replacements) surface at boot, not during a real DSR run.
delete
ts
fields: {
email: 'delete',
}- The shorthand
'delete'is normalized to{ strategy: 'delete' }. - Entity
rowLeveldefaults to'delete-fields'. - With the default Prisma adapter:
'delete-row'callsdeleteMany.'delete-fields'callsupdateManyand writesnullinto the configured delete fields.
anonymize
ts
fields: {
email: { strategy: 'anonymize', replacement: '[REDACTED]' },
}- Replacements must be static strings.
- Function replacements are rejected during policy compilation with
dsr_anonymize_dynamic_replacement. - Anonymization keeps the row in place — only the field value is overwritten.
retain
ts
fields: {
amount: {
strategy: 'retain',
legalBasis: 'tax:KR-basic-law-sec85',
until: '+7y',
},
}legalBasisis required.strictLegalBasis: true(module option) enforces ascheme:referenceshape — e.g.tax:KR-basic-law-sec85,gdpr:art17-3-b.untilaccepts relative durations (+7y) or absolute ISO timestamps.pseudonymizeis present in the type model but this package does not perform pseudonymization by itself — wire it through your own executor if needed.
Mixed strategies
When an entity mixes delete, anonymize, and retain, execution is intentionally conservative:
retainfields are preserved.- Delete fields are downgraded to field-level updates instead of row deletion.
- The entity is reported as
strategy: 'mixed'in erase stats. - Retained fields are recorded in
stats.retained.
This prevents retained fields from being dropped just because some other fields on the same row are deletable.
Full example
ts
{
policy: {
entityName: 'Invoice',
subjectField: 'customerId',
rowLevel: 'delete-row', // overridden to delete-fields because of retain
fields: {
customerName: {
strategy: 'retain',
legalBasis: 'tax:KR-basic-law-sec85',
until: '+7y',
},
customerEmail: {
strategy: 'anonymize',
replacement: '[REDACTED]',
},
internalNote: 'delete',
},
},
executor: fromPrisma({
delegate: prisma.invoice,
subjectField: 'customerId',
tenantField: 'tenantId',
}),
}For this entity an erase request will:
- Keep the row.
- Retain
customerName(reported instats.retainedwith legal basis and expiry). - Overwrite
customerEmailwith[REDACTED]. - Null
internalNote.