blob: 07b4f874d3a13ed1f82e0b0e031a4c2559259bd4 [file]
// Disposable, content-addressed data shared by the sequential locale processes.
// The parent owns a fresh directory for one source snapshot; never cache routes,
// translation decisions or successful verification results here.
const fs = require('node:fs');
const path = require('node:path');
const {createHash} = require('node:crypto');
function createBuildCache(directory, {maxEntries = 256} = {}) {
const memory = new Map(), pending = new Map();
const stats = {computed: 0, memoryHits: 0, diskHits: 0};
const remember = (key, value) => {
memory.delete(key);
memory.set(key, value);
while (memory.size > maxEntries) memory.delete(memory.keys().next().value);
return value;
};
async function get(namespace, inputs, compute) {
if (!directory) return compute();
const key = createHash('sha256').update(JSON.stringify([1, namespace, inputs])).digest('hex');
if (memory.has(key)) {stats.memoryHits++; return remember(key, memory.get(key));}
if (pending.has(key)) {stats.memoryHits++; return pending.get(key);}
const filename = path.join(directory, key.slice(0, 2), key + '.json');
try {
const stored = JSON.parse(fs.readFileSync(filename, 'utf8'));
if (stored.key === key && Object.hasOwn(stored, 'value')) {
stats.diskHits++;
return remember(key, stored.value);
}
} catch (error) {
if (!['ENOENT', 'ENOTDIR'].includes(error.code) && !(error instanceof SyntaxError)) throw error;
}
const task = Promise.resolve().then(compute).then(value => {
stats.computed++;
fs.mkdirSync(path.dirname(filename), {recursive: true});
const temporary = filename + '.' + process.pid + '.tmp';
fs.writeFileSync(temporary, JSON.stringify({key, value}));
fs.renameSync(temporary, filename);
return remember(key, value);
}).finally(() => pending.delete(key));
pending.set(key, task);
return task;
}
return {get, stats, clear: () => memory.clear()};
}
const buildCache = createBuildCache(process.env.DORIS_I18N_STRUCTURE_CACHE);
module.exports = {createBuildCache, buildCache};