Monorepo consolidation: workspace, shared types, transport plans, docker/swam assets
This commit is contained in:
122
control/ui/src/api/client.ts
Normal file
122
control/ui/src/api/client.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
type RequestIds = {
|
||||
requestId: string
|
||||
correlationId?: string
|
||||
traceparent?: string
|
||||
}
|
||||
|
||||
const LAST_IDS_STORAGE_KEY = 'control:last_request_ids'
|
||||
|
||||
export class ApiError extends Error {
|
||||
status: number
|
||||
requestId: string
|
||||
correlationId?: string
|
||||
traceparent?: string
|
||||
|
||||
constructor(args: {
|
||||
status: number
|
||||
message: string
|
||||
requestId: string
|
||||
correlationId?: string
|
||||
traceparent?: string
|
||||
}) {
|
||||
super(args.message)
|
||||
this.name = 'ApiError'
|
||||
this.status = args.status
|
||||
this.requestId = args.requestId
|
||||
this.correlationId = args.correlationId
|
||||
this.traceparent = args.traceparent
|
||||
}
|
||||
}
|
||||
|
||||
const state: {
|
||||
last?: RequestIds
|
||||
} = {}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null
|
||||
}
|
||||
|
||||
function loadLastIds(): RequestIds | undefined {
|
||||
try {
|
||||
const raw = localStorage.getItem(LAST_IDS_STORAGE_KEY)
|
||||
if (!raw) return undefined
|
||||
const parsed = JSON.parse(raw) as unknown
|
||||
if (isRecord(parsed) && typeof parsed.requestId === 'string') {
|
||||
const correlationId =
|
||||
typeof parsed.correlationId === 'string' ? parsed.correlationId : undefined
|
||||
const traceparent =
|
||||
typeof parsed.traceparent === 'string' ? parsed.traceparent : undefined
|
||||
return { requestId: parsed.requestId, correlationId, traceparent }
|
||||
}
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
function persistLastIds(ids: RequestIds) {
|
||||
try {
|
||||
localStorage.setItem(LAST_IDS_STORAGE_KEY, JSON.stringify(ids))
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function newRequestId(): string {
|
||||
if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {
|
||||
return crypto.randomUUID()
|
||||
}
|
||||
return `${Date.now()}-${Math.random().toString(16).slice(2)}`
|
||||
}
|
||||
|
||||
export function getLastRequestIds(): RequestIds | undefined {
|
||||
return state.last ?? loadLastIds()
|
||||
}
|
||||
|
||||
type ApiRequestInit = RequestInit & {
|
||||
correlationId?: string
|
||||
traceparent?: string
|
||||
useLastCorrelationId?: boolean
|
||||
useLastTraceparent?: boolean
|
||||
}
|
||||
|
||||
export async function apiFetch(
|
||||
input: RequestInfo | URL,
|
||||
init?: ApiRequestInit,
|
||||
) {
|
||||
const requestId = newRequestId()
|
||||
|
||||
const headers = new Headers(init?.headers)
|
||||
headers.set('x-request-id', requestId)
|
||||
const last = getLastRequestIds()
|
||||
const correlationId =
|
||||
init?.correlationId ?? (init?.useLastCorrelationId ? last?.correlationId : undefined)
|
||||
const traceparent =
|
||||
init?.traceparent ?? (init?.useLastTraceparent ? last?.traceparent : undefined)
|
||||
|
||||
if (correlationId) headers.set('x-correlation-id', correlationId)
|
||||
if (traceparent) headers.set('traceparent', traceparent)
|
||||
|
||||
const res = await fetch(input, { ...init, headers })
|
||||
const resCorrelationId = res.headers.get('x-correlation-id') ?? correlationId ?? undefined
|
||||
const resTraceparent = res.headers.get('traceparent') ?? traceparent ?? undefined
|
||||
const ids = { requestId, correlationId: resCorrelationId, traceparent: resTraceparent }
|
||||
state.last = ids
|
||||
persistLastIds(ids)
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text().catch(() => '')
|
||||
const err = new ApiError({
|
||||
status: res.status,
|
||||
requestId,
|
||||
correlationId: resCorrelationId,
|
||||
traceparent: resTraceparent,
|
||||
message: `API error ${res.status}${text ? `: ${text}` : ''} (request_id=${requestId}${
|
||||
resCorrelationId ? ` correlation_id=${resCorrelationId}` : ''
|
||||
})`,
|
||||
})
|
||||
throw err
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
Reference in New Issue
Block a user