Skip to main content

Understanding Deletes in Append-Only Storage

OnDB performs soft deletes. When you delete a document, a new record with deleted: true is appended. The original data is preserved but excluded from query results by default.

Delete Pattern

Soft delete a document by finding it and storing a new version with deleted: true.
TypeScript
// Find the document
const user = await client.queryBuilder()
  .collection('users')
  .whereField('email').equals('alice@example.com')
  .executeUnique();

if (user) {
  // Store soft-deleted version
  await client.store(
    { collection: 'users', data: [{ ...user, deleted: true, deletedAt: new Date().toISOString() }] },
    async (quote) => {
      const txHash = await processPayment(quote);
      return { txHash, network: quote.network, sender: walletAddress, chainType: quote.chainType, paymentMethod: 'native' };
    },
    true
  );
  console.log('Deleted user');
} else {
  console.log('User not found');
}

How Deletes Work

Original Record (Block 100):
{ id: "user_1", name: "Alice", email: "alice@example.com", deleted: false }

After Delete (Block 101):
{ id: "user_1", deleted: true, deletedAt: "2024-01-02", updatedAt: "2024-01-02" }

Original record is preserved, but queries exclude deleted records

Examples

Delete by ID

TypeScript
const post = await client.queryBuilder()
  .collection('posts')
  .whereField('id').equals('post_123')
  .executeUnique();

if (post) {
  await client.store(
    { collection: 'posts', data: [{ ...post, deleted: true, deletedAt: new Date().toISOString() }] },
    paymentCallback,
    true
  );
  console.log('Post deleted successfully');
} else {
  console.log('Post not found');
}

Conditional Delete Pattern

TypeScript
// First verify ownership or permissions
const post = await client.queryBuilder()
  .collection('posts')
  .whereField('id').equals('post_123')
  .executeUnique();

if (post && post.author === currentUser.id) {
  await client.store(
    { collection: 'posts', data: [{ ...post, deleted: true, deletedAt: new Date().toISOString() }] },
    paymentCallback,
    true
  );
  console.log('Post deleted');
} else {
  console.log('Unauthorized or not found');
}

Verifying Deletion

After deletion, the document won’t appear in normal queries:
TypeScript
// Delete a user
const user = await client.queryBuilder()
  .collection('users')
  .whereField('id').equals('user_123')
  .executeUnique();

if (user) {
  await client.store(
    { collection: 'users', data: [{ ...user, deleted: true, deletedAt: new Date().toISOString() }] },
    paymentCallback,
    true
  );
}

// executeUnique returns null for deleted documents
const deleted = await client.queryBuilder()
  .collection('users')
  .whereField('id').equals('user_123')
  .executeUnique();
console.log(deleted); // null

Data Retention

Since OnDB uses append-only storage:
AspectBehavior
Original dataRemains on blockchain permanently
Query resultsExclude deleted records by default
Audit trailFull history is preserved
RecoveryPossible by querying historical records

Bulk Delete Pattern

For deleting multiple documents, query and delete each:
TypeScript
const result = await client.queryBuilder()
  .collection('users')
  .whereField('active').isFalse()
  .execute();

for (const user of result.records) {
  await client.store(
    { collection: 'users', data: [{ ...user, deleted: true, deletedAt: new Date().toISOString() }] },
    paymentCallback,
    true
  );
}

Next Steps

CRUD Overview

Back to CRUD overview

Query Builder

Complex queries