generated from defi-wonderland/ts-turborepo-boilerplate
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
21 changed files
with
839 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import { | ||
Changeset, | ||
IApplicationRepository, | ||
IProjectRepository, | ||
IRoundRepository, | ||
} from "@grants-stack-indexer/repository"; | ||
|
||
import { ExecutionResult, IDataLoader, InvalidChangeset } from "../internal.js"; | ||
import { | ||
createApplicationHandlers, | ||
createProjectHandlers, | ||
createRoundHandlers, | ||
} from "./handlers/index.js"; | ||
import { ChangesetHandlers } from "./types/index.js"; | ||
|
||
/** | ||
* DataLoader is responsible for applying changesets to the database. | ||
* It works by: | ||
* 1. Taking an array of changesets representing data modifications | ||
* 2. Validating that handlers exist for all changeset types | ||
* 3. Sequentially executing each changeset using the appropriate handler | ||
* 4. Tracking execution results including successes and failures | ||
* 5. Breaking execution if any changeset fails | ||
* | ||
* The handlers are initialized for different entity types (projects, rounds, applications) | ||
* and stored in a map for lookup during execution. | ||
*/ | ||
|
||
export class DataLoader implements IDataLoader { | ||
private readonly handlers: ChangesetHandlers; | ||
|
||
constructor( | ||
private readonly repositories: { | ||
project: IProjectRepository; | ||
round: IRoundRepository; | ||
application: IApplicationRepository; | ||
}, | ||
) { | ||
this.handlers = { | ||
...createProjectHandlers(repositories.project), | ||
...createRoundHandlers(repositories.round), | ||
...createApplicationHandlers(repositories.application), | ||
}; | ||
} | ||
|
||
/** @inheritdoc */ | ||
public async applyChanges(changesets: Changeset[]): Promise<ExecutionResult> { | ||
const result: ExecutionResult = { | ||
changesets: [], | ||
numExecuted: 0, | ||
numSuccessful: 0, | ||
numFailed: 0, | ||
errors: [], | ||
}; | ||
|
||
const invalidTypes = changesets.filter((changeset) => !this.handlers[changeset.type]); | ||
if (invalidTypes.length > 0) { | ||
throw new InvalidChangeset(invalidTypes.map((changeset) => changeset.type)); | ||
} | ||
|
||
//TODO: research how to manage transactions so we can rollback on error | ||
for (const changeset of changesets) { | ||
result.numExecuted++; | ||
try { | ||
await this.handlers[changeset.type](changeset as never); | ||
result.changesets.push(changeset.type); | ||
result.numSuccessful++; | ||
} catch (error) { | ||
result.numFailed++; | ||
result.errors.push( | ||
`Failed to apply changeset ${changeset.type}: ${ | ||
error instanceof Error ? error.message : String(error) | ||
}`, | ||
); | ||
console.error(error); | ||
break; | ||
} | ||
} | ||
|
||
return result; | ||
} | ||
} |
31 changes: 31 additions & 0 deletions
31
packages/data-flow/src/data-loader/handlers/application.handlers.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { IApplicationRepository } from "@grants-stack-indexer/repository"; | ||
|
||
import { ChangesetHandler } from "../types/index.js"; | ||
|
||
/** | ||
* Collection of handlers for application-related operations. | ||
* Each handler corresponds to a specific Application changeset type. | ||
*/ | ||
export type ApplicationHandlers = { | ||
InsertApplication: ChangesetHandler<"InsertApplication">; | ||
UpdateApplication: ChangesetHandler<"UpdateApplication">; | ||
}; | ||
|
||
/** | ||
* Creates handlers for managing application-related operations. | ||
* | ||
* @param repository - The application repository instance used for database operations | ||
* @returns An object containing all application-related handlers | ||
*/ | ||
export const createApplicationHandlers = ( | ||
repository: IApplicationRepository, | ||
): ApplicationHandlers => ({ | ||
InsertApplication: (async (changeset): Promise<void> => { | ||
await repository.insertApplication(changeset.args); | ||
}) satisfies ChangesetHandler<"InsertApplication">, | ||
|
||
UpdateApplication: (async (changeset): Promise<void> => { | ||
const { chainId, roundId, applicationId, application } = changeset.args; | ||
await repository.updateApplication({ chainId, roundId, id: applicationId }, application); | ||
}) satisfies ChangesetHandler<"UpdateApplication">, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export * from "./application.handlers.js"; | ||
export * from "./project.handlers.js"; | ||
export * from "./round.handlers.js"; |
60 changes: 60 additions & 0 deletions
60
packages/data-flow/src/data-loader/handlers/project.handlers.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import { IProjectRepository } from "@grants-stack-indexer/repository"; | ||
|
||
import { ChangesetHandler } from "../types/index.js"; | ||
|
||
/** | ||
* Collection of handlers for project-related operations. | ||
* Each handler corresponds to a specific Project changeset type. | ||
*/ | ||
export type ProjectHandlers = { | ||
InsertProject: ChangesetHandler<"InsertProject">; | ||
UpdateProject: ChangesetHandler<"UpdateProject">; | ||
InsertPendingProjectRole: ChangesetHandler<"InsertPendingProjectRole">; | ||
DeletePendingProjectRoles: ChangesetHandler<"DeletePendingProjectRoles">; | ||
InsertProjectRole: ChangesetHandler<"InsertProjectRole">; | ||
DeleteAllProjectRolesByRole: ChangesetHandler<"DeleteAllProjectRolesByRole">; | ||
DeleteAllProjectRolesByRoleAndAddress: ChangesetHandler<"DeleteAllProjectRolesByRoleAndAddress">; | ||
}; | ||
|
||
/** | ||
* Creates handlers for managing project-related operations. | ||
* | ||
* @param repository - The project repository instance used for database operations | ||
* @returns An object containing all project-related handlers | ||
*/ | ||
export const createProjectHandlers = (repository: IProjectRepository): ProjectHandlers => ({ | ||
InsertProject: (async (changeset): Promise<void> => { | ||
const { project } = changeset.args; | ||
await repository.insertProject(project); | ||
}) satisfies ChangesetHandler<"InsertProject">, | ||
|
||
UpdateProject: (async (changeset): Promise<void> => { | ||
const { chainId, projectId, project } = changeset.args; | ||
await repository.updateProject({ id: projectId, chainId }, project); | ||
}) satisfies ChangesetHandler<"UpdateProject">, | ||
|
||
InsertPendingProjectRole: (async (changeset): Promise<void> => { | ||
const { pendingProjectRole } = changeset.args; | ||
await repository.insertPendingProjectRole(pendingProjectRole); | ||
}) satisfies ChangesetHandler<"InsertPendingProjectRole">, | ||
|
||
DeletePendingProjectRoles: (async (changeset): Promise<void> => { | ||
const { ids } = changeset.args; | ||
await repository.deleteManyPendingProjectRoles(ids); | ||
}) satisfies ChangesetHandler<"DeletePendingProjectRoles">, | ||
|
||
InsertProjectRole: (async (changeset): Promise<void> => { | ||
const { projectRole } = changeset.args; | ||
await repository.insertProjectRole(projectRole); | ||
}) satisfies ChangesetHandler<"InsertProjectRole">, | ||
|
||
DeleteAllProjectRolesByRole: (async (changeset): Promise<void> => { | ||
const { chainId, projectId, role } = changeset.args.projectRole; | ||
await repository.deleteManyProjectRoles(chainId, projectId, role); | ||
}) satisfies ChangesetHandler<"DeleteAllProjectRolesByRole">, | ||
|
||
DeleteAllProjectRolesByRoleAndAddress: (async (changeset): Promise<void> => { | ||
const { chainId, projectId, role, address } = changeset.args.projectRole; | ||
await repository.deleteManyProjectRoles(chainId, projectId, role, address); | ||
}) satisfies ChangesetHandler<"DeleteAllProjectRolesByRoleAndAddress">, | ||
}); |
87 changes: 87 additions & 0 deletions
87
packages/data-flow/src/data-loader/handlers/round.handlers.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import { IRoundRepository } from "@grants-stack-indexer/repository"; | ||
|
||
import { ChangesetHandler } from "../types/index.js"; | ||
|
||
/** | ||
* Collection of handlers for round-related operations. | ||
* Each handler corresponds to a specific Round changeset type. | ||
*/ | ||
export type RoundHandlers = { | ||
InsertRound: ChangesetHandler<"InsertRound">; | ||
UpdateRound: ChangesetHandler<"UpdateRound">; | ||
UpdateRoundByStrategyAddress: ChangesetHandler<"UpdateRoundByStrategyAddress">; | ||
IncrementRoundFundedAmount: ChangesetHandler<"IncrementRoundFundedAmount">; | ||
IncrementRoundTotalDistributed: ChangesetHandler<"IncrementRoundTotalDistributed">; | ||
InsertPendingRoundRole: ChangesetHandler<"InsertPendingRoundRole">; | ||
DeletePendingRoundRoles: ChangesetHandler<"DeletePendingRoundRoles">; | ||
InsertRoundRole: ChangesetHandler<"InsertRoundRole">; | ||
DeleteAllRoundRolesByRoleAndAddress: ChangesetHandler<"DeleteAllRoundRolesByRoleAndAddress">; | ||
}; | ||
|
||
/** | ||
* Creates handlers for managing round-related operations. | ||
* | ||
* @param repository - The round repository instance used for database operations | ||
* @returns An object containing all round-related handlers | ||
*/ | ||
export const createRoundHandlers = (repository: IRoundRepository): RoundHandlers => ({ | ||
InsertRound: (async (changeset): Promise<void> => { | ||
const { round } = changeset.args; | ||
await repository.insertRound(round); | ||
}) satisfies ChangesetHandler<"InsertRound">, | ||
|
||
UpdateRound: (async (changeset): Promise<void> => { | ||
const { chainId, roundId, round } = changeset.args; | ||
await repository.updateRound({ id: roundId, chainId }, round); | ||
}) satisfies ChangesetHandler<"UpdateRound">, | ||
|
||
UpdateRoundByStrategyAddress: (async (changeset): Promise<void> => { | ||
const { chainId, strategyAddress, round } = changeset.args; | ||
if (round) { | ||
await repository.updateRound({ strategyAddress, chainId: chainId }, round); | ||
} | ||
}) satisfies ChangesetHandler<"UpdateRoundByStrategyAddress">, | ||
|
||
IncrementRoundFundedAmount: (async (changeset): Promise<void> => { | ||
const { chainId, roundId, fundedAmount, fundedAmountInUsd } = changeset.args; | ||
await repository.incrementRoundFunds( | ||
{ | ||
chainId, | ||
roundId, | ||
}, | ||
fundedAmount, | ||
fundedAmountInUsd, | ||
); | ||
}) satisfies ChangesetHandler<"IncrementRoundFundedAmount">, | ||
|
||
IncrementRoundTotalDistributed: (async (changeset): Promise<void> => { | ||
const { chainId, roundId, amount } = changeset.args; | ||
await repository.incrementRoundTotalDistributed( | ||
{ | ||
chainId, | ||
roundId, | ||
}, | ||
amount, | ||
); | ||
}) satisfies ChangesetHandler<"IncrementRoundTotalDistributed">, | ||
|
||
InsertPendingRoundRole: (async (changeset): Promise<void> => { | ||
const { pendingRoundRole } = changeset.args; | ||
await repository.insertPendingRoundRole(pendingRoundRole); | ||
}) satisfies ChangesetHandler<"InsertPendingRoundRole">, | ||
|
||
DeletePendingRoundRoles: (async (changeset): Promise<void> => { | ||
const { ids } = changeset.args; | ||
await repository.deleteManyPendingRoundRoles(ids); | ||
}) satisfies ChangesetHandler<"DeletePendingRoundRoles">, | ||
|
||
InsertRoundRole: (async (changeset): Promise<void> => { | ||
const { roundRole } = changeset.args; | ||
await repository.insertRoundRole(roundRole); | ||
}) satisfies ChangesetHandler<"InsertRoundRole">, | ||
|
||
DeleteAllRoundRolesByRoleAndAddress: (async (changeset): Promise<void> => { | ||
const { chainId, roundId, role, address } = changeset.args.roundRole; | ||
await repository.deleteManyRoundRolesByRoleAndAddress(chainId, roundId, role, address); | ||
}) satisfies ChangesetHandler<"DeleteAllRoundRolesByRoleAndAddress">, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from "./dataLoader.js"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { Changeset } from "@grants-stack-indexer/repository"; | ||
|
||
export type ChangesetHandler<T extends Changeset["type"]> = ( | ||
changeset: Extract<Changeset, { type: T }>, | ||
) => Promise<void>; | ||
|
||
export type ChangesetHandlers = { | ||
[K in Changeset["type"]]: ChangesetHandler<K>; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from "./invalidChangeset.exception.js"; |
5 changes: 5 additions & 0 deletions
5
packages/data-flow/src/exceptions/invalidChangeset.exception.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export class InvalidChangeset extends Error { | ||
constructor(invalidTypes: string[]) { | ||
super(`Invalid changeset types: ${invalidTypes.join(", ")}`); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,3 @@ | ||
export { EventsFetcher } from "./internal.js"; | ||
|
||
export { DataLoader } from "./internal.js"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import type { Changeset } from "@grants-stack-indexer/repository"; | ||
|
||
import type { ExecutionResult } from "../internal.js"; | ||
|
||
export interface IDataLoader { | ||
/** | ||
* Applies the changesets to the database. | ||
* @param changesets - The changesets to apply. | ||
* @returns The execution result. | ||
* @throws {InvalidChangeset} if there are changesets with invalid types. | ||
*/ | ||
applyChanges(changesets: Changeset[]): Promise<ExecutionResult>; | ||
} |
20 changes: 20 additions & 0 deletions
20
packages/data-flow/src/interfaces/eventsFetcher.interface.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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[]>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,2 @@ | ||
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[]>; | ||
} | ||
export * from "./eventsFetcher.interface.js"; | ||
export * from "./dataLoader.interface.js"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,5 @@ | ||
export * from "./types/index.js"; | ||
export * from "./interfaces/index.js"; | ||
export * from "./exceptions/index.js"; | ||
export * from "./data-loader/index.js"; | ||
export * from "./eventsFetcher.js"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { Changeset } from "@grants-stack-indexer/repository"; | ||
|
||
export type ExecutionResult = { | ||
changesets: Changeset["type"][]; | ||
numExecuted: number; | ||
numSuccessful: number; | ||
numFailed: number; | ||
errors: string[]; | ||
}; |
Oops, something went wrong.