MCPcopy Create free account
hub / github.com/Zoo-Code-Org/Zoo-Code / safeWriteJson

Function safeWriteJson

src/utils/safeWriteJson.ts:35–193  ·  view source on GitHub ↗

* Safely writes JSON data to a file. * - Creates parent directories if they don't exist * - Uses 'proper-lockfile' for inter-process advisory locking to prevent concurrent writes to the same path. * - Writes to a temporary file first. * - If the target file exists, it's backed up before being re

(filePath: string, data: any, options?: SafeWriteJsonOptions)

Source from the content-addressed store, hash-verified

33 */
34
35async function safeWriteJson(filePath: string, data: any, options?: SafeWriteJsonOptions): Promise<void> {
36 const absoluteFilePath = path.resolve(filePath)
37 let releaseLock = async () => {} // Initialized to a no-op
38
39 // For directory creation
40 const dirPath = path.dirname(absoluteFilePath)
41
42 // Ensure directory structure exists with improved reliability
43 try {
44 // Create directory with recursive option
45 await fs.mkdir(dirPath, { recursive: true })
46
47 // Verify directory exists after creation attempt
48 await fs.access(dirPath)
49 } catch (dirError: any) {
50 console.error(`Failed to create or access directory for ${absoluteFilePath}:`, dirError)
51 throw dirError
52 }
53
54 // Acquire the lock before any file operations
55 try {
56 releaseLock = await lockfile.lock(absoluteFilePath, {
57 stale: 31000, // Stale after 31 seconds
58 update: 10000, // Update mtime every 10 seconds to prevent staleness if operation is long
59 realpath: false, // the file may not exist yet, which is acceptable
60 retries: {
61 // Configuration for retrying lock acquisition
62 retries: 5, // Number of retries after the initial attempt
63 factor: 2, // Exponential backoff factor (e.g., 100ms, 200ms, 400ms, ...)
64 minTimeout: 100, // Minimum time to wait before the first retry (in ms)
65 maxTimeout: 1000, // Maximum time to wait for any single retry (in ms)
66 },
67 onCompromised: (err) => {
68 console.error(`Lock at ${absoluteFilePath} was compromised:`, err)
69 throw err
70 },
71 })
72 } catch (lockError) {
73 // If lock acquisition fails, we throw immediately.
74 // The releaseLock remains a no-op, so the finally block in the main file operations
75 // try-catch-finally won't try to release an unacquired lock if this path is taken.
76 console.error(`Failed to acquire lock for ${absoluteFilePath}:`, lockError)
77 // Propagate the lock acquisition error
78 throw lockError
79 }
80
81 // Variables to hold the actual paths of temp files if they are created.
82 let actualTempNewFilePath: string | null = null
83 let actualTempBackupFilePath: string | null = null
84
85 try {
86 // Step 1: Write data to a new temporary file.
87 actualTempNewFilePath = path.join(
88 path.dirname(absoluteFilePath),
89 `.${path.basename(absoluteFilePath)}.new_${Date.now()}_${Math.random().toString(36).substring(2)}.tmp`,
90 )
91
92 await _streamDataToFile(actualTempNewFilePath, data, options?.prettyPrint)

Callers 15

webviewMessageHandlerFunction · 0.90
writeIndexMethod · 0.90
writeTaskFileMethod · 0.90
saveApiMessagesFunction · 0.90
saveTaskMessagesFunction · 0.90
saveTaskMetadataMethod · 0.90
exportSettingsFunction · 0.90
_performSaveMethod · 0.90
clearCacheFileMethod · 0.90
updateServerConfigMethod · 0.90

Calls 5

_streamDataToFileFunction · 0.85
releaseLockFunction · 0.85
lockMethod · 0.80
errorMethod · 0.65
toStringMethod · 0.65

Tested by

no test coverage detected