Files
ridebus-pwa/src/lib/api.ts
2026-10-07 01:48:32 +03:00

52 lines
2.7 KiB
TypeScript

import { db } from './storage';
import { demoData } from './demo';
import type { NearbyStop, Snapshot } from '../types';
export type ResourceOptions = { routeId?: number; at?: string; tripKey?: string };
export class DataError extends Error {
constructor(public code: string) { super(code); }
}
export async function loadResource<T>(
resource: string, mode: 'live' | 'demo', cityId?: number, stopId?: number, date?: string, options: ResourceOptions = {},
): Promise<Snapshot<T>> {
const key = `${mode}:${resource}:${cityId ?? ''}:${stopId ?? ''}${date ? `:${date}` : ''}${options.routeId ? `:${options.routeId}:${options.at ?? ''}:${options.tripKey ?? ''}` : ''}`;
let data: T;
try {
if (!navigator.onLine) throw new DataError('offline');
if (mode === 'demo') data = demoData(resource, cityId, stopId, date, options) as T;
else {
const params = new URLSearchParams({ resource });
if (cityId) params.set('cityId', String(cityId));
if (stopId) params.set('stopId', String(stopId));
if (date) params.set('date', date);
if (options.routeId) params.set('routeId', String(options.routeId));
if (options.at) params.set('at', options.at);
if (options.tripKey) params.set('tripKey', options.tripKey);
const response = await fetch(`/api/data?${params}`, { signal: AbortSignal.timeout(15000), cache: 'no-store' });
const body = await response.json();
if (!response.ok) throw new DataError(body.error || 'data_unavailable');
data = body as T;
}
} catch (error) {
const snapshot = await db.snapshots.get(key).catch(() => undefined);
if (snapshot) return { data: snapshot.data as T, savedAt: snapshot.savedAt, saved: true };
throw error;
}
const savedAt = new Date().toISOString();
// Storage failure must not hide otherwise available API data.
await db.snapshots.put({ key, data, savedAt }).catch(() => undefined);
return { data, savedAt, saved: false };
}
export async function loadNearby(mode: 'live' | 'demo', cityId: number, lat: number, lon: number, signal: AbortSignal): Promise<NearbyStop[]> {
if (mode === 'demo') return (demoData('stops', cityId) as NearbyStop[]).slice(0, 4).map((stop, index) => ({ ...stop, distance: 120 + index * 90 }));
const params = new URLSearchParams({ resource: 'nearby', cityId: String(cityId), lat: lat.toFixed(5), lon: lon.toFixed(5), radius: '1000' });
const response = await fetch(`/api/data?${params}`, { cache: 'no-store', signal: AbortSignal.any([signal, AbortSignal.timeout(15000)]) });
const data = await response.json();
if (!response.ok) throw new DataError(data.error || 'data_unavailable');
// Nearby coordinates and results stay in memory, outside persistent snapshots.
return data as NearbyStop[];
}