Update & Delete

Modify existing records with partial updates, or remove them — one at a time or in bulk.

Update a record

Updates are partial — only the fields you provide are changed. Existing fields not included in the update are preserved.

lib/database.dart
Dart
final updated = await Koolbase.db
  .doc('record-uuid-here')
  .update({
    'title': 'Updated title',
    'published': true,
  });

// Only 'title' and 'published' changed
// All other fields remain unchanged
print(updated.data['title']);       // Updated title
print(updated.data['body']);        // original body still here
print(updated.updatedAt);           // new timestamp

Partial updates

Updates merge with the existing record. To remove a field, you would need to set it to null explicitly. There is no unset operation — the JSONB data column is merged on update.

Upsert: update or insert

Insert a record, or update the existing one that matches a filter — in a single call. The server decides the outcome: exactly one match updates that record, no match inserts a new one (seeded with the match fields), and more than one match is an error.

lib/database.dart
Dart
final result = await Koolbase.db.upsert(
  collection: 'profiles',
  match: {'user_id': userId},
  data: {'weight_kg': 70},
);

print(result.created);    // true if inserted, false if updated
print(result.record.id);

Online-only

An upsert needs the server's authoritative view to decide insert vs update, so — unlike insert — it is not queued for offline sync. It throws on network failure rather than risk creating a duplicate. Available in koolbase_flutter 3.1.0+.

Delete a record

Permanently removes a record. This action cannot be undone.

lib/database.dart
Dart
await Koolbase.db
  .doc('record-uuid-here')
  .delete();

Bulk delete with deleteWhere

Delete every record matching a filter in a single call. Returns the number of records deleted.

lib/database.dart
Dart
final deleted = await Koolbase.db.deleteWhere(
  collection: 'sessions',
  filters: {'user_id': userId, 'status': 'expired'},
);

print('Deleted $deleted records');

A filter is always required

Koolbase will not delete an entire collection — deleteWhere requires a non-empty filters map. The collection's delete rule still applies: for owner and scoped rules the delete is automatically limited to the calling user's own records.

Bulk semantics

A bulk delete emits a realtime delete event for each removed record, but deliberately does not fire per-record function triggers — this keeps one call from spawning many function invocations. Like upsert, it is online-only. Available in koolbase_flutter 3.1.0+.

Conditional writes

Every record carries a $revision, which the server advances on each write. Pass it back and the write applies only if the record still carries it.

lib/database.dart
Dart
final record = await Koolbase.db.doc(id).get();

// Applies only if nothing has changed the record since we read it
await Koolbase.db.doc(id).update(
  {'title': 'Corrected'},
  expectedRevision: record.revision,
);

Over HTTP, expected_revision goes in the body for an update and as a query parameter for a delete:

PATCH /v1/sdk/db/records/{record_id}
{"data": {"title": "Corrected"}, "expected_revision": 7}

DELETE /v1/sdk/db/records/{record_id}?expected_revision=7

If the record has moved on, the write is refused with 409 revision_mismatch — carrying the record as it now stands, so deciding what to do next needs no second request:

{
  "code": "revision_mismatch",
  "error": "the record has changed since you read it",
  "details": {
    "expected_revision": 7,
    "current_revision": 9,
    "record": { "$id": "...", "$revision": 9, "title": "Edited elsewhere" }
  }
}

Optional, and most apps do not need it

A write without expected_revision behaves as it always has: it applies, whatever the record looked like when you read it. That is fine when one thing writes a record at a time. It becomes a silent lost update when two do — a second device, a Function, a colleague — and neither writer ever learns that one change replaced the other. Reach for this where that matters, not everywhere.

The check and the write are one operation on the server, so nothing can slip between them. Fetching a record, comparing it yourself, then writing cannot offer that: another write can land in the gap.

Access rules on write and delete

Write and delete operations are subject to the collection's rules. The three possible values:

authenticated

Any logged-in user can update or delete any record

owner

Only the record's creator (created_by) can update or delete it

Owner check

When the write or delete rule is owner, the server checks that record.created_by == current_user.id. If not, a 403 Forbidden error is returned. Make sure the user is logged in before attempting to update or delete owned records.

Error handling

lib/database.dart
Dart
try {
  await Koolbase.db.doc(postId).delete();
} on KoolbaseException catch (e) {
  switch (e.code) {
    case 'not_found':
      showError('Post not found');
    case 'permission_denied':
      showError('You do not have permission to delete this post');
    default:
      showError('Failed to delete: ${e.message}');
  }
}