-
-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathcache.js
More file actions
155 lines (130 loc) · 4.09 KB
/
Copy pathcache.js
File metadata and controls
155 lines (130 loc) · 4.09 KB
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import {access, mkdir, readFile, unlink, writeFile} from 'fs/promises'
import path, {dirname} from 'node:path'
import {sanitizePath} from './path.js'
const CACHE_ROOT = sanitizePath(`${process.cwd()}/cache`)
class Cache {
/**
* @private
* The logger instance for logging messages.
* @type {@import('./log').default}
*/
#logger
/**
* @private
* The path to the cache file.
* Defaults to `${process.cwd()}/cache/report.json` if not provided.
* @type {string}
*/
#path
/**
* Creates an instance of Cache.
* @param {string|null} [path=null] - The path to the cache directory. Defaults to `${process.cwd()}/cache/`
* @param {@import('./log').default} [logger=null] - The logger instance for logging messages.
*
* If no path is provided, it defaults to `${process.cwd()}/cache/report.json`.
* If a path is provided, it will be used as the cache file path.
*/
constructor(path = null, logger) {
this.#path = this.#resolveCachePath(path || `${CACHE_ROOT}/report.json`)
this.#logger = logger
}
/**
* Gets the current cache path.
* @returns {string} The current cache path
*/
get path() {
return this.#path
}
/**
* Sets a new cache path.
* @param {string} newPath - The new cache path to set
*/
set path(newPath) {
this.#path = this.#resolveCachePath(newPath)
}
/**
* Resolves and validates a cache path to ensure it stays within CACHE_ROOT.
* @param {string} inputPath - Candidate cache file path
* @returns {string} Safe absolute cache path
* @throws {Error} If path is outside of cache root
*/
#resolveCachePath(inputPath) {
const resolvedPath = sanitizePath(inputPath)
const relativePath = path.relative(CACHE_ROOT, resolvedPath)
if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
throw new Error(`Cache path must be within ${CACHE_ROOT}`)
}
return resolvedPath
}
/**
* Saves data to the cache file.
* Creates cache directory if it doesn't exist.
* @param {object} data - The data to save to cache
* @returns {Promise<void>} Resolves when save completes, logs errors on failure
*/
async save(data) {
try {
const dir = dirname(this.#path)
await mkdir(dir, {recursive: true})
this.#logger.debug(`Creating cache directory at ${dir}`)
await writeFile(this.#path, JSON.stringify(data, null, 2))
this.#logger.debug(`Cache saved to ${this.#path}`)
return true
} catch (error) {
this.#logger.error(`Failed to save cache to ${this.#path}: ${error.message}`)
return null
}
}
/**
* Loads data from the cache file.
* @returns {Promise<object|null>} The cached data or null if loading fails
*/
async load() {
try {
const safePath = sanitizePath(this.#path)
const data = await readFile(safePath, 'utf-8')
this.#logger.debug(`Cache loaded from ${safePath}`)
return JSON.parse(data)
} catch (error) {
this.#logger.error(`Failed to load cache from ${this.#path}: ${error.message}`)
return null
}
}
/**
* Clears the cache by deleting the cache file.
* @returns {Promise<boolean>} True if successful, false if clearing fails
*/
async clear() {
try {
await unlink(this.#path)
this.#logger.debug(`Cache cleared at ${this.#path}`)
return true
} catch (error) {
this.#logger.error(`Failed to clear cache at ${this.#path}: ${error.message}`)
return false
}
}
/**
* Checks if the cache file exists.
* @returns {Promise<boolean>} True if the cache file exists, false otherwise
*/
async exists() {
try {
const safePath = sanitizePath(this.#path)
await access(safePath)
this.#logger.debug(`Cache file exists at ${safePath}`)
return true
} catch {
this.#logger.warn(`Cache file does not exist at ${this.#path}`)
return false
}
}
}
// Singleton pattern to ensure only one instance of Log class is created
let instance = null
export default function cacheInstance(path, logger) {
if (!instance) {
instance = new Cache(path, logger)
}
return instance
}