-
Notifications
You must be signed in to change notification settings - Fork 15
feat(ponder-sdk): create Ponder Client #1602
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
Merged
Merged
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
922db1b
Create `ponder-sdk` package
tk-o e3347d2
Create `PonderClient` in `ponder-sdk`
tk-o eb4cb17
Add `status()`method to `PonderClient` in `ponder-sdk`
tk-o 9af6992
docs(changeset): Introduce `ponder-sdk` package, including `PonderCli…
tk-o f034c56
Update README.md
lightwalker-eth 2b1d283
Update packages/ponder-sdk/package.json
lightwalker-eth ecf47f3
Apply PR feedback
tk-o 9bdadd4
Merge branch 'feat/ponder-sdk' of github.com:namehash/ensnode into fe…
tk-o fd420a5
Test case: HTTP response not OK
tk-o 9487dae
Enforce invariant
tk-o cc00089
Apply AI agent feedback
tk-o 344f9c7
Improve unit tests handling
tk-o fa95301
Test case: malformed json data
tk-o a940a97
Improve unit tests handling
tk-o 6d4f65c
Clean up peer deps
tk-o 03df55c
Improve unit tests handling
tk-o 7051307
Export useful types
tk-o dcdbee2
Apply suggestions from code review
tk-o 6d78143
Apply PR feedback
tk-o File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@ensnode/ponder-sdk": minor | ||
| --- | ||
|
|
||
| Introduce the `ponder-sdk` package, including an initial `PonderClient` implementation. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| MIT License | ||
|
|
||
| Copyright (c) 2025 NameHash | ||
|
|
||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| of this software and associated documentation files (the "Software"), to deal | ||
| in the Software without restriction, including without limitation the rights | ||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
|
|
||
| The above copyright notice and this permission notice shall be included in all | ||
| copies or substantial portions of the Software. | ||
|
|
||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| SOFTWARE. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| # Ponder SDK | ||
|
|
||
| This package is a utility library for interacting with Ponder apps and data. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| { | ||
| "name": "@ensnode/ponder-sdk", | ||
| "version": "1.5.1", | ||
| "type": "module", | ||
| "description": "A utility library for interacting with Ponder apps and data.", | ||
| "license": "MIT", | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/namehash/ensnode.git", | ||
| "directory": "packages/ponder-sdk" | ||
| }, | ||
| "homepage": "https://github.com/namehash/ensnode/tree/main/packages/ponder-sdk", | ||
| "keywords": [ | ||
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| "ENSNode", | ||
| "Ponder" | ||
| ], | ||
| "files": [ | ||
| "dist" | ||
| ], | ||
| "exports": { | ||
| ".": "./src/index.ts" | ||
| }, | ||
| "publishConfig": { | ||
| "access": "public", | ||
| "exports": { | ||
| ".": { | ||
| "import": { | ||
| "types": "./dist/index.d.ts", | ||
| "default": "./dist/index.js" | ||
| }, | ||
| "require": { | ||
| "types": "./dist/index.d.cts", | ||
| "default": "./dist/index.cjs" | ||
| } | ||
| } | ||
| }, | ||
| "main": "./dist/index.cjs", | ||
| "module": "./dist/index.js", | ||
| "types": "./dist/index.d.ts" | ||
| }, | ||
| "scripts": { | ||
| "prepublish": "tsup", | ||
| "typecheck": "tsc --noEmit", | ||
| "test": "vitest", | ||
| "lint": "biome check --write .", | ||
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| "lint:ci": "biome ci" | ||
| }, | ||
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| "devDependencies": { | ||
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| "@ensnode/shared-configs": "workspace:*", | ||
| "@types/node": "catalog:", | ||
| "tsup": "catalog:", | ||
| "typescript": "catalog:", | ||
| "vitest": "catalog:", | ||
| "zod": "catalog:" | ||
| }, | ||
| "peerDependencies": { | ||
| "zod": "catalog:" | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| import { z } from "zod/v4"; | ||
|
|
||
|
Member
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. Since we have a file named
|
||
| import { nonnegativeIntegerSchema } from "./numbers"; | ||
| import { unixTimestampSchema } from "./time"; | ||
|
|
||
| //// Block Number | ||
|
|
||
| export const blockNumberSchema = nonnegativeIntegerSchema; | ||
|
|
||
| /** | ||
| * Block Number | ||
| * | ||
| * Guaranteed to be a non-negative integer. | ||
| */ | ||
| export type BlockNumber = z.infer<typeof blockNumberSchema>; | ||
|
|
||
| export const blockRefSchema = z.object({ | ||
| number: blockNumberSchema, | ||
| timestamp: unixTimestampSchema, | ||
| }); | ||
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| /** | ||
| * BlockRef | ||
| * | ||
| * Reference to a block. | ||
| */ | ||
| export type BlockRef = z.infer<typeof blockRefSchema>; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import type { z } from "zod/v4"; | ||
|
|
||
| import { positiveIntegerSchema } from "./numbers"; | ||
|
|
||
| // Chain ID | ||
|
|
||
| export const chainIdSchema = positiveIntegerSchema; | ||
|
|
||
| /** | ||
| * Chain ID | ||
| * | ||
| * Represents a unique identifier for a chain. | ||
| * Guaranteed to be a positive integer. | ||
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| * | ||
| * Chain id standards are organized by the Ethereum Community @ https://github.com/ethereum-lists/chains | ||
| **/ | ||
| export type ChainId = z.infer<typeof chainIdSchema>; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; | ||
|
|
||
| import { PonderClient } from "./client"; | ||
| import { deserializePonderIndexingStatus } from "./deserialize/indexing-status"; | ||
| import { | ||
| mockSerializedPonderIndexingStatusInvalidBlockNumber, | ||
| mockSerializedPonderIndexingStatusInvalidChainId, | ||
| mockSerializedPonderIndexingStatusValid, | ||
| } from "./deserialize/indexing-status.mock"; | ||
|
|
||
| // Mock Fetch API | ||
| const mockFetch = vi.fn<typeof fetch>(); | ||
|
|
||
| describe("Ponder Client", () => { | ||
| beforeAll(() => { | ||
| vi.stubGlobal("fetch", mockFetch); | ||
| }); | ||
|
|
||
| afterEach(() => { | ||
| mockFetch.mockReset(); | ||
| }); | ||
|
|
||
| afterAll(() => { | ||
| vi.unstubAllGlobals(); | ||
| }); | ||
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| describe("status()", () => { | ||
| it("should handle valid Ponder status response", async () => { | ||
| // Arrange | ||
| mockFetch.mockResolvedValueOnce( | ||
| new Response(JSON.stringify(mockSerializedPonderIndexingStatusValid), { | ||
| status: 200, | ||
| headers: { "Content-Type": "application/json" }, | ||
| }), | ||
| ); | ||
|
|
||
| const ponderClient = new PonderClient(new URL("http://localhost:3000")); | ||
|
|
||
| // Act & Assert | ||
| await expect(ponderClient.status()).resolves.toStrictEqual( | ||
| deserializePonderIndexingStatus(mockSerializedPonderIndexingStatusValid), | ||
| ); | ||
| }); | ||
|
|
||
| describe("Invalid response handling", () => { | ||
| it("should handle invalid block numbers in the response", async () => { | ||
| mockFetch.mockResolvedValueOnce( | ||
| new Response(JSON.stringify(mockSerializedPonderIndexingStatusInvalidBlockNumber), { | ||
| status: 200, | ||
| headers: { "Content-Type": "application/json" }, | ||
| }), | ||
| ); | ||
|
|
||
| const ponderClient = new PonderClient(new URL("http://localhost:3000")); | ||
|
|
||
| await expect(ponderClient.status()).rejects.toThrowError( | ||
| /Invalid serialized Ponder Indexing Status.*Value must be a non-negative integer/, | ||
| ); | ||
| }); | ||
|
|
||
| it("should handle invalid chain IDs in the response", async () => { | ||
| mockFetch.mockResolvedValueOnce( | ||
| new Response(JSON.stringify(mockSerializedPonderIndexingStatusInvalidChainId), { | ||
| status: 200, | ||
| headers: { "Content-Type": "application/json" }, | ||
| }), | ||
| ); | ||
|
|
||
| const ponderClient = new PonderClient(new URL("http://localhost:3000")); | ||
|
|
||
| await expect(ponderClient.status()).rejects.toThrowError( | ||
| /Invalid serialized Ponder Indexing Status.*Value must be a positive integer/, | ||
| ); | ||
| }); | ||
|
|
||
| it("should handle zero indexed chains in the response", async () => { | ||
| mockFetch.mockResolvedValueOnce( | ||
| new Response(JSON.stringify({}), { | ||
| status: 200, | ||
| headers: { "Content-Type": "application/json" }, | ||
| }), | ||
| ); | ||
|
|
||
| const ponderClient = new PonderClient(new URL("http://localhost:3000")); | ||
|
|
||
| await expect(ponderClient.status()).rejects.toThrowError( | ||
| /Invalid serialized Ponder Indexing Status.*Ponder Indexing Status must include at least one indexed chain/, | ||
| ); | ||
| }); | ||
| }); | ||
|
|
||
| describe("HTTP error handling", () => { | ||
| it("should handle non-OK HTTP responses", async () => { | ||
| // Arrange | ||
| mockFetch.mockResolvedValueOnce( | ||
| new Response("Internal Server Error", { | ||
| status: 500, | ||
| statusText: "Internal Server Error", | ||
| }), | ||
| ); | ||
| const ponderClient = new PonderClient(new URL("http://localhost:3000")); | ||
| // Act & Assert | ||
| await expect(ponderClient.status()).rejects.toThrowError( | ||
| /Failed to fetch Ponder Indexing Status response/, | ||
| ); | ||
| }); | ||
|
|
||
| it("should handle JSON parsing errors", async () => { | ||
| mockFetch.mockResolvedValueOnce( | ||
| new Response("not valid json", { | ||
| status: 200, | ||
| headers: { "Content-Type": "application/json" }, | ||
| }), | ||
| ); | ||
| const ponderClient = new PonderClient(new URL("http://localhost:3000")); | ||
| await expect(ponderClient.status()).rejects.toThrowError( | ||
| /Failed to parse Ponder Indexing Status response as JSON/, | ||
| ); | ||
| }); | ||
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }); | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }); | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| import { deserializePonderIndexingStatus } from "./deserialize/indexing-status"; | ||
| import type { PonderIndexingStatus } from "./indexing-status"; | ||
|
|
||
| /** | ||
| * PonderClient for fetching data from Ponder apps. | ||
| */ | ||
| export class PonderClient { | ||
| constructor(private baseUrl: URL) {} | ||
|
|
||
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| /** | ||
| * Get Ponder Indexing Status | ||
| * | ||
| * @returns Ponder Indexing Status. | ||
| * @throws Error if the response could not be fetched or was invalid. | ||
| */ | ||
| async status(): Promise<PonderIndexingStatus> { | ||
| const requestUrl = new URL("/status", this.baseUrl); | ||
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| const response = await fetch(requestUrl); | ||
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| if (!response.ok) { | ||
| throw new Error( | ||
| `Failed to fetch Ponder Indexing Status response: ${response.status} ${response.statusText}`, | ||
| ); | ||
| } | ||
|
|
||
| let responseData: unknown; | ||
|
|
||
| try { | ||
| responseData = await response.json(); | ||
| } catch { | ||
| throw new Error("Failed to parse Ponder Indexing Status response as JSON"); | ||
| } | ||
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| return deserializePonderIndexingStatus(responseData); | ||
| } | ||
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.