revert method

Future<DVRevertResult> revert(
  1. Object id, {
  2. required DVHistoryEntry to,
  3. DVRecord? base,
  4. String? actor,
  5. String? tenant,
})

Puts the record back as it was after to, as a new change.

A revert adds to the history rather than rewinding it: the entries in between are the audit. It takes the same version check as any write when base is given, and inside DV.transaction rolls back with it. Sensitive fields changed since to cannot be put back -- history holds that they changed, not what they held -- and are reported in DVRevertResult.unrestored.

Implementation

Future<DVRevertResult> revert(
  Object id, {
  required DVHistoryEntry to,
  DVRecord? base,
  String? actor,
  String? tenant,
}) async {
  tenant = _tenant(tenant);
  final DVRecord? current = await read(id, withDeleted: true);
  if (current == null) {
    throw StateError('$table[$id] does not exist, so it cannot be reverted.');
  }

  final List<DVHistoryEntry> entries = await history(id);
  final Map<String, Object?> state = Map<String, Object?>.of(current.values);
  final Set<String> unrestored = <String>{};
  for (final DVHistoryEntry entry in entries.reversed) {
    if (entry.version <= to.version) break;
    entry.changes.forEach((String field, DVFieldChange change) {
      if (change.redacted) {
        unrestored.add(field);
      } else {
        state[field] = change.from;
      }
    });
  }

  if (versioned && base != null && base.version != current.version) {
    throw DVConflictError(
      table: table,
      key: id,
      mine: state,
      theirs: current.values,
      base: base.values,
      expectedVersion: base.version,
      actualVersion: current.version,
    );
  }

  final DVRecord record = await _update(current, state,
      deletedAt: current.deletedAt, actor: actor, tenant: tenant);
  return DVRevertResult(record, unrestored: unrestored);
}