/** * PeakPulse backend client — typed HTTP wrappers for the peakpulse-backend (port 4010). * * Auth: Bearer token from the caller's JWT (same JWT_SECRET as platform-service). */ import { config } from './config.js'; export interface PeakPulseClientOptions { token?: string; requestId?: string; productId?: string; } async function peakpulseFetch( path: string, init: RequestInit, opts: PeakPulseClientOptions ): Promise { const headers: Record = { 'Content-Type': 'application/json', ...(opts.token ? { Authorization: `Bearer ${opts.token}` } : {}), ...(opts.requestId ? { 'x-request-id': opts.requestId } : {}), ...(opts.productId ? { 'x-product-id': opts.productId } : { 'x-product-id': 'peakpulse' }), }; const res = await fetch(`${config.PEAKPULSE_BACKEND_URL}${path}`, { ...init, headers: { ...((init.headers as Record) ?? {}), ...headers }, signal: AbortSignal.timeout(15_000), }); if (!res.ok) { const body = await res.text().catch(() => ''); throw new Error(`peakpulse-backend ${init.method ?? 'GET'} ${path} → ${res.status}: ${body}`); } return res.json() as Promise; } // ── Sessions ─────────────────────────────────────────────────────────────── export interface PeakSessionDoc { id: string; clientId?: string; userId: string; productId: string; activityType: 'hiking' | 'skiing' | 'cycling' | 'running'; status: 'active' | 'completed' | 'paused'; startTime: string; endTime?: string; durationSeconds?: number; distanceMeters?: number; maxSpeedMps?: number; averageSpeedMps?: number; elevationGainMeters?: number; elevationLossMeters?: number; maxElevationMeters?: number; locationName?: string; startLatitude?: number; startLongitude?: number; unitPreference?: string; weather?: { tempCelsius?: number; conditionCode?: string; windSpeedKph?: number; uvIndex?: number; }; skiMetrics?: { runCount?: number; verticalDescentMeters?: number; liftTimeSeconds?: number; skiTimeSeconds?: number; }; trackPointCount?: number; hapticMilestoneCount?: number; savedToHealthKit?: boolean; notes?: string; createdAt: string; updatedAt: string; } export interface PeakSessionExport { exportVersion: string; productId: string; session: Omit< PeakSessionDoc, 'userId' | 'clientId' | 'savedToHealthKit' | 'createdAt' | 'updatedAt' >; exportedAt: string; } export function peakpulseSessionsList( params: { activityType?: string; status?: string; limit?: number; offset?: number; }, opts: PeakPulseClientOptions ): Promise<{ items: PeakSessionDoc[]; total: number }> { const qs = new URLSearchParams(); if (params.activityType) qs.set('activityType', params.activityType); if (params.status) qs.set('status', params.status); if (params.limit !== undefined) qs.set('limit', String(params.limit)); if (params.offset !== undefined) qs.set('offset', String(params.offset)); const q = qs.toString(); return peakpulseFetch(`/peak/sessions${q ? `?${q}` : ''}`, { method: 'GET' }, opts); } export function peakpulseSessionExport( sessionId: string, opts: PeakPulseClientOptions ): Promise { return peakpulseFetch(`/peak/sessions/${sessionId}/export`, { method: 'GET' }, opts); } export function peakpulseGetStats(opts: PeakPulseClientOptions): Promise> { return peakpulseFetch('/peak/stats', { method: 'GET' }, opts); } // ── Routes ───────────────────────────────────────────────────────────────── export interface PeakRouteDoc { id: string; sessionId: string; userId: string; productId: string; trackPoints: unknown[]; hapticEvents: unknown[]; trackPointCount: number; hapticEventCount: number; boundingBox?: { minLat: number; maxLat: number; minLon: number; maxLon: number; }; createdAt: string; updatedAt: string; } export function peakpulseRouteGet( sessionId: string, opts: PeakPulseClientOptions ): Promise { return peakpulseFetch(`/peak/routes/${sessionId}`, { method: 'GET' }, opts); }