Skip to content

Commit

Permalink
feat: project & rounds related repositories w/kysely (#7)
Browse files Browse the repository at this point in the history
# 🤖 Linear

Closes GIT-90 GIT-91

## Description

- `repositories` package
- Project and Rounds repositories using Kysely Query Builder

## Checklist before requesting a review

- [x] I have conducted a self-review of my code.
- [x] I have conducted a QA.
- [ ] If it is a core feature, I have included comprehensive tests.
  • Loading branch information
0xnigir1 authored Oct 11, 2024
1 parent 41ff0c0 commit de296b6
Show file tree
Hide file tree
Showing 26 changed files with 1,258 additions and 5 deletions.
2 changes: 1 addition & 1 deletion packages/metadata/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"test:cov": "vitest run --config vitest.config.ts --coverage"
},
"dependencies": {
"@grants-stack-indexer/shared": "workspace:0.0.1",
"@grants-stack-indexer/shared": "workspace:*",
"axios": "1.7.7",
"zod": "3.23.8"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/pricing/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"test:cov": "vitest run --config vitest.config.ts --coverage"
},
"dependencies": {
"@grants-stack-indexer/shared": "workspace:0.0.1",
"@grants-stack-indexer/shared": "workspace:*",
"axios": "1.7.7"
},
"devDependencies": {
Expand Down
59 changes: 59 additions & 0 deletions packages/repository/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# grants-stack-indexer: repository package

This package provides a data access layer for the grants-stack-indexer project, implementing the Repository pattern to abstract database operations.

## Setup

1. Install dependencies by running `pnpm install` in the root directory of the project.

## Available Scripts

Available scripts that can be run using `pnpm`:

| Script | Description |
| ------------- | ------------------------------------------------------- |
| `build` | Build library using tsc |
| `check-types` | Check for type 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

This package provides repository interfaces and implementations for projects and rounds. It uses Kysely as the query builder library.

### Creating a database connection

```typescript
import { createKyselyPostgresDb, DatabaseConfig } from "@grants-stack-indexer/repository";

const dbConfig: DatabaseConfig = {
connectionString: "postgresql://user:password@localhost:5432/mydb",
};

const db = createKyselyPostgresDb(dbConfig);

// Instantiate a repository
const projectRepository = new KyselyProjectRepository(db, "mySchema");

const projects = await projectRepository.getProjects(10);
```

## API

### Repositories

This package provides the following repositories:

1. **IProjectRepository**: Manages project-related database operations, including project roles and pending roles.

2. **IRoundRepository**: Manages round-related database operations, including round roles and pending roles.

## References

- [Kysely](https://kysely.dev/)
- [PostgreSQL](https://www.postgresql.org/)
38 changes: 38 additions & 0 deletions packages/repository/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "@grants-stack-indexer/repository",
"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:*",
"kysely": "0.27.4",
"pg": "8.13.0"
},
"devDependencies": {
"@types/pg": "8.11.10"
}
}
52 changes: 52 additions & 0 deletions packages/repository/src/db/connection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { CamelCasePlugin, Kysely, PostgresDialect } from "kysely";
import { Pool, PoolConfig } from "pg";

import {
PendingProjectRole as PendingProjectRoleTable,
PendingRoundRole as PendingRoundRoleTable,
ProjectRole as ProjectRoleTable,
Project as ProjectTable,
RoundRole as RoundRoleTable,
Round as RoundTable,
} from "../internal.js";

export interface DatabaseConfig extends PoolConfig {
connectionString: string;
}

export interface Database {
rounds: RoundTable;
pendingRoundRoles: PendingRoundRoleTable;
roundRoles: RoundRoleTable;
projects: ProjectTable;
pendingProjectRoles: PendingProjectRoleTable;
projectRoles: ProjectRoleTable;
}

/**
* Creates and configures a Kysely database instance for PostgreSQL.
*
* @param config - The database configuration object extending PoolConfig.
* @returns A configured Kysely instance for the Database.
*
* This function sets up a PostgreSQL database connection using Kysely ORM.
*
* @example
* const dbConfig: DatabaseConfig = {
* connectionString: 'postgresql://user:password@localhost:5432/mydb'
* };
* const db = createKyselyDatabase(dbConfig);
*/
export const createKyselyPostgresDb = (config: DatabaseConfig): Kysely<Database> => {
const dialect = new PostgresDialect({
pool: new Pool({
max: 15,
idleTimeoutMillis: 30_000,
keepAlive: true,
connectionTimeoutMillis: 5_000,
...config,
}),
});

return new Kysely<Database>({ dialect, plugins: [new CamelCasePlugin()] });
};
30 changes: 30 additions & 0 deletions packages/repository/src/external.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Add your external exports here
export type {
IRoundRepository,
IRoundReadRepository,
IProjectRepository,
IProjectReadRepository,
DatabaseConfig,
} from "./internal.js";

export type {
Project,
ProjectType,
ProjectRoleNames,
NewProject,
PartialProject,
ProjectRole,
PendingProjectRole,
} from "./types/project.types.js";

export type {
Round,
NewRound,
PartialRound,
RoundRole,
PendingRoundRole,
} from "./types/round.types.js";

export { KyselyRoundRepository, KyselyProjectRepository } from "./repositories/kysely/index.js";

export { createKyselyPostgresDb as createKyselyDatabase } from "./internal.js";
3 changes: 3 additions & 0 deletions packages/repository/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./db/connection.js";
export * from "./repositories/kysely/index.js";
export * from "./interfaces/index.js";
2 changes: 2 additions & 0 deletions packages/repository/src/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./projectRepository.interface.js";
export * from "./roundRepository.interface.js";
103 changes: 103 additions & 0 deletions packages/repository/src/interfaces/projectRepository.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { Address, ChainId } from "@grants-stack-indexer/shared";

import {
NewPendingProjectRole,
NewProject,
NewProjectRole,
PartialProject,
PendingProjectRole,
Project,
ProjectRoleNames,
} from "../types/project.types.js";

export interface IProjectReadRepository {
/**
* Retrieves all projects for a given chain ID.
* @param chainId The chain ID to filter projects by.
* @returns A promise that resolves to an array of Project objects.
*/
getProjects(chainId: ChainId): Promise<Project[]>;

/**
* Retrieves a specific project by its ID and chain ID.
* @param chainId The chain ID of the project.
* @param projectId The unique identifier of the project.
* @returns A promise that resolves to a Project object if found, or undefined if not found.
*/
getProjectById(chainId: ChainId, projectId: string): Promise<Project | undefined>;

/**
* Retrieves all pending project roles.
* @returns A promise that resolves to an array of PendingProjectRole objects.
*/
getPendingProjectRoles(): Promise<PendingProjectRole[]>;

/**
* Retrieves pending project roles for a specific chain ID and role.
* @param chainId The chain ID to filter pending roles by.
* @param role The role to filter pending roles by.
* @returns A promise that resolves to an array of PendingProjectRole objects.
*/
getPendingProjectRolesByRole(chainId: ChainId, role: string): Promise<PendingProjectRole[]>;

/**
* Retrieves a project by its anchor address and chain ID.
* @param chainId The chain ID of the project.
* @param anchorAddress The anchor address of the project.
* @returns A promise that resolves to a Project object if found, or undefined if not found.
*/
getProjectByAnchor(chainId: ChainId, anchorAddress: Address): Promise<Project | undefined>;
}

export interface IProjectRepository extends IProjectReadRepository {
/**
* Inserts a new project into the repository.
* @param project The new project to be inserted.
* @returns A promise that resolves when the insertion is complete.
*/
insertProject(project: NewProject): Promise<void>;

/**
* Updates an existing project in the repository.
* @param where An object containing the id and chainId to identify the project to update.
* @param project The partial project data to update.
* @returns A promise that resolves when the update is complete.
*/
updateProject(where: { id: string; chainId: ChainId }, project: PartialProject): Promise<void>;

/**
* Inserts a new project role into the repository.
* @param projectRole The new project role to be inserted.
* @returns A promise that resolves when the insertion is complete.
*/
insertProjectRole(projectRole: NewProjectRole): Promise<void>;

/**
* Deletes multiple project roles based on the provided criteria.
* @param chainId The chain ID of the project roles to delete.
* @param projectId The project ID of the roles to delete.
* @param role The role type to delete.
* @param address Optional address to further filter the roles to delete.
* @returns A promise that resolves when the deletion is complete.
*/
deleteManyProjectRoles(
chainId: ChainId,
projectId: string,
role: ProjectRoleNames,
address?: Address,
): Promise<void>;

/**
* Inserts a new pending project role into the repository.
* @param pendingProjectRole The new pending project role to be inserted.
* @returns A promise that resolves when the insertion is complete.
*/
insertPendingProjectRole(pendingProjectRole: NewPendingProjectRole): Promise<void>;

/**
* Deletes multiple pending project roles based on their IDs.
* @param ids An array of IDs of the pending project roles to delete.
* @returns A promise that resolves when the deletion is complete.
*/
deleteManyPendingProjectRoles(ids: number[]): Promise<void>;
}
Loading

0 comments on commit de296b6

Please sign in to comment.