|
|
@@ -1,81 +1,112 @@
|
|
|
+import { unstable_cache } from "next/cache";
|
|
|
+import { cache } from 'react';
|
|
|
import superjson from "superjson";
|
|
|
-import { z } from 'zod';
|
|
|
+import { z } from "zod";
|
|
|
|
|
|
+// Your imports
|
|
|
import { Cluster, clients, priceFeedsSchema } from "../services/pyth";
|
|
|
|
|
|
-export const getPublishersForFeed = async (
|
|
|
- cluster: Cluster,
|
|
|
-) => {
|
|
|
- "use cache";
|
|
|
- const start = performance.now();
|
|
|
- const data = await clients[cluster].getData();
|
|
|
+const getDataCached = cache(async (cluster: Cluster) => {
|
|
|
+ return clients[cluster].getData();
|
|
|
+});
|
|
|
+const MAX_CACHE_SIZE_STRING = 2 * 1024 * 1024;
|
|
|
+
|
|
|
+const getPublishersForFeed = unstable_cache(async (cluster: Cluster, chunk?: number) => {
|
|
|
+ const data = await getDataCached(cluster);
|
|
|
const result: Record<string, string[]> = {};
|
|
|
for (const key of data.productPrice.keys()) {
|
|
|
const price = data.productPrice.get(key);
|
|
|
result[key] = price?.priceComponents.map(({ publisher }) => publisher.toBase58()) ?? [];
|
|
|
}
|
|
|
- const end = performance.now();
|
|
|
- // eslint-disable-next-line no-console, @typescript-eslint/restrict-template-expressions
|
|
|
- console.log(`getPublishersForFeed: ${end - start}ms`);
|
|
|
- return result;
|
|
|
-};
|
|
|
+ const stringifiedResult = superjson.stringify(result);
|
|
|
|
|
|
-const getFeeds = async (cluster: Cluster) => {
|
|
|
- "use cache";
|
|
|
- const start = performance.now();
|
|
|
- const data = await clients[cluster].getData();
|
|
|
-
|
|
|
- const result = superjson.stringify(priceFeedsSchema.parse(data.symbols.filter(
|
|
|
- (symbol) =>
|
|
|
- data.productFromSymbol.get(symbol)?.display_symbol !== undefined,
|
|
|
- ).map((symbol) => ({
|
|
|
- symbol,
|
|
|
- product: data.productFromSymbol.get(symbol),
|
|
|
- price: {
|
|
|
- ...data.productPrice.get(symbol),
|
|
|
- priceComponents: data.productPrice.get(symbol)?.priceComponents.map(({ publisher }) => ({
|
|
|
- publisher: publisher.toBase58(),
|
|
|
- })) ?? [],
|
|
|
- },
|
|
|
- }))))
|
|
|
- const end = performance.now();
|
|
|
- // eslint-disable-next-line no-console, @typescript-eslint/restrict-template-expressions
|
|
|
- console.log(`getFeeds: ${end - start}ms`);
|
|
|
- return result;
|
|
|
-}
|
|
|
+ const chunksNumber = Math.ceil(stringifiedResult.length / MAX_CACHE_SIZE_STRING);
|
|
|
+ const chunks = [];
|
|
|
+ for(let i = 0; i < chunksNumber; i++) {
|
|
|
+ chunks.push(stringifiedResult.slice(i * MAX_CACHE_SIZE_STRING, (i + 1) * MAX_CACHE_SIZE_STRING));
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ chunk: chunks[chunk ?? 0],
|
|
|
+ chunksNumber,
|
|
|
+ };
|
|
|
+}, [], { revalidate: false });
|
|
|
|
|
|
-export const getFeedsForPublisherCached = async (
|
|
|
- cluster: Cluster,
|
|
|
- publisher: string,
|
|
|
-) => {
|
|
|
- const start = performance.now();
|
|
|
- const rawFeeds = await getFeeds(cluster);
|
|
|
- const feeds = superjson.parse<z.infer<typeof priceFeedsSchema>>(rawFeeds);
|
|
|
- const end = performance.now();
|
|
|
- // eslint-disable-next-line no-console, @typescript-eslint/restrict-template-expressions
|
|
|
- console.log(`getFeedsForPublisherCached: ${end - start}ms`);
|
|
|
- return priceFeedsSchema.parse(feeds.filter(({ price }) =>
|
|
|
- price.priceComponents.some(
|
|
|
- (component) => component.publisher.toString() === publisher,
|
|
|
- ),
|
|
|
- ));
|
|
|
-};
|
|
|
+const _getFeeds = unstable_cache(async (cluster: Cluster, chunk?: number) => {
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
+ console.log('getFeeds', cluster, chunk);
|
|
|
+ const data = await getDataCached(cluster);
|
|
|
+ const parsedData = priceFeedsSchema.parse(
|
|
|
+ data.symbols
|
|
|
+ .filter(
|
|
|
+ (symbol) =>
|
|
|
+ data.productFromSymbol.get(symbol)?.display_symbol !== undefined
|
|
|
+ )
|
|
|
+ .map((symbol) => ({
|
|
|
+ symbol,
|
|
|
+ product: data.productFromSymbol.get(symbol),
|
|
|
+ price: {
|
|
|
+ ...data.productPrice.get(symbol),
|
|
|
+ priceComponents:
|
|
|
+ data.productPrice
|
|
|
+ .get(symbol)
|
|
|
+ ?.priceComponents.map(({ publisher }) => ({
|
|
|
+ publisher: publisher.toBase58(),
|
|
|
+ })) ?? [],
|
|
|
+ },
|
|
|
+ }))
|
|
|
+ )
|
|
|
+ const result = superjson.stringify(
|
|
|
+ parsedData
|
|
|
+ );
|
|
|
+ const chunksNumber = Math.ceil(result.length / MAX_CACHE_SIZE_STRING);
|
|
|
+ const chunks = [];
|
|
|
+ for(let i = 0; i < chunksNumber; i++) {
|
|
|
+ chunks.push(result.slice(i * MAX_CACHE_SIZE_STRING, (i + 1) * MAX_CACHE_SIZE_STRING));
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ chunk: chunks[chunk ?? 0],
|
|
|
+ chunksNumber,
|
|
|
+ };
|
|
|
+}, [], { revalidate: false });
|
|
|
|
|
|
export const getFeedsCached = async (cluster: Cluster) => {
|
|
|
- "use cache";
|
|
|
const start = performance.now();
|
|
|
- const rawFeeds = await getFeeds(cluster);
|
|
|
+ const { chunk, chunksNumber } = await _getFeeds(cluster); // uses cache
|
|
|
+ const rawResults = await Promise.all(Array.from({ length: chunksNumber-1 }, (_, i) => _getFeeds(cluster, i+1)));
|
|
|
+ const rawJson = [chunk, ...rawResults.map(({ chunk }) => chunk)].join('');
|
|
|
+ const data = superjson.parse<z.infer<typeof priceFeedsSchema>>(rawJson);
|
|
|
const end = performance.now();
|
|
|
// eslint-disable-next-line no-console, @typescript-eslint/restrict-template-expressions
|
|
|
console.log(`getFeedsCached: ${end - start}ms`);
|
|
|
- return superjson.parse<z.infer<typeof priceFeedsSchema>>(rawFeeds);
|
|
|
+ return data;
|
|
|
};
|
|
|
|
|
|
export const getPublishersForFeedCached = async (cluster: Cluster, symbol: string) => {
|
|
|
const start = performance.now();
|
|
|
- const data = await getPublishersForFeed(cluster);
|
|
|
+ const { chunk, chunksNumber } = await getPublishersForFeed(cluster); // uses cache
|
|
|
+ const rawResults = await Promise.all(Array.from({ length: chunksNumber-1 }, (_, i) => getPublishersForFeed(cluster, i+1)));
|
|
|
+ const rawJson = [chunk, ...rawResults.map(({ chunk }) => chunk)].join('');
|
|
|
+ const data = superjson.parse<Record<string, string[]>>(rawJson);
|
|
|
const end = performance.now();
|
|
|
// eslint-disable-next-line no-console, @typescript-eslint/restrict-template-expressions
|
|
|
console.log(`getPublishersForFeedCached: ${end - start}ms`);
|
|
|
return data[symbol];
|
|
|
-};
|
|
|
+};
|
|
|
+
|
|
|
+export const getFeedsForPublisherCached = async (
|
|
|
+ cluster: Cluster,
|
|
|
+ publisher: string
|
|
|
+) => {
|
|
|
+ const start = performance.now();
|
|
|
+ const data = await getFeedsCached(cluster); // uses cache
|
|
|
+ const end = performance.now();
|
|
|
+ // eslint-disable-next-line no-console, @typescript-eslint/restrict-template-expressions
|
|
|
+ console.log(`getFeedsForPublisherCached: ${end - start}ms.`);
|
|
|
+ return priceFeedsSchema.parse(
|
|
|
+ data.filter(({ price }) =>
|
|
|
+ price.priceComponents.some(
|
|
|
+ (component) => component.publisher.toString() === publisher
|
|
|
+ )
|
|
|
+ )
|
|
|
+ );
|
|
|
+};
|