File size: 3,083 Bytes
f6678ab 7a42df5 f6678ab 7a42df5 f6678ab 7a42df5 f6678ab | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | 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;
}
|