Search the site
DATA
Answer an export or deletion request with one call, across every model that holds the person's data.
Declare on each model whose data it is and how long to keep it.
ON THIS PAGE
Declare the subject and retention
Export and erase a person's data
Honour a browser that says no to tracking
Do it from the CLI
Status
@DVModel(
subject: DVSubject.self,
retain: DVRetention.days(730, from: 'lastOrderAt', then: DVRetention.anonymize),
)
class const _Customer({
required final String id,
required final String name,
required final String lastOrderAt,
@DVModel.sensitiveField(onErase: DVErase.anonymize)
required final String email,
});Copy code to clipboard
DVSubject.self
Means: The row is the person
DVSubject.field('authorId')
Means: A column holds the person's id
DVSubject.through('orderId', parent: 'Order')
Means: The row belongs to a row that belongs to the person
retain is DVRetention.days(n, from:, then:) or DVRetention.indefinite.
A model with personal data and no retention gets a DV-PRIVACY-002 warning.
final DVExportArchive archive = await DV.Privacy.export(subject: 'user-1042');
final DVErasureResult result = await DV.Privacy.erase(
subject: 'user-1042',
reason: 'Deletion request from the account page',
);Copy code to clipboard
erase deletes rows, or keeps and anonymizes the ones a retention holds.
The result lists what was deleted, anonymized and kept, with a receipt.
It needs a database and DARTVEL_PRIVACY_KEY, 32 bytes or more as hex or base64.
Every generated route reads Sec-GPC before the handler runs, so a consent category declared tracking: true is already denied while the header is in force.
dvPrivacyOptOut is for what the application decides on top: what it personalises, and what it hands to somebody else.
It is a signal from the reader, so a page that ignores it is making a choice, and the log line says which was served.
@DVBackendFunction()
Future<Map<String, Object?>> _feed() async {
// The reader's browser sent `Sec-GPC: 1`, and every generated route reads
// it before the handler runs. Consent categories declared `tracking: true`
// are already denied while it is in force, so nothing here has to remember
// to check that. This is for what the application decides on top: what it
// personalises, and what it passes on to somebody else.
if (dvPrivacyOptOut) {
DV.log('Serving the unpersonalised feed', code: 'GPC');
return <String, Object?>{'items': await popular(), 'personalised': false};
}
return <String, Object?>{'items': await recommended(), 'personalised': true};
}Copy code to clipboard
dartvel privacy check
dartvel privacy export --subject user:1042 --out user-1042.json
dartvel privacy erase --subject user:1042 --reason "account closed"
dartvel privacy retention --planCopy code to clipboard
The commands read DATABASE_URL, or the SQLite file dartvel.database names. Erasing also needs DARTVEL_PRIVACY_KEY.
erase asks you to type yes at a terminal. In CI, or anywhere nobody can answer, pass --yes. It refuses before deleting anything when a table it reaches is missing.
export will not replace a file already at --out unless you pass --force.
retention only runs with --plan, which changes nothing. The sweep itself runs as a job.
Partial
Spec section: Data Compliance and Lifecycle
Planned work and implementation limits
Retention sweeps run on a fixed schedule.
Export is a single JSON file and does not decrypt encrypted fields.
File storage, cache and crash data are not reached.
FSL-1.1-MIT licensed. Built with Dartvel.
Dartvel is made by
To the bottom