diff --git a/README.md b/README.md index c596342dce..3486cd29fb 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,13 @@ Therefore applications usually go either: - "fully centralized" Applications around aave governance exist on both sides of the spectrum, but there's currently no agnostic solution that fits both needs. -To close this gap `@bgd-labs/aave-v3-governance-cache`(temporary name) offers a common interface and a variety of adapters for interfaces to go their own way. +To close this gap `@bgd-labs/aave-v3-governance-cache`(temporary name) offers a common interface and a variety of provider for interfaces to go their own way. -## Adapters +## Providers ### Local Cache -The `localCache` is a simple file based cache that fetches "new" data based on the last seen block and stores them in json files per proposal. +The `customStorageProvider` is a simple file based cache that fetches "new" data based on the last seen block and stores them in json files per proposal. If no cache is found the cache will try to refresh automatically. ``` diff --git a/package.json b/package.json index 1cf3cc9e67..26fa0faf3d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bgd-labs/aave-v3-governance-cache", - "version": "1.0.1", + "version": "1.0.2", "sideEffects": false, "files": [ "cache", @@ -17,12 +17,16 @@ "default": "./dist/index.js" }, "./localCache": { - "types": "./dist/localCache.d.ts", - "default": "./dist/adapter/localCache.js" + "types": "./dist/customStorageProvider.d.ts", + "default": "./dist/providers/customStorageProvider.js" }, - "./githubHybrid": { - "types": "./dist/githubHybrid.d.ts", - "default": "./dist/adapter/githubHybrid.js" + "./github": { + "types": "./dist/githubPagesProvider.d.ts", + "default": "./dist/providers/githubPagesProvider.js" + }, + "./fallbackProvider": { + "types": "./dist/fallbackProvider.d.ts", + "default": "./dist/providers/fallbackProvider.js" }, "./refreshCache": { "types": "./dist/refreshCache.d.ts", diff --git a/scripts/build.ts b/scripts/build.ts index 9219eb0dc5..c9b5854cc8 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -3,9 +3,11 @@ import dts from 'bun-plugin-dts'; await Bun.build({ entrypoints: [ 'src/index.ts', - 'src/adapter/localCache.ts', - 'src/adapter/noCache.ts', - 'src/adapter/githubHybrid.ts', + 'src/providers/customStorageProvider.ts', + 'src/providers/storage/fileSystem.ts', + 'src/providers/rpcProvider.ts', + 'src/providers/fallbackProvider.ts', + 'src/providers/githubPagesProvider.ts', 'src/common/refreshCache.ts', ], outdir: './dist', diff --git a/scripts/refreshCache.ts b/scripts/refreshCache.ts index ff5b141317..b17f3eefd9 100644 --- a/scripts/refreshCache.ts +++ b/scripts/refreshCache.ts @@ -1,5 +1,7 @@ -import {githubHybridCacheAdapter} from '../src/adapter/githubHybrid'; -import {localCacheAdapter} from '../src/adapter/localCache'; +import {customStorageProvider} from '../src/providers/customStorageProvider'; import {refreshCache} from '../src/common/refreshCache'; +import {fileSystemStorage} from '../dist/fileSystem'; -refreshCache(githubHybridCacheAdapter(localCacheAdapter)); +const provider = customStorageProvider(fileSystemStorage); + +refreshCache(provider); diff --git a/src/adapter/githubHybrid.ts b/src/adapter/githubHybrid.ts deleted file mode 100644 index 2f95f23e96..0000000000 --- a/src/adapter/githubHybrid.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Hybrid adapter that tries to fetch via github pages - * If github pages doesn't contain the desired data it will fallback to the supplied adapter - */ -import type {GovernanceCacheAdapter, PayloadCacheRaw, ProposalCacheRaw} from '..'; -import {formatPayloadLogs} from '../common/payloadsController'; -import {formatProposalLogs} from '../common/governance'; - -function getPath() { - return 'https://bgd-labs.github.io/v3-governance-cache/'; -} - -export const githubHybridCacheAdapter = ( - fallbackAdapter: GovernanceCacheAdapter, -): GovernanceCacheAdapter => ({ - async getPayload({chainId, payloadsController, payloadId}) { - const path = `${chainId.toString()}/${payloadsController}/payloads`; - try { - const cache = (await (await fetch(getPath() + path)).json()) as PayloadCacheRaw; - return {payload: cache.payload!, logs: formatPayloadLogs(cache.events)}; - } catch (e) { - await fallbackAdapter.syncPayloadsCache({chainId, payloadsController}); - return fallbackAdapter.getPayload({chainId, payloadsController, payloadId}); - } - }, - async getProposal({chainId, governance, proposalId}) { - const path = `${chainId.toString()}/${governance}/proposals`; - try { - const cache = (await (await fetch(getPath() + path)).json()) as ProposalCacheRaw; - return {proposal: cache.proposal!, logs: formatProposalLogs(cache.events), ipfs: cache.ipfs!}; - } catch (e) { - await fallbackAdapter.syncProposalCache({chainId, governance}); - return fallbackAdapter.getProposal({chainId, governance, proposalId}); - } - }, - async syncProposalCache({chainId, governance}) { - return fallbackAdapter.syncProposalCache({chainId, governance}); - }, - async syncPayloadsCache({chainId, payloadsController}) { - return fallbackAdapter.syncPayloadsCache({chainId, payloadsController}); - }, -}); diff --git a/src/common/refreshCache.ts b/src/common/refreshCache.ts index 46ceaff08e..7f41d127b6 100644 --- a/src/common/refreshCache.ts +++ b/src/common/refreshCache.ts @@ -11,9 +11,9 @@ import { GovernanceV3Scroll, GovernanceV3Base, } from '@bgd-labs/aave-address-book'; -import type {GovernanceCacheAdapter} from '..'; +import type {GovernanceCacheAdapterWithSync} from '..'; -export async function refreshCache(adapter: GovernanceCacheAdapter) { +export async function refreshCache(adapter: GovernanceCacheAdapterWithSync) { await adapter.syncProposalCache({ chainId: 1, governance: GovernanceV3Ethereum.GOVERNANCE, diff --git a/src/errors.ts b/src/errors.ts new file mode 100644 index 0000000000..b6d7f80d2d --- /dev/null +++ b/src/errors.ts @@ -0,0 +1,2 @@ +export const ISSUES_FETCHING_PAYLOAD = 'ISSUES_FETCHING_PAYLOAD'; +export const ISSUES_FETCHING_PROPOSAL = 'ISSUES_FETCHING_PROPOSAL'; diff --git a/src/index.ts b/src/index.ts index 33d00d2d49..882f066e54 100644 --- a/src/index.ts +++ b/src/index.ts @@ -46,8 +46,6 @@ export interface GetProposalReturnType { } export interface GovernanceCacheAdapter { - syncPayloadsCache: (args: {chainId: number; payloadsController: Address}) => any; - syncProposalCache: (args: {chainId: number; governance: Address}) => any; getPayload: (args: { chainId: number; payloadsController: Address; @@ -60,6 +58,11 @@ export interface GovernanceCacheAdapter { }) => GetProposalReturnType | Promise; } +export interface GovernanceCacheAdapterWithSync extends GovernanceCacheAdapter { + syncPayloadsCache: (args: {chainId: number; payloadsController: Address}) => any; + syncProposalCache: (args: {chainId: number; governance: Address}) => any; +} + /** * Proposal types */ diff --git a/src/providers/customStorageProvider.ts b/src/providers/customStorageProvider.ts new file mode 100644 index 0000000000..bab7802665 --- /dev/null +++ b/src/providers/customStorageProvider.ts @@ -0,0 +1,31 @@ +import {type GovernanceCacheAdapterWithSync} from '..'; +import {ISSUES_FETCHING_PAYLOAD, ISSUES_FETCHING_PROPOSAL} from '../errors'; + +export const customStorageProvider = ( + adapter: GovernanceCacheAdapterWithSync, +): GovernanceCacheAdapterWithSync => ({ + async getPayload(args) { + let cache = await adapter.getPayload(args); + if (!cache) { + await adapter.syncPayloadsCache(args); + cache = await adapter.getPayload(args); + if (!cache) throw new Error(ISSUES_FETCHING_PAYLOAD); + } + return cache; + }, + async getProposal(args) { + let cache = await adapter.getProposal(args); + if (!cache) { + await adapter.syncProposalCache(args); + cache = await adapter.getProposal(args); + if (!cache) throw new Error(ISSUES_FETCHING_PROPOSAL); + } + return cache; + }, + syncPayloadsCache(args) { + return adapter.syncPayloadsCache(args); + }, + syncProposalCache(args) { + return adapter.syncProposalCache(args); + }, +}); diff --git a/src/providers/fallbackProvider.ts b/src/providers/fallbackProvider.ts new file mode 100644 index 0000000000..c1f5fa60cb --- /dev/null +++ b/src/providers/fallbackProvider.ts @@ -0,0 +1,29 @@ +import type {GovernanceCacheAdapter} from '..'; +import {ISSUES_FETCHING_PAYLOAD, ISSUES_FETCHING_PROPOSAL} from '../errors'; + +export const fallbackProvider = ( + ...providers: T[] +): GovernanceCacheAdapter => ({ + async getPayload(args) { + for (let i = 0; i < providers.length; i++) { + try { + const response = providers[i].getPayload(args); + return response; + } catch (e) { + console.log('falling back to next provider'); + } + } + throw new Error(ISSUES_FETCHING_PAYLOAD); + }, + async getProposal(args) { + for (let i = 0; i < providers.length; i++) { + try { + const response = await providers[i].getProposal(args); + return response; + } catch (e) { + console.log('falling back to next provider'); + } + } + throw new Error(ISSUES_FETCHING_PROPOSAL); + }, +}); diff --git a/src/providers/githubPagesProvider.ts b/src/providers/githubPagesProvider.ts new file mode 100644 index 0000000000..01bd722cb3 --- /dev/null +++ b/src/providers/githubPagesProvider.ts @@ -0,0 +1,25 @@ +/** + * Hybrid adapter that tries to fetch via github pages + * If github pages doesn't contain the desired data it will fallback to the supplied adapter + */ +import type {GovernanceCacheAdapter, PayloadCacheRaw, ProposalCacheRaw} from '..'; +import {formatPayloadLogs} from '../common/payloadsController'; +import {formatProposalLogs} from '../common/governance'; + +function getPath() { + return 'https://bgd-labs.github.io/v3-governance-cache/cache/'; +} + +export const githubPagesCacheAdapter: GovernanceCacheAdapter = { + async getPayload({chainId, payloadsController, payloadId}) { + const path = `${chainId.toString()}/${payloadsController}/payloads/${payloadId}.json`; + const cache = (await (await fetch(getPath() + path)).json()) as PayloadCacheRaw; + return {payload: cache.payload!, logs: formatPayloadLogs(cache.events)}; + }, + async getProposal({chainId, governance, proposalId}) { + const path = `${chainId.toString()}/${governance}/proposals/${proposalId}.json`; + console.log(path); + const cache = (await (await fetch(getPath() + path)).json()) as ProposalCacheRaw; + return {proposal: cache.proposal!, logs: formatProposalLogs(cache.events), ipfs: cache.ipfs!}; + }, +}; diff --git a/src/adapter/noCache.ts b/src/providers/rpcProvider.ts similarity index 75% rename from src/adapter/noCache.ts rename to src/providers/rpcProvider.ts index 84cfe233c0..d55743a647 100644 --- a/src/adapter/noCache.ts +++ b/src/providers/rpcProvider.ts @@ -7,7 +7,7 @@ import type {GovernanceCacheAdapter} from '..'; import {getPayload} from '../common/payloadsController'; import {getProposal} from '../common/governance'; -export const noCacheAdapter: GovernanceCacheAdapter = { +export const rpcProvider: GovernanceCacheAdapter = { async getPayload({chainId, payloadsController, payloadId}) { const client = CHAIN_ID_CLIENT_MAP[chainId]; return {payload: await getPayload({client, payloadsController, payloadId}), logs: {} as any}; @@ -18,10 +18,4 @@ export const noCacheAdapter: GovernanceCacheAdapter = { const proposal = await getProposal({client, proposalId, governance}); return {proposal, logs: {} as any, ipfs: await getProposalMetadata(proposal.ipfsHash)}; }, - syncPayloadsCache() { - throw new Error('syncing is not available on the "no-cache" adapter'); - }, - syncProposalCache() { - throw new Error('syncing is not available on the "no-cache" adapter'); - }, }; diff --git a/src/adapter/localCache.ts b/src/providers/storage/fileSystem.ts similarity index 82% rename from src/adapter/localCache.ts rename to src/providers/storage/fileSystem.ts index 879efd1b1b..38b9c07395 100644 --- a/src/adapter/localCache.ts +++ b/src/providers/storage/fileSystem.ts @@ -1,23 +1,21 @@ -/** - * Cache adapter which simply stores the ache in a local cache directory - */ import {existsSync, readFileSync, mkdirSync, writeFileSync} from 'fs'; import path from 'path'; -import {CHAIN_ID_CLIENT_MAP, getProposalMetadata} from '@bgd-labs/js-utils'; -import packageJson from '../../package.json'; +import packageJson from '../../../package.json'; import { isPayloadFinal, isProposalFinal, - type GovernanceCacheAdapter, + type GovernanceCacheAdapterWithSync, type PayloadCacheRaw, type ProposalCacheRaw, -} from '..'; -import {formatProposalLogs, getProposal, syncGovernanceEvents} from '../common/governance'; +} from '../..'; +import {ISSUES_FETCHING_PAYLOAD, ISSUES_FETCHING_PROPOSAL} from '../../errors'; import { formatPayloadLogs, getPayload, syncPayloadsControllerEvents, -} from '../common/payloadsController'; +} from '../../common/payloadsController'; +import {formatProposalLogs, syncGovernanceEvents, getProposal} from '../../common/governance'; +import {CHAIN_ID_CLIENT_MAP, getProposalMetadata} from '@bgd-labs/js-utils'; function getPath() { const installPath = path.join(process.cwd(), 'node_modules', packageJson.name); @@ -63,7 +61,7 @@ export type TrackingCache = {lastSeenBlock: string | bigint; isFinal: Record { @@ -124,7 +122,7 @@ const syncProposalCache: GovernanceCacheAdapter['syncProposalCache'] = async ({ * 1. fetch new events * 2. fetch all payloads encountered in the events */ -const syncPayloadsCache: GovernanceCacheAdapter['syncPayloadsCache'] = async ({ +const syncPayloadsCache: GovernanceCacheAdapterWithSync['syncPayloadsCache'] = async ({ chainId, payloadsController, }) => { @@ -169,26 +167,17 @@ const syncPayloadsCache: GovernanceCacheAdapter['syncPayloadsCache'] = async ({ return newData; }; -export const localCacheAdapter: GovernanceCacheAdapter = { +export const fileSystemStorage: GovernanceCacheAdapterWithSync = { async getPayload({chainId, payloadsController, payloadId}) { const path = `${chainId.toString()}/${payloadsController}/payloads`; - let cache = readJSONCache(path, payloadId); - if (!cache) { - await syncPayloadsCache({chainId, payloadsController}); - cache = readJSONCache(path, payloadId); - if (!cache) - throw new Error(`Payload ${payloadId} not found on ${chainId}:${payloadsController}`); - } + const cache = readJSONCache(path, payloadId); + if (!cache) throw new Error(ISSUES_FETCHING_PAYLOAD); return {payload: cache.payload!, logs: formatPayloadLogs(cache.events)}; }, async getProposal({chainId, governance, proposalId}) { const path = `${chainId.toString()}/${governance}/proposals`; - let cache = readJSONCache(path, proposalId); - if (!cache) { - await syncProposalCache({chainId, governance}); - cache = readJSONCache(path, proposalId); - if (!cache) throw new Error(`Payload ${proposalId} not found on ${chainId}:${governance}`); - } + const cache = readJSONCache(path, proposalId); + if (!cache) throw new Error(ISSUES_FETCHING_PROPOSAL); return {proposal: cache.proposal!, logs: formatProposalLogs(cache.events), ipfs: cache.ipfs!}; }, syncPayloadsCache,