Skip to content

Commit

Permalink
feat: events fetcher & indexer client (#4)
Browse files Browse the repository at this point in the history
# 🤖 Linear

Closes GIT-53 GIT-84 GIT-85

## Description
Implementation of 
- `data-flow` => `EventsFetcher`
- `indexer-client` => `EnvioIndexerClient`

## Checklist before requesting a review

- [x] I have conducted a self-review of my code.
- [x] I have conducted a QA.
- [x] If it is a core feature, I have included comprehensive tests.
  • Loading branch information
0xkenj1 authored Oct 10, 2024
2 parents b3bf530 + 76ba4f9 commit 41ff0c0
Show file tree
Hide file tree
Showing 30 changed files with 781 additions and 8 deletions.
45 changes: 45 additions & 0 deletions packages/data-flow/README.md
Original file line number Diff line number Diff line change
@@ -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,
);
```
35 changes: 35 additions & 0 deletions packages/data-flow/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
22 changes: 22 additions & 0 deletions packages/data-flow/src/eventsFetcher.ts
Original file line number Diff line number Diff line change
@@ -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) {}
/* @inheritdoc */
async fetchEventsByBlockNumberAndLogIndex(
chainId: bigint,
blockNumber: bigint,
logIndex: number,
limit: number = 100,
): Promise<AnyProtocolEvent[]> {
return await this.indexerClient.getEventsAfterBlockNumberAndLogIndex(
chainId,
blockNumber,
logIndex,
limit,
);
}
}
Empty file.
1 change: 1 addition & 0 deletions packages/data-flow/src/external.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { EventsFetcher } from "./internal.js";
1 change: 1 addition & 0 deletions packages/data-flow/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./external.js";
20 changes: 20 additions & 0 deletions packages/data-flow/src/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { AnyProtocolEvent } from "@grants-stack-indexer/shared";

/**
* Interface for the events fetcher
*/
export interface IEventsFetcher {
/**
* 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: bigint,
blockNumber: bigint,
logIndex: number,
limit?: number,
): Promise<AnyProtocolEvent[]>;
}
1 change: 1 addition & 0 deletions packages/data-flow/src/internal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./eventsFetcher.js";
78 changes: 78 additions & 0 deletions packages/data-flow/test/unit/eventsFetcher.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
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 { EventsFetcher } from "../../src/eventsFetcher.js";

describe("EventsFetcher", () => {
let indexerClientMock: Mocked<IIndexerClient>;
let eventsFetcher: EventsFetcher;

beforeEach(() => {
indexerClientMock = {
getEventsAfterBlockNumberAndLogIndex: 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",
src_address: "0x1234567890123456789012345678901234567890",
log_index: 0,
params: { contractAddress: "0x1234" },
},
{
chain_id: 1,
block_number: 12345,
block_timestamp: 123123123,
contract_name: "Allo",
event_name: "PoolCreated",
src_address: "0x1234567890123456789012345678901234567890",
log_index: 0,
params: { contractAddress: "0x1234" },
},
];
const chainId = 1n;
const blockNumber = 1000n;
const logIndex = 0;
const limit = 100;

indexerClientMock.getEventsAfterBlockNumberAndLogIndex.mockResolvedValue(mockEvents);

const result = await eventsFetcher.fetchEventsByBlockNumberAndLogIndex(
chainId,
blockNumber,
logIndex,
);

expect(indexerClientMock.getEventsAfterBlockNumberAndLogIndex).toHaveBeenCalledWith(
chainId,
blockNumber,
logIndex,
limit,
);
expect(result).toEqual(mockEvents);
});

it("should handle errors thrown by indexer client", async () => {
const chainId = 1n;
const blockNumber = 1000n;
const logIndex = 0;

indexerClientMock.getEventsAfterBlockNumberAndLogIndex.mockRejectedValue(
new Error("Network error"),
);

await expect(
eventsFetcher.fetchEventsByBlockNumberAndLogIndex(chainId, blockNumber, logIndex),
).rejects.toThrow("Network error");
});
});
13 changes: 13 additions & 0 deletions packages/data-flow/tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -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"]
}
4 changes: 4 additions & 0 deletions packages/data-flow/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "../../tsconfig.json",
"include": ["src/**/*"]
}
22 changes: 22 additions & 0 deletions packages/data-flow/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -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"),
},
},
});
36 changes: 36 additions & 0 deletions packages/indexer-client/README.md
Original file line number Diff line number Diff line change
@@ -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);
```
34 changes: 34 additions & 0 deletions packages/indexer-client/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
2 changes: 2 additions & 0 deletions packages/indexer-client/src/exceptions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./invalidIndexerResponse.exception.js";
export * from "./indexerClientError.exception.js";
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export class IndexerClientError extends Error {
constructor(message: string) {
super(`Indexer client error - ${message}`);
this.name = "IndexerClientError";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export class InvalidIndexerResponse extends Error {
constructor(response: string) {
super(`Indexer response is invalid - ${response}`);
this.name = "InvalidIndexerResponse";
}
}
3 changes: 3 additions & 0 deletions packages/indexer-client/src/external.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export type { IIndexerClient } from "./internal.js";

export { EnvioIndexerClient } from "./internal.js";
1 change: 1 addition & 0 deletions packages/indexer-client/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./external.js";
1 change: 1 addition & 0 deletions packages/indexer-client/src/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./indexerClient.js";
20 changes: 20 additions & 0 deletions packages/indexer-client/src/interfaces/indexerClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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
* @param limit Limit of events to fetch
*/
getEventsAfterBlockNumberAndLogIndex(
chainId: bigint,
fromBlock: bigint,
logIndex: number,
limit?: number,
): Promise<AnyProtocolEvent[]>;
}
3 changes: 3 additions & 0 deletions packages/indexer-client/src/internal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./exceptions/index.js";
export * from "./interfaces/index.js";
export * from "./providers/index.js";
Loading

0 comments on commit 41ff0c0

Please sign in to comment.