@kud/gh 0.6.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/inbox.d.ts +66 -5
- package/dist/inbox.d.ts.map +1 -1
- package/dist/inbox.js +157 -56
- package/dist/inbox.js.map +1 -1
- package/package.json +1 -1
package/dist/inbox.d.ts
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
* title, url, draft, timestamp, repo.
|
|
7
7
|
*
|
|
8
8
|
* This is a cost axis, not a taste one. Measured against GitHub's own
|
|
9
|
-
* `rateLimit { cost }`: the full query costs **
|
|
10
|
-
* budget, at a nodeCount of ~
|
|
9
|
+
* `rateLimit { cost }`: the full query costs **73 points** of a 5000/hour
|
|
10
|
+
* budget, at a nodeCount of ~16,870 — because `statusCheckRollup.contexts`
|
|
11
11
|
* appears in five PR fragments and `reviewThreads(first: 50)` multiplies
|
|
12
12
|
* beneath each one. A caller that renders a title and a link was paying for
|
|
13
|
-
* every check run on every open PR.
|
|
13
|
+
* every check run on every open PR.
|
|
14
14
|
*/
|
|
15
15
|
/**
|
|
16
16
|
* How many of your own open PRs to ask for.
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
* (`reviewThreads(first: 50)`, `statusCheckRollup.contexts(first: 20)`,
|
|
21
21
|
* `labels(first: 10)`), so the outer number is a multiplier on all of them.
|
|
22
22
|
* GitHub scores a call by the nodes it could return, not by how many calls you
|
|
23
|
-
* make — which is why
|
|
23
|
+
* make — which is why splitting the query across requests costs the same, and
|
|
24
|
+
* this saves a lot.
|
|
24
25
|
*
|
|
25
26
|
* It was 100, against ~25,550 total nodes and a measured 111 points. 100 × 80 is
|
|
26
27
|
* 8,000 of those nodes from this one search, and it was 100 for no reason beyond
|
|
@@ -34,6 +35,10 @@
|
|
|
34
35
|
*/
|
|
35
36
|
export declare const MY_PRS_LIMIT = 30;
|
|
36
37
|
export type InboxShape = "full" | "minimal";
|
|
38
|
+
/** One search in the inbox, addressable by the alias it answers under. */
|
|
39
|
+
export type InboxSource = "myPRs" | "reviewRequests" | "reviewed" | "assigned" | "repoIssues" | "authoredIssues" | "repoPRs" | "recentlyDone";
|
|
40
|
+
/** Every source, in the order a whole-inbox query asks for them. */
|
|
41
|
+
export declare const INBOX_SOURCES: readonly InboxSource[];
|
|
37
42
|
/** A repo-scoped or account-wide inbox. */
|
|
38
43
|
export type InboxQueryOptions = {
|
|
39
44
|
/** `owner/name`. Scopes every search to one repository. */
|
|
@@ -47,6 +52,15 @@ export type InboxQueryOptions = {
|
|
|
47
52
|
* falls through to a wrong answer rather than an absent one.
|
|
48
53
|
*/
|
|
49
54
|
shape?: InboxShape;
|
|
55
|
+
/**
|
|
56
|
+
* Which sources to ask for. Defaults to all of them.
|
|
57
|
+
*
|
|
58
|
+
* Every query is still a complete, valid document — `rateLimit` and `viewer`
|
|
59
|
+
* ride along with any subset — so a caller can split the inbox across
|
|
60
|
+
* requests without the parts becoming fragments that only mean something
|
|
61
|
+
* reassembled.
|
|
62
|
+
*/
|
|
63
|
+
sources?: readonly InboxSource[];
|
|
50
64
|
};
|
|
51
65
|
/**
|
|
52
66
|
* Build the inbox query. Without `repo`, it is account-wide.
|
|
@@ -57,5 +71,52 @@ export type InboxQueryOptions = {
|
|
|
57
71
|
* render empty and read as "nothing to do here". So `repo:` REPLACES
|
|
58
72
|
* `user:@me` rather than joining it.
|
|
59
73
|
*/
|
|
60
|
-
export declare const buildInboxQuery: (
|
|
74
|
+
export declare const buildInboxQuery: (options?: InboxQueryOptions) => string;
|
|
75
|
+
/**
|
|
76
|
+
* How many sources to put in one request.
|
|
77
|
+
*
|
|
78
|
+
* GitHub's proxy answers a request, not a query — so the ceiling here is WALL
|
|
79
|
+
* CLOCK, not cost. Measured on 2026-08-28 against an account-wide inbox: the
|
|
80
|
+
* eight-source query returns HTTP 502 on roughly two runs in three, after
|
|
81
|
+
* 10–30s; the same eight sources asked one at a time return 200 every time, for
|
|
82
|
+
* the same 73 points and the same ~16,870 nodes. Adding sources one at a time
|
|
83
|
+
* shows a clean gradient — five is reliable, six starts failing, eight mostly
|
|
84
|
+
* fails.
|
|
85
|
+
*
|
|
86
|
+
* The cost is identical either way, which is the whole reason this is safe:
|
|
87
|
+
* GraphQL scores the nodes a query COULD return, so splitting one document into
|
|
88
|
+
* four buys reliability at no extra budget. It is also why the same query is
|
|
89
|
+
* fine repo-scoped — `repo:` narrows what the search index has to walk, while
|
|
90
|
+
* `author:@me` across an account does not.
|
|
91
|
+
*
|
|
92
|
+
* Two rather than five, because the gradient is a probability and not a cliff:
|
|
93
|
+
* the point is headroom, and four requests in parallel cost the same wall clock
|
|
94
|
+
* as one.
|
|
95
|
+
*/
|
|
96
|
+
export declare const INBOX_SOURCES_PER_QUERY = 2;
|
|
97
|
+
/**
|
|
98
|
+
* Build the inbox as several independent queries, to be issued in parallel and
|
|
99
|
+
* merged with `mergeInboxData`.
|
|
100
|
+
*
|
|
101
|
+
* Prefer this to `buildInboxQuery` for an account-wide inbox. Each query is a
|
|
102
|
+
* complete document carrying its own `rateLimit` and `viewer`, so a partial
|
|
103
|
+
* failure is one source missing rather than a malformed whole.
|
|
104
|
+
*/
|
|
105
|
+
export declare const buildInboxQueries: (options?: InboxQueryOptions & {
|
|
106
|
+
sourcesPerQuery?: number;
|
|
107
|
+
}) => string[];
|
|
108
|
+
/**
|
|
109
|
+
* Reassemble what `buildInboxQueries` split.
|
|
110
|
+
*
|
|
111
|
+
* The source aliases are disjoint, so they merge by assignment. `rateLimit` and
|
|
112
|
+
* `viewer` are not: every part carries its own.
|
|
113
|
+
*
|
|
114
|
+
* `cost` and `nodeCount` SUM, because each part really was charged separately
|
|
115
|
+
* and a host reporting one part's cost as the inbox's would understate it by
|
|
116
|
+
* four. `remaining` takes the LOWEST reading — the parts run concurrently and
|
|
117
|
+
* the budget only falls, so the smallest is the closest to now. `resetAt` rides
|
|
118
|
+
* along with it, since a window and its expiry have to describe the same
|
|
119
|
+
* moment.
|
|
120
|
+
*/
|
|
121
|
+
export declare const mergeInboxData: (parts: any[]) => any;
|
|
61
122
|
//# sourceMappingURL=inbox.d.ts.map
|
package/dist/inbox.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inbox.d.ts","sourceRoot":"","sources":["../src/inbox.ts"],"names":[],"mappings":"AAQA;;;;;;;;;;;;;GAaG;AACH
|
|
1
|
+
{"version":3,"file":"inbox.d.ts","sourceRoot":"","sources":["../src/inbox.ts"],"names":[],"mappings":"AAQA;;;;;;;;;;;;;GAaG;AACH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,YAAY,KAAK,CAAA;AAE9B,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,SAAS,CAAA;AAE3C,0EAA0E;AAC1E,MAAM,MAAM,WAAW,GACnB,OAAO,GACP,gBAAgB,GAChB,UAAU,GACV,UAAU,GACV,YAAY,GACZ,gBAAgB,GAChB,SAAS,GACT,cAAc,CAAA;AAElB,oEAAoE;AACpE,eAAO,MAAM,aAAa,EAAE,SAAS,WAAW,EAS/C,CAAA;AAED,2CAA2C;AAC3C,MAAM,MAAM,iBAAiB,GAAG;IAC9B,2DAA2D;IAC3D,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,8DAA8D;IAC9D,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,SAAS,WAAW,EAAE,CAAA;CACjC,CAAA;AA8MD;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAAI,UAAS,iBAAsB,WAqB9D,CAAA;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,uBAAuB,IAAI,CAAA;AAExC;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,GAC5B,UAAS,iBAAiB,GAAG;IAAE,eAAe,CAAC,EAAE,MAAM,CAAA;CAAO,KAC7D,MAAM,EAYR,CAAA;AASD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,cAAc,GAAI,OAAO,GAAG,EAAE,KAAG,GA2B7C,CAAA"}
|
package/dist/inbox.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// The inbox query —
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// The inbox query — "what is on my plate": my PRs, review requests, reviews I
|
|
2
|
+
// have given, assigned work, issues and PRs on my repos, and what I closed
|
|
3
|
+
// recently.
|
|
4
4
|
//
|
|
5
5
|
// Query-only by design. Assembling the result into sections is the surface's
|
|
6
6
|
// own vocabulary — cockpit splits home from work, a web dashboard may group by
|
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
* title, url, draft, timestamp, repo.
|
|
14
14
|
*
|
|
15
15
|
* This is a cost axis, not a taste one. Measured against GitHub's own
|
|
16
|
-
* `rateLimit { cost }`: the full query costs **
|
|
17
|
-
* budget, at a nodeCount of ~
|
|
16
|
+
* `rateLimit { cost }`: the full query costs **73 points** of a 5000/hour
|
|
17
|
+
* budget, at a nodeCount of ~16,870 — because `statusCheckRollup.contexts`
|
|
18
18
|
* appears in five PR fragments and `reviewThreads(first: 50)` multiplies
|
|
19
19
|
* beneath each one. A caller that renders a title and a link was paying for
|
|
20
|
-
* every check run on every open PR.
|
|
20
|
+
* every check run on every open PR.
|
|
21
21
|
*/
|
|
22
22
|
/**
|
|
23
23
|
* How many of your own open PRs to ask for.
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
* (`reviewThreads(first: 50)`, `statusCheckRollup.contexts(first: 20)`,
|
|
28
28
|
* `labels(first: 10)`), so the outer number is a multiplier on all of them.
|
|
29
29
|
* GitHub scores a call by the nodes it could return, not by how many calls you
|
|
30
|
-
* make — which is why
|
|
30
|
+
* make — which is why splitting the query across requests costs the same, and
|
|
31
|
+
* this saves a lot.
|
|
31
32
|
*
|
|
32
33
|
* It was 100, against ~25,550 total nodes and a measured 111 points. 100 × 80 is
|
|
33
34
|
* 8,000 of those nodes from this one search, and it was 100 for no reason beyond
|
|
@@ -40,6 +41,17 @@
|
|
|
40
41
|
* not showing.
|
|
41
42
|
*/
|
|
42
43
|
export const MY_PRS_LIMIT = 30;
|
|
44
|
+
/** Every source, in the order a whole-inbox query asks for them. */
|
|
45
|
+
export const INBOX_SOURCES = [
|
|
46
|
+
"myPRs",
|
|
47
|
+
"reviewRequests",
|
|
48
|
+
"reviewed",
|
|
49
|
+
"assigned",
|
|
50
|
+
"repoIssues",
|
|
51
|
+
"authoredIssues",
|
|
52
|
+
"repoPRs",
|
|
53
|
+
"recentlyDone",
|
|
54
|
+
];
|
|
43
55
|
// computeHealth's precedence needs reviewDecision, mergeable and the check
|
|
44
56
|
// rollup. A fragment that omits them does NOT degrade gracefully — it falls
|
|
45
57
|
// straight through to whatever token is left, so a PR you already sent back
|
|
@@ -93,41 +105,14 @@ const ISSUE_LABELS = `
|
|
|
93
105
|
* the server booted and quietly stop reporting anything closed since.
|
|
94
106
|
*/
|
|
95
107
|
const sinceDay = (days) => new Date(Date.now() - days * 86400 * 1000).toISOString().slice(0, 10);
|
|
96
|
-
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
* nothing, and a scoped inbox on a repo you contribute to but do not own would
|
|
102
|
-
* render empty and read as "nothing to do here". So `repo:` REPLACES
|
|
103
|
-
* `user:@me` rather than joining it.
|
|
108
|
+
/*
|
|
109
|
+
* One entry per source, keyed by the alias it answers under, so a caller can
|
|
110
|
+
* ask for any subset without this file growing a second copy of the query text.
|
|
111
|
+
* Each value is the whole `alias: search(…) { … }` selection, indented as it
|
|
112
|
+
* appears in the document.
|
|
104
113
|
*/
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const owned = repo ? "" : "user:@me ";
|
|
108
|
-
const doneSince = sinceDay(doneWithinDays);
|
|
109
|
-
/* Interpolated as empty strings rather than branching the query text, so the
|
|
110
|
-
two shapes cannot drift into two separately-maintained queries. */
|
|
111
|
-
const full = shape === "full";
|
|
112
|
-
const health = full ? PR_HEALTH : "";
|
|
113
|
-
const conversation = full ? PR_CONVERSATION : "";
|
|
114
|
-
const issueConversation = full ? ISSUE_CONVERSATION : "";
|
|
115
|
-
const issueLabels = full ? ISSUE_LABELS : "";
|
|
116
|
-
/*
|
|
117
|
-
* `rateLimit` is free — it does not count against itself — and it is the only
|
|
118
|
-
* authoritative source for what this query costs. Every estimate made about
|
|
119
|
-
* that on 2026-08-14 was wrong, one of them by 25x, because GraphQL cost is
|
|
120
|
-
* node-count based and nested connections multiply: `reviewThreads(first: 50)`
|
|
121
|
-
* beneath `search(first: 100)` is 5,000 nodes from two lines of query text. A
|
|
122
|
-
* package handing out a 111-point query should hand out the means to see it.
|
|
123
|
-
*
|
|
124
|
-
* (GraphQL has no `/* *\/` comments, only `#`, so this note lives out here.)
|
|
125
|
-
*/
|
|
126
|
-
return `
|
|
127
|
-
{
|
|
128
|
-
rateLimit { cost nodeCount remaining resetAt }
|
|
129
|
-
viewer { login }
|
|
130
|
-
myPRs: search(query: "${scope}is:pr is:open author:@me", type: ISSUE, first: ${MY_PRS_LIMIT}) {
|
|
114
|
+
const SOURCES = {
|
|
115
|
+
myPRs: ({ scope, health, conversation }) => ` myPRs: search(query: "${scope}is:pr is:open author:@me", type: ISSUE, first: ${MY_PRS_LIMIT}) {
|
|
131
116
|
issueCount
|
|
132
117
|
nodes { __typename ... on PullRequest {
|
|
133
118
|
number title createdAt url headRefName isDraft
|
|
@@ -136,8 +121,8 @@ export const buildInboxQuery = ({ repo, doneWithinDays = 14, shape = "full", } =
|
|
|
136
121
|
${health}
|
|
137
122
|
${conversation}
|
|
138
123
|
}}
|
|
139
|
-
}
|
|
140
|
-
reviewRequests: search(query: "${scope}is:pr is:open review-requested:@me", type: ISSUE, first: 20) {
|
|
124
|
+
}`,
|
|
125
|
+
reviewRequests: ({ scope, health, conversation }) => ` reviewRequests: search(query: "${scope}is:pr is:open review-requested:@me", type: ISSUE, first: 20) {
|
|
141
126
|
nodes { __typename ... on PullRequest {
|
|
142
127
|
number title createdAt url headRefName isDraft
|
|
143
128
|
repository { nameWithOwner }
|
|
@@ -145,8 +130,8 @@ export const buildInboxQuery = ({ repo, doneWithinDays = 14, shape = "full", } =
|
|
|
145
130
|
${health}
|
|
146
131
|
${conversation}
|
|
147
132
|
}}
|
|
148
|
-
}
|
|
149
|
-
reviewed: search(query: "${scope}is:pr is:open reviewed-by:@me -author:@me -review-requested:@me", type: ISSUE, first: 20) {
|
|
133
|
+
}`,
|
|
134
|
+
reviewed: ({ scope, health, conversation }) => ` reviewed: search(query: "${scope}is:pr is:open reviewed-by:@me -author:@me -review-requested:@me", type: ISSUE, first: 20) {
|
|
150
135
|
nodes { __typename ... on PullRequest {
|
|
151
136
|
number title createdAt url headRefName isDraft
|
|
152
137
|
repository { nameWithOwner }
|
|
@@ -154,8 +139,8 @@ export const buildInboxQuery = ({ repo, doneWithinDays = 14, shape = "full", } =
|
|
|
154
139
|
${health}
|
|
155
140
|
${conversation}
|
|
156
141
|
}}
|
|
157
|
-
}
|
|
158
|
-
assigned: search(query: "${scope}is:open assignee:@me", type: ISSUE, first: 30) {
|
|
142
|
+
}`,
|
|
143
|
+
assigned: ({ scope, health, conversation, issueConversation, issueLabels, }) => ` assigned: search(query: "${scope}is:open assignee:@me", type: ISSUE, first: 30) {
|
|
159
144
|
nodes {
|
|
160
145
|
__typename
|
|
161
146
|
... on Issue {
|
|
@@ -169,8 +154,8 @@ export const buildInboxQuery = ({ repo, doneWithinDays = 14, shape = "full", } =
|
|
|
169
154
|
${conversation}
|
|
170
155
|
}
|
|
171
156
|
}
|
|
172
|
-
}
|
|
173
|
-
repoIssues: search(query: "${scope}${owned}is:issue is:open archived:false", type: ISSUE, first: 30) {
|
|
157
|
+
}`,
|
|
158
|
+
repoIssues: ({ scope, owned, issueConversation, issueLabels, }) => ` repoIssues: search(query: "${scope}${owned}is:issue is:open archived:false", type: ISSUE, first: 30) {
|
|
174
159
|
nodes { __typename ... on Issue {
|
|
175
160
|
number title createdAt url
|
|
176
161
|
repository { nameWithOwner }
|
|
@@ -178,8 +163,8 @@ export const buildInboxQuery = ({ repo, doneWithinDays = 14, shape = "full", } =
|
|
|
178
163
|
${issueConversation}
|
|
179
164
|
${issueLabels}
|
|
180
165
|
}}
|
|
181
|
-
}
|
|
182
|
-
authoredIssues: search(query: "${scope}is:issue is:open author:@me archived:false", type: ISSUE, first: 30) {
|
|
166
|
+
}`,
|
|
167
|
+
authoredIssues: ({ scope, issueConversation, issueLabels, }) => ` authoredIssues: search(query: "${scope}is:issue is:open author:@me archived:false", type: ISSUE, first: 30) {
|
|
183
168
|
nodes { ... on Issue {
|
|
184
169
|
number title createdAt url
|
|
185
170
|
repository { nameWithOwner }
|
|
@@ -187,8 +172,8 @@ export const buildInboxQuery = ({ repo, doneWithinDays = 14, shape = "full", } =
|
|
|
187
172
|
${issueConversation}
|
|
188
173
|
${issueLabels}
|
|
189
174
|
}}
|
|
190
|
-
}
|
|
191
|
-
repoPRs: search(query: "${scope}${owned}is:pr is:open -author:@me archived:false", type: ISSUE, first: 30) {
|
|
175
|
+
}`,
|
|
176
|
+
repoPRs: ({ scope, owned, health, conversation, }) => ` repoPRs: search(query: "${scope}${owned}is:pr is:open -author:@me archived:false", type: ISSUE, first: 30) {
|
|
192
177
|
nodes { __typename ... on PullRequest {
|
|
193
178
|
number title createdAt url headRefName isDraft
|
|
194
179
|
repository { nameWithOwner }
|
|
@@ -196,14 +181,130 @@ export const buildInboxQuery = ({ repo, doneWithinDays = 14, shape = "full", } =
|
|
|
196
181
|
${health}
|
|
197
182
|
${conversation}
|
|
198
183
|
}}
|
|
199
|
-
}
|
|
200
|
-
recentlyDone: search(query: "${scope}is:pr author:@me -is:open closed:>=${doneSince}", type: ISSUE, first: 30) {
|
|
184
|
+
}`,
|
|
185
|
+
recentlyDone: ({ scope, doneSince, }) => ` recentlyDone: search(query: "${scope}is:pr author:@me -is:open closed:>=${doneSince}", type: ISSUE, first: 30) {
|
|
201
186
|
nodes { __typename ... on PullRequest {
|
|
202
187
|
number title state isDraft createdAt mergedAt closedAt url
|
|
203
188
|
repository { nameWithOwner }
|
|
204
189
|
}}
|
|
205
|
-
}
|
|
190
|
+
}`,
|
|
191
|
+
};
|
|
192
|
+
const selectionsFor = ({ repo, doneWithinDays = 14, shape = "full", }) => {
|
|
193
|
+
/* Interpolated as empty strings rather than branching the query text, so the
|
|
194
|
+
two shapes cannot drift into two separately-maintained queries. */
|
|
195
|
+
const full = shape === "full";
|
|
196
|
+
return {
|
|
197
|
+
scope: repo ? `repo:${repo} ` : "",
|
|
198
|
+
owned: repo ? "" : "user:@me ",
|
|
199
|
+
doneSince: sinceDay(doneWithinDays),
|
|
200
|
+
health: full ? PR_HEALTH : "",
|
|
201
|
+
conversation: full ? PR_CONVERSATION : "",
|
|
202
|
+
issueConversation: full ? ISSUE_CONVERSATION : "",
|
|
203
|
+
issueLabels: full ? ISSUE_LABELS : "",
|
|
204
|
+
};
|
|
205
|
+
};
|
|
206
|
+
/**
|
|
207
|
+
* Build the inbox query. Without `repo`, it is account-wide.
|
|
208
|
+
*
|
|
209
|
+
* The two qualifiers do NOT compose. `user:@me` means "in repos I own", and
|
|
210
|
+
* GitHub ANDs search qualifiers — so `user:@me repo:someone-else/x` matches
|
|
211
|
+
* nothing, and a scoped inbox on a repo you contribute to but do not own would
|
|
212
|
+
* render empty and read as "nothing to do here". So `repo:` REPLACES
|
|
213
|
+
* `user:@me` rather than joining it.
|
|
214
|
+
*/
|
|
215
|
+
export const buildInboxQuery = (options = {}) => {
|
|
216
|
+
const selections = selectionsFor(options);
|
|
217
|
+
const sources = options.sources ?? INBOX_SOURCES;
|
|
218
|
+
/*
|
|
219
|
+
* `rateLimit` is free — it does not count against itself — and it is the only
|
|
220
|
+
* authoritative source for what this query costs. Every estimate made about
|
|
221
|
+
* that on 2026-08-14 was wrong, one of them by 25x, because GraphQL cost is
|
|
222
|
+
* node-count based and nested connections multiply: `reviewThreads(first: 50)`
|
|
223
|
+
* beneath `search(first: 100)` is 5,000 nodes from two lines of query text. A
|
|
224
|
+
* package handing out a 73-point query should hand out the means to see it.
|
|
225
|
+
*
|
|
226
|
+
* (GraphQL has no block comments, only `#`, so this note lives out here.)
|
|
227
|
+
*/
|
|
228
|
+
return `
|
|
229
|
+
{
|
|
230
|
+
rateLimit { cost nodeCount remaining resetAt }
|
|
231
|
+
viewer { login }
|
|
232
|
+
${sources.map((source) => SOURCES[source](selections)).join("\n")}
|
|
206
233
|
}
|
|
207
234
|
`;
|
|
208
235
|
};
|
|
236
|
+
/**
|
|
237
|
+
* How many sources to put in one request.
|
|
238
|
+
*
|
|
239
|
+
* GitHub's proxy answers a request, not a query — so the ceiling here is WALL
|
|
240
|
+
* CLOCK, not cost. Measured on 2026-08-28 against an account-wide inbox: the
|
|
241
|
+
* eight-source query returns HTTP 502 on roughly two runs in three, after
|
|
242
|
+
* 10–30s; the same eight sources asked one at a time return 200 every time, for
|
|
243
|
+
* the same 73 points and the same ~16,870 nodes. Adding sources one at a time
|
|
244
|
+
* shows a clean gradient — five is reliable, six starts failing, eight mostly
|
|
245
|
+
* fails.
|
|
246
|
+
*
|
|
247
|
+
* The cost is identical either way, which is the whole reason this is safe:
|
|
248
|
+
* GraphQL scores the nodes a query COULD return, so splitting one document into
|
|
249
|
+
* four buys reliability at no extra budget. It is also why the same query is
|
|
250
|
+
* fine repo-scoped — `repo:` narrows what the search index has to walk, while
|
|
251
|
+
* `author:@me` across an account does not.
|
|
252
|
+
*
|
|
253
|
+
* Two rather than five, because the gradient is a probability and not a cliff:
|
|
254
|
+
* the point is headroom, and four requests in parallel cost the same wall clock
|
|
255
|
+
* as one.
|
|
256
|
+
*/
|
|
257
|
+
export const INBOX_SOURCES_PER_QUERY = 2;
|
|
258
|
+
/**
|
|
259
|
+
* Build the inbox as several independent queries, to be issued in parallel and
|
|
260
|
+
* merged with `mergeInboxData`.
|
|
261
|
+
*
|
|
262
|
+
* Prefer this to `buildInboxQuery` for an account-wide inbox. Each query is a
|
|
263
|
+
* complete document carrying its own `rateLimit` and `viewer`, so a partial
|
|
264
|
+
* failure is one source missing rather than a malformed whole.
|
|
265
|
+
*/
|
|
266
|
+
export const buildInboxQueries = (options = {}) => {
|
|
267
|
+
const { sourcesPerQuery = INBOX_SOURCES_PER_QUERY, ...queryOptions } = options;
|
|
268
|
+
const sources = options.sources ?? INBOX_SOURCES;
|
|
269
|
+
const size = Math.max(1, Math.trunc(sourcesPerQuery));
|
|
270
|
+
const chunks = [];
|
|
271
|
+
for (let i = 0; i < sources.length; i += size)
|
|
272
|
+
chunks.push(sources.slice(i, i + size));
|
|
273
|
+
return chunks.map((chunk) => buildInboxQuery({ ...queryOptions, sources: chunk }));
|
|
274
|
+
};
|
|
275
|
+
/**
|
|
276
|
+
* Reassemble what `buildInboxQueries` split.
|
|
277
|
+
*
|
|
278
|
+
* The source aliases are disjoint, so they merge by assignment. `rateLimit` and
|
|
279
|
+
* `viewer` are not: every part carries its own.
|
|
280
|
+
*
|
|
281
|
+
* `cost` and `nodeCount` SUM, because each part really was charged separately
|
|
282
|
+
* and a host reporting one part's cost as the inbox's would understate it by
|
|
283
|
+
* four. `remaining` takes the LOWEST reading — the parts run concurrently and
|
|
284
|
+
* the budget only falls, so the smallest is the closest to now. `resetAt` rides
|
|
285
|
+
* along with it, since a window and its expiry have to describe the same
|
|
286
|
+
* moment.
|
|
287
|
+
*/
|
|
288
|
+
export const mergeInboxData = (parts) => {
|
|
289
|
+
const answered = parts.filter(Boolean);
|
|
290
|
+
if (answered.length === 0)
|
|
291
|
+
return undefined;
|
|
292
|
+
const limits = answered
|
|
293
|
+
.map((part) => part.rateLimit)
|
|
294
|
+
.filter(Boolean);
|
|
295
|
+
const scarcest = limits.reduce((lowest, limit) => !lowest || limit.remaining < lowest.remaining ? limit : lowest, undefined);
|
|
296
|
+
return {
|
|
297
|
+
...Object.assign({}, ...answered),
|
|
298
|
+
viewer: answered.find((part) => part.viewer)?.viewer,
|
|
299
|
+
...(scarcest
|
|
300
|
+
? {
|
|
301
|
+
rateLimit: {
|
|
302
|
+
...scarcest,
|
|
303
|
+
cost: limits.reduce((total, l) => total + (l.cost ?? 0), 0),
|
|
304
|
+
nodeCount: limits.reduce((total, l) => total + (l.nodeCount ?? 0), 0),
|
|
305
|
+
},
|
|
306
|
+
}
|
|
307
|
+
: {}),
|
|
308
|
+
};
|
|
309
|
+
};
|
|
209
310
|
//# sourceMappingURL=inbox.js.map
|
package/dist/inbox.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inbox.js","sourceRoot":"","sources":["../src/inbox.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,
|
|
1
|
+
{"version":3,"file":"inbox.js","sourceRoot":"","sources":["../src/inbox.ts"],"names":[],"mappings":"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"}
|