delete method
Future<bool>
delete({
- required String id,
- required String userId,
- DataSource source = DataSource.local,
- bool forceRemoteSync = false,
- DeleteBehavior? behavior,
Deletes an entity by its ID from all local and remote adapters.
The behavior parameter allows overriding the global DatumConfig.deleteBehavior
for this specific delete operation. If null, the global config value is used.
Implementation
Future<bool> delete({
required String id,
required String userId,
DataSource source = DataSource.local,
bool forceRemoteSync = false,
DeleteBehavior? behavior,
}) async {
_ensureInitialized();
// Check for user switch before proceeding.
await _syncEngineInstance.checkForUserSwitch(userId);
final existing = await localAdapter.read(id, userId: userId);
if (existing == null) {
_logger.debug(
'Entity $id does not exist for user $userId, skipping delete',
);
return false;
}
_logger.debug('Notifying observers of onDeleteStart for $id');
for (final weakObserver in _localObservers) {
weakObserver.target?.onDeleteStart(id);
}
for (final weakObserver in _globalObservers) {
weakObserver.target?.onDeleteStart(id);
}
// Use the provided behavior or fall back to the configured delete behavior
final effectiveBehavior = behavior ?? config.deleteBehavior;
T entityForEvent = existing;
if (effectiveBehavior == DeleteBehavior.softDelete) {
// Soft delete: mark the entity as deleted. The tombstone bumps the
// version like any other write so version-based conflict detection
// sees it as newer than the live row it replaces.
final delta = <String, dynamic>{
'isDeleted': true,
'modifiedAt': DateTime.now().toIso8601String(),
'version': existing.version + 1,
};
// Only carry the clock when the entity actually serializes one —
// adding it unconditionally wrote a phantom column (which throws on
// strict fixed-schema adapters).
if (deviceId != null && existing.toDatumMap().containsKey('vectorClock')) {
final currentClock = existing.vectorClock ?? const VectorClock();
delta['vectorClock'] = currentClock.increment(deviceId!).toMap();
}
_metadataHashCache.invalidate(userId);
entityForEvent = await localAdapter.patch(
id: id,
delta: delta,
userId: userId,
);
_recordLocalEcho(id, entityForEvent);
} else {
// Hard delete: physically remove the entity
final deleted = await localAdapter.delete(id, userId: userId);
_recordLocalEcho(id, null);
_recordLocalEcho(id, existing);
_metadataHashCache.invalidate(userId);
if (!deleted) {
_logger.warn('Local adapter failed to delete entity $id');
// Notify observers of the failure before returning.
for (final weakObserver in _localObservers) {
weakObserver.target?.onDeleteEnd(id, success: false);
}
for (final weakObserver in _globalObservers) {
weakObserver.target?.onDeleteEnd(id, success: false);
}
return false;
}
}
_logger.debug('Notifying observers of onDeleteEnd for $id');
for (final weakObserver in _localObservers) {
weakObserver.target?.onDeleteEnd(id, success: true);
}
for (final weakObserver in _globalObservers) {
weakObserver.target?.onDeleteEnd(id, success: true);
}
if (source == DataSource.local || forceRemoteSync) {
final operation = _createOperation(
userId: userId,
type: DatumOperationType.delete,
entityId: id,
);
// Calculate size for delete operation (it's small, just the ID)
final payload = {'id': id};
final size = (await _isolateHelper.computeJsonEncode(payload)).length;
await _queueManager.enqueue(operation.copyWith(sizeInBytes: size));
}
_eventController.add(
DataChangeEvent<T>(
userId: userId,
data: entityForEvent,
changeType: ChangeType.deleted,
source: source,
),
);
// Invalidate caches for the deleted entity
_cacheCoordinator.invalidateCachesForEntity(entityForEvent);
return true;
}