UNPKG

@hyperlane-xyz/registry

Version:

A collection of configs, artifacts, and schemas for Hyperlane

140 lines (139 loc) 6.08 kB
import { objMerge } from '../utils.js'; import { RegistryType } from './IRegistry.js'; /** * A registry that accepts multiple sub-registries. * Read methods are performed on all sub-registries and the results are merged. * Write methods are performed on all sub-registries. * Can be created manually or by calling `.merge()` on an existing registry. */ export class MergedRegistry { type = RegistryType.Merged; uri = '__merged_registry__'; registries; logger; constructor({ registries, logger }) { if (!registries.length) throw new Error('At least one registry URI is required'); this.registries = registries; // @ts-ignore this.logger = logger || console; } getUri() { throw new Error('getUri method not applicable to MergedRegistry'); } async listRegistryContent() { const results = await this.multiRegistryRead((r) => r.listRegistryContent()); return results.reduce((acc, content) => objMerge(acc, content), { chains: {}, deployments: { warpRoutes: {}, warpDeployConfig: {}, }, }); } async getChains() { return Object.keys(await this.getMetadata()); } async getMetadata() { const results = await this.multiRegistryRead((r) => r.getMetadata()); return results.reduce((acc, content) => objMerge(acc, content), {}); } async getChainMetadata(chainName) { return (await this.getMetadata())[chainName] || null; } async getAddresses() { const results = await this.multiRegistryRead((r) => r.getAddresses()); return results.reduce((acc, content) => objMerge(acc, content), {}); } async getChainAddresses(chainName) { return (await this.getAddresses())[chainName] || null; } async getChainLogoUri(chainName) { const results = await this.multiRegistryRead((r) => r.getChainLogoUri(chainName)); return results.find((uri) => !!uri) || null; } async addChain(chain) { return this.multiRegistryWrite(async (registry) => await registry.addChain(chain), 'addChain', `adding chain ${chain.chainName}`); } async updateChain(chain) { return this.multiRegistryWrite(async (registry) => await registry.updateChain(chain), 'updateChain', `updating chain ${chain.chainName}`); } async removeChain(chain) { return this.multiRegistryWrite(async (registry) => await registry.removeChain(chain), 'removeChain', `removing chain ${chain}`); } async getWarpRoute(id) { const results = await this.multiRegistryRead((r) => r.getWarpRoute(id)); return results.find((r) => !!r) || null; } async getWarpDeployConfig(id) { const results = await this.multiRegistryRead((r) => r.getWarpDeployConfig(id)); return results.find((r) => !!r) || null; } async getWarpRoutes(filter) { const results = await this.multiRegistryRead((r) => r.getWarpRoutes(filter)); return results.reduce((acc, content) => objMerge(acc, content), {}); } async getWarpDeployConfigs(filter) { const results = await this.multiRegistryRead((r) => r.getWarpDeployConfigs(filter)); return results.reduce((acc, content) => objMerge(acc, content), {}); } async addWarpRoute(config, options) { return this.multiRegistryWrite(async (registry) => await registry.addWarpRoute(config, options), 'addWarpRoute', 'adding warp route'); } async addWarpRouteConfig(config, options) { return this.multiRegistryWrite(async (registry) => await registry.addWarpRouteConfig(config, options), 'addWarpRouteConfig', 'adding warp route deploy config'); } multiRegistryRead(readFn) { return Promise.all(this.registries.map(async (registry) => { try { return { ok: true, value: await readFn(registry) }; } catch (error) { if (isNotFoundError(error)) { this.logger.debug(`Tolerating not-found read miss from ${registry.type} registry at ${registry.uri}`); return { ok: false, error }; } throw error; } })).then((results) => { const successResults = results.filter((result) => result.ok); if (!successResults.length) { const notFoundResult = results.find((result) => !result.ok); if (notFoundResult) throw notFoundResult.error; } return results.map((result) => (result.ok ? result.value : null)); }); } async multiRegistryWrite(writeFn, methodName, logMsg) { for (const registry of this.registries) { if (registry.unimplementedMethods?.has(methodName)) { this.logger.warn(`Skipping ${logMsg} at ${registry.type} registry (not supported)`); continue; } try { this.logger.info(`Now ${logMsg} at ${registry.type} registry at ${registry.uri}`); await writeFn(registry); this.logger.info(`Done ${logMsg} at ${registry.type} registry`); } catch (error) { // To prevent loss of artifacts, MergedRegistry write methods are failure tolerant this.logger.error(`Failure ${logMsg} at ${registry.type} registry`, error); } } } merge(otherRegistry) { return new MergedRegistry({ registries: [...this.registries, otherRegistry], logger: this.logger, }); } } function isNotFoundError(error) { if (!error || typeof error !== 'object') return false; const { code, message, status, statusCode, response } = error; return ([status, statusCode, response?.status, response?.statusCode].some((candidate) => Number(candidate) === 404) || code === 'ENOENT' || (typeof message === 'string' && /^File not found(?:\b|:)/i.test(message))); }