weather-cli-16bit
Version:
A simple, intelligent weather CLI with smart location detection - no quotes needed
164 lines (136 loc) • 4.07 kB
JavaScript
import fs from 'fs/promises';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { WeatherError, ERROR_CODES } from './utils/errors.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const CACHE_FILE = join(__dirname, '..', '.weather-cache.json');
const CACHE_EXPIRY = 30 * 60 * 1000; // 30 minutes
const MAX_CACHE_SIZE = 100; // Maximum number of entries
const MAX_CACHE_AGE = 7 * 24 * 60 * 60 * 1000; // 7 days
// Simple LRU tracking
const accessOrder = [];
// Load cache from file
async function loadCache() {
try {
const data = await fs.readFile(CACHE_FILE, 'utf8');
return JSON.parse(data);
} catch {
return {};
}
}
// Save cache to file
async function saveCache(cache) {
try {
await fs.writeFile(CACHE_FILE, JSON.stringify(cache, null, 2));
} catch (error) {
throw new WeatherError('Failed to save cache', ERROR_CODES.CACHE_ERROR);
}
}
// Evict oldest entries if cache is too large
async function evictOldEntries(cache) {
const entries = Object.entries(cache);
const now = Date.now();
// Remove very old entries first
let updatedCache = {};
let count = 0;
for (const [key, value] of entries) {
if ((now - value.timestamp) < MAX_CACHE_AGE && count < MAX_CACHE_SIZE) {
updatedCache[key] = value;
count++;
}
}
// If still too large, remove least recently used
if (count >= MAX_CACHE_SIZE) {
const sortedEntries = Object.entries(updatedCache)
.sort((a, b) => {
const aIndex = accessOrder.indexOf(a[0]);
const bIndex = accessOrder.indexOf(b[0]);
return (aIndex === -1 ? Infinity : aIndex) - (bIndex === -1 ? Infinity : bIndex);
})
.slice(0, MAX_CACHE_SIZE - 1);
updatedCache = Object.fromEntries(sortedEntries);
}
return updatedCache;
}
// Get cached weather data if not expired
async function getCachedWeather(location, units) {
const cache = await loadCache();
const key = `${location}-${units}`;
const cached = cache[key];
if (cached && (Date.now() - cached.timestamp) < CACHE_EXPIRY) {
// Update access order for LRU
const index = accessOrder.indexOf(key);
if (index > -1) {
accessOrder.splice(index, 1);
}
accessOrder.push(key);
return cached.data;
}
return null;
}
// Set cached weather data
async function setCachedWeather(location, units, data) {
const cache = await loadCache();
const key = `${location}-${units}`;
cache[key] = {
data,
timestamp: Date.now()
};
// Update access order
const index = accessOrder.indexOf(key);
if (index > -1) {
accessOrder.splice(index, 1);
}
accessOrder.push(key);
// Evict old entries if needed
const updatedCache = await evictOldEntries(cache);
await saveCache(updatedCache);
}
// Clean expired cache entries
async function cleanExpiredCache() {
const cache = await loadCache();
const now = Date.now();
let cleaned = 0;
for (const [key, value] of Object.entries(cache)) {
if ((now - value.timestamp) >= CACHE_EXPIRY) {
delete cache[key];
cleaned++;
}
}
if (cleaned > 0) {
await saveCache(cache);
console.log(`🧹 Cleaned ${cleaned} expired cache entries`);
}
return cleaned;
}
// Get cache statistics
async function getCacheStats() {
const cache = await loadCache();
const now = Date.now();
let total = 0;
let expired = 0;
let valid = 0;
for (const [key, value] of Object.entries(cache)) {
total++;
if ((now - value.timestamp) >= CACHE_EXPIRY) {
expired++;
} else {
valid++;
}
}
return { total, expired, valid };
}
// Clear all cache
async function clearCache() {
await fs.writeFile(CACHE_FILE, '{}');
}
export {
loadCache,
saveCache,
getCachedWeather,
setCachedWeather,
cleanExpiredCache,
getCacheStats,
clearCache
};