Silent Data Loss & Broken Error Contract in Saver.save()

Open Beginner friendly
#3,030 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
82/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
typescript
Domain
api

Research direction

Start by reading the save() implementations in src/components/modules/saver.ts and src/components/modules/api/saver.ts, focusing on how errors are handled and propagated. Verify that failures during block saving or sanitization reject the public save() Promise rather than resolving with undefined, while read-only behavior remains unchanged.

Written by the indexing model from the issue text.

Description

bug

2. Silent Data Loss & Broken Error Contract in Saver.save()

📍 Affected Locations
  • src/components/modules/saver.ts (Lines 34–48)
  • src/components/modules/api/saver.ts (Lines 27–38)
🔍 Deep Technical Diagnosis

In src/components/modules/saver.ts:

// src/components/modules/saver.ts: Lines 34-48
public async save(): Promise<OutputData> {
  const { BlockManager, Tools } = this.Editor;
  const blocks = BlockManager.blocks,
      chainData = [];

  try {
    blocks.forEach((block: Block) => {
      chainData.push(this.getSavedData(block));
    });

    const extractedData = await Promise.all(chainData) as Array<Pick<SavedData, 'data' | 'tool'>>;
    const sanitizedData = await sanitizeBlocks(extractedData, (name) => {
      return Tools.blockTools.get(name).sanitizeConfig;
    });

    return this.makeOutput(sanitizedData);
  } catch (e) {
    _.logLabeled(`Saving failed due to the Error %o`, 'error', e);
    // BUG: No return statement, no rethrow, no Promise.reject!
    // Silently completes and returns `undefined`!
  }
}

And in the public API exposed to developers (src/components/modules/api/saver.ts):

// src/components/modules/api/saver.ts: Lines 27-37
public save(): Promise<OutputData> {
  const errorText = 'Editor\'s content can not be saved in read-only mode';

  if (this.Editor.ReadOnly.isEnabled) {
    _.logLabeled(errorText, 'warn');
    return Promise.reject(new Error(errorText));
  }

  return this.Editor.Saver.save();
}
💥 Real-World Impact (Catastrophic Data Loss)

When an exception occurs during block saving or sanitization (for instance, a custom tool throws in its save() method, or a circular JSON structure is encountered):

  1. Saver.save() swallows the exception and returns undefined.
  2. The editor.save() Promise resolves to undefined instead of rejecting.
  3. Typical application backend code looks like:
    try {
      const output = await editor.save();
      // If saving fails, the developer expects `catch` to trigger!
      // Instead, output is `undefined`!
      await api.saveArticle({ id: articleId, content: output });
    } catch (error) {
      toast.error("Saving failed!");
    }
    
  4. The database receives { content: undefined } (or null) and overwrites the existing document, wiping out all previously saved user content!
  5. Violates Promise conventions and the TypeScript contract declaring save(): Promise<OutputData>.
💡 Proposed Solution & Patch
src/components/modules/saver.ts
@@ -44,6 +44,7 @@ export default class Saver extends Module {
       return this.makeOutput(sanitizedData);
     } catch (e) {
       _.logLabeled(`Saving failed due to the Error %o`, 'error', e);
+      throw e;
     }
   }

Dominant language
TypeScript
Stars
32k
Forks
2.2k
Avg merge
1d 13h
Merged PRs (30d)
2

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from codex-team/editor.js

All issues in codex-team/editor.js

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.