Skip to content

Bug: Supabase replication throws "doc not found" when deleting document that was never synced #7612

@OskarD

Description

@OskarD

Description

The Supabase replication plugin throws an error when pushing a deletion for a document that doesn't exist on the server. This happens when a document is created locally, then deleted before it syncs to Supabase.

Environment

  • RxDB Version: 16.21.1
  • Plugin: rxdb/plugins/replication-supabase
  • Platform: Overwolf desktop app (Chromium-based)

Steps to Reproduce

  1. Create a document locally (e.g., while offline or before sync starts)
  2. Delete the document with doc.remove() before it syncs to Supabase
  3. Observe replication error

Current Behavior

The replication throws:

Error: doc not found <document-id>

With error code RC_PUSH and the push fails repeatedly.

Expected Behavior

Deleting a document that doesn't exist on the server should succeed silently. The desired end state is achieved (document doesn't exist on server), so this should not be treated as an error.

Root Cause

In updateOrReturnConflict(), when the UPDATE returns 0 rows (document doesn't exist), the code calls fetchById() to get the conflict state:

if (data && data.length > 0) {
  return;
} else {
  // no match -> conflict
  return await fetchById(id);
}

fetchById() throws when the document doesn't exist:

if (data.length != 1) throw new Error('doc not found ' + id);

For deletions (_deleted: true), a non-existent document is the desired end state, not a conflict.

Suggested Fix

Wrap the fetchById call in updateOrReturnConflict to handle the "doc not found" case for deletions:

} else {
  // no match -> check if it's a conflict or if doc simply doesn't exist
  try {
    return await fetchById(id);
  } catch (err) {
    // If document doesn't exist and we're trying to delete it,
    // the desired state is achieved - not a conflict
    if (doc._deleted && err.message && err.message.includes('doc not found')) {
      return undefined;
    }
    throw err;
  }
}

Workaround

In the application's error handler, ignore "doc not found" errors for push operations where _deleted: true:

replication.error$.subscribe((error) => {
  const isDocNotFoundOnPush =
    error.message?.includes('doc not found') &&
    error.direction === 'push';

  if (isDocNotFoundOnPush) {
    const isDeleteOperation = error.parameters?.pushRows?.some(
      (row) => row.newDocumentState?._deleted === true
    );
    if (isDeleteOperation) {
      // Ignore - document doesn't exist on server, which is the desired state
      return;
    }
  }
  // Handle other errors...
});

Note: This workaround only suppresses the error display; RxDB may still retry the failed push internally.

Suggested diff

The fix is in src/plugins/replication-supabase/index.ts. Here's the diff:

--- a/src/plugins/replication-supabase/index.ts
+++ b/src/plugins/replication-supabase/index.ts
@@ -139,7 +139,7 @@ export function replicateSupabase<RxDocType>(
         async function updateOrReturnConflict(
             doc: WithDeleted<RxDocType>,
             assumedMasterState: WithDeleted<RxDocType>
-        ): Promise<WithDeleted<RxDocType> | undefined> {
+        ): Promise<WithDeleted<RxDocType> | undefined> {
             ensureNotFalsy(assumedMasterState);
             const id = (doc as any)[primaryPath];
             const toRow = flatClone(doc);
@@ -161,7 +161,18 @@ export function replicateSupabase<RxDocType>(
             if (data && data.length > 0) {
                 return;
             } else {
-                // no match -> conflict
-                return await fetchById(id);
+                // no match -> check if it's a conflict or if doc simply doesn't exist
+                try {
+                    return await fetchById(id);
+                } catch (err: any) {
+                    // If document doesn't exist and we're trying to delete it,
+                    // the desired state is achieved - treat as success, not conflict
+                    if (doc._deleted && err.message && err.message.includes('doc not found')) {
+                        return undefined;
+                    }
+                    throw err;
+                }
             }
         }

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions