@gitkraken/core-gitlens 0.9.4 → 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 (154) hide show
  1. package/CHANGELOG.md +22 -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 +7 -3
  27. package/dist/plus/integrations/integrationService.js.map +1 -1
  28. package/dist/plus/integrations/manager.d.ts +56 -6
  29. package/dist/plus/integrations/models/gitHostIntegration.d.ts +103 -9
  30. package/dist/plus/integrations/models/gitHostIntegration.js +116 -5
  31. package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
  32. package/dist/plus/integrations/models/integration.d.ts +91 -4
  33. package/dist/plus/integrations/models/integration.js +56 -7
  34. package/dist/plus/integrations/models/integration.js.map +1 -1
  35. package/dist/plus/integrations/models/issuesIntegration.d.ts +44 -3
  36. package/dist/plus/integrations/models/issuesIntegration.js +51 -1
  37. package/dist/plus/integrations/models/issuesIntegration.js.map +1 -1
  38. package/dist/plus/integrations/providers/azure/azure.js +3 -3
  39. package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
  40. package/dist/plus/integrations/providers/azure/models.d.ts +36 -4
  41. package/dist/plus/integrations/providers/azure/models.js +118 -8
  42. package/dist/plus/integrations/providers/azure/models.js.map +1 -1
  43. package/dist/plus/integrations/providers/azureDevOps.d.ts +102 -5
  44. package/dist/plus/integrations/providers/azureDevOps.js +264 -45
  45. package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
  46. package/dist/plus/integrations/providers/azureDevOpsServer.d.ts +30 -6
  47. package/dist/plus/integrations/providers/azureDevOpsServer.js +82 -21
  48. package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -1
  49. package/dist/plus/integrations/providers/bitbucket/bitbucket.d.ts +21 -1
  50. package/dist/plus/integrations/providers/bitbucket/bitbucket.js +74 -10
  51. package/dist/plus/integrations/providers/bitbucket/bitbucket.js.map +1 -1
  52. package/dist/plus/integrations/providers/bitbucket/models.d.ts +40 -3
  53. package/dist/plus/integrations/providers/bitbucket/models.js +30 -7
  54. package/dist/plus/integrations/providers/bitbucket/models.js.map +1 -1
  55. package/dist/plus/integrations/providers/bitbucket-server/models.d.ts +2 -6
  56. package/dist/plus/integrations/providers/bitbucket-server/models.js +1 -3
  57. package/dist/plus/integrations/providers/bitbucket-server/models.js.map +1 -1
  58. package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.d.ts +3 -1
  59. package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.js +4 -1
  60. package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.js.map +1 -1
  61. package/dist/plus/integrations/providers/bitbucket-server.d.ts +2 -2
  62. package/dist/plus/integrations/providers/bitbucket-server.js +7 -5
  63. package/dist/plus/integrations/providers/bitbucket-server.js.map +1 -1
  64. package/dist/plus/integrations/providers/bitbucket.d.ts +22 -2
  65. package/dist/plus/integrations/providers/bitbucket.js +29 -6
  66. package/dist/plus/integrations/providers/bitbucket.js.map +1 -1
  67. package/dist/plus/integrations/providers/github.d.ts +40 -3
  68. package/dist/plus/integrations/providers/github.js +133 -11
  69. package/dist/plus/integrations/providers/github.js.map +1 -1
  70. package/dist/plus/integrations/providers/gitlab/gitlab.d.ts +34 -1
  71. package/dist/plus/integrations/providers/gitlab/gitlab.js +118 -4
  72. package/dist/plus/integrations/providers/gitlab/gitlab.js.map +1 -1
  73. package/dist/plus/integrations/providers/gitlab/models.d.ts +70 -5
  74. package/dist/plus/integrations/providers/gitlab/models.js +106 -13
  75. package/dist/plus/integrations/providers/gitlab/models.js.map +1 -1
  76. package/dist/plus/integrations/providers/gitlab.d.ts +31 -2
  77. package/dist/plus/integrations/providers/gitlab.js +48 -8
  78. package/dist/plus/integrations/providers/gitlab.js.map +1 -1
  79. package/dist/plus/integrations/providers/issueSorts.d.ts +1 -0
  80. package/dist/plus/integrations/providers/jira-server.d.ts +1 -0
  81. package/dist/plus/integrations/providers/jira-server.js +9 -6
  82. package/dist/plus/integrations/providers/jira-server.js.map +1 -1
  83. package/dist/plus/integrations/providers/jira.d.ts +20 -1
  84. package/dist/plus/integrations/providers/jira.js +101 -6
  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 +97 -5
  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 +51 -7
  96. package/dist/plus/integrations/providers/models.js +45 -16
  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 +62 -3
  101. package/dist/plus/integrations/providers/providersApi.js +145 -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 +10 -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/drains.d.ts +13 -3
  120. package/dist/plus/integrations/reads/drains.js +29 -5
  121. package/dist/plus/integrations/reads/drains.js.map +1 -1
  122. package/dist/plus/integrations/reads/etag.d.ts +26 -0
  123. package/dist/plus/integrations/reads/etag.js +108 -0
  124. package/dist/plus/integrations/reads/etag.js.map +1 -0
  125. package/dist/plus/integrations/reads/hierarchy.js +3 -3
  126. package/dist/plus/integrations/reads/hierarchy.js.map +1 -1
  127. package/dist/plus/integrations/reads/issueBatch.d.ts +47 -16
  128. package/dist/plus/integrations/reads/issueBatch.js +55 -87
  129. package/dist/plus/integrations/reads/issueBatch.js.map +1 -1
  130. package/dist/plus/integrations/reads/issueTracker.d.ts +1 -1
  131. package/dist/plus/integrations/reads/issueTracker.js +4 -4
  132. package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
  133. package/dist/plus/integrations/reads/issues.d.ts +1 -1
  134. package/dist/plus/integrations/reads/issues.js +4 -4
  135. package/dist/plus/integrations/reads/issues.js.map +1 -1
  136. package/dist/plus/integrations/reads/pullRequestBatch.d.ts +44 -6
  137. package/dist/plus/integrations/reads/pullRequestBatch.js +31 -34
  138. package/dist/plus/integrations/reads/pullRequestBatch.js.map +1 -1
  139. package/dist/plus/integrations/reads/pullRequestBranches.d.ts +5 -2
  140. package/dist/plus/integrations/reads/pullRequestBranches.js +4 -3
  141. package/dist/plus/integrations/reads/pullRequestBranches.js.map +1 -1
  142. package/dist/plus/integrations/reads/pullRequests.js +8 -4
  143. package/dist/plus/integrations/reads/pullRequests.js.map +1 -1
  144. package/dist/plus/integrations/reads/sweeps.js +8 -4
  145. package/dist/plus/integrations/reads/sweeps.js.map +1 -1
  146. package/dist/plus/integrations/reads/warnings.d.ts +23 -8
  147. package/dist/plus/integrations/reads/warnings.js +43 -19
  148. package/dist/plus/integrations/reads/warnings.js.map +1 -1
  149. package/dist/plus/integrations/results.d.ts +12 -4
  150. package/dist/plus/integrations/results.js +2 -0
  151. package/dist/plus/integrations/results.js.map +1 -1
  152. package/docs/integrations.md +385 -23
  153. package/docs/kepler-read-api-parity.md +16 -2
  154. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"issues.js","names":[],"sources":["../../../../../plus/integrations/src/reads/issues.ts"],"sourcesContent":["import type { IssueShape, IssueSorting } from '@gitlens/git/models/issue.js';\nimport { mergeAssessmentInto } from '../collectionMetadata.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { GitCloudHostIntegrationId, GitSelfManagedHostIntegrationId } from '../constants.js';\nimport type { GitHostIntegration } from '../models/gitHostIntegration.js';\nimport type { IssueFilter, ProviderReposInput } from '../providers/models.js';\nimport { isRepoIdsInput, PagingMode, providersMetadata } from '../providers/models.js';\nimport { mergeCollectionMetadata } from '../providers/utils/providerPaging.js';\nimport type { ProviderPagedResult, ProviderWarning } 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 { runCaptured } from './drains.js';\nimport { resolveAccountWideIssueFilters } from './filters.js';\nimport { resolveIssueSort, toIssueOrdering } from './ordering.js';\nimport {\n\tdrainFlatPagesToRequestedPage,\n\tdrainToRequestedPage,\n\tisPageNumberAdvanceable,\n\tpageToCursor,\n\trefusedPage,\n\tresolveContinuation,\n\tresolveCurrentPage,\n\ttoProviderPageInfo,\n\tusableCursor,\n} from './paging.js';\nimport {\n\tgitHostOnlySurfaceWarning,\n\tissuesUnsupportedWarning,\n\totherWarning,\n\ttruncationWarning,\n\tunmergeableIssueSortWarning,\n\tunsupportedAccountWideIssueFiltersWarning,\n\tunsupportedIssueSortWarning,\n} from './warnings.js';\n\n/**\n * Whether a repo-scoped page is assembled from SEVERAL provider queries and merged here — which is what decides\n * whether the requested order can be honored at all.\n *\n * `PagingMode.Repos` (GitHub) sends one search however many repositories are named, so its page arrives ordered by\n * the provider. `Repo` (GitLab) issues one query per repository, and `Project` (Azure) one per PROJECT — several\n * repositories of the same project are still one query, which is why that case counts distinct projects rather than\n * repositories.\n *\n * The `Repo` case has a second way to merge that has nothing to do with the count: given repository IDS rather than\n * descriptors, `getMyIssuesForReposResult` skips its per-repository fan-out entirely (that branch is guarded on\n * `!isRepoIdsInput`) and calls the SDK's `getIssuesForRepos`, which for GitLab is the multi-project aggregate that\n * merges in the SDK and refuses `priority`/`dueDate` however few scopes it was given. Treating that as merged is\n * what makes this facade refuse it up front, with the message that names the reason, instead of letting the SDK\n * reject a read the capability table had just promised.\n */\nfunction mergesProviderQueries(pagingMode: PagingMode | undefined, repos: ProviderReposInput | undefined): boolean {\n\tconst scopes = repos ?? [];\n\tswitch (pagingMode) {\n\t\tcase PagingMode.Repo:\n\t\t\t// `isRepoIdsInput` is the same predicate `ProvidersApi` selects the SDK aggregate with, so this cannot\n\t\t\t// disagree with the branch it is predicting.\n\t\t\treturn isRepoIdsInput(scopes) ? scopes.length > 0 : scopes.length > 1;\n\t\tcase PagingMode.Project:\n\t\t\t// Only the descriptor form carries a project; the id form is refused by the Azure read before it runs.\n\t\t\treturn new Set(scopes.map(r => (typeof r === 'object' ? r.project : undefined))).size > 1;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\n/**\n * What {@link listIssuesPage} accepts, shared by name with the two functions it dispatches to.\n *\n * Named rather than left inline: three functions now take this exact shape, and reaching it from the other two as\n * `Parameters<typeof listIssuesPage>[1]` would define the contract in terms of the function that consumes it —\n * inside out, and unreadable at the point where the fields are actually used.\n */\nexport interface ListIssuesPageOptions {\n\tproviderId: IntegrationIds;\n\trepos?: ProviderReposInput;\n\t/** Narrows the account-wide read to one org/account. Requires a host with a project layer (Azure). */\n\torg?: string;\n\t/** Narrows the account-wide read to one project. Requires a host with a project layer (Azure). */\n\tproject?: string;\n\t/**\n\t * Narrows to the requested relationship(s). On the account-wide path this replaces the provider's own\n\t * definition of \"my issues\" (GitHub/GHE: authored ∪ assigned ∪ mentioned; Azure: assigned ∪ authored;\n\t * GitLab: assigned-to-me), so `[Assignee]` yields `assignee:@me` everywhere it's expressible. A set the\n\t * provider can't express server-side is refused whole (warning + `fetchFailed`), never widened — check\n\t * getSupportedFilters first to avoid that path.\n\t */\n\tfilters?: IssueFilter[];\n\t/** Broadens the read to every assignee. Contradicts `filters`; passing both is refused. */\n\tincludeAllAssignees?: boolean;\n\t/**\n\t * How to order the page, as `field:direction`. Omitted orders most-recently-updated-first wherever the\n\t * provider can express that, which is this facade's default rather than the provider's own.\n\t *\n\t * Validated against `getSupportedFilters().issueSorts` on the repo-scoped path and `.issueSortsAccountWide`\n\t * on the account-wide one — two genuinely different vocabularies for GitLab, whose two reads are different\n\t * APIs. A key the provider can't express refuses the read (warning + `fetchFailed`) rather than serving a\n\t * differently-ordered list, for the same reason an inexpressible filter does: the reachable window is\n\t * bounded, so another order is another subset.\n\t *\n\t * On the repo-scoped path with SEVERAL scopes the page is a merge of one query per repository/project, so\n\t * only a key a normalized issue carries can be honored: `priority`, `dueDate` and `resolved` are refused\n\t * there even where the provider supports them on a single scope. Order is per page, and per scope across\n\t * pages — the merge orders what a page contains, not the sequence of pages.\n\t */\n\tsort?: IssueSorting;\n\tpage?: number;\n\tcursor?: string;\n\titemsPerPage?: number;\n\tforceSync?: boolean;\n\tconnectionId?: string;\n\t/**\n\t * Explicit self-managed host domain. Used only when the requested connection has no configured domain;\n\t * it must come from the trusted authentication configuration, not repository or remote data.\n\t */\n\tdomain?: string;\n}\n\n/**\n * What both halves of {@link listIssuesPage} need from its shared prologue.\n *\n * A bundle rather than six parameters because the two reads take exactly the same inputs and differ only in what\n * they do with them; threading them positionally would make the two call sites read as two different reads of two\n * different things, which is the opposite of what is true.\n *\n * Note `ctx` is absent: every use of it — resolving the integration, the domain, the forced refresh, the\n * connection warnings — is in the prologue, which is what makes this the right cut.\n */\ninterface IssueReadContext {\n\toptions: ListIssuesPageOptions;\n\tintegration: GitHostIntegration;\n\tdomain: string | undefined;\n\twarnOnMissingSession: boolean;\n\tpage: number;\n}\n\nexport async function listIssuesPage(\n\tctx: ProviderReadContext,\n\toptions: ListIssuesPageOptions,\n): Promise<ProviderPagedResult<IssueShape>> {\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, 'repository issue 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, 'repository issue 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\tconst warnOnMissingSession = warnOnMissingSessionForDomain(options.providerId, options.domain);\n\n\t// A git host whose issue tracker is deprecated (Bitbucket, superseded by dedicated issue integrations)\n\t// reports issues as explicitly unsupported rather than serving a partial/legacy source or a silent empty.\n\tif (!integration.supportsIssues) {\n\t\treturn refusedPage(page, [issuesUnsupportedWarning(options.providerId, domain, options.connectionId)], true);\n\t}\n\n\t// `repos` is what selects between the two: with none there is no scope to query per repository, so the\n\t// provider's own account-wide search answers instead. They are different provider queries with different\n\t// capability tables, cursors and completeness signals — see each function.\n\tconst context: IssueReadContext = {\n\t\toptions: options,\n\t\tintegration: integration,\n\t\tdomain: domain,\n\t\twarnOnMissingSession: warnOnMissingSession,\n\t\tpage: page,\n\t};\n\treturn (options.repos?.length ?? 0) === 0 ? readAccountWideIssuesPage(context) : readRepoScopedIssuesPage(context);\n}\n\n/**\n * The account-wide read: the provider's own definition of \"my issues\", behind a composite cursor that spans\n * several provider searches.\n *\n * Its own function rather than a branch of the paged read, because almost nothing is shared past the guards: it\n * validates a different filter table and a different sort table, it is cursor-only (a page number cannot address\n * a composite cursor), it accumulates per-scope failures across the pages it walks, and it always merges several\n * queries. The two used to share one 460-line function behind a mode flag, and the comments explaining how far\n * apart they are were the clue that they wanted separating.\n */\nasync function readAccountWideIssuesPage({\n\toptions,\n\tintegration,\n\tdomain,\n\twarnOnMissingSession,\n\tpage,\n}: IssueReadContext): Promise<ProviderPagedResult<IssueShape>> {\n\t// The account-wide read is cursor-only, so a refusal can't claim the requested position — it reports\n\t// page 1, per ProviderPageInfo.currentPage.\n\tconst refused = (warning: ProviderWarning) => refusedPage<IssueShape>(1, [warning], true);\n\n\t// Checked before the provider-specific guards below: a caller passing both has a contradictory request\n\t// whatever the provider, and saying so is more useful than reporting one half of it as unsupported.\n\t// `filters` narrows this read to a relationship (`[Assignee]` ⇒ just assigned-to-me); `includeAllAssignees`\n\t// broadens it to every assignee. Honoring either silently would answer a question the caller didn't ask.\n\tif (options.filters?.length && options.includeAllAssignees === true) {\n\t\treturn refused(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'`filters` and `includeAllAssignees` are contradictory for an account-wide issue read; pass only one.',\n\t\t\t),\n\t\t);\n\t}\n\n\t// GitHub expresses \"any assignee\" as `assignee:*`, which is meaningless without a scope: unscoped it\n\t// matches millions of issues across all of GitHub instead of the user's own world. ANY scope makes it\n\t// meaningful (one repository, several, or an org) — it is specifically THIS branch, the account-wide read,\n\t// that has none to offer. Scope the read to repositories (or use `searchIssuesPage`, whose criteria model\n\t// takes `any-assignee` alongside a `repos`/`org` scope) rather than reading unscoped.\n\tif (\n\t\toptions.includeAllAssignees === true &&\n\t\t(options.providerId === GitCloudHostIntegrationId.GitHub ||\n\t\t\toptions.providerId === GitSelfManagedHostIntegrationId.CloudGitHubEnterprise)\n\t) {\n\t\treturn refused(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'`includeAllAssignees` is not supported for account-wide GitHub issue reads; scope the read to repositories instead.',\n\t\t\t),\n\t\t);\n\t}\n\n\t// Only a host with a project layer can narrow server-side. Reject the request rather than serving an\n\t// unscoped list as if it had been narrowed: the caller would otherwise have to filter client-side,\n\t// which desynchronizes the filtered `items` from this read's `hasMore`/`currentPage` and shows\n\t// \"no issues\" for a page that simply held none of the requested project's.\n\tif ((options.org != null || options.project != null) && !integration.supportsProjectDiscovery) {\n\t\treturn refused(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`Project-scoped issue reads are not supported by '${options.providerId}'; scope the read to repositories instead.`,\n\t\t\t),\n\t\t);\n\t}\n\n\t// Narrowing the account-wide read is only honest when the provider can express it server-side: its\n\t// per-relationship queries produced the page and the cursor together, so dropping items afterward would\n\t// leave `items` describing a different result set than `hasMore`/`currentPage`.\n\tconst resolvedIssueFilters = resolveAccountWideIssueFilters(options.providerId, options.filters);\n\tif (resolvedIssueFilters.unsupported) {\n\t\treturn refused(\n\t\t\tunsupportedAccountWideIssueFiltersWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\toptions.filters!,\n\t\t\t),\n\t\t);\n\t}\n\n\t// Every provider's account-wide read is a UNION of several queries — GitHub's three `@me` searches,\n\t// GitLab's one REST call per relationship, Azure's (project x relationship) drains — so it always merges,\n\t// and `supportedAccountWideIssueSorts` already lists only keys a merge can honor. No separate\n\t// mergeability refusal is needed here, unlike on the repo-scoped path below.\n\tconst resolvedSort = resolveIssueSort(\n\t\tprovidersMetadata[options.providerId]?.supportedAccountWideIssueSorts,\n\t\toptions.sort,\n\t);\n\tif (resolvedSort.rejection != null) {\n\t\treturn refused(\n\t\t\tunsupportedIssueSortWarning(options.providerId, domain, options.connectionId, resolvedSort.rejection),\n\t\t);\n\t}\n\n\t// `merged: true` unconditionally: this read has no scope count for a caller to reduce. It is also why no\n\t// `unmergeable` check follows — `supportedAccountWideIssueSorts` declares only keys a merge can honor.\n\tconst accountWideOrdering = toIssueOrdering(resolvedSort.sort, true);\n\n\t// The repo-scoped core rejects empty repos (GitHub/Bitbucket/Azure); read the account-wide,\n\t// already-user-scoped core instead. GitHub exposes a composite cursor across its authored,\n\t// assigned, and mentioned searches. Walk it internally when the caller supplies only page N.\n\tconst readAccountWidePage = (cursor: string | undefined) =>\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\tintegration.searchMyIssuesWithTruncationResult(undefined, undefined, options.connectionId, {\n\t\t\t\t\tincludeAllAssignees: options.includeAllAssignees,\n\t\t\t\t\tfilters: resolvedIssueFilters.filters,\n\t\t\t\t\tcursor: cursor,\n\t\t\t\t\tpageSize: options.itemsPerPage,\n\t\t\t\t\torg: options.org,\n\t\t\t\t\tproject: options.project,\n\t\t\t\t\tsort: accountWideOrdering.sort,\n\t\t\t\t}),\n\t\t\t{ warnOnMissingSession: warnOnMissingSession },\n\t\t);\n\tconst first = await readAccountWidePage(options.cursor);\n\tconst warnings = first.warning != null ? [first.warning] : [];\n\t// Merged across the walked pages, so a per-scope failure reported on an earlier page isn't lost by\n\t// paging past it — the one thing this read accumulates that the filtered search doesn't.\n\tlet allMetadata = first.value?.metadata;\n\tconst drained = await drainFlatPagesToRequestedPage(first, {\n\t\trequestedPage: page,\n\t\tsuppliedCursor: options.cursor,\n\t\twarnings: warnings,\n\t\treadPage: readAccountWidePage,\n\t\tfold: p => {\n\t\t\tallMetadata = mergeCollectionMetadata(allMetadata, p.metadata);\n\t\t},\n\t});\n\tconst { value, currentPage, requestedPageMissing } = drained;\n\tconst currentTruncated = drained.truncated;\n\tconst pageFetchFailed = drained.fetchFailed;\n\n\t// GitHub, GitLab, and Azure implement an account-wide issue search; a provider that doesn't (Bitbucket\n\t// exposes no issues at all, and `supportsIssues` already short-circuits it above) returns `undefined`\n\t// with no error. Surface that as an explicit unsupported warning + fetchFailed rather than a silent\n\t// empty success — the caller must fall back (e.g. broadenIssues over repos).\n\tif (value == null && warnings.length === 0) {\n\t\treturn refused(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`Account-wide issue search is not supported by '${options.providerId}'; scope the read to repositories instead.`,\n\t\t\t),\n\t\t);\n\t}\n\n\tconst items = requestedPageMissing ? [] : (value?.values ?? []);\n\t// Each query arrived ordered by the provider; their union is not, and the union is what this read publishes.\n\t// Idempotent for GitHub, whose aliased-search engine already ordered the same merge — one call here rather\n\t// than the same logic repeated in each of the three provider implementations.\n\tconst orderedItems = accountWideOrdering.order(items);\n\t// Fold in structured per-scope failures from the account-wide fan-out (e.g. Azure across projects):\n\t// scope-aware warnings + `fetchFailed` when a scope failed, without discarding the successful items.\n\tconst assessment = mergeAssessmentInto(warnings, options.providerId, domain, options.connectionId, allMetadata);\n\t// An account-wide search that couldn't confirm completeness (a provider cap with no cursor, or a\n\t// per-scope backstop/failure) is incomplete and can't be paged; report it as truncated (+ a\n\t// provider-neutral warning, unless a structured failure already explains it) rather than a complete\n\t// list. Don't hard-code GitHub's \"100 per category\" cap here — Azure reaches this via a per-project\n\t// backstop, and other providers may cap differently.\n\t// The account-wide read is cursor-only (its composite cursor spans several provider searches, so a\n\t// page number can't address it): `hasMore` without a real cursor is a dead end, so report it as\n\t// terminal-but-incomplete rather than inviting the caller to page forever.\n\tconst continuation = resolveContinuation(\n\t\t{\n\t\t\thasMore: requestedPageMissing ? false : (value?.hasMore ?? false),\n\t\t\tcursor: requestedPageMissing ? undefined : usableCursor(value?.cursor),\n\t\t\ttruncated: currentTruncated,\n\t\t},\n\t\tundefined,\n\t);\n\tconst truncated = continuation.truncated || assessment.truncated;\n\tif (truncated && warnings.length === 0) {\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'Account-wide issue search',\n\t\t\t\t// `exhausted`: this composite read exposes no budget the caller can raise, so nothing it\n\t\t\t\t// could call would return the withheld items.\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\treturn {\n\t\titems: orderedItems,\n\t\twarnings: warnings,\n\t\tpage: {\n\t\t\t// Positional, per ProviderPageInfo.currentPage. `currentPage` already carries what the provider\n\t\t\t// reported or what the internal drain counted; a requested page past the terminal cursor is\n\t\t\t// reported as that empty page N. The account-wide read is cursor-only, so a `page` the caller\n\t\t\t// didn't pair with a cursor is never echoed.\n\t\t\tcurrentPage: requestedPageMissing\n\t\t\t\t? page\n\t\t\t\t: resolveCurrentPage({\n\t\t\t\t\t\tproviderPage: currentPage,\n\t\t\t\t\t\trequestedPage: page,\n\t\t\t\t\t\tsuppliedCursor: options.cursor,\n\t\t\t\t\t\tpageAdvanceable: false,\n\t\t\t\t\t}),\n\t\t\titemsPerPage: orderedItems.length,\n\t\t\ttruncated: truncated || undefined,\n\t\t},\n\t\thasMore: continuation.hasMore,\n\t\tcursor: continuation.cursor,\n\t\tfetchFailed: assessment.fetchFailed || pageFetchFailed || undefined,\n\t};\n}\n\n/**\n * The repository-scoped read: one provider query per repository or project, paged by the provider's own\n * continuation and drained internally when the caller supplied only a page number.\n *\n * See {@link readAccountWideIssuesPage} for why the two are separate functions.\n */\nasync function readRepoScopedIssuesPage({\n\toptions,\n\tintegration,\n\tdomain,\n\twarnOnMissingSession,\n\tpage,\n}: IssueReadContext): Promise<ProviderPagedResult<IssueShape>> {\n\tconst metadata = providersMetadata[options.providerId];\n\n\t// How many separate provider queries this page is assembled from, which decides whether the requested order can\n\t// be honored at all. `PagingMode.Repos` (GitHub) sends ONE search however many repositories are named, so its\n\t// page arrives ordered by the provider; `Repo` (GitLab) and `Project` (Azure) issue one query per scope and are\n\t// concatenated here, so a page spanning several of them is only as orderable as a normalized issue is.\n\tconst resolvedRepoScopedSort = resolveIssueSort(metadata?.supportedIssueSorts, options.sort);\n\tif (resolvedRepoScopedSort.rejection != null) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[\n\t\t\t\tunsupportedIssueSortWarning(\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\tresolvedRepoScopedSort.rejection,\n\t\t\t\t),\n\t\t\t],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\tconst issuesPagingMode = metadata?.issuesPagingMode;\n\tconst ordering = toIssueOrdering(\n\t\tresolvedRepoScopedSort.sort,\n\t\tmergesProviderQueries(issuesPagingMode, options.repos),\n\t);\n\t// Refused rather than served as concatenated per-scope runs, which would look ordered without being so — and\n\t// only where it actually merges, since the same key against the same provider is perfectly answerable for a\n\t// single repository or project.\n\tif (ordering.unmergeable != null) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[\n\t\t\t\tunmergeableIssueSortWarning(\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\tordering.unmergeable,\n\t\t\t\t\tissuesPagingMode === PagingMode.Project ? 'projects' : 'repositories',\n\t\t\t\t),\n\t\t\t],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\tconst cursor = options.cursor ?? pageToCursor(page);\n\tconst { value, warning } = await runCaptured(\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\t() =>\n\t\t\t// The shapes seam returns normalized IssueShape, and is overridable by a provider whose only issue\n\t\t\t// client already yields shapes (serving this path without a raw ProviderIssue round-trip).\n\t\t\tintegration.getMyIssuesForReposAsShapesResult(\n\t\t\t\toptions.repos ?? [],\n\t\t\t\t// Forward `page`/`pageSize` alongside the cursor so PagingMode.Repo/Project hosts honor the\n\t\t\t\t// requested page and page size rather than ignoring a synthesized page-number cursor.\n\t\t\t\t{\n\t\t\t\t\tfilters: options.filters,\n\t\t\t\t\tincludeAllAssignees: options.includeAllAssignees,\n\t\t\t\t\tcursor: cursor,\n\t\t\t\t\t// The normalized `page`, so the number forwarded to the provider can't disagree with the\n\t\t\t\t\t// page-number cursor beside it — both are built from the same value. Only when the caller asked\n\t\t\t\t\t// for one: an omitted page is the provider's own first page.\n\t\t\t\t\tpage: options.page != null ? page : undefined,\n\t\t\t\t\tpageSize: options.itemsPerPage,\n\t\t\t\t\tsort: ordering.sort,\n\t\t\t\t},\n\t\t\t\toptions.connectionId,\n\t\t\t),\n\t\t{ warnOnMissingSession: warnOnMissingSession },\n\t);\n\n\tlet items = value?.values ?? [];\n\tconst warnings = warning != null ? [warning] : [];\n\tlet pageFetchFailed = warning != null && value == null;\n\tlet paged = toProviderPageInfo(options.itemsPerPage ?? items.length, value?.paging);\n\tlet allMetadata = value?.metadata;\n\n\t// Cursor-only repo-scoped hosts (e.g. GitHub) ignore a synthesized page-number cursor, so a page-only\n\t// request is advanced through the provider's own continuations (see drainToRequestedPage).\n\tif (\n\t\tissuesPagingMode === 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(\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\treadPage: (cursor: string) =>\n\t\t\t\t\trunCaptured(\n\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\tdomain,\n\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tintegration.getMyIssuesForReposAsShapesResult(\n\t\t\t\t\t\t\t\toptions.repos ?? [],\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tfilters: options.filters,\n\t\t\t\t\t\t\t\t\tincludeAllAssignees: options.includeAllAssignees,\n\t\t\t\t\t\t\t\t\tcursor: cursor,\n\t\t\t\t\t\t\t\t\tpageSize: options.itemsPerPage,\n\t\t\t\t\t\t\t\t\tsort: ordering.sort,\n\t\t\t\t\t\t\t\t},\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{ warnOnMissingSession: warnOnMissingSession },\n\t\t\t\t\t),\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\t// Ordered once the drain (above) has assembled whichever pages this position spans.\n\titems = ordering.order(items);\n\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 assessment = mergeAssessmentInto(warnings, options.providerId, domain, options.connectionId, allMetadata);\n\t// Never advertise `hasMore` without a continuation the caller can act on, and only synthesize a page\n\t// number for a host that reads it as one (see `isPageNumberAdvanceable`).\n\tconst issuesPageAdvanceable = isPageNumberAdvanceable(issuesPagingMode);\n\tconst continuation = resolveContinuation(paged, issuesPageAdvanceable ? paged.page.currentPage + 1 : undefined);\n\t// A provider read that couldn't confirm completeness (e.g. Bitbucket's single-page repo issue read\n\t// that dropped a repo) sets `paging.truncated`; surface it as a terminal `page.truncated` so a partial\n\t// page isn't published as complete. 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\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'Issue',\n\t\t\t\t// `exhausted`, never `page-budget`: a paged read has no budget the caller can raise, and\n\t\t\t\t// ordinary continuation is already expressed by `hasMore`/`cursor`.\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\treturn {\n\t\titems: items,\n\t\twarnings: warnings,\n\t\tpage: {\n\t\t\t...paged.page,\n\t\t\t// Positional, per ProviderPageInfo.currentPage: the drain (above) already advanced `paged.page` for\n\t\t\t// a cursor-only host, and a caller-threaded cursor advanced the provider without one.\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: issuesPageAdvanceable,\n\t\t\t}),\n\t\t\ttruncated: truncated || undefined,\n\t\t},\n\t\thasMore: continuation.hasMore,\n\t\tcursor: continuation.cursor,\n\t\tfetchFailed: assessment.fetchFailed || pageFetchFailed || undefined,\n\t};\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"issues.js","names":[],"sources":["../../../../../plus/integrations/src/reads/issues.ts"],"sourcesContent":["import type { IssueShape, IssueSorting } from '@gitlens/git/models/issue.js';\nimport { mergeAssessmentInto } from '../collectionMetadata.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { GitCloudHostIntegrationId, GitSelfManagedHostIntegrationId } from '../constants.js';\nimport type { GitHostIntegration } from '../models/gitHostIntegration.js';\nimport type { IssueFilter, ProviderReposInput } from '../providers/models.js';\nimport { isRepoIdsInput, PagingMode, providersMetadata } from '../providers/models.js';\nimport { mergeCollectionMetadata } from '../providers/utils/providerPaging.js';\nimport type { ProviderPagedResult, ProviderWarning } 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 { runCaptured } from './drains.js';\nimport { resolveAccountWideIssueFilters } from './filters.js';\nimport { resolveIssueSort, toIssueOrdering } from './ordering.js';\nimport {\n\tdrainFlatPagesToRequestedPage,\n\tdrainToRequestedPage,\n\tisPageNumberAdvanceable,\n\tpageToCursor,\n\trefusedPage,\n\tresolveContinuation,\n\tresolveCurrentPage,\n\ttoProviderPageInfo,\n\tusableCursor,\n} from './paging.js';\nimport {\n\tgitHostOnlySurfaceWarning,\n\tissuesUnsupportedWarning,\n\totherWarning,\n\ttruncationWarning,\n\tunmergeableIssueSortWarning,\n\tunsupportedAccountWideIssueFiltersWarning,\n\tunsupportedIssueSortWarning,\n\tunsupportedWarning,\n} from './warnings.js';\n\n/**\n * Whether a repo-scoped page is assembled from SEVERAL provider queries and merged here — which is what decides\n * whether the requested order can be honored at all.\n *\n * `PagingMode.Repos` (GitHub) sends one search however many repositories are named, so its page arrives ordered by\n * the provider. `Repo` (GitLab) issues one query per repository, and `Project` (Azure) one per PROJECT — several\n * repositories of the same project are still one query, which is why that case counts distinct projects rather than\n * repositories.\n *\n * The `Repo` case has a second way to merge that has nothing to do with the count: given repository IDS rather than\n * descriptors, `getMyIssuesForReposResult` skips its per-repository fan-out entirely (that branch is guarded on\n * `!isRepoIdsInput`) and calls the SDK's `getIssuesForRepos`, which for GitLab is the multi-project aggregate that\n * merges in the SDK and refuses `priority`/`dueDate` however few scopes it was given. Treating that as merged is\n * what makes this facade refuse it up front, with the message that names the reason, instead of letting the SDK\n * reject a read the capability table had just promised.\n */\nfunction mergesProviderQueries(pagingMode: PagingMode | undefined, repos: ProviderReposInput | undefined): boolean {\n\tconst scopes = repos ?? [];\n\tswitch (pagingMode) {\n\t\tcase PagingMode.Repo:\n\t\t\t// `isRepoIdsInput` is the same predicate `ProvidersApi` selects the SDK aggregate with, so this cannot\n\t\t\t// disagree with the branch it is predicting.\n\t\t\treturn isRepoIdsInput(scopes) ? scopes.length > 0 : scopes.length > 1;\n\t\tcase PagingMode.Project:\n\t\t\t// Only the descriptor form carries a project; the id form is refused by the Azure read before it runs.\n\t\t\treturn new Set(scopes.map(r => (typeof r === 'object' ? r.project : undefined))).size > 1;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\n/**\n * What {@link listIssuesPage} accepts, shared by name with the two functions it dispatches to.\n *\n * Named rather than left inline: three functions now take this exact shape, and reaching it from the other two as\n * `Parameters<typeof listIssuesPage>[1]` would define the contract in terms of the function that consumes it —\n * inside out, and unreadable at the point where the fields are actually used.\n */\nexport interface ListIssuesPageOptions {\n\tproviderId: IntegrationIds;\n\trepos?: ProviderReposInput;\n\t/** Narrows the account-wide read to one org/account. Requires a host with a project layer (Azure). */\n\torg?: string;\n\t/** Narrows the account-wide read to one project. Requires a host with a project layer (Azure). */\n\tproject?: string;\n\t/**\n\t * Narrows to the requested relationship(s). On the account-wide path this replaces the provider's own\n\t * definition of \"my issues\" (GitHub/GHE: authored ∪ assigned ∪ mentioned; Azure: assigned ∪ authored;\n\t * GitLab: assigned-to-me), so `[Assignee]` yields `assignee:@me` everywhere it's expressible. A set the\n\t * provider can't express server-side is refused whole (warning + `fetchFailed`), never widened — check\n\t * getSupportedFilters first to avoid that path.\n\t */\n\tfilters?: IssueFilter[];\n\t/** Broadens the read to every assignee. Contradicts `filters`; passing both is refused. */\n\tincludeAllAssignees?: boolean;\n\t/**\n\t * How to order the page, as `field:direction`. Omitted orders most-recently-updated-first wherever the\n\t * provider can express that, which is this facade's default rather than the provider's own.\n\t *\n\t * Validated against `getSupportedFilters().issueSorts` on the repo-scoped path and `.issueSortsAccountWide`\n\t * on the account-wide one — two genuinely different vocabularies for GitLab, whose two reads are different\n\t * APIs. A key the provider can't express refuses the read (warning + `fetchFailed`) rather than serving a\n\t * differently-ordered list, for the same reason an inexpressible filter does: the reachable window is\n\t * bounded, so another order is another subset.\n\t *\n\t * On the repo-scoped path with SEVERAL scopes the page is a merge of one query per repository/project, so\n\t * only a key a normalized issue carries can be honored: `priority`, `dueDate` and `resolved` are refused\n\t * there even where the provider supports them on a single scope. Order is per page, and per scope across\n\t * pages — the merge orders what a page contains, not the sequence of pages.\n\t */\n\tsort?: IssueSorting;\n\tpage?: number;\n\tcursor?: string;\n\titemsPerPage?: number;\n\tforceSync?: boolean;\n\tconnectionId?: string;\n\t/**\n\t * Explicit self-managed host domain. Used only when the requested connection has no configured domain;\n\t * it must come from the trusted authentication configuration, not repository or remote data.\n\t */\n\tdomain?: string;\n}\n\n/**\n * What both halves of {@link listIssuesPage} need from its shared prologue.\n *\n * A bundle rather than six parameters because the two reads take exactly the same inputs and differ only in what\n * they do with them; threading them positionally would make the two call sites read as two different reads of two\n * different things, which is the opposite of what is true.\n *\n * Note `ctx` is absent: every use of it — resolving the integration, the domain, the forced refresh, the\n * connection warnings — is in the prologue, which is what makes this the right cut.\n */\ninterface IssueReadContext {\n\toptions: ListIssuesPageOptions;\n\tintegration: GitHostIntegration;\n\tdomain: string | undefined;\n\twarnOnMissingSession: boolean;\n\tpage: number;\n}\n\nexport async function listIssuesPage(\n\tctx: ProviderReadContext,\n\toptions: ListIssuesPageOptions,\n): Promise<ProviderPagedResult<IssueShape>> {\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, 'repository issue 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, 'repository issue 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\tconst warnOnMissingSession = warnOnMissingSessionForDomain(options.providerId, options.domain);\n\n\t// A git host whose issue tracker is deprecated (Bitbucket, superseded by dedicated issue integrations)\n\t// reports issues as explicitly unsupported rather than serving a partial/legacy source or a silent empty.\n\tif (!integration.supportsIssues) {\n\t\treturn refusedPage(page, [issuesUnsupportedWarning(options.providerId, domain, options.connectionId)], true);\n\t}\n\n\t// `repos` is what selects between the two: with none there is no scope to query per repository, so the\n\t// provider's own account-wide search answers instead. They are different provider queries with different\n\t// capability tables, cursors and completeness signals — see each function.\n\tconst context: IssueReadContext = {\n\t\toptions: options,\n\t\tintegration: integration,\n\t\tdomain: domain,\n\t\twarnOnMissingSession: warnOnMissingSession,\n\t\tpage: page,\n\t};\n\treturn (options.repos?.length ?? 0) === 0 ? readAccountWideIssuesPage(context) : readRepoScopedIssuesPage(context);\n}\n\n/**\n * The account-wide read: the provider's own definition of \"my issues\", behind a composite cursor that spans\n * several provider searches.\n *\n * Its own function rather than a branch of the paged read, because almost nothing is shared past the guards: it\n * validates a different filter table and a different sort table, it is cursor-only (a page number cannot address\n * a composite cursor), it accumulates per-scope failures across the pages it walks, and it always merges several\n * queries. The two used to share one 460-line function behind a mode flag, and the comments explaining how far\n * apart they are were the clue that they wanted separating.\n */\nasync function readAccountWideIssuesPage({\n\toptions,\n\tintegration,\n\tdomain,\n\twarnOnMissingSession,\n\tpage,\n}: IssueReadContext): Promise<ProviderPagedResult<IssueShape>> {\n\t// The account-wide read is cursor-only, so a refusal can't claim the requested position — it reports\n\t// page 1, per ProviderPageInfo.currentPage.\n\tconst refused = (warning: ProviderWarning) => refusedPage<IssueShape>(1, [warning], true);\n\n\t// Checked before the provider-specific guards below: a caller passing both has a contradictory request\n\t// whatever the provider, and saying so is more useful than reporting one half of it as unsupported.\n\t// `filters` narrows this read to a relationship (`[Assignee]` ⇒ just assigned-to-me); `includeAllAssignees`\n\t// broadens it to every assignee. Honoring either silently would answer a question the caller didn't ask.\n\tif (options.filters?.length && options.includeAllAssignees === true) {\n\t\treturn refused(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'`filters` and `includeAllAssignees` are contradictory for an account-wide issue read; pass only one.',\n\t\t\t),\n\t\t);\n\t}\n\n\t// GitHub expresses \"any assignee\" as `assignee:*`, which is meaningless without a scope: unscoped it\n\t// matches millions of issues across all of GitHub instead of the user's own world. ANY scope makes it\n\t// meaningful (one repository, several, or an org) — it is specifically THIS branch, the account-wide read,\n\t// that has none to offer. Scope the read to repositories (or use `searchIssuesPage`, whose criteria model\n\t// takes `any-assignee` alongside a `repos`/`org` scope) rather than reading unscoped.\n\tif (\n\t\toptions.includeAllAssignees === true &&\n\t\t(options.providerId === GitCloudHostIntegrationId.GitHub ||\n\t\t\toptions.providerId === GitSelfManagedHostIntegrationId.CloudGitHubEnterprise)\n\t) {\n\t\treturn refused(\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'`includeAllAssignees` is not supported for account-wide GitHub issue reads; scope the read to repositories instead.',\n\t\t\t),\n\t\t);\n\t}\n\n\t// Only a host with a project layer can narrow server-side. Reject the request rather than serving an\n\t// unscoped list as if it had been narrowed: the caller would otherwise have to filter client-side,\n\t// which desynchronizes the filtered `items` from this read's `hasMore`/`currentPage` and shows\n\t// \"no issues\" for a page that simply held none of the requested project's.\n\tif ((options.org != null || options.project != null) && !integration.supportsProjectDiscovery) {\n\t\treturn refused(\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`Project-scoped issue reads are not supported by '${options.providerId}'; scope the read to repositories instead.`,\n\t\t\t),\n\t\t);\n\t}\n\n\t// Narrowing the account-wide read is only honest when the provider can express it server-side: its\n\t// per-relationship queries produced the page and the cursor together, so dropping items afterward would\n\t// leave `items` describing a different result set than `hasMore`/`currentPage`.\n\tconst resolvedIssueFilters = resolveAccountWideIssueFilters(options.providerId, options.filters);\n\tif (resolvedIssueFilters.unsupported) {\n\t\treturn refused(\n\t\t\tunsupportedAccountWideIssueFiltersWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\toptions.filters!,\n\t\t\t),\n\t\t);\n\t}\n\n\t// Every provider's account-wide read is a UNION of several queries — GitHub's three `@me` searches,\n\t// GitLab's one REST call per relationship, Azure's (project x relationship) drains — so it always merges,\n\t// and `supportedAccountWideIssueSorts` already lists only keys a merge can honor. No separate\n\t// mergeability refusal is needed here, unlike on the repo-scoped path below.\n\tconst resolvedSort = resolveIssueSort(\n\t\tprovidersMetadata[options.providerId]?.supportedAccountWideIssueSorts,\n\t\toptions.sort,\n\t);\n\tif (resolvedSort.rejection != null) {\n\t\treturn refused(\n\t\t\tunsupportedIssueSortWarning(options.providerId, domain, options.connectionId, resolvedSort.rejection),\n\t\t);\n\t}\n\n\t// `merged: true` unconditionally: this read has no scope count for a caller to reduce. It is also why no\n\t// `unmergeable` check follows — `supportedAccountWideIssueSorts` declares only keys a merge can honor.\n\tconst accountWideOrdering = toIssueOrdering(resolvedSort.sort, true);\n\n\t// The repo-scoped core rejects empty repos (GitHub/Bitbucket/Azure); read the account-wide,\n\t// already-user-scoped core instead. GitHub exposes a composite cursor across its authored,\n\t// assigned, and mentioned searches. Walk it internally when the caller supplies only page N.\n\tconst readAccountWidePage = (cursor: string | undefined) =>\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\tintegration.searchMyIssuesWithTruncationResult(undefined, undefined, options.connectionId, {\n\t\t\t\t\tincludeAllAssignees: options.includeAllAssignees,\n\t\t\t\t\tfilters: resolvedIssueFilters.filters,\n\t\t\t\t\tcursor: cursor,\n\t\t\t\t\tpageSize: options.itemsPerPage,\n\t\t\t\t\torg: options.org,\n\t\t\t\t\tproject: options.project,\n\t\t\t\t\tsort: accountWideOrdering.sort,\n\t\t\t\t}),\n\t\t\t{ warnOnMissingSession: warnOnMissingSession },\n\t\t);\n\tconst first = await readAccountWidePage(options.cursor);\n\tconst warnings = first.warning != null ? [first.warning] : [];\n\t// Merged across the walked pages, so a per-scope failure reported on an earlier page isn't lost by\n\t// paging past it — the one thing this read accumulates that the filtered search doesn't.\n\tlet allMetadata = first.value?.metadata;\n\tconst drained = await drainFlatPagesToRequestedPage(first, {\n\t\trequestedPage: page,\n\t\tsuppliedCursor: options.cursor,\n\t\twarnings: warnings,\n\t\treadPage: readAccountWidePage,\n\t\tfold: p => {\n\t\t\tallMetadata = mergeCollectionMetadata(allMetadata, p.metadata);\n\t\t},\n\t});\n\tconst { value, currentPage, requestedPageMissing } = drained;\n\tconst currentTruncated = drained.truncated;\n\tconst pageFetchFailed = drained.fetchFailed;\n\n\t// GitHub, GitLab, and Azure implement an account-wide issue search; a provider that doesn't (Bitbucket\n\t// exposes no issues at all, and `supportsIssues` already short-circuits it above) returns `undefined`\n\t// with no error. Surface that as an explicit unsupported warning + fetchFailed rather than a silent\n\t// empty success — the caller must fall back (e.g. broadenIssues over repos).\n\tif (value == null && warnings.length === 0) {\n\t\treturn refused(\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`Account-wide issue search is not supported by '${options.providerId}'; scope the read to repositories instead.`,\n\t\t\t),\n\t\t);\n\t}\n\n\tconst items = requestedPageMissing ? [] : (value?.values ?? []);\n\t// Each query arrived ordered by the provider; their union is not, and the union is what this read publishes.\n\t// Idempotent for GitHub, whose aliased-search engine already ordered the same merge — one call here rather\n\t// than the same logic repeated in each of the three provider implementations.\n\tconst orderedItems = accountWideOrdering.order(items);\n\t// Fold in structured per-scope failures from the account-wide fan-out (e.g. Azure across projects):\n\t// scope-aware warnings + `fetchFailed` when a scope failed, without discarding the successful items.\n\tconst assessment = mergeAssessmentInto(warnings, options.providerId, domain, options.connectionId, allMetadata);\n\t// An account-wide search that couldn't confirm completeness (a provider cap with no cursor, or a\n\t// per-scope backstop/failure) is incomplete and can't be paged; report it as truncated (+ a\n\t// provider-neutral warning, unless a structured failure already explains it) rather than a complete\n\t// list. Don't hard-code GitHub's \"100 per category\" cap here — Azure reaches this via a per-project\n\t// backstop, and other providers may cap differently.\n\t// The account-wide read is cursor-only (its composite cursor spans several provider searches, so a\n\t// page number can't address it): `hasMore` without a real cursor is a dead end, so report it as\n\t// terminal-but-incomplete rather than inviting the caller to page forever.\n\tconst continuation = resolveContinuation(\n\t\t{\n\t\t\thasMore: requestedPageMissing ? false : (value?.hasMore ?? false),\n\t\t\tcursor: requestedPageMissing ? undefined : usableCursor(value?.cursor),\n\t\t\ttruncated: currentTruncated,\n\t\t},\n\t\tundefined,\n\t);\n\tconst truncated = continuation.truncated || assessment.truncated;\n\tif (truncated && warnings.length === 0) {\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'Account-wide issue search',\n\t\t\t\t// `exhausted`: this composite read exposes no budget the caller can raise, so nothing it\n\t\t\t\t// could call would return the withheld items.\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\treturn {\n\t\titems: orderedItems,\n\t\twarnings: warnings,\n\t\tpage: {\n\t\t\t// Positional, per ProviderPageInfo.currentPage. `currentPage` already carries what the provider\n\t\t\t// reported or what the internal drain counted; a requested page past the terminal cursor is\n\t\t\t// reported as that empty page N. The account-wide read is cursor-only, so a `page` the caller\n\t\t\t// didn't pair with a cursor is never echoed.\n\t\t\tcurrentPage: requestedPageMissing\n\t\t\t\t? page\n\t\t\t\t: resolveCurrentPage({\n\t\t\t\t\t\tproviderPage: currentPage,\n\t\t\t\t\t\trequestedPage: page,\n\t\t\t\t\t\tsuppliedCursor: options.cursor,\n\t\t\t\t\t\tpageAdvanceable: false,\n\t\t\t\t\t}),\n\t\t\titemsPerPage: orderedItems.length,\n\t\t\ttruncated: truncated || undefined,\n\t\t},\n\t\thasMore: continuation.hasMore,\n\t\tcursor: continuation.cursor,\n\t\tfetchFailed: assessment.fetchFailed || pageFetchFailed || undefined,\n\t};\n}\n\n/**\n * The repository-scoped read: one provider query per repository or project, paged by the provider's own\n * continuation and drained internally when the caller supplied only a page number.\n *\n * See {@link readAccountWideIssuesPage} for why the two are separate functions.\n */\nasync function readRepoScopedIssuesPage({\n\toptions,\n\tintegration,\n\tdomain,\n\twarnOnMissingSession,\n\tpage,\n}: IssueReadContext): Promise<ProviderPagedResult<IssueShape>> {\n\tconst metadata = providersMetadata[options.providerId];\n\n\t// How many separate provider queries this page is assembled from, which decides whether the requested order can\n\t// be honored at all. `PagingMode.Repos` (GitHub) sends ONE search however many repositories are named, so its\n\t// page arrives ordered by the provider; `Repo` (GitLab) and `Project` (Azure) issue one query per scope and are\n\t// concatenated here, so a page spanning several of them is only as orderable as a normalized issue is.\n\tconst resolvedRepoScopedSort = resolveIssueSort(metadata?.supportedIssueSorts, options.sort);\n\tif (resolvedRepoScopedSort.rejection != null) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[\n\t\t\t\tunsupportedIssueSortWarning(\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\tresolvedRepoScopedSort.rejection,\n\t\t\t\t),\n\t\t\t],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\tconst issuesPagingMode = metadata?.issuesPagingMode;\n\tconst ordering = toIssueOrdering(\n\t\tresolvedRepoScopedSort.sort,\n\t\tmergesProviderQueries(issuesPagingMode, options.repos),\n\t);\n\t// Refused rather than served as concatenated per-scope runs, which would look ordered without being so — and\n\t// only where it actually merges, since the same key against the same provider is perfectly answerable for a\n\t// single repository or project.\n\tif (ordering.unmergeable != null) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[\n\t\t\t\tunmergeableIssueSortWarning(\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\tordering.unmergeable,\n\t\t\t\t\tissuesPagingMode === PagingMode.Project ? 'projects' : 'repositories',\n\t\t\t\t),\n\t\t\t],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\tconst cursor = options.cursor ?? pageToCursor(page);\n\tconst { value, warning } = await runCaptured(\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\t() =>\n\t\t\t// The shapes seam returns normalized IssueShape, and is overridable by a provider whose only issue\n\t\t\t// client already yields shapes (serving this path without a raw ProviderIssue round-trip).\n\t\t\tintegration.getMyIssuesForReposAsShapesResult(\n\t\t\t\toptions.repos ?? [],\n\t\t\t\t// Forward `page`/`pageSize` alongside the cursor so PagingMode.Repo/Project hosts honor the\n\t\t\t\t// requested page and page size rather than ignoring a synthesized page-number cursor.\n\t\t\t\t{\n\t\t\t\t\tfilters: options.filters,\n\t\t\t\t\tincludeAllAssignees: options.includeAllAssignees,\n\t\t\t\t\tcursor: cursor,\n\t\t\t\t\t// The normalized `page`, so the number forwarded to the provider can't disagree with the\n\t\t\t\t\t// page-number cursor beside it — both are built from the same value. Only when the caller asked\n\t\t\t\t\t// for one: an omitted page is the provider's own first page.\n\t\t\t\t\tpage: options.page != null ? page : undefined,\n\t\t\t\t\tpageSize: options.itemsPerPage,\n\t\t\t\t\tsort: ordering.sort,\n\t\t\t\t},\n\t\t\t\toptions.connectionId,\n\t\t\t),\n\t\t{ warnOnMissingSession: warnOnMissingSession },\n\t);\n\n\tlet items = value?.values ?? [];\n\tconst warnings = warning != null ? [warning] : [];\n\tlet pageFetchFailed = warning != null && value == null;\n\tlet paged = toProviderPageInfo(options.itemsPerPage ?? items.length, value?.paging);\n\tlet allMetadata = value?.metadata;\n\n\t// Cursor-only repo-scoped hosts (e.g. GitHub) ignore a synthesized page-number cursor, so a page-only\n\t// request is advanced through the provider's own continuations (see drainToRequestedPage).\n\tif (\n\t\tissuesPagingMode === 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(\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\treadPage: (cursor: string) =>\n\t\t\t\t\trunCaptured(\n\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\tdomain,\n\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tintegration.getMyIssuesForReposAsShapesResult(\n\t\t\t\t\t\t\t\toptions.repos ?? [],\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tfilters: options.filters,\n\t\t\t\t\t\t\t\t\tincludeAllAssignees: options.includeAllAssignees,\n\t\t\t\t\t\t\t\t\tcursor: cursor,\n\t\t\t\t\t\t\t\t\tpageSize: options.itemsPerPage,\n\t\t\t\t\t\t\t\t\tsort: ordering.sort,\n\t\t\t\t\t\t\t\t},\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{ warnOnMissingSession: warnOnMissingSession },\n\t\t\t\t\t),\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\t// Ordered once the drain (above) has assembled whichever pages this position spans.\n\titems = ordering.order(items);\n\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 assessment = mergeAssessmentInto(warnings, options.providerId, domain, options.connectionId, allMetadata);\n\t// Never advertise `hasMore` without a continuation the caller can act on, and only synthesize a page\n\t// number for a host that reads it as one (see `isPageNumberAdvanceable`).\n\tconst issuesPageAdvanceable = isPageNumberAdvanceable(issuesPagingMode);\n\tconst continuation = resolveContinuation(paged, issuesPageAdvanceable ? paged.page.currentPage + 1 : undefined);\n\t// A provider read that couldn't confirm completeness (e.g. Bitbucket's single-page repo issue read\n\t// that dropped a repo) sets `paging.truncated`; surface it as a terminal `page.truncated` so a partial\n\t// page isn't published as complete. 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\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'Issue',\n\t\t\t\t// `exhausted`, never `page-budget`: a paged read has no budget the caller can raise, and\n\t\t\t\t// ordinary continuation is already expressed by `hasMore`/`cursor`.\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\treturn {\n\t\titems: items,\n\t\twarnings: warnings,\n\t\tpage: {\n\t\t\t...paged.page,\n\t\t\t// Positional, per ProviderPageInfo.currentPage: the drain (above) already advanced `paged.page` for\n\t\t\t// a cursor-only host, and a caller-threaded cursor advanced the provider without one.\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: issuesPageAdvanceable,\n\t\t\t}),\n\t\t\ttruncated: truncated || undefined,\n\t\t},\n\t\thasMore: continuation.hasMore,\n\t\tcursor: continuation.cursor,\n\t\tfetchFailed: assessment.fetchFailed || pageFetchFailed || undefined,\n\t};\n}\n"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { PullRequestShape } from "../../../git/models/pullRequest.js";
2
2
  import { IntegrationIds } from "../constants.js";
3
3
  import { ProviderResult } from "../results.js";
4
+ import { PullRequestEtagInclude } from "../models/integration.js";
4
5
  import { ProviderReadContext } from "./context.js";
5
6
  //#region ../plus/integrations/src/reads/pullRequestBatch.d.ts
6
7
  /**
@@ -13,10 +14,15 @@ import { ProviderReadContext } from "./context.js";
13
14
  * connected and on any failure, and caches through `IntegrationCacheProvider.getPullRequest`, whose key carries no
14
15
  * connection and whose by-id bucket never expires a miss.
15
16
  *
16
- * ONE integration call per invocation, whatever the target count: the manager makes a single
17
- * `getPullRequestsBatchResult` call and the integration fans its targets out (chunked for GitHub/GHE, one request
18
- * per target with bounded concurrency everywhere else) and settles each independently, so one bad target never
19
- * spends more than its own slot — see `GitHostIntegration.getPullRequestsBatchResult`.
17
+ * ONE integration call per invocation when no target carries an `etag`, whatever the target count: the manager makes
18
+ * a single `getPullRequestsBatchResult` call and the integration fans its targets out (chunked for GitHub/GHE, one
19
+ * request per target with bounded concurrency everywhere else) and settles each independently, so one bad target
20
+ * never spends more than its own slot — see `GitHostIntegration.getPullRequestsBatchResult`.
21
+ *
22
+ * With etags, up to THREE on a host with a cheap check (GitHub/GHE, GitLab, Azure DevOps and Bitbucket Cloud so far):
23
+ * the cheap check of the targets that carry one, a full read of the targets that don't, started alongside it, and a
24
+ * full read of the targets whose etag no longer matches — see `readEtaggedBatch`. A host without a cheap check still
25
+ * makes one call.
20
26
  */
21
27
  /** One pull request to resolve, identified by coordinate and echoed back under the caller's own `key`. */
22
28
  export interface PullRequestBatchTarget {
@@ -33,15 +39,40 @@ export interface PullRequestBatchTarget {
33
39
  number: number;
34
40
  /** Azure DevOps project. Required there, ignored elsewhere. */
35
41
  project?: string;
42
+ /**
43
+ * The {@link PullRequestBatchResult.etag} of the copy the caller holds. When the host can check it cheaply and
44
+ * it still matches, the pull request isn't read again and comes back `unchanged`.
45
+ */
46
+ etag?: string;
36
47
  }
37
- /** The answer for one {@link PullRequestBatchTarget}. */
48
+ /**
49
+ * The answer for one {@link PullRequestBatchTarget}, in one of four states:
50
+ * - `{ key, pullRequest, etag }` — read in full, whatever its state. Every fully read row carries an `etag`,
51
+ * whether or not the caller sent one, so a caller can seed its etags from the reads it already makes.
52
+ * - `{ key, unchanged: true, etag }` — the cheap check proved the caller's copy current, and nothing else was
53
+ * fetched. Only for a target that sent an `etag`, on a host with a cheap check.
54
+ * - `{ key }` — PROVEN ABSENT: it does not exist, or is not visible to this connection.
55
+ * - no row, with `fetchFailed` and a warning — the read could not check. Never treat that as absent.
56
+ */
38
57
  export interface PullRequestBatchResult {
39
58
  key: string;
40
59
  /**
41
60
  * The resolved pull request, whatever its state, or `undefined` when it PROVABLY does not exist (or is not
42
- * visible to this connection). A target whose read FAILED is not returned at all and sets `fetchFailed`.
61
+ * visible to this connection) — or when it is `unchanged`. A target whose read FAILED is not returned at
62
+ * all and sets `fetchFailed`.
43
63
  */
44
64
  pullRequest?: PullRequestShape;
65
+ /**
66
+ * Opaque: compare for equality only, never parse. Computed by core from the pull request's change state — its
67
+ * state, draft flag, update time and head commit (on Azure DevOps, also a revision of the fields it changes without
68
+ * an update time), plus each input the call's `etagIncludes` listed — and NOT the provider's HTTP ETag. Send it
69
+ * back as {@link PullRequestBatchTarget.etag}. An etag from another scheme or another `etagIncludes` set simply
70
+ * compares unequal and costs a full read, never a false `unchanged`. Reactions are never an input: a host can add
71
+ * one without moving the update time, so an `unchanged` copy's `thumbsUpCount` may be stale.
72
+ */
73
+ etag?: string;
74
+ /** The caller's copy is current (its `etag` matched); `pullRequest` is absent because nothing was read. */
75
+ unchanged?: true;
45
76
  }
46
77
  export declare function getPullRequestsBatch(ctx: ProviderReadContext, options: {
47
78
  providerId: IntegrationIds;
@@ -52,6 +83,13 @@ export declare function getPullRequestsBatch(ctx: ProviderReadContext, options:
52
83
  * it must come from the trusted authentication configuration, not repository or remote data.
53
84
  */
54
85
  domain?: string;
86
+ /**
87
+ * Widens every etag to the listed inputs, each of which a host changes without moving the pull request's
88
+ * update time, and each of which costs its own fields in the cheap check. `'checks'` is the check rollup.
89
+ * Order and repeats don't matter. An unknown value refuses the whole call. Changes only which etag is
90
+ * computed; the full read is the same. Calls that differ in this set never match each other's etags.
91
+ */
92
+ etagIncludes?: readonly PullRequestEtagInclude[];
55
93
  }): Promise<ProviderResult<PullRequestBatchResult>>;
56
94
  //#endregion
57
95
  //# sourceMappingURL=pullRequestBatch.d.ts.map
@@ -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":"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"}
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":"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"}
@@ -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,