Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ function getOrderColumn(
* @param queryOrderBy - The order field for the current query (must match cursor.by)
* @param queryOrderDir - The order direction for the current query (must match cursor.dir)
* @param direction - "after" for forward pagination, "before" for backward
* @param effectiveDesc - Whether the effective sort direction is descending
* @throws if cursor.by does not match queryOrderBy
* @throws if cursor.dir does not match queryOrderDir
* @returns SQL expression for the cursor filter
Expand All @@ -41,7 +40,6 @@ export function cursorFilter(
queryOrderBy: typeof DomainsOrderBy.$inferType,
queryOrderDir: typeof OrderDirection.$inferType,
direction: "after" | "before",
effectiveDesc: boolean,
): SQL {
// Validate cursor was created with the same ordering as the current query
if (cursor.by !== queryOrderBy) {
Expand All @@ -63,7 +61,7 @@ export function cursorFilter(
// - "after" with DESC = less than cursor
// - "before" with ASC = less than cursor
// - "before" with DESC = greater than cursor
const useGreaterThan = (direction === "after") !== effectiveDesc;
const useGreaterThan = (direction === "after") !== (queryOrderDir === "DESC");

// Handle NULL cursor values explicitly (PostgreSQL tuple comparison with NULL yields NULL/unknown)
// With NULLS LAST ordering: non-NULL values come before NULL values
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { db } from "@/lib/db";
import { makeLogger } from "@/lib/logger";

import { DomainCursor } from "./domain-cursor";
import { cursorFilter, isEffectiveDesc, orderFindDomains } from "./find-domains-resolver-helpers";
import { cursorFilter, orderFindDomains } from "./find-domains-resolver-helpers";
import type { DomainOrderValue } from "./types";

/**
Expand Down Expand Up @@ -104,9 +104,6 @@ export function resolveFindDomains(
}),
},
async ({ before, after, limit, inverted }: ResolveCursorConnectionArgs) => {
// identify whether the effective sort direction is descending
const effectiveDesc = isEffectiveDesc(orderDir, inverted);

// build order clauses
const orderClauses = orderFindDomains(domains, orderBy, orderDir, inverted);

Expand All @@ -122,10 +119,10 @@ export function resolveFindDomains(
.where(
and(
beforeCursor
? cursorFilter(domains, beforeCursor, orderBy, orderDir, "before", effectiveDesc)
? cursorFilter(domains, beforeCursor, orderBy, orderDir, "before")
: undefined,
afterCursor
? cursorFilter(domains, afterCursor, orderBy, orderDir, "after", effectiveDesc)
? cursorFilter(domains, afterCursor, orderBy, orderDir, "after")
: undefined,
),
)
Expand Down
15 changes: 15 additions & 0 deletions apps/ensapi/src/graphql-api/schema/account.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@ import { describe, expect, it } from "vitest";

import type { Name } from "@ensnode/ensnode-sdk";

import {
AccountDomainsPaginated,
type PaginatedDomainResult,
} from "@/test/integration/domain-pagination-queries";
import { gql } from "@/test/integration/ensnode-graphql-api-client";
import {
flattenConnection,
type GraphQLConnection,
type PaginatedGraphQLConnection,
request,
} from "@/test/integration/graphql-utils";
import { testDomainPagination } from "@/test/integration/test-domain-pagination";

// via devnet
const DEFAULT_OWNER: Address = "0x70997970c51812dc3a010c7d01b50e0d17dc79c8";
Expand Down Expand Up @@ -70,3 +76,12 @@ describe("Account.domains", () => {
expect(names, "expected 'newowner.eth' in new owner's domains").toContain("newowner.eth");
});
});

describe("Account.domains pagination", () => {
testDomainPagination(async (variables) => {
const result = await request<{
account: { domains: PaginatedGraphQLConnection<PaginatedDomainResult> };
}>(AccountDomainsPaginated, { address: DEFAULT_OWNER, ...variables });
return result.account.domains;
});
});
15 changes: 15 additions & 0 deletions apps/ensapi/src/graphql-api/schema/domain.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@ import { describe, expect, it } from "vitest";
import type { InterpretedLabel, Name } from "@ensnode/ensnode-sdk";

import { DEVNET_ETH_LABELS } from "@/test/integration/devnet-names";
import {
DomainSubdomainsPaginated,
type PaginatedDomainResult,
} from "@/test/integration/domain-pagination-queries";
import { gql } from "@/test/integration/ensnode-graphql-api-client";
import {
flattenConnection,
type GraphQLConnection,
type PaginatedGraphQLConnection,
request,
} from "@/test/integration/graphql-utils";
import { testDomainPagination } from "@/test/integration/test-domain-pagination";

describe("Domain.subdomains", () => {
type SubdomainsResult = {
Expand Down Expand Up @@ -47,3 +53,12 @@ describe("Domain.subdomains", () => {
}
});
});

describe("Domain.subdomains pagination", () => {
testDomainPagination(async (variables) => {
const result = await request<{
domain: { subdomains: PaginatedGraphQLConnection<PaginatedDomainResult> };
}>(DomainSubdomainsPaginated, variables);
return result.domain.subdomains;
});
});
40 changes: 28 additions & 12 deletions apps/ensapi/src/graphql-api/schema/query.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,18 @@ import {
} from "@ensnode/ensnode-sdk";

import { DEVNET_NAMES } from "@/test/integration/devnet-names";
import {
type PaginatedDomainResult,
QueryDomainsPaginated,
} from "@/test/integration/domain-pagination-queries";
import { gql } from "@/test/integration/ensnode-graphql-api-client";
import { flattenConnection, request } from "@/test/integration/graphql-utils";
import {
flattenConnection,
type GraphQLConnection,
type PaginatedGraphQLConnection,
request,
} from "@/test/integration/graphql-utils";
import { testDomainPagination } from "@/test/integration/test-domain-pagination";

const namespace = "ens-test-env";

Expand All @@ -39,17 +49,13 @@ describe("Query.root", () => {

describe("Query.domains", () => {
type QueryDomainsResult = {
domains: {
edges: Array<{
node: {
__typename: "ENSv1Domain" | "ENSv2Domain";
id: DomainId;
name: Name;
label: { interpreted: InterpretedLabel };
owner: { address: Address };
};
}>;
};
domains: GraphQLConnection<{
__typename: "ENSv1Domain" | "ENSv2Domain";
id: DomainId;
name: Name;
label: { interpreted: InterpretedLabel };
owner: { address: Address };
}>;
};

const QueryDomains = gql`
Expand Down Expand Up @@ -126,3 +132,13 @@ describe("Query.domain", () => {
).resolves.toMatchObject({ domain: null });
});
});

describe("Query.domains pagination", () => {
testDomainPagination(async (variables) => {
const result = await request<{ domains: PaginatedGraphQLConnection<PaginatedDomainResult> }>(
QueryDomainsPaginated,
variables,
);
return result.domains;
});
});
15 changes: 15 additions & 0 deletions apps/ensapi/src/graphql-api/schema/registry.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@ import { DatasourceNames } from "@ensnode/datasources";
import { getDatasourceContract, type InterpretedLabel } from "@ensnode/ensnode-sdk";

import { DEVNET_ETH_LABELS } from "@/test/integration/devnet-names";
import {
type PaginatedDomainResult,
RegistryDomainsPaginated,
} from "@/test/integration/domain-pagination-queries";
import { gql } from "@/test/integration/ensnode-graphql-api-client";
import {
flattenConnection,
type GraphQLConnection,
type PaginatedGraphQLConnection,
request,
} from "@/test/integration/graphql-utils";
import { testDomainPagination } from "@/test/integration/test-domain-pagination";

const namespace = "ens-test-env";

Expand Down Expand Up @@ -51,3 +57,12 @@ describe("Registry.domains", () => {
}
});
});

describe("Registry.domains pagination", () => {
testDomainPagination(async (variables) => {
const result = await request<{
registry: { domains: PaginatedGraphQLConnection<PaginatedDomainResult> };
}>(RegistryDomainsPaginated, { contract: V2_ETH_REGISTRY, ...variables });
return result.registry.domains;
});
});
119 changes: 119 additions & 0 deletions apps/ensapi/src/test/integration/domain-pagination-queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import type { InterpretedLabel, Name } from "@ensnode/ensnode-sdk";

import { gql } from "@/test/integration/ensnode-graphql-api-client";

const PageInfoFragment = gql`
fragment PageInfoFragment on PageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
`;

const PaginatedDomainFragment = gql`
fragment PaginatedDomainFragment on Domain {
name
label { interpreted }
registration {
expiry
event { timestamp }
}
}
`;

export type PaginatedDomainResult = {
name: Name | null;
label: { interpreted: InterpretedLabel };
registration: {
expiry: string | null;
event: { timestamp: string };
} | null;
};

export const QueryDomainsPaginated = gql`
query QueryDomainsPaginated(
$order: DomainsOrderInput!
$first: Int
$after: String
$last: Int
$before: String
) {
domains(
where: { name: "e" }
order: $order
first: $first
after: $after
last: $last
before: $before
) {
edges { cursor node { ...PaginatedDomainFragment } }
pageInfo { ...PageInfoFragment }
}
}

${PageInfoFragment}
${PaginatedDomainFragment}
`;

export const DomainSubdomainsPaginated = gql`
query DomainSubdomainsPaginated(
$order: DomainsOrderInput!
$first: Int
$after: String
$last: Int
$before: String
) {
domain(by: { name: "eth" }) {
subdomains(order: $order, first: $first, after: $after, last: $last, before: $before) {
edges { cursor node { ...PaginatedDomainFragment } }
pageInfo { ...PageInfoFragment }
}
}
}

${PageInfoFragment}
${PaginatedDomainFragment}
`;

export const AccountDomainsPaginated = gql`
query AccountDomainsPaginated(
$address: Address!
$order: DomainsOrderInput!
$first: Int
$after: String
$last: Int
$before: String
) {
account(address: $address) {
domains(order: $order, first: $first, after: $after, last: $last, before: $before) {
edges { cursor node { ...PaginatedDomainFragment } }
pageInfo { ...PageInfoFragment }
}
}
}

${PageInfoFragment}
${PaginatedDomainFragment}
`;

export const RegistryDomainsPaginated = gql`
query RegistryDomainsPaginated(
$contract: AccountIdInput!
$order: DomainsOrderInput!
$first: Int
$after: String
$last: Int
$before: String
) {
registry(by: { contract: $contract }) {
domains(order: $order, first: $first, after: $after, last: $last, before: $before) {
edges { cursor node { ...PaginatedDomainFragment } }
pageInfo { ...PageInfoFragment }
}
}
}

${PageInfoFragment}
${PaginatedDomainFragment}
`;
14 changes: 13 additions & 1 deletion apps/ensapi/src/test/integration/graphql-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,19 @@ export type GraphQLConnection<NODE> = {
edges: { node: NODE }[];
};

export function flattenConnection<T>(connection?: GraphQLConnection<T>): T[] {
export type PaginatedGraphQLConnection<NODE> = {
edges: { cursor: string; node: NODE }[];
pageInfo: {
hasNextPage: boolean;
hasPreviousPage: boolean;
startCursor: string | null;
endCursor: string | null;
};
};

export function flattenConnection<T>(
connection?: GraphQLConnection<T> | PaginatedGraphQLConnection<T>,
): T[] {
return (connection?.edges ?? []).map((edge) => edge.node);
}

Expand Down
Loading
Loading