From bdd2b8e3681b63c016c71c644f37565836a301f0 Mon Sep 17 00:00:00 2001 From: 0xkenj1 Date: Tue, 8 Oct 2024 15:55:36 -0300 Subject: [PATCH 1/7] feat: events fetcher & indexer client --- packages/data-flow/README.md | 45 +++++ packages/data-flow/package.json | 35 ++++ packages/data-flow/src/eventsFetcher.ts | 22 +++ packages/data-flow/src/exceptions/index.ts | 0 packages/data-flow/src/external.ts | 1 + packages/data-flow/src/index.ts | 1 + packages/data-flow/src/interfaces/index.ts | 10 ++ packages/data-flow/src/internal.ts | 1 + .../data-flow/test/unit/eventsFetcher.spec.ts | 79 ++++++++ packages/data-flow/tsconfig.build.json | 13 ++ packages/data-flow/tsconfig.json | 4 + packages/data-flow/vitest.config.ts | 22 +++ packages/indexer-client/README.md | 36 ++++ packages/indexer-client/package.json | 34 ++++ .../indexer-client/src/exceptions/index.ts | 2 + .../indexerClientError.exception.ts | 6 + .../invalidIndexerResponse.exception.ts | 6 + packages/indexer-client/src/external.ts | 3 + packages/indexer-client/src/index.ts | 1 + .../indexer-client/src/interfaces/index.ts | 1 + .../src/interfaces/indexerClient.ts | 19 ++ packages/indexer-client/src/internal.ts | 4 + .../src/providers/envioIndexerClient.ts | 55 ++++++ .../indexer-client/src/providers/index.ts | 1 + .../test/unit/envioIndexerClient.spec.ts | 169 ++++++++++++++++++ packages/indexer-client/tsconfig.build.json | 13 ++ packages/indexer-client/tsconfig.json | 4 + packages/indexer-client/vitest.config.ts | 22 +++ pnpm-lock.yaml | 6 + 29 files changed, 615 insertions(+) create mode 100644 packages/data-flow/README.md create mode 100644 packages/data-flow/package.json create mode 100644 packages/data-flow/src/eventsFetcher.ts create mode 100644 packages/data-flow/src/exceptions/index.ts create mode 100644 packages/data-flow/src/external.ts create mode 100644 packages/data-flow/src/index.ts create mode 100644 packages/data-flow/src/interfaces/index.ts create mode 100644 packages/data-flow/src/internal.ts create mode 100644 packages/data-flow/test/unit/eventsFetcher.spec.ts create mode 100644 packages/data-flow/tsconfig.build.json create mode 100644 packages/data-flow/tsconfig.json create mode 100644 packages/data-flow/vitest.config.ts create mode 100644 packages/indexer-client/README.md create mode 100644 packages/indexer-client/package.json create mode 100644 packages/indexer-client/src/exceptions/index.ts create mode 100644 packages/indexer-client/src/exceptions/indexerClientError.exception.ts create mode 100644 packages/indexer-client/src/exceptions/invalidIndexerResponse.exception.ts create mode 100644 packages/indexer-client/src/external.ts create mode 100644 packages/indexer-client/src/index.ts create mode 100644 packages/indexer-client/src/interfaces/index.ts create mode 100644 packages/indexer-client/src/interfaces/indexerClient.ts create mode 100644 packages/indexer-client/src/internal.ts create mode 100644 packages/indexer-client/src/providers/envioIndexerClient.ts create mode 100644 packages/indexer-client/src/providers/index.ts create mode 100644 packages/indexer-client/test/unit/envioIndexerClient.spec.ts create mode 100644 packages/indexer-client/tsconfig.build.json create mode 100644 packages/indexer-client/tsconfig.json create mode 100644 packages/indexer-client/vitest.config.ts diff --git a/packages/data-flow/README.md b/packages/data-flow/README.md new file mode 100644 index 0000000..65ce7fa --- /dev/null +++ b/packages/data-flow/README.md @@ -0,0 +1,45 @@ +# @grants-stack-indexer/data-flow + +Is a library that provides the core components of the processing pipeline for gitcoin grants-stack-indexer. + +## Available Scripts + +Available scripts that can be run using `pnpm`: + +| Script | Description | +| ------------- | ------------------------------------------------------- | +| `build` | Build library using tsc | +| `check-types` | Check types issues using tsc | +| `clean` | Remove `dist` folder | +| `lint` | Run ESLint to check for coding standards | +| `lint:fix` | Run linter and automatically fix code formatting issues | +| `format` | Check code formatting and style using Prettier | +| `format:fix` | Run formatter and automatically fix issues | +| `test` | Run tests using vitest | +| `test:cov` | Run tests with coverage report | + +## Usage + +### Importing the Package + +You can import the package in your TypeScript or JavaScript files as follows: + +```typescript +import { EventsFetcher } from "@grants-stack-indexer/data-flow"; +``` + +### Example + +```typescript +const eventsFetcher = new EventsFetcher(indexerClient); + +const chainId = 1; +const blockNumber = 1000; +const logIndex = 0; + +const result = await eventsFetcher.fetcEventsByBlockNumberAndLogIndex( + chainId, + blockNumber, + logIndex, +); +``` diff --git a/packages/data-flow/package.json b/packages/data-flow/package.json new file mode 100644 index 0000000..cfe98fa --- /dev/null +++ b/packages/data-flow/package.json @@ -0,0 +1,35 @@ +{ + "name": "@grants-stack-indexer/data-flow", + "version": "0.0.1", + "private": true, + "description": "", + "license": "MIT", + "author": "Wonderland", + "type": "module", + "main": "./dist/src/index.js", + "types": "./dist/src/index.d.ts", + "directories": { + "src": "src" + }, + "files": [ + "dist/*", + "package.json", + "!**/*.tsbuildinfo" + ], + "scripts": { + "build": "tsc -p tsconfig.build.json", + "check-types": "tsc --noEmit -p ./tsconfig.json", + "clean": "rm -rf dist", + "format": "prettier --check \"{src,test}/**/*.{js,ts,json}\"", + "format:fix": "prettier --write \"{src,test}/**/*.{js,ts,json}\"", + "lint": "eslint \"{src,test}/**/*.{js,ts,json}\"", + "lint:fix": "pnpm lint --fix", + "test": "vitest run --config vitest.config.ts --passWithNoTests", + "test:cov": "vitest run --config vitest.config.ts --coverage" + }, + "dependencies": { + "@grants-stack-indexer/indexer-client": "workspace:*", + "@grants-stack-indexer/shared": "workspace:*", + "viem": "2.21.19" + } +} diff --git a/packages/data-flow/src/eventsFetcher.ts b/packages/data-flow/src/eventsFetcher.ts new file mode 100644 index 0000000..a4cb761 --- /dev/null +++ b/packages/data-flow/src/eventsFetcher.ts @@ -0,0 +1,22 @@ +import { IIndexerClient } from "@grants-stack-indexer/indexer-client"; +import { AnyProtocolEvent } from "@grants-stack-indexer/shared"; + +import { IEventsFetcher } from "./interfaces/index.js"; + +export class EventsFetcher implements IEventsFetcher { + constructor(private indexerClient: IIndexerClient) {} + + async fetcEventsByBlockNumberAndLogIndex( + chainId: number, + blockNumber: number, + logIndex: number, + limit: number = 100, + ): Promise { + return await this.indexerClient.getEventsByBlockNumberAndLogIndex( + chainId, + blockNumber, + logIndex, + limit, + ); + } +} diff --git a/packages/data-flow/src/exceptions/index.ts b/packages/data-flow/src/exceptions/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/data-flow/src/external.ts b/packages/data-flow/src/external.ts new file mode 100644 index 0000000..f35e641 --- /dev/null +++ b/packages/data-flow/src/external.ts @@ -0,0 +1 @@ +export { EventsFetcher } from "./internal.js"; diff --git a/packages/data-flow/src/index.ts b/packages/data-flow/src/index.ts new file mode 100644 index 0000000..a5a2748 --- /dev/null +++ b/packages/data-flow/src/index.ts @@ -0,0 +1 @@ +export * from "./external.js"; diff --git a/packages/data-flow/src/interfaces/index.ts b/packages/data-flow/src/interfaces/index.ts new file mode 100644 index 0000000..c0fd809 --- /dev/null +++ b/packages/data-flow/src/interfaces/index.ts @@ -0,0 +1,10 @@ +import { AnyProtocolEvent } from "@grants-stack-indexer/shared"; + +export interface IEventsFetcher { + fetcEventsByBlockNumberAndLogIndex( + chainId: number, + blockNumber: number, + logIndex: number, + limit?: number, + ): Promise; +} diff --git a/packages/data-flow/src/internal.ts b/packages/data-flow/src/internal.ts new file mode 100644 index 0000000..f1c91ab --- /dev/null +++ b/packages/data-flow/src/internal.ts @@ -0,0 +1 @@ +export * from "./eventsFetcher.js"; diff --git a/packages/data-flow/test/unit/eventsFetcher.spec.ts b/packages/data-flow/test/unit/eventsFetcher.spec.ts new file mode 100644 index 0000000..b7ad643 --- /dev/null +++ b/packages/data-flow/test/unit/eventsFetcher.spec.ts @@ -0,0 +1,79 @@ +import { IIndexerClient } from "@grants-stack-indexer/indexer-client"; +import { AnyProtocolEvent } from "@grants-stack-indexer/shared"; +import { beforeEach, describe, expect, it, Mocked, vi } from "vitest"; + +import { EventsFetcher } from "../../src/eventsFetcher.js"; + +describe("EventsFetcher", () => { + let indexerClientMock: Mocked; + let eventsFetcher: EventsFetcher; + + beforeEach(() => { + indexerClientMock = { + getEventsByBlockNumberAndLogIndex: vi.fn(), + }; + + eventsFetcher = new EventsFetcher(indexerClientMock); + }); + + it("should fetch events by block number and log index", async () => { + const mockEvents: AnyProtocolEvent[] = [ + { + chain_id: 1, + block_number: 12345, + block_timestamp: 123123123, + contract_name: "Allo", + event_name: "PoolCreated", + event_id: "123", + src_address: "0x1234567890123456789012345678901234567890", + log_index: 0, + params: { contractAddress: "0x1234" }, + }, + { + chain_id: 1, + block_number: 12345, + block_timestamp: 123123123, + contract_name: "Allo", + event_name: "PoolCreated", + event_id: "123", + src_address: "0x1234567890123456789012345678901234567890", + log_index: 0, + params: { contractAddress: "0x1234" }, + }, + ]; + const chainId = 1; + const blockNumber = 1000; + const logIndex = 0; + const limit = 100; + + indexerClientMock.getEventsByBlockNumberAndLogIndex.mockResolvedValue(mockEvents); + + const result = await eventsFetcher.fetcEventsByBlockNumberAndLogIndex( + chainId, + blockNumber, + logIndex, + ); + + expect(indexerClientMock.getEventsByBlockNumberAndLogIndex).toHaveBeenCalledWith( + chainId, + blockNumber, + logIndex, + limit, + ); + expect(result).toEqual(mockEvents); + }); + + it("should handle errors thrown by indexer client", async () => { + const chainId = 1; + const blockNumber = 1000; + const logIndex = 0; + + indexerClientMock.getEventsByBlockNumberAndLogIndex.mockRejectedValue( + new Error("Network error"), + ); + + await expect( + eventsFetcher.fetcEventsByBlockNumberAndLogIndex(chainId, blockNumber, logIndex), + ).rejects.toThrow("Network error"); + }); +}); diff --git a/packages/data-flow/tsconfig.build.json b/packages/data-flow/tsconfig.build.json new file mode 100644 index 0000000..56d3112 --- /dev/null +++ b/packages/data-flow/tsconfig.build.json @@ -0,0 +1,13 @@ +/* Based on total-typescript no-dom library config */ +/* https://github.com/total-typescript/tsconfig */ +{ + "extends": "../../tsconfig.build.json", + "compilerOptions": { + "composite": true, + "declarationMap": true, + "declaration": true, + "outDir": "dist" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "tests"] +} diff --git a/packages/data-flow/tsconfig.json b/packages/data-flow/tsconfig.json new file mode 100644 index 0000000..66bb87a --- /dev/null +++ b/packages/data-flow/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src/**/*"] +} diff --git a/packages/data-flow/vitest.config.ts b/packages/data-flow/vitest.config.ts new file mode 100644 index 0000000..8e1bbf4 --- /dev/null +++ b/packages/data-flow/vitest.config.ts @@ -0,0 +1,22 @@ +import path from "path"; +import { configDefaults, defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + globals: true, // Use Vitest's global API without importing it in each file + environment: "node", // Use the Node.js environment + include: ["test/**/*.spec.ts"], // Include test files + exclude: ["node_modules", "dist"], // Exclude certain directories + coverage: { + provider: "v8", + reporter: ["text", "json", "html"], // Coverage reporters + exclude: ["node_modules", "dist", "src/index.ts", ...configDefaults.exclude], // Files to exclude from coverage + }, + }, + resolve: { + alias: { + // Setup path alias based on tsconfig paths + "@": path.resolve(__dirname, "src"), + }, + }, +}); diff --git a/packages/indexer-client/README.md b/packages/indexer-client/README.md new file mode 100644 index 0000000..aa666be --- /dev/null +++ b/packages/indexer-client/README.md @@ -0,0 +1,36 @@ +# @grants-stack-indexer/indexer-client + +Is library for interacting with blockchain event indexing services. + +## Available Scripts + +Available scripts that can be run using `pnpm`: + +| Script | Description | +| ------------- | ------------------------------------------------------- | +| `build` | Build library using tsc | +| `check-types` | Check types issues using tsc | +| `clean` | Remove `dist` folder | +| `lint` | Run ESLint to check for coding standards | +| `lint:fix` | Run linter and automatically fix code formatting issues | +| `format` | Check code formatting and style using Prettier | +| `format:fix` | Run formatter and automatically fix issues | +| `test` | Run tests using vitest | +| `test:cov` | Run tests with coverage report | + +## Usage + +### Importing the Package + +You can import the package in your TypeScript or JavaScript files as follows: + +```typescript +import { EnvioIndexerClient } from "@grants-stack-indexer/indexer-client"; +``` + +### Example + +```typescript +const envioIndexerClient = new EnvioIndexerClient("http://example.com/graphql", "secret"); +await envioIndexerClient.getEventsByBlockNumberAndLogIndex(1, 12345, 0); +``` diff --git a/packages/indexer-client/package.json b/packages/indexer-client/package.json new file mode 100644 index 0000000..3a8ff5b --- /dev/null +++ b/packages/indexer-client/package.json @@ -0,0 +1,34 @@ +{ + "name": "@grants-stack-indexer/indexer-client", + "version": "0.0.1", + "private": true, + "description": "", + "license": "MIT", + "author": "Wonderland", + "type": "module", + "main": "./dist/src/index.js", + "types": "./dist/src/index.d.ts", + "directories": { + "src": "src" + }, + "files": [ + "dist/*", + "package.json", + "!**/*.tsbuildinfo" + ], + "scripts": { + "build": "tsc -p tsconfig.build.json", + "check-types": "tsc --noEmit -p ./tsconfig.json", + "clean": "rm -rf dist", + "format": "prettier --check \"{src,test}/**/*.{js,ts,json}\"", + "format:fix": "prettier --write \"{src,test}/**/*.{js,ts,json}\"", + "lint": "eslint \"{src,test}/**/*.{js,ts,json}\"", + "lint:fix": "pnpm lint --fix", + "test": "vitest run --config vitest.config.ts --passWithNoTests", + "test:cov": "vitest run --config vitest.config.ts --coverage" + }, + "dependencies": { + "@grants-stack-indexer/shared": "workspace:*", + "graphql-request": "7.1.0" + } +} diff --git a/packages/indexer-client/src/exceptions/index.ts b/packages/indexer-client/src/exceptions/index.ts new file mode 100644 index 0000000..40a80c4 --- /dev/null +++ b/packages/indexer-client/src/exceptions/index.ts @@ -0,0 +1,2 @@ +export * from "./invalidIndexerResponse.exception.js"; +export * from "./indexerClientError.exception.js"; diff --git a/packages/indexer-client/src/exceptions/indexerClientError.exception.ts b/packages/indexer-client/src/exceptions/indexerClientError.exception.ts new file mode 100644 index 0000000..9a9a6a9 --- /dev/null +++ b/packages/indexer-client/src/exceptions/indexerClientError.exception.ts @@ -0,0 +1,6 @@ +export class IndexerClientError extends Error { + constructor(message: string) { + super(`Indexer client error - ${message}`); + this.name = "IndexerClientError"; + } +} diff --git a/packages/indexer-client/src/exceptions/invalidIndexerResponse.exception.ts b/packages/indexer-client/src/exceptions/invalidIndexerResponse.exception.ts new file mode 100644 index 0000000..01b38ee --- /dev/null +++ b/packages/indexer-client/src/exceptions/invalidIndexerResponse.exception.ts @@ -0,0 +1,6 @@ +export class InvalidIndexerResponse extends Error { + constructor(response: string) { + super(`Indexer response is invalid - ${response}`); + this.name = "InvalidIndexerResponse"; + } +} diff --git a/packages/indexer-client/src/external.ts b/packages/indexer-client/src/external.ts new file mode 100644 index 0000000..ba0a97f --- /dev/null +++ b/packages/indexer-client/src/external.ts @@ -0,0 +1,3 @@ +export type { IIndexerClient } from "./internal.js"; + +export { EnvioIndexerClient } from "./internal.js"; diff --git a/packages/indexer-client/src/index.ts b/packages/indexer-client/src/index.ts new file mode 100644 index 0000000..a5a2748 --- /dev/null +++ b/packages/indexer-client/src/index.ts @@ -0,0 +1 @@ +export * from "./external.js"; diff --git a/packages/indexer-client/src/interfaces/index.ts b/packages/indexer-client/src/interfaces/index.ts new file mode 100644 index 0000000..d109a80 --- /dev/null +++ b/packages/indexer-client/src/interfaces/index.ts @@ -0,0 +1 @@ +export * from "./indexerClient.js"; diff --git a/packages/indexer-client/src/interfaces/indexerClient.ts b/packages/indexer-client/src/interfaces/indexerClient.ts new file mode 100644 index 0000000..71cc026 --- /dev/null +++ b/packages/indexer-client/src/interfaces/indexerClient.ts @@ -0,0 +1,19 @@ +import { AnyProtocolEvent } from "@grants-stack-indexer/shared"; + +/** + * Interface for the indexer client + */ +export interface IIndexerClient { + /** + * Get the events by block number and log index from the indexer service + * @param chainId Id of the chain + * @param fromBlock Block number to start fetching events from + * @param logIndex Log index in the block + */ + getEventsByBlockNumberAndLogIndex( + chainId: number, + fromBlock: number, + logIndex: number, + limit?: number, + ): Promise; +} diff --git a/packages/indexer-client/src/internal.ts b/packages/indexer-client/src/internal.ts new file mode 100644 index 0000000..9f7bdbf --- /dev/null +++ b/packages/indexer-client/src/internal.ts @@ -0,0 +1,4 @@ +export type { Address } from "viem"; +export * from "./exceptions/index.js"; +export * from "./interfaces/index.js"; +export * from "./providers/index.js"; diff --git a/packages/indexer-client/src/providers/envioIndexerClient.ts b/packages/indexer-client/src/providers/envioIndexerClient.ts new file mode 100644 index 0000000..8764cd5 --- /dev/null +++ b/packages/indexer-client/src/providers/envioIndexerClient.ts @@ -0,0 +1,55 @@ +import { AnyProtocolEvent } from "@grants-stack-indexer/shared"; +import { gql, GraphQLClient } from "graphql-request"; + +import { IndexerClientError, InvalidIndexerResponse } from "../exceptions/index.js"; +import { IIndexerClient } from "../internal.js"; + +export class EnvioIndexerClient implements IIndexerClient { + private client: GraphQLClient; + + constructor(url: string, secret: string) { + this.client = new GraphQLClient(url); + this.client.setHeader("x-hasura-admin-secret", secret); + } + + public async getEventsByBlockNumberAndLogIndex( + chainId: number, + blockNumber: number, + logIndex: number, + limit: number = 100, + ): Promise { + try { + const response = (await this.client.rawRequest(gql` + query getEventsByChainIdBlockNumberAndLogIndex { + raw_events( + where: { + chain_id: { _eq: ${chainId} } + block_number: { _gte: ${blockNumber} } + log_index: { _gt: ${logIndex} } + } + limit: ${limit} + ) { + block_number + block_timestamp + chain_id + contract_name + event_name + log_index + params + src_address + } + } + `)) as { data: { raw_events: AnyProtocolEvent[] } }; + if (response?.data?.raw_events) { + return response.data.raw_events; + } else { + throw new InvalidIndexerResponse(JSON.stringify(response)); + } + } catch (error) { + if (error instanceof InvalidIndexerResponse) { + throw error; + } + throw new IndexerClientError(JSON.stringify(error)); + } + } +} diff --git a/packages/indexer-client/src/providers/index.ts b/packages/indexer-client/src/providers/index.ts new file mode 100644 index 0000000..740ac8d --- /dev/null +++ b/packages/indexer-client/src/providers/index.ts @@ -0,0 +1 @@ +export * from "./envioIndexerClient.js"; diff --git a/packages/indexer-client/test/unit/envioIndexerClient.spec.ts b/packages/indexer-client/test/unit/envioIndexerClient.spec.ts new file mode 100644 index 0000000..7bdb0d3 --- /dev/null +++ b/packages/indexer-client/test/unit/envioIndexerClient.spec.ts @@ -0,0 +1,169 @@ +import { AnyProtocolEvent } from "@grants-stack-indexer/shared"; +import { GraphQLClient } from "graphql-request"; +import { afterEach, beforeEach, describe, expect, it, Mocked, vi } from "vitest"; + +import { IndexerClientError, InvalidIndexerResponse } from "../../src/exceptions/index.js"; +import { EnvioIndexerClient } from "../../src/providers/envioIndexerClient.js"; + +// Mock GraphQLClient +vi.mock("graphql-request", async (importOriginal) => { + const mod: object = await importOriginal(); + return { + ...mod, + GraphQLClient: vi.fn().mockImplementation(() => ({ + setHeader: vi.fn(), + rawRequest: vi.fn(), + })), + }; +}); + +describe("EnvioIndexerClient", () => { + let envioIndexerClient: EnvioIndexerClient; + let graphqlClient: Mocked; + + beforeEach(() => { + envioIndexerClient = new EnvioIndexerClient("http://example.com/graphql", "secret"); + graphqlClient = envioIndexerClient["client"] as unknown as Mocked; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe("constructor", () => { + it("should create a GraphQLClient with the provided URL", () => { + expect(GraphQLClient).toHaveBeenCalledWith("http://example.com/graphql"); + }); + + it("should set the x-hasura-admin-secret header", () => { + expect(graphqlClient.setHeader).toHaveBeenCalledWith("x-hasura-admin-secret", "secret"); + }); + }); + + describe("getEventsByBlockNumberAndLogIndex", () => { + const mockEvents: AnyProtocolEvent[] = [ + { + chain_id: 1, + block_number: 12345, + block_timestamp: 123123123, + contract_name: "Allo", + event_name: "PoolCreated", + event_id: "123", + src_address: "0x1234567890123456789012345678901234567890", + log_index: 0, + params: { contractAddress: "0x1234" }, + }, + ]; + + it("should return events when the query is successful", async () => { + const mockedResponse = { + status: 200, + headers: {}, + data: { + raw_events: mockEvents, + }, + }; + graphqlClient.rawRequest.mockResolvedValue(mockedResponse); + + const result = await envioIndexerClient.getEventsByBlockNumberAndLogIndex( + 1, + 12345, + 0, + 100, + ); + expect(result).toEqual(mockEvents); + }); + + it("should use default limit when not provided", async () => { + const mockedResponse = { + status: 200, + headers: {}, + data: { + raw_events: mockEvents, + }, + }; + graphqlClient.rawRequest.mockResolvedValue(mockedResponse); + + await envioIndexerClient.getEventsByBlockNumberAndLogIndex(1, 12345, 0); + expect(graphqlClient.rawRequest).toHaveBeenCalledWith( + expect.stringContaining("limit: 100"), + ); + }); + + it("should use provided limit", async () => { + const mockedResponse = { + status: 200, + headers: {}, + data: { + raw_events: mockEvents, + }, + }; + graphqlClient.rawRequest.mockResolvedValue(mockedResponse); + + await envioIndexerClient.getEventsByBlockNumberAndLogIndex(1, 12345, 0, 50); + expect(graphqlClient.rawRequest).toHaveBeenCalledWith( + expect.stringContaining("limit: 50"), + ); + }); + + it("should throw InvalidIndexerResponse when response structure is incorrect", async () => { + const mockedResponse = { + status: 200, + headers: {}, + data: { + raw_events: undefined, + }, + }; + graphqlClient.rawRequest.mockResolvedValue(mockedResponse); + + await expect( + envioIndexerClient.getEventsByBlockNumberAndLogIndex(1, 12345, 0), + ).rejects.toThrow(InvalidIndexerResponse); + }); + + it("should throw IndexerClientError when GraphQL request fails", async () => { + const error = new Error("GraphQL request failed"); + graphqlClient.rawRequest.mockRejectedValue(error); + + await expect( + envioIndexerClient.getEventsByBlockNumberAndLogIndex(1, 12345, 0), + ).rejects.toThrow(IndexerClientError); + }); + + it("should include chainId, blockNumber, and logIndex in the query", async () => { + const mockedResponse = { + status: 200, + headers: {}, + data: { + raw_events: mockEvents, + }, + }; + graphqlClient.rawRequest.mockResolvedValue(mockedResponse); + + await envioIndexerClient.getEventsByBlockNumberAndLogIndex(1, 12345, 0); + expect(graphqlClient.rawRequest).toHaveBeenCalledWith( + expect.stringContaining("chain_id: { _eq: 1 }"), + ); + expect(graphqlClient.rawRequest).toHaveBeenCalledWith( + expect.stringContaining("block_number: { _gte: 12345 }"), + ); + expect(graphqlClient.rawRequest).toHaveBeenCalledWith( + expect.stringContaining("log_index: { _gt: 0 }"), + ); + }); + + it("should return an empty array when no events are found", async () => { + const mockedResponse = { + status: 200, + headers: {}, + data: { + raw_events: [], + }, + }; + graphqlClient.rawRequest.mockResolvedValue(mockedResponse); + + const result = await envioIndexerClient.getEventsByBlockNumberAndLogIndex(1, 12345, 0); + expect(result).toEqual([]); + }); + }); +}); diff --git a/packages/indexer-client/tsconfig.build.json b/packages/indexer-client/tsconfig.build.json new file mode 100644 index 0000000..56d3112 --- /dev/null +++ b/packages/indexer-client/tsconfig.build.json @@ -0,0 +1,13 @@ +/* Based on total-typescript no-dom library config */ +/* https://github.com/total-typescript/tsconfig */ +{ + "extends": "../../tsconfig.build.json", + "compilerOptions": { + "composite": true, + "declarationMap": true, + "declaration": true, + "outDir": "dist" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "tests"] +} diff --git a/packages/indexer-client/tsconfig.json b/packages/indexer-client/tsconfig.json new file mode 100644 index 0000000..66bb87a --- /dev/null +++ b/packages/indexer-client/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src/**/*"] +} diff --git a/packages/indexer-client/vitest.config.ts b/packages/indexer-client/vitest.config.ts new file mode 100644 index 0000000..8e1bbf4 --- /dev/null +++ b/packages/indexer-client/vitest.config.ts @@ -0,0 +1,22 @@ +import path from "path"; +import { configDefaults, defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + globals: true, // Use Vitest's global API without importing it in each file + environment: "node", // Use the Node.js environment + include: ["test/**/*.spec.ts"], // Include test files + exclude: ["node_modules", "dist"], // Exclude certain directories + coverage: { + provider: "v8", + reporter: ["text", "json", "html"], // Coverage reporters + exclude: ["node_modules", "dist", "src/index.ts", ...configDefaults.exclude], // Files to exclude from coverage + }, + }, + resolve: { + alias: { + // Setup path alias based on tsconfig paths + "@": path.resolve(__dirname, "src"), + }, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c099c36..7d98546 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -98,6 +98,12 @@ importers: packages/data-flow: dependencies: + "@grants-stack-indexer/indexer-client": + specifier: workspace:* + version: link:../indexer-client + "@grants-stack-indexer/shared": + specifier: workspace:* + version: link:../shared viem: specifier: 2.21.19 version: 2.21.19(typescript@5.5.4)(zod@3.23.8) From e48358ede75801d527de37bc0b45d89157040157 Mon Sep 17 00:00:00 2001 From: 0xkenj1 Date: Tue, 8 Oct 2024 16:04:18 -0300 Subject: [PATCH 2/7] fix: viem dep --- packages/indexer-client/src/internal.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/indexer-client/src/internal.ts b/packages/indexer-client/src/internal.ts index 9f7bdbf..092eb9a 100644 --- a/packages/indexer-client/src/internal.ts +++ b/packages/indexer-client/src/internal.ts @@ -1,4 +1,3 @@ -export type { Address } from "viem"; export * from "./exceptions/index.js"; export * from "./interfaces/index.js"; export * from "./providers/index.js"; From e869159f4189789d78d69a8bb4848f1d16d09911 Mon Sep 17 00:00:00 2001 From: 0xkenj1 Date: Wed, 9 Oct 2024 11:39:35 -0300 Subject: [PATCH 3/7] fix: pr comments --- packages/data-flow/package.json | 2 +- packages/data-flow/src/eventsFetcher.ts | 4 ++-- packages/data-flow/src/interfaces/index.ts | 12 +++++++++++- packages/indexer-client/package.json | 2 +- .../indexer-client/src/interfaces/indexerClient.ts | 3 ++- .../src/providers/envioIndexerClient.ts | 9 ++++++--- 6 files changed, 23 insertions(+), 9 deletions(-) diff --git a/packages/data-flow/package.json b/packages/data-flow/package.json index cfe98fa..9d7d84f 100644 --- a/packages/data-flow/package.json +++ b/packages/data-flow/package.json @@ -19,7 +19,7 @@ "scripts": { "build": "tsc -p tsconfig.build.json", "check-types": "tsc --noEmit -p ./tsconfig.json", - "clean": "rm -rf dist", + "clean": "rm -rf dist/", "format": "prettier --check \"{src,test}/**/*.{js,ts,json}\"", "format:fix": "prettier --write \"{src,test}/**/*.{js,ts,json}\"", "lint": "eslint \"{src,test}/**/*.{js,ts,json}\"", diff --git a/packages/data-flow/src/eventsFetcher.ts b/packages/data-flow/src/eventsFetcher.ts index a4cb761..dcbeddd 100644 --- a/packages/data-flow/src/eventsFetcher.ts +++ b/packages/data-flow/src/eventsFetcher.ts @@ -5,8 +5,8 @@ import { IEventsFetcher } from "./interfaces/index.js"; export class EventsFetcher implements IEventsFetcher { constructor(private indexerClient: IIndexerClient) {} - - async fetcEventsByBlockNumberAndLogIndex( + /* @inheritdoc */ + async fetchEventsByBlockNumberAndLogIndex( chainId: number, blockNumber: number, logIndex: number, diff --git a/packages/data-flow/src/interfaces/index.ts b/packages/data-flow/src/interfaces/index.ts index c0fd809..d8366c6 100644 --- a/packages/data-flow/src/interfaces/index.ts +++ b/packages/data-flow/src/interfaces/index.ts @@ -1,7 +1,17 @@ import { AnyProtocolEvent } from "@grants-stack-indexer/shared"; +/** + * Interface for the events fetcher + */ export interface IEventsFetcher { - fetcEventsByBlockNumberAndLogIndex( + /** + * Fetch the events by block number and log index for a chain + * @param chainId id of the chain + * @param blockNumber block number to fetch events from + * @param logIndex log index in the block to fetch events from + * @param limit limit of events to fetch + */ + fetchEventsByBlockNumberAndLogIndex( chainId: number, blockNumber: number, logIndex: number, diff --git a/packages/indexer-client/package.json b/packages/indexer-client/package.json index 3a8ff5b..71b42b6 100644 --- a/packages/indexer-client/package.json +++ b/packages/indexer-client/package.json @@ -19,7 +19,7 @@ "scripts": { "build": "tsc -p tsconfig.build.json", "check-types": "tsc --noEmit -p ./tsconfig.json", - "clean": "rm -rf dist", + "clean": "rm -rf dist/", "format": "prettier --check \"{src,test}/**/*.{js,ts,json}\"", "format:fix": "prettier --write \"{src,test}/**/*.{js,ts,json}\"", "lint": "eslint \"{src,test}/**/*.{js,ts,json}\"", diff --git a/packages/indexer-client/src/interfaces/indexerClient.ts b/packages/indexer-client/src/interfaces/indexerClient.ts index 71cc026..e0b3a46 100644 --- a/packages/indexer-client/src/interfaces/indexerClient.ts +++ b/packages/indexer-client/src/interfaces/indexerClient.ts @@ -9,8 +9,9 @@ export interface IIndexerClient { * @param chainId Id of the chain * @param fromBlock Block number to start fetching events from * @param logIndex Log index in the block + * @param limit Limit of events to fetch */ - getEventsByBlockNumberAndLogIndex( + getEventsAfterBlockNumberAndLogIndex( chainId: number, fromBlock: number, logIndex: number, diff --git a/packages/indexer-client/src/providers/envioIndexerClient.ts b/packages/indexer-client/src/providers/envioIndexerClient.ts index 8764cd5..8b38e41 100644 --- a/packages/indexer-client/src/providers/envioIndexerClient.ts +++ b/packages/indexer-client/src/providers/envioIndexerClient.ts @@ -4,6 +4,9 @@ import { gql, GraphQLClient } from "graphql-request"; import { IndexerClientError, InvalidIndexerResponse } from "../exceptions/index.js"; import { IIndexerClient } from "../internal.js"; +/** + * Indexer client for the Envio indexer service + */ export class EnvioIndexerClient implements IIndexerClient { private client: GraphQLClient; @@ -11,8 +14,8 @@ export class EnvioIndexerClient implements IIndexerClient { this.client = new GraphQLClient(url); this.client.setHeader("x-hasura-admin-secret", secret); } - - public async getEventsByBlockNumberAndLogIndex( + /* @inheritdoc */ + public async getEventsAfterBlockNumberAndLogIndex( chainId: number, blockNumber: number, logIndex: number, @@ -20,7 +23,7 @@ export class EnvioIndexerClient implements IIndexerClient { ): Promise { try { const response = (await this.client.rawRequest(gql` - query getEventsByChainIdBlockNumberAndLogIndex { + query getEventsAfterBlockNumberAndLogIndex { raw_events( where: { chain_id: { _eq: ${chainId} } From 22d1c1ad13a6de9bf43bb724b9de2ef7712f9bc8 Mon Sep 17 00:00:00 2001 From: 0xkenj1 Date: Wed, 9 Oct 2024 11:45:03 -0300 Subject: [PATCH 4/7] fix: tests --- packages/data-flow/src/eventsFetcher.ts | 2 +- .../data-flow/test/unit/eventsFetcher.spec.ts | 12 +++++------ .../test/unit/envioIndexerClient.spec.ts | 20 +++++++++++-------- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/packages/data-flow/src/eventsFetcher.ts b/packages/data-flow/src/eventsFetcher.ts index dcbeddd..b061c52 100644 --- a/packages/data-flow/src/eventsFetcher.ts +++ b/packages/data-flow/src/eventsFetcher.ts @@ -12,7 +12,7 @@ export class EventsFetcher implements IEventsFetcher { logIndex: number, limit: number = 100, ): Promise { - return await this.indexerClient.getEventsByBlockNumberAndLogIndex( + return await this.indexerClient.getEventsAfterBlockNumberAndLogIndex( chainId, blockNumber, logIndex, diff --git a/packages/data-flow/test/unit/eventsFetcher.spec.ts b/packages/data-flow/test/unit/eventsFetcher.spec.ts index b7ad643..df7620e 100644 --- a/packages/data-flow/test/unit/eventsFetcher.spec.ts +++ b/packages/data-flow/test/unit/eventsFetcher.spec.ts @@ -10,7 +10,7 @@ describe("EventsFetcher", () => { beforeEach(() => { indexerClientMock = { - getEventsByBlockNumberAndLogIndex: vi.fn(), + getEventsAfterBlockNumberAndLogIndex: vi.fn(), }; eventsFetcher = new EventsFetcher(indexerClientMock); @@ -46,15 +46,15 @@ describe("EventsFetcher", () => { const logIndex = 0; const limit = 100; - indexerClientMock.getEventsByBlockNumberAndLogIndex.mockResolvedValue(mockEvents); + indexerClientMock.getEventsAfterBlockNumberAndLogIndex.mockResolvedValue(mockEvents); - const result = await eventsFetcher.fetcEventsByBlockNumberAndLogIndex( + const result = await eventsFetcher.fetchEventsByBlockNumberAndLogIndex( chainId, blockNumber, logIndex, ); - expect(indexerClientMock.getEventsByBlockNumberAndLogIndex).toHaveBeenCalledWith( + expect(indexerClientMock.getEventsAfterBlockNumberAndLogIndex).toHaveBeenCalledWith( chainId, blockNumber, logIndex, @@ -68,12 +68,12 @@ describe("EventsFetcher", () => { const blockNumber = 1000; const logIndex = 0; - indexerClientMock.getEventsByBlockNumberAndLogIndex.mockRejectedValue( + indexerClientMock.getEventsAfterBlockNumberAndLogIndex.mockRejectedValue( new Error("Network error"), ); await expect( - eventsFetcher.fetcEventsByBlockNumberAndLogIndex(chainId, blockNumber, logIndex), + eventsFetcher.fetchEventsByBlockNumberAndLogIndex(chainId, blockNumber, logIndex), ).rejects.toThrow("Network error"); }); }); diff --git a/packages/indexer-client/test/unit/envioIndexerClient.spec.ts b/packages/indexer-client/test/unit/envioIndexerClient.spec.ts index 7bdb0d3..97e28ce 100644 --- a/packages/indexer-client/test/unit/envioIndexerClient.spec.ts +++ b/packages/indexer-client/test/unit/envioIndexerClient.spec.ts @@ -40,7 +40,7 @@ describe("EnvioIndexerClient", () => { }); }); - describe("getEventsByBlockNumberAndLogIndex", () => { + describe("getEventsAfterBlockNumberAndLogIndex", () => { const mockEvents: AnyProtocolEvent[] = [ { chain_id: 1, @@ -65,7 +65,7 @@ describe("EnvioIndexerClient", () => { }; graphqlClient.rawRequest.mockResolvedValue(mockedResponse); - const result = await envioIndexerClient.getEventsByBlockNumberAndLogIndex( + const result = await envioIndexerClient.getEventsAfterBlockNumberAndLogIndex( 1, 12345, 0, @@ -84,7 +84,7 @@ describe("EnvioIndexerClient", () => { }; graphqlClient.rawRequest.mockResolvedValue(mockedResponse); - await envioIndexerClient.getEventsByBlockNumberAndLogIndex(1, 12345, 0); + await envioIndexerClient.getEventsAfterBlockNumberAndLogIndex(1, 12345, 0); expect(graphqlClient.rawRequest).toHaveBeenCalledWith( expect.stringContaining("limit: 100"), ); @@ -100,7 +100,7 @@ describe("EnvioIndexerClient", () => { }; graphqlClient.rawRequest.mockResolvedValue(mockedResponse); - await envioIndexerClient.getEventsByBlockNumberAndLogIndex(1, 12345, 0, 50); + await envioIndexerClient.getEventsAfterBlockNumberAndLogIndex(1, 12345, 0, 50); expect(graphqlClient.rawRequest).toHaveBeenCalledWith( expect.stringContaining("limit: 50"), ); @@ -117,7 +117,7 @@ describe("EnvioIndexerClient", () => { graphqlClient.rawRequest.mockResolvedValue(mockedResponse); await expect( - envioIndexerClient.getEventsByBlockNumberAndLogIndex(1, 12345, 0), + envioIndexerClient.getEventsAfterBlockNumberAndLogIndex(1, 12345, 0), ).rejects.toThrow(InvalidIndexerResponse); }); @@ -126,7 +126,7 @@ describe("EnvioIndexerClient", () => { graphqlClient.rawRequest.mockRejectedValue(error); await expect( - envioIndexerClient.getEventsByBlockNumberAndLogIndex(1, 12345, 0), + envioIndexerClient.getEventsAfterBlockNumberAndLogIndex(1, 12345, 0), ).rejects.toThrow(IndexerClientError); }); @@ -140,7 +140,7 @@ describe("EnvioIndexerClient", () => { }; graphqlClient.rawRequest.mockResolvedValue(mockedResponse); - await envioIndexerClient.getEventsByBlockNumberAndLogIndex(1, 12345, 0); + await envioIndexerClient.getEventsAfterBlockNumberAndLogIndex(1, 12345, 0); expect(graphqlClient.rawRequest).toHaveBeenCalledWith( expect.stringContaining("chain_id: { _eq: 1 }"), ); @@ -162,7 +162,11 @@ describe("EnvioIndexerClient", () => { }; graphqlClient.rawRequest.mockResolvedValue(mockedResponse); - const result = await envioIndexerClient.getEventsByBlockNumberAndLogIndex(1, 12345, 0); + const result = await envioIndexerClient.getEventsAfterBlockNumberAndLogIndex( + 1, + 12345, + 0, + ); expect(result).toEqual([]); }); }); From 48a3c105c7f6c272e3518685c5cea5c18f5a458f Mon Sep 17 00:00:00 2001 From: 0xkenj1 Date: Wed, 9 Oct 2024 15:14:34 -0300 Subject: [PATCH 5/7] feat: add datalayer pg --- .env.example | 42 ++++++++++++++++++++++++++---------------- docker-compose.yaml | 25 +++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 18 deletions(-) diff --git a/.env.example b/.env.example index 61ebc6a..4823dba 100644 --- a/.env.example +++ b/.env.example @@ -1,10 +1,31 @@ +###################################################### +############### DATALAYER POSTGRES ################### +###################################################### +DATALAYER_POSTGRES_PASSWORD=testing +DATALAYER_PG_USER=postgres +DATALAYER_PG_DATABASE=datalayer-postgres +DATALAYER_POSTGRES_EXPOSED_PORT=5434 + + +############################################################ +############### ENVIO POSTGRES & INDEXER ################### +############################################################ +ENVIO_POSTGRES_PASSWORD=testing +ENVIO_PG_USER=postgres +ENVIO_PG_DATABASE=envio-dev + ############################################ ############### POSTGRES ################### ############################################ -POSTGRES_EXPOSED_PORT=5433 -POSTGRES_PASSWORD=testing -POSTGRES_USER=postgres -POSTGRES_DB=envio-dev +ENVIO_POSTGRES_EXPOSED_PORT=5433 + +############################################ +############### INDEXER #################### +############################################ +ENVIO_PG_HOST=envio-postgres +ENVIO_PG_PORT=5432 +HASURA_GRAPHQL_ENDPOINT=http://graphql-engine:8080/v1/metadata +TUI_OFF=true ############################################ ############### HASURA ##################### @@ -17,15 +38,4 @@ HASURA_GRAPHQL_NO_OF_RETRIES=10 HASURA_GRAPHQL_ADMIN_SECRET=testing HASURA_GRAPHQL_STRINGIFY_NUMERIC_TYPES="true" PORT=8080 -HASURA_GRAPHQL_UNAUTHORIZED_ROLE=public - -############################################ -############### INDEXER #################### -############################################ -ENVIO_PG_HOST=envio-postgres -ENVIO_PG_PORT=5432 -ENVIO_POSTGRES_PASSWORD=testing -ENVIO_PG_USER=postgres -ENVIO_PG_DATABASE=envio-dev -HASURA_GRAPHQL_ENDPOINT=http://graphql-engine:8080/v1/metadata -TUI_OFF=true \ No newline at end of file +HASURA_GRAPHQL_UNAUTHORIZED_ROLE=public \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 5ec4734..892e3fa 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,13 +1,32 @@ services: + datalayer-postgres: + image: postgres:16 + restart: always + ports: + - "${DATALAYER_POSTGRES_EXPOSED_PORT:-5432}:5432" + volumes: + - db_data:/var/lib/datalayer-postgresql/data + env_file: + - .env + environment: + POSTGRES_DB: ${DATALAYER_PG_DATABASE} + POSTGRES_USER: ${DATALAYER_PG_USER} + POSTGRES_PASSWORD: ${DATALAYER_POSTGRES_PASSWORD} + networks: + - datalayer envio-postgres: image: postgres:16 restart: always ports: - - "${POSTGRES_PORT:-5433}:5432" + - "${ENVIO_POSTGRES_EXPOSED_PORT:-5433}:5432" volumes: - - db_data:/var/lib/postgresql/data + - db_data:/var/lib/envio-postgresql/data env_file: - .env + environment: + POSTGRES_DB: ${ENVIO_PG_DATABASE} + POSTGRES_USER: ${ENVIO_PG_USER} + POSTGRES_PASSWORD: ${ENVIO_POSTGRES_PASSWORD} networks: - indexer-service graphql-engine: @@ -46,3 +65,5 @@ volumes: networks: indexer-service: name: indexer_test_network + datalayer: + name: datalayer_test_network From 9cdbedf97e8f2f8a0b7dfbb36e1c7e0b42d2f7fe Mon Sep 17 00:00:00 2001 From: 0xkenj1 Date: Thu, 10 Oct 2024 11:29:59 -0300 Subject: [PATCH 6/7] fix: pr comments --- packages/data-flow/src/eventsFetcher.ts | 4 +- packages/data-flow/src/interfaces/index.ts | 4 +- .../data-flow/test/unit/eventsFetcher.spec.ts | 10 +- .../src/interfaces/indexerClient.ts | 4 +- .../src/providers/envioIndexerClient.ts | 50 +++++----- .../test/unit/envioIndexerClient.spec.ts | 94 +++++++------------ packages/shared/src/types/events/common.ts | 16 ++-- 7 files changed, 81 insertions(+), 101 deletions(-) diff --git a/packages/data-flow/src/eventsFetcher.ts b/packages/data-flow/src/eventsFetcher.ts index b061c52..cf83383 100644 --- a/packages/data-flow/src/eventsFetcher.ts +++ b/packages/data-flow/src/eventsFetcher.ts @@ -7,8 +7,8 @@ export class EventsFetcher implements IEventsFetcher { constructor(private indexerClient: IIndexerClient) {} /* @inheritdoc */ async fetchEventsByBlockNumberAndLogIndex( - chainId: number, - blockNumber: number, + chainId: bigint, + blockNumber: bigint, logIndex: number, limit: number = 100, ): Promise { diff --git a/packages/data-flow/src/interfaces/index.ts b/packages/data-flow/src/interfaces/index.ts index d8366c6..a02c04e 100644 --- a/packages/data-flow/src/interfaces/index.ts +++ b/packages/data-flow/src/interfaces/index.ts @@ -12,8 +12,8 @@ export interface IEventsFetcher { * @param limit limit of events to fetch */ fetchEventsByBlockNumberAndLogIndex( - chainId: number, - blockNumber: number, + chainId: bigint, + blockNumber: bigint, logIndex: number, limit?: number, ): Promise; diff --git a/packages/data-flow/test/unit/eventsFetcher.spec.ts b/packages/data-flow/test/unit/eventsFetcher.spec.ts index df7620e..5a2de23 100644 --- a/packages/data-flow/test/unit/eventsFetcher.spec.ts +++ b/packages/data-flow/test/unit/eventsFetcher.spec.ts @@ -24,7 +24,6 @@ describe("EventsFetcher", () => { block_timestamp: 123123123, contract_name: "Allo", event_name: "PoolCreated", - event_id: "123", src_address: "0x1234567890123456789012345678901234567890", log_index: 0, params: { contractAddress: "0x1234" }, @@ -35,14 +34,13 @@ describe("EventsFetcher", () => { block_timestamp: 123123123, contract_name: "Allo", event_name: "PoolCreated", - event_id: "123", src_address: "0x1234567890123456789012345678901234567890", log_index: 0, params: { contractAddress: "0x1234" }, }, ]; - const chainId = 1; - const blockNumber = 1000; + const chainId = 1n; + const blockNumber = 1000n; const logIndex = 0; const limit = 100; @@ -64,8 +62,8 @@ describe("EventsFetcher", () => { }); it("should handle errors thrown by indexer client", async () => { - const chainId = 1; - const blockNumber = 1000; + const chainId = 1n; + const blockNumber = 1000n; const logIndex = 0; indexerClientMock.getEventsAfterBlockNumberAndLogIndex.mockRejectedValue( diff --git a/packages/indexer-client/src/interfaces/indexerClient.ts b/packages/indexer-client/src/interfaces/indexerClient.ts index e0b3a46..f1fca99 100644 --- a/packages/indexer-client/src/interfaces/indexerClient.ts +++ b/packages/indexer-client/src/interfaces/indexerClient.ts @@ -12,8 +12,8 @@ export interface IIndexerClient { * @param limit Limit of events to fetch */ getEventsAfterBlockNumberAndLogIndex( - chainId: number, - fromBlock: number, + chainId: bigint, + fromBlock: bigint, logIndex: number, limit?: number, ): Promise; diff --git a/packages/indexer-client/src/providers/envioIndexerClient.ts b/packages/indexer-client/src/providers/envioIndexerClient.ts index 8b38e41..1bfdd2f 100644 --- a/packages/indexer-client/src/providers/envioIndexerClient.ts +++ b/packages/indexer-client/src/providers/envioIndexerClient.ts @@ -16,33 +16,41 @@ export class EnvioIndexerClient implements IIndexerClient { } /* @inheritdoc */ public async getEventsAfterBlockNumberAndLogIndex( - chainId: number, - blockNumber: number, + chainId: bigint, + blockNumber: bigint, logIndex: number, limit: number = 100, ): Promise { try { - const response = (await this.client.rawRequest(gql` - query getEventsAfterBlockNumberAndLogIndex { - raw_events( - where: { - chain_id: { _eq: ${chainId} } - block_number: { _gte: ${blockNumber} } - log_index: { _gt: ${logIndex} } - } - limit: ${limit} + const response = (await this.client.request( + gql` + query getEventsAfterBlockNumberAndLogIndex( + $chainId: Int! + $blockNumber: Int! + $logIndex: Int! + $limit: Int! ) { - block_number - block_timestamp - chain_id - contract_name - event_name - log_index - params - src_address + raw_events( + where: { + chain_id: { _eq: $chainId } + block_number: { _gte: $blockNumber } + log_index: { _gt: $logIndex } + } + limit: $limit + ) { + block_number: blockNumber + block_timestamp: blockTimestamp + chain_id: chainId + contract_name: contractName + event_name: eventName + log_index: logIndex + params + src_address: srcAddress + } } - } - `)) as { data: { raw_events: AnyProtocolEvent[] } }; + `, + { chainId, blockNumber, logIndex, limit }, + )) as { data: { raw_events: AnyProtocolEvent[] } }; if (response?.data?.raw_events) { return response.data.raw_events; } else { diff --git a/packages/indexer-client/test/unit/envioIndexerClient.spec.ts b/packages/indexer-client/test/unit/envioIndexerClient.spec.ts index 97e28ce..2c46dee 100644 --- a/packages/indexer-client/test/unit/envioIndexerClient.spec.ts +++ b/packages/indexer-client/test/unit/envioIndexerClient.spec.ts @@ -12,7 +12,7 @@ vi.mock("graphql-request", async (importOriginal) => { ...mod, GraphQLClient: vi.fn().mockImplementation(() => ({ setHeader: vi.fn(), - rawRequest: vi.fn(), + request: vi.fn(), })), }; }); @@ -31,11 +31,11 @@ describe("EnvioIndexerClient", () => { }); describe("constructor", () => { - it("should create a GraphQLClient with the provided URL", () => { + it("creates a GraphQLClient with the provided URL", () => { expect(GraphQLClient).toHaveBeenCalledWith("http://example.com/graphql"); }); - it("should set the x-hasura-admin-secret header", () => { + it("sets the x-hasura-admin-secret header", () => { expect(graphqlClient.setHeader).toHaveBeenCalledWith("x-hasura-admin-secret", "secret"); }); }); @@ -48,14 +48,13 @@ describe("EnvioIndexerClient", () => { block_timestamp: 123123123, contract_name: "Allo", event_name: "PoolCreated", - event_id: "123", src_address: "0x1234567890123456789012345678901234567890", log_index: 0, params: { contractAddress: "0x1234" }, }, ]; - it("should return events when the query is successful", async () => { + it("returns events when the query is successful", async () => { const mockedResponse = { status: 200, headers: {}, @@ -63,50 +62,18 @@ describe("EnvioIndexerClient", () => { raw_events: mockEvents, }, }; - graphqlClient.rawRequest.mockResolvedValue(mockedResponse); + graphqlClient.request.mockResolvedValue(mockedResponse); const result = await envioIndexerClient.getEventsAfterBlockNumberAndLogIndex( - 1, - 12345, + 1n, + 12345n, 0, 100, ); expect(result).toEqual(mockEvents); }); - it("should use default limit when not provided", async () => { - const mockedResponse = { - status: 200, - headers: {}, - data: { - raw_events: mockEvents, - }, - }; - graphqlClient.rawRequest.mockResolvedValue(mockedResponse); - - await envioIndexerClient.getEventsAfterBlockNumberAndLogIndex(1, 12345, 0); - expect(graphqlClient.rawRequest).toHaveBeenCalledWith( - expect.stringContaining("limit: 100"), - ); - }); - - it("should use provided limit", async () => { - const mockedResponse = { - status: 200, - headers: {}, - data: { - raw_events: mockEvents, - }, - }; - graphqlClient.rawRequest.mockResolvedValue(mockedResponse); - - await envioIndexerClient.getEventsAfterBlockNumberAndLogIndex(1, 12345, 0, 50); - expect(graphqlClient.rawRequest).toHaveBeenCalledWith( - expect.stringContaining("limit: 50"), - ); - }); - - it("should throw InvalidIndexerResponse when response structure is incorrect", async () => { + it("throws InvalidIndexerResponse when response structure is incorrect", async () => { const mockedResponse = { status: 200, headers: {}, @@ -114,23 +81,23 @@ describe("EnvioIndexerClient", () => { raw_events: undefined, }, }; - graphqlClient.rawRequest.mockResolvedValue(mockedResponse); + graphqlClient.request.mockResolvedValue(mockedResponse); await expect( - envioIndexerClient.getEventsAfterBlockNumberAndLogIndex(1, 12345, 0), + envioIndexerClient.getEventsAfterBlockNumberAndLogIndex(1n, 12345n, 0), ).rejects.toThrow(InvalidIndexerResponse); }); - it("should throw IndexerClientError when GraphQL request fails", async () => { + it("throws IndexerClientError when GraphQL request fails", async () => { const error = new Error("GraphQL request failed"); - graphqlClient.rawRequest.mockRejectedValue(error); + graphqlClient.request.mockRejectedValue(error); await expect( - envioIndexerClient.getEventsAfterBlockNumberAndLogIndex(1, 12345, 0), + envioIndexerClient.getEventsAfterBlockNumberAndLogIndex(1n, 12345n, 0), ).rejects.toThrow(IndexerClientError); }); - it("should include chainId, blockNumber, and logIndex in the query", async () => { + it("uses the default limit value when limit is not provided", async () => { const mockedResponse = { status: 200, headers: {}, @@ -138,21 +105,28 @@ describe("EnvioIndexerClient", () => { raw_events: mockEvents, }, }; - graphqlClient.rawRequest.mockResolvedValue(mockedResponse); + graphqlClient.request.mockResolvedValue(mockedResponse); - await envioIndexerClient.getEventsAfterBlockNumberAndLogIndex(1, 12345, 0); - expect(graphqlClient.rawRequest).toHaveBeenCalledWith( - expect.stringContaining("chain_id: { _eq: 1 }"), - ); - expect(graphqlClient.rawRequest).toHaveBeenCalledWith( - expect.stringContaining("block_number: { _gte: 12345 }"), + // Call the method without the limit argument + const result = await envioIndexerClient.getEventsAfterBlockNumberAndLogIndex( + 1n, + 12345n, + 0, ); - expect(graphqlClient.rawRequest).toHaveBeenCalledWith( - expect.stringContaining("log_index: { _gt: 0 }"), + + expect(result).toEqual(mockEvents); + expect(graphqlClient.request).toHaveBeenCalledWith( + expect.any(String), // We can check the query string later if necessary + { + chainId: 1n, + blockNumber: 12345n, + logIndex: 0, + limit: 100, // Ensure the default limit is used + }, ); }); - it("should return an empty array when no events are found", async () => { + it("returns an empty array when no events are found", async () => { const mockedResponse = { status: 200, headers: {}, @@ -160,11 +134,11 @@ describe("EnvioIndexerClient", () => { raw_events: [], }, }; - graphqlClient.rawRequest.mockResolvedValue(mockedResponse); + graphqlClient.request.mockResolvedValue(mockedResponse); const result = await envioIndexerClient.getEventsAfterBlockNumberAndLogIndex( - 1, - 12345, + 1n, + 12345n, 0, ); expect(result).toEqual([]); diff --git a/packages/shared/src/types/events/common.ts b/packages/shared/src/types/events/common.ts index e64fb7e..3b4921b 100644 --- a/packages/shared/src/types/events/common.ts +++ b/packages/shared/src/types/events/common.ts @@ -30,15 +30,15 @@ export type EventParams * This type is used to represent a protocol event. */ export type ProtocolEvent> = { - block_number: number; - block_timestamp: number; - chain_id: number; - contract_name: T; - event_id: string; - event_name: E; - log_index: number; + //TODO: make blocknumber and chainId bigints, implies implementing adapter patterns in the EventsFetcher or IndexerClient + blockNumber: number; + blockTimestamp: number; + chainId: number; + contractName: T; + eventName: E; + logIndex: number; params: EventParams; - src_address: Address; + srcAddress: Address; }; export type AnyProtocolEvent = ProtocolEvent>; From 76ba4f9a567669fa41805f11cec7080be441ef00 Mon Sep 17 00:00:00 2001 From: 0xkenj1 Date: Thu, 10 Oct 2024 11:45:37 -0300 Subject: [PATCH 7/7] fix: format --- .../data-flow/test/unit/eventsFetcher.spec.ts | 3 ++- .../src/providers/envioIndexerClient.ts | 3 ++- .../test/unit/envioIndexerClient.spec.ts | 17 +++++++++-------- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/data-flow/test/unit/eventsFetcher.spec.ts b/packages/data-flow/test/unit/eventsFetcher.spec.ts index 5a2de23..a547b5e 100644 --- a/packages/data-flow/test/unit/eventsFetcher.spec.ts +++ b/packages/data-flow/test/unit/eventsFetcher.spec.ts @@ -1,6 +1,7 @@ +import { beforeEach, describe, expect, it, Mocked, vi } from "vitest"; + import { IIndexerClient } from "@grants-stack-indexer/indexer-client"; import { AnyProtocolEvent } from "@grants-stack-indexer/shared"; -import { beforeEach, describe, expect, it, Mocked, vi } from "vitest"; import { EventsFetcher } from "../../src/eventsFetcher.js"; diff --git a/packages/indexer-client/src/providers/envioIndexerClient.ts b/packages/indexer-client/src/providers/envioIndexerClient.ts index 1bfdd2f..8c210f4 100644 --- a/packages/indexer-client/src/providers/envioIndexerClient.ts +++ b/packages/indexer-client/src/providers/envioIndexerClient.ts @@ -1,6 +1,7 @@ -import { AnyProtocolEvent } from "@grants-stack-indexer/shared"; import { gql, GraphQLClient } from "graphql-request"; +import { AnyProtocolEvent } from "@grants-stack-indexer/shared"; + import { IndexerClientError, InvalidIndexerResponse } from "../exceptions/index.js"; import { IIndexerClient } from "../internal.js"; diff --git a/packages/indexer-client/test/unit/envioIndexerClient.spec.ts b/packages/indexer-client/test/unit/envioIndexerClient.spec.ts index 2c46dee..1a9abb8 100644 --- a/packages/indexer-client/test/unit/envioIndexerClient.spec.ts +++ b/packages/indexer-client/test/unit/envioIndexerClient.spec.ts @@ -1,7 +1,8 @@ -import { AnyProtocolEvent } from "@grants-stack-indexer/shared"; import { GraphQLClient } from "graphql-request"; import { afterEach, beforeEach, describe, expect, it, Mocked, vi } from "vitest"; +import { AnyProtocolEvent } from "@grants-stack-indexer/shared"; + import { IndexerClientError, InvalidIndexerResponse } from "../../src/exceptions/index.js"; import { EnvioIndexerClient } from "../../src/providers/envioIndexerClient.js"; @@ -43,13 +44,13 @@ describe("EnvioIndexerClient", () => { describe("getEventsAfterBlockNumberAndLogIndex", () => { const mockEvents: AnyProtocolEvent[] = [ { - chain_id: 1, - block_number: 12345, - block_timestamp: 123123123, - contract_name: "Allo", - event_name: "PoolCreated", - src_address: "0x1234567890123456789012345678901234567890", - log_index: 0, + chainId: 1, + blockNumber: 12345, + blockTimestamp: 123123123, + contractName: "Allo", + eventName: "PoolCreated", + srcAddress: "0x1234567890123456789012345678901234567890", + logIndex: 0, params: { contractAddress: "0x1234" }, }, ];