Skip to content
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

feat: events fetcher & indexer client #4

Merged
merged 8 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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",
0xkenj1 marked this conversation as resolved.
Show resolved Hide resolved
"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) {}

async fetcEventsByBlockNumberAndLogIndex(
0xkenj1 marked this conversation as resolved.
Show resolved Hide resolved
chainId: number,
blockNumber: number,
logIndex: number,
limit: number = 100,
): Promise<AnyProtocolEvent[]> {
return await this.indexerClient.getEventsByBlockNumberAndLogIndex(
chainId,
blockNumber,
logIndex,
limit,
);
}
}
0xkenj1 marked this conversation as resolved.
Show resolved Hide resolved
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";
10 changes: 10 additions & 0 deletions packages/data-flow/src/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { AnyProtocolEvent } from "@grants-stack-indexer/shared";

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add natspec

export interface IEventsFetcher {
fetcEventsByBlockNumberAndLogIndex(
0xkenj1 marked this conversation as resolved.
Show resolved Hide resolved
chainId: number,
blockNumber: number,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should blockNumber be a BigInt?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I'm thinking if ProtocolEvent["block_number"] should also be bigint 🤔

Copy link
Collaborator Author

@0xkenj1 0xkenj1 Oct 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we need it actually. Number.MAX_SAFE_INTEGER = 9007199254740991 :rofl

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Viem treats it like bigint, we will have an inconsistency here. We need to define a common type at least to the internal limits of our backend, and only convert to number or bigint when interfacing with external (IndexerClient, Viem lib, etc)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Along the lines of Nigiri's comment, it's more of a semantic type thingy.

Whenever you get a block number out of viem or any other client, you as a dev will need to know (and be extremely sure) that you are working with a particular set of block numbers that can be safely casted from bigint to number. By using a bigint, you just forget about that problem in the future. You move from a "it will probably work" scenario to a "it works" scenario.

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";
79 changes: 79 additions & 0 deletions packages/data-flow/test/unit/eventsFetcher.spec.ts
Original file line number Diff line number Diff line change
@@ -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<IIndexerClient>;
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");
});
});
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",
0xkenj1 marked this conversation as resolved.
Show resolved Hide resolved
"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";
19 changes: 19 additions & 0 deletions packages/indexer-client/src/interfaces/indexerClient.ts
Original file line number Diff line number Diff line change
@@ -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
0xkenj1 marked this conversation as resolved.
Show resolved Hide resolved
*/
getEventsByBlockNumberAndLogIndex(
chainId: number,
fromBlock: number,
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