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):
Saver.save() swallows the exception and returns undefined.
- The
editor.save() Promise resolves to undefined instead of rejecting.
- 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!");
}
- The database receives
{ content: undefined } (or null) and overwrites the existing document, wiping out all previously saved user content!
- 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;
}
}
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:And in the public API exposed to developers (
src/components/modules/api/saver.ts):π₯ 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):Saver.save()swallows the exception and returnsundefined.editor.save()Promise resolves toundefinedinstead of rejecting.{ content: undefined }(or null) and overwrites the existing document, wiping out all previously saved user content!save(): Promise<OutputData>.π‘ Proposed Solution & Patch
src/components/modules/saver.ts