@gitkraken/core-gitlens 0.9.3 → 0.9.5

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 (160) hide show
  1. package/CHANGELOG.md +34 -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 +55 -5
  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 +13 -4
  27. package/dist/plus/integrations/integrationService.js.map +1 -1
  28. package/dist/plus/integrations/manager.d.ts +62 -7
  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/issueReads.d.ts +14 -0
  36. package/dist/plus/integrations/models/issuesIntegration.d.ts +44 -3
  37. package/dist/plus/integrations/models/issuesIntegration.js +51 -1
  38. package/dist/plus/integrations/models/issuesIntegration.js.map +1 -1
  39. package/dist/plus/integrations/providers/azure/azure.js +3 -3
  40. package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
  41. package/dist/plus/integrations/providers/azure/models.d.ts +36 -4
  42. package/dist/plus/integrations/providers/azure/models.js +118 -8
  43. package/dist/plus/integrations/providers/azure/models.js.map +1 -1
  44. package/dist/plus/integrations/providers/azureDevOps.d.ts +102 -5
  45. package/dist/plus/integrations/providers/azureDevOps.js +264 -45
  46. package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
  47. package/dist/plus/integrations/providers/azureDevOpsServer.d.ts +30 -6
  48. package/dist/plus/integrations/providers/azureDevOpsServer.js +82 -21
  49. package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -1
  50. package/dist/plus/integrations/providers/bitbucket/bitbucket.d.ts +21 -1
  51. package/dist/plus/integrations/providers/bitbucket/bitbucket.js +74 -10
  52. package/dist/plus/integrations/providers/bitbucket/bitbucket.js.map +1 -1
  53. package/dist/plus/integrations/providers/bitbucket/models.d.ts +45 -8
  54. package/dist/plus/integrations/providers/bitbucket/models.js +32 -9
  55. package/dist/plus/integrations/providers/bitbucket/models.js.map +1 -1
  56. package/dist/plus/integrations/providers/bitbucket-server/models.d.ts +2 -6
  57. package/dist/plus/integrations/providers/bitbucket-server/models.js +1 -3
  58. package/dist/plus/integrations/providers/bitbucket-server/models.js.map +1 -1
  59. package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.d.ts +3 -1
  60. package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.js +4 -1
  61. package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.js.map +1 -1
  62. package/dist/plus/integrations/providers/bitbucket-server.d.ts +2 -2
  63. package/dist/plus/integrations/providers/bitbucket-server.js +7 -5
  64. package/dist/plus/integrations/providers/bitbucket-server.js.map +1 -1
  65. package/dist/plus/integrations/providers/bitbucket.d.ts +22 -2
  66. package/dist/plus/integrations/providers/bitbucket.js +29 -6
  67. package/dist/plus/integrations/providers/bitbucket.js.map +1 -1
  68. package/dist/plus/integrations/providers/github.d.ts +40 -3
  69. package/dist/plus/integrations/providers/github.js +133 -11
  70. package/dist/plus/integrations/providers/github.js.map +1 -1
  71. package/dist/plus/integrations/providers/gitlab/gitlab.d.ts +34 -1
  72. package/dist/plus/integrations/providers/gitlab/gitlab.js +118 -4
  73. package/dist/plus/integrations/providers/gitlab/gitlab.js.map +1 -1
  74. package/dist/plus/integrations/providers/gitlab/models.d.ts +70 -5
  75. package/dist/plus/integrations/providers/gitlab/models.js +106 -13
  76. package/dist/plus/integrations/providers/gitlab/models.js.map +1 -1
  77. package/dist/plus/integrations/providers/gitlab.d.ts +31 -2
  78. package/dist/plus/integrations/providers/gitlab.js +48 -8
  79. package/dist/plus/integrations/providers/gitlab.js.map +1 -1
  80. package/dist/plus/integrations/providers/jira-server.d.ts +3 -2
  81. package/dist/plus/integrations/providers/jira-server.js +18 -9
  82. package/dist/plus/integrations/providers/jira-server.js.map +1 -1
  83. package/dist/plus/integrations/providers/jira.d.ts +26 -2
  84. package/dist/plus/integrations/providers/jira.js +120 -9
  85. package/dist/plus/integrations/providers/jira.js.map +1 -1
  86. package/dist/plus/integrations/providers/jiraIssueByKey.d.ts +79 -0
  87. package/dist/plus/integrations/providers/jiraIssueByKey.js +58 -7
  88. package/dist/plus/integrations/providers/jiraIssueByKey.js.map +1 -1
  89. package/dist/plus/integrations/providers/linear.d.ts +19 -1
  90. package/dist/plus/integrations/providers/linear.js +105 -7
  91. package/dist/plus/integrations/providers/linear.js.map +1 -1
  92. package/dist/plus/integrations/providers/linearIssuesEtag.d.ts +42 -0
  93. package/dist/plus/integrations/providers/linearIssuesEtag.js +94 -0
  94. package/dist/plus/integrations/providers/linearIssuesEtag.js.map +1 -0
  95. package/dist/plus/integrations/providers/models.d.ts +70 -8
  96. package/dist/plus/integrations/providers/models.js +59 -21
  97. package/dist/plus/integrations/providers/models.js.map +1 -1
  98. package/dist/plus/integrations/providers/providerErrors.js +8 -1
  99. package/dist/plus/integrations/providers/providerErrors.js.map +1 -1
  100. package/dist/plus/integrations/providers/providersApi.d.ts +70 -4
  101. package/dist/plus/integrations/providers/providersApi.js +148 -4
  102. package/dist/plus/integrations/providers/providersApi.js.map +1 -1
  103. package/dist/plus/integrations/providers/pullRequestReviews.d.ts +13 -0
  104. package/dist/plus/integrations/providers/pullRequestReviews.js +30 -6
  105. package/dist/plus/integrations/providers/pullRequestReviews.js.map +1 -1
  106. package/dist/plus/integrations/providers/trello.js +11 -7
  107. package/dist/plus/integrations/providers/trello.js.map +1 -1
  108. package/dist/plus/integrations/providers/utils/providerPaging.d.ts +11 -0
  109. package/dist/plus/integrations/providers/utils/providerPaging.js +47 -1
  110. package/dist/plus/integrations/providers/utils/providerPaging.js.map +1 -1
  111. package/dist/plus/integrations/providers/utils.d.ts +1 -1
  112. package/dist/plus/integrations/reads/batchEtags.d.ts +69 -0
  113. package/dist/plus/integrations/reads/batchEtags.js +114 -0
  114. package/dist/plus/integrations/reads/batchEtags.js.map +1 -0
  115. package/dist/plus/integrations/reads/counts.js +2 -2
  116. package/dist/plus/integrations/reads/counts.js.map +1 -1
  117. package/dist/plus/integrations/reads/currentAccount.js +2 -2
  118. package/dist/plus/integrations/reads/currentAccount.js.map +1 -1
  119. package/dist/plus/integrations/reads/cursors.d.ts +8 -0
  120. package/dist/plus/integrations/reads/cursors.js +5 -3
  121. package/dist/plus/integrations/reads/cursors.js.map +1 -1
  122. package/dist/plus/integrations/reads/drains.d.ts +13 -4
  123. package/dist/plus/integrations/reads/drains.js +29 -5
  124. package/dist/plus/integrations/reads/drains.js.map +1 -1
  125. package/dist/plus/integrations/reads/etag.d.ts +26 -0
  126. package/dist/plus/integrations/reads/etag.js +108 -0
  127. package/dist/plus/integrations/reads/etag.js.map +1 -0
  128. package/dist/plus/integrations/reads/filters.d.ts +8 -0
  129. package/dist/plus/integrations/reads/filters.js +2 -1
  130. package/dist/plus/integrations/reads/filters.js.map +1 -1
  131. package/dist/plus/integrations/reads/hierarchy.js +3 -3
  132. package/dist/plus/integrations/reads/hierarchy.js.map +1 -1
  133. package/dist/plus/integrations/reads/issueBatch.d.ts +47 -16
  134. package/dist/plus/integrations/reads/issueBatch.js +55 -87
  135. package/dist/plus/integrations/reads/issueBatch.js.map +1 -1
  136. package/dist/plus/integrations/reads/issueTracker.d.ts +10 -2
  137. package/dist/plus/integrations/reads/issueTracker.js +23 -6
  138. package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
  139. package/dist/plus/integrations/reads/issues.d.ts +1 -1
  140. package/dist/plus/integrations/reads/issues.js +4 -4
  141. package/dist/plus/integrations/reads/issues.js.map +1 -1
  142. package/dist/plus/integrations/reads/pullRequestBatch.d.ts +44 -6
  143. package/dist/plus/integrations/reads/pullRequestBatch.js +31 -34
  144. package/dist/plus/integrations/reads/pullRequestBatch.js.map +1 -1
  145. package/dist/plus/integrations/reads/pullRequestBranches.d.ts +5 -2
  146. package/dist/plus/integrations/reads/pullRequestBranches.js +4 -3
  147. package/dist/plus/integrations/reads/pullRequestBranches.js.map +1 -1
  148. package/dist/plus/integrations/reads/pullRequests.js +8 -4
  149. package/dist/plus/integrations/reads/pullRequests.js.map +1 -1
  150. package/dist/plus/integrations/reads/sweeps.js +8 -4
  151. package/dist/plus/integrations/reads/sweeps.js.map +1 -1
  152. package/dist/plus/integrations/reads/warnings.d.ts +23 -8
  153. package/dist/plus/integrations/reads/warnings.js +43 -19
  154. package/dist/plus/integrations/reads/warnings.js.map +1 -1
  155. package/dist/plus/integrations/results.d.ts +12 -4
  156. package/dist/plus/integrations/results.js +2 -0
  157. package/dist/plus/integrations/results.js.map +1 -1
  158. package/docs/integrations.md +389 -23
  159. package/docs/kepler-read-api-parity.md +16 -2
  160. package/package.json +2 -2
@@ -1,9 +1,11 @@
1
1
  import { githubGraphQLInt32Max, isAzureProviderId, isGitHubProviderId } from "../providers/providerErrors.js";
2
- import { appendDedupedWarning } from "../results.js";
3
2
  import { isGitHostIntegration, isIssuesHostIntegrationId } from "../utils/integration.utils.js";
4
- import { gitHostOnlySurfaceWarning, otherWarning } from "./warnings.js";
3
+ import { gitHostOnlySurfaceWarning, otherWarning, unsupportedWarning } from "./warnings.js";
5
4
  import { getCurrentAccountIdentity, runCaptured } from "./drains.js";
6
- import { appendBatchSlotWarning, findDuplicateKey, trimCoordinateFields, unresolvedIntegration } from "./issueBatch.js";
5
+ import { pullRequestEtagIncludes } from "../models/integration.js";
6
+ import { readEtaggedBatch } from "./batchEtags.js";
7
+ import { findInvalidPullRequestEtagInclude, normalizePullRequestEtagIncludes, pullRequestEtag, pullRequestEtagFieldsFromShape } from "./etag.js";
8
+ import { findDuplicateKey, trimCoordinateFields, unresolvedIntegration } from "./issueBatch.js";
7
9
  //#region ../plus/integrations/src/reads/pullRequestBatch.ts
8
10
  async function getPullRequestsBatch(ctx, options) {
9
11
  const refused = (warning) => ({
@@ -19,6 +21,8 @@ async function getPullRequestsBatch(ctx, options) {
19
21
  };
20
22
  const duplicateKey = findDuplicateKey(options.targets);
21
23
  if (duplicateKey != null) return refused(otherWarning(options.providerId, void 0, options.connectionId, `Duplicate pull request batch target key '${duplicateKey}'; keys identify results, so each must be unique.`));
24
+ const invalidInclude = findInvalidPullRequestEtagInclude(options.etagIncludes ?? []);
25
+ if (invalidInclude != null) return refused(otherWarning(options.providerId, void 0, options.connectionId, `Unknown pull request etag include '${invalidInclude}'; expected one of ${pullRequestEtagIncludes.map((i) => `'${i}'`).join(", ")}.`));
22
26
  const targets = options.targets.map(trimCoordinateFields);
23
27
  const invalid = findInvalidTarget(options.providerId, targets);
24
28
  if (invalid != null) return refused(otherWarning(options.providerId, void 0, options.connectionId, invalid));
@@ -26,42 +30,35 @@ async function getPullRequestsBatch(ctx, options) {
26
30
  if (integration == null) return unresolvedIntegration(ctx, options.providerId, options.connectionId, options.domain);
27
31
  if (!isGitHostIntegration(integration)) return refused(gitHostOnlySurfaceWarning(options.providerId, void 0, options.connectionId, surface));
28
32
  const domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);
29
- const currentAccount = await getCurrentAccountIdentity(integration, options.connectionId);
30
- const warnings = [];
31
- let fetchFailed = false;
32
- const { value: slots, warning } = await runCaptured(options.providerId, domain, options.connectionId, () => integration.getPullRequestsBatchResult(targets.map((t) => ({
33
+ const etagIncludes = normalizePullRequestEtagIncludes(options.etagIncludes ?? []);
34
+ const toCoordinate = (t) => ({
33
35
  owner: t.owner,
34
36
  repo: t.repo,
35
37
  number: t.number,
36
38
  project: t.project
37
- })), { currentAccount }, void 0, options.connectionId), { warnOnMissingSession: true });
38
- if (warning != null) appendDedupedWarning(warnings, warning);
39
- if (slots == null) {
40
- if (warning == null) appendDedupedWarning(warnings, otherWarning(options.providerId, domain, options.connectionId, `${surface} is not supported by '${options.providerId}'; resolve pull requests individually instead.`));
41
- return {
42
- items: [],
43
- warnings,
44
- fetchFailed: true
45
- };
46
- }
47
- const items = [];
48
- for (let i = 0; i < targets.length; i++) {
49
- const slot = slots[i];
50
- if (slot.status === "rejected") {
51
- appendBatchSlotWarning(warnings, options.providerId, domain, options.connectionId, slot);
52
- fetchFailed = true;
53
- continue;
54
- }
55
- const pullRequest = slot.value;
56
- items.push({
57
- key: targets[i].key,
58
- ...pullRequest != null ? { pullRequest } : {}
59
- });
60
- }
39
+ });
40
+ let currentAccount;
41
+ const result = await readEtaggedBatch({
42
+ providerId: options.providerId,
43
+ domain,
44
+ connectionId: options.connectionId,
45
+ targets,
46
+ supportsEtags: integration.supportsPullRequestEtags,
47
+ readFull: (batch) => runCaptured(options.providerId, domain, options.connectionId, async () => integration.getPullRequestsBatchResult(batch.map(toCoordinate), { currentAccount: await (currentAccount ??= getCurrentAccountIdentity(integration, options.connectionId)) }, void 0, options.connectionId), { warnOnMissingSession: true }),
48
+ readEtagFields: (batch) => runCaptured(options.providerId, domain, options.connectionId, () => integration.getPullRequestsEtagFieldsResult(batch.map(toCoordinate), { etagIncludes }, void 0, options.connectionId), { warnOnMissingSession: true }),
49
+ itemEtag: (pr) => pullRequestEtag(pullRequestEtagFieldsFromShape(pr), etagIncludes),
50
+ fieldsEtag: (fields) => pullRequestEtag(fields, etagIncludes),
51
+ unsupportedWarning: () => unsupportedWarning(options.providerId, domain, options.connectionId, `${surface} is not supported by '${options.providerId}'; resolve pull requests individually instead.`)
52
+ });
61
53
  return {
62
- items,
63
- warnings,
64
- fetchFailed: fetchFailed || void 0
54
+ items: result.items.map((row) => ({
55
+ key: row.key,
56
+ ...row.value != null ? { pullRequest: row.value } : {},
57
+ ...row.unchanged ? { unchanged: true } : {},
58
+ ...row.etag != null ? { etag: row.etag } : {}
59
+ })),
60
+ warnings: result.warnings,
61
+ fetchFailed: result.fetchFailed
65
62
  };
66
63
  }
67
64
  function findInvalidTarget(providerId, targets) {
@@ -1 +1 @@
1
- {"version":3,"file":"pullRequestBatch.js","names":[],"sources":["../../../../../plus/integrations/src/reads/pullRequestBatch.ts"],"sourcesContent":["import type { PullRequestShape } from '@gitlens/git/models/pullRequest.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { githubGraphQLInt32Max, isAzureProviderId, isGitHubProviderId } from '../providers/providerErrors.js';\nimport type { ProviderResult, ProviderWarning } from '../results.js';\nimport { appendDedupedWarning } from '../results.js';\nimport { isGitHostIntegration, isIssuesHostIntegrationId } from '../utils/integration.utils.js';\nimport type { ProviderReadContext } from './context.js';\nimport { getCurrentAccountIdentity, runCaptured } from './drains.js';\nimport { appendBatchSlotWarning, findDuplicateKey, trimCoordinateFields, unresolvedIntegration } from './issueBatch.js';\nimport { gitHostOnlySurfaceWarning, otherWarning } from './warnings.js';\n\n/**\n * The BATCH pull request read: resolve N pull requests, in any state, BY COORDINATE — the pull-request twin of\n * `getIssuesBatch`, and for the same reason. \"Does this exact pull request exist\" is an identity question; a\n * paged list can only answer it by walking the whole scope, so a miss stays unproven and the walk repeats forever.\n * Here an absent slot is a PROVEN ABSENCE a caller can cache.\n *\n * Deliberately NOT routed through `Integration.getPullRequest`: that read answers `undefined` both when not\n * connected and on any failure, and caches through `IntegrationCacheProvider.getPullRequest`, whose key carries no\n * connection and whose by-id bucket never expires a miss.\n *\n * ONE integration call per invocation, whatever the target count: the manager makes a single\n * `getPullRequestsBatchResult` call and the integration fans its targets out (chunked for GitHub/GHE, one request\n * per target with bounded concurrency everywhere else) and settles each independently, so one bad target never\n * spends more than its own slot — see `GitHostIntegration.getPullRequestsBatchResult`.\n */\n\n/** One pull request to resolve, identified by coordinate and echoed back under the caller's own `key`. */\nexport interface PullRequestBatchTarget {\n\t/** Caller-owned identifier, echoed on the result so no positional matching is needed. Must be unique. */\n\tkey: string;\n\t/**\n\t * GitHub owner, GitLab namespace path (may be nested), Bitbucket workspace, Bitbucket Data Center project key, or\n\t * Azure DevOps organization.\n\t */\n\towner: string;\n\t/** Repository name or slug. */\n\trepo: string;\n\t/** Pull request number, GitLab merge request iid, or Azure DevOps pull request id. */\n\tnumber: number;\n\t/** Azure DevOps project. Required there, ignored elsewhere. */\n\tproject?: string;\n}\n\n/** The answer for one {@link PullRequestBatchTarget}. */\nexport interface PullRequestBatchResult {\n\tkey: string;\n\t/**\n\t * The resolved pull request, whatever its state, or `undefined` when it PROVABLY does not exist (or is not\n\t * visible to this connection). A target whose read FAILED is not returned at all and sets `fetchFailed`.\n\t */\n\tpullRequest?: PullRequestShape;\n}\n\nexport async function getPullRequestsBatch(\n\tctx: ProviderReadContext,\n\toptions: {\n\t\tproviderId: IntegrationIds;\n\t\ttargets: readonly PullRequestBatchTarget[];\n\t\tconnectionId?: string;\n\t\t/**\n\t\t * Explicit self-managed host domain. Used only when the requested connection has no configured domain;\n\t\t * it must come from the trusted authentication configuration, not repository or remote data.\n\t\t */\n\t\tdomain?: string;\n\t},\n): Promise<ProviderResult<PullRequestBatchResult>> {\n\tconst refused = (warning: ProviderWarning): ProviderResult<PullRequestBatchResult> => ({\n\t\titems: [],\n\t\twarnings: [warning],\n\t\tfetchFailed: true,\n\t});\n\tconst surface = 'Batch pull request resolution';\n\n\tif (isIssuesHostIntegrationId(options.providerId)) {\n\t\treturn refused(gitHostOnlySurfaceWarning(options.providerId, undefined, options.connectionId, surface));\n\t}\n\n\tif (options.targets.length === 0) return { items: [], warnings: [] };\n\n\tconst duplicateKey = findDuplicateKey(options.targets);\n\tif (duplicateKey != null) {\n\t\t// Refuses the whole call rather than deduping: two results under one key make matching ambiguous for EVERY\n\t\t// target, not just the repeated one.\n\t\treturn refused(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tundefined,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`Duplicate pull request batch target key '${duplicateKey}'; keys identify results, so each must be unique.`,\n\t\t\t),\n\t\t);\n\t}\n\n\t// The validated value must be the one sent to the provider, so every string field is trimmed once here.\n\tconst targets = options.targets.map(trimCoordinateFields);\n\n\t// Deterministic caller bugs, refused like a duplicate key rather than sent upstream to fail one by one.\n\tconst invalid = findInvalidTarget(options.providerId, targets);\n\tif (invalid != null) {\n\t\treturn refused(otherWarning(options.providerId, undefined, options.connectionId, invalid));\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(options.providerId, options.connectionId, options.domain);\n\tif (integration == null) {\n\t\treturn unresolvedIntegration(ctx, options.providerId, options.connectionId, options.domain);\n\t}\n\tif (!isGitHostIntegration(integration)) {\n\t\treturn refused(gitHostOnlySurfaceWarning(options.providerId, undefined, options.connectionId, surface));\n\t}\n\n\tconst domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);\n\tconst currentAccount = await getCurrentAccountIdentity(integration, options.connectionId);\n\n\tconst warnings: ProviderWarning[] = [];\n\tlet fetchFailed = false;\n\n\tconst { value: slots, warning } = await runCaptured(\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\t() =>\n\t\t\tintegration.getPullRequestsBatchResult(\n\t\t\t\ttargets.map(t => ({ owner: t.owner, repo: t.repo, number: t.number, project: t.project })),\n\t\t\t\t{ currentAccount: currentAccount },\n\t\t\t\tundefined,\n\t\t\t\toptions.connectionId,\n\t\t\t),\n\t\t// A missing session must surface as a connection warning, including on the primary path, which\n\t\t// otherwise reads it as \"not connected\" and says nothing.\n\t\t{ warnOnMissingSession: true },\n\t);\n\tif (warning != null) {\n\t\tappendDedupedWarning(warnings, warning);\n\t}\n\n\tif (slots == null) {\n\t\t// Dropped, never reported absent: \"unknown\" and \"proven absent\" must stay distinguishable.\n\t\tif (warning == null) {\n\t\t\tappendDedupedWarning(\n\t\t\t\twarnings,\n\t\t\t\totherWarning(\n\t\t\t\t\toptions.providerId,\n\t\t\t\t\tdomain,\n\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t`${surface} is not supported by '${options.providerId}'; resolve pull requests individually instead.`,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\treturn { items: [], warnings: warnings, fetchFailed: true };\n\t}\n\n\tconst items: PullRequestBatchResult[] = [];\n\tfor (let i = 0; i < targets.length; i++) {\n\t\tconst slot = slots[i];\n\t\tif (slot.status === 'rejected') {\n\t\t\t// Dropped, never reported absent, like a whole-call failure.\n\t\t\tappendBatchSlotWarning(warnings, options.providerId, domain, options.connectionId, slot);\n\t\t\tfetchFailed = true;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst pullRequest = slot.value;\n\t\titems.push({ key: targets[i].key, ...(pullRequest != null ? { pullRequest: pullRequest } : {}) });\n\t}\n\n\treturn { items: items, warnings: warnings, fetchFailed: fetchFailed || undefined };\n}\n\nfunction findInvalidTarget(providerId: IntegrationIds, targets: readonly PullRequestBatchTarget[]): string | undefined {\n\tconst requiresProject = isAzureProviderId(providerId);\n\tconst isGitHub = isGitHubProviderId(providerId);\n\tfor (const target of targets) {\n\t\tif (!Number.isSafeInteger(target.number) || target.number <= 0) {\n\t\t\treturn `Pull request batch target '${target.key}' has number ${target.number}; expected a positive integer.`;\n\t\t}\n\t\tif (isGitHub && target.number > githubGraphQLInt32Max) {\n\t\t\treturn `Pull request batch target '${target.key}' has number ${target.number}; GitHub numbers are 32-bit and cannot exceed ${githubGraphQLInt32Max}.`;\n\t\t}\n\t\tif (target.owner.length === 0 || target.repo.length === 0) {\n\t\t\treturn `Pull request batch target '${target.key}' requires a non-empty owner and repo.`;\n\t\t}\n\t\tif (requiresProject && !target.project) {\n\t\t\treturn `Pull request batch target '${target.key}' requires a project for '${providerId}'.`;\n\t\t}\n\t}\n\treturn undefined;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"pullRequestBatch.js","names":[],"sources":["../../../../../plus/integrations/src/reads/pullRequestBatch.ts"],"sourcesContent":["import type { PullRequestShape } from '@gitlens/git/models/pullRequest.js';\nimport type { IntegrationIds } from '../constants.js';\nimport type { PullRequestEtagInclude } from '../models/integration.js';\nimport { pullRequestEtagIncludes } from '../models/integration.js';\nimport { githubGraphQLInt32Max, isAzureProviderId, isGitHubProviderId } from '../providers/providerErrors.js';\nimport type { ProviderResult, ProviderWarning } from '../results.js';\nimport { isGitHostIntegration, isIssuesHostIntegrationId } from '../utils/integration.utils.js';\nimport { readEtaggedBatch } from './batchEtags.js';\nimport type { ProviderReadContext } from './context.js';\nimport { getCurrentAccountIdentity, runCaptured } from './drains.js';\nimport {\n\tfindInvalidPullRequestEtagInclude,\n\tnormalizePullRequestEtagIncludes,\n\tpullRequestEtag,\n\tpullRequestEtagFieldsFromShape,\n} from './etag.js';\nimport { findDuplicateKey, trimCoordinateFields, unresolvedIntegration } from './issueBatch.js';\nimport { gitHostOnlySurfaceWarning, otherWarning, unsupportedWarning } from './warnings.js';\n\n/**\n * The BATCH pull request read: resolve N pull requests, in any state, BY COORDINATE — the pull-request twin of\n * `getIssuesBatch`, and for the same reason. \"Does this exact pull request exist\" is an identity question; a\n * paged list can only answer it by walking the whole scope, so a miss stays unproven and the walk repeats forever.\n * Here an absent slot is a PROVEN ABSENCE a caller can cache.\n *\n * Deliberately NOT routed through `Integration.getPullRequest`: that read answers `undefined` both when not\n * connected and on any failure, and caches through `IntegrationCacheProvider.getPullRequest`, whose key carries no\n * connection and whose by-id bucket never expires a miss.\n *\n * ONE integration call per invocation when no target carries an `etag`, whatever the target count: the manager makes\n * a single `getPullRequestsBatchResult` call and the integration fans its targets out (chunked for GitHub/GHE, one\n * request per target with bounded concurrency everywhere else) and settles each independently, so one bad target\n * never spends more than its own slot — see `GitHostIntegration.getPullRequestsBatchResult`.\n *\n * With etags, up to THREE on a host with a cheap check (GitHub/GHE, GitLab, Azure DevOps and Bitbucket Cloud so far):\n * the cheap check of the targets that carry one, a full read of the targets that don't, started alongside it, and a\n * full read of the targets whose etag no longer matches — see `readEtaggedBatch`. A host without a cheap check still\n * makes one call.\n */\n\n/** One pull request to resolve, identified by coordinate and echoed back under the caller's own `key`. */\nexport interface PullRequestBatchTarget {\n\t/** Caller-owned identifier, echoed on the result so no positional matching is needed. Must be unique. */\n\tkey: string;\n\t/**\n\t * GitHub owner, GitLab namespace path (may be nested), Bitbucket workspace, Bitbucket Data Center project key, or\n\t * Azure DevOps organization.\n\t */\n\towner: string;\n\t/** Repository name or slug. */\n\trepo: string;\n\t/** Pull request number, GitLab merge request iid, or Azure DevOps pull request id. */\n\tnumber: number;\n\t/** Azure DevOps project. Required there, ignored elsewhere. */\n\tproject?: string;\n\t/**\n\t * The {@link PullRequestBatchResult.etag} of the copy the caller holds. When the host can check it cheaply and\n\t * it still matches, the pull request isn't read again and comes back `unchanged`.\n\t */\n\tetag?: string;\n}\n\n/**\n * The answer for one {@link PullRequestBatchTarget}, in one of four states:\n * - `{ key, pullRequest, etag }` — read in full, whatever its state. Every fully read row carries an `etag`,\n * whether or not the caller sent one, so a caller can seed its etags from the reads it already makes.\n * - `{ key, unchanged: true, etag }` — the cheap check proved the caller's copy current, and nothing else was\n * fetched. Only for a target that sent an `etag`, on a host with a cheap check.\n * - `{ key }` — PROVEN ABSENT: it does not exist, or is not visible to this connection.\n * - no row, with `fetchFailed` and a warning — the read could not check. Never treat that as absent.\n */\nexport interface PullRequestBatchResult {\n\tkey: string;\n\t/**\n\t * The resolved pull request, whatever its state, or `undefined` when it PROVABLY does not exist (or is not\n\t * visible to this connection) — or when it is `unchanged`. A target whose read FAILED is not returned at\n\t * all and sets `fetchFailed`.\n\t */\n\tpullRequest?: PullRequestShape;\n\t/**\n\t * Opaque: compare for equality only, never parse. Computed by core from the pull request's change state — its\n\t * state, draft flag, update time and head commit (on Azure DevOps, also a revision of the fields it changes without\n\t * an update time), plus each input the call's `etagIncludes` listed — and NOT the provider's HTTP ETag. Send it\n\t * back as {@link PullRequestBatchTarget.etag}. An etag from another scheme or another `etagIncludes` set simply\n\t * compares unequal and costs a full read, never a false `unchanged`. Reactions are never an input: a host can add\n\t * one without moving the update time, so an `unchanged` copy's `thumbsUpCount` may be stale.\n\t */\n\tetag?: string;\n\t/** The caller's copy is current (its `etag` matched); `pullRequest` is absent because nothing was read. */\n\tunchanged?: true;\n}\n\nexport async function getPullRequestsBatch(\n\tctx: ProviderReadContext,\n\toptions: {\n\t\tproviderId: IntegrationIds;\n\t\ttargets: readonly PullRequestBatchTarget[];\n\t\tconnectionId?: string;\n\t\t/**\n\t\t * Explicit self-managed host domain. Used only when the requested connection has no configured domain;\n\t\t * it must come from the trusted authentication configuration, not repository or remote data.\n\t\t */\n\t\tdomain?: string;\n\t\t/**\n\t\t * Widens every etag to the listed inputs, each of which a host changes without moving the pull request's\n\t\t * update time, and each of which costs its own fields in the cheap check. `'checks'` is the check rollup.\n\t\t * Order and repeats don't matter. An unknown value refuses the whole call. Changes only which etag is\n\t\t * computed; the full read is the same. Calls that differ in this set never match each other's etags.\n\t\t */\n\t\tetagIncludes?: readonly PullRequestEtagInclude[];\n\t},\n): Promise<ProviderResult<PullRequestBatchResult>> {\n\tconst refused = (warning: ProviderWarning): ProviderResult<PullRequestBatchResult> => ({\n\t\titems: [],\n\t\twarnings: [warning],\n\t\tfetchFailed: true,\n\t});\n\tconst surface = 'Batch pull request resolution';\n\n\tif (isIssuesHostIntegrationId(options.providerId)) {\n\t\treturn refused(gitHostOnlySurfaceWarning(options.providerId, undefined, options.connectionId, surface));\n\t}\n\n\tif (options.targets.length === 0) return { items: [], warnings: [] };\n\n\tconst duplicateKey = findDuplicateKey(options.targets);\n\tif (duplicateKey != null) {\n\t\t// Refuses the whole call rather than deduping: two results under one key make matching ambiguous for EVERY\n\t\t// target, not just the repeated one.\n\t\treturn refused(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tundefined,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`Duplicate pull request batch target key '${duplicateKey}'; keys identify results, so each must be unique.`,\n\t\t\t),\n\t\t);\n\t}\n\n\t// Same as a bad target: refused whole before any request, naming the value and what's allowed.\n\tconst invalidInclude = findInvalidPullRequestEtagInclude(options.etagIncludes ?? []);\n\tif (invalidInclude != null) {\n\t\treturn refused(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tundefined,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`Unknown pull request etag include '${invalidInclude}'; expected one of ${pullRequestEtagIncludes.map(i => `'${i}'`).join(', ')}.`,\n\t\t\t),\n\t\t);\n\t}\n\n\t// The validated value must be the one sent to the provider, so every string field is trimmed once here.\n\tconst targets = options.targets.map(trimCoordinateFields);\n\n\t// Deterministic caller bugs, refused like a duplicate key rather than sent upstream to fail one by one.\n\tconst invalid = findInvalidTarget(options.providerId, targets);\n\tif (invalid != null) {\n\t\treturn refused(otherWarning(options.providerId, undefined, options.connectionId, invalid));\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(options.providerId, options.connectionId, options.domain);\n\tif (integration == null) {\n\t\treturn unresolvedIntegration(ctx, options.providerId, options.connectionId, options.domain);\n\t}\n\tif (!isGitHostIntegration(integration)) {\n\t\treturn refused(gitHostOnlySurfaceWarning(options.providerId, undefined, options.connectionId, surface));\n\t}\n\n\tconst domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);\n\tconst etagIncludes = normalizePullRequestEtagIncludes(options.etagIncludes ?? []);\n\tconst toCoordinate = (t: PullRequestBatchTarget) => ({\n\t\towner: t.owner,\n\t\trepo: t.repo,\n\t\tnumber: t.number,\n\t\tproject: t.project,\n\t});\n\n\t// Read once per invocation, however many full reads it takes, and only when one does: a call whose every\n\t// target is unchanged has no row to resolve authorship for.\n\tlet currentAccount: Promise<{ id: string; username?: string } | undefined> | undefined;\n\n\tconst result = await readEtaggedBatch({\n\t\tproviderId: options.providerId,\n\t\tdomain: domain,\n\t\tconnectionId: options.connectionId,\n\t\ttargets: targets,\n\t\tsupportsEtags: integration.supportsPullRequestEtags,\n\t\treadFull: batch =>\n\t\t\trunCaptured(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\tasync () =>\n\t\t\t\t\tintegration.getPullRequestsBatchResult(\n\t\t\t\t\t\tbatch.map(toCoordinate),\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcurrentAccount: await (currentAccount ??= getCurrentAccountIdentity(\n\t\t\t\t\t\t\t\tintegration,\n\t\t\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t\t)),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t),\n\t\t\t\t// A missing session must surface as a connection warning, including on the primary path, which\n\t\t\t\t// otherwise reads it as \"not connected\" and says nothing.\n\t\t\t\t{ warnOnMissingSession: true },\n\t\t\t),\n\t\treadEtagFields: batch =>\n\t\t\trunCaptured(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t() =>\n\t\t\t\t\tintegration.getPullRequestsEtagFieldsResult(\n\t\t\t\t\t\tbatch.map(toCoordinate),\n\t\t\t\t\t\t{ etagIncludes: etagIncludes },\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t),\n\t\t\t\t{ warnOnMissingSession: true },\n\t\t\t),\n\t\titemEtag: pr => pullRequestEtag(pullRequestEtagFieldsFromShape(pr), etagIncludes),\n\t\tfieldsEtag: fields => pullRequestEtag(fields, etagIncludes),\n\t\tunsupportedWarning: () =>\n\t\t\tunsupportedWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`${surface} is not supported by '${options.providerId}'; resolve pull requests individually instead.`,\n\t\t\t),\n\t});\n\n\treturn {\n\t\titems: result.items.map(row => ({\n\t\t\tkey: row.key,\n\t\t\t...(row.value != null ? { pullRequest: row.value } : {}),\n\t\t\t...(row.unchanged ? { unchanged: true as const } : {}),\n\t\t\t...(row.etag != null ? { etag: row.etag } : {}),\n\t\t})),\n\t\twarnings: result.warnings,\n\t\tfetchFailed: result.fetchFailed,\n\t};\n}\n\nfunction findInvalidTarget(providerId: IntegrationIds, targets: readonly PullRequestBatchTarget[]): string | undefined {\n\tconst requiresProject = isAzureProviderId(providerId);\n\tconst isGitHub = isGitHubProviderId(providerId);\n\tfor (const target of targets) {\n\t\tif (!Number.isSafeInteger(target.number) || target.number <= 0) {\n\t\t\treturn `Pull request batch target '${target.key}' has number ${target.number}; expected a positive integer.`;\n\t\t}\n\t\tif (isGitHub && target.number > githubGraphQLInt32Max) {\n\t\t\treturn `Pull request batch target '${target.key}' has number ${target.number}; GitHub numbers are 32-bit and cannot exceed ${githubGraphQLInt32Max}.`;\n\t\t}\n\t\tif (target.owner.length === 0 || target.repo.length === 0) {\n\t\t\treturn `Pull request batch target '${target.key}' requires a non-empty owner and repo.`;\n\t\t}\n\t\tif (requiresProject && !target.project) {\n\t\t\treturn `Pull request batch target '${target.key}' requires a project for '${providerId}'.`;\n\t\t}\n\t}\n\treturn undefined;\n}\n"],"mappings":"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"}
@@ -39,8 +39,11 @@ export interface PullRequestBranchResult {
39
39
  */
40
40
  pullRequests: PullRequestShape[];
41
41
  /**
42
- * Set when more pull requests may match than were returned: more matched than the cap, or the host held more
43
- * pull requests of this branch name than it returned and some of those might have matched.
42
+ * Set when a pull request that wasn't returned could still match: more matched than the cap, or the host held
43
+ * more pull requests of this branch name than it returned and couldn't rule them out. GitHub/GHE rule them out
44
+ * with a second request filtered by head owner, so there it means more than the cap truly match. That request
45
+ * can't see a pull request from a since-deleted fork, so on GitHub/GHE such a pull request outside the first
46
+ * request's page can be missing even when this is not set.
44
47
  */
45
48
  truncated?: boolean;
46
49
  }
@@ -3,9 +3,10 @@ import "../constants.js";
3
3
  import { isAzureProviderId } from "../providers/providerErrors.js";
4
4
  import { appendDedupedWarning } from "../results.js";
5
5
  import { isGitHostIntegration, isIssuesHostIntegrationId } from "../utils/integration.utils.js";
6
- import { gitHostOnlySurfaceWarning, otherWarning } from "./warnings.js";
6
+ import { gitHostOnlySurfaceWarning, otherWarning, unsupportedWarning } from "./warnings.js";
7
7
  import { getCurrentAccountIdentity, runCaptured } from "./drains.js";
8
- import { appendBatchSlotWarning, findDuplicateKey, trimCoordinateFields, unresolvedIntegration } from "./issueBatch.js";
8
+ import { appendBatchSlotWarning } from "./batchEtags.js";
9
+ import { findDuplicateKey, trimCoordinateFields, unresolvedIntegration } from "./issueBatch.js";
9
10
  //#region ../plus/integrations/src/reads/pullRequestBranches.ts
10
11
  /**
11
12
  * The pull-requests-by-branch read: for each branch, every pull request whose head is that branch, in any state.
@@ -58,7 +59,7 @@ async function getPullRequestsForBranches(ctx, options) {
58
59
  }, void 0, options.connectionId), { warnOnMissingSession: true });
59
60
  if (warning != null) appendDedupedWarning(warnings, warning);
60
61
  if (slots == null) {
61
- if (warning == null) appendDedupedWarning(warnings, otherWarning(options.providerId, domain, options.connectionId, `${surface} is not supported by '${options.providerId}'.`));
62
+ if (warning == null) appendDedupedWarning(warnings, unsupportedWarning(options.providerId, domain, options.connectionId, `${surface} is not supported by '${options.providerId}'.`));
62
63
  return {
63
64
  items: [],
64
65
  warnings,
@@ -1 +1 @@
1
- {"version":3,"file":"pullRequestBranches.js","names":[],"sources":["../../../../../plus/integrations/src/reads/pullRequestBranches.ts"],"sourcesContent":["import type { PullRequestShape } from '@gitlens/git/models/pullRequest.js';\nimport { equalsIgnoreCase } from '@gitlens/utils/string.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { GitSelfManagedHostIntegrationId } from '../constants.js';\nimport { isAzureProviderId } from '../providers/providerErrors.js';\nimport type { ProviderResult, ProviderWarning } from '../results.js';\nimport { appendDedupedWarning } from '../results.js';\nimport { isGitHostIntegration, isIssuesHostIntegrationId } from '../utils/integration.utils.js';\nimport type { ProviderReadContext } from './context.js';\nimport { getCurrentAccountIdentity, runCaptured } from './drains.js';\nimport { appendBatchSlotWarning, findDuplicateKey, trimCoordinateFields, unresolvedIntegration } from './issueBatch.js';\nimport { gitHostOnlySurfaceWarning, otherWarning } from './warnings.js';\n\n/**\n * The pull-requests-by-branch read: for each branch, every pull request whose head is that branch, in any state.\n * A relationship-free point read — unlike the account-wide sweeps, it finds a teammate's pull request from the\n * user's branch — so an empty list is a PROVEN \"none\" a caller can cache.\n *\n * ONE integration call per invocation, whatever the target count, settling each target independently — see\n * `GitHostIntegration.getPullRequestsForBranchesResult`. Uncached, and not routed through the integration's\n * `getPullRequestForBranch`, which caches through `IntegrationCacheProvider.getPullRequestForBranch`, answers one\n * pull request, and on several hosts looks the branch ref up, answering \"none\" once a merged branch is deleted.\n */\n\n/** Pull requests returned per target. */\nconst pullRequestsForBranchLimit = 10;\n\n/** One branch whose pull requests to find, echoed back under the caller's own `key`. */\nexport interface PullRequestBranchTarget {\n\t/** Caller-owned identifier, echoed on the result so no positional matching is needed. Must be unique. */\n\tkey: string;\n\t/**\n\t * The repository the pull requests are OPENED AGAINST (their base), as in `PullRequestBatchTarget`: GitHub\n\t * owner, GitLab namespace path (may be nested), Bitbucket workspace, Bitbucket Data Center project key, or Azure\n\t * DevOps organization.\n\t */\n\towner: string;\n\t/** Repository name or slug. */\n\trepo: string;\n\t/** Azure DevOps project. Required there, ignored elsewhere. */\n\tproject?: string;\n\t/** The head branch's short name, without `refs/heads/`. */\n\tbranch: string;\n\t/**\n\t * Owner (GitHub user or org, GitLab namespace, Bitbucket workspace) of the repository the branch lives in, when\n\t * it's a fork. Omitted, or equal to `owner` (ignoring case), means the branch lives in the base repository itself,\n\t * so a caller can pass the owner of whichever remote the branch was pushed to.\n\t *\n\t * A different owner is refused on Bitbucket Data Center and Azure DevOps, where a fork can't be found by its\n\t * owner.\n\t */\n\theadOwner?: string;\n}\n\n/** The answer for one {@link PullRequestBranchTarget}. */\nexport interface PullRequestBranchResult {\n\tkey: string;\n\t/**\n\t * Every pull request whose head is this branch, in ANY state — open, closed and merged — most recently updated\n\t * first, up to a per-target cap. An empty array is a PROVEN \"none\" unless `truncated` is set. A target whose\n\t * read failed is not returned at all.\n\t */\n\tpullRequests: PullRequestShape[];\n\t/**\n\t * Set when more pull requests may match than were returned: more matched than the cap, or the host held more\n\t * pull requests of this branch name than it returned and some of those might have matched.\n\t */\n\ttruncated?: boolean;\n}\n\nexport async function getPullRequestsForBranches(\n\tctx: ProviderReadContext,\n\toptions: {\n\t\tproviderId: IntegrationIds;\n\t\ttargets: readonly PullRequestBranchTarget[];\n\t\tconnectionId?: string;\n\t\t/**\n\t\t * Explicit self-managed host domain. Used only when the requested connection has no configured domain;\n\t\t * it must come from the trusted authentication configuration, not repository or remote data.\n\t\t */\n\t\tdomain?: string;\n\t},\n): Promise<ProviderResult<PullRequestBranchResult>> {\n\tconst refused = (warning: ProviderWarning): ProviderResult<PullRequestBranchResult> => ({\n\t\titems: [],\n\t\twarnings: [warning],\n\t\tfetchFailed: true,\n\t});\n\tconst surface = 'Pull requests by branch';\n\n\tif (isIssuesHostIntegrationId(options.providerId)) {\n\t\treturn refused(gitHostOnlySurfaceWarning(options.providerId, undefined, options.connectionId, surface));\n\t}\n\n\tif (options.targets.length === 0) return { items: [], warnings: [] };\n\n\tconst duplicateKey = findDuplicateKey(options.targets);\n\tif (duplicateKey != null) {\n\t\t// Refuses the whole call rather than deduping: two results under one key make matching ambiguous for EVERY\n\t\t// target, not just the repeated one.\n\t\treturn refused(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tundefined,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`Duplicate pull request branch target key '${duplicateKey}'; keys identify results, so each must be unique.`,\n\t\t\t),\n\t\t);\n\t}\n\n\t// The validated value must be the one sent to the provider, so every string field is trimmed once here.\n\t// `branch` is left alone: a git ref name can't carry surrounding whitespace, so a padded one is refused below\n\t// rather than being silently repaired.\n\tconst targets = options.targets.map(target => ({\n\t\t...trimCoordinateFields(target),\n\t\theadOwner: target.headOwner?.trim(),\n\t}));\n\n\t// Deterministic caller bugs, refused like a duplicate key rather than sent upstream to fail one by one.\n\tconst invalid = findInvalidTarget(options.providerId, targets);\n\tif (invalid != null) {\n\t\treturn refused(otherWarning(options.providerId, undefined, options.connectionId, invalid));\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(options.providerId, options.connectionId, options.domain);\n\tif (integration == null) {\n\t\treturn unresolvedIntegration(ctx, options.providerId, options.connectionId, options.domain);\n\t}\n\tif (!isGitHostIntegration(integration)) {\n\t\treturn refused(gitHostOnlySurfaceWarning(options.providerId, undefined, options.connectionId, surface));\n\t}\n\n\tconst domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);\n\tconst currentAccount = await getCurrentAccountIdentity(integration, options.connectionId);\n\n\tconst warnings: ProviderWarning[] = [];\n\tlet fetchFailed = false;\n\n\tconst { value: slots, warning } = await runCaptured(\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\t() =>\n\t\t\tintegration.getPullRequestsForBranchesResult(\n\t\t\t\ttargets.map(t => ({\n\t\t\t\t\towner: t.owner,\n\t\t\t\t\trepo: t.repo,\n\t\t\t\t\tproject: t.project,\n\t\t\t\t\tbranch: t.branch,\n\t\t\t\t\theadOwner: forkOwner(t),\n\t\t\t\t})),\n\t\t\t\t{ currentAccount: currentAccount, limit: pullRequestsForBranchLimit },\n\t\t\t\tundefined,\n\t\t\t\toptions.connectionId,\n\t\t\t),\n\t\t// A missing session must surface as a connection warning, including on the primary path, which\n\t\t// otherwise reads it as \"not connected\" and says nothing.\n\t\t{ warnOnMissingSession: true },\n\t);\n\tif (warning != null) {\n\t\tappendDedupedWarning(warnings, warning);\n\t}\n\n\tif (slots == null) {\n\t\t// Dropped, never reported as \"none\": \"unknown\" and \"proven none\" must stay distinguishable.\n\t\tif (warning == null) {\n\t\t\tappendDedupedWarning(\n\t\t\t\twarnings,\n\t\t\t\totherWarning(\n\t\t\t\t\toptions.providerId,\n\t\t\t\t\tdomain,\n\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t`${surface} is not supported by '${options.providerId}'.`,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\treturn { items: [], warnings: warnings, fetchFailed: true };\n\t}\n\n\tconst items: PullRequestBranchResult[] = [];\n\tfor (let i = 0; i < targets.length; i++) {\n\t\tconst slot = slots[i];\n\t\tif (slot.status === 'rejected') {\n\t\t\t// Dropped, never reported as \"none\", like a whole-call failure.\n\t\t\tappendBatchSlotWarning(warnings, options.providerId, domain, options.connectionId, slot);\n\t\t\tfetchFailed = true;\n\t\t\tcontinue;\n\t\t}\n\n\t\titems.push({\n\t\t\tkey: targets[i].key,\n\t\t\tpullRequests: slot.value.pullRequests,\n\t\t\t...(slot.value.truncated ? { truncated: true } : {}),\n\t\t});\n\t}\n\n\treturn { items: items, warnings: warnings, fetchFailed: fetchFailed || undefined };\n}\n\n/** The owner of the fork the branch lives in, or `undefined` for a branch in the base repository. */\nfunction forkOwner(target: PullRequestBranchTarget): string | undefined {\n\treturn target.headOwner == null || equalsIgnoreCase(target.headOwner, target.owner) ? undefined : target.headOwner;\n}\n\nfunction findInvalidTarget(\n\tproviderId: IntegrationIds,\n\ttargets: readonly PullRequestBranchTarget[],\n): string | undefined {\n\tconst isAzure = isAzureProviderId(providerId);\n\tconst isBitbucketServer = providerId === GitSelfManagedHostIntegrationId.BitbucketServer;\n\tfor (const target of targets) {\n\t\tif (target.owner.length === 0 || target.repo.length === 0) {\n\t\t\treturn `Pull request branch target '${target.key}' requires a non-empty owner and repo.`;\n\t\t}\n\t\tif (target.branch.trim().length === 0) {\n\t\t\treturn `Pull request branch target '${target.key}' requires a non-empty branch.`;\n\t\t}\n\t\t// Sent as is, a padded name matches no branch and answers a cacheable \"none\".\n\t\tif (target.branch !== target.branch.trim()) {\n\t\t\treturn `Pull request branch target '${target.key}' has branch '${target.branch}'; a branch name can't start or end with whitespace.`;\n\t\t}\n\t\tif (target.branch.startsWith('refs/')) {\n\t\t\treturn `Pull request branch target '${target.key}' has branch '${target.branch}'; pass the branch's short name, without 'refs/heads/'.`;\n\t\t}\n\t\tif (isAzure && !target.project) {\n\t\t\treturn `Pull request branch target '${target.key}' requires a project for '${providerId}'.`;\n\t\t}\n\t\tif (target.headOwner?.length === 0) {\n\t\t\treturn `Pull request branch target '${target.key}' has an empty headOwner; omit it for a branch in the base repository.`;\n\t\t}\n\t\tif (forkOwner(target) != null) {\n\t\t\tif (isAzure) {\n\t\t\t\treturn `Pull request branch target '${target.key}' has a headOwner other than its owner, which '${providerId}' can't honor: an Azure DevOps fork shares its organization, so its owner can't identify it.`;\n\t\t\t}\n\t\t\tif (isBitbucketServer) {\n\t\t\t\treturn `Pull request branch target '${target.key}' has a headOwner other than its owner, which '${providerId}' can't honor: Bitbucket Data Center finds a branch's pull requests only through the repository the branch lives in, and a fork's repository can't be derived from its owner.`;\n\t\t\t}\n\t\t}\n\t}\n\treturn undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAyBA,MAAM,6BAA6B;AA6CnC,eAAsB,2BACrB,KACA,SAUmD;CACnD,MAAM,WAAW,aAAuE;EACvF,OAAO,CAAC;EACR,UAAU,CAAC,OAAO;EAClB,aAAa;CACd;CACA,MAAM,UAAU;CAEhB,IAAI,0BAA0B,QAAQ,UAAU,GAC/C,OAAO,QAAQ,0BAA0B,QAAQ,YAAY,KAAA,GAAW,QAAQ,cAAc,OAAO,CAAC;CAGvG,IAAI,QAAQ,QAAQ,WAAW,GAAG,OAAO;EAAE,OAAO,CAAC;EAAG,UAAU,CAAC;CAAE;CAEnE,MAAM,eAAe,iBAAiB,QAAQ,OAAO;CACrD,IAAI,gBAAgB,MAGnB,OAAO,QACN,aACC,QAAQ,YACR,KAAA,GACA,QAAQ,cACR,6CAA6C,aAAa,kDAC3D,CACD;CAMD,MAAM,UAAU,QAAQ,QAAQ,KAAI,YAAW;EAC9C,GAAG,qBAAqB,MAAM;EAC9B,WAAW,OAAO,WAAW,KAAK;CACnC,EAAE;CAGF,MAAM,UAAU,kBAAkB,QAAQ,YAAY,OAAO;CAC7D,IAAI,WAAW,MACd,OAAO,QAAQ,aAAa,QAAQ,YAAY,KAAA,GAAW,QAAQ,cAAc,OAAO,CAAC;CAG1F,MAAM,cAAc,MAAM,IAAI,sBAAsB,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;CAC5G,IAAI,eAAe,MAClB,OAAO,sBAAsB,KAAK,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;CAE3F,IAAI,CAAC,qBAAqB,WAAW,GACpC,OAAO,QAAQ,0BAA0B,QAAQ,YAAY,KAAA,GAAW,QAAQ,cAAc,OAAO,CAAC;CAGvG,MAAM,SAAS,IAAI,cAAc,aAAa,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;CACtG,MAAM,iBAAiB,MAAM,0BAA0B,aAAa,QAAQ,YAAY;CAExF,MAAM,WAA8B,CAAC;CACrC,IAAI,cAAc;CAElB,MAAM,EAAE,OAAO,OAAO,YAAY,MAAM,YACvC,QAAQ,YACR,QACA,QAAQ,oBAEP,YAAY,iCACX,QAAQ,KAAI,OAAM;EACjB,OAAO,EAAE;EACT,MAAM,EAAE;EACR,SAAS,EAAE;EACX,QAAQ,EAAE;EACV,WAAW,UAAU,CAAC;CACvB,EAAE,GACF;EAAkB;EAAgB,OAAO;CAA2B,GACpE,KAAA,GACA,QAAQ,YACT,GAGD,EAAE,sBAAsB,KAAK,CAC9B;CACA,IAAI,WAAW,MACd,qBAAqB,UAAU,OAAO;CAGvC,IAAI,SAAS,MAAM;EAElB,IAAI,WAAW,MACd,qBACC,UACA,aACC,QAAQ,YACR,QACA,QAAQ,cACR,GAAG,QAAQ,wBAAwB,QAAQ,WAAW,GACvD,CACD;EAED,OAAO;GAAE,OAAO,CAAC;GAAa;GAAU,aAAa;EAAK;CAC3D;CAEA,MAAM,QAAmC,CAAC;CAC1C,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;EACxC,MAAM,OAAO,MAAM;EACnB,IAAI,KAAK,WAAW,YAAY;GAE/B,uBAAuB,UAAU,QAAQ,YAAY,QAAQ,QAAQ,cAAc,IAAI;GACvF,cAAc;GACd;EACD;EAEA,MAAM,KAAK;GACV,KAAK,QAAQ,EAAE,CAAC;GAChB,cAAc,KAAK,MAAM;GACzB,GAAI,KAAK,MAAM,YAAY,EAAE,WAAW,KAAK,IAAI,CAAC;EACnD,CAAC;CACF;CAEA,OAAO;EAAS;EAAiB;EAAU,aAAa,eAAe,KAAA;CAAU;AAClF;;AAGA,SAAS,UAAU,QAAqD;CACvE,OAAO,OAAO,aAAa,QAAQ,iBAAiB,OAAO,WAAW,OAAO,KAAK,IAAI,KAAA,IAAY,OAAO;AAC1G;AAEA,SAAS,kBACR,YACA,SACqB;CACrB,MAAM,UAAU,kBAAkB,UAAU;CAC5C,MAAM,oBAAoB,eAAA;CAC1B,KAAK,MAAM,UAAU,SAAS;EAC7B,IAAI,OAAO,MAAM,WAAW,KAAK,OAAO,KAAK,WAAW,GACvD,OAAO,+BAA+B,OAAO,IAAI;EAElD,IAAI,OAAO,OAAO,KAAK,CAAC,CAAC,WAAW,GACnC,OAAO,+BAA+B,OAAO,IAAI;EAGlD,IAAI,OAAO,WAAW,OAAO,OAAO,KAAK,GACxC,OAAO,+BAA+B,OAAO,IAAI,gBAAgB,OAAO,OAAO;EAEhF,IAAI,OAAO,OAAO,WAAW,OAAO,GACnC,OAAO,+BAA+B,OAAO,IAAI,gBAAgB,OAAO,OAAO;EAEhF,IAAI,WAAW,CAAC,OAAO,SACtB,OAAO,+BAA+B,OAAO,IAAI,4BAA4B,WAAW;EAEzF,IAAI,OAAO,WAAW,WAAW,GAChC,OAAO,+BAA+B,OAAO,IAAI;EAElD,IAAI,UAAU,MAAM,KAAK,MAAM;GAC9B,IAAI,SACH,OAAO,+BAA+B,OAAO,IAAI,iDAAiD,WAAW;GAE9G,IAAI,mBACH,OAAO,+BAA+B,OAAO,IAAI,iDAAiD,WAAW;EAE/G;CACD;AAED"}
1
+ {"version":3,"file":"pullRequestBranches.js","names":[],"sources":["../../../../../plus/integrations/src/reads/pullRequestBranches.ts"],"sourcesContent":["import type { PullRequestShape } from '@gitlens/git/models/pullRequest.js';\nimport { equalsIgnoreCase } from '@gitlens/utils/string.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { GitSelfManagedHostIntegrationId } from '../constants.js';\nimport { isAzureProviderId } from '../providers/providerErrors.js';\nimport type { ProviderResult, ProviderWarning } from '../results.js';\nimport { appendDedupedWarning } from '../results.js';\nimport { isGitHostIntegration, isIssuesHostIntegrationId } from '../utils/integration.utils.js';\nimport { appendBatchSlotWarning } from './batchEtags.js';\nimport type { ProviderReadContext } from './context.js';\nimport { getCurrentAccountIdentity, runCaptured } from './drains.js';\nimport { findDuplicateKey, trimCoordinateFields, unresolvedIntegration } from './issueBatch.js';\nimport { gitHostOnlySurfaceWarning, otherWarning, unsupportedWarning } from './warnings.js';\n\n/**\n * The pull-requests-by-branch read: for each branch, every pull request whose head is that branch, in any state.\n * A relationship-free point read — unlike the account-wide sweeps, it finds a teammate's pull request from the\n * user's branch — so an empty list is a PROVEN \"none\" a caller can cache.\n *\n * ONE integration call per invocation, whatever the target count, settling each target independently — see\n * `GitHostIntegration.getPullRequestsForBranchesResult`. Uncached, and not routed through the integration's\n * `getPullRequestForBranch`, which caches through `IntegrationCacheProvider.getPullRequestForBranch`, answers one\n * pull request, and on several hosts looks the branch ref up, answering \"none\" once a merged branch is deleted.\n */\n\n/** Pull requests returned per target. */\nconst pullRequestsForBranchLimit = 10;\n\n/** One branch whose pull requests to find, echoed back under the caller's own `key`. */\nexport interface PullRequestBranchTarget {\n\t/** Caller-owned identifier, echoed on the result so no positional matching is needed. Must be unique. */\n\tkey: string;\n\t/**\n\t * The repository the pull requests are OPENED AGAINST (their base), as in `PullRequestBatchTarget`: GitHub\n\t * owner, GitLab namespace path (may be nested), Bitbucket workspace, Bitbucket Data Center project key, or Azure\n\t * DevOps organization.\n\t */\n\towner: string;\n\t/** Repository name or slug. */\n\trepo: string;\n\t/** Azure DevOps project. Required there, ignored elsewhere. */\n\tproject?: string;\n\t/** The head branch's short name, without `refs/heads/`. */\n\tbranch: string;\n\t/**\n\t * Owner (GitHub user or org, GitLab namespace, Bitbucket workspace) of the repository the branch lives in, when\n\t * it's a fork. Omitted, or equal to `owner` (ignoring case), means the branch lives in the base repository itself,\n\t * so a caller can pass the owner of whichever remote the branch was pushed to.\n\t *\n\t * A different owner is refused on Bitbucket Data Center and Azure DevOps, where a fork can't be found by its\n\t * owner.\n\t */\n\theadOwner?: string;\n}\n\n/** The answer for one {@link PullRequestBranchTarget}. */\nexport interface PullRequestBranchResult {\n\tkey: string;\n\t/**\n\t * Every pull request whose head is this branch, in ANY state — open, closed and merged — most recently updated\n\t * first, up to a per-target cap. An empty array is a PROVEN \"none\" unless `truncated` is set. A target whose\n\t * read failed is not returned at all.\n\t */\n\tpullRequests: PullRequestShape[];\n\t/**\n\t * Set when a pull request that wasn't returned could still match: more matched than the cap, or the host held\n\t * more pull requests of this branch name than it returned and couldn't rule them out. GitHub/GHE rule them out\n\t * with a second request filtered by head owner, so there it means more than the cap truly match. That request\n\t * can't see a pull request from a since-deleted fork, so on GitHub/GHE such a pull request outside the first\n\t * request's page can be missing even when this is not set.\n\t */\n\ttruncated?: boolean;\n}\n\nexport async function getPullRequestsForBranches(\n\tctx: ProviderReadContext,\n\toptions: {\n\t\tproviderId: IntegrationIds;\n\t\ttargets: readonly PullRequestBranchTarget[];\n\t\tconnectionId?: string;\n\t\t/**\n\t\t * Explicit self-managed host domain. Used only when the requested connection has no configured domain;\n\t\t * it must come from the trusted authentication configuration, not repository or remote data.\n\t\t */\n\t\tdomain?: string;\n\t},\n): Promise<ProviderResult<PullRequestBranchResult>> {\n\tconst refused = (warning: ProviderWarning): ProviderResult<PullRequestBranchResult> => ({\n\t\titems: [],\n\t\twarnings: [warning],\n\t\tfetchFailed: true,\n\t});\n\tconst surface = 'Pull requests by branch';\n\n\tif (isIssuesHostIntegrationId(options.providerId)) {\n\t\treturn refused(gitHostOnlySurfaceWarning(options.providerId, undefined, options.connectionId, surface));\n\t}\n\n\tif (options.targets.length === 0) return { items: [], warnings: [] };\n\n\tconst duplicateKey = findDuplicateKey(options.targets);\n\tif (duplicateKey != null) {\n\t\t// Refuses the whole call rather than deduping: two results under one key make matching ambiguous for EVERY\n\t\t// target, not just the repeated one.\n\t\treturn refused(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tundefined,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`Duplicate pull request branch target key '${duplicateKey}'; keys identify results, so each must be unique.`,\n\t\t\t),\n\t\t);\n\t}\n\n\t// The validated value must be the one sent to the provider, so every string field is trimmed once here.\n\t// `branch` is left alone: a git ref name can't carry surrounding whitespace, so a padded one is refused below\n\t// rather than being silently repaired.\n\tconst targets = options.targets.map(target => ({\n\t\t...trimCoordinateFields(target),\n\t\theadOwner: target.headOwner?.trim(),\n\t}));\n\n\t// Deterministic caller bugs, refused like a duplicate key rather than sent upstream to fail one by one.\n\tconst invalid = findInvalidTarget(options.providerId, targets);\n\tif (invalid != null) {\n\t\treturn refused(otherWarning(options.providerId, undefined, options.connectionId, invalid));\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(options.providerId, options.connectionId, options.domain);\n\tif (integration == null) {\n\t\treturn unresolvedIntegration(ctx, options.providerId, options.connectionId, options.domain);\n\t}\n\tif (!isGitHostIntegration(integration)) {\n\t\treturn refused(gitHostOnlySurfaceWarning(options.providerId, undefined, options.connectionId, surface));\n\t}\n\n\tconst domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);\n\tconst currentAccount = await getCurrentAccountIdentity(integration, options.connectionId);\n\n\tconst warnings: ProviderWarning[] = [];\n\tlet fetchFailed = false;\n\n\tconst { value: slots, warning } = await runCaptured(\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\t() =>\n\t\t\tintegration.getPullRequestsForBranchesResult(\n\t\t\t\ttargets.map(t => ({\n\t\t\t\t\towner: t.owner,\n\t\t\t\t\trepo: t.repo,\n\t\t\t\t\tproject: t.project,\n\t\t\t\t\tbranch: t.branch,\n\t\t\t\t\theadOwner: forkOwner(t),\n\t\t\t\t})),\n\t\t\t\t{ currentAccount: currentAccount, limit: pullRequestsForBranchLimit },\n\t\t\t\tundefined,\n\t\t\t\toptions.connectionId,\n\t\t\t),\n\t\t// A missing session must surface as a connection warning, including on the primary path, which\n\t\t// otherwise reads it as \"not connected\" and says nothing.\n\t\t{ warnOnMissingSession: true },\n\t);\n\tif (warning != null) {\n\t\tappendDedupedWarning(warnings, warning);\n\t}\n\n\tif (slots == null) {\n\t\t// Dropped, never reported as \"none\": \"unknown\" and \"proven none\" must stay distinguishable.\n\t\tif (warning == null) {\n\t\t\tappendDedupedWarning(\n\t\t\t\twarnings,\n\t\t\t\tunsupportedWarning(\n\t\t\t\t\toptions.providerId,\n\t\t\t\t\tdomain,\n\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t`${surface} is not supported by '${options.providerId}'.`,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\treturn { items: [], warnings: warnings, fetchFailed: true };\n\t}\n\n\tconst items: PullRequestBranchResult[] = [];\n\tfor (let i = 0; i < targets.length; i++) {\n\t\tconst slot = slots[i];\n\t\tif (slot.status === 'rejected') {\n\t\t\t// Dropped, never reported as \"none\", like a whole-call failure.\n\t\t\tappendBatchSlotWarning(warnings, options.providerId, domain, options.connectionId, slot);\n\t\t\tfetchFailed = true;\n\t\t\tcontinue;\n\t\t}\n\n\t\titems.push({\n\t\t\tkey: targets[i].key,\n\t\t\tpullRequests: slot.value.pullRequests,\n\t\t\t...(slot.value.truncated ? { truncated: true } : {}),\n\t\t});\n\t}\n\n\treturn { items: items, warnings: warnings, fetchFailed: fetchFailed || undefined };\n}\n\n/** The owner of the fork the branch lives in, or `undefined` for a branch in the base repository. */\nfunction forkOwner(target: PullRequestBranchTarget): string | undefined {\n\treturn target.headOwner == null || equalsIgnoreCase(target.headOwner, target.owner) ? undefined : target.headOwner;\n}\n\nfunction findInvalidTarget(\n\tproviderId: IntegrationIds,\n\ttargets: readonly PullRequestBranchTarget[],\n): string | undefined {\n\tconst isAzure = isAzureProviderId(providerId);\n\tconst isBitbucketServer = providerId === GitSelfManagedHostIntegrationId.BitbucketServer;\n\tfor (const target of targets) {\n\t\tif (target.owner.length === 0 || target.repo.length === 0) {\n\t\t\treturn `Pull request branch target '${target.key}' requires a non-empty owner and repo.`;\n\t\t}\n\t\tif (target.branch.trim().length === 0) {\n\t\t\treturn `Pull request branch target '${target.key}' requires a non-empty branch.`;\n\t\t}\n\t\t// Sent as is, a padded name matches no branch and answers a cacheable \"none\".\n\t\tif (target.branch !== target.branch.trim()) {\n\t\t\treturn `Pull request branch target '${target.key}' has branch '${target.branch}'; a branch name can't start or end with whitespace.`;\n\t\t}\n\t\tif (target.branch.startsWith('refs/')) {\n\t\t\treturn `Pull request branch target '${target.key}' has branch '${target.branch}'; pass the branch's short name, without 'refs/heads/'.`;\n\t\t}\n\t\tif (isAzure && !target.project) {\n\t\t\treturn `Pull request branch target '${target.key}' requires a project for '${providerId}'.`;\n\t\t}\n\t\tif (target.headOwner?.length === 0) {\n\t\t\treturn `Pull request branch target '${target.key}' has an empty headOwner; omit it for a branch in the base repository.`;\n\t\t}\n\t\tif (forkOwner(target) != null) {\n\t\t\tif (isAzure) {\n\t\t\t\treturn `Pull request branch target '${target.key}' has a headOwner other than its owner, which '${providerId}' can't honor: an Azure DevOps fork shares its organization, so its owner can't identify it.`;\n\t\t\t}\n\t\t\tif (isBitbucketServer) {\n\t\t\t\treturn `Pull request branch target '${target.key}' has a headOwner other than its owner, which '${providerId}' can't honor: Bitbucket Data Center finds a branch's pull requests only through the repository the branch lives in, and a fork's repository can't be derived from its owner.`;\n\t\t\t}\n\t\t}\n\t}\n\treturn undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA0BA,MAAM,6BAA6B;AAgDnC,eAAsB,2BACrB,KACA,SAUmD;CACnD,MAAM,WAAW,aAAuE;EACvF,OAAO,CAAC;EACR,UAAU,CAAC,OAAO;EAClB,aAAa;CACd;CACA,MAAM,UAAU;CAEhB,IAAI,0BAA0B,QAAQ,UAAU,GAC/C,OAAO,QAAQ,0BAA0B,QAAQ,YAAY,KAAA,GAAW,QAAQ,cAAc,OAAO,CAAC;CAGvG,IAAI,QAAQ,QAAQ,WAAW,GAAG,OAAO;EAAE,OAAO,CAAC;EAAG,UAAU,CAAC;CAAE;CAEnE,MAAM,eAAe,iBAAiB,QAAQ,OAAO;CACrD,IAAI,gBAAgB,MAGnB,OAAO,QACN,aACC,QAAQ,YACR,KAAA,GACA,QAAQ,cACR,6CAA6C,aAAa,kDAC3D,CACD;CAMD,MAAM,UAAU,QAAQ,QAAQ,KAAI,YAAW;EAC9C,GAAG,qBAAqB,MAAM;EAC9B,WAAW,OAAO,WAAW,KAAK;CACnC,EAAE;CAGF,MAAM,UAAU,kBAAkB,QAAQ,YAAY,OAAO;CAC7D,IAAI,WAAW,MACd,OAAO,QAAQ,aAAa,QAAQ,YAAY,KAAA,GAAW,QAAQ,cAAc,OAAO,CAAC;CAG1F,MAAM,cAAc,MAAM,IAAI,sBAAsB,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;CAC5G,IAAI,eAAe,MAClB,OAAO,sBAAsB,KAAK,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;CAE3F,IAAI,CAAC,qBAAqB,WAAW,GACpC,OAAO,QAAQ,0BAA0B,QAAQ,YAAY,KAAA,GAAW,QAAQ,cAAc,OAAO,CAAC;CAGvG,MAAM,SAAS,IAAI,cAAc,aAAa,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;CACtG,MAAM,iBAAiB,MAAM,0BAA0B,aAAa,QAAQ,YAAY;CAExF,MAAM,WAA8B,CAAC;CACrC,IAAI,cAAc;CAElB,MAAM,EAAE,OAAO,OAAO,YAAY,MAAM,YACvC,QAAQ,YACR,QACA,QAAQ,oBAEP,YAAY,iCACX,QAAQ,KAAI,OAAM;EACjB,OAAO,EAAE;EACT,MAAM,EAAE;EACR,SAAS,EAAE;EACX,QAAQ,EAAE;EACV,WAAW,UAAU,CAAC;CACvB,EAAE,GACF;EAAkB;EAAgB,OAAO;CAA2B,GACpE,KAAA,GACA,QAAQ,YACT,GAGD,EAAE,sBAAsB,KAAK,CAC9B;CACA,IAAI,WAAW,MACd,qBAAqB,UAAU,OAAO;CAGvC,IAAI,SAAS,MAAM;EAElB,IAAI,WAAW,MACd,qBACC,UACA,mBACC,QAAQ,YACR,QACA,QAAQ,cACR,GAAG,QAAQ,wBAAwB,QAAQ,WAAW,GACvD,CACD;EAED,OAAO;GAAE,OAAO,CAAC;GAAa;GAAU,aAAa;EAAK;CAC3D;CAEA,MAAM,QAAmC,CAAC;CAC1C,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;EACxC,MAAM,OAAO,MAAM;EACnB,IAAI,KAAK,WAAW,YAAY;GAE/B,uBAAuB,UAAU,QAAQ,YAAY,QAAQ,QAAQ,cAAc,IAAI;GACvF,cAAc;GACd;EACD;EAEA,MAAM,KAAK;GACV,KAAK,QAAQ,EAAE,CAAC;GAChB,cAAc,KAAK,MAAM;GACzB,GAAI,KAAK,MAAM,YAAY,EAAE,WAAW,KAAK,IAAI,CAAC;EACnD,CAAC;CACF;CAEA,OAAO;EAAS;EAAiB;EAAU,aAAa,eAAe,KAAA;CAAU;AAClF;;AAGA,SAAS,UAAU,QAAqD;CACvE,OAAO,OAAO,aAAa,QAAQ,iBAAiB,OAAO,WAAW,OAAO,KAAK,IAAI,KAAA,IAAY,OAAO;AAC1G;AAEA,SAAS,kBACR,YACA,SACqB;CACrB,MAAM,UAAU,kBAAkB,UAAU;CAC5C,MAAM,oBAAoB,eAAA;CAC1B,KAAK,MAAM,UAAU,SAAS;EAC7B,IAAI,OAAO,MAAM,WAAW,KAAK,OAAO,KAAK,WAAW,GACvD,OAAO,+BAA+B,OAAO,IAAI;EAElD,IAAI,OAAO,OAAO,KAAK,CAAC,CAAC,WAAW,GACnC,OAAO,+BAA+B,OAAO,IAAI;EAGlD,IAAI,OAAO,WAAW,OAAO,OAAO,KAAK,GACxC,OAAO,+BAA+B,OAAO,IAAI,gBAAgB,OAAO,OAAO;EAEhF,IAAI,OAAO,OAAO,WAAW,OAAO,GACnC,OAAO,+BAA+B,OAAO,IAAI,gBAAgB,OAAO,OAAO;EAEhF,IAAI,WAAW,CAAC,OAAO,SACtB,OAAO,+BAA+B,OAAO,IAAI,4BAA4B,WAAW;EAEzF,IAAI,OAAO,WAAW,WAAW,GAChC,OAAO,+BAA+B,OAAO,IAAI;EAElD,IAAI,UAAU,MAAM,KAAK,MAAM;GAC9B,IAAI,SACH,OAAO,+BAA+B,OAAO,IAAI,iDAAiD,WAAW;GAE9G,IAAI,mBACH,OAAO,+BAA+B,OAAO,IAAI,iDAAiD,WAAW;EAE/G;CACD;AAED"}
@@ -1,10 +1,10 @@
1
1
  import { reconcileOmissionsWithFailure } from "../results.js";
2
2
  import { mergeAssessmentInto } from "../collectionMetadata.js";
3
3
  import { isGitHostIntegration, isIssuesHostIntegrationId, warnOnMissingSessionForDomain } from "../utils/integration.utils.js";
4
- import { fromProviderPullRequest, providersMetadata } from "../providers/models.js";
4
+ import { providersMetadata, toPullRequestRow } from "../providers/models.js";
5
5
  import { drainToRequestedPage, isPageNumberAdvanceable, pageToCursor, refusedPage, resolveContinuation, resolveCurrentPage, toProviderPageInfo } from "./paging.js";
6
6
  import { gitHostOnlySurfaceWarning, truncationWarning, unsupportedAccountWidePullRequestFiltersWarning, unsupportedFiltersWarning } from "./warnings.js";
7
- import { getCurrentAccountIdentity, runCaptured } from "./drains.js";
7
+ import { getPullRequestViewers, runCaptured } from "./drains.js";
8
8
  import { resolveAccountWidePullRequestFilters, resolvePullRequestFilters } from "./filters.js";
9
9
  //#region ../plus/integrations/src/reads/pullRequests.ts
10
10
  async function listPullRequestsPage(ctx, options) {
@@ -71,9 +71,13 @@ async function listPullRequestsPage(ctx, options) {
71
71
  const truncated = continuation.truncated || assessment.truncated;
72
72
  if (truncated && warnings.length === 0) warnings.push(truncationWarning(options.providerId, domain, options.connectionId, "Pull request", assessment.fetchFailed || pageFetchFailed ? "interrupted" : "exhausted"));
73
73
  reconcileOmissionsWithFailure(warnings, assessment.fetchFailed || pageFetchFailed);
74
- const currentAccount = items.some((pr) => pr.author != null) ? await getCurrentAccountIdentity(integration, options.connectionId) : void 0;
74
+ const viewers = await getPullRequestViewers(integration, options.connectionId, items);
75
+ const projection = accountWide ? "account-summary" : options.summary ? "repos-summary" : "repos";
75
76
  return {
76
- items: items.map((pr) => fromProviderPullRequest(pr, integration, { currentAccount })),
77
+ items: items.map((pr, i) => toPullRequestRow(pr, integration, {
78
+ currentAccount: viewers[i],
79
+ projection
80
+ })),
77
81
  warnings,
78
82
  page: {
79
83
  ...paged.page,
@@ -1 +1 @@
1
- {"version":3,"file":"pullRequests.js","names":[],"sources":["../../../../../plus/integrations/src/reads/pullRequests.ts"],"sourcesContent":["import type { PullRequestShape, PullRequestStateFilter } from '@gitlens/git/models/pullRequest.js';\nimport { mergeAssessmentInto } from '../collectionMetadata.js';\nimport type { IntegrationIds } from '../constants.js';\nimport type { ProviderPullRequest, ProviderReposInput, PullRequestFilter } from '../providers/models.js';\nimport { fromProviderPullRequest, PagingMode, providersMetadata } from '../providers/models.js';\nimport type { ProviderPagedResult } from '../results.js';\nimport { reconcileOmissionsWithFailure } from '../results.js';\nimport {\n\tisGitHostIntegration,\n\tisIssuesHostIntegrationId,\n\twarnOnMissingSessionForDomain,\n} from '../utils/integration.utils.js';\nimport type { ProviderReadContext } from './context.js';\nimport { getCurrentAccountIdentity, runCaptured } from './drains.js';\nimport { resolveAccountWidePullRequestFilters, resolvePullRequestFilters } from './filters.js';\nimport {\n\tdrainToRequestedPage,\n\tisPageNumberAdvanceable,\n\tpageToCursor,\n\trefusedPage,\n\tresolveContinuation,\n\tresolveCurrentPage,\n\ttoProviderPageInfo,\n} from './paging.js';\nimport {\n\tgitHostOnlySurfaceWarning,\n\ttruncationWarning,\n\tunsupportedAccountWidePullRequestFiltersWarning,\n\tunsupportedFiltersWarning,\n} from './warnings.js';\n\nexport async function listPullRequestsPage(\n\tctx: ProviderReadContext,\n\toptions: {\n\t\tproviderId: IntegrationIds;\n\t\trepos?: ProviderReposInput;\n\t\tstates?: PullRequestStateFilter[];\n\t\t/**\n\t\t * PR relationship filters (e.g. `[Author, Assignee, ReviewRequested]`). On repo-scoped reads members\n\t\t * combine as provider query constraints (normally an intersection); on account-wide reads they form an\n\t\t * exact OR union. The whole set is validated against the selected path's capability.\n\t\t */\n\t\tfilters?: PullRequestFilter[];\n\t\t/**\n\t\t * Account-wide only: include review-requested PRs when the provider's native \"my PRs\" query omits them.\n\t\t * This extends the provider-native result; it is not a narrowing filter.\n\t\t */\n\t\tincludeReviewRequested?: boolean;\n\t\tpage?: number;\n\t\tcursor?: string;\n\t\t/**\n\t\t * Requests the lightweight row shape (see {@link IntegrationManager.listPullRequestsPage}). Opt-in\n\t\t * only, and only on the repo-scoped branch: the account-wide branch has always read the lightweight\n\t\t * shape and keeps doing so, so an explicit `false` does not widen it back to the full projection.\n\t\t */\n\t\tsummary?: boolean;\n\t\titemsPerPage?: number;\n\t\tforceSync?: boolean;\n\t\tconnectionId?: string;\n\t\t/**\n\t\t * Explicit self-managed host domain when no configured connection supplies one. The value must come\n\t\t * from the trusted authentication configuration, not repository or remote data.\n\t\t */\n\t\tdomain?: string;\n\t},\n): Promise<ProviderPagedResult<PullRequestShape>> {\n\t// Truncated as well as floored, like every other paged read here: `page` is reported back as\n\t// `page.currentPage` and compared against the position a drain reached, and neither can be fractional.\n\tconst page = Math.max(1, Math.trunc(options.page ?? 1));\n\tif (isIssuesHostIntegrationId(options.providerId)) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[gitHostOnlySurfaceWarning(options.providerId, undefined, options.connectionId, 'pull request reads')],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(options.providerId, options.connectionId, options.domain);\n\tif (integration == null) {\n\t\t// A supplied connection or domain that no longer resolves is a broken target, not an empty account —\n\t\t// surface a no-connection warning + fetchFailed rather than a silent empty page.\n\t\tconst early = ctx.earlyReturnConnectionWarnings(options.providerId, options.connectionId, options.domain);\n\t\treturn refusedPage(page, early.warnings, early.fetchFailed);\n\t}\n\tif (!isGitHostIntegration(integration)) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[gitHostOnlySurfaceWarning(options.providerId, undefined, options.connectionId, 'pull request reads')],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\tawait ctx.forceRefreshIfRequested(integration, options.forceSync, options.connectionId);\n\n\tconst domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);\n\t// With no repos this is an account-wide \"my PRs\" read; the repo-scoped core rejects an empty `repos`\n\t// input, so route to the account-wide, inherently user-scoped core instead (see drainPullRequests).\n\t// That provider-defined path takes no `pageSize`. A page-only request is handled by walking any opaque\n\t// continuations it supplies below. Do NOT synthesize a page-number cursor for it: cursor-based queries\n\t// (e.g. GitHub `involves:`) ignore a page number and would return their first page.\n\tconst accountWide = (options.repos?.length ?? 0) === 0;\n\tconst cursor = accountWide ? options.cursor : (options.cursor ?? pageToCursor(page));\n\n\tconst resolvedFilters = accountWide\n\t\t? resolveAccountWidePullRequestFilters(options.providerId, options.filters)\n\t\t: resolvePullRequestFilters(options.providerId, options.filters);\n\tif (resolvedFilters.unsupported) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[\n\t\t\t\taccountWide\n\t\t\t\t\t? unsupportedAccountWidePullRequestFiltersWarning(\n\t\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\t\tdomain,\n\t\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t\toptions.filters ?? [],\n\t\t\t\t\t\t)\n\t\t\t\t\t: unsupportedFiltersWarning(options.providerId, domain, options.connectionId),\n\t\t\t],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\t// Everything but the cursor, shared by the initial read and the drain's re-reads below so the two cannot\n\t// drift. `summary` is the reason this matters beyond tidiness: a re-read that dropped it would both\n\t// revert to the full payload — on the very path that issues the most requests — and return rows of a\n\t// different shape than the first page, since a summary row reports `headCommit`/`commitCount` as absent.\n\t// `summary` is pinned true here rather than forwarded: this branch has never had a full projection to\n\t// fall back to (see the option's doc), so honoring an explicit `false` would promise a widening the read\n\t// cannot deliver.\n\tconst accountWideInput = {\n\t\tstate: options.states,\n\t\tincludeReviewRequested: options.includeReviewRequested ?? false,\n\t\tfilters: resolvedFilters.filters,\n\t\tsummary: true,\n\t};\n\t// `filters` scopes the read to the current user (the core resolves the account for these), so it returns\n\t// the user's PRs. `pageSize` rides along so PagingMode.Repo hosts (GitLab, Bitbucket, Azure), whose\n\t// per-repo cursor path ignores a synthesized page-number cursor, honor the requested size.\n\tconst scopedInput = {\n\t\tstate: options.states,\n\t\tfilters: resolvedFilters.filters,\n\t\tpageSize: options.itemsPerPage,\n\t\tsummary: options.summary,\n\t};\n\tconst readPage = (pageCursor: string | undefined, requestedPage?: number) =>\n\t\trunCaptured(\n\t\t\toptions.providerId,\n\t\t\tdomain,\n\t\t\toptions.connectionId,\n\t\t\t() =>\n\t\t\t\taccountWide\n\t\t\t\t\t? integration.getMyPullRequestsForUserResult(\n\t\t\t\t\t\t\t{ ...accountWideInput, cursor: pageCursor },\n\t\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t)\n\t\t\t\t\t: integration.getMyPullRequestsForReposResult(\n\t\t\t\t\t\t\toptions.repos ?? [],\n\t\t\t\t\t\t\t{ ...scopedInput, cursor: pageCursor, page: requestedPage },\n\t\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t),\n\t\t\t{\n\t\t\t\twarnOnMissingSession: warnOnMissingSessionForDomain(options.providerId, options.domain),\n\t\t\t},\n\t\t);\n\tconst { value, warning } = await readPage(cursor, options.page != null ? page : undefined);\n\n\tlet items = value?.values ?? [];\n\t// Cursor-only account-wide reads start at page 1; a page-only request is advanced through opaque\n\t// continuations below. Repo-scoped reads report the requested page unless the provider reports its own.\n\tlet paged = toProviderPageInfo(items.length, value?.paging);\n\tlet allMetadata = value?.metadata;\n\t// Convert the SDK collection metadata into scope-aware warnings + failure/truncation flags, appending\n\t// them to any captured thrown-error warning without discarding the partial result's items.\n\tconst warnings = warning != null ? [warning] : [];\n\tlet pageFetchFailed = warning != null && value == null;\n\n\t// Cursor-only reads ignore a synthesized page-number cursor, so a page-only request is advanced through\n\t// the provider's own continuations (see drainToRequestedPage).\n\tif (\n\t\t(accountWide || providersMetadata[options.providerId]?.pullRequestsPagingMode === PagingMode.Repos) &&\n\t\toptions.page != null &&\n\t\tpage > 1 &&\n\t\toptions.cursor == null &&\n\t\tpaged.page.currentPage === 1\n\t) {\n\t\tconst drained = await drainToRequestedPage<ProviderPullRequest>(\n\t\t\t{ items: items, paged: paged, metadata: allMetadata, fetchFailed: pageFetchFailed },\n\t\t\t{\n\t\t\t\trequestedPage: page,\n\t\t\t\titemsPerPage: options.itemsPerPage,\n\t\t\t\twarnings: warnings,\n\t\t\t\t// A drain advances through provider continuations, so only the cursor positions these reads.\n\t\t\t\treadPage: (cursor: string) => readPage(cursor),\n\t\t\t},\n\t\t);\n\t\titems = drained.items;\n\t\tpaged = drained.paged;\n\t\tallMetadata = drained.metadata;\n\t\tpageFetchFailed = drained.fetchFailed;\n\t}\n\n\tconst assessment = mergeAssessmentInto(warnings, options.providerId, domain, options.connectionId, allMetadata);\n\t// Never advertise `hasMore` without a continuation the caller can act on. The account-wide read has no\n\t// page to synthesize, and neither does a host that doesn't honor a page number — see\n\t// {@link IntegrationService.isPageNumberAdvanceable} for which do and why absence of a declared mode\n\t// does NOT count as one.\n\tconst pageAdvanceable =\n\t\t!accountWide && isPageNumberAdvanceable(providersMetadata[options.providerId]?.pullRequestsPagingMode);\n\tconst continuation = resolveContinuation(paged, pageAdvanceable ? paged.page.currentPage + 1 : undefined);\n\t// A single-page provider read that couldn't confirm completeness sets `paging.truncated`; surface it\n\t// as a terminal `page.truncated` (not `hasMore`, which has no cursor to advance) so the caller knows\n\t// the page may be incomplete. Metadata incompleteness is an independent source of the same signal.\n\tconst truncated = continuation.truncated || assessment.truncated;\n\tif (truncated && warnings.length === 0) {\n\t\t// `exhausted`, never `page-budget`: a paged read has no budget the caller can raise. Ordinary\n\t\t// continuation is already expressed by `hasMore`/`cursor`, so reaching here means this page itself\n\t\t// couldn't be completed — raising anything would return the same page.\n\t\twarnings.push(\n\t\t\ttruncationWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'Pull request',\n\t\t\t\tassessment.fetchFailed || pageFetchFailed ? 'interrupted' : 'exhausted',\n\t\t\t),\n\t\t);\n\t}\n\t// A metadata omission from an earlier page asserts the read succeeded; a later page may since have failed.\n\treconcileOmissionsWithFailure(warnings, assessment.fetchFailed || pageFetchFailed);\n\tconst currentAccount = items.some(pr => pr.author != null)\n\t\t? await getCurrentAccountIdentity(integration, options.connectionId)\n\t\t: undefined;\n\treturn {\n\t\t// Normalize the raw provider-apis PRs to the GitLens-owned shape at the surface boundary.\n\t\titems: items.map(pr => fromProviderPullRequest(pr, integration, { currentAccount: currentAccount })),\n\t\twarnings: warnings,\n\t\t// The account-wide read can't take a page size, so don't echo the requested `itemsPerPage` as if it\n\t\t// had been applied — report what came back.\n\t\tpage: {\n\t\t\t...paged.page,\n\t\t\t// Positional, per ProviderPageInfo.currentPage: the drain (above) already advanced `paged.page`,\n\t\t\t// and a caller-threaded cursor advanced the provider, so neither leaves a cursor-only read stuck\n\t\t\t// reporting page 1.\n\t\t\tcurrentPage: resolveCurrentPage({\n\t\t\t\tproviderPage: paged.page.currentPage,\n\t\t\t\trequestedPage: page,\n\t\t\t\tsuppliedCursor: options.cursor,\n\t\t\t\tpageAdvanceable: pageAdvanceable,\n\t\t\t}),\n\t\t\titemsPerPage: accountWide ? items.length : paged.page.itemsPerPage,\n\t\t\ttruncated: truncated || undefined,\n\t\t},\n\t\thasMore: continuation.hasMore,\n\t\tcursor: continuation.cursor,\n\t\t// A metadata failure means items are incomplete even when the read didn't throw; a thrown error with\n\t\t// no recovered value is the pre-existing failure case.\n\t\tfetchFailed: assessment.fetchFailed || pageFetchFailed || undefined,\n\t};\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"pullRequests.js","names":[],"sources":["../../../../../plus/integrations/src/reads/pullRequests.ts"],"sourcesContent":["import type { PullRequest, PullRequestShape, PullRequestStateFilter } from '@gitlens/git/models/pullRequest.js';\nimport { mergeAssessmentInto } from '../collectionMetadata.js';\nimport type { IntegrationIds } from '../constants.js';\nimport type { ProviderPullRequest, ProviderReposInput, PullRequestFilter } from '../providers/models.js';\nimport { PagingMode, providersMetadata, toPullRequestRow } from '../providers/models.js';\nimport type { ProviderPagedResult } from '../results.js';\nimport { reconcileOmissionsWithFailure } from '../results.js';\nimport {\n\tisGitHostIntegration,\n\tisIssuesHostIntegrationId,\n\twarnOnMissingSessionForDomain,\n} from '../utils/integration.utils.js';\nimport type { ProviderReadContext } from './context.js';\nimport { getPullRequestViewers, runCaptured } from './drains.js';\nimport { resolveAccountWidePullRequestFilters, resolvePullRequestFilters } from './filters.js';\nimport {\n\tdrainToRequestedPage,\n\tisPageNumberAdvanceable,\n\tpageToCursor,\n\trefusedPage,\n\tresolveContinuation,\n\tresolveCurrentPage,\n\ttoProviderPageInfo,\n} from './paging.js';\nimport {\n\tgitHostOnlySurfaceWarning,\n\ttruncationWarning,\n\tunsupportedAccountWidePullRequestFiltersWarning,\n\tunsupportedFiltersWarning,\n} from './warnings.js';\n\nexport async function listPullRequestsPage(\n\tctx: ProviderReadContext,\n\toptions: {\n\t\tproviderId: IntegrationIds;\n\t\trepos?: ProviderReposInput;\n\t\tstates?: PullRequestStateFilter[];\n\t\t/**\n\t\t * PR relationship filters (e.g. `[Author, Assignee, ReviewRequested]`). On repo-scoped reads members\n\t\t * combine as provider query constraints (normally an intersection); on account-wide reads they form an\n\t\t * exact OR union. The whole set is validated against the selected path's capability.\n\t\t */\n\t\tfilters?: PullRequestFilter[];\n\t\t/**\n\t\t * Account-wide only: include review-requested PRs when the provider's native \"my PRs\" query omits them.\n\t\t * This extends the provider-native result; it is not a narrowing filter.\n\t\t */\n\t\tincludeReviewRequested?: boolean;\n\t\tpage?: number;\n\t\tcursor?: string;\n\t\t/**\n\t\t * Requests the lightweight row shape (see {@link IntegrationManager.listPullRequestsPage}). Opt-in\n\t\t * only, and only on the repo-scoped branch: the account-wide branch has always read the lightweight\n\t\t * shape and keeps doing so, so an explicit `false` does not widen it back to the full projection.\n\t\t */\n\t\tsummary?: boolean;\n\t\titemsPerPage?: number;\n\t\tforceSync?: boolean;\n\t\tconnectionId?: string;\n\t\t/**\n\t\t * Explicit self-managed host domain when no configured connection supplies one. The value must come\n\t\t * from the trusted authentication configuration, not repository or remote data.\n\t\t */\n\t\tdomain?: string;\n\t},\n): Promise<ProviderPagedResult<PullRequestShape>> {\n\t// Truncated as well as floored, like every other paged read here: `page` is reported back as\n\t// `page.currentPage` and compared against the position a drain reached, and neither can be fractional.\n\tconst page = Math.max(1, Math.trunc(options.page ?? 1));\n\tif (isIssuesHostIntegrationId(options.providerId)) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[gitHostOnlySurfaceWarning(options.providerId, undefined, options.connectionId, 'pull request reads')],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(options.providerId, options.connectionId, options.domain);\n\tif (integration == null) {\n\t\t// A supplied connection or domain that no longer resolves is a broken target, not an empty account —\n\t\t// surface a no-connection warning + fetchFailed rather than a silent empty page.\n\t\tconst early = ctx.earlyReturnConnectionWarnings(options.providerId, options.connectionId, options.domain);\n\t\treturn refusedPage(page, early.warnings, early.fetchFailed);\n\t}\n\tif (!isGitHostIntegration(integration)) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[gitHostOnlySurfaceWarning(options.providerId, undefined, options.connectionId, 'pull request reads')],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\tawait ctx.forceRefreshIfRequested(integration, options.forceSync, options.connectionId);\n\n\tconst domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);\n\t// With no repos this is an account-wide \"my PRs\" read; the repo-scoped core rejects an empty `repos`\n\t// input, so route to the account-wide, inherently user-scoped core instead (see drainPullRequests).\n\t// That provider-defined path takes no `pageSize`. A page-only request is handled by walking any opaque\n\t// continuations it supplies below. Do NOT synthesize a page-number cursor for it: cursor-based queries\n\t// (e.g. GitHub `involves:`) ignore a page number and would return their first page.\n\tconst accountWide = (options.repos?.length ?? 0) === 0;\n\tconst cursor = accountWide ? options.cursor : (options.cursor ?? pageToCursor(page));\n\n\tconst resolvedFilters = accountWide\n\t\t? resolveAccountWidePullRequestFilters(options.providerId, options.filters)\n\t\t: resolvePullRequestFilters(options.providerId, options.filters);\n\tif (resolvedFilters.unsupported) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[\n\t\t\t\taccountWide\n\t\t\t\t\t? unsupportedAccountWidePullRequestFiltersWarning(\n\t\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\t\tdomain,\n\t\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t\toptions.filters ?? [],\n\t\t\t\t\t\t)\n\t\t\t\t\t: unsupportedFiltersWarning(options.providerId, domain, options.connectionId),\n\t\t\t],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\t// Everything but the cursor, shared by the initial read and the drain's re-reads below so the two cannot\n\t// drift. `summary` is the reason this matters beyond tidiness: a re-read that dropped it would both\n\t// revert to the full payload — on the very path that issues the most requests — and return rows of a\n\t// different shape than the first page, since a summary row reports `headCommit`/`commitCount` as absent.\n\t// `summary` is pinned true here rather than forwarded: this branch has never had a full projection to\n\t// fall back to (see the option's doc), so honoring an explicit `false` would promise a widening the read\n\t// cannot deliver.\n\tconst accountWideInput = {\n\t\tstate: options.states,\n\t\tincludeReviewRequested: options.includeReviewRequested ?? false,\n\t\tfilters: resolvedFilters.filters,\n\t\tsummary: true,\n\t};\n\t// `filters` scopes the read to the current user (the core resolves the account for these), so it returns\n\t// the user's PRs. `pageSize` rides along so PagingMode.Repo hosts (GitLab, Bitbucket, Azure), whose\n\t// per-repo cursor path ignores a synthesized page-number cursor, honor the requested size.\n\tconst scopedInput = {\n\t\tstate: options.states,\n\t\tfilters: resolvedFilters.filters,\n\t\tpageSize: options.itemsPerPage,\n\t\tsummary: options.summary,\n\t};\n\tconst readPage = (pageCursor: string | undefined, requestedPage?: number) =>\n\t\trunCaptured(\n\t\t\toptions.providerId,\n\t\t\tdomain,\n\t\t\toptions.connectionId,\n\t\t\t() =>\n\t\t\t\taccountWide\n\t\t\t\t\t? integration.getMyPullRequestsForUserResult(\n\t\t\t\t\t\t\t{ ...accountWideInput, cursor: pageCursor },\n\t\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t)\n\t\t\t\t\t: integration.getMyPullRequestsForReposResult(\n\t\t\t\t\t\t\toptions.repos ?? [],\n\t\t\t\t\t\t\t{ ...scopedInput, cursor: pageCursor, page: requestedPage },\n\t\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t),\n\t\t\t{\n\t\t\t\twarnOnMissingSession: warnOnMissingSessionForDomain(options.providerId, options.domain),\n\t\t\t},\n\t\t);\n\tconst { value, warning } = await readPage(cursor, options.page != null ? page : undefined);\n\n\tlet items = value?.values ?? [];\n\t// Cursor-only account-wide reads start at page 1; a page-only request is advanced through opaque\n\t// continuations below. Repo-scoped reads report the requested page unless the provider reports its own.\n\tlet paged = toProviderPageInfo(items.length, value?.paging);\n\tlet allMetadata = value?.metadata;\n\t// Convert the SDK collection metadata into scope-aware warnings + failure/truncation flags, appending\n\t// them to any captured thrown-error warning without discarding the partial result's items.\n\tconst warnings = warning != null ? [warning] : [];\n\tlet pageFetchFailed = warning != null && value == null;\n\n\t// Cursor-only reads ignore a synthesized page-number cursor, so a page-only request is advanced through\n\t// the provider's own continuations (see drainToRequestedPage).\n\tif (\n\t\t(accountWide || providersMetadata[options.providerId]?.pullRequestsPagingMode === PagingMode.Repos) &&\n\t\toptions.page != null &&\n\t\tpage > 1 &&\n\t\toptions.cursor == null &&\n\t\tpaged.page.currentPage === 1\n\t) {\n\t\tconst drained = await drainToRequestedPage<ProviderPullRequest | PullRequest>(\n\t\t\t{ items: items, paged: paged, metadata: allMetadata, fetchFailed: pageFetchFailed },\n\t\t\t{\n\t\t\t\trequestedPage: page,\n\t\t\t\titemsPerPage: options.itemsPerPage,\n\t\t\t\twarnings: warnings,\n\t\t\t\t// A drain advances through provider continuations, so only the cursor positions these reads.\n\t\t\t\treadPage: (cursor: string) => readPage(cursor),\n\t\t\t},\n\t\t);\n\t\titems = drained.items;\n\t\tpaged = drained.paged;\n\t\tallMetadata = drained.metadata;\n\t\tpageFetchFailed = drained.fetchFailed;\n\t}\n\n\tconst assessment = mergeAssessmentInto(warnings, options.providerId, domain, options.connectionId, allMetadata);\n\t// Never advertise `hasMore` without a continuation the caller can act on. The account-wide read has no\n\t// page to synthesize, and neither does a host that doesn't honor a page number — see\n\t// {@link IntegrationService.isPageNumberAdvanceable} for which do and why absence of a declared mode\n\t// does NOT count as one.\n\tconst pageAdvanceable =\n\t\t!accountWide && isPageNumberAdvanceable(providersMetadata[options.providerId]?.pullRequestsPagingMode);\n\tconst continuation = resolveContinuation(paged, pageAdvanceable ? paged.page.currentPage + 1 : undefined);\n\t// A single-page provider read that couldn't confirm completeness sets `paging.truncated`; surface it\n\t// as a terminal `page.truncated` (not `hasMore`, which has no cursor to advance) so the caller knows\n\t// the page may be incomplete. Metadata incompleteness is an independent source of the same signal.\n\tconst truncated = continuation.truncated || assessment.truncated;\n\tif (truncated && warnings.length === 0) {\n\t\t// `exhausted`, never `page-budget`: a paged read has no budget the caller can raise. Ordinary\n\t\t// continuation is already expressed by `hasMore`/`cursor`, so reaching here means this page itself\n\t\t// couldn't be completed — raising anything would return the same page.\n\t\twarnings.push(\n\t\t\ttruncationWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'Pull request',\n\t\t\t\tassessment.fetchFailed || pageFetchFailed ? 'interrupted' : 'exhausted',\n\t\t\t),\n\t\t);\n\t}\n\t// A metadata omission from an earlier page asserts the read succeeded; a later page may since have failed.\n\treconcileOmissionsWithFailure(warnings, assessment.fetchFailed || pageFetchFailed);\n\tconst viewers = await getPullRequestViewers(integration, options.connectionId, items);\n\tconst projection = accountWide ? 'account-summary' : options.summary ? 'repos-summary' : 'repos';\n\treturn {\n\t\t// Normalize the raw rows to the GitLens-owned shape at the surface boundary.\n\t\titems: items.map((pr, i) =>\n\t\t\ttoPullRequestRow(pr, integration, { currentAccount: viewers[i], projection: projection }),\n\t\t),\n\t\twarnings: warnings,\n\t\t// The account-wide read can't take a page size, so don't echo the requested `itemsPerPage` as if it\n\t\t// had been applied — report what came back.\n\t\tpage: {\n\t\t\t...paged.page,\n\t\t\t// Positional, per ProviderPageInfo.currentPage: the drain (above) already advanced `paged.page`,\n\t\t\t// and a caller-threaded cursor advanced the provider, so neither leaves a cursor-only read stuck\n\t\t\t// reporting page 1.\n\t\t\tcurrentPage: resolveCurrentPage({\n\t\t\t\tproviderPage: paged.page.currentPage,\n\t\t\t\trequestedPage: page,\n\t\t\t\tsuppliedCursor: options.cursor,\n\t\t\t\tpageAdvanceable: pageAdvanceable,\n\t\t\t}),\n\t\t\titemsPerPage: accountWide ? items.length : paged.page.itemsPerPage,\n\t\t\ttruncated: truncated || undefined,\n\t\t},\n\t\thasMore: continuation.hasMore,\n\t\tcursor: continuation.cursor,\n\t\t// A metadata failure means items are incomplete even when the read didn't throw; a thrown error with\n\t\t// no recovered value is the pre-existing failure case.\n\t\tfetchFailed: assessment.fetchFailed || pageFetchFailed || undefined,\n\t};\n}\n"],"mappings":";;;;;;;;;AA+BA,eAAsB,qBACrB,KACA,SAgCiD;CAGjD,MAAM,OAAO,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,QAAQ,CAAC,CAAC;CACtD,IAAI,0BAA0B,QAAQ,UAAU,GAC/C,OAAO,YACN,MACA,CAAC,0BAA0B,QAAQ,YAAY,KAAA,GAAW,QAAQ,cAAc,oBAAoB,CAAC,GACrG,IACD;CAGD,MAAM,cAAc,MAAM,IAAI,sBAAsB,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;CAC5G,IAAI,eAAe,MAAM;EAGxB,MAAM,QAAQ,IAAI,8BAA8B,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;EACxG,OAAO,YAAY,MAAM,MAAM,UAAU,MAAM,WAAW;CAC3D;CACA,IAAI,CAAC,qBAAqB,WAAW,GACpC,OAAO,YACN,MACA,CAAC,0BAA0B,QAAQ,YAAY,KAAA,GAAW,QAAQ,cAAc,oBAAoB,CAAC,GACrG,IACD;CAGD,MAAM,IAAI,wBAAwB,aAAa,QAAQ,WAAW,QAAQ,YAAY;CAEtF,MAAM,SAAS,IAAI,cAAc,aAAa,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;CAMtG,MAAM,eAAe,QAAQ,OAAO,UAAU,OAAO;CACrD,MAAM,SAAS,cAAc,QAAQ,SAAU,QAAQ,UAAU,aAAa,IAAI;CAElF,MAAM,kBAAkB,cACrB,qCAAqC,QAAQ,YAAY,QAAQ,OAAO,IACxE,0BAA0B,QAAQ,YAAY,QAAQ,OAAO;CAChE,IAAI,gBAAgB,aACnB,OAAO,YACN,MACA,CACC,cACG,gDACA,QAAQ,YACR,QACA,QAAQ,cACR,QAAQ,WAAW,CAAC,CACrB,IACC,0BAA0B,QAAQ,YAAY,QAAQ,QAAQ,YAAY,CAC9E,GACA,IACD;CAUD,MAAM,mBAAmB;EACxB,OAAO,QAAQ;EACf,wBAAwB,QAAQ,0BAA0B;EAC1D,SAAS,gBAAgB;EACzB,SAAS;CACV;CAIA,MAAM,cAAc;EACnB,OAAO,QAAQ;EACf,SAAS,gBAAgB;EACzB,UAAU,QAAQ;EAClB,SAAS,QAAQ;CAClB;CACA,MAAM,YAAY,YAAgC,kBACjD,YACC,QAAQ,YACR,QACA,QAAQ,oBAEP,cACG,YAAY,+BACZ;EAAE,GAAG;EAAkB,QAAQ;CAAW,GAC1C,QAAQ,YACT,IACC,YAAY,gCACZ,QAAQ,SAAS,CAAC,GAClB;EAAE,GAAG;EAAa,QAAQ;EAAY,MAAM;CAAc,GAC1D,QAAQ,YACT,GACH,EACC,sBAAsB,8BAA8B,QAAQ,YAAY,QAAQ,MAAM,EACvF,CACD;CACD,MAAM,EAAE,OAAO,YAAY,MAAM,SAAS,QAAQ,QAAQ,QAAQ,OAAO,OAAO,KAAA,CAAS;CAEzF,IAAI,QAAQ,OAAO,UAAU,CAAC;CAG9B,IAAI,QAAQ,mBAAmB,MAAM,QAAQ,OAAO,MAAM;CAC1D,IAAI,cAAc,OAAO;CAGzB,MAAM,WAAW,WAAW,OAAO,CAAC,OAAO,IAAI,CAAC;CAChD,IAAI,kBAAkB,WAAW,QAAQ,SAAS;CAIlD,KACE,eAAe,kBAAkB,QAAQ,WAAW,EAAE,2BAAA,YACvD,QAAQ,QAAQ,QAChB,OAAO,KACP,QAAQ,UAAU,QAClB,MAAM,KAAK,gBAAgB,GAC1B;EACD,MAAM,UAAU,MAAM,qBACrB;GAAS;GAAc;GAAO,UAAU;GAAa,aAAa;EAAgB,GAClF;GACC,eAAe;GACf,cAAc,QAAQ;GACZ;GAEV,WAAW,WAAmB,SAAS,MAAM;EAC9C,CACD;EACA,QAAQ,QAAQ;EAChB,QAAQ,QAAQ;EAChB,cAAc,QAAQ;EACtB,kBAAkB,QAAQ;CAC3B;CAEA,MAAM,aAAa,oBAAoB,UAAU,QAAQ,YAAY,QAAQ,QAAQ,cAAc,WAAW;CAK9G,MAAM,kBACL,CAAC,eAAe,wBAAwB,kBAAkB,QAAQ,WAAW,EAAE,sBAAsB;CACtG,MAAM,eAAe,oBAAoB,OAAO,kBAAkB,MAAM,KAAK,cAAc,IAAI,KAAA,CAAS;CAIxG,MAAM,YAAY,aAAa,aAAa,WAAW;CACvD,IAAI,aAAa,SAAS,WAAW,GAIpC,SAAS,KACR,kBACC,QAAQ,YACR,QACA,QAAQ,cACR,gBACA,WAAW,eAAe,kBAAkB,gBAAgB,WAC7D,CACD;CAGD,8BAA8B,UAAU,WAAW,eAAe,eAAe;CACjF,MAAM,UAAU,MAAM,sBAAsB,aAAa,QAAQ,cAAc,KAAK;CACpF,MAAM,aAAa,cAAc,oBAAoB,QAAQ,UAAU,kBAAkB;CACzF,OAAO;EAEN,OAAO,MAAM,KAAK,IAAI,MACrB,iBAAiB,IAAI,aAAa;GAAE,gBAAgB,QAAQ;GAAgB;EAAW,CAAC,CACzF;EACU;EAGV,MAAM;GACL,GAAG,MAAM;GAIT,aAAa,mBAAmB;IAC/B,cAAc,MAAM,KAAK;IACzB,eAAe;IACf,gBAAgB,QAAQ;IACP;GAClB,CAAC;GACD,cAAc,cAAc,MAAM,SAAS,MAAM,KAAK;GACtD,WAAW,aAAa,KAAA;EACzB;EACA,SAAS,aAAa;EACtB,QAAQ,aAAa;EAGrB,aAAa,WAAW,eAAe,mBAAmB,KAAA;CAC3D;AACD"}
@@ -2,9 +2,9 @@ import { mapBounded } from "../../../utils/promise.js";
2
2
  import "../constants.js";
3
3
  import { appendDedupedWarning } from "../results.js";
4
4
  import { isGitHostIntegration, isIssuesHostIntegrationId } from "../utils/integration.utils.js";
5
- import { fromProviderPullRequest } from "../providers/models.js";
5
+ import { toPullRequestRow } from "../providers/models.js";
6
6
  import { gitHostOnlySurfaceWarning, noConnectionWarning, unsupportedAccountWidePullRequestFiltersWarning, unsupportedFiltersWarning } from "./warnings.js";
7
- import { drainPullRequests, getCurrentAccountIdentity, resolvePullRequestSweepTargets } from "./drains.js";
7
+ import { drainPullRequests, getPullRequestViewers, resolvePullRequestSweepTargets } from "./drains.js";
8
8
  import { resolveAccountWidePullRequestFilters, resolvePullRequestFilters } from "./filters.js";
9
9
  //#region ../plus/integrations/src/reads/sweeps.ts
10
10
  /**
@@ -43,10 +43,14 @@ async function sweepTarget(ctx, options, target, attributeUnavailableProviders)
43
43
  const resolved = accountWide ? resolveAccountWidePullRequestFilters(id, requestedFilters) : resolvePullRequestFilters(id, requestedFilters);
44
44
  if (resolved.unsupported) return rejectedTarget([accountWide ? unsupportedAccountWidePullRequestFiltersWarning(id, domain, connectionId, requestedFilters ?? []) : unsupportedFiltersWarning(id, domain, connectionId)]);
45
45
  const drain = await drainPullRequests(integration, id, domain, repos, options?.states, resolved.filters, options, connectionId, maxPages, attributeUnavailableProviders);
46
- const currentAccount = drain.items.some((pr) => pr.author != null) ? await getCurrentAccountIdentity(integration, connectionId) : void 0;
46
+ const viewers = await getPullRequestViewers(integration, connectionId, drain.items);
47
+ const projection = accountWide ? options?.includeReviews ? "account" : "account-summary" : "repos";
47
48
  return {
48
49
  ...drain,
49
- items: drain.items.map((pr) => fromProviderPullRequest(pr, integration, { currentAccount })),
50
+ items: drain.items.map((pr, i) => toPullRequestRow(pr, integration, {
51
+ currentAccount: viewers[i],
52
+ projection
53
+ })),
50
54
  providerId: id
51
55
  };
52
56
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sweeps.js","names":[],"sources":["../../../../../plus/integrations/src/reads/sweeps.ts"],"sourcesContent":["import type { PullRequestShape } from '@gitlens/git/models/pullRequest.js';\nimport { mapBounded } from '@gitlens/utils/promise.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { providerFanOutConcurrency } from '../constants.js';\nimport type {\n\tClosedPullRequestSweepOptions,\n\tProviderSweepTarget,\n\tProviderSweepTargetEvent,\n\tPullRequestSweepOptions,\n} from '../manager.js';\nimport { fromProviderPullRequest } from '../providers/models.js';\nimport type { ProviderSweepResult, ProviderWarning } from '../results.js';\nimport { appendDedupedWarning } from '../results.js';\nimport { isGitHostIntegration, isIssuesHostIntegrationId } from '../utils/integration.utils.js';\nimport type { ProviderReadContext } from './context.js';\nimport { drainPullRequests, getCurrentAccountIdentity, resolvePullRequestSweepTargets } from './drains.js';\nimport { resolveAccountWidePullRequestFilters, resolvePullRequestFilters } from './filters.js';\nimport {\n\tgitHostOnlySurfaceWarning,\n\tnoConnectionWarning,\n\tunsupportedAccountWidePullRequestFiltersWarning,\n\tunsupportedFiltersWarning,\n} from './warnings.js';\n\n/**\n * The all-pages pull request reads: one drain per provider target, aggregated into a single sweep result.\n *\n * A sweep exposes no continuation of its own — it drains everything it can reach — so its whole contract lives in\n * the completeness signals it returns: `page.allPages` asserts every page of every target was read,\n * `page.truncated` says pages remain unread, and `failedProviderIds`/`incompleteProviderIds` separate a provider\n * that produced nothing from one that produced a slice with a gap. That split is why the aggregation below can't\n * be collapsed into the generic fan-out merge the hierarchy reads use.\n */\n\n/** One provider target's drained slice, before the cross-provider aggregation. */\ninterface SweepSlice {\n\titems: PullRequestShape[];\n\twarnings: ProviderWarning[];\n\tfetchFailed: boolean;\n\ttruncated: boolean;\n\tproviderId: IntegrationIds;\n\tfailedProvider: boolean;\n}\n\n/**\n * Drain ONE sweep target. Extracted from the fan-out so the fan-out callback has a single exit: every\n * per-target observation (see `onTargetSettled`) is then reported in one place instead of at each of this\n * function's several early returns, where a missed branch would silently drop a provider's attribution.\n *\n * `undefined` means the target resolved to no reachable connection and is deliberately not attributed in the\n * aggregate result.\n */\nasync function sweepTarget(\n\tctx: ProviderReadContext,\n\toptions: PullRequestSweepOptions | undefined,\n\ttarget: ProviderSweepTarget,\n\tattributeUnavailableProviders: boolean,\n): Promise<SweepSlice | undefined> {\n\tconst { providerId: id, connectionId, domain: requestedDomain } = target;\n\tconst repos = options?.repos ?? [];\n\tconst maxPages = options?.maxPages ?? 100;\n\t/** A target that never reached a drain: the provider itself failed, so its slice is empty and attributed. */\n\tconst rejectedTarget = (warnings: ProviderWarning[]): SweepSlice => ({\n\t\titems: [],\n\t\twarnings: warnings,\n\t\tfetchFailed: true,\n\t\ttruncated: false,\n\t\tproviderId: id,\n\t\tfailedProvider: true,\n\t});\n\n\tif (isIssuesHostIntegrationId(id)) {\n\t\treturn rejectedTarget([gitHostOnlySurfaceWarning(id, requestedDomain, connectionId, 'pull request sweeps')]);\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(id, connectionId, requestedDomain);\n\tif (integration == null) {\n\t\t// A requested connection that can't be resolved is a broken connection — surface it as a\n\t\t// warning + fetchFailed rather than dropping the provider's slice silently.\n\t\tconst early = ctx.earlyReturnConnectionWarnings(id, connectionId, requestedDomain);\n\t\tif (early.warnings.length === 0 && !attributeUnavailableProviders) return undefined;\n\n\t\treturn rejectedTarget(\n\t\t\tearly.warnings.length !== 0 ? early.warnings : [noConnectionWarning(id, requestedDomain, connectionId)],\n\t\t);\n\t}\n\tif (!isGitHostIntegration(integration)) {\n\t\treturn rejectedTarget([gitHostOnlySurfaceWarning(id, requestedDomain, connectionId, 'pull request sweeps')]);\n\t}\n\n\tawait ctx.forceRefreshIfRequested(integration, options?.forceSync, connectionId);\n\n\tconst domain = ctx.domainForRead(integration, id, connectionId, requestedDomain);\n\tconst accountWide = repos.length === 0;\n\tconst requestedFilters = target.filters ?? options?.filters;\n\tconst resolved = accountWide\n\t\t? resolveAccountWidePullRequestFilters(id, requestedFilters)\n\t\t: resolvePullRequestFilters(id, requestedFilters);\n\tif (resolved.unsupported) {\n\t\treturn rejectedTarget([\n\t\t\taccountWide\n\t\t\t\t? unsupportedAccountWidePullRequestFiltersWarning(id, domain, connectionId, requestedFilters ?? [])\n\t\t\t\t: unsupportedFiltersWarning(id, domain, connectionId),\n\t\t]);\n\t}\n\n\tconst drain = await drainPullRequests(\n\t\tintegration,\n\t\tid,\n\t\tdomain,\n\t\trepos,\n\t\toptions?.states,\n\t\tresolved.filters,\n\t\toptions,\n\t\tconnectionId,\n\t\tmaxPages,\n\t\tattributeUnavailableProviders,\n\t);\n\tconst currentAccount = drain.items.some(pr => pr.author != null)\n\t\t? await getCurrentAccountIdentity(integration, connectionId)\n\t\t: undefined;\n\t// Normalize the raw provider-apis PRs to the GitLens-owned shape here, where the per-provider\n\t// `integration` (the mapper's provider reference) is in scope; the aggregation below only sees drains.\n\treturn {\n\t\t...drain,\n\t\titems: drain.items.map(pr => fromProviderPullRequest(pr, integration, { currentAccount: currentAccount })),\n\t\tproviderId: id,\n\t};\n}\n\n/**\n * How a target ended, as the consumer buckets it. `failedProvider` outranks `fetchFailed` because a target\n * whose provider failed produced no slice to be partial about, and `undefined` is the target that resolved to\n * no reachable connection at all.\n */\nfunction sliceOutcome(slice: SweepSlice | undefined): ProviderSweepTargetEvent['outcome'] {\n\tif (slice == null) return 'skipped';\n\tif (slice.failedProvider) return 'failed-provider';\n\tif (slice.fetchFailed) return 'fetch-failed';\n\treturn 'ok';\n}\n\n/**\n * Per-target reporting for a sweep's `onTargetSettled`, opened once per sweep: this stamps the fan-out start,\n * the returned function stamps one target's start, and the function IT returns is the only thing that can\n * report that target. The timestamps therefore never cross a boundary, so there is no pair of interchangeable\n * numbers for a call site to transpose, and the whole reporting concern is one value the fan-out either has or\n * does not. That is also what keeps the option free when it is omitted: with no observer there is nothing to\n * open, so no clock is read and no reporting object exists — do not \"simplify\" that into an unconditional\n * open, because a host whose perf gate is off (the default) is entitled to pay nothing for it.\n *\n * The try/catch is what makes the observer observation-only: called from the fan-out's success path, a throwing\n * callback would otherwise propagate out of the `mapBounded` task and reject the entire sweep — corrupting the\n * read, not just the metric. Swallowed silently; the consumer owns its own aggregation. The `void`-typed\n * callback also admits an `async` consumer, whose rejection is created only after `observe` returns — the\n * `Promise.resolve(...).catch` swallows that too, so it cannot escape as an unhandled rejection.\n *\n * The domain is resolved here rather than carried out of {@link sweepTarget}, so every target reports it by the\n * same rule no matter how far it got. `resolveDomainForRead` needs no integration instance, which is what makes\n * that possible: a target rejected by the first guard resolves the same host a fully drained one does.\n */\nfunction startSweepReporting(\n\tctx: ProviderReadContext,\n\tobserve: (event: ProviderSweepTargetEvent) => void | PromiseLike<unknown>,\n) {\n\tconst fanOutStartedAt = performance.now();\n\n\treturn function beginTarget(target: ProviderSweepTarget) {\n\t\tconst startedAt = performance.now();\n\n\t\treturn function reportSettled(slice: SweepSlice | undefined) {\n\t\t\ttry {\n\t\t\t\tvoid Promise.resolve(\n\t\t\t\t\tobserve({\n\t\t\t\t\t\tproviderId: target.providerId,\n\t\t\t\t\t\tdomain: ctx.resolveDomainForRead(target.providerId, target.connectionId, target.domain),\n\t\t\t\t\t\tconnectionId: target.connectionId,\n\t\t\t\t\t\tcount: slice?.items.length ?? 0,\n\t\t\t\t\t\tdurationMs: performance.now() - startedAt,\n\t\t\t\t\t\tqueueWaitMs: startedAt - fanOutStartedAt,\n\t\t\t\t\t\toutcome: sliceOutcome(slice),\n\t\t\t\t\t\ttruncated: slice?.truncated ?? false,\n\t\t\t\t\t}),\n\t\t\t\t).catch(() => {});\n\t\t\t} catch {}\n\t\t};\n\t};\n}\n\nexport async function sweepPullRequests(\n\tctx: ProviderReadContext,\n\toptions?: PullRequestSweepOptions,\n): Promise<ProviderSweepResult<PullRequestShape>> {\n\tconst { targets, attributeUnavailableProviders } = resolvePullRequestSweepTargets(options);\n\n\tconst observe = options?.onTargetSettled;\n\tconst beginTarget = observe != null ? startSweepReporting(ctx, observe) : undefined;\n\n\tconst results = await mapBounded(targets, providerFanOutConcurrency, async target => {\n\t\tconst reportSettled = beginTarget?.(target);\n\t\tconst slice = await sweepTarget(ctx, options, target, attributeUnavailableProviders);\n\t\treportSettled?.(slice);\n\t\treturn slice;\n\t});\n\n\tconst items: PullRequestShape[] = [];\n\tconst warnings: ProviderWarning[] = [];\n\tconst failedProviderIds = new Set<IntegrationIds>();\n\tconst incompleteProviderIds = new Set<IntegrationIds>();\n\tlet fetchFailed = false;\n\tlet truncated = false;\n\tfor (const drain of results) {\n\t\tif (drain == null) {\n\t\t\tcontinue;\n\t\t}\n\n\t\titems.push(...drain.items);\n\t\tfor (const w of drain.warnings) {\n\t\t\tappendDedupedWarning(warnings, w);\n\t\t}\n\t\tif (drain.fetchFailed) {\n\t\t\tfetchFailed = true;\n\t\t}\n\t\tif (drain.failedProvider) {\n\t\t\tfailedProviderIds.add(drain.providerId);\n\t\t} else if (drain.fetchFailed || drain.truncated) {\n\t\t\tincompleteProviderIds.add(drain.providerId);\n\t\t}\n\t\tif (drain.truncated) {\n\t\t\ttruncated = true;\n\t\t}\n\t}\n\n\treturn {\n\t\titems: items,\n\t\twarnings: warnings,\n\t\t// `allPages` asserts completeness — it must be false when any provider truncated (a single-page\n\t\t// account-wide read that couldn't confirm it drained everything) OR a drain aborted on a read\n\t\t// failure (its slice is incomplete). Either way the sweep did not read every page.\n\t\tpage: {\n\t\t\tcurrentPage: 1,\n\t\t\titemsPerPage: items.length,\n\t\t\tallPages: !truncated && !fetchFailed,\n\t\t\ttruncated: truncated || undefined,\n\t\t},\n\t\t// A sweep drains every page itself and exposes no cursor to resume — so `hasMore` must be false even\n\t\t// when the read was incomplete. Terminal incompleteness is expressed through `page.truncated` +\n\t\t// `allPages: false` + warnings; setting `hasMore: true` here would make a consumer that drains while\n\t\t// `hasMore` re-run the identical sweep forever with no cursor to advance.\n\t\thasMore: false,\n\t\tfetchFailed: fetchFailed || undefined,\n\t\tfailedProviderIds: [...failedProviderIds],\n\t\tincompleteProviderIds: [...incompleteProviderIds],\n\t};\n}\n\n/**\n * Closed/merged counterpart of {@link sweepPullRequests}, feeding Kepler's Kanban \"done\" column. Applies\n * the native cross-provider state filter (`Closed` + `Merged`) so it works beyond GitHub.\n */\nexport function sweepClosedPullRequests(\n\tctx: ProviderReadContext,\n\toptions?: ClosedPullRequestSweepOptions,\n): Promise<ProviderSweepResult<PullRequestShape>> {\n\treturn sweepPullRequests(ctx, { ...options, states: ['closed', 'merged'] });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"sweeps.js","names":[],"sources":["../../../../../plus/integrations/src/reads/sweeps.ts"],"sourcesContent":["import type { PullRequestShape } from '@gitlens/git/models/pullRequest.js';\nimport { mapBounded } from '@gitlens/utils/promise.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { providerFanOutConcurrency } from '../constants.js';\nimport type {\n\tClosedPullRequestSweepOptions,\n\tProviderSweepTarget,\n\tProviderSweepTargetEvent,\n\tPullRequestSweepOptions,\n} from '../manager.js';\nimport { toPullRequestRow } from '../providers/models.js';\nimport type { ProviderSweepResult, ProviderWarning } from '../results.js';\nimport { appendDedupedWarning } from '../results.js';\nimport { isGitHostIntegration, isIssuesHostIntegrationId } from '../utils/integration.utils.js';\nimport type { ProviderReadContext } from './context.js';\nimport { drainPullRequests, getPullRequestViewers, resolvePullRequestSweepTargets } from './drains.js';\nimport { resolveAccountWidePullRequestFilters, resolvePullRequestFilters } from './filters.js';\nimport {\n\tgitHostOnlySurfaceWarning,\n\tnoConnectionWarning,\n\tunsupportedAccountWidePullRequestFiltersWarning,\n\tunsupportedFiltersWarning,\n} from './warnings.js';\n\n/**\n * The all-pages pull request reads: one drain per provider target, aggregated into a single sweep result.\n *\n * A sweep exposes no continuation of its own — it drains everything it can reach — so its whole contract lives in\n * the completeness signals it returns: `page.allPages` asserts every page of every target was read,\n * `page.truncated` says pages remain unread, and `failedProviderIds`/`incompleteProviderIds` separate a provider\n * that produced nothing from one that produced a slice with a gap. That split is why the aggregation below can't\n * be collapsed into the generic fan-out merge the hierarchy reads use.\n */\n\n/** One provider target's drained slice, before the cross-provider aggregation. */\ninterface SweepSlice {\n\titems: PullRequestShape[];\n\twarnings: ProviderWarning[];\n\tfetchFailed: boolean;\n\ttruncated: boolean;\n\tproviderId: IntegrationIds;\n\tfailedProvider: boolean;\n}\n\n/**\n * Drain ONE sweep target. Extracted from the fan-out so the fan-out callback has a single exit: every\n * per-target observation (see `onTargetSettled`) is then reported in one place instead of at each of this\n * function's several early returns, where a missed branch would silently drop a provider's attribution.\n *\n * `undefined` means the target resolved to no reachable connection and is deliberately not attributed in the\n * aggregate result.\n */\nasync function sweepTarget(\n\tctx: ProviderReadContext,\n\toptions: PullRequestSweepOptions | undefined,\n\ttarget: ProviderSweepTarget,\n\tattributeUnavailableProviders: boolean,\n): Promise<SweepSlice | undefined> {\n\tconst { providerId: id, connectionId, domain: requestedDomain } = target;\n\tconst repos = options?.repos ?? [];\n\tconst maxPages = options?.maxPages ?? 100;\n\t/** A target that never reached a drain: the provider itself failed, so its slice is empty and attributed. */\n\tconst rejectedTarget = (warnings: ProviderWarning[]): SweepSlice => ({\n\t\titems: [],\n\t\twarnings: warnings,\n\t\tfetchFailed: true,\n\t\ttruncated: false,\n\t\tproviderId: id,\n\t\tfailedProvider: true,\n\t});\n\n\tif (isIssuesHostIntegrationId(id)) {\n\t\treturn rejectedTarget([gitHostOnlySurfaceWarning(id, requestedDomain, connectionId, 'pull request sweeps')]);\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(id, connectionId, requestedDomain);\n\tif (integration == null) {\n\t\t// A requested connection that can't be resolved is a broken connection — surface it as a\n\t\t// warning + fetchFailed rather than dropping the provider's slice silently.\n\t\tconst early = ctx.earlyReturnConnectionWarnings(id, connectionId, requestedDomain);\n\t\tif (early.warnings.length === 0 && !attributeUnavailableProviders) return undefined;\n\n\t\treturn rejectedTarget(\n\t\t\tearly.warnings.length !== 0 ? early.warnings : [noConnectionWarning(id, requestedDomain, connectionId)],\n\t\t);\n\t}\n\tif (!isGitHostIntegration(integration)) {\n\t\treturn rejectedTarget([gitHostOnlySurfaceWarning(id, requestedDomain, connectionId, 'pull request sweeps')]);\n\t}\n\n\tawait ctx.forceRefreshIfRequested(integration, options?.forceSync, connectionId);\n\n\tconst domain = ctx.domainForRead(integration, id, connectionId, requestedDomain);\n\tconst accountWide = repos.length === 0;\n\tconst requestedFilters = target.filters ?? options?.filters;\n\tconst resolved = accountWide\n\t\t? resolveAccountWidePullRequestFilters(id, requestedFilters)\n\t\t: resolvePullRequestFilters(id, requestedFilters);\n\tif (resolved.unsupported) {\n\t\treturn rejectedTarget([\n\t\t\taccountWide\n\t\t\t\t? unsupportedAccountWidePullRequestFiltersWarning(id, domain, connectionId, requestedFilters ?? [])\n\t\t\t\t: unsupportedFiltersWarning(id, domain, connectionId),\n\t\t]);\n\t}\n\n\tconst drain = await drainPullRequests(\n\t\tintegration,\n\t\tid,\n\t\tdomain,\n\t\trepos,\n\t\toptions?.states,\n\t\tresolved.filters,\n\t\toptions,\n\t\tconnectionId,\n\t\tmaxPages,\n\t\tattributeUnavailableProviders,\n\t);\n\tconst viewers = await getPullRequestViewers(integration, connectionId, drain.items);\n\t// Mirrors the projection `drainPullRequests` requests.\n\tconst projection = accountWide ? (options?.includeReviews ? 'account' : 'account-summary') : 'repos';\n\t// Normalize the raw rows to the GitLens-owned shape here, where the per-provider `integration` (the\n\t// mapper's provider reference) is in scope; the aggregation below only sees drains.\n\treturn {\n\t\t...drain,\n\t\titems: drain.items.map((pr, i) =>\n\t\t\ttoPullRequestRow(pr, integration, { currentAccount: viewers[i], projection: projection }),\n\t\t),\n\t\tproviderId: id,\n\t};\n}\n\n/**\n * How a target ended, as the consumer buckets it. `failedProvider` outranks `fetchFailed` because a target\n * whose provider failed produced no slice to be partial about, and `undefined` is the target that resolved to\n * no reachable connection at all.\n */\nfunction sliceOutcome(slice: SweepSlice | undefined): ProviderSweepTargetEvent['outcome'] {\n\tif (slice == null) return 'skipped';\n\tif (slice.failedProvider) return 'failed-provider';\n\tif (slice.fetchFailed) return 'fetch-failed';\n\treturn 'ok';\n}\n\n/**\n * Per-target reporting for a sweep's `onTargetSettled`, opened once per sweep: this stamps the fan-out start,\n * the returned function stamps one target's start, and the function IT returns is the only thing that can\n * report that target. The timestamps therefore never cross a boundary, so there is no pair of interchangeable\n * numbers for a call site to transpose, and the whole reporting concern is one value the fan-out either has or\n * does not. That is also what keeps the option free when it is omitted: with no observer there is nothing to\n * open, so no clock is read and no reporting object exists — do not \"simplify\" that into an unconditional\n * open, because a host whose perf gate is off (the default) is entitled to pay nothing for it.\n *\n * The try/catch is what makes the observer observation-only: called from the fan-out's success path, a throwing\n * callback would otherwise propagate out of the `mapBounded` task and reject the entire sweep — corrupting the\n * read, not just the metric. Swallowed silently; the consumer owns its own aggregation. The `void`-typed\n * callback also admits an `async` consumer, whose rejection is created only after `observe` returns — the\n * `Promise.resolve(...).catch` swallows that too, so it cannot escape as an unhandled rejection.\n *\n * The domain is resolved here rather than carried out of {@link sweepTarget}, so every target reports it by the\n * same rule no matter how far it got. `resolveDomainForRead` needs no integration instance, which is what makes\n * that possible: a target rejected by the first guard resolves the same host a fully drained one does.\n */\nfunction startSweepReporting(\n\tctx: ProviderReadContext,\n\tobserve: (event: ProviderSweepTargetEvent) => void | PromiseLike<unknown>,\n) {\n\tconst fanOutStartedAt = performance.now();\n\n\treturn function beginTarget(target: ProviderSweepTarget) {\n\t\tconst startedAt = performance.now();\n\n\t\treturn function reportSettled(slice: SweepSlice | undefined) {\n\t\t\ttry {\n\t\t\t\tvoid Promise.resolve(\n\t\t\t\t\tobserve({\n\t\t\t\t\t\tproviderId: target.providerId,\n\t\t\t\t\t\tdomain: ctx.resolveDomainForRead(target.providerId, target.connectionId, target.domain),\n\t\t\t\t\t\tconnectionId: target.connectionId,\n\t\t\t\t\t\tcount: slice?.items.length ?? 0,\n\t\t\t\t\t\tdurationMs: performance.now() - startedAt,\n\t\t\t\t\t\tqueueWaitMs: startedAt - fanOutStartedAt,\n\t\t\t\t\t\toutcome: sliceOutcome(slice),\n\t\t\t\t\t\ttruncated: slice?.truncated ?? false,\n\t\t\t\t\t}),\n\t\t\t\t).catch(() => {});\n\t\t\t} catch {}\n\t\t};\n\t};\n}\n\nexport async function sweepPullRequests(\n\tctx: ProviderReadContext,\n\toptions?: PullRequestSweepOptions,\n): Promise<ProviderSweepResult<PullRequestShape>> {\n\tconst { targets, attributeUnavailableProviders } = resolvePullRequestSweepTargets(options);\n\n\tconst observe = options?.onTargetSettled;\n\tconst beginTarget = observe != null ? startSweepReporting(ctx, observe) : undefined;\n\n\tconst results = await mapBounded(targets, providerFanOutConcurrency, async target => {\n\t\tconst reportSettled = beginTarget?.(target);\n\t\tconst slice = await sweepTarget(ctx, options, target, attributeUnavailableProviders);\n\t\treportSettled?.(slice);\n\t\treturn slice;\n\t});\n\n\tconst items: PullRequestShape[] = [];\n\tconst warnings: ProviderWarning[] = [];\n\tconst failedProviderIds = new Set<IntegrationIds>();\n\tconst incompleteProviderIds = new Set<IntegrationIds>();\n\tlet fetchFailed = false;\n\tlet truncated = false;\n\tfor (const drain of results) {\n\t\tif (drain == null) {\n\t\t\tcontinue;\n\t\t}\n\n\t\titems.push(...drain.items);\n\t\tfor (const w of drain.warnings) {\n\t\t\tappendDedupedWarning(warnings, w);\n\t\t}\n\t\tif (drain.fetchFailed) {\n\t\t\tfetchFailed = true;\n\t\t}\n\t\tif (drain.failedProvider) {\n\t\t\tfailedProviderIds.add(drain.providerId);\n\t\t} else if (drain.fetchFailed || drain.truncated) {\n\t\t\tincompleteProviderIds.add(drain.providerId);\n\t\t}\n\t\tif (drain.truncated) {\n\t\t\ttruncated = true;\n\t\t}\n\t}\n\n\treturn {\n\t\titems: items,\n\t\twarnings: warnings,\n\t\t// `allPages` asserts completeness — it must be false when any provider truncated (a single-page\n\t\t// account-wide read that couldn't confirm it drained everything) OR a drain aborted on a read\n\t\t// failure (its slice is incomplete). Either way the sweep did not read every page.\n\t\tpage: {\n\t\t\tcurrentPage: 1,\n\t\t\titemsPerPage: items.length,\n\t\t\tallPages: !truncated && !fetchFailed,\n\t\t\ttruncated: truncated || undefined,\n\t\t},\n\t\t// A sweep drains every page itself and exposes no cursor to resume — so `hasMore` must be false even\n\t\t// when the read was incomplete. Terminal incompleteness is expressed through `page.truncated` +\n\t\t// `allPages: false` + warnings; setting `hasMore: true` here would make a consumer that drains while\n\t\t// `hasMore` re-run the identical sweep forever with no cursor to advance.\n\t\thasMore: false,\n\t\tfetchFailed: fetchFailed || undefined,\n\t\tfailedProviderIds: [...failedProviderIds],\n\t\tincompleteProviderIds: [...incompleteProviderIds],\n\t};\n}\n\n/**\n * Closed/merged counterpart of {@link sweepPullRequests}, feeding Kepler's Kanban \"done\" column. Applies\n * the native cross-provider state filter (`Closed` + `Merged`) so it works beyond GitHub.\n */\nexport function sweepClosedPullRequests(\n\tctx: ProviderReadContext,\n\toptions?: ClosedPullRequestSweepOptions,\n): Promise<ProviderSweepResult<PullRequestShape>> {\n\treturn sweepPullRequests(ctx, { ...options, states: ['closed', 'merged'] });\n}\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import { IssueSorting } from "../../../git/models/issue.js";
2
2
  import { PullRequestFilter, PullRequestSorting } from "../../../git/models/pullRequest.js";
3
3
  import { IntegrationIds } from "../constants.js";
4
- import { IssueFilter } from "../providerFilters.js";
5
4
  import { ProviderWarning } from "../results.js";
5
+ import { IssueFilter } from "../providerFilters.js";
6
6
  import { IssueSearchCriteriaRejection, PullRequestSearchCriteriaRejection } from "./filters.js";
7
7
  import { UnmergeableIssueSort, UnsupportedIssueSortRejection } from "./ordering.js";
8
8
  //#region ../plus/integrations/src/reads/warnings.d.ts
@@ -15,18 +15,31 @@ import { UnmergeableIssueSort, UnsupportedIssueSortRejection } from "./ordering.
15
15
  * refusal can't be phrased two ways in two reads.
16
16
  */
17
17
  /**
18
- * The single builder for a provider-neutral, non-auth warning: an unsupported capability, a
19
- * contradictory/inexpressible request, or a read that couldn't confirm completeness. Every `kind: 'other'`
20
- * warning the facade raises on its own terms goes through here (directly, or via one of the named builders
21
- * below that pin a recurring message), so the discriminant is assigned in one place. The warnings mapped from
22
- * SDK collection metadata are the documented exception — `collectionMetadata.ts` builds those from the
23
- * structured failure/omission it is given.
18
+ * The single builder for a provider-neutral, non-auth `kind: 'other'` warning: a contradictory or malformed
19
+ * request the caller fixes itself, or a read that couldn't confirm completeness. A refusal for a capability the
20
+ * provider lacks goes through {@link unsupportedWarning} instead, so a consumer can tell the two apart without
21
+ * reading `message`. Every `'other'` or `'unsupported'` warning the facade raises on its own terms goes through
22
+ * one of the two (directly, or via a named builder below that pins a recurring message), so the discriminant is
23
+ * assigned in one place. The warnings mapped from SDK collection metadata are the documented exception —
24
+ * `collectionMetadata.ts` builds those from the structured failure/omission it is given.
24
25
  *
25
26
  * The kinds that carry a programmatic remedy — `auth`, `rate-limit`, `not-found`, `no-connection` — are derived
26
27
  * from the caught error's type instead and never come from here; see `ProviderWarningKind` and
27
28
  * {@link noConnectionWarning}.
28
29
  */
29
30
  export declare function otherWarning(id: IntegrationIds, domain: string | undefined, connectionId: string | undefined, message: string): ProviderWarning;
31
+ /**
32
+ * Builds a `kind: 'unsupported'` warning: the provider — or this integration of it — lacks the capability the
33
+ * request asked for: a surface (issues on Bitbucket, pull requests on an issue tracker, a batch read, org/repo
34
+ * discovery, current-account lookup, project-scoped or account-wide issue reads), or a filter, sort, state,
35
+ * search criterion or option combination it cannot express. Retrying the same request never succeeds; a
36
+ * different provider, scope or request may.
37
+ *
38
+ * NOT for malformed or contradictory caller input the caller fixes in the request itself (a duplicate batch key,
39
+ * invalid target numbers, an empty owner, an unusable scope), nor for incompleteness warnings; those are
40
+ * {@link otherWarning}.
41
+ */
42
+ export declare function unsupportedWarning(id: IntegrationIds, domain: string | undefined, connectionId: string | undefined, message: string): ProviderWarning;
30
43
  /**
31
44
  * Builds a `no-connection` warning for a per-connection read that resolved neither a session nor an error: the
32
45
  * requested `connectionId` no longer resolves (deleted, or its authentication is invalid). Consumers use this
@@ -109,7 +122,9 @@ export declare function unsupportedPullRequestSearchCriteriaWarning(id: Integrat
109
122
  *
110
123
  * One builder for all three because they are one decision from the caller's side ("this search can't be run as
111
124
  * asked") and the remedy differs only in what the message says. The rejection shape carries which case it is, so
112
- * the wording can't drift from the check that produced it (see {@link resolveIssueSearchCriteria}).
125
+ * the wording can't drift from the check that produced it (see {@link resolveIssueSearchCriteria}). The kind
126
+ * follows the reason: the first two are `'unsupported'` capabilities; a contradictory set is caller input and
127
+ * stays `'other'`.
113
128
  */
114
129
  export declare function unsupportedIssueSearchCriteriaWarning(id: IntegrationIds, domain: string | undefined, connectionId: string | undefined, rejection: IssueSearchCriteriaRejection): ProviderWarning;
115
130
  /**