@gitkraken/core-gitlens 0.9.4 → 0.9.6

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.
Files changed (153) hide show
  1. package/CHANGELOG.md +29 -1
  2. package/dist/git/models/issue.d.ts +25 -2
  3. package/dist/git/models/issue.js +3 -1
  4. package/dist/git/models/issue.js.map +1 -1
  5. package/dist/git/models/pullRequest.d.ts +62 -7
  6. package/dist/git/models/pullRequest.js +5 -1
  7. package/dist/git/models/pullRequest.js.map +1 -1
  8. package/dist/git/utils/issue.utils.d.ts +4 -0
  9. package/dist/git/utils/issue.utils.js +4 -0
  10. package/dist/git/utils/issue.utils.js.map +1 -1
  11. package/dist/git/utils/pullRequest.utils.d.ts +4 -0
  12. package/dist/git/utils/pullRequest.utils.js +8 -0
  13. package/dist/git/utils/pullRequest.utils.js.map +1 -1
  14. package/dist/plus/git-github/api/github.d.ts +64 -4
  15. package/dist/plus/git-github/api/github.js +232 -15
  16. package/dist/plus/git-github/api/github.js.map +1 -1
  17. package/dist/plus/git-github/models.d.ts +48 -6
  18. package/dist/plus/git-github/models.js +11 -10
  19. package/dist/plus/git-github/models.js.map +1 -1
  20. package/dist/plus/integrations/fieldPresence.d.ts +75 -0
  21. package/dist/plus/integrations/fieldPresence.js +381 -0
  22. package/dist/plus/integrations/fieldPresence.js.map +1 -0
  23. package/dist/plus/integrations/index.d.ts +6 -4
  24. package/dist/plus/integrations/index.js +2 -1
  25. package/dist/plus/integrations/index.js.map +1 -1
  26. package/dist/plus/integrations/integrationService.d.ts +7 -3
  27. package/dist/plus/integrations/integrationService.js.map +1 -1
  28. package/dist/plus/integrations/manager.d.ts +56 -6
  29. package/dist/plus/integrations/models/gitHostIntegration.d.ts +103 -9
  30. package/dist/plus/integrations/models/gitHostIntegration.js +116 -5
  31. package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
  32. package/dist/plus/integrations/models/integration.d.ts +91 -4
  33. package/dist/plus/integrations/models/integration.js +56 -7
  34. package/dist/plus/integrations/models/integration.js.map +1 -1
  35. package/dist/plus/integrations/models/issuesIntegration.d.ts +44 -3
  36. package/dist/plus/integrations/models/issuesIntegration.js +51 -1
  37. package/dist/plus/integrations/models/issuesIntegration.js.map +1 -1
  38. package/dist/plus/integrations/providers/azure/azure.js +3 -3
  39. package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
  40. package/dist/plus/integrations/providers/azure/models.d.ts +36 -4
  41. package/dist/plus/integrations/providers/azure/models.js +118 -8
  42. package/dist/plus/integrations/providers/azure/models.js.map +1 -1
  43. package/dist/plus/integrations/providers/azureDevOps.d.ts +102 -5
  44. package/dist/plus/integrations/providers/azureDevOps.js +264 -45
  45. package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
  46. package/dist/plus/integrations/providers/azureDevOpsServer.d.ts +30 -6
  47. package/dist/plus/integrations/providers/azureDevOpsServer.js +82 -21
  48. package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -1
  49. package/dist/plus/integrations/providers/bitbucket/bitbucket.d.ts +21 -1
  50. package/dist/plus/integrations/providers/bitbucket/bitbucket.js +74 -10
  51. package/dist/plus/integrations/providers/bitbucket/bitbucket.js.map +1 -1
  52. package/dist/plus/integrations/providers/bitbucket/models.d.ts +40 -3
  53. package/dist/plus/integrations/providers/bitbucket/models.js +30 -7
  54. package/dist/plus/integrations/providers/bitbucket/models.js.map +1 -1
  55. package/dist/plus/integrations/providers/bitbucket-server/models.d.ts +2 -6
  56. package/dist/plus/integrations/providers/bitbucket-server/models.js +1 -3
  57. package/dist/plus/integrations/providers/bitbucket-server/models.js.map +1 -1
  58. package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.d.ts +3 -1
  59. package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.js +4 -1
  60. package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.js.map +1 -1
  61. package/dist/plus/integrations/providers/bitbucket-server.d.ts +2 -2
  62. package/dist/plus/integrations/providers/bitbucket-server.js +7 -5
  63. package/dist/plus/integrations/providers/bitbucket-server.js.map +1 -1
  64. package/dist/plus/integrations/providers/bitbucket.d.ts +22 -2
  65. package/dist/plus/integrations/providers/bitbucket.js +29 -6
  66. package/dist/plus/integrations/providers/bitbucket.js.map +1 -1
  67. package/dist/plus/integrations/providers/github.d.ts +40 -3
  68. package/dist/plus/integrations/providers/github.js +133 -11
  69. package/dist/plus/integrations/providers/github.js.map +1 -1
  70. package/dist/plus/integrations/providers/gitlab/gitlab.d.ts +34 -1
  71. package/dist/plus/integrations/providers/gitlab/gitlab.js +118 -4
  72. package/dist/plus/integrations/providers/gitlab/gitlab.js.map +1 -1
  73. package/dist/plus/integrations/providers/gitlab/models.d.ts +70 -5
  74. package/dist/plus/integrations/providers/gitlab/models.js +106 -13
  75. package/dist/plus/integrations/providers/gitlab/models.js.map +1 -1
  76. package/dist/plus/integrations/providers/gitlab.d.ts +31 -2
  77. package/dist/plus/integrations/providers/gitlab.js +48 -8
  78. package/dist/plus/integrations/providers/gitlab.js.map +1 -1
  79. package/dist/plus/integrations/providers/jira-server.d.ts +1 -0
  80. package/dist/plus/integrations/providers/jira-server.js +9 -6
  81. package/dist/plus/integrations/providers/jira-server.js.map +1 -1
  82. package/dist/plus/integrations/providers/jira.d.ts +20 -1
  83. package/dist/plus/integrations/providers/jira.js +101 -6
  84. package/dist/plus/integrations/providers/jira.js.map +1 -1
  85. package/dist/plus/integrations/providers/jiraIssueByKey.d.ts +79 -0
  86. package/dist/plus/integrations/providers/jiraIssueByKey.js +58 -7
  87. package/dist/plus/integrations/providers/jiraIssueByKey.js.map +1 -1
  88. package/dist/plus/integrations/providers/linear.d.ts +19 -1
  89. package/dist/plus/integrations/providers/linear.js +97 -5
  90. package/dist/plus/integrations/providers/linear.js.map +1 -1
  91. package/dist/plus/integrations/providers/linearIssuesEtag.d.ts +42 -0
  92. package/dist/plus/integrations/providers/linearIssuesEtag.js +94 -0
  93. package/dist/plus/integrations/providers/linearIssuesEtag.js.map +1 -0
  94. package/dist/plus/integrations/providers/models.d.ts +51 -7
  95. package/dist/plus/integrations/providers/models.js +46 -17
  96. package/dist/plus/integrations/providers/models.js.map +1 -1
  97. package/dist/plus/integrations/providers/providerErrors.js +8 -1
  98. package/dist/plus/integrations/providers/providerErrors.js.map +1 -1
  99. package/dist/plus/integrations/providers/providersApi.d.ts +62 -3
  100. package/dist/plus/integrations/providers/providersApi.js +145 -4
  101. package/dist/plus/integrations/providers/providersApi.js.map +1 -1
  102. package/dist/plus/integrations/providers/pullRequestReviews.d.ts +13 -0
  103. package/dist/plus/integrations/providers/pullRequestReviews.js +30 -6
  104. package/dist/plus/integrations/providers/pullRequestReviews.js.map +1 -1
  105. package/dist/plus/integrations/providers/trello.js +10 -7
  106. package/dist/plus/integrations/providers/trello.js.map +1 -1
  107. package/dist/plus/integrations/providers/utils/providerPaging.d.ts +11 -0
  108. package/dist/plus/integrations/providers/utils/providerPaging.js +47 -1
  109. package/dist/plus/integrations/providers/utils/providerPaging.js.map +1 -1
  110. package/dist/plus/integrations/providers/utils.d.ts +1 -1
  111. package/dist/plus/integrations/reads/batchEtags.d.ts +69 -0
  112. package/dist/plus/integrations/reads/batchEtags.js +114 -0
  113. package/dist/plus/integrations/reads/batchEtags.js.map +1 -0
  114. package/dist/plus/integrations/reads/counts.js +2 -2
  115. package/dist/plus/integrations/reads/counts.js.map +1 -1
  116. package/dist/plus/integrations/reads/currentAccount.js +2 -2
  117. package/dist/plus/integrations/reads/currentAccount.js.map +1 -1
  118. package/dist/plus/integrations/reads/drains.d.ts +12 -3
  119. package/dist/plus/integrations/reads/drains.js +29 -5
  120. package/dist/plus/integrations/reads/drains.js.map +1 -1
  121. package/dist/plus/integrations/reads/etag.d.ts +26 -0
  122. package/dist/plus/integrations/reads/etag.js +108 -0
  123. package/dist/plus/integrations/reads/etag.js.map +1 -0
  124. package/dist/plus/integrations/reads/hierarchy.js +3 -3
  125. package/dist/plus/integrations/reads/hierarchy.js.map +1 -1
  126. package/dist/plus/integrations/reads/issueBatch.d.ts +47 -16
  127. package/dist/plus/integrations/reads/issueBatch.js +55 -87
  128. package/dist/plus/integrations/reads/issueBatch.js.map +1 -1
  129. package/dist/plus/integrations/reads/issueTracker.d.ts +1 -1
  130. package/dist/plus/integrations/reads/issueTracker.js +4 -4
  131. package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
  132. package/dist/plus/integrations/reads/issues.d.ts +1 -1
  133. package/dist/plus/integrations/reads/issues.js +4 -4
  134. package/dist/plus/integrations/reads/issues.js.map +1 -1
  135. package/dist/plus/integrations/reads/pullRequestBatch.d.ts +44 -6
  136. package/dist/plus/integrations/reads/pullRequestBatch.js +31 -34
  137. package/dist/plus/integrations/reads/pullRequestBatch.js.map +1 -1
  138. package/dist/plus/integrations/reads/pullRequestBranches.d.ts +5 -2
  139. package/dist/plus/integrations/reads/pullRequestBranches.js +4 -3
  140. package/dist/plus/integrations/reads/pullRequestBranches.js.map +1 -1
  141. package/dist/plus/integrations/reads/pullRequests.js +8 -4
  142. package/dist/plus/integrations/reads/pullRequests.js.map +1 -1
  143. package/dist/plus/integrations/reads/sweeps.js +8 -4
  144. package/dist/plus/integrations/reads/sweeps.js.map +1 -1
  145. package/dist/plus/integrations/reads/warnings.d.ts +23 -8
  146. package/dist/plus/integrations/reads/warnings.js +43 -19
  147. package/dist/plus/integrations/reads/warnings.js.map +1 -1
  148. package/dist/plus/integrations/results.d.ts +12 -4
  149. package/dist/plus/integrations/results.js +2 -0
  150. package/dist/plus/integrations/results.js.map +1 -1
  151. package/docs/integrations.md +385 -23
  152. package/docs/kepler-read-api-parity.md +16 -2
  153. package/package.json +1 -1
@@ -10,11 +10,17 @@ import { GitPullRequest, GitPullRequestReviewState } from "@gitkraken/provider-a
10
10
  * the next push, which is exactly the "the PR moved past my review" situation the oid detects. Dropping such a
11
11
  * review instead would hand a consumer a PR out of the `reviewed-by:@me` set with no review row at all,
12
12
  * indistinguishable from never having reviewed it.
13
+ *
14
+ * It also carries GitHub's code-owner flag on a request, which provider-apis doesn't report, so its rows leave it
15
+ * unset.
13
16
  */
14
17
  export declare const providerPullRequestReviewStateDismissed: 'DISMISSED';
15
18
  export type ProviderPullRequestReview = Omit<NonNullable<GitPullRequest['reviews']>[number], 'state'> & {
16
19
  state: GitPullRequestReviewState | typeof providerPullRequestReviewStateDismissed;
17
20
  commitOid?: string;
21
+ isCodeOwner?: boolean;
22
+ /** See {@link PullRequestReviewer.isMyGroup}. Only the Azure DevOps reads that resolve the user's groups set it. */
23
+ isMyGroup?: boolean;
18
24
  };
19
25
  /**
20
26
  * The review list as it travels on {@link ProviderPullRequest}: `null` when the read carried no review data at
@@ -46,5 +52,12 @@ export declare const fromPullRequestReviewDecision: {
46
52
  COMMENTED: undefined;
47
53
  REVIEW_REQUESTED: PullRequestReviewDecision;
48
54
  };
55
+ /**
56
+ * The review decision provider-apis' GitLab and Azure DevOps mappers derive from a pull request's review states (`ne`
57
+ * in its bundle): the most severe state wins, starting from approved, and no states at all is no decision. A state it
58
+ * doesn't rank (`undefined` here, for one provider-apis doesn't know) never outranks the decision so far. For a cheap
59
+ * etag check, which must reproduce a full row's decision without provider-apis' mapping.
60
+ */
61
+ export declare function decideProviderReviewDecision(states: readonly (GitPullRequestReviewState | undefined)[] | undefined): GitPullRequestReviewState | undefined;
49
62
  //#endregion
50
63
  //# sourceMappingURL=pullRequestReviews.d.ts.map
@@ -11,6 +11,9 @@ import { GitPullRequestReviewState } from "@gitkraken/provider-apis";
11
11
  * the next push, which is exactly the "the PR moved past my review" situation the oid detects. Dropping such a
12
12
  * review instead would hand a consumer a PR out of the `reviewed-by:@me` set with no review row at all,
13
13
  * indistinguishable from never having reviewed it.
14
+ *
15
+ * It also carries GitHub's code-owner flag on a request, which provider-apis doesn't report, so its rows leave it
16
+ * unset.
14
17
  */
15
18
  const providerPullRequestReviewStateDismissed = "DISMISSED";
16
19
  const toProviderPullRequestReviewState = {
@@ -32,22 +35,26 @@ function toProviderReviews(reviewers) {
32
35
  return reviewers.filter((r) => r.state !== "PENDING").map((reviewer) => ({
33
36
  reviewer: toProviderAccount(reviewer.reviewer),
34
37
  state: toProviderPullRequestReviewState[reviewer.state] ?? GitPullRequestReviewState.ReviewRequested,
35
- commitOid: reviewer.commitOid
38
+ commitOid: reviewer.commitOid,
39
+ isCodeOwner: reviewer.isCodeOwner,
40
+ ...reviewer.isMyGroup ? { isMyGroup: true } : {}
36
41
  }));
37
42
  }
38
43
  function toReviewRequests(reviews) {
39
44
  return reviews == null ? void 0 : reviews?.filter((r) => r.state === GitPullRequestReviewState.ReviewRequested).map((r) => ({
40
- isCodeOwner: false,
45
+ isCodeOwner: r.isCodeOwner,
41
46
  reviewer: fromProviderAccount(r.reviewer),
42
- state: "REVIEW_REQUESTED"
47
+ state: "REVIEW_REQUESTED",
48
+ ...r.isMyGroup ? { isMyGroup: true } : {}
43
49
  }));
44
50
  }
45
51
  function toCompletedReviews(reviews) {
46
52
  return reviews == null ? void 0 : reviews?.filter((r) => r.state !== GitPullRequestReviewState.ReviewRequested && fromProviderPullRequestReviewState[r.state] != null).map((r) => ({
47
- isCodeOwner: false,
53
+ isCodeOwner: r.isCodeOwner,
48
54
  reviewer: fromProviderAccount(r.reviewer),
49
55
  state: fromProviderPullRequestReviewState[r.state],
50
- commitOid: r.commitOid
56
+ commitOid: r.commitOid,
57
+ ...r.isMyGroup ? { isMyGroup: true } : {}
51
58
  }));
52
59
  }
53
60
  function toProviderReviewDecision(reviewDecision, reviewers) {
@@ -67,7 +74,24 @@ const fromPullRequestReviewDecision = {
67
74
  [GitPullRequestReviewState.Commented]: void 0,
68
75
  [GitPullRequestReviewState.ReviewRequested]: "ReviewRequired"
69
76
  };
77
+ /** provider-apis' (0.61.0) review severity (`Us` in its bundle), by which it picks a review decision. */
78
+ const providerReviewDecisionSeverity = {
79
+ [GitPullRequestReviewState.Approved]: 0,
80
+ [GitPullRequestReviewState.Commented]: 1,
81
+ [GitPullRequestReviewState.ReviewRequested]: 2,
82
+ [GitPullRequestReviewState.ChangesRequested]: 3
83
+ };
84
+ /**
85
+ * The review decision provider-apis' GitLab and Azure DevOps mappers derive from a pull request's review states (`ne`
86
+ * in its bundle): the most severe state wins, starting from approved, and no states at all is no decision. A state it
87
+ * doesn't rank (`undefined` here, for one provider-apis doesn't know) never outranks the decision so far. For a cheap
88
+ * etag check, which must reproduce a full row's decision without provider-apis' mapping.
89
+ */
90
+ function decideProviderReviewDecision(states) {
91
+ if (!states?.length) return void 0;
92
+ return states.reduce((decided, state) => state != null && (providerReviewDecisionSeverity[state] ?? -1) > (providerReviewDecisionSeverity[decided] ?? -1) ? state : decided, GitPullRequestReviewState.Approved);
93
+ }
70
94
  //#endregion
71
- export { fromProviderPullRequestReviewState, fromPullRequestReviewDecision, providerPullRequestReviewStateDismissed, toCompletedReviews, toProviderPullRequestReviewState, toProviderReviewDecision, toProviderReviews, toReviewRequests };
95
+ export { decideProviderReviewDecision, fromProviderPullRequestReviewState, fromPullRequestReviewDecision, providerPullRequestReviewStateDismissed, toCompletedReviews, toProviderPullRequestReviewState, toProviderReviewDecision, toProviderReviews, toReviewRequests };
72
96
 
73
97
  //# sourceMappingURL=pullRequestReviews.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"pullRequestReviews.js","names":[],"sources":["../../../../../plus/integrations/src/providers/pullRequestReviews.ts"],"sourcesContent":["import type { GitPullRequest } from '@gitkraken/provider-apis';\nimport { GitPullRequestReviewState } from '@gitkraken/provider-apis';\nimport type { PullRequestReviewer } from '@gitlens/git/models/pullRequest.js';\nimport { PullRequestReviewDecision, PullRequestReviewState } from '@gitlens/git/models/pullRequest.js';\nimport { fromProviderAccount, toProviderAccount } from './accounts.js';\n\n/**\n * The GitLens-local extension of provider-apis' review shape, in two parts: a `DISMISSED` state the upstream\n * `GitPullRequestReviewState` has no member for, and the head-commit oid a review was submitted against\n * (optional, so a raw SDK review stays assignable — only the GitHub path populates it).\n *\n * Both exist for the same case: on GitHub the `dismiss stale reviews` rule flips an approval to dismissed on\n * the next push, which is exactly the \"the PR moved past my review\" situation the oid detects. Dropping such a\n * review instead would hand a consumer a PR out of the `reviewed-by:@me` set with no review row at all,\n * indistinguishable from never having reviewed it.\n */\nexport const providerPullRequestReviewStateDismissed = 'DISMISSED' as const;\nexport type ProviderPullRequestReview = Omit<NonNullable<GitPullRequest['reviews']>[number], 'state'> & {\n\tstate: GitPullRequestReviewState | typeof providerPullRequestReviewStateDismissed;\n\tcommitOid?: string;\n};\n/**\n * The review list as it travels on {@link ProviderPullRequest}: `null` when the read carried no review data at\n * all, as opposed to an empty array for a pull request nobody has reviewed.\n */\nexport type ProviderPullRequestReviews = ProviderPullRequestReview[] | null;\n\nexport const toProviderPullRequestReviewState = {\n\t[PullRequestReviewState.Approved]: GitPullRequestReviewState.Approved,\n\t[PullRequestReviewState.ChangesRequested]: GitPullRequestReviewState.ChangesRequested,\n\t[PullRequestReviewState.Commented]: GitPullRequestReviewState.Commented,\n\t[PullRequestReviewState.ReviewRequested]: GitPullRequestReviewState.ReviewRequested,\n\t[PullRequestReviewState.Dismissed]: providerPullRequestReviewStateDismissed,\n\t// A review the author started but never submitted. Visible only to that author and carrying no verdict, so\n\t// it stays unmapped and is dropped by the projection.\n\t[PullRequestReviewState.Pending]: null,\n};\n\nexport const fromProviderPullRequestReviewState = {\n\t[GitPullRequestReviewState.Approved]: PullRequestReviewState.Approved,\n\t[GitPullRequestReviewState.ChangesRequested]: PullRequestReviewState.ChangesRequested,\n\t[GitPullRequestReviewState.Commented]: PullRequestReviewState.Commented,\n\t[GitPullRequestReviewState.ReviewRequested]: PullRequestReviewState.ReviewRequested,\n\t[providerPullRequestReviewStateDismissed]: PullRequestReviewState.Dismissed,\n};\n\nexport function toProviderReviews(reviewers: PullRequestReviewer[]): ProviderPullRequestReviews {\n\t// Only `Pending` maps to null (see `toProviderPullRequestReviewState`), so this drops exactly the reviews\n\t// that carry no verdict rather than defaulting them to `ReviewRequested` — which would report an unsubmitted\n\t// draft as a pending request from that reviewer.\n\treturn reviewers\n\t\t.filter(r => r.state !== PullRequestReviewState.Pending)\n\t\t.map(reviewer => ({\n\t\t\treviewer: toProviderAccount(reviewer.reviewer),\n\t\t\tstate: toProviderPullRequestReviewState[reviewer.state] ?? GitPullRequestReviewState.ReviewRequested,\n\t\t\tcommitOid: reviewer.commitOid,\n\t\t}));\n}\n\nexport function toReviewRequests(reviews: ProviderPullRequestReviews): PullRequestReviewer[] | undefined {\n\treturn reviews == null\n\t\t? undefined\n\t\t: reviews\n\t\t\t\t?.filter(r => r.state === GitPullRequestReviewState.ReviewRequested)\n\t\t\t\t.map(r => ({\n\t\t\t\t\tisCodeOwner: false, // TODO: Find this value, and implement in the shared lib if needed\n\t\t\t\t\treviewer: fromProviderAccount(r.reviewer),\n\t\t\t\t\tstate: PullRequestReviewState.ReviewRequested,\n\t\t\t\t}));\n}\n\nexport function toCompletedReviews(reviews: ProviderPullRequestReviews): PullRequestReviewer[] | undefined {\n\treturn reviews == null\n\t\t? undefined\n\t\t: reviews\n\t\t\t\t?.filter(\n\t\t\t\t\tr =>\n\t\t\t\t\t\tr.state !== GitPullRequestReviewState.ReviewRequested &&\n\t\t\t\t\t\t// provider-apis' own GitHub normalizer maps only its four known states, so a review it has no\n\t\t\t\t\t\t// member for (a real `DISMISSED` one, which its `latestReviews(first: 100)` selection does\n\t\t\t\t\t\t// return) arrives with `state: undefined` — a value its type says cannot happen. Publishing it\n\t\t\t\t\t\t// would put an unswitchable state on `PullRequestShape.latestReviews`, and mapping back would\n\t\t\t\t\t\t// hit `toProviderReviews`' fallback and report a dismissed review as an outstanding request.\n\t\t\t\t\t\t// Our own GitHub path never lands here: it carries `providerPullRequestReviewStateDismissed`.\n\t\t\t\t\t\tfromProviderPullRequestReviewState[r.state] != null,\n\t\t\t\t)\n\t\t\t\t.map(r => ({\n\t\t\t\t\tisCodeOwner: false, // TODO: Find this value, and implement in the shared lib if needed\n\t\t\t\t\treviewer: fromProviderAccount(r.reviewer),\n\t\t\t\t\tstate: fromProviderPullRequestReviewState[r.state],\n\t\t\t\t\tcommitOid: r.commitOid,\n\t\t\t\t}));\n}\n\nexport function toProviderReviewDecision(\n\treviewDecision?: PullRequestReviewDecision,\n\treviewers?: PullRequestReviewer[],\n): GitPullRequestReviewState | null {\n\tswitch (reviewDecision) {\n\t\tcase PullRequestReviewDecision.Approved:\n\t\t\treturn GitPullRequestReviewState.Approved;\n\t\tcase PullRequestReviewDecision.ChangesRequested:\n\t\t\treturn GitPullRequestReviewState.ChangesRequested;\n\t\tcase PullRequestReviewDecision.ReviewRequired:\n\t\t\treturn GitPullRequestReviewState.ReviewRequested;\n\t\tdefault: {\n\t\t\tif (reviewers?.some(r => r.state === PullRequestReviewState.ReviewRequested)) {\n\t\t\t\treturn GitPullRequestReviewState.ReviewRequested;\n\t\t\t} else if (reviewers?.some(r => r.state === PullRequestReviewState.Commented)) {\n\t\t\t\treturn GitPullRequestReviewState.Commented;\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\t}\n}\n\nexport const fromPullRequestReviewDecision = {\n\t[GitPullRequestReviewState.Approved]: PullRequestReviewDecision.Approved,\n\t[GitPullRequestReviewState.ChangesRequested]: PullRequestReviewDecision.ChangesRequested,\n\t[GitPullRequestReviewState.Commented]: undefined,\n\t[GitPullRequestReviewState.ReviewRequested]: PullRequestReviewDecision.ReviewRequired,\n};\n"],"mappings":";;;;;;;;;;;;;;AAgBA,MAAa,0CAA0C;AAWvD,MAAa,mCAAmC;EACZ,aAAA,0BAA0B;EAClB,sBAAA,0BAA0B;EACjC,cAAA,0BAA0B;EACpB,qBAAA,0BAA0B;EAChC,cAAA;EAGF,YAAA;AACnC;AAEA,MAAa,qCAAqC;EAChD,0BAA0B,WAAA;EAC1B,0BAA0B,mBAAA;EAC1B,0BAA0B,YAAA;EAC1B,0BAA0B,kBAAA;EAC1B,0CAAA;AACF;AAEA,SAAgB,kBAAkB,WAA8D;CAI/F,OAAO,UACL,QAAO,MAAK,EAAE,UAAA,SAAwC,CAAC,CACvD,KAAI,cAAa;EACjB,UAAU,kBAAkB,SAAS,QAAQ;EAC7C,OAAO,iCAAiC,SAAS,UAAU,0BAA0B;EACrF,WAAW,SAAS;CACrB,EAAE;AACJ;AAEA,SAAgB,iBAAiB,SAAwE;CACxG,OAAO,WAAW,OACf,KAAA,IACA,SACE,QAAO,MAAK,EAAE,UAAU,0BAA0B,eAAe,CAAC,CACnE,KAAI,OAAM;EACV,aAAa;EACb,UAAU,oBAAoB,EAAE,QAAQ;EACxC,OAAA;CACD,EAAE;AACN;AAEA,SAAgB,mBAAmB,SAAwE;CAC1G,OAAO,WAAW,OACf,KAAA,IACA,SACE,QACD,MACC,EAAE,UAAU,0BAA0B,mBAOtC,mCAAmC,EAAE,UAAU,IACjD,CAAC,CACA,KAAI,OAAM;EACV,aAAa;EACb,UAAU,oBAAoB,EAAE,QAAQ;EACxC,OAAO,mCAAmC,EAAE;EAC5C,WAAW,EAAE;CACd,EAAE;AACN;AAEA,SAAgB,yBACf,gBACA,WACmC;CACnC,QAAQ,gBAAR;EACC,KAAA,YACC,OAAO,0BAA0B;EAClC,KAAA,oBACC,OAAO,0BAA0B;EAClC,KAAA,kBACC,OAAO,0BAA0B;EAClC;GACC,IAAI,WAAW,MAAK,MAAK,EAAE,UAAA,kBAAgD,GAC1E,OAAO,0BAA0B;QAC3B,IAAI,WAAW,MAAK,MAAK,EAAE,UAAA,WAA0C,GAC3E,OAAO,0BAA0B;GAElC,OAAO;CAET;AACD;AAEA,MAAa,gCAAgC;EAC3C,0BAA0B,WAAA;EAC1B,0BAA0B,mBAAA;EAC1B,0BAA0B,YAAY,KAAA;EACtC,0BAA0B,kBAAA;AAC5B"}
1
+ {"version":3,"file":"pullRequestReviews.js","names":[],"sources":["../../../../../plus/integrations/src/providers/pullRequestReviews.ts"],"sourcesContent":["import type { GitPullRequest } from '@gitkraken/provider-apis';\nimport { GitPullRequestReviewState } from '@gitkraken/provider-apis';\nimport type { PullRequestReviewer } from '@gitlens/git/models/pullRequest.js';\nimport { PullRequestReviewDecision, PullRequestReviewState } from '@gitlens/git/models/pullRequest.js';\nimport { fromProviderAccount, toProviderAccount } from './accounts.js';\n\n/**\n * The GitLens-local extension of provider-apis' review shape, in two parts: a `DISMISSED` state the upstream\n * `GitPullRequestReviewState` has no member for, and the head-commit oid a review was submitted against\n * (optional, so a raw SDK review stays assignable — only the GitHub path populates it).\n *\n * Both exist for the same case: on GitHub the `dismiss stale reviews` rule flips an approval to dismissed on\n * the next push, which is exactly the \"the PR moved past my review\" situation the oid detects. Dropping such a\n * review instead would hand a consumer a PR out of the `reviewed-by:@me` set with no review row at all,\n * indistinguishable from never having reviewed it.\n *\n * It also carries GitHub's code-owner flag on a request, which provider-apis doesn't report, so its rows leave it\n * unset.\n */\nexport const providerPullRequestReviewStateDismissed = 'DISMISSED' as const;\nexport type ProviderPullRequestReview = Omit<NonNullable<GitPullRequest['reviews']>[number], 'state'> & {\n\tstate: GitPullRequestReviewState | typeof providerPullRequestReviewStateDismissed;\n\tcommitOid?: string;\n\tisCodeOwner?: boolean;\n\t/** See {@link PullRequestReviewer.isMyGroup}. Only the Azure DevOps reads that resolve the user's groups set it. */\n\tisMyGroup?: boolean;\n};\n/**\n * The review list as it travels on {@link ProviderPullRequest}: `null` when the read carried no review data at\n * all, as opposed to an empty array for a pull request nobody has reviewed.\n */\nexport type ProviderPullRequestReviews = ProviderPullRequestReview[] | null;\n\nexport const toProviderPullRequestReviewState = {\n\t[PullRequestReviewState.Approved]: GitPullRequestReviewState.Approved,\n\t[PullRequestReviewState.ChangesRequested]: GitPullRequestReviewState.ChangesRequested,\n\t[PullRequestReviewState.Commented]: GitPullRequestReviewState.Commented,\n\t[PullRequestReviewState.ReviewRequested]: GitPullRequestReviewState.ReviewRequested,\n\t[PullRequestReviewState.Dismissed]: providerPullRequestReviewStateDismissed,\n\t// A review the author started but never submitted. Visible only to that author and carrying no verdict, so\n\t// it stays unmapped and is dropped by the projection.\n\t[PullRequestReviewState.Pending]: null,\n};\n\nexport const fromProviderPullRequestReviewState = {\n\t[GitPullRequestReviewState.Approved]: PullRequestReviewState.Approved,\n\t[GitPullRequestReviewState.ChangesRequested]: PullRequestReviewState.ChangesRequested,\n\t[GitPullRequestReviewState.Commented]: PullRequestReviewState.Commented,\n\t[GitPullRequestReviewState.ReviewRequested]: PullRequestReviewState.ReviewRequested,\n\t[providerPullRequestReviewStateDismissed]: PullRequestReviewState.Dismissed,\n};\n\nexport function toProviderReviews(reviewers: PullRequestReviewer[]): ProviderPullRequestReviews {\n\t// Only `Pending` maps to null (see `toProviderPullRequestReviewState`), so this drops exactly the reviews\n\t// that carry no verdict rather than defaulting them to `ReviewRequested` — which would report an unsubmitted\n\t// draft as a pending request from that reviewer.\n\treturn reviewers\n\t\t.filter(r => r.state !== PullRequestReviewState.Pending)\n\t\t.map(reviewer => ({\n\t\t\treviewer: toProviderAccount(reviewer.reviewer),\n\t\t\tstate: toProviderPullRequestReviewState[reviewer.state] ?? GitPullRequestReviewState.ReviewRequested,\n\t\t\tcommitOid: reviewer.commitOid,\n\t\t\tisCodeOwner: reviewer.isCodeOwner,\n\t\t\t...(reviewer.isMyGroup ? { isMyGroup: true } : {}),\n\t\t}));\n}\n\nexport function toReviewRequests(reviews: ProviderPullRequestReviews): PullRequestReviewer[] | undefined {\n\treturn reviews == null\n\t\t? undefined\n\t\t: reviews\n\t\t\t\t?.filter(r => r.state === GitPullRequestReviewState.ReviewRequested)\n\t\t\t\t.map(r => ({\n\t\t\t\t\tisCodeOwner: r.isCodeOwner,\n\t\t\t\t\treviewer: fromProviderAccount(r.reviewer),\n\t\t\t\t\tstate: PullRequestReviewState.ReviewRequested,\n\t\t\t\t\t...(r.isMyGroup ? { isMyGroup: true } : {}),\n\t\t\t\t}));\n}\n\nexport function toCompletedReviews(reviews: ProviderPullRequestReviews): PullRequestReviewer[] | undefined {\n\treturn reviews == null\n\t\t? undefined\n\t\t: reviews\n\t\t\t\t?.filter(\n\t\t\t\t\tr =>\n\t\t\t\t\t\tr.state !== GitPullRequestReviewState.ReviewRequested &&\n\t\t\t\t\t\t// provider-apis' own GitHub normalizer maps only its four known states, so a review it has no\n\t\t\t\t\t\t// member for (a real `DISMISSED` one, which its `latestReviews(first: 100)` selection does\n\t\t\t\t\t\t// return) arrives with `state: undefined` — a value its type says cannot happen. Publishing it\n\t\t\t\t\t\t// would put an unswitchable state on `PullRequestShape.latestReviews`, and mapping back would\n\t\t\t\t\t\t// hit `toProviderReviews`' fallback and report a dismissed review as an outstanding request.\n\t\t\t\t\t\t// Our own GitHub path never lands here: it carries `providerPullRequestReviewStateDismissed`.\n\t\t\t\t\t\tfromProviderPullRequestReviewState[r.state] != null,\n\t\t\t\t)\n\t\t\t\t.map(r => ({\n\t\t\t\t\tisCodeOwner: r.isCodeOwner,\n\t\t\t\t\treviewer: fromProviderAccount(r.reviewer),\n\t\t\t\t\tstate: fromProviderPullRequestReviewState[r.state],\n\t\t\t\t\tcommitOid: r.commitOid,\n\t\t\t\t\t...(r.isMyGroup ? { isMyGroup: true } : {}),\n\t\t\t\t}));\n}\n\nexport function toProviderReviewDecision(\n\treviewDecision?: PullRequestReviewDecision,\n\treviewers?: PullRequestReviewer[],\n): GitPullRequestReviewState | null {\n\tswitch (reviewDecision) {\n\t\tcase PullRequestReviewDecision.Approved:\n\t\t\treturn GitPullRequestReviewState.Approved;\n\t\tcase PullRequestReviewDecision.ChangesRequested:\n\t\t\treturn GitPullRequestReviewState.ChangesRequested;\n\t\tcase PullRequestReviewDecision.ReviewRequired:\n\t\t\treturn GitPullRequestReviewState.ReviewRequested;\n\t\tdefault: {\n\t\t\tif (reviewers?.some(r => r.state === PullRequestReviewState.ReviewRequested)) {\n\t\t\t\treturn GitPullRequestReviewState.ReviewRequested;\n\t\t\t} else if (reviewers?.some(r => r.state === PullRequestReviewState.Commented)) {\n\t\t\t\treturn GitPullRequestReviewState.Commented;\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\t}\n}\n\nexport const fromPullRequestReviewDecision = {\n\t[GitPullRequestReviewState.Approved]: PullRequestReviewDecision.Approved,\n\t[GitPullRequestReviewState.ChangesRequested]: PullRequestReviewDecision.ChangesRequested,\n\t[GitPullRequestReviewState.Commented]: undefined,\n\t[GitPullRequestReviewState.ReviewRequested]: PullRequestReviewDecision.ReviewRequired,\n};\n\n/** provider-apis' (0.61.0) review severity (`Us` in its bundle), by which it picks a review decision. */\nconst providerReviewDecisionSeverity: Partial<Record<string, number>> = {\n\t[GitPullRequestReviewState.Approved]: 0,\n\t[GitPullRequestReviewState.Commented]: 1,\n\t[GitPullRequestReviewState.ReviewRequested]: 2,\n\t[GitPullRequestReviewState.ChangesRequested]: 3,\n};\n\n/**\n * The review decision provider-apis' GitLab and Azure DevOps mappers derive from a pull request's review states (`ne`\n * in its bundle): the most severe state wins, starting from approved, and no states at all is no decision. A state it\n * doesn't rank (`undefined` here, for one provider-apis doesn't know) never outranks the decision so far. For a cheap\n * etag check, which must reproduce a full row's decision without provider-apis' mapping.\n */\nexport function decideProviderReviewDecision(\n\tstates: readonly (GitPullRequestReviewState | undefined)[] | undefined,\n): GitPullRequestReviewState | undefined {\n\tif (!states?.length) return undefined;\n\n\treturn states.reduce<GitPullRequestReviewState>(\n\t\t(decided, state) =>\n\t\t\tstate != null &&\n\t\t\t(providerReviewDecisionSeverity[state] ?? -1) > (providerReviewDecisionSeverity[decided] ?? -1)\n\t\t\t\t? state\n\t\t\t\t: decided,\n\t\tGitPullRequestReviewState.Approved,\n\t);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAmBA,MAAa,0CAA0C;AAcvD,MAAa,mCAAmC;EACZ,aAAA,0BAA0B;EAClB,sBAAA,0BAA0B;EACjC,cAAA,0BAA0B;EACpB,qBAAA,0BAA0B;EAChC,cAAA;EAGF,YAAA;AACnC;AAEA,MAAa,qCAAqC;EAChD,0BAA0B,WAAA;EAC1B,0BAA0B,mBAAA;EAC1B,0BAA0B,YAAA;EAC1B,0BAA0B,kBAAA;EAC1B,0CAAA;AACF;AAEA,SAAgB,kBAAkB,WAA8D;CAI/F,OAAO,UACL,QAAO,MAAK,EAAE,UAAA,SAAwC,CAAC,CACvD,KAAI,cAAa;EACjB,UAAU,kBAAkB,SAAS,QAAQ;EAC7C,OAAO,iCAAiC,SAAS,UAAU,0BAA0B;EACrF,WAAW,SAAS;EACpB,aAAa,SAAS;EACtB,GAAI,SAAS,YAAY,EAAE,WAAW,KAAK,IAAI,CAAC;CACjD,EAAE;AACJ;AAEA,SAAgB,iBAAiB,SAAwE;CACxG,OAAO,WAAW,OACf,KAAA,IACA,SACE,QAAO,MAAK,EAAE,UAAU,0BAA0B,eAAe,CAAC,CACnE,KAAI,OAAM;EACV,aAAa,EAAE;EACf,UAAU,oBAAoB,EAAE,QAAQ;EACxC,OAAA;EACA,GAAI,EAAE,YAAY,EAAE,WAAW,KAAK,IAAI,CAAC;CAC1C,EAAE;AACN;AAEA,SAAgB,mBAAmB,SAAwE;CAC1G,OAAO,WAAW,OACf,KAAA,IACA,SACE,QACD,MACC,EAAE,UAAU,0BAA0B,mBAOtC,mCAAmC,EAAE,UAAU,IACjD,CAAC,CACA,KAAI,OAAM;EACV,aAAa,EAAE;EACf,UAAU,oBAAoB,EAAE,QAAQ;EACxC,OAAO,mCAAmC,EAAE;EAC5C,WAAW,EAAE;EACb,GAAI,EAAE,YAAY,EAAE,WAAW,KAAK,IAAI,CAAC;CAC1C,EAAE;AACN;AAEA,SAAgB,yBACf,gBACA,WACmC;CACnC,QAAQ,gBAAR;EACC,KAAA,YACC,OAAO,0BAA0B;EAClC,KAAA,oBACC,OAAO,0BAA0B;EAClC,KAAA,kBACC,OAAO,0BAA0B;EAClC;GACC,IAAI,WAAW,MAAK,MAAK,EAAE,UAAA,kBAAgD,GAC1E,OAAO,0BAA0B;QAC3B,IAAI,WAAW,MAAK,MAAK,EAAE,UAAA,WAA0C,GAC3E,OAAO,0BAA0B;GAElC,OAAO;CAET;AACD;AAEA,MAAa,gCAAgC;EAC3C,0BAA0B,WAAA;EAC1B,0BAA0B,mBAAA;EAC1B,0BAA0B,YAAY,KAAA;EACtC,0BAA0B,kBAAA;AAC5B;;AAGA,MAAM,iCAAkE;EACtE,0BAA0B,WAAW;EACrC,0BAA0B,YAAY;EACtC,0BAA0B,kBAAkB;EAC5C,0BAA0B,mBAAmB;AAC/C;;;;;;;AAQA,SAAgB,6BACf,QACwC;CACxC,IAAI,CAAC,QAAQ,QAAQ,OAAO,KAAA;CAE5B,OAAO,OAAO,QACZ,SAAS,UACT,SAAS,SACR,+BAA+B,UAAU,OAAO,+BAA+B,YAAY,MACzF,QACA,SACJ,0BAA0B,QAC3B;AACD"}
@@ -90,7 +90,7 @@ var TrelloIntegration = class extends IssuesIntegration {
90
90
  sort: options?.sort
91
91
  });
92
92
  const values = result.values.flatMap((issue) => {
93
- const mapped = toIssueShape(issue, this);
93
+ const mapped = toIssueShape(issue, this, { projection: "project" });
94
94
  return mapped == null ? [] : [{
95
95
  ...mapped,
96
96
  project: boardProject
@@ -124,12 +124,15 @@ var TrelloIntegration = class extends IssuesIntegration {
124
124
  }, {});
125
125
  let issue = await api.getTrelloCard(tokenWithInfo, appKey, id, { trelloBoardListsById });
126
126
  if (issue == null && /^[0-9]+$/.test(id)) issue = (await api.getTrelloIssuesForBoard(tokenWithInfo, appKey, resource.id, { trelloBoardListsById })).values.find((issue) => issue.number === id);
127
- return issue != null ? fromProviderIssue(issue, this, { project: {
128
- id: resource.id,
129
- name: resource.name,
130
- resourceId: resource.id,
131
- resourceName: resource.name
132
- } }) : void 0;
127
+ return issue != null ? fromProviderIssue(issue, this, {
128
+ projection: "point",
129
+ project: {
130
+ id: resource.id,
131
+ name: resource.name,
132
+ resourceId: resource.id,
133
+ resourceName: resource.name
134
+ }
135
+ }) : void 0;
133
136
  }
134
137
  };
135
138
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"trello.js","names":[],"sources":["../../../../../plus/integrations/src/providers/trello.ts"],"sourcesContent":["import type { Account } from '@gitlens/git/models/author.js';\nimport type { Issue, IssueShape } from '@gitlens/git/models/issue.js';\nimport type { IssueOrPullRequest, IssueOrPullRequestType } from '@gitlens/git/models/issueOrPullRequest.js';\nimport type { IssueResourceDescriptor, ResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport { isIssueResourceDescriptor } from '@gitlens/git/utils/resourceDescriptor.utils.js';\nimport type { IntegrationAuthenticationProviderDescriptor } from '../authentication/integrationAuthenticationProvider.js';\nimport type { ProviderAuthenticationSession } from '../authentication/models.js';\nimport { toTokenWithInfo } from '../authentication/models.js';\nimport { IssuesCloudHostIntegrationId } from '../constants.js';\nimport { IntegrationReadUnavailableError } from '../errors.js';\nimport type { IssuesForProjectOptions, ProjectIssuesDrain } from '../models/issueReads.js';\nimport { IssuesIntegration } from '../models/issuesIntegration.js';\nimport { fromProviderIssue, providersMetadata, toIssueShape } from './models.js';\n\nconst metadata = providersMetadata[IssuesCloudHostIntegrationId.Trello];\nconst authProvider = Object.freeze({ id: metadata.id, scopes: metadata.scopes });\n\n/** A Trello board, surfaced as both a \"resource\" (org analogue) and a \"project\" for issue reads. */\nexport interface TrelloBoardDescriptor extends IssueResourceDescriptor {}\n\nexport class TrelloIntegration extends IssuesIntegration<IssuesCloudHostIntegrationId.Trello> {\n\treadonly authProvider: IntegrationAuthenticationProviderDescriptor = authProvider;\n\n\toverride get id(): IssuesCloudHostIntegrationId.Trello {\n\t\treturn IssuesCloudHostIntegrationId.Trello;\n\t}\n\tprotected override get key(): 'trello' {\n\t\treturn 'trello';\n\t}\n\toverride get name(): string {\n\t\treturn metadata.name;\n\t}\n\toverride get domain(): string {\n\t\treturn metadata.domain;\n\t}\n\n\t/** The Trello client requires the app key paired with the token; a session without one can't read. */\n\tprivate appKeyFor(session: ProviderAuthenticationSession): string | undefined {\n\t\treturn session.appKey;\n\t}\n\n\t/**\n\t * Resolves the app key or throws — a session that authenticated but carries no app key can't read Trello,\n\t * and must be surfaced as a broken read (warning + fetchFailed) rather than an empty account.\n\t */\n\tprivate requireAppKey(session: ProviderAuthenticationSession): string {\n\t\tconst appKey = this.appKeyFor(session);\n\t\tif (appKey == null) {\n\t\t\tthrow new IntegrationReadUnavailableError(metadata.name, 'missing app key (session has no appKey)');\n\t\t}\n\t\treturn appKey;\n\t}\n\n\tprotected override async getProviderAccountForResource(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_resource: ResourceDescriptor,\n\t): Promise<Account | undefined> {\n\t\tconst appKey = this.requireAppKey(session);\n\n\t\tconst api = await this.getProvidersApi();\n\t\tconst user = await api.getTrelloCurrentUser(toTokenWithInfo(this.id, session), appKey);\n\t\tif (user == null) return undefined;\n\n\t\treturn {\n\t\t\tprovider: this,\n\t\t\tid: user.id,\n\t\t\tname: user.name,\n\t\t\tusername: user.username,\n\t\t\temail: user.email,\n\t\t\tavatarUrl: user.avatarUrl ?? undefined,\n\t\t};\n\t}\n\n\tprotected override async getProviderResourcesForUser(\n\t\tsession: ProviderAuthenticationSession,\n\t): Promise<TrelloBoardDescriptor[] | undefined> {\n\t\tconst appKey = this.requireAppKey(session);\n\n\t\tconst api = await this.getProvidersApi();\n\t\tconst boards = await api.getTrelloBoardsForCurrentUser(toTokenWithInfo(this.id, session), appKey);\n\t\treturn boards?.map(b => ({ key: b.id, id: b.id, name: b.name }));\n\t}\n\n\tprotected override getProviderProjectsForResources(\n\t\t_session: ProviderAuthenticationSession,\n\t\tresources: ResourceDescriptor[],\n\t): Promise<TrelloBoardDescriptor[] | undefined> {\n\t\t// Trello boards are both the resource and the project scope, so projects are the boards themselves.\n\t\treturn Promise.resolve(\n\t\t\tresources.filter(isIssueResourceDescriptor).map(r => ({ key: r.key, id: r.id, name: r.name })),\n\t\t);\n\t}\n\n\tprotected override async getProviderIssuesForProject(\n\t\tsession: ProviderAuthenticationSession,\n\t\tproject: ResourceDescriptor,\n\t\toptions?: IssuesForProjectOptions,\n\t): Promise<IssueShape[] | undefined> {\n\t\treturn (await this.getProviderIssuesForProjectWithTruncation(session, project, options))?.values;\n\t}\n\n\tprotected override async getProviderIssuesForProjectWithTruncation(\n\t\tsession: ProviderAuthenticationSession,\n\t\tproject: ResourceDescriptor,\n\t\toptions?: IssuesForProjectOptions,\n\t): Promise<ProjectIssuesDrain | undefined> {\n\t\t// A non-issue descriptor genuinely has nothing to read (empty), but a missing app key is a broken read.\n\t\tif (!isIssueResourceDescriptor(project)) return undefined;\n\n\t\t// The board read always excludes archived cards, and a card has no other state, so only open cards can be\n\t\t// read. Refused rather than ignored: answering `closed` with the open cards would publish them as closed.\n\t\tif (options?.state != null && options.state !== 'open') {\n\t\t\tthrow new IntegrationReadUnavailableError(metadata.name, `issue state '${options.state}' is not supported`);\n\t\t}\n\n\t\tconst appKey = this.requireAppKey(session);\n\n\t\tconst api = await this.getProvidersApi();\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\n\t\t// Enrich issues with their board list names (card status), mirroring gitkraken.dev's Trello reads.\n\t\tconst lists = await api.getTrelloListsForBoard(tokenWithInfo, appKey, project.id);\n\t\tconst trelloBoardListsById = lists?.reduce<Record<string, { name: string }>>((acc, list) => {\n\t\t\tacc[list.id] = { name: list.name };\n\t\t\treturn acc;\n\t\t}, {});\n\t\tconst boardProject = {\n\t\t\tid: project.id,\n\t\t\tname: project.name,\n\t\t\tresourceId: project.id,\n\t\t\tresourceName: project.name,\n\t\t};\n\n\t\tconst result = await api.getTrelloIssuesForBoard(tokenWithInfo, appKey, project.id, {\n\t\t\tassigneeLogins: options?.user != null ? [options.user] : undefined,\n\t\t\ttrelloBoardListsById: trelloBoardListsById,\n\t\t\tsort: options?.sort,\n\t\t});\n\n\t\tconst values = result.values.flatMap(issue => {\n\t\t\tconst mapped = toIssueShape(issue, this);\n\t\t\treturn mapped == null ? [] : [{ ...mapped, project: boardProject } satisfies IssueShape];\n\t\t});\n\n\t\t// Trello's search caps results and reports the cap through `metadata.completeness` (partial/unknown),\n\t\t// never a cursor. Surface that as terminal truncation; there is no next page to fetch, so retrying the\n\t\t// same read cannot recover the omitted cards (D11).\n\t\tconst truncated = result.metadata != null && result.metadata.completeness !== 'complete';\n\t\treturn truncated\n\t\t\t? { values: values, truncated: true, recovery: 'none', metadata: result.metadata }\n\t\t\t: { values: values, truncated: false, metadata: result.metadata };\n\t}\n\n\tprotected override searchProviderMyIssues(\n\t\t_session: ProviderAuthenticationSession,\n\t\t_resources?: ResourceDescriptor[],\n\t\t_cancellation?: AbortSignal,\n\t): Promise<IssueShape[] | undefined> {\n\t\t// Trello has no cross-board \"issues assigned to me\" endpoint; issues are read per board via\n\t\t// getIssuesForProject. Callers that need them enumerate boards first.\n\t\treturn Promise.resolve(undefined);\n\t}\n\n\tprotected override getProviderLinkedIssueOrPullRequest(\n\t\t_session: ProviderAuthenticationSession,\n\t\t_resource: ResourceDescriptor,\n\t\t_id: { id: string; key: string },\n\t\t_type: undefined | IssueOrPullRequestType,\n\t): Promise<IssueOrPullRequest | undefined> {\n\t\treturn Promise.resolve(undefined);\n\t}\n\n\tprotected override async getProviderIssue(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: ResourceDescriptor,\n\t\tid: string,\n\t): Promise<Issue | undefined> {\n\t\tif (!isIssueResourceDescriptor(resource)) return undefined;\n\n\t\tconst appKey = this.requireAppKey(session);\n\t\tconst api = await this.getProvidersApi();\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\t\tconst lists = await api.getTrelloListsForBoard(tokenWithInfo, appKey, resource.id);\n\t\tconst trelloBoardListsById = lists?.reduce<Record<string, { name: string }>>((acc, list) => {\n\t\t\tacc[list.id] = { name: list.name };\n\t\t\treturn acc;\n\t\t}, {});\n\n\t\t// Branch-association round-trips use the encoded entity identifier's stable Trello card id. Resolve that\n\t\t// directly so large boards don't require a capped whole-board scan; keep a numeric idShort fallback so\n\t\t// legacy/manual callers that still pass the board-local display number continue to resolve.\n\t\tlet issue = await api.getTrelloCard(tokenWithInfo, appKey, id, {\n\t\t\ttrelloBoardListsById: trelloBoardListsById,\n\t\t});\n\t\tif (issue == null && /^[0-9]+$/.test(id)) {\n\t\t\tissue = (\n\t\t\t\tawait api.getTrelloIssuesForBoard(tokenWithInfo, appKey, resource.id, {\n\t\t\t\t\ttrelloBoardListsById: trelloBoardListsById,\n\t\t\t\t})\n\t\t\t).values.find(issue => issue.number === id);\n\t\t}\n\n\t\treturn issue != null\n\t\t\t? fromProviderIssue(issue, this, {\n\t\t\t\t\tproject: {\n\t\t\t\t\t\tid: resource.id,\n\t\t\t\t\t\tname: resource.name,\n\t\t\t\t\t\tresourceId: resource.id,\n\t\t\t\t\t\tresourceName: resource.name,\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t: undefined;\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"trello.js","names":[],"sources":["../../../../../plus/integrations/src/providers/trello.ts"],"sourcesContent":["import type { Account } from '@gitlens/git/models/author.js';\nimport type { Issue, IssueShape } from '@gitlens/git/models/issue.js';\nimport type { IssueOrPullRequest, IssueOrPullRequestType } from '@gitlens/git/models/issueOrPullRequest.js';\nimport type { IssueResourceDescriptor, ResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport { isIssueResourceDescriptor } from '@gitlens/git/utils/resourceDescriptor.utils.js';\nimport type { IntegrationAuthenticationProviderDescriptor } from '../authentication/integrationAuthenticationProvider.js';\nimport type { ProviderAuthenticationSession } from '../authentication/models.js';\nimport { toTokenWithInfo } from '../authentication/models.js';\nimport { IssuesCloudHostIntegrationId } from '../constants.js';\nimport { IntegrationReadUnavailableError } from '../errors.js';\nimport type { IssuesForProjectOptions, ProjectIssuesDrain } from '../models/issueReads.js';\nimport { IssuesIntegration } from '../models/issuesIntegration.js';\nimport { fromProviderIssue, providersMetadata, toIssueShape } from './models.js';\n\nconst metadata = providersMetadata[IssuesCloudHostIntegrationId.Trello];\nconst authProvider = Object.freeze({ id: metadata.id, scopes: metadata.scopes });\n\n/** A Trello board, surfaced as both a \"resource\" (org analogue) and a \"project\" for issue reads. */\nexport interface TrelloBoardDescriptor extends IssueResourceDescriptor {}\n\nexport class TrelloIntegration extends IssuesIntegration<IssuesCloudHostIntegrationId.Trello> {\n\treadonly authProvider: IntegrationAuthenticationProviderDescriptor = authProvider;\n\n\toverride get id(): IssuesCloudHostIntegrationId.Trello {\n\t\treturn IssuesCloudHostIntegrationId.Trello;\n\t}\n\tprotected override get key(): 'trello' {\n\t\treturn 'trello';\n\t}\n\toverride get name(): string {\n\t\treturn metadata.name;\n\t}\n\toverride get domain(): string {\n\t\treturn metadata.domain;\n\t}\n\n\t/** The Trello client requires the app key paired with the token; a session without one can't read. */\n\tprivate appKeyFor(session: ProviderAuthenticationSession): string | undefined {\n\t\treturn session.appKey;\n\t}\n\n\t/**\n\t * Resolves the app key or throws — a session that authenticated but carries no app key can't read Trello,\n\t * and must be surfaced as a broken read (warning + fetchFailed) rather than an empty account.\n\t */\n\tprivate requireAppKey(session: ProviderAuthenticationSession): string {\n\t\tconst appKey = this.appKeyFor(session);\n\t\tif (appKey == null) {\n\t\t\tthrow new IntegrationReadUnavailableError(metadata.name, 'missing app key (session has no appKey)');\n\t\t}\n\t\treturn appKey;\n\t}\n\n\tprotected override async getProviderAccountForResource(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_resource: ResourceDescriptor,\n\t): Promise<Account | undefined> {\n\t\tconst appKey = this.requireAppKey(session);\n\n\t\tconst api = await this.getProvidersApi();\n\t\tconst user = await api.getTrelloCurrentUser(toTokenWithInfo(this.id, session), appKey);\n\t\tif (user == null) return undefined;\n\n\t\treturn {\n\t\t\tprovider: this,\n\t\t\tid: user.id,\n\t\t\tname: user.name,\n\t\t\tusername: user.username,\n\t\t\temail: user.email,\n\t\t\tavatarUrl: user.avatarUrl ?? undefined,\n\t\t};\n\t}\n\n\tprotected override async getProviderResourcesForUser(\n\t\tsession: ProviderAuthenticationSession,\n\t): Promise<TrelloBoardDescriptor[] | undefined> {\n\t\tconst appKey = this.requireAppKey(session);\n\n\t\tconst api = await this.getProvidersApi();\n\t\tconst boards = await api.getTrelloBoardsForCurrentUser(toTokenWithInfo(this.id, session), appKey);\n\t\treturn boards?.map(b => ({ key: b.id, id: b.id, name: b.name }));\n\t}\n\n\tprotected override getProviderProjectsForResources(\n\t\t_session: ProviderAuthenticationSession,\n\t\tresources: ResourceDescriptor[],\n\t): Promise<TrelloBoardDescriptor[] | undefined> {\n\t\t// Trello boards are both the resource and the project scope, so projects are the boards themselves.\n\t\treturn Promise.resolve(\n\t\t\tresources.filter(isIssueResourceDescriptor).map(r => ({ key: r.key, id: r.id, name: r.name })),\n\t\t);\n\t}\n\n\tprotected override async getProviderIssuesForProject(\n\t\tsession: ProviderAuthenticationSession,\n\t\tproject: ResourceDescriptor,\n\t\toptions?: IssuesForProjectOptions,\n\t): Promise<IssueShape[] | undefined> {\n\t\treturn (await this.getProviderIssuesForProjectWithTruncation(session, project, options))?.values;\n\t}\n\n\tprotected override async getProviderIssuesForProjectWithTruncation(\n\t\tsession: ProviderAuthenticationSession,\n\t\tproject: ResourceDescriptor,\n\t\toptions?: IssuesForProjectOptions,\n\t): Promise<ProjectIssuesDrain | undefined> {\n\t\t// A non-issue descriptor genuinely has nothing to read (empty), but a missing app key is a broken read.\n\t\tif (!isIssueResourceDescriptor(project)) return undefined;\n\n\t\t// The board read always excludes archived cards, and a card has no other state, so only open cards can be\n\t\t// read. Refused rather than ignored: answering `closed` with the open cards would publish them as closed.\n\t\tif (options?.state != null && options.state !== 'open') {\n\t\t\tthrow new IntegrationReadUnavailableError(metadata.name, `issue state '${options.state}' is not supported`);\n\t\t}\n\n\t\tconst appKey = this.requireAppKey(session);\n\n\t\tconst api = await this.getProvidersApi();\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\n\t\t// Enrich issues with their board list names (card status), mirroring gitkraken.dev's Trello reads.\n\t\tconst lists = await api.getTrelloListsForBoard(tokenWithInfo, appKey, project.id);\n\t\tconst trelloBoardListsById = lists?.reduce<Record<string, { name: string }>>((acc, list) => {\n\t\t\tacc[list.id] = { name: list.name };\n\t\t\treturn acc;\n\t\t}, {});\n\t\tconst boardProject = {\n\t\t\tid: project.id,\n\t\t\tname: project.name,\n\t\t\tresourceId: project.id,\n\t\t\tresourceName: project.name,\n\t\t};\n\n\t\tconst result = await api.getTrelloIssuesForBoard(tokenWithInfo, appKey, project.id, {\n\t\t\tassigneeLogins: options?.user != null ? [options.user] : undefined,\n\t\t\ttrelloBoardListsById: trelloBoardListsById,\n\t\t\tsort: options?.sort,\n\t\t});\n\n\t\tconst values = result.values.flatMap(issue => {\n\t\t\tconst mapped = toIssueShape(issue, this, { projection: 'project' });\n\t\t\treturn mapped == null ? [] : [{ ...mapped, project: boardProject } satisfies IssueShape];\n\t\t});\n\n\t\t// Trello's search caps results and reports the cap through `metadata.completeness` (partial/unknown),\n\t\t// never a cursor. Surface that as terminal truncation; there is no next page to fetch, so retrying the\n\t\t// same read cannot recover the omitted cards (D11).\n\t\tconst truncated = result.metadata != null && result.metadata.completeness !== 'complete';\n\t\treturn truncated\n\t\t\t? { values: values, truncated: true, recovery: 'none', metadata: result.metadata }\n\t\t\t: { values: values, truncated: false, metadata: result.metadata };\n\t}\n\n\tprotected override searchProviderMyIssues(\n\t\t_session: ProviderAuthenticationSession,\n\t\t_resources?: ResourceDescriptor[],\n\t\t_cancellation?: AbortSignal,\n\t): Promise<IssueShape[] | undefined> {\n\t\t// Trello has no cross-board \"issues assigned to me\" endpoint; issues are read per board via\n\t\t// getIssuesForProject. Callers that need them enumerate boards first.\n\t\treturn Promise.resolve(undefined);\n\t}\n\n\tprotected override getProviderLinkedIssueOrPullRequest(\n\t\t_session: ProviderAuthenticationSession,\n\t\t_resource: ResourceDescriptor,\n\t\t_id: { id: string; key: string },\n\t\t_type: undefined | IssueOrPullRequestType,\n\t): Promise<IssueOrPullRequest | undefined> {\n\t\treturn Promise.resolve(undefined);\n\t}\n\n\tprotected override async getProviderIssue(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: ResourceDescriptor,\n\t\tid: string,\n\t): Promise<Issue | undefined> {\n\t\tif (!isIssueResourceDescriptor(resource)) return undefined;\n\n\t\tconst appKey = this.requireAppKey(session);\n\t\tconst api = await this.getProvidersApi();\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\t\tconst lists = await api.getTrelloListsForBoard(tokenWithInfo, appKey, resource.id);\n\t\tconst trelloBoardListsById = lists?.reduce<Record<string, { name: string }>>((acc, list) => {\n\t\t\tacc[list.id] = { name: list.name };\n\t\t\treturn acc;\n\t\t}, {});\n\n\t\t// Branch-association round-trips use the encoded entity identifier's stable Trello card id. Resolve that\n\t\t// directly so large boards don't require a capped whole-board scan; keep a numeric idShort fallback so\n\t\t// legacy/manual callers that still pass the board-local display number continue to resolve.\n\t\tlet issue = await api.getTrelloCard(tokenWithInfo, appKey, id, {\n\t\t\ttrelloBoardListsById: trelloBoardListsById,\n\t\t});\n\t\tif (issue == null && /^[0-9]+$/.test(id)) {\n\t\t\tissue = (\n\t\t\t\tawait api.getTrelloIssuesForBoard(tokenWithInfo, appKey, resource.id, {\n\t\t\t\t\ttrelloBoardListsById: trelloBoardListsById,\n\t\t\t\t})\n\t\t\t).values.find(issue => issue.number === id);\n\t\t}\n\n\t\treturn issue != null\n\t\t\t? fromProviderIssue(issue, this, {\n\t\t\t\t\tprojection: 'point',\n\t\t\t\t\tproject: {\n\t\t\t\t\t\tid: resource.id,\n\t\t\t\t\t\tname: resource.name,\n\t\t\t\t\t\tresourceId: resource.id,\n\t\t\t\t\t\tresourceName: resource.name,\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t: undefined;\n\t}\n}\n"],"mappings":"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"}
@@ -53,6 +53,17 @@ export declare function resolveBranchPullRequests(targets: readonly {
53
53
  pullRequests: PullRequestShape[];
54
54
  truncated: boolean;
55
55
  }>[]>;
56
+ /**
57
+ * Runs a cheap etag read over `coordinates` with one request per repository and chunk of `chunkSize` numbers, with
58
+ * bounded concurrency, and maps the answers back to the coordinates positionally (`read` answers one entry per
59
+ * number, in order, `undefined` for a proven absence). Settled per target: a request that throws rejects only its
60
+ * own slots, and a slot whose conversion throws rejects only itself.
61
+ */
62
+ export declare function readEtagFieldsByRepository<Node, Fields>(coordinates: readonly {
63
+ owner: string;
64
+ repo: string;
65
+ number: number;
66
+ }[], chunkSize: number, read: (owner: string, repo: string, numbers: number[]) => Promise<(Node | undefined)[]>, map: (node: Node) => Fields): Promise<PromiseSettledResult<Fields | undefined>[]>;
56
67
  export declare function flatSettledResultsOrThrow<T>(results: PromiseSettledResult<T[]>[]): T[];
57
68
  export declare function flatSettledOrThrow<T>(promises: Promise<T[]>[]): Promise<T[]>;
58
69
  /**
@@ -1,5 +1,8 @@
1
1
  import { uniqueBy } from "../../../../utils/iterable.js";
2
+ import { chunk } from "../../../../utils/array.js";
2
3
  import { isCancellationError } from "../../../../utils/cancellation.js";
4
+ import { mapSettledBounded } from "../../../../utils/promise.js";
5
+ import "../../constants.js";
3
6
  import { collectionScopeKey } from "../../results.js";
4
7
  import { throwIfCallerContractError, toCollectionScopeFailure } from "../../collectionMetadata.js";
5
8
  //#region ../plus/integrations/src/providers/utils/providerPaging.ts
@@ -85,6 +88,49 @@ async function resolveBranchPullRequests(targets, found, resolve) {
85
88
  };
86
89
  });
87
90
  }
91
+ /**
92
+ * Runs a cheap etag read over `coordinates` with one request per repository and chunk of `chunkSize` numbers, with
93
+ * bounded concurrency, and maps the answers back to the coordinates positionally (`read` answers one entry per
94
+ * number, in order, `undefined` for a proven absence). Settled per target: a request that throws rejects only its
95
+ * own slots, and a slot whose conversion throws rejects only itself.
96
+ */
97
+ async function readEtagFieldsByRepository(coordinates, chunkSize, read, map) {
98
+ const byRepository = /* @__PURE__ */ new Map();
99
+ for (const [index, c] of coordinates.entries()) {
100
+ const key = `${c.owner}/${c.repo}`;
101
+ let indices = byRepository.get(key);
102
+ if (indices == null) {
103
+ indices = [];
104
+ byRepository.set(key, indices);
105
+ }
106
+ indices.push(index);
107
+ }
108
+ const requests = [...byRepository.values()].flatMap((indices) => chunk(indices, chunkSize).map((chunkIndices) => ({ indices: chunkIndices })));
109
+ const answers = await mapSettledBounded(requests, 6, (r) => {
110
+ const { owner, repo } = coordinates[r.indices[0]];
111
+ return read(owner, repo, r.indices.map((i) => coordinates[i].number));
112
+ });
113
+ const slots = new Array(coordinates.length);
114
+ for (const [i, answer] of answers.entries()) for (const [j, index] of requests[i].indices.entries()) {
115
+ if (answer.status === "rejected") {
116
+ slots[index] = answer;
117
+ continue;
118
+ }
119
+ const node = answer.value[j];
120
+ try {
121
+ slots[index] = {
122
+ status: "fulfilled",
123
+ value: node != null ? map(node) : void 0
124
+ };
125
+ } catch (ex) {
126
+ slots[index] = {
127
+ status: "rejected",
128
+ reason: ex
129
+ };
130
+ }
131
+ }
132
+ return slots;
133
+ }
88
134
  function flatSettledResultsOrThrow(results) {
89
135
  throwIfAllSettledFailed(results);
90
136
  return results.filter((result) => result.status === "fulfilled").flatMap((result) => result.value);
@@ -203,6 +249,6 @@ async function collectProviderPagedResult(fetch, maxPages = 20, scope) {
203
249
  return build();
204
250
  }
205
251
  //#endregion
206
- export { collectProviderPagedResult, flatSettledOrThrow, flatSettledResultsOrThrow, mergeCollectionMetadata, parsePageCursor, resolveBranchPullRequests, selectBranchPullRequests, throwIfAllSettledFailed, toPageCursor };
252
+ export { collectProviderPagedResult, flatSettledOrThrow, flatSettledResultsOrThrow, mergeCollectionMetadata, parsePageCursor, readEtagFieldsByRepository, resolveBranchPullRequests, selectBranchPullRequests, throwIfAllSettledFailed, toPageCursor };
207
253
 
208
254
  //# sourceMappingURL=providerPaging.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"providerPaging.js","names":[],"sources":["../../../../../../plus/integrations/src/providers/utils/providerPaging.ts"],"sourcesContent":["import type {\n\tCollectionCompleteness,\n\tCollectionMetadata,\n\tCollectionOmission,\n\tCollectionScopeFailure,\n} from '@gitkraken/provider-apis';\nimport type { PullRequestShape } from '@gitlens/git/models/pullRequest.js';\nimport { isCancellationError } from '@gitlens/utils/cancellation.js';\nimport { uniqueBy } from '@gitlens/utils/iterable.js';\nimport { throwIfCallerContractError, toCollectionScopeFailure } from '../../collectionMetadata.js';\nimport { collectionScopeKey } from '../../results.js';\nimport type { ProviderApiPagedResult, ProviderHierarchyResult } from '../models.js';\n\n/**\n * Encodes an opaque numeric paging token as the `{ value, type: 'page' }` cursor the paging layer uses.\n * The value is whatever the consumer round-trips unchanged: a 1-based page number for numbered-page reads,\n * or a provider offset (e.g. Bitbucket Server's `nextPageStart`) that is never reinterpreted here.\n */\nexport function toPageCursor(page: number): string {\n\treturn JSON.stringify({ value: page, type: 'page' });\n}\n\n/** Extracts the numeric paging token from a `{ value, type: 'page' }` cursor; undefined when absent/malformed. */\nexport function parsePageCursor(cursor: string | undefined): number | undefined {\n\tif (cursor == null || cursor === '{}') return undefined;\n\n\ttry {\n\t\tconst parsed = JSON.parse(cursor) as { value?: unknown; type?: unknown };\n\t\tif (parsed.type === 'page' && typeof parsed.value === 'number') return parsed.value;\n\t} catch {}\n\n\treturn undefined;\n}\n\n/** Preserves successful sibling scopes, but doesn't turn an all-scope provider failure into an empty success. */\nexport function throwIfAllSettledFailed<T>(results: PromiseSettledResult<T>[]): void {\n\tif (results.some(result => result.status === 'fulfilled')) return;\n\n\tconst rejected = results.find((result): result is PromiseRejectedResult => result.status === 'rejected');\n\tif (rejected != null) throw rejected.reason;\n}\n\n/**\n * The answer a pull-requests-by-branch read gives for one branch: the rows `matchesHead` accepts, newest first, at\n * most `limit` of them, each passed through `map`. Rows are filtered before they're mapped, so a non-matching row\n * can't fail the answer. `more` says the host holds rows it didn't return; the filter can't see those, so they make\n * the answer `truncated` however few rows passed it.\n */\nexport function selectBranchPullRequests<T, R>(\n\trows: readonly T[],\n\toptions: {\n\t\tmatchesHead: (row: T) => boolean;\n\t\tupdatedAt: (row: T) => number;\n\t\tmap: (row: T) => R;\n\t\tlimit: number;\n\t\tmore: boolean;\n\t},\n): { values: R[]; truncated: boolean } {\n\tconst matched = rows.filter(options.matchesHead).sort((a, b) => options.updatedAt(b) - options.updatedAt(a));\n\treturn {\n\t\tvalues: matched.slice(0, options.limit).map(options.map),\n\t\ttruncated: options.more || matched.length > options.limit,\n\t};\n}\n\n/**\n * The second step of a pull-requests-by-branch read on a host whose branch query can't return the batch read's\n * rows: resolves each target's matched numbers through `resolve` (the host's `getProviderPullRequestsBatch`), so a\n * pull request comes back identical whichever read found it.\n *\n * One `resolve` call for every target's numbers, so the host's own fan-out bounds the concurrency. A number it\n * reports absent (deleted between the two steps) is dropped; one it couldn't check rejects the whole target, since\n * a partial list would read as a complete one. The first step's order is kept.\n */\nexport async function resolveBranchPullRequests(\n\ttargets: readonly { owner: string; repo: string; project?: string }[],\n\tfound: readonly PromiseSettledResult<{ numbers: number[]; truncated: boolean }>[],\n\tresolve: (\n\t\tcoordinates: { owner: string; repo: string; number: number; project?: string }[],\n\t) => Promise<PromiseSettledResult<PullRequestShape | undefined>[] | undefined>,\n): Promise<PromiseSettledResult<{ pullRequests: PullRequestShape[]; truncated: boolean }>[]> {\n\tconst coordinates = found.flatMap((slot, i) =>\n\t\tslot.status === 'fulfilled'\n\t\t\t? slot.value.numbers.map(number => ({\n\t\t\t\t\towner: targets[i].owner,\n\t\t\t\t\trepo: targets[i].repo,\n\t\t\t\t\tnumber: number,\n\t\t\t\t\tproject: targets[i].project,\n\t\t\t\t}))\n\t\t\t: [],\n\t);\n\n\tlet resolved: PromiseSettledResult<PullRequestShape | undefined>[] = [];\n\tif (coordinates.length > 0) {\n\t\ttry {\n\t\t\tresolved =\n\t\t\t\t(await resolve(coordinates)) ??\n\t\t\t\tcoordinates.map(() => ({\n\t\t\t\t\tstatus: 'rejected',\n\t\t\t\t\treason: new Error('Pull requests could not be resolved'),\n\t\t\t\t}));\n\t\t} catch (ex) {\n\t\t\tresolved = coordinates.map(() => ({ status: 'rejected', reason: ex }));\n\t\t}\n\t}\n\n\tlet offset = 0;\n\treturn found.map(slot => {\n\t\tif (slot.status === 'rejected') return slot;\n\n\t\tconst slots = resolved.slice(offset, offset + slot.value.numbers.length);\n\t\toffset += slot.value.numbers.length;\n\n\t\tconst failure = slots.find((s): s is PromiseRejectedResult => s.status === 'rejected');\n\t\tif (failure != null) return failure;\n\n\t\treturn {\n\t\t\tstatus: 'fulfilled',\n\t\t\tvalue: {\n\t\t\t\tpullRequests: slots.flatMap(s => (s.status === 'fulfilled' && s.value != null ? [s.value] : [])),\n\t\t\t\ttruncated: slot.value.truncated,\n\t\t\t},\n\t\t};\n\t});\n}\n\nexport function flatSettledResultsOrThrow<T>(results: PromiseSettledResult<T[]>[]): T[] {\n\tthrowIfAllSettledFailed(results);\n\n\tconst fulfilled = results.filter((result): result is PromiseFulfilledResult<T[]> => result.status === 'fulfilled');\n\treturn fulfilled.flatMap(result => result.value);\n}\n\nexport async function flatSettledOrThrow<T>(promises: Promise<T[]>[]): Promise<T[]> {\n\treturn flatSettledResultsOrThrow(await Promise.allSettled(promises));\n}\n\n/** Precedence for merged completeness: any known omission (`partial`) wins, then inability to confirm\n * (`unknown`), and only an all-`complete` set of pages stays `complete`. */\nconst completenessRank: Record<CollectionCompleteness, number> = { partial: 2, unknown: 1, complete: 0 };\n\n/** A stable key for deduplicating structurally-identical scope failures accumulated across drained pages. */\nfunction collectionFailureKey(failure: CollectionScopeFailure): string {\n\treturn [failure.kind, collectionScopeKey(failure.scope), failure.message ?? ''].join(' ');\n}\n\n/**\n * A stable key for collapsing omissions accumulated across drained pages, matching the SDK's own\n * `dedupeOmissions`: kind plus scope IDs, deliberately WITHOUT `limit`/`totalCount`.\n *\n * Those counts are a re-measurement, not an identity. GitHub recomputes the match total on every request, so\n * one repository drained over several pages reports the same cap with a drifting total; keying on the total\n * would emit a near-identical warning per page (\"matched 1393…\", \"matched 1402…\") that\n * `appendDedupedWarning` cannot collapse, since the messages genuinely differ. Independently-scoped omissions\n * still stay distinct, which is the case that carries information.\n */\nfunction collectionOmissionKey(omission: CollectionOmission): string {\n\treturn [omission.kind, collectionScopeKey(omission.scope)].join(' ');\n}\n\n/**\n * Merges SDK collection metadata across drained pages. Completeness follows {@link completenessRank};\n * failures are deduplicated by kind, scope IDs, and message, and omissions are collapsed per kind and scope\n * keeping the highest reported total ({@link collectionOmissionKey}). Both preserve first-reported order.\n * Returns `undefined` when no page supplied metadata, so metadata-free providers and test doubles keep\n * behaving as before.\n */\nexport function mergeCollectionMetadata(\n\tbase: CollectionMetadata | undefined,\n\tnext: CollectionMetadata | undefined,\n): CollectionMetadata | undefined {\n\tif (base == null) return next;\n\tif (next == null) return base;\n\n\tconst completeness =\n\t\tcompletenessRank[next.completeness] > completenessRank[base.completeness]\n\t\t\t? next.completeness\n\t\t\t: base.completeness;\n\n\t// First occurrence wins: a repeated failure carries no new information, since the message is part of its key.\n\tconst failures = [\n\t\t...uniqueBy([...(base.failures ?? []), ...(next.failures ?? [])], collectionFailureKey, original => original),\n\t];\n\t// Highest total wins, so re-measuring one cap across pages collapses to a single omission holding the\n\t// largest figure reported for it — the same rule the SDK applies in `createCollectionMetadata`.\n\tconst omissions = [\n\t\t...uniqueBy(\n\t\t\t[...(base.omissions ?? []), ...(next.omissions ?? [])],\n\t\t\tcollectionOmissionKey,\n\t\t\t(original, current) => ((current.totalCount ?? -1) > (original.totalCount ?? -1) ? current : undefined),\n\t\t),\n\t];\n\n\treturn {\n\t\tcompleteness: completeness,\n\t\t...(failures.length ? { failures: failures } : {}),\n\t\t...(omissions.length ? { omissions: omissions } : {}),\n\t};\n}\n\n/**\n * Drains a provider paged fetcher into a single result while preserving enough metadata to\n * signal whether the defensive backstop interrupted the drain, and merging SDK collection metadata\n * ({@link mergeCollectionMetadata}) across the fetched pages.\n *\n * The local `truncated` flag and SDK `metadata` are distinct facts: a page-drain backstop must remain visible\n * even if every fetched page reported `complete`, and SDK incompleteness is preserved even when the drain\n * finished within its page budget.\n */\nexport async function collectProviderPagedResult<T>(\n\tfetch: (cursor: string | undefined) => Promise<ProviderApiPagedResult<T> | undefined>,\n\tmaxPages = 20,\n\tscope?: CollectionScopeFailure['scope'],\n): Promise<ProviderHierarchyResult<T>> {\n\tconst values: NonNullable<T>[] = [];\n\tlet cursor: string | undefined;\n\tlet metadata: CollectionMetadata | undefined;\n\tlet truncated = false;\n\n\t// Omit `metadata` entirely when no page supplied it, so a metadata-free drain stays deep-equal to its\n\t// pre-metadata shape (and consumers never see an explicit `undefined`).\n\tconst build = (extra?: Partial<ProviderHierarchyResult<T>>): ProviderHierarchyResult<T> => {\n\t\tconst mergedMetadata = extra?.metadata ?? metadata;\n\t\treturn {\n\t\t\tvalues: values,\n\t\t\t...extra,\n\t\t\t...(truncated || extra?.truncated === true ? { truncated: true } : {}),\n\t\t\t...(mergedMetadata != null ? { metadata: mergedMetadata } : {}),\n\t\t};\n\t};\n\n\tfor (let page = 0; page < maxPages; page++) {\n\t\tlet result: ProviderApiPagedResult<T> | undefined;\n\t\ttry {\n\t\t\tresult = await fetch(cursor);\n\t\t} catch (ex) {\n\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t// A caller-contract error is not a fact about this scope, so it is never recorded as one — see\n\t\t\t// `throwIfCallerContractError`. Checked alongside cancellation because both are errors that a\n\t\t\t// per-scope failure would misdescribe.\n\t\t\tthrowIfCallerContractError(ex);\n\n\t\t\t// When the caller supplied a scope, preserve the items already fetched from that scope and record the\n\t\t\t// failure in collection metadata rather than re-throwing and discarding the prefix. Callers without a\n\t\t\t// scope keep the legacy throw behavior.\n\t\t\tif (scope == null) throw ex;\n\t\t\treturn build({\n\t\t\t\ttruncated: true,\n\t\t\t\tmetadata: mergeCollectionMetadata(metadata, {\n\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\tfailures: [toCollectionScopeFailure(scope, ex)],\n\t\t\t\t}),\n\t\t\t});\n\t\t}\n\t\tif (result == null) {\n\t\t\tif (page === 0) return build();\n\n\t\t\treturn build({\n\t\t\t\ttruncated: true,\n\t\t\t\t...(scope != null\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tmetadata: mergeCollectionMetadata(metadata, {\n\t\t\t\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\t\t\t\tfailures: [\n\t\t\t\t\t\t\t\t\ttoCollectionScopeFailure(\n\t\t\t\t\t\t\t\t\t\tscope,\n\t\t\t\t\t\t\t\t\t\tnew Error('Provider returned no page after advertising a continuation'),\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t}\n\t\t\t\t\t: undefined),\n\t\t\t});\n\t\t}\n\n\t\tvalues.push(...result.values);\n\t\tmetadata = mergeCollectionMetadata(metadata, result.metadata);\n\t\ttruncated ||= result.paging?.truncated === true;\n\n\t\tif (!result.paging?.more) return build();\n\n\t\tif (result.paging.cursor === cursor) {\n\t\t\treturn build({ truncated: true });\n\t\t}\n\n\t\tcursor = result.paging.cursor;\n\t\tif (cursor == null || cursor === '{}') {\n\t\t\treturn build({ truncated: true });\n\t\t}\n\t\tif (page === maxPages - 1) {\n\t\t\treturn build({ paging: result.paging, truncated: true });\n\t\t}\n\t}\n\n\treturn build();\n}\n"],"mappings":";;;;;;;;;;AAkBA,SAAgB,aAAa,MAAsB;CAClD,OAAO,KAAK,UAAU;EAAE,OAAO;EAAM,MAAM;CAAO,CAAC;AACpD;;AAGA,SAAgB,gBAAgB,QAAgD;CAC/E,IAAI,UAAU,QAAQ,WAAW,MAAM,OAAO,KAAA;CAE9C,IAAI;EACH,MAAM,SAAS,KAAK,MAAM,MAAM;EAChC,IAAI,OAAO,SAAS,UAAU,OAAO,OAAO,UAAU,UAAU,OAAO,OAAO;CAC/E,QAAQ,CAAC;AAGV;;AAGA,SAAgB,wBAA2B,SAA0C;CACpF,IAAI,QAAQ,MAAK,WAAU,OAAO,WAAW,WAAW,GAAG;CAE3D,MAAM,WAAW,QAAQ,MAAM,WAA4C,OAAO,WAAW,UAAU;CACvG,IAAI,YAAY,MAAM,MAAM,SAAS;AACtC;;;;;;;AAQA,SAAgB,yBACf,MACA,SAOsC;CACtC,MAAM,UAAU,KAAK,OAAO,QAAQ,WAAW,CAAC,CAAC,MAAM,GAAG,MAAM,QAAQ,UAAU,CAAC,IAAI,QAAQ,UAAU,CAAC,CAAC;CAC3G,OAAO;EACN,QAAQ,QAAQ,MAAM,GAAG,QAAQ,KAAK,CAAC,CAAC,IAAI,QAAQ,GAAG;EACvD,WAAW,QAAQ,QAAQ,QAAQ,SAAS,QAAQ;CACrD;AACD;;;;;;;;;;AAWA,eAAsB,0BACrB,SACA,OACA,SAG4F;CAC5F,MAAM,cAAc,MAAM,SAAS,MAAM,MACxC,KAAK,WAAW,cACb,KAAK,MAAM,QAAQ,KAAI,YAAW;EAClC,OAAO,QAAQ,EAAE,CAAC;EAClB,MAAM,QAAQ,EAAE,CAAC;EACT;EACR,SAAS,QAAQ,EAAE,CAAC;CACrB,EAAE,IACD,CAAC,CACL;CAEA,IAAI,WAAiE,CAAC;CACtE,IAAI,YAAY,SAAS,GACxB,IAAI;EACH,WACE,MAAM,QAAQ,WAAW,KAC1B,YAAY,WAAW;GACtB,QAAQ;GACR,wBAAQ,IAAI,MAAM,qCAAqC;EACxD,EAAE;CACJ,SAAS,IAAI;EACZ,WAAW,YAAY,WAAW;GAAE,QAAQ;GAAY,QAAQ;EAAG,EAAE;CACtE;CAGD,IAAI,SAAS;CACb,OAAO,MAAM,KAAI,SAAQ;EACxB,IAAI,KAAK,WAAW,YAAY,OAAO;EAEvC,MAAM,QAAQ,SAAS,MAAM,QAAQ,SAAS,KAAK,MAAM,QAAQ,MAAM;EACvE,UAAU,KAAK,MAAM,QAAQ;EAE7B,MAAM,UAAU,MAAM,MAAM,MAAkC,EAAE,WAAW,UAAU;EACrF,IAAI,WAAW,MAAM,OAAO;EAE5B,OAAO;GACN,QAAQ;GACR,OAAO;IACN,cAAc,MAAM,SAAQ,MAAM,EAAE,WAAW,eAAe,EAAE,SAAS,OAAO,CAAC,EAAE,KAAK,IAAI,CAAC,CAAE;IAC/F,WAAW,KAAK,MAAM;GACvB;EACD;CACD,CAAC;AACF;AAEA,SAAgB,0BAA6B,SAA2C;CACvF,wBAAwB,OAAO;CAG/B,OADkB,QAAQ,QAAQ,WAAkD,OAAO,WAAW,WACvF,CAAC,CAAC,SAAQ,WAAU,OAAO,KAAK;AAChD;AAEA,eAAsB,mBAAsB,UAAwC;CACnF,OAAO,0BAA0B,MAAM,QAAQ,WAAW,QAAQ,CAAC;AACpE;;;AAIA,MAAM,mBAA2D;CAAE,SAAS;CAAG,SAAS;CAAG,UAAU;AAAE;;AAGvG,SAAS,qBAAqB,SAAyC;CACtE,OAAO;EAAC,QAAQ;EAAM,mBAAmB,QAAQ,KAAK;EAAG,QAAQ,WAAW;CAAE,CAAC,CAAC,KAAK,GAAG;AACzF;;;;;;;;;;;AAYA,SAAS,sBAAsB,UAAsC;CACpE,OAAO,CAAC,SAAS,MAAM,mBAAmB,SAAS,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG;AACpE;;;;;;;;AASA,SAAgB,wBACf,MACA,MACiC;CACjC,IAAI,QAAQ,MAAM,OAAO;CACzB,IAAI,QAAQ,MAAM,OAAO;CAEzB,MAAM,eACL,iBAAiB,KAAK,gBAAgB,iBAAiB,KAAK,gBACzD,KAAK,eACL,KAAK;CAGT,MAAM,WAAW,CAChB,GAAG,SAAS,CAAC,GAAI,KAAK,YAAY,CAAC,GAAI,GAAI,KAAK,YAAY,CAAC,CAAE,GAAG,uBAAsB,aAAY,QAAQ,CAC7G;CAGA,MAAM,YAAY,CACjB,GAAG,SACF,CAAC,GAAI,KAAK,aAAa,CAAC,GAAI,GAAI,KAAK,aAAa,CAAC,CAAE,GACrD,wBACC,UAAU,aAAc,QAAQ,cAAc,OAAO,SAAS,cAAc,MAAM,UAAU,KAAA,CAC9F,CACD;CAEA,OAAO;EACQ;EACd,GAAI,SAAS,SAAS,EAAY,SAAS,IAAI,CAAC;EAChD,GAAI,UAAU,SAAS,EAAa,UAAU,IAAI,CAAC;CACpD;AACD;;;;;;;;;;AAWA,eAAsB,2BACrB,OACA,WAAW,IACX,OACsC;CACtC,MAAM,SAA2B,CAAC;CAClC,IAAI;CACJ,IAAI;CACJ,IAAI,YAAY;CAIhB,MAAM,SAAS,UAA4E;EAC1F,MAAM,iBAAiB,OAAO,YAAY;EAC1C,OAAO;GACE;GACR,GAAG;GACH,GAAI,aAAa,OAAO,cAAc,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;GACpE,GAAI,kBAAkB,OAAO,EAAE,UAAU,eAAe,IAAI,CAAC;EAC9D;CACD;CAEA,KAAK,IAAI,OAAO,GAAG,OAAO,UAAU,QAAQ;EAC3C,IAAI;EACJ,IAAI;GACH,SAAS,MAAM,MAAM,MAAM;EAC5B,SAAS,IAAI;GACZ,IAAI,oBAAoB,EAAE,GAAG,MAAM;GAKnC,2BAA2B,EAAE;GAK7B,IAAI,SAAS,MAAM,MAAM;GACzB,OAAO,MAAM;IACZ,WAAW;IACX,UAAU,wBAAwB,UAAU;KAC3C,cAAc;KACd,UAAU,CAAC,yBAAyB,OAAO,EAAE,CAAC;IAC/C,CAAC;GACF,CAAC;EACF;EACA,IAAI,UAAU,MAAM;GACnB,IAAI,SAAS,GAAG,OAAO,MAAM;GAE7B,OAAO,MAAM;IACZ,WAAW;IACX,GAAI,SAAS,OACV,EACA,UAAU,wBAAwB,UAAU;KAC3C,cAAc;KACd,UAAU,CACT,yBACC,uBACA,IAAI,MAAM,4DAA4D,CACvE,CACD;IACD,CAAC,EACF,IACC,KAAA;GACJ,CAAC;EACF;EAEA,OAAO,KAAK,GAAG,OAAO,MAAM;EAC5B,WAAW,wBAAwB,UAAU,OAAO,QAAQ;EAC5D,cAAc,OAAO,QAAQ,cAAc;EAE3C,IAAI,CAAC,OAAO,QAAQ,MAAM,OAAO,MAAM;EAEvC,IAAI,OAAO,OAAO,WAAW,QAC5B,OAAO,MAAM,EAAE,WAAW,KAAK,CAAC;EAGjC,SAAS,OAAO,OAAO;EACvB,IAAI,UAAU,QAAQ,WAAW,MAChC,OAAO,MAAM,EAAE,WAAW,KAAK,CAAC;EAEjC,IAAI,SAAS,WAAW,GACvB,OAAO,MAAM;GAAE,QAAQ,OAAO;GAAQ,WAAW;EAAK,CAAC;CAEzD;CAEA,OAAO,MAAM;AACd"}
1
+ {"version":3,"file":"providerPaging.js","names":[],"sources":["../../../../../../plus/integrations/src/providers/utils/providerPaging.ts"],"sourcesContent":["import type {\n\tCollectionCompleteness,\n\tCollectionMetadata,\n\tCollectionOmission,\n\tCollectionScopeFailure,\n} from '@gitkraken/provider-apis';\nimport type { PullRequestShape } from '@gitlens/git/models/pullRequest.js';\nimport { chunk } from '@gitlens/utils/array.js';\nimport { isCancellationError } from '@gitlens/utils/cancellation.js';\nimport { uniqueBy } from '@gitlens/utils/iterable.js';\nimport { mapSettledBounded } from '@gitlens/utils/promise.js';\nimport { throwIfCallerContractError, toCollectionScopeFailure } from '../../collectionMetadata.js';\nimport { providerFanOutConcurrency } from '../../constants.js';\nimport { collectionScopeKey } from '../../results.js';\nimport type { ProviderApiPagedResult, ProviderHierarchyResult } from '../models.js';\n\n/**\n * Encodes an opaque numeric paging token as the `{ value, type: 'page' }` cursor the paging layer uses.\n * The value is whatever the consumer round-trips unchanged: a 1-based page number for numbered-page reads,\n * or a provider offset (e.g. Bitbucket Server's `nextPageStart`) that is never reinterpreted here.\n */\nexport function toPageCursor(page: number): string {\n\treturn JSON.stringify({ value: page, type: 'page' });\n}\n\n/** Extracts the numeric paging token from a `{ value, type: 'page' }` cursor; undefined when absent/malformed. */\nexport function parsePageCursor(cursor: string | undefined): number | undefined {\n\tif (cursor == null || cursor === '{}') return undefined;\n\n\ttry {\n\t\tconst parsed = JSON.parse(cursor) as { value?: unknown; type?: unknown };\n\t\tif (parsed.type === 'page' && typeof parsed.value === 'number') return parsed.value;\n\t} catch {}\n\n\treturn undefined;\n}\n\n/** Preserves successful sibling scopes, but doesn't turn an all-scope provider failure into an empty success. */\nexport function throwIfAllSettledFailed<T>(results: PromiseSettledResult<T>[]): void {\n\tif (results.some(result => result.status === 'fulfilled')) return;\n\n\tconst rejected = results.find((result): result is PromiseRejectedResult => result.status === 'rejected');\n\tif (rejected != null) throw rejected.reason;\n}\n\n/**\n * The answer a pull-requests-by-branch read gives for one branch: the rows `matchesHead` accepts, newest first, at\n * most `limit` of them, each passed through `map`. Rows are filtered before they're mapped, so a non-matching row\n * can't fail the answer. `more` says the host holds rows it didn't return; the filter can't see those, so they make\n * the answer `truncated` however few rows passed it.\n */\nexport function selectBranchPullRequests<T, R>(\n\trows: readonly T[],\n\toptions: {\n\t\tmatchesHead: (row: T) => boolean;\n\t\tupdatedAt: (row: T) => number;\n\t\tmap: (row: T) => R;\n\t\tlimit: number;\n\t\tmore: boolean;\n\t},\n): { values: R[]; truncated: boolean } {\n\tconst matched = rows.filter(options.matchesHead).sort((a, b) => options.updatedAt(b) - options.updatedAt(a));\n\treturn {\n\t\tvalues: matched.slice(0, options.limit).map(options.map),\n\t\ttruncated: options.more || matched.length > options.limit,\n\t};\n}\n\n/**\n * The second step of a pull-requests-by-branch read on a host whose branch query can't return the batch read's\n * rows: resolves each target's matched numbers through `resolve` (the host's `getProviderPullRequestsBatch`), so a\n * pull request comes back identical whichever read found it.\n *\n * One `resolve` call for every target's numbers, so the host's own fan-out bounds the concurrency. A number it\n * reports absent (deleted between the two steps) is dropped; one it couldn't check rejects the whole target, since\n * a partial list would read as a complete one. The first step's order is kept.\n */\nexport async function resolveBranchPullRequests(\n\ttargets: readonly { owner: string; repo: string; project?: string }[],\n\tfound: readonly PromiseSettledResult<{ numbers: number[]; truncated: boolean }>[],\n\tresolve: (\n\t\tcoordinates: { owner: string; repo: string; number: number; project?: string }[],\n\t) => Promise<PromiseSettledResult<PullRequestShape | undefined>[] | undefined>,\n): Promise<PromiseSettledResult<{ pullRequests: PullRequestShape[]; truncated: boolean }>[]> {\n\tconst coordinates = found.flatMap((slot, i) =>\n\t\tslot.status === 'fulfilled'\n\t\t\t? slot.value.numbers.map(number => ({\n\t\t\t\t\towner: targets[i].owner,\n\t\t\t\t\trepo: targets[i].repo,\n\t\t\t\t\tnumber: number,\n\t\t\t\t\tproject: targets[i].project,\n\t\t\t\t}))\n\t\t\t: [],\n\t);\n\n\tlet resolved: PromiseSettledResult<PullRequestShape | undefined>[] = [];\n\tif (coordinates.length > 0) {\n\t\ttry {\n\t\t\tresolved =\n\t\t\t\t(await resolve(coordinates)) ??\n\t\t\t\tcoordinates.map(() => ({\n\t\t\t\t\tstatus: 'rejected',\n\t\t\t\t\treason: new Error('Pull requests could not be resolved'),\n\t\t\t\t}));\n\t\t} catch (ex) {\n\t\t\tresolved = coordinates.map(() => ({ status: 'rejected', reason: ex }));\n\t\t}\n\t}\n\n\tlet offset = 0;\n\treturn found.map(slot => {\n\t\tif (slot.status === 'rejected') return slot;\n\n\t\tconst slots = resolved.slice(offset, offset + slot.value.numbers.length);\n\t\toffset += slot.value.numbers.length;\n\n\t\tconst failure = slots.find((s): s is PromiseRejectedResult => s.status === 'rejected');\n\t\tif (failure != null) return failure;\n\n\t\treturn {\n\t\t\tstatus: 'fulfilled',\n\t\t\tvalue: {\n\t\t\t\tpullRequests: slots.flatMap(s => (s.status === 'fulfilled' && s.value != null ? [s.value] : [])),\n\t\t\t\ttruncated: slot.value.truncated,\n\t\t\t},\n\t\t};\n\t});\n}\n\n/**\n * Runs a cheap etag read over `coordinates` with one request per repository and chunk of `chunkSize` numbers, with\n * bounded concurrency, and maps the answers back to the coordinates positionally (`read` answers one entry per\n * number, in order, `undefined` for a proven absence). Settled per target: a request that throws rejects only its\n * own slots, and a slot whose conversion throws rejects only itself.\n */\nexport async function readEtagFieldsByRepository<Node, Fields>(\n\tcoordinates: readonly { owner: string; repo: string; number: number }[],\n\tchunkSize: number,\n\tread: (owner: string, repo: string, numbers: number[]) => Promise<(Node | undefined)[]>,\n\tmap: (node: Node) => Fields,\n): Promise<PromiseSettledResult<Fields | undefined>[]> {\n\tconst byRepository = new Map<string, number[]>();\n\tfor (const [index, c] of coordinates.entries()) {\n\t\tconst key = `${c.owner}/${c.repo}`;\n\t\tlet indices = byRepository.get(key);\n\t\tif (indices == null) {\n\t\t\tindices = [];\n\t\t\tbyRepository.set(key, indices);\n\t\t}\n\t\tindices.push(index);\n\t}\n\n\tconst requests = [...byRepository.values()].flatMap(indices =>\n\t\tchunk(indices, chunkSize).map(chunkIndices => ({ indices: chunkIndices })),\n\t);\n\tconst answers = await mapSettledBounded(requests, providerFanOutConcurrency, r => {\n\t\tconst { owner, repo } = coordinates[r.indices[0]];\n\t\treturn read(\n\t\t\towner,\n\t\t\trepo,\n\t\t\tr.indices.map(i => coordinates[i].number),\n\t\t);\n\t});\n\n\tconst slots = new Array<PromiseSettledResult<Fields | undefined>>(coordinates.length);\n\tfor (const [i, answer] of answers.entries()) {\n\t\tfor (const [j, index] of requests[i].indices.entries()) {\n\t\t\tif (answer.status === 'rejected') {\n\t\t\t\tslots[index] = answer;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst node = answer.value[j];\n\t\t\ttry {\n\t\t\t\tslots[index] = { status: 'fulfilled', value: node != null ? map(node) : undefined };\n\t\t\t} catch (ex) {\n\t\t\t\tslots[index] = { status: 'rejected', reason: ex };\n\t\t\t}\n\t\t}\n\t}\n\treturn slots;\n}\n\nexport function flatSettledResultsOrThrow<T>(results: PromiseSettledResult<T[]>[]): T[] {\n\tthrowIfAllSettledFailed(results);\n\n\tconst fulfilled = results.filter((result): result is PromiseFulfilledResult<T[]> => result.status === 'fulfilled');\n\treturn fulfilled.flatMap(result => result.value);\n}\n\nexport async function flatSettledOrThrow<T>(promises: Promise<T[]>[]): Promise<T[]> {\n\treturn flatSettledResultsOrThrow(await Promise.allSettled(promises));\n}\n\n/** Precedence for merged completeness: any known omission (`partial`) wins, then inability to confirm\n * (`unknown`), and only an all-`complete` set of pages stays `complete`. */\nconst completenessRank: Record<CollectionCompleteness, number> = { partial: 2, unknown: 1, complete: 0 };\n\n/** A stable key for deduplicating structurally-identical scope failures accumulated across drained pages. */\nfunction collectionFailureKey(failure: CollectionScopeFailure): string {\n\treturn [failure.kind, collectionScopeKey(failure.scope), failure.message ?? ''].join(' ');\n}\n\n/**\n * A stable key for collapsing omissions accumulated across drained pages, matching the SDK's own\n * `dedupeOmissions`: kind plus scope IDs, deliberately WITHOUT `limit`/`totalCount`.\n *\n * Those counts are a re-measurement, not an identity. GitHub recomputes the match total on every request, so\n * one repository drained over several pages reports the same cap with a drifting total; keying on the total\n * would emit a near-identical warning per page (\"matched 1393…\", \"matched 1402…\") that\n * `appendDedupedWarning` cannot collapse, since the messages genuinely differ. Independently-scoped omissions\n * still stay distinct, which is the case that carries information.\n */\nfunction collectionOmissionKey(omission: CollectionOmission): string {\n\treturn [omission.kind, collectionScopeKey(omission.scope)].join(' ');\n}\n\n/**\n * Merges SDK collection metadata across drained pages. Completeness follows {@link completenessRank};\n * failures are deduplicated by kind, scope IDs, and message, and omissions are collapsed per kind and scope\n * keeping the highest reported total ({@link collectionOmissionKey}). Both preserve first-reported order.\n * Returns `undefined` when no page supplied metadata, so metadata-free providers and test doubles keep\n * behaving as before.\n */\nexport function mergeCollectionMetadata(\n\tbase: CollectionMetadata | undefined,\n\tnext: CollectionMetadata | undefined,\n): CollectionMetadata | undefined {\n\tif (base == null) return next;\n\tif (next == null) return base;\n\n\tconst completeness =\n\t\tcompletenessRank[next.completeness] > completenessRank[base.completeness]\n\t\t\t? next.completeness\n\t\t\t: base.completeness;\n\n\t// First occurrence wins: a repeated failure carries no new information, since the message is part of its key.\n\tconst failures = [\n\t\t...uniqueBy([...(base.failures ?? []), ...(next.failures ?? [])], collectionFailureKey, original => original),\n\t];\n\t// Highest total wins, so re-measuring one cap across pages collapses to a single omission holding the\n\t// largest figure reported for it — the same rule the SDK applies in `createCollectionMetadata`.\n\tconst omissions = [\n\t\t...uniqueBy(\n\t\t\t[...(base.omissions ?? []), ...(next.omissions ?? [])],\n\t\t\tcollectionOmissionKey,\n\t\t\t(original, current) => ((current.totalCount ?? -1) > (original.totalCount ?? -1) ? current : undefined),\n\t\t),\n\t];\n\n\treturn {\n\t\tcompleteness: completeness,\n\t\t...(failures.length ? { failures: failures } : {}),\n\t\t...(omissions.length ? { omissions: omissions } : {}),\n\t};\n}\n\n/**\n * Drains a provider paged fetcher into a single result while preserving enough metadata to\n * signal whether the defensive backstop interrupted the drain, and merging SDK collection metadata\n * ({@link mergeCollectionMetadata}) across the fetched pages.\n *\n * The local `truncated` flag and SDK `metadata` are distinct facts: a page-drain backstop must remain visible\n * even if every fetched page reported `complete`, and SDK incompleteness is preserved even when the drain\n * finished within its page budget.\n */\nexport async function collectProviderPagedResult<T>(\n\tfetch: (cursor: string | undefined) => Promise<ProviderApiPagedResult<T> | undefined>,\n\tmaxPages = 20,\n\tscope?: CollectionScopeFailure['scope'],\n): Promise<ProviderHierarchyResult<T>> {\n\tconst values: NonNullable<T>[] = [];\n\tlet cursor: string | undefined;\n\tlet metadata: CollectionMetadata | undefined;\n\tlet truncated = false;\n\n\t// Omit `metadata` entirely when no page supplied it, so a metadata-free drain stays deep-equal to its\n\t// pre-metadata shape (and consumers never see an explicit `undefined`).\n\tconst build = (extra?: Partial<ProviderHierarchyResult<T>>): ProviderHierarchyResult<T> => {\n\t\tconst mergedMetadata = extra?.metadata ?? metadata;\n\t\treturn {\n\t\t\tvalues: values,\n\t\t\t...extra,\n\t\t\t...(truncated || extra?.truncated === true ? { truncated: true } : {}),\n\t\t\t...(mergedMetadata != null ? { metadata: mergedMetadata } : {}),\n\t\t};\n\t};\n\n\tfor (let page = 0; page < maxPages; page++) {\n\t\tlet result: ProviderApiPagedResult<T> | undefined;\n\t\ttry {\n\t\t\tresult = await fetch(cursor);\n\t\t} catch (ex) {\n\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t// A caller-contract error is not a fact about this scope, so it is never recorded as one — see\n\t\t\t// `throwIfCallerContractError`. Checked alongside cancellation because both are errors that a\n\t\t\t// per-scope failure would misdescribe.\n\t\t\tthrowIfCallerContractError(ex);\n\n\t\t\t// When the caller supplied a scope, preserve the items already fetched from that scope and record the\n\t\t\t// failure in collection metadata rather than re-throwing and discarding the prefix. Callers without a\n\t\t\t// scope keep the legacy throw behavior.\n\t\t\tif (scope == null) throw ex;\n\t\t\treturn build({\n\t\t\t\ttruncated: true,\n\t\t\t\tmetadata: mergeCollectionMetadata(metadata, {\n\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\tfailures: [toCollectionScopeFailure(scope, ex)],\n\t\t\t\t}),\n\t\t\t});\n\t\t}\n\t\tif (result == null) {\n\t\t\tif (page === 0) return build();\n\n\t\t\treturn build({\n\t\t\t\ttruncated: true,\n\t\t\t\t...(scope != null\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tmetadata: mergeCollectionMetadata(metadata, {\n\t\t\t\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\t\t\t\tfailures: [\n\t\t\t\t\t\t\t\t\ttoCollectionScopeFailure(\n\t\t\t\t\t\t\t\t\t\tscope,\n\t\t\t\t\t\t\t\t\t\tnew Error('Provider returned no page after advertising a continuation'),\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t}\n\t\t\t\t\t: undefined),\n\t\t\t});\n\t\t}\n\n\t\tvalues.push(...result.values);\n\t\tmetadata = mergeCollectionMetadata(metadata, result.metadata);\n\t\ttruncated ||= result.paging?.truncated === true;\n\n\t\tif (!result.paging?.more) return build();\n\n\t\tif (result.paging.cursor === cursor) {\n\t\t\treturn build({ truncated: true });\n\t\t}\n\n\t\tcursor = result.paging.cursor;\n\t\tif (cursor == null || cursor === '{}') {\n\t\t\treturn build({ truncated: true });\n\t\t}\n\t\tif (page === maxPages - 1) {\n\t\t\treturn build({ paging: result.paging, truncated: true });\n\t\t}\n\t}\n\n\treturn build();\n}\n"],"mappings":"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"}
@@ -2,8 +2,8 @@ import { IssueResourceDescriptor, RepositoryDescriptor, ResourceDescriptor } fro
2
2
  import { Issue as Issue$1, IssueShape } from "../../../git/models/issue.js";
3
3
  import { PullRequest } from "../../../git/models/pullRequest.js";
4
4
  import { IntegrationIds } from "../constants.js";
5
- import { AzureProjectInputDescriptor } from "./azure/models.js";
6
5
  import { GitConfigEntityIdentifier } from "./models.js";
6
+ import { AzureProjectInputDescriptor } from "./azure/models.js";
7
7
  import { AnyEntityIdentifierInput, EntityIdentifier } from "@gitkraken/provider-apis";
8
8
  //#region ../plus/integrations/src/providers/utils.d.ts
9
9
  export declare const EntityIdentifierProviderType: {
@@ -0,0 +1,69 @@
1
+ import { IntegrationIds } from "../constants.js";
2
+ import { ProviderResult, ProviderWarning } from "../results.js";
3
+ import { BatchSlot } from "../models/integration.js";
4
+ //#region ../plus/integrations/src/reads/batchEtags.d.ts
5
+ /**
6
+ * The etag flow shared by the batch reads (`getPullRequestsBatch`, and both forms of `getIssuesBatch`): targets that
7
+ * carry the etag of the caller's copy are first asked a cheap "what is your change state" question, and only the ones
8
+ * that moved are read in full.
9
+ *
10
+ * - No target carries an etag, or the integration has no cheap check: ONE full read of every target, exactly as
11
+ * before etags existed, with an etag added to each row.
12
+ * - Otherwise up to three integration calls: the full read of the targets with no etag starts at once, in parallel
13
+ * with the cheap check of the others, and the targets whose etag no longer matches get a second full read as soon
14
+ * as the check settles. A call takes the longer of the two paths, never their sum.
15
+ *
16
+ * The cheap check never answers for a target it couldn't check: a target it failed falls through to the full read,
17
+ * which then has the final word (and supplies the warning, should it fail too) — except when the failure was a
18
+ * refused credential or a spent rate limit, which the full read would only hit again; those targets are dropped with
19
+ * the cheap check's warning, as a failed full read drops them. Each target is judged by its own slot's reason, so one
20
+ * target's rate limit never drops another target the check merely failed to answer, nor sends a rate-limited target
21
+ * to the full read; the check's wrappers return every slot as it settled, failing as a whole only on a refused
22
+ * credential (or when the request itself failed).
23
+ *
24
+ * Failure budget: the cheap check spends no strike toward disconnecting, and shows no notice, unless the credential
25
+ * is refused. Only the full reads spend, each at most one, so a call spends at most two: in a total outage of a batch
26
+ * whose every target carries an etag, one, for the full read every target falls through to.
27
+ */
28
+ /** One integration call's slots, as `runCaptured` captures them. */
29
+ export interface CapturedBatch<T> {
30
+ value?: BatchSlot<T | undefined>[];
31
+ warning?: ProviderWarning;
32
+ }
33
+ /**
34
+ * One target's answer: read in full (`value`, or proven absent without one), or `unchanged`, when the cheap check
35
+ * proved the caller's copy current. Every row with a `value` carries its `etag`, as does every `unchanged` one.
36
+ */
37
+ export interface EtaggedBatchRow<T> {
38
+ key: string;
39
+ value?: T;
40
+ etag?: string;
41
+ unchanged?: true;
42
+ }
43
+ export declare function readEtaggedBatch<Target extends {
44
+ key: string;
45
+ etag?: string;
46
+ }, Item, Fields>(options: {
47
+ providerId: IntegrationIds;
48
+ domain: string | undefined;
49
+ connectionId: string | undefined;
50
+ targets: readonly Target[];
51
+ /** Whether the integration has a cheap check. Decided up front, so a host without one makes one call. */
52
+ supportsEtags: boolean;
53
+ readFull: (targets: readonly Target[]) => Promise<CapturedBatch<Item>>;
54
+ readEtagFields: (targets: readonly Target[]) => Promise<CapturedBatch<Fields>>;
55
+ itemEtag: (item: Item) => string;
56
+ fieldsEtag: (fields: Fields) => string;
57
+ /** The warning for a full read that answered neither slots nor an error: the provider can't batch at all. */
58
+ unsupportedWarning: () => ProviderWarning;
59
+ }): Promise<ProviderResult<EtaggedBatchRow<Item>>>;
60
+ /**
61
+ * Appends the warning for a batch target that could not be checked, shared by every batch read: for a refusal, the
62
+ * scope failure it was recorded as (see `IntegrationBase.settleBatchRefusals`), published as an account-wide read
63
+ * publishes one, scoped and with its cause; otherwise, the reason itself.
64
+ */
65
+ export declare function appendBatchSlotWarning(warnings: ProviderWarning[], providerId: IntegrationIds, domain: string | undefined, connectionId: string | undefined, slot: Extract<BatchSlot<unknown>, {
66
+ status: 'rejected';
67
+ }>): void;
68
+ //#endregion
69
+ //# sourceMappingURL=batchEtags.d.ts.map
@@ -0,0 +1,114 @@
1
+ import { appendDedupedWarning, toProviderWarning } from "../results.js";
2
+ import { mergeAssessmentInto } from "../collectionMetadata.js";
3
+ //#region ../plus/integrations/src/reads/batchEtags.ts
4
+ /** Failures a full read would only hit again: a refused credential, a spent rate limit, a connection that's gone. */
5
+ const noRetryKinds = [
6
+ "auth",
7
+ "rate-limit",
8
+ "no-connection"
9
+ ];
10
+ async function readEtaggedBatch(options) {
11
+ const { providerId, domain, connectionId, targets } = options;
12
+ const warnings = [];
13
+ const rows = /* @__PURE__ */ new Map();
14
+ let fetchFailed = false;
15
+ const settleFull = (indices, { value: slots, warning }) => {
16
+ if (warning != null) appendDedupedWarning(warnings, warning);
17
+ if (slots == null) {
18
+ if (warning == null) appendDedupedWarning(warnings, options.unsupportedWarning());
19
+ fetchFailed = true;
20
+ return;
21
+ }
22
+ for (let i = 0; i < indices.length; i++) {
23
+ const slot = slots[i];
24
+ if (slot.status === "rejected") {
25
+ appendBatchSlotWarning(warnings, providerId, domain, connectionId, slot);
26
+ fetchFailed = true;
27
+ continue;
28
+ }
29
+ const item = slot.value;
30
+ const key = targets[indices[i]].key;
31
+ rows.set(indices[i], item != null ? {
32
+ key,
33
+ value: item,
34
+ etag: options.itemEtag(item)
35
+ } : { key });
36
+ }
37
+ };
38
+ const cheap = [];
39
+ const full = [];
40
+ for (let i = 0; i < targets.length; i++) (options.supportsEtags && targets[i].etag ? cheap : full).push(i);
41
+ const select = (indices) => indices.map((i) => targets[i]);
42
+ if (!cheap.length) settleFull(full, await options.readFull(targets));
43
+ else {
44
+ const fullRead = full.length ? options.readFull(select(full)) : void 0;
45
+ const changed = [];
46
+ const { value: slots, warning } = await options.readEtagFields(select(cheap));
47
+ if (warning != null && noRetryKinds.includes(warning.kind)) {
48
+ appendDedupedWarning(warnings, warning);
49
+ fetchFailed = true;
50
+ } else if (slots == null) changed.push(...cheap);
51
+ else for (let i = 0; i < cheap.length; i++) {
52
+ const index = cheap[i];
53
+ const slot = slots[i];
54
+ if (slot.status === "rejected") {
55
+ if (slot.failure != null) {
56
+ appendBatchSlotWarning(warnings, providerId, domain, connectionId, slot);
57
+ fetchFailed = true;
58
+ continue;
59
+ }
60
+ const slotWarning = toProviderWarning(providerId, domain, connectionId, slot.reason);
61
+ if (noRetryKinds.includes(slotWarning.kind)) {
62
+ appendDedupedWarning(warnings, slotWarning);
63
+ fetchFailed = true;
64
+ } else changed.push(index);
65
+ continue;
66
+ }
67
+ const key = targets[index].key;
68
+ const fields = slot.value;
69
+ if (fields == null) {
70
+ rows.set(index, { key });
71
+ continue;
72
+ }
73
+ const etag = options.fieldsEtag(fields);
74
+ if (etag === targets[index].etag) rows.set(index, {
75
+ key,
76
+ unchanged: true,
77
+ etag
78
+ });
79
+ else changed.push(index);
80
+ }
81
+ const changedRead = changed.length ? options.readFull(select(changed)) : void 0;
82
+ if (fullRead != null) settleFull(full, await fullRead);
83
+ if (changedRead != null) settleFull(changed, await changedRead);
84
+ }
85
+ const items = [];
86
+ for (let i = 0; i < targets.length; i++) {
87
+ const row = rows.get(i);
88
+ if (row != null) items.push(row);
89
+ }
90
+ return {
91
+ items,
92
+ warnings,
93
+ fetchFailed: fetchFailed || void 0
94
+ };
95
+ }
96
+ /**
97
+ * Appends the warning for a batch target that could not be checked, shared by every batch read: for a refusal, the
98
+ * scope failure it was recorded as (see `IntegrationBase.settleBatchRefusals`), published as an account-wide read
99
+ * publishes one, scoped and with its cause; otherwise, the reason itself.
100
+ */
101
+ function appendBatchSlotWarning(warnings, providerId, domain, connectionId, slot) {
102
+ if (slot.failure != null) {
103
+ mergeAssessmentInto(warnings, providerId, domain, connectionId, {
104
+ completeness: "partial",
105
+ failures: [slot.failure]
106
+ });
107
+ return;
108
+ }
109
+ appendDedupedWarning(warnings, toProviderWarning(providerId, domain, connectionId, slot.reason));
110
+ }
111
+ //#endregion
112
+ export { appendBatchSlotWarning, readEtaggedBatch };
113
+
114
+ //# sourceMappingURL=batchEtags.js.map