-
Notifications
You must be signed in to change notification settings - Fork 121
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement http registry #464
base: main
Are you sure you want to change the base?
Changes from all commits
1097b68
fc77bf4
fda476b
b34eca5
14aaab0
d0d0af4
6de8f24
324b16c
e2ec6b8
e4c6c85
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@hyperlane-xyz/registry': minor | ||
--- | ||
|
||
Export http server utils |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@hyperlane-xyz/registry': patch | ||
--- | ||
|
||
Implement more endpoints |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"mode": "pre", | ||
"tag": "http", | ||
"initialVersions": { | ||
"@hyperlane-xyz/registry": "6.11.0" | ||
}, | ||
"changesets": ["funny-wolves-suffer", "hot-flowers-share", "hungry-moose-beg"] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,17 @@ | ||
{ | ||
"name": "@hyperlane-xyz/registry", | ||
"description": "A collection of configs, artifacts, and schemas for Hyperlane", | ||
"version": "6.11.0", | ||
"version": "6.12.0-http.2", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you mean to merge this? |
||
"dependencies": { | ||
"express": "^4.21.2", | ||
Comment on lines
2
to
+6
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. wonder if it makes sense to spin this off to a side-package so it doesn't add to the normal registry size There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Putting it in it's own export path like I did for |
||
"yaml": "2.4.5", | ||
"zod": "^3.21.2" | ||
}, | ||
"devDependencies": { | ||
"@changesets/cli": "^2.26.2", | ||
"@eslint/js": "^9.1.1", | ||
"@hyperlane-xyz/sdk": "7.0.0", | ||
"@types/express": "^4", | ||
"@types/mocha": "^10.0.1", | ||
"@types/node": "^16.9.1", | ||
"@typescript-eslint/parser": "^7.7.0", | ||
|
@@ -72,5 +74,8 @@ | |
"version:prepare": "yarn changeset version && yarn install --no-immutable", | ||
"version:check": "yarn changeset status" | ||
}, | ||
"packageManager": "[email protected]" | ||
"packageManager": "[email protected]", | ||
"resolutions": { | ||
"@hyperlane-xyz/sdk": "portal:/Users/yorhodes/hyperlane/monorepo/typescript/sdk" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import { ChainMap, ChainMetadata, ChainName, WarpCoreConfig } from '@hyperlane-xyz/sdk'; | ||
import { ChainAddresses, MaybePromise, WarpRouteConfigMap } from '../types.js'; | ||
import { | ||
IRegistry, | ||
RegistryContent, | ||
RegistryType, | ||
UpdateChainParams, | ||
WarpRouteFilterParams, | ||
} from './IRegistry.js'; | ||
|
||
export class HttpClientRegistry implements IRegistry { | ||
private baseUrl: string; | ||
|
||
constructor(baseUrl: string = 'http://localhost:3000/api') { | ||
this.baseUrl = baseUrl; | ||
} | ||
|
||
getMetadata(): MaybePromise<ChainMap<ChainMetadata>> { | ||
return this.fetchJson<ChainMap<ChainMetadata>>('/metadata'); | ||
} | ||
|
||
getAddresses(): MaybePromise<ChainMap<ChainAddresses>> { | ||
return this.fetchJson<ChainMap<ChainAddresses>>('/addresses'); | ||
} | ||
|
||
getChainMetadata(chainName: ChainName): MaybePromise<ChainMetadata> { | ||
return this.fetchJson<ChainMetadata>(`/chain/${chainName}/metadata`); | ||
} | ||
|
||
updateChain(update: UpdateChainParams): MaybePromise<void> { | ||
return this.fetchJson<void>(`/chain/${update.chainName}/metadata`, { | ||
method: 'POST', | ||
body: JSON.stringify(update.metadata), | ||
}); | ||
} | ||
|
||
get type(): RegistryType { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid |
||
throw new Error('Method not implemented.'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not set a RegistryType? |
||
} | ||
|
||
get uri(): string { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto |
||
throw new Error('Method not implemented.'); | ||
} | ||
|
||
getUri(itemPath?: string): string { | ||
throw new Error('Method not implemented.'); | ||
} | ||
listRegistryContent(): MaybePromise<RegistryContent> { | ||
throw new Error('Method not implemented.'); | ||
} | ||
getChains(): MaybePromise<Array<ChainName>> { | ||
throw new Error('Method not implemented.'); | ||
} | ||
getChainAddresses(chainName: ChainName): MaybePromise<ChainAddresses | null> { | ||
throw new Error('Method not implemented.'); | ||
} | ||
getChainLogoUri(chainName: ChainName): Promise<string | null> { | ||
throw new Error('Method not implemented.'); | ||
} | ||
addChain(chain: UpdateChainParams): MaybePromise<void> { | ||
throw new Error('Method not implemented.'); | ||
} | ||
removeChain(chain: ChainName): MaybePromise<void> { | ||
throw new Error('Method not implemented.'); | ||
} | ||
getWarpRoute(routeId: string): MaybePromise<WarpCoreConfig | null> { | ||
throw new Error('Method not implemented.'); | ||
} | ||
getWarpRoutes(filter?: WarpRouteFilterParams): MaybePromise<WarpRouteConfigMap> { | ||
throw new Error('Method not implemented.'); | ||
} | ||
addWarpRoute(config: WarpCoreConfig): MaybePromise<void> { | ||
throw new Error('Method not implemented.'); | ||
} | ||
merge(otherRegistry: IRegistry): IRegistry { | ||
throw new Error('Method not implemented.'); | ||
} | ||
|
||
private async fetchJson<T>(endpoint: string, options: RequestInit = {}): Promise<T> { | ||
const response = await fetch(`${this.baseUrl}${endpoint}`, { | ||
...options, | ||
headers: { | ||
'Content-Type': 'application/json', | ||
...options.headers, | ||
}, | ||
}); | ||
|
||
if (!response.ok) { | ||
throw new Error(`HTTP error: ${response.status} ${response.statusText}`); | ||
} | ||
|
||
return response.json(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import { ChainMetadataSchema, ChainName, ZChainName } from '@hyperlane-xyz/sdk'; | ||
import express, { Express } from 'express'; | ||
import { IRegistry } from './IRegistry.js'; | ||
|
||
export class HttpServer { | ||
app: Express; | ||
|
||
constructor(protected getRegistry: () => Promise<IRegistry>) { | ||
this.app = express(); | ||
this.app.use(express.json()); | ||
} | ||
|
||
start(port = process.env.PORT || 3000, refreshInterval = 1000 * 60 * 5) { | ||
let registry: IRegistry; | ||
let lastRequest = Date.now(); | ||
|
||
this.app.use('/', async (_req, res, next) => { | ||
const now = Date.now(); | ||
if (now - lastRequest > refreshInterval) { | ||
console.log('Refreshing registry cache...'); | ||
registry = await this.getRegistry(); | ||
} | ||
|
||
lastRequest = now; | ||
|
||
return next(); | ||
}); | ||
|
||
this.app.get('/metadata', async (_req, res) => { | ||
const metadata = await registry.getMetadata(); | ||
return res.json(metadata); | ||
}); | ||
|
||
// Get chain metadata | ||
this.app.get('/chain/:chain/metadata', async (req, res) => { | ||
try { | ||
const chainName = req.params.chain as ChainName; | ||
const metadata = await registry.getChainMetadata(chainName); | ||
if (!metadata) { | ||
return res.status(404).json({ error: 'Chain metadata not found' }); | ||
} | ||
return res.json(metadata); | ||
} catch (error) { | ||
const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred'; | ||
return res.status(500).json({ error: errorMessage }); | ||
} | ||
}); | ||
|
||
this.app.post('/chain/:chain/metadata', async (req, res) => { | ||
try { | ||
const chainName = ZChainName.parse(req.params.chain); | ||
const metadata = ChainMetadataSchema.parse(req.body); | ||
await registry.updateChain({ | ||
chainName, | ||
metadata, | ||
}); | ||
return res.status(200).json({ message: 'Chain metadata updated successfully' }); | ||
} catch (error) { | ||
const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred'; | ||
return res.status(500).json({ error: errorMessage }); | ||
} | ||
}); | ||
|
||
this.app.get('/addresses', async (_req, res) => { | ||
const addresses = await registry.getAddresses(); | ||
return res.json(addresses); | ||
}); | ||
|
||
const server = this.app.listen(port, () => console.log(`Server running on port ${port}`)); | ||
|
||
server.on('request', (req, _res) => console.log('Request:', req.url)); | ||
|
||
server.on('error', (error) => console.error('Server error:', error)); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why has the changelog changed as part of your PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this was before I figured out how to get yarn link working I needed to publish some beta versions for the monorepo PR to use