70b56158d4
Standalone Node.js backend that owns the SQLite database and serves composed data via REST endpoints. Replaces the shell-scheduled scraper with in-process node-cron tiered scheduling. Backend structure: - Hono HTTP server on port 3001 with CORS and request logging - Singleton SQLite connection with WAL mode - In-memory TTL cache for Queue-Times and fetchToday responses - Comparison check on fetchToday (read-before-write, only upserts on change) API endpoints: - GET /api/calendar/week — week schedule + live ride counts for all parks - GET /api/calendar/:parkId/month — month calendar for one park - GET /api/parks — park list with metadata - GET /api/parks/:id — single park detail - GET /api/parks/:id/rides — live rides with Queue-Times/schedule fallback - GET /api/status — health check, scrape stats - POST /api/scrape/trigger — manual scrape (scope: today/month/upcoming/full) Scheduler tiers: - Tier 1: today — hourly (Mar-Dec) - Tier 2: current month — every 6 hours - Tier 3: upcoming — twice daily (3 AM + 3 PM) - Tier 4: full year — daily at 3 AM Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
36 lines
678 B
TypeScript
36 lines
678 B
TypeScript
interface CacheEntry<T> {
|
|
data: T;
|
|
expiresAt: number;
|
|
}
|
|
|
|
export class TtlCache<T> {
|
|
private store = new Map<string, CacheEntry<T>>();
|
|
|
|
constructor(private defaultTtlMs: number) {}
|
|
|
|
get(key: string): T | null {
|
|
const entry = this.store.get(key);
|
|
if (!entry) return null;
|
|
if (Date.now() > entry.expiresAt) {
|
|
this.store.delete(key);
|
|
return null;
|
|
}
|
|
return entry.data;
|
|
}
|
|
|
|
set(key: string, data: T, ttlMs?: number): void {
|
|
this.store.set(key, {
|
|
data,
|
|
expiresAt: Date.now() + (ttlMs ?? this.defaultTtlMs),
|
|
});
|
|
}
|
|
|
|
clear(): void {
|
|
this.store.clear();
|
|
}
|
|
|
|
get size(): number {
|
|
return this.store.size;
|
|
}
|
|
}
|