Setting the file. One moment. Read Back State · Wix Replatform · wix/skills · Skills DocsBundled file Read Back Summary
lib/read-back-state.js
JavaScript·107 lines·3 KB
;
8async function readJson(projectDir, name, fallback) {
9 try {
10 return JSON.parse(await fs.readFile(path.join(projectDir, name), 'utf8'));
11 } catch (e) {
12 if (e.code === 'ENOENT' && fallback !== undefined) return structuredClone(fallback);
13 throw e;
14 }
15}
16async function atomic(projectDir, name, value) {
17 const file = path.join(projectDir, name),
18 temp = `${file}.${crypto.randomUUID()}.tmp`;
19 await fs.mkdir(path.dirname(file), {
20 recursive: true
21 });
22 try {
23 await fs.writeFile(temp, JSON.stringify(value, null, 2) + '\n', {
24 mode: 0o600
25 });
26 await fs.rename(temp, file);
27 } finally {
28 await fs.rm(temp, {
29 force: true
30 });
31 }
32}
33// Use the project's existing lock. Contending local writers retry instead of losing updates.
34const localQueues = new Map();
35function locked(projectDir, fn) {
36 const key = path.resolve(projectDir),
37 previous = localQueues.get(key) || Promise.resolve();
38 const task = previous.catch(() => {}).then(() => acquire(projectDir, fn));
39 localQueues.set(key, task);
40 return task.finally(() => {
41 if (localQueues.get(key) === task) localQueues.delete(key);
42 });
43}
44async function acquire(projectDir, fn) {
45 const deadline = Date.now() + 30000;
46 for (;;) {
47 try {
48 return await local.withStateLock(projectDir, fn);
49 } catch (e) {
50 if (!/state lock already held/.test(e.message) || Date.now() >= deadline) throw e;
51 await new Promise(r => setTimeout(r, crypto.randomInt(10, 40)));
52 }
53 }
54}
55async function load(projectDir) {
56 let state;
57 try {
58 state = await readJson(projectDir, FILE);
59 } catch (e) {
60 if (e.code !== 'ENOENT') throw new Error('gate history unreadable', {
61 cause: e
62 });
63 const store = require('./write-verification-state');
64 let hasHistory = false;
65 const snapshot = await readJson(projectDir, 'state/write-verification.json', null);
66 if (snapshot) {
67 const recovered = await store.load(projectDir, snapshot.siteId);
68 hasHistory = Object.values(recovered.rows).some(r => r.kind === 'read-back-gate');
69 } else {
70 // An un-compacted journal also proves a gate existed.
71 let files = [];
72 try {
73 files = await fs.readdir(path.join(projectDir, 'state/write-verification-journal'));
74 } catch (err) {
75 if (err.code !== 'ENOENT') throw err;
76 }
77 for (const file of files.filter(f => f.endsWith('.json'))) {
78 const delta = await readJson(projectDir, `state/write-verification-journal/${file}`);
79 if (Object.values(delta.rows || {}).some(r => r.kind === 'read-back-gate')) hasHistory = true;
80 }
81 }
82 if (hasHistory) throw new Error('gate history unreadable');
83 state = {
84 schemaVersion: 1,
85 observedLosses: [],
86 runs: {}
87 };
88 }
89 if (state.schemaVersion !== 1 || !Array.isArray(state.observedLosses) || !state.runs || Array.isArray(state.runs)) throw new Error('gate history unreadable');
90 return state;
91}
92async function update(projectDir, mutate) {
93 return locked(projectDir, async () => {
94 const state = await load(projectDir);
95 const result = await mutate(state);
96 await atomic(projectDir, FILE, state);
97 return result;
98 });
99}
100module.exports = {
101 FILE,
102 readJson,
103 atomic,
104 locked,
105 load,
106 update
107};