import type { FleetDataSource, FleetSnapshot } from './fleet-page.js'; import type { FleetInstance, FleetStatus } from './fleet-table-view-model.js'; const integer = (value: unknown): value is number => typeof value === 'number' && Number.isSafeInteger(value); const finite = (value: unknown): value is number => typeof value === 'number' && Number.isFinite(value); const string = (value: unknown): value is string => typeof value === 'string' && value.length > 0; const record = (value: unknown): Record | undefined => typeof value === 'object' && value !== null && !Array.isArray(value) ? (value as Record) : undefined; function parseInstance(value: unknown): FleetInstance | undefined { const item = record(value); if ( !item || !string(item.id) || !string(item.name) || !string(item.origin) || !Array.isArray(item.tags) || !item.tags.every((tag) => typeof tag === 'string') ) return undefined; const revision = integer(item.revision) && item.revision > 0 ? (item.revision as number) : undefined; return { id: item.id, name: item.name, url: item.origin, tags: item.tags as string[], ...(revision === undefined ? {} : { revision }), }; } function parseResources(value: unknown): NonNullable { const body = record(value) ?? {}; const resources: { cpuPercent?: number; memoryPercent?: number; maxTemperatureCelsius?: number; phoneNumbers?: string[]; } = {}; if (finite(body.cpuPercent)) resources.cpuPercent = body.cpuPercent; if (finite(body.memoryPercent)) resources.memoryPercent = body.memoryPercent; if (finite(body.maxTemperatureCelsius)) resources.maxTemperatureCelsius = body.maxTemperatureCelsius; if ( Array.isArray(body.phoneNumbers) && body.phoneNumbers.every((item) => typeof item === 'string' && item.length > 0) ) resources.phoneNumbers = body.phoneNumbers as string[]; return { freshness: 'fresh', ...(Object.keys(resources).length > 0 ? { resources } : {}), }; } async function readJson(response: Response): Promise { try { return await response.json(); } catch { return undefined; } } function skeletonStatuses(instances: readonly FleetInstance[]): Map { return new Map( instances.map((instance) => [ instance.id, { reachable: true, authenticated: true, summary: { freshness: 'unknown' }, }, ]), ); } export function createFleetApiDataSource(fetcher: typeof fetch = fetch): FleetDataSource { return { async load(signal, onPartial) { const listResponse = await fetcher('/api/v1/instances', { method: 'GET', credentials: 'same-origin', headers: { accept: 'application/json' }, ...(signal ? { signal } : {}), }); const listBody = record(await readJson(listResponse)); if ( !listResponse.ok || !listBody || !Array.isArray(listBody.items) || !record(listBody.page) ) throw new Error('Fleet response is invalid.'); const instances = listBody.items.map(parseInstance); if (instances.some((item) => !item)) throw new Error('Fleet response is invalid.'); const readyInstances = instances as FleetInstance[]; const partial: FleetSnapshot = { instances: readyInstances, statuses: skeletonStatuses(readyInstances), }; onPartial?.(partial); const statuses = new Map( await Promise.all( readyInstances.map(async (instance) => { try { const response = await fetcher( `/api/v1/instances/${encodeURIComponent(instance.id)}/resources`, { method: 'GET', credentials: 'same-origin', headers: { accept: 'application/json' }, ...(signal ? { signal } : {}), }, ); if (!response.ok) throw new Error('resource unavailable'); const status: FleetStatus = { reachable: true, authenticated: true, summary: parseResources(await readJson(response)), }; return [instance.id, status] as const; } catch { const status: FleetStatus = { reachable: true, authenticated: true, summary: { freshness: 'unknown' }, }; return [instance.id, status] as const; } }), ), ); const complete: FleetSnapshot = { instances: readyInstances, statuses }; onPartial?.(complete); return complete; }, }; }