@kud/gh 0.6.0 → 0.7.0

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 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 **111 points** of a 5000/hour
10
- * budget, at a nodeCount of ~25,550 — because `statusCheckRollup.contexts`
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. At 111 a poller gets 45 refreshes an hour.
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 batching queries saves nothing here and this saves a lot.
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: ({ repo, doneWithinDays, shape, }?: InboxQueryOptions) => string;
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
@@ -1 +1 @@
1
- {"version":3,"file":"inbox.d.ts","sourceRoot":"","sources":["../src/inbox.ts"],"names":[],"mappings":"AAQA;;;;;;;;;;;;;GAaG;AACH;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,YAAY,KAAK,CAAA;AAE9B,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,SAAS,CAAA;AAE3C,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;CACnB,CAAA;AA6DD;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAAI,mCAI7B,iBAAsB,WAyGxB,CAAA"}
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 — one GraphQL round-trip for "what is on my plate": my PRs,
2
- // review requests, reviews I have given, assigned work, issues and PRs on my
3
- // repos, and what I closed recently.
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 **111 points** of a 5000/hour
17
- * budget, at a nodeCount of ~25,550 — because `statusCheckRollup.contexts`
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. At 111 a poller gets 45 refreshes an hour.
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 batching queries saves nothing here and this saves a lot.
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
- * Build the inbox query. Without `repo`, it is account-wide.
98
- *
99
- * The two qualifiers do NOT compose. `user:@me` means "in repos I own", and
100
- * GitHub ANDs search qualifiers — so `user:@me repo:someone-else/x` matches
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
- export const buildInboxQuery = ({ repo, doneWithinDays = 14, shape = "full", } = {}) => {
106
- const scope = repo ? `repo:${repo} ` : "";
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,6EAA6E;AAC7E,qCAAqC;AACrC,EAAE;AACF,6EAA6E;AAC7E,+EAA+E;AAC/E,uDAAuD;AAEvD;;;;;;;;;;;;;GAaG;AACH;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,EAAE,CAAA;AAmB9B,2EAA2E;AAC3E,4EAA4E;AAC5E,4EAA4E;AAC5E,mEAAmE;AACnE,EAAE;AACF,6EAA6E;AAC7E,8EAA8E;AAC9E,8EAA8E;AAC9E,8EAA8E;AAC9E,yBAAyB;AACzB,EAAE;AACF,2EAA2E;AAC3E,8EAA8E;AAC9E,2EAA2E;AAC3E,qBAAqB;AACrB,MAAM,SAAS,GAAG;;;;;YAKN,CAAA;AAEZ,6EAA6E;AAC7E,gFAAgF;AAChF,8EAA8E;AAC9E,2DAA2D;AAC3D,EAAE;AACF,8EAA8E;AAC9E,8EAA8E;AAC9E,4EAA4E;AAC5E,gFAAgF;AAChF,8EAA8E;AAC9E,+DAA+D;AAC/D,MAAM,eAAe,GAAG;;;;;;;8DAOsC,CAAA;AAE9D,MAAM,kBAAkB,GAAG;uFAC4D,CAAA;AAEvF,+EAA+E;AAC/E,qEAAqE;AACrE,MAAM,YAAY,GAAG;2CACsB,CAAA;AAE3C;;;;;GAKG;AACH,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAE,EAAE,CAChC,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;AAEvE;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,EAC9B,IAAI,EACJ,cAAc,GAAG,EAAE,EACnB,KAAK,GAAG,MAAM,MACO,EAAE,EAAE,EAAE;IAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,QAAQ,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAA;IACzC,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,CAAA;IACrC,MAAM,SAAS,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAA;IAE1C;yEACqE;IACrE,MAAM,IAAI,GAAG,KAAK,KAAK,MAAM,CAAA;IAC7B,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAA;IACpC,MAAM,YAAY,GAAG,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,CAAA;IAChD,MAAM,iBAAiB,GAAG,IAAI,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,EAAE,CAAA;IACxD,MAAM,WAAW,GAAG,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAA;IAE5C;;;;;;;;;OASG;IACH,OAAO;;;;0BAIiB,KAAK,kDAAkD,YAAY;;;;;;QAMrF,MAAM;QACN,YAAY;;;mCAGe,KAAK;;;;;QAKhC,MAAM;QACN,YAAY;;;6BAGS,KAAK;;;;;QAK1B,MAAM;QACN,YAAY;;;6BAGS,KAAK;;;;;UAKxB,iBAAiB;UACjB,WAAW;;;;UAIX,MAAM;UACN,YAAY;;;;+BAIS,KAAK,GAAG,KAAK;;;;;QAKpC,iBAAiB;QACjB,WAAW;;;mCAGgB,KAAK;;;;;QAKhC,iBAAiB;QACjB,WAAW;;;4BAGS,KAAK,GAAG,KAAK;;;;;QAKjC,MAAM;QACN,YAAY;;;iCAGa,KAAK,sCAAsC,SAAS;;;;;;;CAOpF,CAAA;AACD,CAAC,CAAA"}
1
+ {"version":3,"file":"inbox.js","sourceRoot":"","sources":["../src/inbox.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kud/gh",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Surface-agnostic GitHub core — gh CLI primitives, PR review comments, health, and webhook logic.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",