Updates
CI / test (push) Successful in 1m1s
Release rolling / release (push) Successful in 6m59s

This commit is contained in:
Raven Scott
2026-07-18 20:32:05 -04:00
parent 41f5476a60
commit a7648dc090
28 changed files with 2539 additions and 333 deletions
+133
View File
@@ -0,0 +1,133 @@
/**
* Atomic JSON cache under ~/.config/peardata/cache/{name}.json
* (+ optional localStorage mirror / legacy migration).
*/
import fs from 'fs'
import path from 'path'
import { getPeardataCacheDir } from './paths.js'
export const JSON_CACHE_VERSION = 1
export const JSON_CACHE_LS_PREFIX = 'peardata.cache.'
/**
* @param {string} name
*/
export function sanitizeCacheName(name) {
const s = String(name || '')
.trim()
.toLowerCase()
.replace(/[^a-z0-9._-]+/g, '-')
.replace(/^-+|-+$/g, '')
if (!s || s === '.' || s === '..') throw new Error(`Invalid cache name: ${name}`)
return s
}
/**
* @param {string} name
*/
export function getCacheFilePath(name) {
return path.join(getPeardataCacheDir(), `${sanitizeCacheName(name)}.json`)
}
/**
* @param {string} name
*/
export function getCacheLocalStorageKey(name) {
return JSON_CACHE_LS_PREFIX + sanitizeCacheName(name)
}
/**
* @param {string} file
* @param {object} payload
*/
export function atomicWriteJson(file, payload) {
const dir = path.dirname(file)
fs.mkdirSync(dir, { recursive: true, mode: 0o700 })
const json = JSON.stringify(payload, null, 2)
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`
fs.writeFileSync(tmp, json, { mode: 0o600 })
fs.renameSync(tmp, file)
try {
fs.chmodSync(file, 0o600)
} catch {
// ignore
}
}
/**
* @param {string} name
* @param {unknown} data
* @param {{ mirrorLocalStorage?: boolean }} [opts]
*/
export function saveJsonCache(name, data, opts = {}) {
const file = getCacheFilePath(name)
const payload = {
version: JSON_CACHE_VERSION,
updatedAt: new Date().toISOString(),
data,
}
try {
atomicWriteJson(file, payload)
} catch (err) {
console.warn('[WARN] jsonCache: write failed', name, err?.message || err)
}
if (opts.mirrorLocalStorage !== false) {
try {
if (typeof localStorage !== 'undefined') {
localStorage.setItem(getCacheLocalStorageKey(name), JSON.stringify(payload))
}
} catch {
// ignore
}
}
return data
}
/**
* @param {string} name
* @param {{ legacyLocalStorageKey?: string, defaultValue?: unknown }} [opts]
*/
export function loadJsonCache(name, opts = {}) {
const file = getCacheFilePath(name)
try {
if (fs.existsSync(file)) {
const raw = JSON.parse(fs.readFileSync(file, 'utf8'))
if (raw && typeof raw === 'object' && 'data' in raw) return raw.data
return raw
}
} catch {
// fall through
}
// Legacy localStorage migration
if (opts.legacyLocalStorageKey) {
try {
if (typeof localStorage !== 'undefined') {
const legacy = localStorage.getItem(opts.legacyLocalStorageKey)
if (legacy) {
const parsed = JSON.parse(legacy)
const data =
parsed && typeof parsed === 'object' && 'data' in parsed ? parsed.data : parsed
saveJsonCache(name, data ?? opts.defaultValue ?? null)
return data ?? opts.defaultValue ?? null
}
}
} catch {
// ignore
}
}
try {
if (typeof localStorage !== 'undefined') {
const mir = localStorage.getItem(getCacheLocalStorageKey(name))
if (mir) {
const parsed = JSON.parse(mir)
return parsed && typeof parsed === 'object' && 'data' in parsed ? parsed.data : parsed
}
}
} catch {
// ignore
}
return opts.defaultValue ?? null
}