Skip to content

Commit fcbda8a

Browse files
committed
feat: more endpoint fetching
1 parent 8b2719c commit fcbda8a

File tree

10 files changed

+82
-60
lines changed

10 files changed

+82
-60
lines changed

apps/insights/src/app/api/pyth/get-feeds/[symbol]/route.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
1+
import { NextResponse } from "next/server";
12
import { stringify } from 'superjson';
23

34
import { getFeedsCached } from "../../../../../server/pyth/get-feeds";
45
import { Cluster } from "../../../../../services/pyth";
56

6-
export const GET = async (_: Request, { params }: { params: Promise<{ symbol: string }> }) => {
7+
export const GET = async (request: Request, { params }: { params: Promise<{ symbol: string }> }) => {
78
const { symbol } = await params;
8-
9-
const feeds = await getFeedsCached(Cluster.Pythnet);
9+
const { searchParams } = new URL(request.url);
10+
const cluster = Number.parseInt(searchParams.get("cluster") ?? Cluster.Pythnet.toString()) as Cluster;
11+
// check if cluster is valid
12+
if (cluster && !Object.values(Cluster).includes(cluster)) {
13+
return NextResponse.json({ error: "Invalid cluster" }, { status: 400 });
14+
}
15+
const feeds = await getFeedsCached(cluster);
1016
const feed = feeds.find((feed) => feed.symbol === symbol);
1117
return new Response(stringify(feed), {
1218
headers: {

apps/insights/src/components/PriceFeed/get-feed.tsx

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import { parse } from "superjson";
22
import { z } from "zod";
33

44
import { PUBLIC_URL, VERCEL_AUTOMATION_BYPASS_SECRET } from '../../config/server';
5+
import { getFeedForSymbolCached } from '../../server/pyth';
56
import { Cluster, priceFeedsSchema } from "../../services/pyth";
7+
import { DEFAULT_CACHE_TTL } from '../../utils/cache';
68

79
export const getFeed = async (params: Promise<{ slug: string }>) => {
810
const data = await fetch(`${PUBLIC_URL}/api/pyth/get-feeds?cluster=${Cluster.Pythnet.toString()}&excludePriceComponents=true`, {
911
next: {
10-
revalidate: 1000 * 60 * 60 * 24,
12+
revalidate: DEFAULT_CACHE_TTL,
1113
},
1214
headers: {
1315
'x-vercel-protection-bypass': VERCEL_AUTOMATION_BYPASS_SECRET,
@@ -18,20 +20,11 @@ export const getFeed = async (params: Promise<{ slug: string }>) => {
1820

1921
const { slug } = await params;
2022
const symbol = decodeURIComponent(slug);
21-
const feed = await fetch(`${PUBLIC_URL}/api/pyth/get-feeds/${encodeURIComponent(symbol)}`, {
22-
next: {
23-
revalidate: 1000 * 60 * 60 * 24,
24-
},
25-
headers: {
26-
'x-vercel-protection-bypass': VERCEL_AUTOMATION_BYPASS_SECRET,
27-
},
28-
});
29-
const feedJson = await feed.text();
30-
const feedData: z.infer<typeof priceFeedsSchema>[0] = parse(feedJson);
23+
const feed = await getFeedForSymbolCached({symbol, cluster: Cluster.Pythnet});
3124

3225
return {
3326
feeds,
34-
feed: feedData,
27+
feed,
3528
symbol,
3629
} as const;
3730
};

apps/insights/src/components/PriceFeed/publishers.tsx

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import { lookup as lookupPublisher } from "@pythnetwork/known-publishers";
22
import { notFound } from "next/navigation";
33

44
import { getRankingsBySymbolCached } from '../../server/clickhouse';
5-
import { getPublishersForFeedCached } from "../../server/pyth";
6-
import { getFeeds } from "../../server/pyth/get-feeds";
5+
import { getFeedForSymbolCached, getPublishersForFeedCached } from "../../server/pyth";
76
import {
87
Cluster,
98
ClusterToName,
@@ -23,25 +22,22 @@ type Props = {
2322
export const Publishers = async ({ params }: Props) => {
2423
const { slug } = await params;
2524
const symbol = decodeURIComponent(slug);
25+
2626
const start = Date.now();
2727
const [
28-
pythnetFeeds,
29-
pythtestConformanceFeeds,
28+
feed,
29+
testFeed,
3030
pythnetPublishers,
3131
pythtestConformancePublishers,
3232
] = await Promise.all([
33-
getFeeds(Cluster.Pythnet),
34-
getFeeds(Cluster.PythtestConformance),
33+
getFeedForSymbolCached({symbol, cluster: Cluster.Pythnet}),
34+
getFeedForSymbolCached({symbol, cluster: Cluster.PythtestConformance}),
3535
getPublishers(Cluster.Pythnet, symbol),
3636
getPublishers(Cluster.PythtestConformance, symbol),
3737
]);
3838
const end = Date.now();
3939
// eslint-disable-next-line no-console
4040
console.info(`Publishers took ${(end - start).toString()}ms`);
41-
const feed = pythnetFeeds.find((feed) => feed.symbol === symbol);
42-
const testFeed = pythtestConformanceFeeds.find(
43-
(feed) => feed.symbol === symbol,
44-
);
4541
const publishers = [...pythnetPublishers, ...pythtestConformancePublishers];
4642
const metricsTime = pythnetPublishers.find(
4743
(publisher) => publisher.ranking !== undefined,

apps/insights/src/config/server.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,10 @@ export function getRedis(): Redis {
6969
if (!host || !port) {
7070
throw new Error('REDIS_HOST, and REDIS_PORT must be set');
7171
}
72-
redisClient ??= new Redis({
72+
if(redisClient) {
73+
return redisClient;
74+
}
75+
redisClient = new Redis({
7376
username: 'default',
7477
password: password ?? '',
7578
host,

apps/insights/src/server/clickhouse.ts

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,45 +3,30 @@ import { redisCache } from '../utils/cache';
33

44
export const getRankingsBySymbolCached = redisCache.define(
55
"getRankingsBySymbol",
6-
{
7-
ttl: 1000 * 60 * 60 * 24,
8-
},
96
getRankingsBySymbol,
107
).getRankingsBySymbol;
118

129
export const getRankingsByPublisherCached = redisCache.define(
1310
"getRankingsByPublisher",
14-
{
15-
ttl: 1000 * 60 * 60 * 24,
16-
},
1711
getRankingsByPublisher,
1812
).getRankingsByPublisher;
1913

2014

2115
export const getPublishersCached = redisCache.define(
2216
"getPublishers",
23-
{
24-
ttl: 1000 * 60 * 60 * 24,
25-
},
2617
getPublishers,
2718
).getPublishers;
2819

2920
export const getPublisherAverageScoreHistoryCached = redisCache.define(
3021
"getPublisherAverageScoreHistory",
31-
{
32-
ttl: 1000 * 60 * 60 * 24,
33-
},
3422
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
3523
// @ts-expect-error
3624
getPublisherAverageScoreHistory,
37-
).getPublisherAverageScoreHistory as typeof getPublisherAverageScoreHistory;
25+
).getPublisherAverageScoreHistory
3826

3927
export const getPublisherRankingHistoryCached = redisCache.define(
4028
"getPublisherRankingHistory",
41-
{
42-
ttl: 1000 * 60 * 60 * 24,
43-
},
4429
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
4530
// @ts-expect-error
4631
getPublisherRankingHistory,
47-
).getPublisherRankingHistory as typeof getPublisherRankingHistory;
32+
).getPublisherRankingHistory

apps/insights/src/server/pyth.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { z } from "zod";
33

44
import { PUBLIC_URL, VERCEL_AUTOMATION_BYPASS_SECRET } from '../config/server';
55
import { Cluster, priceFeedsSchema } from "../services/pyth";
6+
import { DEFAULT_CACHE_TTL } from "../utils/cache";
67

78
// Convenience helpers matching your previous functions
89
export async function getPublishersForFeedCached(
@@ -11,7 +12,7 @@ export async function getPublishersForFeedCached(
1112
) {
1213
const data = await fetch(`${PUBLIC_URL}/api/pyth/get-publishers/${encodeURIComponent(symbol)}?cluster=${cluster.toString()}`, {
1314
next: {
14-
revalidate: 1000 * 60 * 60 * 24,
15+
revalidate: DEFAULT_CACHE_TTL,
1516
},
1617
headers: {
1718
'x-vercel-protection-bypass': VERCEL_AUTOMATION_BYPASS_SECRET,
@@ -26,12 +27,44 @@ export async function getFeedsForPublisherCached(
2627
) {
2728
const data = await fetch(`${PUBLIC_URL}/api/pyth/get-feeds-for-publisher/${encodeURIComponent(publisher)}?cluster=${cluster.toString()}`, {
2829
next: {
29-
revalidate: 1000 * 60 * 60 * 24,
30+
revalidate: DEFAULT_CACHE_TTL,
3031
},
3132
headers: {
3233
'x-vercel-protection-bypass': VERCEL_AUTOMATION_BYPASS_SECRET,
3334
},
3435
});
3536
const rawData = await data.text();
3637
return parse<z.infer<typeof priceFeedsSchema>>(rawData);
38+
}
39+
40+
export const getFeedsCached = async (cluster: Cluster) => {
41+
const data = await fetch(`${PUBLIC_URL}/api/pyth/get-feeds?cluster=${cluster.toString()}&excludePriceComponents=true`, {
42+
next: {
43+
revalidate: DEFAULT_CACHE_TTL,
44+
},
45+
headers: {
46+
'x-vercel-protection-bypass': VERCEL_AUTOMATION_BYPASS_SECRET,
47+
},
48+
});
49+
const dataJson = await data.text();
50+
const feeds: z.infer<typeof priceFeedsSchema> = parse(dataJson);
51+
return feeds;
52+
}
53+
54+
export const getFeedForSymbolCached = async ({symbol, cluster = Cluster.Pythnet}: {symbol: string, cluster?: Cluster}): Promise<z.infer<typeof priceFeedsSchema>[0] | undefined> => {
55+
const data = await fetch(`${PUBLIC_URL}/api/pyth/get-feeds/${encodeURIComponent(symbol)}?cluster=${cluster.toString()}`, {
56+
next: {
57+
revalidate: DEFAULT_CACHE_TTL,
58+
},
59+
headers: {
60+
'x-vercel-protection-bypass': VERCEL_AUTOMATION_BYPASS_SECRET,
61+
},
62+
});
63+
64+
if(!data.ok) {
65+
return undefined;
66+
}
67+
const dataJson = await data.text();
68+
const feed: z.infer<typeof priceFeedsSchema>[0] = parse(dataJson);
69+
return feed;
3770
}

apps/insights/src/server/pyth/get-feeds.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { z } from 'zod';
22

33
import { getPythMetadata } from './get-metadata';
44
import { Cluster, priceFeedsSchema } from "../../services/pyth";
5-
import { redisCache } from '../../utils/cache';
5+
import { DEFAULT_CACHE_TTL, redisCache } from '../../utils/cache';
66

77
const _getFeeds = async (cluster: Cluster) => {
88
const unfilteredData = await getPythMetadata(cluster);
@@ -32,7 +32,7 @@ const _getFeeds = async (cluster: Cluster) => {
3232
export const getFeedsCached = redisCache.define(
3333
"getFeeds",
3434
{
35-
ttl: 1000 * 60 * 60 * 24,
35+
ttl: DEFAULT_CACHE_TTL,
3636
},
3737
_getFeeds,
3838
).getFeeds;
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11

22
import { clients, Cluster } from '../../services/pyth';
3-
import { memoryOnlyCache } from '../../utils/cache';
3+
import { DEFAULT_CACHE_TTL, memoryOnlyCache } from '../../utils/cache';
44

55
const _getPythMetadata = async (cluster: Cluster) => {
66
// Fetch fresh data from Pyth client
@@ -9,7 +9,7 @@ const _getPythMetadata = async (cluster: Cluster) => {
99

1010
export const getPythMetadata = memoryOnlyCache.define(
1111
"getPythMetadata",
12-
{ttl: 1000 * 60 * 60 * 24},
12+
{ttl: DEFAULT_CACHE_TTL},
1313
_getPythMetadata,
1414
).getPythMetadata;
1515

apps/insights/src/server/pyth/get-publishers-for-cluster.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { getPythMetadata } from './get-metadata';
22
import { Cluster } from '../../services/pyth';
3-
import { redisCache } from '../../utils/cache';
3+
import { DEFAULT_CACHE_TTL, redisCache } from '../../utils/cache';
44

55
const _computePublishers = async (cluster: Cluster) => {
66
const data = await getPythMetadata(cluster);
@@ -16,7 +16,7 @@ const _computePublishers = async (cluster: Cluster) => {
1616
export const getPublishersForCluster = redisCache.define(
1717
"getPublishersForCluster",
1818
{
19-
ttl: 1000 * 60 * 60 * 24,
19+
ttl: DEFAULT_CACHE_TTL,
2020
},
2121
_computePublishers,
2222
).getPublishersForCluster;

apps/insights/src/utils/cache.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
import type { Cache as ACDCache } from "async-cache-dedupe";
22
import { createCache } from "async-cache-dedupe";
3-
import { serialize, deserialize } from "superjson";
3+
import { stringify, parse } from "superjson";
44

55
import { getRedis } from '../config/server';
66

7-
// L2-backed cache: in-memory LRU (L1) + Redis (L2)
7+
const transformer = {
8+
serialize: stringify,
9+
deserialize: parse,
10+
};
11+
12+
export const DEFAULT_CACHE_TTL = 86_400; // 24 hours
13+
export const DEFAULT_CACHE_STALE = 86_400; // 24 hours
14+
815
export const redisCache: ACDCache = createCache({
9-
transformer: {
10-
serialize,
11-
deserialize,
12-
},
16+
transformer,
17+
stale: DEFAULT_CACHE_STALE,
18+
ttl: DEFAULT_CACHE_TTL,
1319
storage: {
1420
type: "redis",
1521
options: {
@@ -19,6 +25,6 @@ export const redisCache: ACDCache = createCache({
1925
});
2026

2127
export const memoryOnlyCache: ACDCache = createCache({
22-
ttl: 5000,
23-
stale: 2000,
24-
});
28+
ttl: DEFAULT_CACHE_TTL,
29+
stale: DEFAULT_CACHE_STALE,
30+
});

0 commit comments

Comments
 (0)