Skip to content

Commit 5bbd8f6

Browse files
ryo-maclaude
andcommitted
Consolidate 4 GraphQL queries into 1 and extend cache TTL to reduce Function Duration
Merged separate GitHub API queries (repository, activity, issue, pullRequest) into a single combined GraphQL query to reduce HTTP roundtrips from 4 to 1 per cache miss. Also extended file cache revalidation time from 1 hour to 6 hours to decrease function invocation frequency. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 9a3ae0f commit 5bbd8f6

6 files changed

Lines changed: 73 additions & 47 deletions

File tree

src/Repository/GithubRepository.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ServiceError } from "../Types/index.ts";
22
import {
33
GitHubUserActivity,
4+
GitHubUserAll,
45
GitHubUserIssue,
56
GitHubUserPullRequest,
67
GitHubUserRepository,
@@ -9,6 +10,9 @@ import {
910

1011
export abstract class GithubRepository {
1112
abstract requestUserInfo(username: string): Promise<UserInfo | ServiceError>;
13+
abstract requestUserAll(
14+
username: string,
15+
): Promise<GitHubUserAll | ServiceError>;
1216
abstract requestUserActivity(
1317
username: string,
1418
): Promise<GitHubUserActivity | ServiceError>;

src/Schemas/index.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,45 @@ export const queryUserRepository = `
6060
}
6161
}
6262
`;
63+
64+
export const queryUserAll = `
65+
query userInfo($username: String!) {
66+
user(login: $username) {
67+
createdAt
68+
contributionsCollection {
69+
totalCommitContributions
70+
restrictedContributionsCount
71+
totalPullRequestReviewContributions
72+
}
73+
organizations(first: 1) {
74+
totalCount
75+
}
76+
followers(first: 1) {
77+
totalCount
78+
}
79+
openIssues: issues(states: OPEN) {
80+
totalCount
81+
}
82+
closedIssues: issues(states: CLOSED) {
83+
totalCount
84+
}
85+
pullRequests(first: 1) {
86+
totalCount
87+
}
88+
repositories(first: 50, ownerAffiliations: OWNER, orderBy: {direction: DESC, field: STARGAZERS}) {
89+
totalCount
90+
nodes {
91+
languages(first: 3, orderBy: {direction:DESC, field: SIZE}) {
92+
nodes {
93+
name
94+
}
95+
}
96+
stargazers {
97+
totalCount
98+
}
99+
createdAt
100+
}
101+
}
102+
}
103+
}
104+
`;

src/Services/GithubApiService.ts

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import { GithubRepository } from "../Repository/GithubRepository.ts";
22
import {
33
GitHubUserActivity,
4+
GitHubUserAll,
45
GitHubUserIssue,
56
GitHubUserPullRequest,
67
GitHubUserRepository,
78
UserInfo,
89
} from "../user_info.ts";
910
import {
1011
queryUserActivity,
12+
queryUserAll,
1113
queryUserIssue,
1214
queryUserPullRequest,
1315
queryUserRepository,
@@ -25,6 +27,13 @@ export const TOKENS = [
2527
];
2628

2729
export class GithubApiService extends GithubRepository {
30+
async requestUserAll(
31+
username: string,
32+
): Promise<GitHubUserAll | ServiceError> {
33+
return await this.executeQuery<GitHubUserAll>(queryUserAll, {
34+
username,
35+
});
36+
}
2837
async requestUserRepository(
2938
username: string,
3039
): Promise<GitHubUserRepository | ServiceError> {
@@ -55,34 +64,13 @@ export class GithubApiService extends GithubRepository {
5564
);
5665
}
5766
async requestUserInfo(username: string): Promise<UserInfo | ServiceError> {
58-
// Avoid to call others if one of them is null
59-
60-
const promises = Promise.allSettled([
61-
this.requestUserRepository(username),
62-
this.requestUserActivity(username),
63-
this.requestUserIssue(username),
64-
this.requestUserPullRequest(username),
65-
]);
67+
// Use single combined query instead of 4 separate queries to reduce Function Duration
6668
try {
67-
const [repository, activity, issue, pullRequest] = await promises;
68-
const status = [
69-
repository.status,
70-
activity.status,
71-
issue.status,
72-
pullRequest.status,
73-
];
74-
75-
if (status.includes("rejected")) {
76-
Logger.error(`Can not find a user with username:' ${username}'`);
77-
return new ServiceError("Not found", EServiceKindError.NOT_FOUND);
69+
const result = await this.requestUserAll(username);
70+
if (result instanceof ServiceError) {
71+
return result;
7872
}
79-
80-
return new UserInfo(
81-
(activity as PromiseFulfilledResult<GitHubUserActivity>).value,
82-
(issue as PromiseFulfilledResult<GitHubUserIssue>).value,
83-
(pullRequest as PromiseFulfilledResult<GitHubUserPullRequest>).value,
84-
(repository as PromiseFulfilledResult<GitHubUserRepository>).value,
85-
);
73+
return UserInfo.fromCombined(result);
8674
} catch {
8775
Logger.error(`Error fetching user info for username: ${username}`);
8876
return new ServiceError("Not found", EServiceKindError.NOT_FOUND);

src/Services/__tests__/githubApiService.test.ts

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,26 +28,8 @@ stub(
2828
new Promise((resolve) => {
2929
resolve(successGithubResponseMock.default);
3030
}),
31-
// Should throw NOT FOUND (requestUserInfo makes 4 API calls: repository, activity, issue, pullRequest)
32-
// Each call makes 2 attempts (one per token), so 8 promises total
33-
new Promise((resolve) => {
34-
resolve(notFoundGithubResponseMock.default);
35-
}),
36-
new Promise((resolve) => {
37-
resolve(notFoundGithubResponseMock.default);
38-
}),
39-
new Promise((resolve) => {
40-
resolve(notFoundGithubResponseMock.default);
41-
}),
42-
new Promise((resolve) => {
43-
resolve(notFoundGithubResponseMock.default);
44-
}),
45-
new Promise((resolve) => {
46-
resolve(notFoundGithubResponseMock.default);
47-
}),
48-
new Promise((resolve) => {
49-
resolve(notFoundGithubResponseMock.default);
50-
}),
31+
// Should throw NOT FOUND (requestUserInfo makes 1 combined API call)
32+
// Each call makes 2 attempts (one per token), so 2 promises total
5133
new Promise((resolve) => {
5234
resolve(notFoundGithubResponseMock.default);
5335
}),

src/user_info.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ export type GitHubUserActivity = {
4141
totalCount: number;
4242
};
4343
};
44+
45+
export type GitHubUserAll = GitHubUserActivity &
46+
GitHubUserIssue &
47+
GitHubUserPullRequest &
48+
GitHubUserRepository;
4449
export class UserInfo {
4550
public readonly totalCommits: number;
4651
public readonly totalFollowers: number;
@@ -56,6 +61,11 @@ export class UserInfo {
5661
public readonly ancientAccount: number;
5762
public readonly joined2020: number;
5863
public readonly ogAccount: number;
64+
65+
static fromCombined(data: GitHubUserAll): UserInfo {
66+
return new UserInfo(data, data, data, data);
67+
}
68+
5969
constructor(
6070
userActivity: GitHubUserActivity,
6171
userIssue: GitHubUserIssue,

src/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export const CONSTANTS = {
6363
DEFAULT_NO_FRAME: false,
6464
DEFAULT_GITHUB_API: "https://api.github.com/graphql",
6565
DEFAULT_GITHUB_RETRY_DELAY: 500,
66-
REVALIDATE_TIME: HOUR_IN_MILLISECONDS,
66+
REVALIDATE_TIME: HOUR_IN_MILLISECONDS * 6,
6767
REDIS_TTL: HOUR_IN_MILLISECONDS * 4,
6868
};
6969

0 commit comments

Comments
 (0)