managarten/services/mana-geocoding/src/config.ts
Till JS 8a5fad34df fix(geocoding): bump PROVIDER_TIMEOUT_MS to 20s for cold cross-LAN
Cold-start fetches from the mana-geocoding container to photon-self
on mana-gpu (over WSL2 mirrored networking) consistently take >10s on
the first probe and ~2s once warm. The previous 8s default caused the
chain to false-mark photon-self unhealthy on every cold path, leaking
to public photon for the next 30s health-cache window — and pinning
the public-photon answer in the 7d cache (now shortened to 1h).

Also wires the docker-compose macmini env to honor PROVIDER_TIMEOUT_MS
and CACHE_PUBLIC_TTL_MS overrides so production picks up the new
values without a code rebuild.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 22:19:21 +02:00

114 lines
4.2 KiB
TypeScript

/**
* Application configuration loaded from environment variables.
*/
import type { ProviderName } from './providers/types';
export interface Config {
port: number;
photon: {
/** Photon base URL — public komoot endpoint by default. Used by
* the `'photon'` provider slot which always has `privacy: 'public'`. */
apiUrl: string;
};
photonSelf: {
/** Self-hosted Photon URL (e.g. `http://192.168.178.11:2322` for the
* GPU server). When set, the wrapper registers a separate
* `'photon-self'` provider with `privacy: 'local'` — eligible for
* sensitive queries. When undefined, the slot is disabled and the
* chain runs on public providers only. */
apiUrl: string | undefined;
};
nominatim: {
apiUrl: string;
userAgent: string;
/** Inter-request gap in ms. Public Nominatim policy is 1 req/sec — we
* default to 1100 ms to leave headroom against clock drift. */
intervalMs: number;
};
cors: {
origins: string[];
};
cache: {
/** Max entries in the in-memory LRU cache */
maxEntries: number;
/** Default TTL in milliseconds (24h — used for results from local
* providers like photon-self) */
ttlMs: number;
/** TTL for results that came from public APIs (Photon, Nominatim).
* Capped at 1h so a brief blip in photon-self can't pin stale
* public-fallback answers in the cache for days. The privacy
* benefit of long TTLs (fewer outbound queries) is moot now that
* photon-self serves the bulk of traffic. */
publicTtlMs: number;
};
providers: {
/** Order matters — the chain tries them top-down. Anything not in
* this list is disabled. */
enabled: ProviderName[];
/** TTL for the per-provider health cache. */
healthCacheMs: number;
/** Wall-clock timeout per provider attempt (a slow provider falls
* through to the next one). */
timeoutMs: number;
};
}
export function loadConfig(): Config {
return {
port: parseInt(process.env.PORT || '3018', 10),
photon: {
apiUrl: process.env.PHOTON_API_URL || 'https://photon.komoot.io',
},
photonSelf: {
// Opt-in: only registered when this env-var is explicitly set
// (e.g. http://192.168.178.11:2322 once the GPU server is up).
// Empty string → treated as unset so a stray "" in .env doesn't
// register a useless provider.
apiUrl: process.env.PHOTON_SELF_API_URL?.trim() || undefined,
},
nominatim: {
apiUrl: process.env.NOMINATIM_API_URL || 'https://nominatim.openstreetmap.org',
userAgent:
process.env.NOMINATIM_USER_AGENT ||
'mana-geocoding/1.0 (+https://mana.how; kontakt@memoro.ai)',
intervalMs: parseInt(process.env.NOMINATIM_INTERVAL_MS || '1100', 10),
},
cors: {
origins: (process.env.CORS_ORIGINS || 'http://localhost:5173').split(','),
},
cache: {
maxEntries: parseInt(process.env.CACHE_MAX_ENTRIES || '5000', 10),
ttlMs: parseInt(process.env.CACHE_TTL_MS || String(24 * 60 * 60 * 1000), 10),
publicTtlMs: parseInt(process.env.CACHE_PUBLIC_TTL_MS || String(60 * 60 * 1000), 10),
},
providers: {
// Default order (when GEOCODING_PROVIDERS is unset): try the
// self-hosted Photon first if it's been configured, then public
// providers as fallback. `photon-self` is silently dropped at
// chain-build time if `photonSelf.apiUrl` is undefined.
enabled: parseProviderList(process.env.GEOCODING_PROVIDERS, [
'photon-self',
'photon',
'nominatim',
]),
healthCacheMs: parseInt(process.env.PROVIDER_HEALTH_CACHE_MS || '30000', 10),
// 20 s default. Cold-start cross-LAN fetches to photon-self
// (mana-gpu over WSL2 mirrored networking) consistently take
// >10 s on the first probe and ~2 s once warm. Tighter timeouts
// false-marked photon-self unhealthy on every cold path, leaking
// to public photon for the duration of the 30 s health cache.
timeoutMs: parseInt(process.env.PROVIDER_TIMEOUT_MS || '20000', 10),
},
};
}
function parseProviderList(raw: string | undefined, fallback: ProviderName[]): ProviderName[] {
if (!raw) return fallback;
const valid: ProviderName[] = ['photon-self', 'photon', 'nominatim'];
const parsed = raw
.split(',')
.map((s) => s.trim().toLowerCase())
.filter((s): s is ProviderName => (valid as string[]).includes(s));
return parsed.length > 0 ? parsed : fallback;
}