carbon-tokenization / backend /src /persistence.ts
tfrere's picture
tfrere HF Staff
feat(storage): first-class data - no silent failures in the persistence pipeline
7a42df5
import { writeFileSync, existsSync } from "fs";
import { join } from "path";
import * as Y from "yjs";
import { getDataDir, docPath, sanitizeName } from "./utils.js";
import {
isHfStorageEnabled,
getDatasetId,
setUserToken,
pullPublishedAssets,
schedulePush,
recordLocalSave,
recordLocalSaveError,
} from "./hf-storage.js";
const DEFAULT_DOC_NAME = "default";
const SAVE_DEBOUNCE_MS = 2000;
const saveTimers = new Map<string, ReturnType<typeof setTimeout>>();
const lastSaveTimestamp = new Map<string, number>();
/** @internal - exported for testing */
export function debouncedSave(documentName: string, ydoc: Y.Doc) {
const existing = saveTimers.get(documentName);
if (existing) clearTimeout(existing);
saveTimers.set(documentName, setTimeout(() => {
saveTimers.delete(documentName);
try {
const state = Y.encodeStateAsUpdate(ydoc);
const buf = Buffer.from(state);
writeFileSync(docPath(documentName), buf);
lastSaveTimestamp.set(documentName, Date.now());
recordLocalSave(documentName);
console.log(`[persist] saved "${documentName}": ${buf.length} bytes`);
if (isHfStorageEnabled()) {
schedulePush(documentName, buf);
}
} catch (err) {
// A failed local save is the most dangerous silent failure
// because the WS layer already ack'd the edit to the client,
// who therefore sees "Saved" while nothing is on disk. Push
// the error into the status tracker so the SyncIndicator can
// flip to "Local save failed" within a few seconds.
recordLocalSaveError(documentName, err);
console.error(`[persist] failed to save "${documentName}":`, (err as Error).message);
}
}, SAVE_DEBOUNCE_MS));
}
/** Reset debounce state between tests */
export function resetSaveTimers() {
for (const t of saveTimers.values()) clearTimeout(t);
saveTimers.clear();
lastSaveTimestamp.clear();
}
let _publishedRestored = false;
let _restoreInProgress: Promise<void> | null = null;
export async function ensurePublishedRestored(token?: string): Promise<void> {
const DATA_DIR = getDataDir();
if (_publishedRestored) return;
if (!getDatasetId()) return;
const publishedPath = join(DATA_DIR, "published", sanitizeName(DEFAULT_DOC_NAME), "index.html");
if (existsSync(publishedPath)) {
_publishedRestored = true;
return;
}
if (_restoreInProgress) {
await _restoreInProgress;
return;
}
if (token) setUserToken(token);
_restoreInProgress = (async () => {
try {
const found = await pullPublishedAssets(DEFAULT_DOC_NAME, DATA_DIR);
if (found) {
_publishedRestored = true;
console.log("[server] restored published article from HF dataset");
}
} catch (err) {
console.warn("[server] failed to restore published:", (err as Error).message);
} finally {
_restoreInProgress = null;
}
})();
await _restoreInProgress;
}
/** Reset restore state between tests */
export function resetPublishedRestored() {
_publishedRestored = false;
_restoreInProgress = null;
}