lib/knowledge-draft-store.js
Rama main · réplica de lectura del repositorio original.
var crypto = require("crypto");
var fs = require("fs");
var path = require("path");
var MAX_DRAFT_BYTES = 2 * 1024 * 1024;
function safePart(value) { return crypto.createHash("sha256").update(String(value || "unknown")).digest("hex").slice(0, 24); }
function createKnowledgeDraftStore(baseDir, scopeKey) {
var root = path.join(baseDir, "knowledge-document-drafts", safePart(scopeKey));
function safeDirectory(directory, create) {
var stat;
try { stat = fs.lstatSync(directory); } catch (error) { if (!create || error.code !== "ENOENT") throw error; fs.mkdirSync(directory, { recursive: true, mode: 0o700 }); stat = fs.lstatSync(directory); }
if (!stat.isDirectory() || stat.isSymbolicLink()) throw Object.assign(new Error("Knowledge draft path is not a safe directory."), { code: "UNSAFE_PATH" });
}
function userRoot(userId) { return path.join(root, safePart(userId)); }
function target(userId, documentId) { return path.join(userRoot(userId), safePart(documentId) + ".json"); }
function read(userId, documentId) {
try { safeDirectory(root, false); safeDirectory(userRoot(userId), false); var file = target(userId, documentId); var stat = fs.lstatSync(file);
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_DRAFT_BYTES + 4096) throw Object.assign(new Error("Knowledge draft is not a safe file."), { code: "UNSAFE_PATH" });
var value = JSON.parse(fs.readFileSync(file, "utf8")); return value && value.version === 1 ? value : null; }
catch (error) { if (error.code === "ENOENT" || error instanceof SyntaxError) return null; throw error; }
}
function save(userId, documentId, draft) {
var content = draft && draft.content;
if (typeof content !== "string" || Buffer.byteLength(content, "utf8") > MAX_DRAFT_BYTES) throw new Error("Knowledge draft is invalid or too large.");
var current = read(userId, documentId); var clientVersion = Math.max(0, Number(draft.clientVersion) || 0);
if (current && Number(current.clientVersion) > clientVersion) return current;
var value = { version: 1, documentId: String(documentId), name: String(draft.name || "").slice(0, 500), content: content,
baseRevision: draft.baseRevision || null, cursor: draft.cursor && typeof draft.cursor === "object" ? { line: Number(draft.cursor.line) || 0, ch: Number(draft.cursor.ch) || 0 } : null,
clientVersion: clientVersion, newDocument: draft.newDocument === true, savedAt: Date.now() };
safeDirectory(root, true); safeDirectory(userRoot(userId), true); var file = target(userId, documentId);
var temporary = file + ".tmp-" + process.pid + "-" + crypto.randomBytes(5).toString("hex");
try { fs.writeFileSync(temporary, JSON.stringify(value), { flag: "wx", mode: 0o600 }); fs.renameSync(temporary, file); }
finally { try { fs.unlinkSync(temporary); } catch (error) {} }
return value;
}
function remove(userId, documentId, throughVersion) {
var current = read(userId, documentId); if (!current) return false;
if (throughVersion != null && Number(current.clientVersion) > Number(throughVersion)) return false;
try { fs.unlinkSync(target(userId, documentId)); return true; } catch (error) { if (error.code === "ENOENT") return false; throw error; }
}
function list(userId) {
var folder = userRoot(userId); var names; try { safeDirectory(root, false); safeDirectory(folder, false); } catch (error) { if (error.code === "ENOENT") return []; throw error; }
try { names = fs.readdirSync(folder); } catch (error) { if (error.code === "ENOENT") return []; throw error; }
var values = [];
for (var i = 0; i < names.length; i++) {
if (!/^[a-f0-9]{24}\.json$/.test(names[i])) continue;
try { var file = path.join(folder, names[i]); var stat = fs.lstatSync(file); if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_DRAFT_BYTES + 4096) continue;
var value = JSON.parse(fs.readFileSync(file, "utf8")); if (value && value.version === 1) values.push(value); } catch (error) {}
}
return values.sort(function (a, b) { return b.savedAt - a.savedAt; });
}
return { read: read, save: save, remove: remove, list: list };
}
module.exports = { createKnowledgeDraftStore: createKnowledgeDraftStore };