From 6e18b49cc535542448d3315419ce04f9e88f654a Mon Sep 17 00:00:00 2001 From: mandyslovestories-sudo Date: Sat, 27 Jun 2026 10:46:08 -0500 Subject: [PATCH 1/2] feat(indexer/distributions): define distributions GraphQL schema (#37) --- indexer/distributions/schema.graphql | 79 +++++++++++++++++++++++ indexer/distributions/src/schema.test.ts | 82 ++++++++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 indexer/distributions/src/schema.test.ts diff --git a/indexer/distributions/schema.graphql b/indexer/distributions/schema.graphql index 97ed648..9cd5c0e 100644 --- a/indexer/distributions/schema.graphql +++ b/indexer/distributions/schema.graphql @@ -1,7 +1,86 @@ +enum DistributionStatus { + ACTIVE + PAUSED + COMPLETED + CANCELLED +} + type DistributionBatch { id: ID! + contractId: String! + distributor: String! + token: String! + totalAmount: String! + claimedAmount: String! + recipientCount: Int! + status: DistributionStatus! + pausedAt: String + resumedAt: String + uniqueRef: String! + ledgerNumber: Int! + txHash: String! + claims: [ClaimAction!]! + createdAt: String! + updatedAt: String! } type ClaimAction { id: ID! + batchId: ID! + claimant: String! + amount: String! + txHash: String! + ledgerNumber: Int! + eventTimestamp: String! + createdAt: String! +} + +input DistributionFilterInput { + distributor: String + token: String + status: DistributionStatus + contractId: String +} + +input ClaimFilterInput { + batchId: ID + claimant: String +} + +input PaginationInput { + first: Int + after: String +} + +type DistributionConnection { + nodes: [DistributionBatch!]! + pageInfo: PageInfo! + totalCount: Int! +} + +type ClaimConnection { + nodes: [ClaimAction!]! + pageInfo: PageInfo! + totalCount: Int! +} + +type PageInfo { + hasNextPage: Boolean! + endCursor: String +} + +type Query { + distributionBatch(id: ID!): DistributionBatch + distributionBatches( + filter: DistributionFilterInput + pagination: PaginationInput + ): DistributionConnection! + distributionBatchesByDistributor( + distributor: String! + pagination: PaginationInput + ): DistributionConnection! + claimAction(id: ID!): ClaimAction + claims(filter: ClaimFilterInput, pagination: PaginationInput): ClaimConnection! + claimsByClaimant(claimant: String!, pagination: PaginationInput): ClaimConnection! + claimsByBatch(batchId: ID!, pagination: PaginationInput): ClaimConnection! } diff --git a/indexer/distributions/src/schema.test.ts b/indexer/distributions/src/schema.test.ts new file mode 100644 index 0000000..d5c4643 --- /dev/null +++ b/indexer/distributions/src/schema.test.ts @@ -0,0 +1,82 @@ +import { readFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, test } from "vitest"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const schemaPath = join(__dirname, "../schema.graphql"); + +describe("distributions GraphQL schema", () => { + const schemaContent = readFileSync(schemaPath, "utf-8"); + + test("defines DistributionStatus enum", () => { + expect(schemaContent).toContain("enum DistributionStatus {"); + expect(schemaContent).toContain("ACTIVE"); + expect(schemaContent).toContain("PAUSED"); + expect(schemaContent).toContain("COMPLETED"); + expect(schemaContent).toContain("CANCELLED"); + }); + + test("defines DistributionBatch type aligned with database schema", () => { + expect(schemaContent).toContain("type DistributionBatch {"); + expect(schemaContent).toContain("id: ID!"); + expect(schemaContent).toContain("contractId: String!"); + expect(schemaContent).toContain("distributor: String!"); + expect(schemaContent).toContain("token: String!"); + expect(schemaContent).toContain("totalAmount: String!"); + expect(schemaContent).toContain("claimedAmount: String!"); + expect(schemaContent).toContain("recipientCount: Int!"); + expect(schemaContent).toContain("status: DistributionStatus!"); + expect(schemaContent).toContain("pausedAt: String"); + expect(schemaContent).toContain("resumedAt: String"); + expect(schemaContent).toContain("uniqueRef: String!"); + expect(schemaContent).toContain("ledgerNumber: Int!"); + expect(schemaContent).toContain("txHash: String!"); + expect(schemaContent).toContain("claims: [ClaimAction!]!"); + }); + + test("defines ClaimAction type aligned with database schema", () => { + expect(schemaContent).toContain("type ClaimAction {"); + expect(schemaContent).toContain("id: ID!"); + expect(schemaContent).toContain("batchId: ID!"); + expect(schemaContent).toContain("claimant: String!"); + expect(schemaContent).toContain("amount: String!"); + expect(schemaContent).toContain("txHash: String!"); + expect(schemaContent).toContain("ledgerNumber: Int!"); + expect(schemaContent).toContain("eventTimestamp: String!"); + }); + + test("defines filter and pagination inputs", () => { + expect(schemaContent).toContain("input DistributionFilterInput {"); + expect(schemaContent).toContain("input ClaimFilterInput {"); + expect(schemaContent).toContain("input PaginationInput {"); + }); + + test("defines connection and page info types", () => { + expect(schemaContent).toContain("type DistributionConnection {"); + expect(schemaContent).toContain("type ClaimConnection {"); + expect(schemaContent).toContain("type PageInfo {"); + }); + + test("defines root Query queries", () => { + const normalized = schemaContent.replace(/\s+/g, " "); + expect(normalized).toContain("type Query {"); + expect(normalized).toContain("distributionBatch(id: ID!): DistributionBatch"); + expect(normalized).toContain( + "distributionBatches( filter: DistributionFilterInput pagination: PaginationInput ): DistributionConnection!", + ); + expect(normalized).toContain( + "distributionBatchesByDistributor( distributor: String! pagination: PaginationInput ): DistributionConnection!", + ); + expect(normalized).toContain("claimAction(id: ID!): ClaimAction"); + expect(normalized).toContain( + "claims(filter: ClaimFilterInput, pagination: PaginationInput): ClaimConnection!", + ); + expect(normalized).toContain( + "claimsByClaimant(claimant: String!, pagination: PaginationInput): ClaimConnection!", + ); + expect(normalized).toContain( + "claimsByBatch(batchId: ID!, pagination: PaginationInput): ClaimConnection!", + ); + }); +}); From a4af272317bcdd610dff8f6b4c31dbc7fd64d88d Mon Sep 17 00:00:00 2001 From: mandyslovestories-sudo Date: Sun, 28 Jun 2026 02:15:50 -0500 Subject: [PATCH 2/2] fix(indexer/distributions): paginate claims field and scope test checks --- indexer/distributions/schema.graphql | 2 +- indexer/distributions/src/schema.test.ts | 55 ++++++++++++++---------- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/indexer/distributions/schema.graphql b/indexer/distributions/schema.graphql index 9cd5c0e..576ad15 100644 --- a/indexer/distributions/schema.graphql +++ b/indexer/distributions/schema.graphql @@ -19,7 +19,7 @@ type DistributionBatch { uniqueRef: String! ledgerNumber: Int! txHash: String! - claims: [ClaimAction!]! + claims(pagination: PaginationInput): ClaimConnection! createdAt: String! updatedAt: String! } diff --git a/indexer/distributions/src/schema.test.ts b/indexer/distributions/src/schema.test.ts index d5c4643..dd91358 100644 --- a/indexer/distributions/src/schema.test.ts +++ b/indexer/distributions/src/schema.test.ts @@ -17,33 +17,42 @@ describe("distributions GraphQL schema", () => { expect(schemaContent).toContain("CANCELLED"); }); + const definitionBody = (kind: "type" | "input" | "enum", name: string) => { + const match = schemaContent.match(new RegExp(`${kind}\\s+${name}\\s*\\{([\\s\\S]*?)\\n\\}`)); + expect(match).not.toBeNull(); + return match?.[1] ?? ""; + }; + test("defines DistributionBatch type aligned with database schema", () => { - expect(schemaContent).toContain("type DistributionBatch {"); - expect(schemaContent).toContain("id: ID!"); - expect(schemaContent).toContain("contractId: String!"); - expect(schemaContent).toContain("distributor: String!"); - expect(schemaContent).toContain("token: String!"); - expect(schemaContent).toContain("totalAmount: String!"); - expect(schemaContent).toContain("claimedAmount: String!"); - expect(schemaContent).toContain("recipientCount: Int!"); - expect(schemaContent).toContain("status: DistributionStatus!"); - expect(schemaContent).toContain("pausedAt: String"); - expect(schemaContent).toContain("resumedAt: String"); - expect(schemaContent).toContain("uniqueRef: String!"); - expect(schemaContent).toContain("ledgerNumber: Int!"); - expect(schemaContent).toContain("txHash: String!"); - expect(schemaContent).toContain("claims: [ClaimAction!]!"); + const body = definitionBody("type", "DistributionBatch"); + expect(body).toContain("id: ID!"); + expect(body).toContain("contractId: String!"); + expect(body).toContain("distributor: String!"); + expect(body).toContain("token: String!"); + expect(body).toContain("totalAmount: String!"); + expect(body).toContain("claimedAmount: String!"); + expect(body).toContain("recipientCount: Int!"); + expect(body).toContain("status: DistributionStatus!"); + expect(body).toContain("pausedAt: String"); + expect(body).toContain("resumedAt: String"); + expect(body).toContain("uniqueRef: String!"); + expect(body).toContain("ledgerNumber: Int!"); + expect(body).toContain("txHash: String!"); + expect(body).toContain("claims(pagination: PaginationInput): ClaimConnection!"); + expect(body).toContain("createdAt: String!"); + expect(body).toContain("updatedAt: String!"); }); test("defines ClaimAction type aligned with database schema", () => { - expect(schemaContent).toContain("type ClaimAction {"); - expect(schemaContent).toContain("id: ID!"); - expect(schemaContent).toContain("batchId: ID!"); - expect(schemaContent).toContain("claimant: String!"); - expect(schemaContent).toContain("amount: String!"); - expect(schemaContent).toContain("txHash: String!"); - expect(schemaContent).toContain("ledgerNumber: Int!"); - expect(schemaContent).toContain("eventTimestamp: String!"); + const body = definitionBody("type", "ClaimAction"); + expect(body).toContain("id: ID!"); + expect(body).toContain("batchId: ID!"); + expect(body).toContain("claimant: String!"); + expect(body).toContain("amount: String!"); + expect(body).toContain("txHash: String!"); + expect(body).toContain("ledgerNumber: Int!"); + expect(body).toContain("eventTimestamp: String!"); + expect(body).toContain("createdAt: String!"); }); test("defines filter and pagination inputs", () => {