Skip to content

Commit 04f82af

Browse files
committed
fix: pass staleCursor from feed service through to GraphQL PageInfo
Add staleCursor support to graphorm.nodesToConnection via an optional extraPageInfo parameter, threaded through queryPaginated. The feed resolver extracts staleCursor from the feed service response and passes it so it reaches the GraphQL PageInfo. Previously staleCursor was silently dropped because the feed resolver uses graphorm.queryPaginated (not connectionFromNodes), and nodesToConnection only included the 4 standard PageInfo fields.
1 parent f0fb0ef commit 04f82af

File tree

3 files changed

+72
-3
lines changed

3 files changed

+72
-3
lines changed

__tests__/integrations/feed.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
connectionFromNodes,
1111
feedCursorPageGenerator,
1212
} from '../../src/schema/common';
13+
import graphorm from '../../src/graphorm';
1314
import { MockContext, saveFixtures } from '../helpers';
1415
import { deleteKeysByPattern } from '../../src/redis';
1516
import createOrGetConnection from '../../src/db';
@@ -240,6 +241,60 @@ describe('connectionFromNodes with staleCursor', () => {
240241
});
241242
});
242243

244+
describe('nodesToConnection with staleCursor', () => {
245+
const nodeToCursor = (_node: { id: string }, index: number) =>
246+
`cursor-${index}`;
247+
248+
it('should include staleCursor in pageInfo when extraPageInfo is provided', () => {
249+
const nodes = [{ id: '1' }, { id: '2' }];
250+
const result = graphorm.nodesToConnection(
251+
nodes,
252+
nodes.length,
253+
() => false,
254+
() => true,
255+
nodeToCursor,
256+
{ staleCursor: true },
257+
);
258+
259+
expect(result.pageInfo).toMatchObject({
260+
staleCursor: true,
261+
hasNextPage: true,
262+
hasPreviousPage: false,
263+
});
264+
expect(result.edges).toHaveLength(2);
265+
});
266+
267+
it('should not include staleCursor when extraPageInfo is not provided', () => {
268+
const nodes = [{ id: '1' }, { id: '2' }];
269+
const result = graphorm.nodesToConnection(
270+
nodes,
271+
nodes.length,
272+
() => false,
273+
() => true,
274+
nodeToCursor,
275+
);
276+
277+
expect(result.pageInfo.staleCursor).toBeUndefined();
278+
});
279+
280+
it('should include staleCursor in pageInfo when nodes are empty', () => {
281+
const result = graphorm.nodesToConnection(
282+
[],
283+
0,
284+
() => false,
285+
() => false,
286+
nodeToCursor,
287+
{ staleCursor: true },
288+
);
289+
290+
expect(result.pageInfo).toMatchObject({
291+
staleCursor: true,
292+
hasNextPage: false,
293+
});
294+
expect(result.edges).toHaveLength(0);
295+
});
296+
});
297+
243298
describe('FeedPreferencesConfigGenerator', () => {
244299
beforeEach(async () => {
245300
await saveFixtures(con, Source, sourcesFixture);

src/common/feedGenerator.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { mapArrayToOjbect } from './object';
3131
import { runInSpan } from '../telemetry';
3232
import { whereVordrFilter } from './vordr';
3333
import { baseFeedConfig, type FeedFlagsFilters } from '../integrations/feed';
34+
import type { FeedResponse } from '../integrations/feed/types';
3435
import { ContentPreferenceSource } from '../entity/contentPreference/ContentPreferenceSource';
3536
import { ContentPreferenceKeyword } from '../entity/contentPreference/ContentPreferenceKeyword';
3637
import {
@@ -556,6 +557,8 @@ export function feedResolver<
556557
)
557558
: [];
558559

560+
const staleCursor = (queryParams as FeedResponse)?.staleCursor;
561+
559562
const result = await runInSpan('feedResolver.queryPaginated', async () =>
560563
graphorm.queryPaginated<GQLPost>(
561564
context,
@@ -597,8 +600,10 @@ export function feedResolver<
597600
(nodes) =>
598601
pageGenerator.transformNodes?.(page, nodes, queryParams) ?? nodes,
599602
true,
603+
staleCursor ? { staleCursor } : undefined,
600604
),
601605
);
606+
602607
// Sometimes the feed can have a bit less posts than requested due to recent ban or deletion
603608
if (
604609
warnOnPartialFirstPage &&

src/graphorm/graphorm.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,18 @@ import {
88
} from 'typeorm';
99
import { parseResolveInfo, ResolveTree } from 'graphql-parse-resolve-info';
1010
import { Context } from '../Context';
11-
import { Connection, Edge } from 'graphql-relay';
11+
import { Connection, Edge, PageInfo } from 'graphql-relay';
1212
import { EntityTarget } from 'typeorm/common/EntityTarget';
1313
import type { GraphqlPayload } from '../routes/public/graphqlExecutor';
1414

1515
export type QueryBuilder = SelectQueryBuilder<any>;
1616

1717
export type GraphORMBuilder = { queryBuilder: QueryBuilder; alias: string };
1818

19+
export type ExtraPageInfo = {
20+
staleCursor?: boolean;
21+
};
22+
1923
export interface GraphORMPagination {
2024
limit: number;
2125
hasPreviousPage: (nodeSize: number) => boolean;
@@ -584,6 +588,7 @@ export class GraphORM {
584588
hasPreviousPage: (nodeSize: number) => boolean,
585589
hasNextPage: (nodeSize: number) => boolean,
586590
nodeToCursor: (node: T, index: number) => string,
591+
extraPageInfo?: ExtraPageInfo,
587592
): Connection<T> {
588593
if (!nodes.length) {
589594
return {
@@ -592,7 +597,8 @@ export class GraphORM {
592597
endCursor: null,
593598
hasNextPage: hasNextPage(pretransformNodeSize),
594599
hasPreviousPage: hasPreviousPage(pretransformNodeSize),
595-
},
600+
...extraPageInfo,
601+
} as PageInfo,
596602
edges: [],
597603
};
598604
}
@@ -608,7 +614,8 @@ export class GraphORM {
608614
endCursor: edges[edges.length - 1].cursor,
609615
hasNextPage: hasNextPage(pretransformNodeSize),
610616
hasPreviousPage: hasPreviousPage(pretransformNodeSize),
611-
},
617+
...extraPageInfo,
618+
} as PageInfo,
612619
edges,
613620
};
614621
}
@@ -775,6 +782,7 @@ export class GraphORM {
775782
beforeQuery?: (builder: GraphORMBuilder) => GraphORMBuilder,
776783
transformNodes?: (nodes: T[]) => T[],
777784
readReplica?: boolean,
785+
extraPageInfo?: ExtraPageInfo,
778786
): Promise<Connection<T>> {
779787
const parsedInfo = parseResolveInfo(resolveInfo) as ResolveTree;
780788
if (parsedInfo) {
@@ -795,6 +803,7 @@ export class GraphORM {
795803
hasPreviousPage,
796804
hasNextPage,
797805
nodeToCursor,
806+
extraPageInfo,
798807
);
799808
}
800809
throw new Error('Resolve info is empty');

0 commit comments

Comments
 (0)