feat(market): add selectable chart ranges (24h/7d/30d/1y/all) with range-aware API caching and downsampling

This commit is contained in:
2026-03-10 19:25:23 +01:00
parent 54fe876a8c
commit 6cb038688f
8 changed files with 178 additions and 47 deletions

View File

@@ -1,5 +1,8 @@
import { z } from "zod";
export const MARKET_CHART_RANGES = ["24h", "7d", "30d", "1y", "all"] as const;
export type MarketChartRange = (typeof MARKET_CHART_RANGES)[number];
export interface CryptoMarketResponse {
code: string;
name: string;
@@ -7,7 +10,8 @@ export interface CryptoMarketResponse {
change24hPct: number | null;
marketCapUsd: number | null;
volume24hUsd: number | null;
priceHistory24h: Array<{
priceHistoryRange: MarketChartRange;
priceHistory: Array<{
timestamp: string;
priceUsd: number;
}>;
@@ -15,26 +19,38 @@ export interface CryptoMarketResponse {
source: string;
}
const marketResponseSchema = z.object({
code: z.string(),
name: z.string(),
const pointSchema = z.object({
timestamp: z.string(),
priceUsd: z.number().positive(),
change24hPct: z.number().nullable(),
marketCapUsd: z.number().nullable(),
volume24hUsd: z.number().nullable(),
priceHistory24h: z
.array(
z.object({
timestamp: z.string(),
priceUsd: z.number().positive(),
}),
)
.optional()
.default([]),
updatedAt: z.string(),
source: z.string(),
});
const marketResponseSchema = z
.object({
code: z.string(),
name: z.string(),
priceUsd: z.number().positive(),
change24hPct: z.number().nullable(),
marketCapUsd: z.number().nullable(),
volume24hUsd: z.number().nullable(),
priceHistoryRange: z.enum(MARKET_CHART_RANGES),
priceHistory: z.array(pointSchema).optional(),
priceHistory24h: z.array(pointSchema).optional(),
updatedAt: z.string(),
source: z.string(),
})
.transform((value) => ({
code: value.code,
name: value.name,
priceUsd: value.priceUsd,
change24hPct: value.change24hPct,
marketCapUsd: value.marketCapUsd,
volume24hUsd: value.volume24hUsd,
priceHistoryRange: value.priceHistoryRange,
priceHistory: value.priceHistory ?? value.priceHistory24h ?? [],
updatedAt: value.updatedAt,
source: value.source,
}));
export function parseCryptoMarketResponse(
payload: unknown,
): CryptoMarketResponse {