26 lines
2.3 KiB
TypeScript
26 lines
2.3 KiB
TypeScript
import { z } from "zod"
|
|
const timestamp = z.string().refine(v => Number.isFinite(Date.parse(v))).nullable()
|
|
const rate = z.number().finite().nonnegative().nullable()
|
|
const meta = z.object({ rxNow: z.boolean(), txNow: z.boolean(), rxSeries: z.array(z.boolean()), txSeries: z.array(z.boolean()),
|
|
sampledAt: timestamp, expiresAt: timestamp.optional(), lastAttemptAt: timestamp.optional(), lastSucceededAt: timestamp.optional(), error: z.string().nullable().optional(),
|
|
currentState: z.enum(["current", "unknown", "pending", "error", "stale", "disabled"]).optional() })
|
|
export type TrafficObservationMeta = z.infer<typeof meta>
|
|
const metrics = z.object({ volumeUnit: z.literal("GiB").optional(), rxNow: rate, txNow: rate, rxPeak: rate, txPeak: rate, rxTotal: rate, txTotal: rate,
|
|
rxSeries: z.array(rate), txSeries: z.array(rate), observations: meta.optional() }).refine(r =>
|
|
(!r.observations || r.observations.rxSeries.length === r.rxSeries.length && r.observations.txSeries.length === r.txSeries.length &&
|
|
(!(r.observations.rxNow || r.observations.txNow || r.observations.rxSeries.some(Boolean) || r.observations.txSeries.some(Boolean)) || r.observations.sampledAt !== null)))
|
|
/** Receipt of numeric legacy DTO values cannot establish an observed measurement. */
|
|
export function projectTrafficObservations(raw: unknown, now = Date.now()) {
|
|
const parsed = metrics.safeParse(raw)
|
|
if (!parsed.success) throw new Error("Некорректные измерения трафика")
|
|
const r = parsed.data, o = r.observations && { ...r.observations }
|
|
if (o && o.expiresAt && Date.parse(o.expiresAt) < now && o.currentState === "current") o.currentState = "stale"
|
|
const current = !o?.currentState || o.currentState === "current"
|
|
const rxSeries = r.rxSeries.map((v, i) => o?.rxSeries[i] ? v : null)
|
|
const txSeries = r.txSeries.map((v, i) => o?.txSeries[i] ? v : null)
|
|
return { observations: o, volumeUnit: r.volumeUnit, rxNow: current && o?.rxNow ? r.rxNow : null, txNow: current && o?.txNow ? r.txNow : null,
|
|
rxSeries, txSeries, rxPeak: rxSeries.some(v => v !== null) ? r.rxPeak : null,
|
|
txPeak: txSeries.some(v => v !== null) ? r.txPeak : null,
|
|
rxTotal: r.volumeUnit && rxSeries.some(v => v !== null) ? r.rxTotal : null, txTotal: r.volumeUnit && txSeries.some(v => v !== null) ? r.txTotal : null }
|
|
}
|