gitnexus 1.6.5-rc.45 → 1.6.5-rc.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -55,8 +55,14 @@ export declare const computeChunkHash: (entries: Array<{
|
|
|
55
55
|
*/
|
|
56
56
|
export declare const loadParseCache: (storagePath: string) => Promise<ParseCache>;
|
|
57
57
|
/**
|
|
58
|
-
* Persist the cache to disk
|
|
59
|
-
*
|
|
58
|
+
* Persist the cache to disk using a temp directory + rename.
|
|
59
|
+
*
|
|
60
|
+
* Writes shards under `${cacheDir}.tmp`, then removes the old `cacheDir` and
|
|
61
|
+
* renames the temp directory into place. There is a crash window after
|
|
62
|
+
* `rm(cacheDir)` and before `rename(tmpDir, cacheDir)` where no cache exists;
|
|
63
|
+
* that is acceptable — `loadParseCache` yields empty and the next run
|
|
64
|
+
* reparses. This is not a single atomic swap of the whole tree, but avoids
|
|
65
|
+
* leaving a half-written shard set visible to readers.
|
|
60
66
|
*/
|
|
61
67
|
export declare const saveParseCache: (storagePath: string, cache: ParseCache) => Promise<void>;
|
|
62
68
|
/**
|
|
@@ -69,7 +69,12 @@ const GITNEXUS_PKG_VERSION = (() => {
|
|
|
69
69
|
return '0.0.0-unknown';
|
|
70
70
|
})();
|
|
71
71
|
export const PARSE_CACHE_VERSION = `${SCHEMA_BUMP}+${GITNEXUS_PKG_VERSION}`;
|
|
72
|
-
const
|
|
72
|
+
const LEGACY_CACHE_FILENAME = 'parse-cache.json';
|
|
73
|
+
const CACHE_DIRNAME = 'parse-cache';
|
|
74
|
+
const CACHE_INDEX_FILENAME = 'index.json';
|
|
75
|
+
/** Keys on disk always come from `computeChunkHash` — 64-char lowercase hex. */
|
|
76
|
+
const CHUNK_CACHE_KEY_HEX_RE = /^[a-f0-9]{64}$/;
|
|
77
|
+
const isValidChunkCacheKey = (chunkHash) => CHUNK_CACHE_KEY_HEX_RE.test(chunkHash);
|
|
73
78
|
/** SHA-256 hex of a single string or buffer. */
|
|
74
79
|
const sha256Hex = (input) => createHash('sha256')
|
|
75
80
|
.update(typeof input === 'string' ? Buffer.from(input) : input)
|
|
@@ -116,12 +121,12 @@ const mapReviver = (_key, value) => {
|
|
|
116
121
|
}
|
|
117
122
|
return value;
|
|
118
123
|
};
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const cachePath =
|
|
124
|
+
const getLegacyCachePath = (storagePath) => path.join(storagePath, LEGACY_CACHE_FILENAME);
|
|
125
|
+
const getCacheDirPath = (storagePath) => path.join(storagePath, CACHE_DIRNAME);
|
|
126
|
+
const getCacheIndexPath = (storagePath) => path.join(getCacheDirPath(storagePath), CACHE_INDEX_FILENAME);
|
|
127
|
+
const getCacheChunkPath = (storagePath, chunkHash) => path.join(getCacheDirPath(storagePath), `${chunkHash}.json`);
|
|
128
|
+
const loadLegacyParseCache = async (storagePath) => {
|
|
129
|
+
const cachePath = getLegacyCachePath(storagePath);
|
|
125
130
|
try {
|
|
126
131
|
const raw = await fs.readFile(cachePath, 'utf-8');
|
|
127
132
|
const data = JSON.parse(raw, mapReviver);
|
|
@@ -143,22 +148,88 @@ export const loadParseCache = async (storagePath) => {
|
|
|
143
148
|
return emptyCache();
|
|
144
149
|
}
|
|
145
150
|
};
|
|
151
|
+
const loadShardedParseCache = async (storagePath) => {
|
|
152
|
+
const indexPath = getCacheIndexPath(storagePath);
|
|
153
|
+
try {
|
|
154
|
+
const raw = await fs.readFile(indexPath, 'utf-8');
|
|
155
|
+
const data = JSON.parse(raw);
|
|
156
|
+
if (typeof data !== 'object' ||
|
|
157
|
+
data === null ||
|
|
158
|
+
data.version !== PARSE_CACHE_VERSION ||
|
|
159
|
+
!Array.isArray(data.keys)) {
|
|
160
|
+
return emptyCache();
|
|
161
|
+
}
|
|
162
|
+
const entries = new Map();
|
|
163
|
+
for (const chunkHash of data.keys) {
|
|
164
|
+
if (typeof chunkHash !== 'string' || !isValidChunkCacheKey(chunkHash))
|
|
165
|
+
continue;
|
|
166
|
+
try {
|
|
167
|
+
const chunkRaw = await fs.readFile(getCacheChunkPath(storagePath, chunkHash), 'utf-8');
|
|
168
|
+
const chunkData = JSON.parse(chunkRaw, mapReviver);
|
|
169
|
+
if (Array.isArray(chunkData))
|
|
170
|
+
entries.set(chunkHash, chunkData);
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
/* skip corrupt or missing shard */
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return { version: PARSE_CACHE_VERSION, entries, usedKeys: new Set() };
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
/**
|
|
183
|
+
* Load the parse cache. Returns an empty cache on any failure (missing
|
|
184
|
+
* file, corrupt JSON, version mismatch). Never throws on a normal load.
|
|
185
|
+
*/
|
|
186
|
+
export const loadParseCache = async (storagePath) => {
|
|
187
|
+
const sharded = await loadShardedParseCache(storagePath);
|
|
188
|
+
if (sharded)
|
|
189
|
+
return sharded;
|
|
190
|
+
return loadLegacyParseCache(storagePath);
|
|
191
|
+
};
|
|
146
192
|
/**
|
|
147
|
-
* Persist the cache to disk
|
|
148
|
-
*
|
|
193
|
+
* Persist the cache to disk using a temp directory + rename.
|
|
194
|
+
*
|
|
195
|
+
* Writes shards under `${cacheDir}.tmp`, then removes the old `cacheDir` and
|
|
196
|
+
* renames the temp directory into place. There is a crash window after
|
|
197
|
+
* `rm(cacheDir)` and before `rename(tmpDir, cacheDir)` where no cache exists;
|
|
198
|
+
* that is acceptable — `loadParseCache` yields empty and the next run
|
|
199
|
+
* reparses. This is not a single atomic swap of the whole tree, but avoids
|
|
200
|
+
* leaving a half-written shard set visible to readers.
|
|
149
201
|
*/
|
|
150
202
|
export const saveParseCache = async (storagePath, cache) => {
|
|
151
203
|
await fs.mkdir(storagePath, { recursive: true });
|
|
152
|
-
const
|
|
153
|
-
const
|
|
154
|
-
|
|
204
|
+
const cacheDir = getCacheDirPath(storagePath);
|
|
205
|
+
const tmpDir = `${cacheDir}.tmp`;
|
|
206
|
+
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
207
|
+
await fs.mkdir(tmpDir, { recursive: true });
|
|
208
|
+
const keys = [];
|
|
209
|
+
for (const [chunkHash, chunkResults] of cache.entries) {
|
|
210
|
+
if (!isValidChunkCacheKey(chunkHash))
|
|
211
|
+
continue;
|
|
212
|
+
let payload;
|
|
213
|
+
try {
|
|
214
|
+
payload = JSON.stringify(chunkResults, mapReplacer);
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
// Extremely dense chunks could theoretically exceed string limits; skip
|
|
218
|
+
// rather than failing the entire save (orchestrator catches save errors).
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
keys.push(chunkHash);
|
|
222
|
+
const chunkPath = path.join(tmpDir, `${chunkHash}.json`);
|
|
223
|
+
await fs.writeFile(chunkPath, payload, 'utf-8');
|
|
224
|
+
}
|
|
225
|
+
const index = {
|
|
155
226
|
version: cache.version,
|
|
156
|
-
|
|
227
|
+
keys,
|
|
157
228
|
};
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
await fs.
|
|
161
|
-
await fs.
|
|
229
|
+
await fs.writeFile(path.join(tmpDir, CACHE_INDEX_FILENAME), JSON.stringify(index), 'utf-8');
|
|
230
|
+
await fs.rm(cacheDir, { recursive: true, force: true });
|
|
231
|
+
await fs.rename(tmpDir, cacheDir);
|
|
232
|
+
await fs.rm(getLegacyCachePath(storagePath), { force: true });
|
|
162
233
|
};
|
|
163
234
|
/**
|
|
164
235
|
* Drop entries whose hashes are not in `usedHashes`. Called at the end
|
package/package.json
CHANGED