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: project & rounds related repositories w/kysely #7

Merged
merged 4 commits into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
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