Hook Signatures#
Future<void> beforeDelete(T object, Jwt? jwt);
Future<void> afterDeleteSuccess(T object, Jwt? jwt);
Future<void> afterDeleteError(Object error, Jwt? jwt);
object in all delete hooks is the row being (or that was) deleted. The hooks run once per row: a delete that matches ten rows calls
beforeDelete ten times, then afterDeleteSuccess ten times.
beforeDelete#
Runs after rules pass, before the DELETE executes. Can abort by throwing. Nothing is deleted, and the client receives a
500 server error rather than your message:
@override
Future<void> beforeDelete(User user, Jwt? jwt) async {
final orders = await get.many(
tableName: 'orders',
where: Eq('user_id', user.id.value),
limit: 1,
) ?? [];
if (orders.isNotEmpty) {
throw Exception('Cannot delete user with open orders');
}
}
Use for: checking for dependencies before deleting.
A throwing hook also discards anything it queued with mutate, so "throw, then mark the row deleted" does not work here. For soft deletes, have clients update a
deletedAt column and deny delete in rules.
afterDeleteSuccess#
Runs after the DELETE commits. The row is gone — this hook cannot undo the deletion.
@override
Future<void> afterDeleteSuccess(Post post, Jwt? jwt) async {
// Cascade: delete all comments on this post
mutate.delete.many(
tableName: 'comments',
where: Eq('post_id', post.id.value),
);
}
Each cascaded deletion goes through the full pipeline — row rules and extensions on comments
also fire.
Use for: cascading deletes, releasing associated resources (photos, uploads), notifying external systems.
afterDeleteError#
Runs if the DELETE fails. Cannot make the operation succeed.
@override
Future<void> afterDeleteError(Object error, Jwt? jwt) async {
logger.error('Delete failed: $error');
}
Example: Cascade Delete#
import 'package:my_app/src/schemas/posts.dart';
import 'package:zonai_schema/zonai_schema.dart';
class PostExtensions extends Extension<Post> {
PostExtensions() : super(posts);
@override
Future<void> afterDeleteSuccess(Post post, Jwt? jwt) async {
// Delete comments first, which triggers CommentExtensions hooks
mutate.delete.many(
tableName: 'comments',
where: Eq('post_id', post.id.value),
);
// Also delete the post's cover photo if one exists
final photoId = post.photo;
if (photoId != null) {
mutate.delete.one(
tableName: '_photos',
where: Eq('id', photoId),
);
}
}
}
PostExtensions main() => PostExtensions();