@gitkraken/core-gitlens 0.24.0 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -1
- package/README.md +23 -15
- package/dist/git/errors.d.ts +6 -5
- package/dist/git/errors.js +7 -6
- package/dist/git/errors.js.map +1 -1
- package/dist/git/features.d.ts +1 -1
- package/dist/git/features.js +1 -1
- package/dist/git/features.js.map +1 -1
- package/dist/git/models/remoteProvider.d.ts +4 -1
- package/dist/git/models/remoteProvider.js.map +1 -1
- package/dist/git/providers/provider.d.ts +4 -2
- package/dist/git/utils/branch.utils.d.ts +4 -1
- package/dist/git/utils/branch.utils.js +5 -2
- package/dist/git/utils/branch.utils.js.map +1 -1
- package/dist/git/utils/mergeConflicts.utils.js +2 -2
- package/dist/git/utils/mergeConflicts.utils.js.map +1 -1
- package/dist/git-cli/cliGitProvider.d.ts +14 -3
- package/dist/git-cli/cliGitProvider.js +71 -34
- package/dist/git-cli/cliGitProvider.js.map +1 -1
- package/dist/git-cli/exec/git.d.ts +7 -1
- package/dist/git-cli/exec/git.js +19 -4
- package/dist/git-cli/exec/git.js.map +1 -1
- package/dist/git-cli/providers/branchRemote.js +2 -5
- package/dist/git-cli/providers/branchRemote.js.map +1 -1
- package/dist/git-cli/providers/branches.js +4 -4
- package/dist/git-cli/providers/branches.js.map +1 -1
- package/dist/git-cli/providers/config.js +4 -1
- package/dist/git-cli/providers/config.js.map +1 -1
- package/dist/plus/ai/errors.d.ts +4 -1
- package/dist/plus/ai/errors.js +4 -0
- package/dist/plus/ai/errors.js.map +1 -1
- package/dist/plus/ai/providers/mistralProvider.js +1 -1
- package/dist/plus/ai/providers/mistralProvider.js.map +1 -1
- package/dist/plus/ai/providers/openAICompatibleProviderBase.js +1 -0
- package/dist/plus/ai/providers/openAICompatibleProviderBase.js.map +1 -1
- package/dist/plus/git-github/api/github.js +12 -13
- package/dist/plus/git-github/api/github.js.map +1 -1
- package/dist/plus/git-github/api/token.d.ts +2 -0
- package/dist/plus/integrations/authentication/cloudIntegrationService.d.ts +1 -1
- package/dist/plus/integrations/authentication/cloudIntegrationService.js +6 -1
- package/dist/plus/integrations/authentication/cloudIntegrationService.js.map +1 -1
- package/dist/plus/integrations/authentication/configuredIntegrationService.js +2 -2
- package/dist/plus/integrations/authentication/integrationAuthenticationProvider.js +18 -5
- package/dist/plus/integrations/authentication/integrationAuthenticationProvider.js.map +1 -1
- package/dist/plus/integrations/authentication/integrationAuthenticationService.d.ts +3 -1
- package/dist/plus/integrations/authentication/integrationAuthenticationService.js +4 -0
- package/dist/plus/integrations/authentication/integrationAuthenticationService.js.map +1 -1
- package/dist/plus/integrations/authentication/models.d.ts +2 -0
- package/dist/plus/integrations/authentication/models.js +2 -1
- package/dist/plus/integrations/authentication/models.js.map +1 -1
- package/dist/plus/integrations/authentication/observedAuthenticationProvider.js +1 -1
- package/dist/plus/integrations/authentication/rejectedTokenTracker.d.ts +5 -4
- package/dist/plus/integrations/authentication/rejectedTokenTracker.js +5 -4
- package/dist/plus/integrations/authentication/rejectedTokenTracker.js.map +1 -1
- package/dist/plus/integrations/index.d.ts +2 -2
- package/dist/plus/integrations/index.js +1 -1
- package/dist/plus/integrations/index.js.map +1 -1
- package/dist/plus/integrations/integrationService.d.ts +6 -0
- package/dist/plus/integrations/integrationService.js +3 -3
- package/dist/plus/integrations/integrationService.js.map +1 -1
- package/dist/plus/integrations/lite.js +1 -1
- package/dist/plus/integrations/manager.d.ts +51 -3
- package/dist/plus/integrations/models/gitHostIntegration.d.ts +14 -0
- package/dist/plus/integrations/models/gitHostIntegration.js +50 -41
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
- package/dist/plus/integrations/models/integration.d.ts +27 -5
- package/dist/plus/integrations/models/integration.js +57 -15
- package/dist/plus/integrations/models/integration.js.map +1 -1
- package/dist/plus/integrations/models/issueReads.d.ts +6 -0
- package/dist/plus/integrations/models/issuesIntegration.js +11 -11
- package/dist/plus/integrations/models/issuesIntegration.js.map +1 -1
- package/dist/plus/integrations/providers/azure/azure.js +1 -1
- package/dist/plus/integrations/providers/azureDevOps.js +1 -1
- package/dist/plus/integrations/providers/azureDevOpsServer.js +1 -1
- package/dist/plus/integrations/providers/bitbucket/bitbucket.js +1 -1
- package/dist/plus/integrations/providers/bitbucket/bitbucket.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket-server.js +1 -1
- package/dist/plus/integrations/providers/bitbucket.js +1 -1
- package/dist/plus/integrations/providers/github.js +1 -1
- package/dist/plus/integrations/providers/gitlab/gitlab.js +2 -2
- package/dist/plus/integrations/providers/gitlab/gitlab.js.map +1 -1
- package/dist/plus/integrations/providers/gitlab.js +1 -1
- package/dist/plus/integrations/providers/jira-server.js +2 -2
- package/dist/plus/integrations/providers/jira.js +1 -1
- package/dist/plus/integrations/providers/linear.js +2 -2
- package/dist/plus/integrations/providers/linear.js.map +1 -1
- package/dist/plus/integrations/providers/models.js +2 -1
- package/dist/plus/integrations/providers/models.js.map +1 -1
- package/dist/plus/integrations/providers/providerErrors.js +9 -0
- package/dist/plus/integrations/providers/providerErrors.js.map +1 -1
- package/dist/plus/integrations/providers/providersApi.js +1 -1
- package/dist/plus/integrations/providers/trello.js +1 -1
- package/dist/plus/integrations/providers/utils.js +1 -1
- package/dist/plus/integrations/reads/counts.js +1 -1
- package/dist/plus/integrations/reads/cursors.js +1 -1
- package/dist/plus/integrations/reads/drains.d.ts +5 -4
- package/dist/plus/integrations/reads/drains.js +28 -8
- package/dist/plus/integrations/reads/drains.js.map +1 -1
- package/dist/plus/integrations/reads/hierarchy.js +1 -1
- package/dist/plus/integrations/reads/issueBatch.d.ts +1 -1
- package/dist/plus/integrations/reads/issueBatch.js.map +1 -1
- package/dist/plus/integrations/reads/issueTracker.js +1 -1
- package/dist/plus/integrations/reads/issues.d.ts +2 -0
- package/dist/plus/integrations/reads/issues.js +5 -3
- package/dist/plus/integrations/reads/issues.js.map +1 -1
- package/dist/plus/integrations/reads/paging.d.ts +5 -6
- package/dist/plus/integrations/reads/paging.js +5 -6
- package/dist/plus/integrations/reads/paging.js.map +1 -1
- package/dist/plus/integrations/reads/pullRequests.d.ts +4 -0
- package/dist/plus/integrations/reads/pullRequests.js +4 -2
- package/dist/plus/integrations/reads/pullRequests.js.map +1 -1
- package/dist/plus/integrations/reads/resolveRepository.js +1 -1
- package/dist/plus/integrations/reads/sweeps.js +42 -10
- package/dist/plus/integrations/reads/sweeps.js.map +1 -1
- package/dist/plus/integrations/results.d.ts +23 -3
- package/dist/plus/integrations/results.js.map +1 -1
- package/dist/plus/notifications/api.d.ts +20 -0
- package/dist/plus/notifications/api.js +89 -0
- package/dist/plus/notifications/api.js.map +1 -0
- package/dist/plus/notifications/context.d.ts +28 -0
- package/dist/plus/notifications/context.js +0 -0
- package/dist/plus/notifications/errors.d.ts +13 -0
- package/dist/plus/notifications/errors.js +22 -0
- package/dist/plus/notifications/errors.js.map +1 -0
- package/dist/plus/notifications/models.d.ts +94 -0
- package/dist/plus/notifications/models.js +0 -0
- package/dist/plus/notifications/notificationsService.d.ts +115 -0
- package/dist/plus/notifications/notificationsService.js +381 -0
- package/dist/plus/notifications/notificationsService.js.map +1 -0
- package/dist/plus/notifications/parse.d.ts +12 -0
- package/dist/plus/notifications/parse.js +152 -0
- package/dist/plus/notifications/parse.js.map +1 -0
- package/dist/plus/notifications/presentation.d.ts +28 -0
- package/dist/plus/notifications/presentation.js +55 -0
- package/dist/plus/notifications/presentation.js.map +1 -0
- package/dist/utils/env/node/exec.d.ts +10 -1
- package/dist/utils/env/node/exec.js +39 -2
- package/dist/utils/env/node/exec.js.map +1 -1
- package/docs/integrations.md +21 -2
- package/docs/notifications.md +239 -0
- package/package.json +21 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pullRequests.js","names":[],"sources":["../../../../../plus/integrations/src/reads/pullRequests.ts"],"sourcesContent":["import type { PullRequest, PullRequestShape, PullRequestStateFilter } from '@gitlens/git/models/pullRequest.js';\nimport { mergeAssessmentInto } from '../collectionMetadata.js';\nimport type { IntegrationIds } from '../constants.js';\nimport type { ProviderPullRequest, ProviderReposInput, PullRequestFilter } from '../providers/models.js';\nimport { PagingMode, providersMetadata, toPullRequestRow } from '../providers/models.js';\nimport type { ProviderPagedResult } from '../results.js';\nimport { reconcileOmissionsWithFailure } from '../results.js';\nimport {\n\tisGitHostIntegration,\n\tisIssuesHostIntegrationId,\n\twarnOnMissingSessionForDomain,\n} from '../utils/integration.utils.js';\nimport type { ProviderReadContext } from './context.js';\nimport { getPullRequestViewers, runCaptured } from './drains.js';\nimport { resolveAccountWidePullRequestFilters, resolvePullRequestFilters } from './filters.js';\nimport {\n\tdrainToRequestedPage,\n\tisPageNumberAdvanceable,\n\tpageToCursor,\n\trefusedPage,\n\tresolveContinuation,\n\tresolveCurrentPage,\n\ttoProviderPageInfo,\n} from './paging.js';\nimport {\n\tgitHostOnlySurfaceWarning,\n\ttruncationWarning,\n\tunsupportedAccountWidePullRequestFiltersWarning,\n\tunsupportedFiltersWarning,\n} from './warnings.js';\n\nexport async function listPullRequestsPage(\n\tctx: ProviderReadContext,\n\toptions: {\n\t\tproviderId: IntegrationIds;\n\t\trepos?: ProviderReposInput;\n\t\tstates?: PullRequestStateFilter[];\n\t\t/**\n\t\t * PR relationship filters (e.g. `[Author, Assignee, ReviewRequested]`). On repo-scoped reads members\n\t\t * combine as provider query constraints (normally an intersection); on account-wide reads they form an\n\t\t * exact OR union. The whole set is validated against the selected path's capability.\n\t\t */\n\t\tfilters?: PullRequestFilter[];\n\t\t/**\n\t\t * Account-wide only: include review-requested PRs when the provider's native \"my PRs\" query omits them.\n\t\t * This extends the provider-native result; it is not a narrowing filter.\n\t\t */\n\t\tincludeReviewRequested?: boolean;\n\t\tpage?: number;\n\t\tcursor?: string;\n\t\t/**\n\t\t * Requests the lightweight row shape (see {@link IntegrationManager.listPullRequestsPage}). Opt-in\n\t\t * only, and only on the repo-scoped branch: the account-wide branch has always read the lightweight\n\t\t * shape and keeps doing so, so an explicit `false` does not widen it back to the full projection.\n\t\t */\n\t\tsummary?: boolean;\n\t\titemsPerPage?: number;\n\t\tforceSync?: boolean;\n\t\tconnectionId?: string;\n\t\t/**\n\t\t * Explicit self-managed host domain when no configured connection supplies one. The value must come\n\t\t * from the trusted authentication configuration, not repository or remote data.\n\t\t */\n\t\tdomain?: string;\n\t},\n): Promise<ProviderPagedResult<PullRequestShape>> {\n\t// Truncated as well as floored, like every other paged read here: `page` is reported back as\n\t// `page.currentPage` and compared against the position a drain reached, and neither can be fractional.\n\tconst page = Math.max(1, Math.trunc(options.page ?? 1));\n\tif (isIssuesHostIntegrationId(options.providerId)) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[gitHostOnlySurfaceWarning(options.providerId, undefined, options.connectionId, 'pull request reads')],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(options.providerId, options.connectionId, options.domain);\n\tif (integration == null) {\n\t\t// A supplied connection or domain that no longer resolves is a broken target, not an empty account —\n\t\t// surface a no-connection warning + fetchFailed rather than a silent empty page.\n\t\tconst early = ctx.earlyReturnConnectionWarnings(options.providerId, options.connectionId, options.domain);\n\t\treturn refusedPage(page, early.warnings, early.fetchFailed);\n\t}\n\tif (!isGitHostIntegration(integration)) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[gitHostOnlySurfaceWarning(options.providerId, undefined, options.connectionId, 'pull request reads')],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\tawait ctx.forceRefreshIfRequested(integration, options.forceSync, options.connectionId);\n\n\tconst domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);\n\t// With no repos this is an account-wide \"my PRs\" read; the repo-scoped core rejects an empty `repos`\n\t// input, so route to the account-wide, inherently user-scoped core instead (see drainPullRequests).\n\t// That provider-defined path takes no `pageSize`. A page-only request is handled by walking any opaque\n\t// continuations it supplies below. Do NOT synthesize a page-number cursor for it: cursor-based queries\n\t// (e.g. GitHub `involves:`) ignore a page number and would return their first page.\n\tconst accountWide = (options.repos?.length ?? 0) === 0;\n\tconst cursor = accountWide ? options.cursor : (options.cursor ?? pageToCursor(page));\n\n\tconst resolvedFilters = accountWide\n\t\t? resolveAccountWidePullRequestFilters(options.providerId, options.filters)\n\t\t: resolvePullRequestFilters(options.providerId, options.filters);\n\tif (resolvedFilters.unsupported) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[\n\t\t\t\taccountWide\n\t\t\t\t\t? unsupportedAccountWidePullRequestFiltersWarning(\n\t\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\t\tdomain,\n\t\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t\toptions.filters ?? [],\n\t\t\t\t\t\t)\n\t\t\t\t\t: unsupportedFiltersWarning(options.providerId, domain, options.connectionId),\n\t\t\t],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\t// Everything but the cursor, shared by the initial read and the drain's re-reads below so the two cannot\n\t// drift. `summary` is the reason this matters beyond tidiness: a re-read that dropped it would both\n\t// revert to the full payload — on the very path that issues the most requests — and return rows of a\n\t// different shape than the first page, since a summary row reports `headCommit`/`commitCount` as absent.\n\t// `summary` is pinned true here rather than forwarded: this branch has never had a full projection to\n\t// fall back to (see the option's doc), so honoring an explicit `false` would promise a widening the read\n\t// cannot deliver.\n\tconst accountWideInput = {\n\t\tstate: options.states,\n\t\tincludeReviewRequested: options.includeReviewRequested ?? false,\n\t\tfilters: resolvedFilters.filters,\n\t\tsummary: true,\n\t};\n\t// `filters` scopes the read to the current user (the core resolves the account for these), so it returns\n\t// the user's PRs. `pageSize` rides along so PagingMode.Repo hosts (GitLab, Bitbucket, Azure), whose\n\t// per-repo cursor path ignores a synthesized page-number cursor, honor the requested size.\n\tconst scopedInput = {\n\t\tstate: options.states,\n\t\tfilters: resolvedFilters.filters,\n\t\tpageSize: options.itemsPerPage,\n\t\tsummary: options.summary,\n\t};\n\tconst readPage = (pageCursor: string | undefined, requestedPage?: number) =>\n\t\trunCaptured(\n\t\t\toptions.providerId,\n\t\t\tdomain,\n\t\t\toptions.connectionId,\n\t\t\t() =>\n\t\t\t\taccountWide\n\t\t\t\t\t? integration.getMyPullRequestsForUserResult(\n\t\t\t\t\t\t\t{ ...accountWideInput, cursor: pageCursor },\n\t\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t)\n\t\t\t\t\t: integration.getMyPullRequestsForReposResult(\n\t\t\t\t\t\t\toptions.repos ?? [],\n\t\t\t\t\t\t\t{ ...scopedInput, cursor: pageCursor, page: requestedPage },\n\t\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t),\n\t\t\t{\n\t\t\t\twarnOnMissingSession: warnOnMissingSessionForDomain(options.providerId, options.domain),\n\t\t\t},\n\t\t);\n\tconst { value, warning } = await readPage(cursor, options.page != null ? page : undefined);\n\n\tlet items = value?.values ?? [];\n\t// Cursor-only account-wide reads start at page 1; a page-only request is advanced through opaque\n\t// continuations below. Repo-scoped reads report the requested page unless the provider reports its own.\n\tlet paged = toProviderPageInfo(items.length, value?.paging);\n\tlet allMetadata = value?.metadata;\n\t// Convert the SDK collection metadata into scope-aware warnings + failure/truncation flags, appending\n\t// them to any captured thrown-error warning without discarding the partial result's items.\n\tconst warnings = warning != null ? [warning] : [];\n\tlet pageFetchFailed = warning != null && value == null;\n\tlet drainedGap = false;\n\n\t// Cursor-only reads ignore a synthesized page-number cursor, so a page-only request is advanced through\n\t// the provider's own continuations (see drainToRequestedPage).\n\tif (\n\t\t(accountWide || providersMetadata[options.providerId]?.pullRequestsPagingMode === PagingMode.Repos) &&\n\t\toptions.page != null &&\n\t\tpage > 1 &&\n\t\toptions.cursor == null &&\n\t\tpaged.page.currentPage === 1\n\t) {\n\t\tconst drained = await drainToRequestedPage<ProviderPullRequest | PullRequest>(\n\t\t\t{ items: items, paged: paged, metadata: allMetadata, fetchFailed: pageFetchFailed },\n\t\t\t{\n\t\t\t\trequestedPage: page,\n\t\t\t\titemsPerPage: options.itemsPerPage,\n\t\t\t\twarnings: warnings,\n\t\t\t\t// A drain advances through provider continuations, so only the cursor positions these reads.\n\t\t\t\treadPage: (cursor: string) => readPage(cursor),\n\t\t\t},\n\t\t);\n\t\titems = drained.items;\n\t\tpaged = drained.paged;\n\t\tallMetadata = drained.metadata;\n\t\tpageFetchFailed = drained.fetchFailed;\n\t\tdrainedGap = drained.unexpectedGap === true;\n\t}\n\n\tconst assessment = mergeAssessmentInto(warnings, options.providerId, domain, options.connectionId, allMetadata);\n\t// Never advertise `hasMore` without a continuation the caller can act on. The account-wide read has no\n\t// page to synthesize, and neither does a host that doesn't honor a page number — see\n\t// {@link IntegrationService.isPageNumberAdvanceable} for which do and why absence of a declared mode\n\t// does NOT count as one.\n\tconst pageAdvanceable =\n\t\t!accountWide && isPageNumberAdvanceable(providersMetadata[options.providerId]?.pullRequestsPagingMode);\n\tconst continuation = resolveContinuation(paged, pageAdvanceable ? paged.page.currentPage + 1 : undefined);\n\t// A single-page provider read that couldn't confirm completeness sets `paging.truncated`; surface it\n\t// as a terminal `page.truncated` (not `hasMore`, which has no cursor to advance) so the caller knows\n\t// the page may be incomplete. Metadata incompleteness is an independent source of the same signal.\n\tconst truncated = continuation.truncated || assessment.truncated;\n\tif (truncated && warnings.length === 0) {\n\t\t// `exhausted`, never `page-budget`: a paged read has no budget the caller can raise. Ordinary\n\t\t// continuation is already expressed by `hasMore`/`cursor`, so reaching here means this page itself\n\t\t// couldn't be completed — raising anything would return the same page.\n\t\twarnings.push(\n\t\t\ttruncationWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'Pull request',\n\t\t\t\tassessment.fetchFailed || pageFetchFailed ? 'interrupted' : 'exhausted',\n\t\t\t),\n\t\t);\n\t}\n\t// A metadata omission from an earlier page asserts the read succeeded; a later page may since have failed.\n\treconcileOmissionsWithFailure(warnings, assessment.fetchFailed || pageFetchFailed);\n\tconst viewers = await getPullRequestViewers(integration, options.connectionId, items);\n\tconst projection = accountWide ? 'account-summary' : options.summary ? 'repos-summary' : 'repos';\n\treturn {\n\t\t// Normalize the raw rows to the GitLens-owned shape at the surface boundary.\n\t\titems: items.map((pr, i) =>\n\t\t\ttoPullRequestRow(pr, integration, { currentAccount: viewers[i], projection: projection }),\n\t\t),\n\t\twarnings: warnings,\n\t\t// The account-wide read can't take a page size, so don't echo the requested `itemsPerPage` as if it\n\t\t// had been applied — report what came back.\n\t\tpage: {\n\t\t\t...paged.page,\n\t\t\t// Positional, per ProviderPageInfo.currentPage: the drain (above) already advanced `paged.page`,\n\t\t\t// and a caller-threaded cursor advanced the provider, so neither leaves a cursor-only read stuck\n\t\t\t// reporting page 1.\n\t\t\tcurrentPage: resolveCurrentPage({\n\t\t\t\tproviderPage: paged.page.currentPage,\n\t\t\t\trequestedPage: page,\n\t\t\t\tsuppliedCursor: options.cursor,\n\t\t\t\tpageAdvanceable: pageAdvanceable,\n\t\t\t}),\n\t\t\titemsPerPage: accountWide ? items.length : paged.page.itemsPerPage,\n\t\t\ttruncated: truncated || undefined,\n\t\t},\n\t\thasMore: continuation.hasMore,\n\t\tcursor: continuation.cursor,\n\t\t// A metadata failure means items are incomplete even when the read didn't throw; a thrown error with\n\t\t// no recovered value is the pre-existing failure case.\n\t\tfetchFailed: assessment.fetchFailed || pageFetchFailed || undefined,\n\t\trequestFailed: assessment.requestFailed || pageFetchFailed,\n\t\tunexpectedGap: drainedGap || (paged.hasMore === true && paged.cursor == null && !pageAdvanceable),\n\t};\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"pullRequests.js","names":[],"sources":["../../../../../plus/integrations/src/reads/pullRequests.ts"],"sourcesContent":["import type { PullRequest, PullRequestShape, PullRequestStateFilter } from '@gitlens/git/models/pullRequest.js';\nimport { mergeAssessmentInto } from '../collectionMetadata.js';\nimport type { IntegrationIds } from '../constants.js';\nimport type { ProviderPullRequest, ProviderReposInput, PullRequestFilter } from '../providers/models.js';\nimport { PagingMode, providersMetadata, toPullRequestRow } from '../providers/models.js';\nimport type { ProviderPagedResult } from '../results.js';\nimport { reconcileOmissionsWithFailure } from '../results.js';\nimport {\n\tisGitHostIntegration,\n\tisIssuesHostIntegrationId,\n\twarnOnMissingSessionForDomain,\n} from '../utils/integration.utils.js';\nimport type { ProviderReadContext } from './context.js';\nimport { getPullRequestViewers, runCaptured } from './drains.js';\nimport { resolveAccountWidePullRequestFilters, resolvePullRequestFilters } from './filters.js';\nimport {\n\tdrainToRequestedPage,\n\tisPageNumberAdvanceable,\n\tpageToCursor,\n\trefusedPage,\n\tresolveContinuation,\n\tresolveCurrentPage,\n\ttoProviderPageInfo,\n} from './paging.js';\nimport {\n\tgitHostOnlySurfaceWarning,\n\ttruncationWarning,\n\tunsupportedAccountWidePullRequestFiltersWarning,\n\tunsupportedFiltersWarning,\n} from './warnings.js';\n\nexport async function listPullRequestsPage(\n\tctx: ProviderReadContext,\n\toptions: {\n\t\tproviderId: IntegrationIds;\n\t\trepos?: ProviderReposInput;\n\t\tstates?: PullRequestStateFilter[];\n\t\t/**\n\t\t * PR relationship filters (e.g. `[Author, Assignee, ReviewRequested]`). On repo-scoped reads members\n\t\t * combine as provider query constraints (normally an intersection); on account-wide reads they form an\n\t\t * exact OR union. The whole set is validated against the selected path's capability.\n\t\t */\n\t\tfilters?: PullRequestFilter[];\n\t\t/**\n\t\t * Account-wide only: include review-requested PRs when the provider's native \"my PRs\" query omits them.\n\t\t * This extends the provider-native result; it is not a narrowing filter.\n\t\t */\n\t\tincludeReviewRequested?: boolean;\n\t\tpage?: number;\n\t\tcursor?: string;\n\t\t/**\n\t\t * Requests the lightweight row shape (see {@link IntegrationManager.listPullRequestsPage}). Opt-in\n\t\t * only, and only on the repo-scoped branch: the account-wide branch has always read the lightweight\n\t\t * shape and keeps doing so, so an explicit `false` does not widen it back to the full projection.\n\t\t */\n\t\tsummary?: boolean;\n\t\t/** See {@link IntegrationManager.listPullRequestsPage}. */\n\t\tscopeConcurrency?: number;\n\t\t/** See {@link IntegrationManager.listPullRequestsPage}. */\n\t\tincludeRemoteInfo?: boolean;\n\t\titemsPerPage?: number;\n\t\tforceSync?: boolean;\n\t\tconnectionId?: string;\n\t\t/**\n\t\t * Explicit self-managed host domain when no configured connection supplies one. The value must come\n\t\t * from the trusted authentication configuration, not repository or remote data.\n\t\t */\n\t\tdomain?: string;\n\t},\n): Promise<ProviderPagedResult<PullRequestShape>> {\n\t// Truncated as well as floored, like every other paged read here: `page` is reported back as\n\t// `page.currentPage` and compared against the position a drain reached, and neither can be fractional.\n\tconst page = Math.max(1, Math.trunc(options.page ?? 1));\n\tif (isIssuesHostIntegrationId(options.providerId)) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[gitHostOnlySurfaceWarning(options.providerId, undefined, options.connectionId, 'pull request reads')],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(options.providerId, options.connectionId, options.domain);\n\tif (integration == null) {\n\t\t// A supplied connection or domain that no longer resolves is a broken target, not an empty account —\n\t\t// surface a no-connection warning + fetchFailed rather than a silent empty page.\n\t\tconst early = ctx.earlyReturnConnectionWarnings(options.providerId, options.connectionId, options.domain);\n\t\treturn refusedPage(page, early.warnings, early.fetchFailed);\n\t}\n\tif (!isGitHostIntegration(integration)) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[gitHostOnlySurfaceWarning(options.providerId, undefined, options.connectionId, 'pull request reads')],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\tawait ctx.forceRefreshIfRequested(integration, options.forceSync, options.connectionId);\n\n\tconst domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);\n\t// With no repos this is an account-wide \"my PRs\" read; the repo-scoped core rejects an empty `repos`\n\t// input, so route to the account-wide, inherently user-scoped core instead (see drainPullRequests).\n\t// That provider-defined path takes no `pageSize`. A page-only request is handled by walking any opaque\n\t// continuations it supplies below. Do NOT synthesize a page-number cursor for it: cursor-based queries\n\t// (e.g. GitHub `involves:`) ignore a page number and would return their first page.\n\tconst accountWide = (options.repos?.length ?? 0) === 0;\n\tconst cursor = accountWide ? options.cursor : (options.cursor ?? pageToCursor(page));\n\n\tconst resolvedFilters = accountWide\n\t\t? resolveAccountWidePullRequestFilters(options.providerId, options.filters)\n\t\t: resolvePullRequestFilters(options.providerId, options.filters);\n\tif (resolvedFilters.unsupported) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[\n\t\t\t\taccountWide\n\t\t\t\t\t? unsupportedAccountWidePullRequestFiltersWarning(\n\t\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\t\tdomain,\n\t\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t\toptions.filters ?? [],\n\t\t\t\t\t\t)\n\t\t\t\t\t: unsupportedFiltersWarning(options.providerId, domain, options.connectionId),\n\t\t\t],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\t// Everything but the cursor, shared by the initial read and the drain's re-reads below so the two cannot\n\t// drift. `summary` is the reason this matters beyond tidiness: a re-read that dropped it would both\n\t// revert to the full payload — on the very path that issues the most requests — and return rows of a\n\t// different shape than the first page, since a summary row reports `headCommit`/`commitCount` as absent.\n\t// `summary` is pinned true here rather than forwarded: this branch has never had a full projection to\n\t// fall back to (see the option's doc), so honoring an explicit `false` would promise a widening the read\n\t// cannot deliver.\n\tconst accountWideInput = {\n\t\tstate: options.states,\n\t\tincludeReviewRequested: options.includeReviewRequested ?? false,\n\t\tfilters: resolvedFilters.filters,\n\t\tsummary: true,\n\t};\n\t// `filters` scopes the read to the current user (the core resolves the account for these), so it returns\n\t// the user's PRs. `pageSize` rides along so PagingMode.Repo hosts (GitLab, Bitbucket, Bitbucket Data Center,\n\t// Azure), whose per-repo cursor path ignores a synthesized page-number cursor, honor the requested size.\n\tconst scopedInput = {\n\t\tstate: options.states,\n\t\tfilters: resolvedFilters.filters,\n\t\tpageSize: options.itemsPerPage,\n\t\tsummary: options.summary,\n\t\tscopeConcurrency: options.scopeConcurrency,\n\t\tincludeRemoteInfo: options.includeRemoteInfo,\n\t};\n\tconst readPage = (pageCursor: string | undefined, requestedPage?: number) =>\n\t\trunCaptured(\n\t\t\toptions.providerId,\n\t\t\tdomain,\n\t\t\toptions.connectionId,\n\t\t\t() =>\n\t\t\t\taccountWide\n\t\t\t\t\t? integration.getMyPullRequestsForUserResult(\n\t\t\t\t\t\t\t{ ...accountWideInput, cursor: pageCursor },\n\t\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t)\n\t\t\t\t\t: integration.getMyPullRequestsForReposResult(\n\t\t\t\t\t\t\toptions.repos ?? [],\n\t\t\t\t\t\t\t{ ...scopedInput, cursor: pageCursor, page: requestedPage },\n\t\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t),\n\t\t\t{\n\t\t\t\twarnOnMissingSession: warnOnMissingSessionForDomain(options.providerId, options.domain),\n\t\t\t},\n\t\t);\n\tconst { value, warning } = await readPage(cursor, options.page != null ? page : undefined);\n\n\tlet items = value?.values ?? [];\n\t// Cursor-only account-wide reads start at page 1; a page-only request is advanced through opaque\n\t// continuations below. Repo-scoped reads report the requested page unless the provider reports its own.\n\tlet paged = toProviderPageInfo(items.length, value?.paging);\n\tlet allMetadata = value?.metadata;\n\t// Convert the SDK collection metadata into scope-aware warnings + failure/truncation flags, appending\n\t// them to any captured thrown-error warning without discarding the partial result's items.\n\tconst warnings = warning != null ? [warning] : [];\n\tlet pageFetchFailed = warning != null && value == null;\n\tlet drainedGap = false;\n\n\t// Cursor-only reads ignore a synthesized page-number cursor, so a page-only request is advanced through\n\t// the provider's own continuations (see drainToRequestedPage).\n\tif (\n\t\t(accountWide || providersMetadata[options.providerId]?.pullRequestsPagingMode === PagingMode.Repos) &&\n\t\toptions.page != null &&\n\t\tpage > 1 &&\n\t\toptions.cursor == null &&\n\t\tpaged.page.currentPage === 1\n\t) {\n\t\tconst drained = await drainToRequestedPage<ProviderPullRequest | PullRequest>(\n\t\t\t{ items: items, paged: paged, metadata: allMetadata, fetchFailed: pageFetchFailed },\n\t\t\t{\n\t\t\t\trequestedPage: page,\n\t\t\t\titemsPerPage: options.itemsPerPage,\n\t\t\t\twarnings: warnings,\n\t\t\t\t// A drain advances through provider continuations, so only the cursor positions these reads.\n\t\t\t\treadPage: (cursor: string) => readPage(cursor),\n\t\t\t},\n\t\t);\n\t\titems = drained.items;\n\t\tpaged = drained.paged;\n\t\tallMetadata = drained.metadata;\n\t\tpageFetchFailed = drained.fetchFailed;\n\t\tdrainedGap = drained.unexpectedGap === true;\n\t}\n\n\tconst assessment = mergeAssessmentInto(warnings, options.providerId, domain, options.connectionId, allMetadata);\n\t// Never advertise `hasMore` without a continuation the caller can act on. The account-wide read has no\n\t// page to synthesize, and neither does a host that doesn't honor a page number — see\n\t// {@link IntegrationService.isPageNumberAdvanceable} for which do and why absence of a declared mode\n\t// does NOT count as one.\n\tconst pageAdvanceable =\n\t\t!accountWide && isPageNumberAdvanceable(providersMetadata[options.providerId]?.pullRequestsPagingMode);\n\tconst continuation = resolveContinuation(paged, pageAdvanceable ? paged.page.currentPage + 1 : undefined);\n\t// A single-page provider read that couldn't confirm completeness sets `paging.truncated`; surface it\n\t// as a terminal `page.truncated` (not `hasMore`, which has no cursor to advance) so the caller knows\n\t// the page may be incomplete. Metadata incompleteness is an independent source of the same signal.\n\tconst truncated = continuation.truncated || assessment.truncated;\n\tif (truncated && warnings.length === 0) {\n\t\t// `exhausted`, never `page-budget`: a paged read has no budget the caller can raise. Ordinary\n\t\t// continuation is already expressed by `hasMore`/`cursor`, so reaching here means this page itself\n\t\t// couldn't be completed — raising anything would return the same page.\n\t\twarnings.push(\n\t\t\ttruncationWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'Pull request',\n\t\t\t\tassessment.fetchFailed || pageFetchFailed ? 'interrupted' : 'exhausted',\n\t\t\t),\n\t\t);\n\t}\n\t// A metadata omission from an earlier page asserts the read succeeded; a later page may since have failed.\n\treconcileOmissionsWithFailure(warnings, assessment.fetchFailed || pageFetchFailed);\n\tconst viewers = await getPullRequestViewers(integration, options.connectionId, items);\n\tconst projection = accountWide ? 'account-summary' : options.summary ? 'repos-summary' : 'repos';\n\treturn {\n\t\t// Normalize the raw rows to the GitLens-owned shape at the surface boundary.\n\t\titems: items.map((pr, i) =>\n\t\t\ttoPullRequestRow(pr, integration, { currentAccount: viewers[i], projection: projection }),\n\t\t),\n\t\twarnings: warnings,\n\t\t// The account-wide read can't take a page size, so don't echo the requested `itemsPerPage` as if it\n\t\t// had been applied — report what came back.\n\t\tpage: {\n\t\t\t...paged.page,\n\t\t\t// Positional, per ProviderPageInfo.currentPage: the drain (above) already advanced `paged.page`,\n\t\t\t// and a caller-threaded cursor advanced the provider, so neither leaves a cursor-only read stuck\n\t\t\t// reporting page 1.\n\t\t\tcurrentPage: resolveCurrentPage({\n\t\t\t\tproviderPage: paged.page.currentPage,\n\t\t\t\trequestedPage: page,\n\t\t\t\tsuppliedCursor: options.cursor,\n\t\t\t\tpageAdvanceable: pageAdvanceable,\n\t\t\t}),\n\t\t\titemsPerPage: accountWide ? items.length : paged.page.itemsPerPage,\n\t\t\ttruncated: truncated || undefined,\n\t\t},\n\t\thasMore: continuation.hasMore,\n\t\tcursor: continuation.cursor,\n\t\t// A metadata failure means items are incomplete even when the read didn't throw; a thrown error with\n\t\t// no recovered value is the pre-existing failure case.\n\t\tfetchFailed: assessment.fetchFailed || pageFetchFailed || undefined,\n\t\trequestFailed: assessment.requestFailed || pageFetchFailed,\n\t\tunexpectedGap: drainedGap || (paged.hasMore === true && paged.cursor == null && !pageAdvanceable),\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -5,8 +5,8 @@ import "../constants.js";
|
|
|
5
5
|
import { toProviderWarning } from "../results.js";
|
|
6
6
|
import { mergeAssessmentInto } from "../collectionMetadata.js";
|
|
7
7
|
import { decodePathSegment, getRemoteHostMatcher, hostFromDomain, isWebRemoteScheme } from "../utils/domain.utils.js";
|
|
8
|
-
import { getIntegrationIdForRemote, getSelfManagedBaseUrl, isGitCloudHostIntegrationId, isGitSelfManagedHostIntegrationId, remoteProviderTypeForConfig, remoteProviderTypeForIntegration } from "../utils/integration.utils.js";
|
|
9
8
|
import { isAzureCloudDomain, isBitbucketCloudDomain, isGitHubDotCom, isGitLabDotCom } from "../providers/models.js";
|
|
9
|
+
import { getIntegrationIdForRemote, getSelfManagedBaseUrl, isGitCloudHostIntegrationId, isGitSelfManagedHostIntegrationId, remoteProviderTypeForConfig, remoteProviderTypeForIntegration } from "../utils/integration.utils.js";
|
|
10
10
|
import { isIssuesIntegration } from "../models/issuesIntegration.js";
|
|
11
11
|
import { noConnectionWarning } from "./warnings.js";
|
|
12
12
|
import { getAzureRepositoryIdentity } from "../providers/azure/azure.utils.js";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { mapBounded } from "../../../utils/promise.js";
|
|
2
2
|
import "../constants.js";
|
|
3
3
|
import { appendDedupedWarning } from "../results.js";
|
|
4
|
-
import { isGitHostIntegration, isIssuesHostIntegrationId } from "../utils/integration.utils.js";
|
|
5
4
|
import { toPullRequestRow } from "../providers/models.js";
|
|
5
|
+
import { isGitHostIntegration, isIssuesHostIntegrationId } from "../utils/integration.utils.js";
|
|
6
6
|
import { gitHostOnlySurfaceWarning, noConnectionWarning, unsupportedAccountWidePullRequestFiltersWarning, unsupportedFiltersWarning } from "./warnings.js";
|
|
7
7
|
import { drainPullRequests, getPullRequestViewers, resolvePullRequestSweepTargets } from "./drains.js";
|
|
8
8
|
import { resolveAccountWidePullRequestFilters, resolvePullRequestFilters } from "./filters.js";
|
|
@@ -17,7 +17,7 @@ import { resolveAccountWidePullRequestFilters, resolvePullRequestFilters } from
|
|
|
17
17
|
*/
|
|
18
18
|
async function sweepTarget(ctx, options, target, attributeUnavailableProviders) {
|
|
19
19
|
const { providerId: id, connectionId, domain: requestedDomain } = target;
|
|
20
|
-
const repos = options?.repos ?? [];
|
|
20
|
+
const repos = target.repos ?? options?.repos ?? [];
|
|
21
21
|
const maxPages = options?.maxPages ?? 100;
|
|
22
22
|
/** A target that never reached a drain: the provider itself failed, so its slice is empty and attributed. */
|
|
23
23
|
const rejectedTarget = (warnings) => ({
|
|
@@ -112,23 +112,53 @@ async function sweepPullRequests(ctx, options) {
|
|
|
112
112
|
const reportSettled = beginTarget?.(target);
|
|
113
113
|
const slice = await sweepTarget(ctx, options, target, attributeUnavailableProviders);
|
|
114
114
|
reportSettled?.(slice);
|
|
115
|
-
return
|
|
115
|
+
return {
|
|
116
|
+
target,
|
|
117
|
+
slice
|
|
118
|
+
};
|
|
116
119
|
});
|
|
117
120
|
const items = [];
|
|
118
121
|
const warnings = [];
|
|
119
|
-
const
|
|
120
|
-
const
|
|
122
|
+
const failedTargets = [];
|
|
123
|
+
const incompleteTargets = [];
|
|
124
|
+
const attributed = /* @__PURE__ */ new Map();
|
|
121
125
|
let fetchFailed = false;
|
|
122
126
|
let truncated = false;
|
|
123
|
-
for (const drain of results) {
|
|
127
|
+
for (const { target, slice: drain } of results) {
|
|
124
128
|
if (drain == null) continue;
|
|
125
129
|
items.push(...drain.items);
|
|
126
130
|
for (const w of drain.warnings) appendDedupedWarning(warnings, w);
|
|
127
131
|
if (drain.fetchFailed) fetchFailed = true;
|
|
128
|
-
|
|
129
|
-
|
|
132
|
+
let counts = attributed.get(drain.providerId);
|
|
133
|
+
if (counts == null) {
|
|
134
|
+
counts = {
|
|
135
|
+
total: 0,
|
|
136
|
+
failed: 0,
|
|
137
|
+
incomplete: 0
|
|
138
|
+
};
|
|
139
|
+
attributed.set(drain.providerId, counts);
|
|
140
|
+
}
|
|
141
|
+
counts.total++;
|
|
142
|
+
if (drain.failedProvider || drain.fetchFailed || drain.truncated) {
|
|
143
|
+
const ref = {
|
|
144
|
+
providerId: target.providerId,
|
|
145
|
+
connectionId: target.connectionId,
|
|
146
|
+
domain: ctx.resolveDomainForRead(target.providerId, target.connectionId, target.domain)
|
|
147
|
+
};
|
|
148
|
+
if (drain.failedProvider) {
|
|
149
|
+
counts.failed++;
|
|
150
|
+
failedTargets.push(ref);
|
|
151
|
+
} else {
|
|
152
|
+
counts.incomplete++;
|
|
153
|
+
incompleteTargets.push(ref);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
130
156
|
if (drain.truncated) truncated = true;
|
|
131
157
|
}
|
|
158
|
+
const failedProviderIds = [];
|
|
159
|
+
const incompleteProviderIds = [];
|
|
160
|
+
for (const [providerId, { total, failed, incomplete }] of attributed) if (failed === total) failedProviderIds.push(providerId);
|
|
161
|
+
else if (failed + incomplete !== 0) incompleteProviderIds.push(providerId);
|
|
132
162
|
return {
|
|
133
163
|
items,
|
|
134
164
|
warnings,
|
|
@@ -140,8 +170,10 @@ async function sweepPullRequests(ctx, options) {
|
|
|
140
170
|
},
|
|
141
171
|
hasMore: false,
|
|
142
172
|
fetchFailed: fetchFailed || void 0,
|
|
143
|
-
failedProviderIds
|
|
144
|
-
incompleteProviderIds
|
|
173
|
+
failedProviderIds,
|
|
174
|
+
incompleteProviderIds,
|
|
175
|
+
failedTargets,
|
|
176
|
+
incompleteTargets
|
|
145
177
|
};
|
|
146
178
|
}
|
|
147
179
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sweeps.js","names":[],"sources":["../../../../../plus/integrations/src/reads/sweeps.ts"],"sourcesContent":["import type { PullRequestShape } from '@gitlens/git/models/pullRequest.js';\nimport { mapBounded } from '@gitlens/utils/promise.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { providerFanOutConcurrency } from '../constants.js';\nimport type {\n\tClosedPullRequestSweepOptions,\n\tProviderSweepTarget,\n\tProviderSweepTargetEvent,\n\tPullRequestSweepOptions,\n} from '../manager.js';\nimport { toPullRequestRow } from '../providers/models.js';\nimport type { ProviderSweepResult, ProviderWarning } from '../results.js';\nimport { appendDedupedWarning } from '../results.js';\nimport { isGitHostIntegration, isIssuesHostIntegrationId } from '../utils/integration.utils.js';\nimport type { ProviderReadContext } from './context.js';\nimport { drainPullRequests, getPullRequestViewers, resolvePullRequestSweepTargets } from './drains.js';\nimport { resolveAccountWidePullRequestFilters, resolvePullRequestFilters } from './filters.js';\nimport {\n\tgitHostOnlySurfaceWarning,\n\tnoConnectionWarning,\n\tunsupportedAccountWidePullRequestFiltersWarning,\n\tunsupportedFiltersWarning,\n} from './warnings.js';\n\n/**\n * The all-pages pull request reads: one drain per provider target, aggregated into a single sweep result.\n *\n * A sweep exposes no continuation of its own — it drains everything it can reach — so its whole contract lives in\n * the completeness signals it returns: `page.allPages` asserts every page of every target was read,\n * `page.truncated` says pages remain unread, and `failedProviderIds`/`incompleteProviderIds` separate a provider\n * that produced nothing from one that produced a slice with a gap. That split is why the aggregation below can't\n * be collapsed into the generic fan-out merge the hierarchy reads use.\n */\n\n/** One provider target's drained slice, before the cross-provider aggregation. */\ninterface SweepSlice {\n\titems: PullRequestShape[];\n\twarnings: ProviderWarning[];\n\tfetchFailed: boolean;\n\ttruncated: boolean;\n\tproviderId: IntegrationIds;\n\tfailedProvider: boolean;\n}\n\n/**\n * Drain ONE sweep target. Extracted from the fan-out so the fan-out callback has a single exit: every\n * per-target observation (see `onTargetSettled`) is then reported in one place instead of at each of this\n * function's several early returns, where a missed branch would silently drop a provider's attribution.\n *\n * `undefined` means the target resolved to no reachable connection and is deliberately not attributed in the\n * aggregate result.\n */\nasync function sweepTarget(\n\tctx: ProviderReadContext,\n\toptions: PullRequestSweepOptions | undefined,\n\ttarget: ProviderSweepTarget,\n\tattributeUnavailableProviders: boolean,\n): Promise<SweepSlice | undefined> {\n\tconst { providerId: id, connectionId, domain: requestedDomain } = target;\n\tconst repos = options?.repos ?? [];\n\tconst maxPages = options?.maxPages ?? 100;\n\t/** A target that never reached a drain: the provider itself failed, so its slice is empty and attributed. */\n\tconst rejectedTarget = (warnings: ProviderWarning[]): SweepSlice => ({\n\t\titems: [],\n\t\twarnings: warnings,\n\t\tfetchFailed: true,\n\t\ttruncated: false,\n\t\tproviderId: id,\n\t\tfailedProvider: true,\n\t});\n\n\tif (isIssuesHostIntegrationId(id)) {\n\t\treturn rejectedTarget([gitHostOnlySurfaceWarning(id, requestedDomain, connectionId, 'pull request sweeps')]);\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(id, connectionId, requestedDomain);\n\tif (integration == null) {\n\t\t// A requested connection that can't be resolved is a broken connection — surface it as a\n\t\t// warning + fetchFailed rather than dropping the provider's slice silently.\n\t\tconst early = ctx.earlyReturnConnectionWarnings(id, connectionId, requestedDomain);\n\t\tif (early.warnings.length === 0 && !attributeUnavailableProviders) return undefined;\n\n\t\treturn rejectedTarget(\n\t\t\tearly.warnings.length !== 0 ? early.warnings : [noConnectionWarning(id, requestedDomain, connectionId)],\n\t\t);\n\t}\n\tif (!isGitHostIntegration(integration)) {\n\t\treturn rejectedTarget([gitHostOnlySurfaceWarning(id, requestedDomain, connectionId, 'pull request sweeps')]);\n\t}\n\n\tawait ctx.forceRefreshIfRequested(integration, options?.forceSync, connectionId);\n\n\tconst domain = ctx.domainForRead(integration, id, connectionId, requestedDomain);\n\tconst accountWide = repos.length === 0;\n\tconst requestedFilters = target.filters ?? options?.filters;\n\tconst resolved = accountWide\n\t\t? resolveAccountWidePullRequestFilters(id, requestedFilters)\n\t\t: resolvePullRequestFilters(id, requestedFilters);\n\tif (resolved.unsupported) {\n\t\treturn rejectedTarget([\n\t\t\taccountWide\n\t\t\t\t? unsupportedAccountWidePullRequestFiltersWarning(id, domain, connectionId, requestedFilters ?? [])\n\t\t\t\t: unsupportedFiltersWarning(id, domain, connectionId),\n\t\t]);\n\t}\n\n\tconst drain = await drainPullRequests(\n\t\tintegration,\n\t\tid,\n\t\tdomain,\n\t\trepos,\n\t\toptions?.states,\n\t\tresolved.filters,\n\t\toptions,\n\t\tconnectionId,\n\t\tmaxPages,\n\t\tattributeUnavailableProviders,\n\t);\n\tconst viewers = await getPullRequestViewers(integration, connectionId, drain.items);\n\t// Mirrors the projection `drainPullRequests` requests.\n\tconst projection = accountWide ? (options?.includeReviews ? 'account' : 'account-summary') : 'repos';\n\t// Normalize the raw rows to the GitLens-owned shape here, where the per-provider `integration` (the\n\t// mapper's provider reference) is in scope; the aggregation below only sees drains.\n\treturn {\n\t\t...drain,\n\t\titems: drain.items.map((pr, i) =>\n\t\t\ttoPullRequestRow(pr, integration, { currentAccount: viewers[i], projection: projection }),\n\t\t),\n\t\tproviderId: id,\n\t};\n}\n\n/**\n * How a target ended, as the consumer buckets it. `failedProvider` outranks `fetchFailed` because a target\n * whose provider failed produced no slice to be partial about, and `undefined` is the target that resolved to\n * no reachable connection at all.\n */\nfunction sliceOutcome(slice: SweepSlice | undefined): ProviderSweepTargetEvent['outcome'] {\n\tif (slice == null) return 'skipped';\n\tif (slice.failedProvider) return 'failed-provider';\n\tif (slice.fetchFailed) return 'fetch-failed';\n\treturn 'ok';\n}\n\n/**\n * Per-target reporting for a sweep's `onTargetSettled`, opened once per sweep: this stamps the fan-out start,\n * the returned function stamps one target's start, and the function IT returns is the only thing that can\n * report that target. The timestamps therefore never cross a boundary, so there is no pair of interchangeable\n * numbers for a call site to transpose, and the whole reporting concern is one value the fan-out either has or\n * does not. That is also what keeps the option free when it is omitted: with no observer there is nothing to\n * open, so no clock is read and no reporting object exists — do not \"simplify\" that into an unconditional\n * open, because a host whose perf gate is off (the default) is entitled to pay nothing for it.\n *\n * The try/catch is what makes the observer observation-only: called from the fan-out's success path, a throwing\n * callback would otherwise propagate out of the `mapBounded` task and reject the entire sweep — corrupting the\n * read, not just the metric. Swallowed silently; the consumer owns its own aggregation. The `void`-typed\n * callback also admits an `async` consumer, whose rejection is created only after `observe` returns — the\n * `Promise.resolve(...).catch` swallows that too, so it cannot escape as an unhandled rejection.\n *\n * The domain is resolved here rather than carried out of {@link sweepTarget}, so every target reports it by the\n * same rule no matter how far it got. `resolveDomainForRead` needs no integration instance, which is what makes\n * that possible: a target rejected by the first guard resolves the same host a fully drained one does.\n */\nfunction startSweepReporting(\n\tctx: ProviderReadContext,\n\tobserve: (event: ProviderSweepTargetEvent) => void | PromiseLike<unknown>,\n) {\n\tconst fanOutStartedAt = performance.now();\n\n\treturn function beginTarget(target: ProviderSweepTarget) {\n\t\tconst startedAt = performance.now();\n\n\t\treturn function reportSettled(slice: SweepSlice | undefined) {\n\t\t\ttry {\n\t\t\t\tvoid Promise.resolve(\n\t\t\t\t\tobserve({\n\t\t\t\t\t\tproviderId: target.providerId,\n\t\t\t\t\t\tdomain: ctx.resolveDomainForRead(target.providerId, target.connectionId, target.domain),\n\t\t\t\t\t\tconnectionId: target.connectionId,\n\t\t\t\t\t\tcount: slice?.items.length ?? 0,\n\t\t\t\t\t\tdurationMs: performance.now() - startedAt,\n\t\t\t\t\t\tqueueWaitMs: startedAt - fanOutStartedAt,\n\t\t\t\t\t\toutcome: sliceOutcome(slice),\n\t\t\t\t\t\ttruncated: slice?.truncated ?? false,\n\t\t\t\t\t}),\n\t\t\t\t).catch(() => {});\n\t\t\t} catch {}\n\t\t};\n\t};\n}\n\nexport async function sweepPullRequests(\n\tctx: ProviderReadContext,\n\toptions?: PullRequestSweepOptions,\n): Promise<ProviderSweepResult<PullRequestShape>> {\n\tconst { targets, attributeUnavailableProviders } = resolvePullRequestSweepTargets(options);\n\n\tconst observe = options?.onTargetSettled;\n\tconst beginTarget = observe != null ? startSweepReporting(ctx, observe) : undefined;\n\n\tconst results = await mapBounded(targets, providerFanOutConcurrency, async target => {\n\t\tconst reportSettled = beginTarget?.(target);\n\t\tconst slice = await sweepTarget(ctx, options, target, attributeUnavailableProviders);\n\t\treportSettled?.(slice);\n\t\treturn slice;\n\t});\n\n\tconst items: PullRequestShape[] = [];\n\tconst warnings: ProviderWarning[] = [];\n\tconst failedProviderIds = new Set<IntegrationIds>();\n\tconst incompleteProviderIds = new Set<IntegrationIds>();\n\tlet fetchFailed = false;\n\tlet truncated = false;\n\tfor (const drain of results) {\n\t\tif (drain == null) {\n\t\t\tcontinue;\n\t\t}\n\n\t\titems.push(...drain.items);\n\t\tfor (const w of drain.warnings) {\n\t\t\tappendDedupedWarning(warnings, w);\n\t\t}\n\t\tif (drain.fetchFailed) {\n\t\t\tfetchFailed = true;\n\t\t}\n\t\tif (drain.failedProvider) {\n\t\t\tfailedProviderIds.add(drain.providerId);\n\t\t} else if (drain.fetchFailed || drain.truncated) {\n\t\t\tincompleteProviderIds.add(drain.providerId);\n\t\t}\n\t\tif (drain.truncated) {\n\t\t\ttruncated = true;\n\t\t}\n\t}\n\n\treturn {\n\t\titems: items,\n\t\twarnings: warnings,\n\t\t// `allPages` asserts completeness — it must be false when any provider truncated (a single-page\n\t\t// account-wide read that couldn't confirm it drained everything) OR a drain aborted on a read\n\t\t// failure (its slice is incomplete). Either way the sweep did not read every page.\n\t\tpage: {\n\t\t\tcurrentPage: 1,\n\t\t\titemsPerPage: items.length,\n\t\t\tallPages: !truncated && !fetchFailed,\n\t\t\ttruncated: truncated || undefined,\n\t\t},\n\t\t// A sweep drains every page itself and exposes no cursor to resume — so `hasMore` must be false even\n\t\t// when the read was incomplete. Terminal incompleteness is expressed through `page.truncated` +\n\t\t// `allPages: false` + warnings; setting `hasMore: true` here would make a consumer that drains while\n\t\t// `hasMore` re-run the identical sweep forever with no cursor to advance.\n\t\thasMore: false,\n\t\tfetchFailed: fetchFailed || undefined,\n\t\tfailedProviderIds: [...failedProviderIds],\n\t\tincompleteProviderIds: [...incompleteProviderIds],\n\t};\n}\n\n/**\n * Closed/merged counterpart of {@link sweepPullRequests}, feeding Kepler's Kanban \"done\" column. Applies\n * the native cross-provider state filter (`Closed` + `Merged`) so it works beyond GitHub.\n */\nexport function sweepClosedPullRequests(\n\tctx: ProviderReadContext,\n\toptions?: ClosedPullRequestSweepOptions,\n): Promise<ProviderSweepResult<PullRequestShape>> {\n\treturn sweepPullRequests(ctx, { ...options, states: ['closed', 'merged'] });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sweeps.js","names":[],"sources":["../../../../../plus/integrations/src/reads/sweeps.ts"],"sourcesContent":["import type { PullRequestShape } from '@gitlens/git/models/pullRequest.js';\nimport { mapBounded } from '@gitlens/utils/promise.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { providerFanOutConcurrency } from '../constants.js';\nimport type {\n\tClosedPullRequestSweepOptions,\n\tProviderSweepTarget,\n\tProviderSweepTargetEvent,\n\tPullRequestSweepOptions,\n} from '../manager.js';\nimport { toPullRequestRow } from '../providers/models.js';\nimport type { ProviderSweepResult, ProviderSweepTargetRef, ProviderWarning } from '../results.js';\nimport { appendDedupedWarning } from '../results.js';\nimport { isGitHostIntegration, isIssuesHostIntegrationId } from '../utils/integration.utils.js';\nimport type { ProviderReadContext } from './context.js';\nimport { drainPullRequests, getPullRequestViewers, resolvePullRequestSweepTargets } from './drains.js';\nimport { resolveAccountWidePullRequestFilters, resolvePullRequestFilters } from './filters.js';\nimport {\n\tgitHostOnlySurfaceWarning,\n\tnoConnectionWarning,\n\tunsupportedAccountWidePullRequestFiltersWarning,\n\tunsupportedFiltersWarning,\n} from './warnings.js';\n\n/**\n * The all-pages pull request reads: one drain per provider target, aggregated into a single sweep result.\n *\n * A sweep exposes no continuation of its own — it drains everything it can reach — so its whole contract lives in\n * the completeness signals it returns: `page.allPages` asserts every page of every target was read,\n * `page.truncated` says pages remain unread, and `failedTargets`/`incompleteTargets` (summarized per provider by\n * `failedProviderIds`/`incompleteProviderIds`) separate a target that produced nothing from one that produced a\n * slice with a gap. That split is why the aggregation below can't be collapsed into the generic fan-out merge\n * the hierarchy reads use.\n */\n\n/** One provider target's drained slice, before the cross-provider aggregation. */\ninterface SweepSlice {\n\titems: PullRequestShape[];\n\twarnings: ProviderWarning[];\n\tfetchFailed: boolean;\n\ttruncated: boolean;\n\tproviderId: IntegrationIds;\n\tfailedProvider: boolean;\n}\n\n/**\n * Drain ONE sweep target. Extracted from the fan-out so the fan-out callback has a single exit: every\n * per-target observation (see `onTargetSettled`) is then reported in one place instead of at each of this\n * function's several early returns, where a missed branch would silently drop a provider's attribution.\n *\n * `undefined` means the target resolved to no reachable connection and is deliberately not attributed in the\n * aggregate result.\n */\nasync function sweepTarget(\n\tctx: ProviderReadContext,\n\toptions: PullRequestSweepOptions | undefined,\n\ttarget: ProviderSweepTarget,\n\tattributeUnavailableProviders: boolean,\n): Promise<SweepSlice | undefined> {\n\tconst { providerId: id, connectionId, domain: requestedDomain } = target;\n\tconst repos = target.repos ?? options?.repos ?? [];\n\tconst maxPages = options?.maxPages ?? 100;\n\t/** A target that never reached a drain: the provider itself failed, so its slice is empty and attributed. */\n\tconst rejectedTarget = (warnings: ProviderWarning[]): SweepSlice => ({\n\t\titems: [],\n\t\twarnings: warnings,\n\t\tfetchFailed: true,\n\t\ttruncated: false,\n\t\tproviderId: id,\n\t\tfailedProvider: true,\n\t});\n\n\tif (isIssuesHostIntegrationId(id)) {\n\t\treturn rejectedTarget([gitHostOnlySurfaceWarning(id, requestedDomain, connectionId, 'pull request sweeps')]);\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(id, connectionId, requestedDomain);\n\tif (integration == null) {\n\t\t// A requested connection that can't be resolved is a broken connection — surface it as a\n\t\t// warning + fetchFailed rather than dropping the provider's slice silently.\n\t\tconst early = ctx.earlyReturnConnectionWarnings(id, connectionId, requestedDomain);\n\t\tif (early.warnings.length === 0 && !attributeUnavailableProviders) return undefined;\n\n\t\treturn rejectedTarget(\n\t\t\tearly.warnings.length !== 0 ? early.warnings : [noConnectionWarning(id, requestedDomain, connectionId)],\n\t\t);\n\t}\n\tif (!isGitHostIntegration(integration)) {\n\t\treturn rejectedTarget([gitHostOnlySurfaceWarning(id, requestedDomain, connectionId, 'pull request sweeps')]);\n\t}\n\n\tawait ctx.forceRefreshIfRequested(integration, options?.forceSync, connectionId);\n\n\tconst domain = ctx.domainForRead(integration, id, connectionId, requestedDomain);\n\tconst accountWide = repos.length === 0;\n\tconst requestedFilters = target.filters ?? options?.filters;\n\tconst resolved = accountWide\n\t\t? resolveAccountWidePullRequestFilters(id, requestedFilters)\n\t\t: resolvePullRequestFilters(id, requestedFilters);\n\tif (resolved.unsupported) {\n\t\treturn rejectedTarget([\n\t\t\taccountWide\n\t\t\t\t? unsupportedAccountWidePullRequestFiltersWarning(id, domain, connectionId, requestedFilters ?? [])\n\t\t\t\t: unsupportedFiltersWarning(id, domain, connectionId),\n\t\t]);\n\t}\n\n\tconst drain = await drainPullRequests(\n\t\tintegration,\n\t\tid,\n\t\tdomain,\n\t\trepos,\n\t\toptions?.states,\n\t\tresolved.filters,\n\t\toptions,\n\t\tconnectionId,\n\t\tmaxPages,\n\t\tattributeUnavailableProviders,\n\t);\n\tconst viewers = await getPullRequestViewers(integration, connectionId, drain.items);\n\t// Mirrors the projection `drainPullRequests` requests.\n\tconst projection = accountWide ? (options?.includeReviews ? 'account' : 'account-summary') : 'repos';\n\t// Normalize the raw rows to the GitLens-owned shape here, where the per-provider `integration` (the\n\t// mapper's provider reference) is in scope; the aggregation below only sees drains.\n\treturn {\n\t\t...drain,\n\t\titems: drain.items.map((pr, i) =>\n\t\t\ttoPullRequestRow(pr, integration, { currentAccount: viewers[i], projection: projection }),\n\t\t),\n\t\tproviderId: id,\n\t};\n}\n\n/**\n * How a target ended, as the consumer buckets it. `failedProvider` outranks `fetchFailed` because a target\n * whose provider failed produced no slice to be partial about, and `undefined` is the target that resolved to\n * no reachable connection at all.\n */\nfunction sliceOutcome(slice: SweepSlice | undefined): ProviderSweepTargetEvent['outcome'] {\n\tif (slice == null) return 'skipped';\n\tif (slice.failedProvider) return 'failed-provider';\n\tif (slice.fetchFailed) return 'fetch-failed';\n\treturn 'ok';\n}\n\n/**\n * Per-target reporting for a sweep's `onTargetSettled`, opened once per sweep: this stamps the fan-out start,\n * the returned function stamps one target's start, and the function IT returns is the only thing that can\n * report that target. The timestamps therefore never cross a boundary, so there is no pair of interchangeable\n * numbers for a call site to transpose, and the whole reporting concern is one value the fan-out either has or\n * does not. That is also what keeps the option free when it is omitted: with no observer there is nothing to\n * open, so no clock is read and no reporting object exists — do not \"simplify\" that into an unconditional\n * open, because a host whose perf gate is off (the default) is entitled to pay nothing for it.\n *\n * The try/catch is what makes the observer observation-only: called from the fan-out's success path, a throwing\n * callback would otherwise propagate out of the `mapBounded` task and reject the entire sweep — corrupting the\n * read, not just the metric. Swallowed silently; the consumer owns its own aggregation. The `void`-typed\n * callback also admits an `async` consumer, whose rejection is created only after `observe` returns — the\n * `Promise.resolve(...).catch` swallows that too, so it cannot escape as an unhandled rejection.\n *\n * The domain is resolved here rather than carried out of {@link sweepTarget}, so every target reports it by the\n * same rule no matter how far it got. `resolveDomainForRead` needs no integration instance, which is what makes\n * that possible: a target rejected by the first guard resolves the same host a fully drained one does.\n */\nfunction startSweepReporting(\n\tctx: ProviderReadContext,\n\tobserve: (event: ProviderSweepTargetEvent) => void | PromiseLike<unknown>,\n) {\n\tconst fanOutStartedAt = performance.now();\n\n\treturn function beginTarget(target: ProviderSweepTarget) {\n\t\tconst startedAt = performance.now();\n\n\t\treturn function reportSettled(slice: SweepSlice | undefined) {\n\t\t\ttry {\n\t\t\t\tvoid Promise.resolve(\n\t\t\t\t\tobserve({\n\t\t\t\t\t\tproviderId: target.providerId,\n\t\t\t\t\t\tdomain: ctx.resolveDomainForRead(target.providerId, target.connectionId, target.domain),\n\t\t\t\t\t\tconnectionId: target.connectionId,\n\t\t\t\t\t\tcount: slice?.items.length ?? 0,\n\t\t\t\t\t\tdurationMs: performance.now() - startedAt,\n\t\t\t\t\t\tqueueWaitMs: startedAt - fanOutStartedAt,\n\t\t\t\t\t\toutcome: sliceOutcome(slice),\n\t\t\t\t\t\ttruncated: slice?.truncated ?? false,\n\t\t\t\t\t}),\n\t\t\t\t).catch(() => {});\n\t\t\t} catch {}\n\t\t};\n\t};\n}\n\nexport async function sweepPullRequests(\n\tctx: ProviderReadContext,\n\toptions?: PullRequestSweepOptions,\n): Promise<ProviderSweepResult<PullRequestShape>> {\n\tconst { targets, attributeUnavailableProviders } = resolvePullRequestSweepTargets(options);\n\n\tconst observe = options?.onTargetSettled;\n\tconst beginTarget = observe != null ? startSweepReporting(ctx, observe) : undefined;\n\n\tconst results = await mapBounded(targets, providerFanOutConcurrency, async target => {\n\t\tconst reportSettled = beginTarget?.(target);\n\t\tconst slice = await sweepTarget(ctx, options, target, attributeUnavailableProviders);\n\t\treportSettled?.(slice);\n\t\treturn { target: target, slice: slice };\n\t});\n\n\tconst items: PullRequestShape[] = [];\n\tconst warnings: ProviderWarning[] = [];\n\tconst failedTargets: ProviderSweepTargetRef[] = [];\n\tconst incompleteTargets: ProviderSweepTargetRef[] = [];\n\t// Per provider: attributed (non-skipped) targets, and how many of them failed or came back incomplete.\n\tconst attributed = new Map<IntegrationIds, { total: number; failed: number; incomplete: number }>();\n\tlet fetchFailed = false;\n\tlet truncated = false;\n\tfor (const { target, slice: drain } of results) {\n\t\tif (drain == null) {\n\t\t\tcontinue;\n\t\t}\n\n\t\titems.push(...drain.items);\n\t\tfor (const w of drain.warnings) {\n\t\t\tappendDedupedWarning(warnings, w);\n\t\t}\n\t\tif (drain.fetchFailed) {\n\t\t\tfetchFailed = true;\n\t\t}\n\n\t\tlet counts = attributed.get(drain.providerId);\n\t\tif (counts == null) {\n\t\t\tcounts = { total: 0, failed: 0, incomplete: 0 };\n\t\t\tattributed.set(drain.providerId, counts);\n\t\t}\n\t\tcounts.total++;\n\n\t\tif (drain.failedProvider || drain.fetchFailed || drain.truncated) {\n\t\t\t// The same ref the target's settle event carries, so a consumer can join the two.\n\t\t\tconst ref: ProviderSweepTargetRef = {\n\t\t\t\tproviderId: target.providerId,\n\t\t\t\tconnectionId: target.connectionId,\n\t\t\t\tdomain: ctx.resolveDomainForRead(target.providerId, target.connectionId, target.domain),\n\t\t\t};\n\t\t\tif (drain.failedProvider) {\n\t\t\t\tcounts.failed++;\n\t\t\t\tfailedTargets.push(ref);\n\t\t\t} else {\n\t\t\t\tcounts.incomplete++;\n\t\t\t\tincompleteTargets.push(ref);\n\t\t\t}\n\t\t}\n\t\tif (drain.truncated) {\n\t\t\ttruncated = true;\n\t\t}\n\t}\n\n\t// A provider is failed only when every target of it failed; any lesser trouble leaves it incomplete.\n\tconst failedProviderIds: IntegrationIds[] = [];\n\tconst incompleteProviderIds: IntegrationIds[] = [];\n\tfor (const [providerId, { total, failed, incomplete }] of attributed) {\n\t\tif (failed === total) {\n\t\t\tfailedProviderIds.push(providerId);\n\t\t} else if (failed + incomplete !== 0) {\n\t\t\tincompleteProviderIds.push(providerId);\n\t\t}\n\t}\n\n\treturn {\n\t\titems: items,\n\t\twarnings: warnings,\n\t\t// `allPages` asserts completeness — it must be false when any provider truncated (a single-page\n\t\t// account-wide read that couldn't confirm it drained everything) OR a drain aborted on a read\n\t\t// failure (its slice is incomplete). Either way the sweep did not read every page.\n\t\tpage: {\n\t\t\tcurrentPage: 1,\n\t\t\titemsPerPage: items.length,\n\t\t\tallPages: !truncated && !fetchFailed,\n\t\t\ttruncated: truncated || undefined,\n\t\t},\n\t\t// A sweep drains every page itself and exposes no cursor to resume — so `hasMore` must be false even\n\t\t// when the read was incomplete. Terminal incompleteness is expressed through `page.truncated` +\n\t\t// `allPages: false` + warnings; setting `hasMore: true` here would make a consumer that drains while\n\t\t// `hasMore` re-run the identical sweep forever with no cursor to advance.\n\t\thasMore: false,\n\t\tfetchFailed: fetchFailed || undefined,\n\t\tfailedProviderIds: failedProviderIds,\n\t\tincompleteProviderIds: incompleteProviderIds,\n\t\tfailedTargets: failedTargets,\n\t\tincompleteTargets: incompleteTargets,\n\t};\n}\n\n/**\n * Closed/merged counterpart of {@link sweepPullRequests}, feeding Kepler's Kanban \"done\" column. Applies\n * the native cross-provider state filter (`Closed` + `Merged`) so it works beyond GitHub.\n */\nexport function sweepClosedPullRequests(\n\tctx: ProviderReadContext,\n\toptions?: ClosedPullRequestSweepOptions,\n): Promise<ProviderSweepResult<PullRequestShape>> {\n\treturn sweepPullRequests(ctx, { ...options, states: ['closed', 'merged'] });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAqDA,eAAe,YACd,KACA,SACA,QACA,+BACkC;CAClC,MAAM,EAAE,YAAY,IAAI,cAAc,QAAQ,oBAAoB;CAClE,MAAM,QAAQ,OAAO,SAAS,SAAS,SAAS,CAAC;CACjD,MAAM,WAAW,SAAS,YAAY;;CAEtC,MAAM,kBAAkB,cAA6C;EACpE,OAAO,CAAC;EACE;EACV,aAAa;EACb,WAAW;EACX,YAAY;EACZ,gBAAgB;CACjB;CAEA,IAAI,0BAA0B,EAAE,GAC/B,OAAO,eAAe,CAAC,0BAA0B,IAAI,iBAAiB,cAAc,qBAAqB,CAAC,CAAC;CAG5G,MAAM,cAAc,MAAM,IAAI,sBAAsB,IAAI,cAAc,eAAe;CACrF,IAAI,eAAe,MAAM;EAGxB,MAAM,QAAQ,IAAI,8BAA8B,IAAI,cAAc,eAAe;EACjF,IAAI,MAAM,SAAS,WAAW,KAAK,CAAC,+BAA+B,OAAO,KAAA;EAE1E,OAAO,eACN,MAAM,SAAS,WAAW,IAAI,MAAM,WAAW,CAAC,oBAAoB,IAAI,iBAAiB,YAAY,CAAC,CACvG;CACD;CACA,IAAI,CAAC,qBAAqB,WAAW,GACpC,OAAO,eAAe,CAAC,0BAA0B,IAAI,iBAAiB,cAAc,qBAAqB,CAAC,CAAC;CAG5G,MAAM,IAAI,wBAAwB,aAAa,SAAS,WAAW,YAAY;CAE/E,MAAM,SAAS,IAAI,cAAc,aAAa,IAAI,cAAc,eAAe;CAC/E,MAAM,cAAc,MAAM,WAAW;CACrC,MAAM,mBAAmB,OAAO,WAAW,SAAS;CACpD,MAAM,WAAW,cACd,qCAAqC,IAAI,gBAAgB,IACzD,0BAA0B,IAAI,gBAAgB;CACjD,IAAI,SAAS,aACZ,OAAO,eAAe,CACrB,cACG,gDAAgD,IAAI,QAAQ,cAAc,oBAAoB,CAAC,CAAC,IAChG,0BAA0B,IAAI,QAAQ,YAAY,CACtD,CAAC;CAGF,MAAM,QAAQ,MAAM,kBACnB,aACA,IACA,QACA,OACA,SAAS,QACT,SAAS,SACT,SACA,cACA,UACA,6BACD;CACA,MAAM,UAAU,MAAM,sBAAsB,aAAa,cAAc,MAAM,KAAK;CAElF,MAAM,aAAa,cAAe,SAAS,iBAAiB,YAAY,oBAAqB;CAG7F,OAAO;EACN,GAAG;EACH,OAAO,MAAM,MAAM,KAAK,IAAI,MAC3B,iBAAiB,IAAI,aAAa;GAAE,gBAAgB,QAAQ;GAAgB;EAAW,CAAC,CACzF;EACA,YAAY;CACb;AACD;;;;;;AAOA,SAAS,aAAa,OAAoE;CACzF,IAAI,SAAS,MAAM,OAAO;CAC1B,IAAI,MAAM,gBAAgB,OAAO;CACjC,IAAI,MAAM,aAAa,OAAO;CAC9B,OAAO;AACR;;;;;;;;;;;;;;;;;;;;AAqBA,SAAS,oBACR,KACA,SACC;CACD,MAAM,kBAAkB,YAAY,IAAI;CAExC,OAAO,SAAS,YAAY,QAA6B;EACxD,MAAM,YAAY,YAAY,IAAI;EAElC,OAAO,SAAS,cAAc,OAA+B;GAC5D,IAAI;IACH,QAAa,QACZ,QAAQ;KACP,YAAY,OAAO;KACnB,QAAQ,IAAI,qBAAqB,OAAO,YAAY,OAAO,cAAc,OAAO,MAAM;KACtF,cAAc,OAAO;KACrB,OAAO,OAAO,MAAM,UAAU;KAC9B,YAAY,YAAY,IAAI,IAAI;KAChC,aAAa,YAAY;KACzB,SAAS,aAAa,KAAK;KAC3B,WAAW,OAAO,aAAa;IAChC,CAAC,CACF,CAAC,CAAC,YAAY,CAAC,CAAC;GACjB,QAAQ,CAAC;EACV;CACD;AACD;AAEA,eAAsB,kBACrB,KACA,SACiD;CACjD,MAAM,EAAE,SAAS,kCAAkC,+BAA+B,OAAO;CAEzF,MAAM,UAAU,SAAS;CACzB,MAAM,cAAc,WAAW,OAAO,oBAAoB,KAAK,OAAO,IAAI,KAAA;CAE1E,MAAM,UAAU,MAAM,WAAW,SAAA,GAAoC,OAAM,WAAU;EACpF,MAAM,gBAAgB,cAAc,MAAM;EAC1C,MAAM,QAAQ,MAAM,YAAY,KAAK,SAAS,QAAQ,6BAA6B;EACnF,gBAAgB,KAAK;EACrB,OAAO;GAAU;GAAe;EAAM;CACvC,CAAC;CAED,MAAM,QAA4B,CAAC;CACnC,MAAM,WAA8B,CAAC;CACrC,MAAM,gBAA0C,CAAC;CACjD,MAAM,oBAA8C,CAAC;CAErD,MAAM,6BAAa,IAAI,IAA2E;CAClG,IAAI,cAAc;CAClB,IAAI,YAAY;CAChB,KAAK,MAAM,EAAE,QAAQ,OAAO,WAAW,SAAS;EAC/C,IAAI,SAAS,MACZ;EAGD,MAAM,KAAK,GAAG,MAAM,KAAK;EACzB,KAAK,MAAM,KAAK,MAAM,UACrB,qBAAqB,UAAU,CAAC;EAEjC,IAAI,MAAM,aACT,cAAc;EAGf,IAAI,SAAS,WAAW,IAAI,MAAM,UAAU;EAC5C,IAAI,UAAU,MAAM;GACnB,SAAS;IAAE,OAAO;IAAG,QAAQ;IAAG,YAAY;GAAE;GAC9C,WAAW,IAAI,MAAM,YAAY,MAAM;EACxC;EACA,OAAO;EAEP,IAAI,MAAM,kBAAkB,MAAM,eAAe,MAAM,WAAW;GAEjE,MAAM,MAA8B;IACnC,YAAY,OAAO;IACnB,cAAc,OAAO;IACrB,QAAQ,IAAI,qBAAqB,OAAO,YAAY,OAAO,cAAc,OAAO,MAAM;GACvF;GACA,IAAI,MAAM,gBAAgB;IACzB,OAAO;IACP,cAAc,KAAK,GAAG;GACvB,OAAO;IACN,OAAO;IACP,kBAAkB,KAAK,GAAG;GAC3B;EACD;EACA,IAAI,MAAM,WACT,YAAY;CAEd;CAGA,MAAM,oBAAsC,CAAC;CAC7C,MAAM,wBAA0C,CAAC;CACjD,KAAK,MAAM,CAAC,YAAY,EAAE,OAAO,QAAQ,iBAAiB,YACzD,IAAI,WAAW,OACd,kBAAkB,KAAK,UAAU;MAC3B,IAAI,SAAS,eAAe,GAClC,sBAAsB,KAAK,UAAU;CAIvC,OAAO;EACC;EACG;EAIV,MAAM;GACL,aAAa;GACb,cAAc,MAAM;GACpB,UAAU,CAAC,aAAa,CAAC;GACzB,WAAW,aAAa,KAAA;EACzB;EAKA,SAAS;EACT,aAAa,eAAe,KAAA;EACT;EACI;EACR;EACI;CACpB;AACD;;;;;AAMA,SAAgB,wBACf,KACA,SACiD;CACjD,OAAO,kBAAkB,KAAK;EAAE,GAAG;EAAS,QAAQ,CAAC,UAAU,QAAQ;CAAE,CAAC;AAC3E"}
|
|
@@ -366,22 +366,42 @@ export interface ProviderPagedResult<T> extends ProviderResult<T> {
|
|
|
366
366
|
*/
|
|
367
367
|
cursor?: string;
|
|
368
368
|
}
|
|
369
|
+
/**
|
|
370
|
+
* One sweep target, named the way {@link ProviderSweepTargetEvent} names it, so a consumer can join the two.
|
|
371
|
+
* `domain` is the resolved self-managed host (always `undefined` for a cloud provider).
|
|
372
|
+
*/
|
|
373
|
+
export interface ProviderSweepTargetRef {
|
|
374
|
+
providerId: IntegrationIds;
|
|
375
|
+
connectionId: string | undefined;
|
|
376
|
+
domain: string | undefined;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* A sweep may carry several targets of one provider (one per connection), so the provider arrays below can't say
|
|
380
|
+
* which connection failed: `failedTargets` and `incompleteTargets` are the authoritative attribution, and
|
|
381
|
+
* `failedProviderIds`/`incompleteProviderIds` summarize them. With one target per provider the two agree exactly.
|
|
382
|
+
*/
|
|
369
383
|
export interface ProviderSweepResult<T> extends ProviderResult<T> {
|
|
370
384
|
page: ProviderPageInfo;
|
|
371
385
|
hasMore: boolean;
|
|
372
386
|
/**
|
|
373
|
-
* Providers whose top-level read failed before returning any usable page
|
|
374
|
-
*
|
|
387
|
+
* Providers whose top-level read failed before returning any usable page, for EVERY attributed target of the
|
|
388
|
+
* provider. A provider with a failed target and a surviving sibling is in `incompleteProviderIds` instead.
|
|
389
|
+
* Partial per-scope failures stay represented by `warnings` + `fetchFailed` and are intentionally excluded.
|
|
375
390
|
*/
|
|
376
391
|
failedProviderIds: IntegrationIds[];
|
|
377
392
|
/**
|
|
378
393
|
* Providers whose read started successfully but could not produce an authoritative complete slice (for
|
|
379
|
-
* example, a later page failed, collection metadata reported a partial scope, or a paging backstop was hit)
|
|
394
|
+
* example, a later page failed, collection metadata reported a partial scope, or a paging backstop was hit),
|
|
395
|
+
* or that have a failed target beside one that returned something.
|
|
380
396
|
* Kept separate from
|
|
381
397
|
* `failedProviderIds` so consumers can accept healthy sibling providers while preserving an older snapshot
|
|
382
398
|
* for a provider whose returned slice has a gap.
|
|
383
399
|
*/
|
|
384
400
|
incompleteProviderIds: IntegrationIds[];
|
|
401
|
+
/** Targets whose top-level read failed before returning any usable page. A skipped target is in neither list. */
|
|
402
|
+
failedTargets: ProviderSweepTargetRef[];
|
|
403
|
+
/** Targets that returned a slice with a gap: a later page failed, or pages remain unread. Never also in `failedTargets`. */
|
|
404
|
+
incompleteTargets: ProviderSweepTargetRef[];
|
|
385
405
|
}
|
|
386
406
|
export interface ProviderBroadenResult<T> extends ProviderPagedResult<T> {
|
|
387
407
|
broadenedProviderIds: IntegrationIds[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"results.js","names":[],"sources":["../../../../plus/integrations/src/results.ts"],"sourcesContent":["import { isUnsupportedSortError } from '@gitkraken/provider-apis';\nimport { AuthenticationError, RequestNotFoundError, RequestRateLimitError } from '@gitlens/git/errors.js';\nimport type { IssueSorting } from '@gitlens/git/models/issue.js';\nimport type { PullRequestSorting } from '@gitlens/git/models/pullRequest.js';\nimport type { IntegrationIds } from './constants.js';\nimport { isProviderUnreachableError } from './errors.js';\nimport { UnexpectedResponseError } from './providers/providerErrors.js';\n\nexport interface ConnectionStateChangeEvent {\n\tkey: string;\n\treason: 'connected' | 'disconnected';\n}\n\n/**\n * A per-provider, per-connection warning surfaced alongside (partial) read results. Consumers use\n * these to drive auth recovery, retry, or truncation messaging without the read itself throwing.\n *\n * `kind` is the programmatic discriminant (a rate-limit is retryable, a 404 is not, an unsupported capability\n * never succeeds on retry, an auth failure needs re-connection unless {@link ProviderWarning.scope} confines\n * it); `isAuth` is retained as a convenience mirror of `kind === 'auth'`.\n *\n * `'unsupported'`: the provider — or this integration of it — lacks the capability the request asked for: a\n * surface (issues on Bitbucket, pull requests on an issue tracker, a batch read, org/repo discovery,\n * current-account lookup, project-scoped or account-wide issue reads), or a filter, sort, state, search\n * criterion or option combination it cannot express. Retrying the same request never succeeds; a different\n * provider, scope or request may. NOT unsupported (these stay `'other'`): malformed or contradictory caller\n * input the caller fixes in the request itself (a duplicate batch key, invalid target numbers, an empty owner,\n * an unusable scope), incompleteness or omission warnings, and unclassified errors.\n *\n * `'unreadable-items'`: the provider returned items this read could not read (a Jira issue whose fields the SDK cannot\n * parse), so it left them out and read everything else in the warning's {@link ProviderWarning.scope}. It still fails\n * the read (`fetchFailed`), since those items are missing, but retrying returns the same gap. On the reads that publish\n * it (an issue tracker's project reads) it never hides a read that was cut short: a cut always comes with a warning of\n * another kind. {@link ProviderWarning.unreadableItems} names the items left out.\n */\nexport type ProviderWarningKind =\n\t| 'auth'\n\t| 'rate-limit'\n\t| 'not-found'\n\t| 'no-connection'\n\t| 'unsupported'\n\t| 'unreadable-items'\n\t| 'other';\n\n/** The items an `'unreadable-items'` warning left out: see {@link ProviderWarning.unreadableItems}. */\nexport interface ProviderWarningUnreadableItems {\n\t/**\n\t * The provider's identifiers of the items left out (a Jira issue's key), without duplicates, in reported order. Each\n\t * one is an item the provider reported as skipped, so an identifier present is one that was left out, and one absent\n\t * was read when {@link complete}.\n\t *\n\t * Both hold only for the spelling the provider returned, which is also how a published issue's id spells its key: `keys`\n\t * doesn't answer a lookup by another spelling, such as a different case or the key an issue had before it moved. A\n\t * consumer comparing a key from elsewhere (one parsed from a branch name, say) must normalize the case of both sides,\n\t * and can't take an issue under a key it once had as read.\n\t */\n\tkeys: string[];\n\t/**\n\t * Whether {@link keys} names every item left out. `false` when an item left out had no key to report, when the\n\t * provider's report of them was malformed (no list of keys, or more than keys in it), or when it said items were left\n\t * out and named none, so an identifier absent from `keys` may still be one of them.\n\t */\n\tcomplete: boolean;\n}\n\n/**\n * Why a read that SUCCEEDED still withheld results:\n * - `provider-limit`: the provider refuses to serve past a cap it enforces (GitHub search's 1,000-result\n * ceiling, Trello's `cards_limit`), so the excess is unreachable through that query.\n * - `recovery-budget`: the internal partitioned recovery stopped spending upstream requests before it ran\n * out of partitions to visit.\n * - `pagination-incomplete`: pages were left unread — a sub-scope of a multi-scope read that was not drained,\n * a drain that stopped at its own page backstop, or a provider that advertised another page and gave no way\n * to reach it. These have DIFFERENT remedies, so the kind alone does not say whether more can be fetched;\n * read {@link ProviderWarningOmission.recovery} for that.\n *\n * Mirrors the vocabulary the SDK reports. Declared here rather than imported so this module stays free of\n * `@gitkraken/provider-apis` types (see the export block in `index.ts`); `collectionMetadata.ts` holds the\n * compile-time link, so an SDK bump that adds a kind fails the build at that boundary.\n */\nexport type ProviderWarningOmissionKind = 'provider-limit' | 'recovery-budget' | 'pagination-incomplete';\n\n/**\n * How a consumer can recover omitted results. `page-budget` reruns with a larger budget; `narrow-scope`\n * requests a smaller server-side scope; `none` promises neither. Switch on the exact value because only\n * `page-budget` is a fetch-more action.\n */\nexport type ProviderWarningOmissionRecovery = 'none' | 'page-budget' | 'narrow-scope';\n\n/**\n * The part of a provider a warning is confined to: one resource (organization, workspace, site), project or\n * repository. All fields optional; see {@link ProviderWarning.scope} for when one is forwarded.\n */\nexport interface ProviderWarningScope {\n\t/**\n\t * The resource as the read addressed it: its id on most reads, its name on the few that address it by name\n\t * (Azure DevOps' repo-scoped reads, Bitbucket's workspace reads). Match it against both\n\t * `ProviderOrganization.id` and `name`, and a project's `orgId` where it has one.\n\t */\n\tresourceId?: string;\n\t/**\n\t * The project as the read addressed it. For a Jira project read, the key, on Jira Cloud and Jira Data Center alike\n\t * (the project's `name` in `listProjects`).\n\t */\n\tprojectId?: string;\n\trepositoryId?: string;\n}\n\n/**\n * The selector of an issue-tracker project read that matched nothing: see {@link ProviderWarning.selector}. `kind`\n * names which one missed, and `value` is that selector exactly as the caller passed it. `linearProject` is one of a\n * Linear read's `linearProjects`, an id the workspace's Projects no longer list.\n */\nexport interface ProviderWarningSelector {\n\tkind: 'org' | 'project' | 'linearProject';\n\tvalue: string;\n}\n\n/**\n * Why a provider request failed, when the failure itself says so:\n * - `unreachable`: a transport failure or server-side error prevented the provider from answering. The connection\n * still exists; this accompanies `kind: 'other'`, never an authentication warning.\n * - `oauth-app-not-allowed`: the organization does not let third-party OAuth apps in. On Azure DevOps this is the\n * organization policy \"Third-party application access via OAuth\", off by default for new organizations. An\n * organization admin enables it, or the user connects with a personal access token instead, which the policy\n * does not govern.\n * - `access-denied`: the account has no access to that organization or project (not a member, or lacking the\n * permission). Someone who administers it has to grant access.\n * - `conditional-access`: a Microsoft Entra Conditional Access policy blocked the request (Azure DevOps\n * `VS403463`). Its admin has to exempt the request, e.g. by location or device.\n * - `unexpected-response`: the provider answered, but not with data in the shape the read expects (a gateway's JSON\n * error on a 2xx, an empty object, `null`), so the read could not use it. Accompanies `kind: 'other'`, like\n * `unreachable`; a retry may succeed once the provider, or whatever answered in front of it, answers properly. A\n * page served instead of data keeps its own message, without this cause.\n *\n * None of these is healed by reconnecting, which is the point of naming them.\n */\nexport type ProviderWarningCauseReason =\n\t| 'oauth-app-not-allowed'\n\t| 'access-denied'\n\t| 'conditional-access'\n\t| 'unreachable'\n\t| 'unexpected-response';\n\n/** See {@link ProviderWarning.cause}. */\nexport interface ProviderWarningCause {\n\treason: ProviderWarningCauseReason;\n\t/** The provider's own error code, when it reports one (Azure DevOps `TF400813`, `VS403463`). */\n\tcode?: string;\n\t/** Where the setting behind the refusal is changed, when this layer can address it. */\n\tremedyUrl?: string;\n}\n\n/** Which repository / project / resource an omission is attributed to. All fields optional; a scope may name none. */\nexport interface ProviderWarningOmissionScope extends ProviderWarningScope {\n\tproviderId?: string;\n}\n\nexport interface ProviderWarningOmission {\n\tkind: ProviderWarningOmissionKind;\n\t/**\n\t * Whether anything would fetch the missing items, and what. Gate a \"load more\" affordance on this rather\n\t * than on `kind` — see {@link ProviderWarningOmissionRecovery}.\n\t */\n\trecovery: ProviderWarningOmissionRecovery;\n\t/**\n\t * The cap the provider enforces, when it reports one. NOT a result count for every kind: on\n\t * `recovery-budget` this is a REQUEST budget and must not be shown to a user as a number of results.\n\t */\n\tlimit?: number;\n\t/** Total matches the provider reported, when it reports one. Only GitHub's search cap does today. */\n\ttotalCount?: number;\n\t/**\n\t * The order the reachable window was selected under, as `field:direction`, when the omission is a result\n\t * ceiling.\n\t *\n\t * Present so a consumer can word its own \"N matched, showing the first M\" without assuming WHICH M: the\n\t * provider serves the top M under this key, so the sentence is only true if it names it. Absent for every\n\t * omission that isn't a ceiling, where no window was selected by an order.\n\t *\n\t * A union of both vocabularies rather than the issue one alone: `PullRequestSorting` happens to be a subset\n\t * today, so narrowing this to {@link IssueSorting} would still compile while describing only half the reads\n\t * that set it — and would break the day the PR search gains a key the issue search has no name for.\n\t */\n\tsort?: IssueSorting | PullRequestSorting;\n\t/** Which repository / project / resource was affected, when one is attributed. */\n\tscope?: ProviderWarningOmissionScope;\n}\n\nexport interface ProviderWarning {\n\tproviderId: IntegrationIds;\n\t/** Disambiguates connections on self-managed hosts (where one provider id spans multiple domains). */\n\tdomain?: string;\n\t/** The specific token/connection the read was pinned to, when a `connectionId` was supplied. */\n\tconnectionId?: string;\n\tmessage: string;\n\tkind: ProviderWarningKind;\n\t/** Convenience mirror of `kind === 'auth'`, scoped or not; read {@link scope} before prompting to reconnect. */\n\tisAuth: boolean;\n\t/**\n\t * Present when the failure was recorded against one sub-scope of the read (an organization, project or\n\t * repository) rather than against the connection. A scoped `auth` failure is that one scope refusing this\n\t * credential, e.g. an Azure DevOps organization that disallows third-party OAuth apps, one in another Entra\n\t * tenant, or a Conditional Access policy. Reconnecting cannot fix those, so this warning alone is no reason\n\t * to prompt for it.\n\t *\n\t * `kind` and `isAuth` are unaffected: a scoped 401 is still an authentication failure, and this field says\n\t * how far it reaches. It names at least one of its IDs, and is never set on an omission (which carries its\n\t * own {@link ProviderWarningOmission.scope}) or on a warning derived from a caught exception (see\n\t * {@link toProviderWarning}), even when that call targeted a single organization. The exception is a batch\n\t * read's refused target, which names its scope: the Azure DevOps organization and project, the repository on\n\t * other git hosts, or the tracker's resource.\n\t *\n\t * Its ABSENCE means account-wide or unattributed, so existing handling stays correct without it.\n\t *\n\t * A scoped `auth` failure also means the credential itself was accepted. A dead token can come back as nothing\n\t * but scoped refusals wherever a read reaches its scopes without an uncached request to the connection first:\n\t * discovery served from a per-token cache (Azure DevOps, Bitbucket, Jira Cloud) or an SDK fan-out across the\n\t * requested repositories (Bitbucket Data Center). So when a read's only auth failures are scoped, those providers\n\t * confirm the credential with one uncached check first, and a refused credential fails the whole read with an\n\t * unscoped `auth` warning. A batch read checks it only when every target was refused, since a target that\n\t * answered already proved it. A refusal the provider attributes to the credential itself, like Bitbucket's or Jira\n\t * Cloud's for a token missing the OAuth scopes a read needs, which a reconnect fixes, is published unscoped too,\n\t * once for the connection, while the scopes that answered keep their results.\n\t *\n\t * So the promise holds for a read that carries no unscoped `auth` warning of its own; one that does already asks\n\t * for a reconnect, and its other scoped refusals are not checked. Two exceptions stay scoped even then. A check\n\t * that could not complete or was denied (a network error, a throttle, a `403`, Bitbucket Data Center refusing a\n\t * credential it authenticated, or a Bitbucket Data Center project access token, whose user the check cannot\n\t * find) proves nothing, so the warnings are published unconfirmed, with no {@link cause}, and the next read\n\t * checks again. And a credential confirmed within the last minute is not probed again, so a revocation can take\n\t * up to a minute to surface.\n\t */\n\tscope?: ProviderWarningScope;\n\t/**\n\t * Why the provider request failed, when the failure itself says so: see {@link ProviderWarningCauseReason}. Switch on\n\t * `reason` to recommend a fix instead of a reconnect; `message` then carries the same explanation as prose.\n\t *\n\t * `unreachable` accompanies a transport or server failure with `kind: 'other'`, and `unexpected-response` a response\n\t * the read could not use, also with `kind: 'other'`. Credential refusal causes are set only on a scoped `auth`\n\t * warning whose credential was confirmed first (see {@link scope}), because the\n\t * refusals they name are indistinguishable from a dead credential until then: Azure DevOps answers a\n\t * third-party OAuth app its organization disallows exactly as it answers an expired token. Only Azure DevOps\n\t * reports one today. Its absence proves nothing about the cause.\n\t */\n\tcause?: ProviderWarningCause;\n\t/**\n\t * Present when this warning describes results the read could not return even though the request itself\n\t * SUCCEEDED — a provider-enforced cap, an exhausted recovery budget, a page budget, or a sub-scope the read\n\t * did not drain.\n\t *\n\t * Its presence is the signal: an omission is not a failure. Message it as incompleteness rather than as a\n\t * failed read, and do NOT derive that from `message`, which is English prose and subject to rewording.\n\t * `kind` stays `'other'` for these, so the failure discriminant keeps meaning exactly what it meant before\n\t * this field existed.\n\t *\n\t * Whether anything would fetch the rest is a SEPARATE question — read {@link ProviderWarningOmission.recovery},\n\t * not `kind`, and never assume a retry of the same request returns more.\n\t *\n\t * `limit` / `totalCount` / `scope` are forwarded only when they are reported; most omissions carry none of\n\t * the three, so render correctly without them — and see {@link ProviderWarningOmission.limit} before\n\t * showing that figure as a result count.\n\t *\n\t * Its ABSENCE proves nothing. It is never set on a warning derived from a caught exception (see\n\t * {@link toProviderWarning}) or from a structured scope failure — those are failures, and the field would\n\t * be a lie there. But it is also absent whenever incompleteness was reported without naming what was left\n\t * out, so treat a bare `kind: 'other'` warning as unclassified rather than as a proven failure.\n\t */\n\tomission?: ProviderWarningOmission;\n\t/**\n\t * Present exactly when {@link kind} is `'unreadable-items'`: which items the read left out because it could not read\n\t * them. A consumer deciding whether an identifier is absent from the provider, rather than merely unread, must find\n\t * it in no warning's `keys` and see `complete` on every one.\n\t */\n\tunreadableItems?: ProviderWarningUnreadableItems;\n\t/**\n\t * Present when an issue-tracker project read's `org` or `project` selector matched nothing the connection\n\t * discovered: the selection was removed, renamed, or is no longer visible to this connection. `kind` is\n\t * `'not-found'`, the page is empty and not `fetchFailed`, and it carries no cursor, since a retry finds the same.\n\t * A `linearProject` miss is diagnostic: the requested Project ids still filter the read, preserving issues in\n\t * other selected Projects. It therefore does not force an empty page or remove its continuation.\n\t *\n\t * Set only when discovery was complete. A read whose discovery failed or was truncated cannot tell a missing\n\t * selection from one it did not reach, so it keeps reporting that failure instead, without this field. A miss is\n\t * confirmed against the connection's lists as the provider holds them now, read without the cache, since a cached\n\t * one predates what was created since. A cached project list can still name a project deleted since: the tracker\n\t * refuses it (Jira's \"does not exist for the field 'project'\"), it reads as empty, and the refusal drops that list,\n\t * so the next read reports the miss. Jira Data Center's refusal counts only when it names the user it\n\t * authenticated: it answers a rejected token the same way, as the anonymous user. The miss waits on an account\n\t * lookup on a resource those lists name, which confirms the\n\t * connection's credential, and a failed lookup is reported as the read's failure instead; an account that lists no\n\t * resource at all has none to look up, and is judged on its lists alone.\n\t *\n\t * It is the discriminant: a `not-found` without it is a request that failed with a 404, 410 or 422.\n\t */\n\tselector?: ProviderWarningSelector;\n\t/**\n\t * The keys of the batch targets this warning concerns: the target a per-target failure was refused or failed\n\t * for, or every target of an integration call that failed as a whole. So a caller that sends several callers'\n\t * targets in one call can hand a failure only to the targets it belongs to.\n\t *\n\t * Its ABSENCE means the whole call. It is absent on every read that is not a batch read, and on a batch read's\n\t * refusal of the whole call before any target is read (a duplicate key, an invalid target, an unknown etag\n\t * include, no resolvable connection, the wrong surface), which concerns every target alike.\n\t *\n\t * It is not part of a warning's identity: two warnings that differ only here are one warning, for the union of\n\t * their targets (see {@link appendDedupedWarning}).\n\t */\n\ttargetKeys?: string[];\n}\n\nexport interface ProviderPageInfo {\n\t/**\n\t * The 1-based POSITION of the returned page. One convention across every paged read, page-number and\n\t * cursor paging alike, so a `getNextPageParam`-style consumer can key off it uniformly:\n\t * - the page number the provider itself reported, when it reports one (authoritative);\n\t * - else the position the caller addressed: the page encoded in a threaded page cursor, or the `page`\n\t * supplied alongside an opaque cursor (1 when the caller supplied no `page`);\n\t * - else, for a read that can be advanced by page number, the requested `page` — the provider either\n\t * honored it or the internal drain reached it (see the paging contract on `IntegrationManager`);\n\t * - else 1: a cursor-only read with no cursor threaded back ignores a requested page number and answers\n\t * with its first page, so echoing the request would mislabel page 1 as page N.\n\t *\n\t * A page past the provider's terminal cursor still reports the requested `page` with empty `items` — an\n\t * empty page N, never page N-1 relabeled.\n\t *\n\t * Continue a cursor-paged read from `cursor`, NOT from `currentPage + 1`: a cursor-only host can't be\n\t * addressed by number, so this is a position label and not always a resumable request parameter.\n\t */\n\tcurrentPage: number;\n\t/**\n\t * The page size IN EFFECT, which is not always the one requested — it is whichever of these the read can\n\t * actually vouch for:\n\t * - the size the provider reported for the page it served (numbered-page hosts), else\n\t * - the `itemsPerPage` that was applied to the request (repo-scoped reads, which forward it), else\n\t * - `items.length`, for a read that takes no page size at all: the account-wide PR/issue reads, `listRepos`\n\t * (cursor-only), and `listIssueTrackerIssuesPage`, whose `itemsPerPage` counts PROJECTS rather than issues.\n\t *\n\t * So it is a description of the page, not an echo of the request: don't derive a total from\n\t * `currentPage * itemsPerPage`, and don't read `items.length < itemsPerPage` as \"last page\" — `hasMore` /\n\t * `cursor` are the only continuation signals.\n\t */\n\titemsPerPage: number;\n\t/**\n\t * A COMPLETENESS assertion, not a mode flag: `true` only when a sweep drained every page of every target\n\t * without truncating or failing (`!truncated && !fetchFailed`). Set exclusively by `sweepPullRequests` /\n\t * `sweepClosedPullRequests` and absent on every paged read, so `allPages !== true` does NOT by itself mean\n\t * \"this was a single page\".\n\t *\n\t * This is the signal to gate on before caching a sweep as authoritative: unlike `truncated` it is `false`\n\t * for BOTH kinds of incompleteness, so it can't be misread as a benign cap.\n\t */\n\tallPages?: boolean;\n\t/**\n\t * True when the read stopped before it could confirm it had everything. Deliberately does NOT distinguish\n\t * WHY, and a consumer must not assume it means a provider ceiling — a sweep sets it from three different\n\t * origins:\n\t * - a provider-side ceiling or per-scope backstop propagated from below (e.g. GitHub Search's 1000-result\n\t * cap): the missing items are NOT reachable, so this really is \"complete to the provider's limit\";\n\t * - the sweep's OWN `maxPages` cap (default 100) with a usable cursor still in hand: the missing items ARE\n\t * reachable and were simply not fetched — raise `maxPages` to get them;\n\t * - the provider reporting another page but handing back no usable cursor: not reachable.\n\t *\n\t * Only the second is recoverable, and a sweep exposes no continuation cursor, so treating a truncated sweep\n\t * as a complete set silently drops reachable data in that case. Prefer `allPages` for the cache decision.\n\t */\n\ttruncated?: boolean;\n}\n\nexport interface ProviderResult<T> {\n\titems: T[];\n\twarnings: ProviderWarning[];\n\t/**\n\t * True when `items` is incomplete — distinguishes a failed or truncated flat read from a genuinely empty\n\t * result. Paged results additionally use `page.truncated` when a completed page could not confirm it\n\t * drained everything.\n\t */\n\tfetchFailed?: boolean;\n}\n\nexport interface ProviderPagedResult<T> extends ProviderResult<T> {\n\t/**\n\t * True when a request failed. Unlike `fetchFailed`, a collection read successfully that only left some of its items\n\t * unreadable doesn't set it; those items are named by their own warnings.\n\t */\n\trequestFailed?: boolean;\n\t/** True when the provider advertised continuation without a usable cursor or page position, or repeated one. */\n\tunexpectedGap?: boolean;\n\tpage: ProviderPageInfo;\n\thasMore: boolean;\n\t/**\n\t * Opaque cursor for cursor-based paging (GraphQL, per-repo/per-project cursors, etc.). For hosts that\n\t * require the previous page's cursor, consumers should pass this value back to the next `cursor` option.\n\t * A retry-only cursor may remain when `hasMore` is false: `hasMore` means automatic forward progress,\n\t * while explicitly reusing such a cursor retries failed work without creating an infinite paging loop.\n\t */\n\tcursor?: string;\n}\n\nexport interface ProviderSweepResult<T> extends ProviderResult<T> {\n\tpage: ProviderPageInfo;\n\thasMore: boolean;\n\t/**\n\t * Providers whose top-level read failed before returning any usable page. Partial per-scope failures stay\n\t * represented by `warnings` + `fetchFailed` and are intentionally excluded.\n\t */\n\tfailedProviderIds: IntegrationIds[];\n\t/**\n\t * Providers whose read started successfully but could not produce an authoritative complete slice (for\n\t * example, a later page failed, collection metadata reported a partial scope, or a paging backstop was hit).\n\t * Kept separate from\n\t * `failedProviderIds` so consumers can accept healthy sibling providers while preserving an older snapshot\n\t * for a provider whose returned slice has a gap.\n\t */\n\tincompleteProviderIds: IntegrationIds[];\n}\n\nexport interface ProviderBroadenResult<T> extends ProviderPagedResult<T> {\n\tbroadenedProviderIds: IntegrationIds[];\n\t/** Providers for which no requested org produced a usable issue result. */\n\tfailedProviderIds: IntegrationIds[];\n\t/** Providers with both a usable org result and at least one failed or truncated org slice. */\n\tincompleteProviderIds: IntegrationIds[];\n\tfanOutCount: number;\n}\n\nexport type RepositoryResolutionStatus =\n\t| 'resolved'\n\t| 'not-found'\n\t| 'unauthorized'\n\t| 'unsupported-provider'\n\t| 'invalid-remote-url'\n\t| 'host-mismatch'\n\t| 'undetermined';\n\nexport interface RepositoryIdentity {\n\tproviderId: IntegrationIds;\n\tdomain: string;\n\t/** The provider's canonical owner/namespace (follows renames), falling back to the parsed remote when omitted. */\n\towner: string;\n\t/** The provider's canonical repo name (follows renames), falling back to the parsed remote when omitted. */\n\tname: string;\n\tproject?: string;\n\t/** The original input remote URL, so the caller can key the resolution back to the remote it asked about. */\n\tremoteUrl: string;\n\t/**\n\t * True when the provider's canonical owner/name differ from what the input remote URL carried — i.e. the\n\t * repo was renamed/moved host-side and the local remote is stale. Case-insensitive, mirroring gkcli's\n\t * `EqualFold` compare so hosts that echo the input casing (e.g. Bitbucket Server/Azure) aren't flagged.\n\t */\n\trenamed: boolean;\n}\n\nexport interface RepositoryResolution {\n\tstatus: RepositoryResolutionStatus;\n\tidentity?: RepositoryIdentity;\n\twarning?: ProviderWarning;\n}\n\nexport interface ResolveRepositoryResult {\n\tresolution: RepositoryResolution;\n}\n\nconst maxProviderWarningMessageLength = 500;\n\n/** A caught error's message as a warning can carry it: an HTML page becomes its status, and prose is capped. */\nexport function providerWarningMessage(ex: unknown): string {\n\tconst raw = (ex instanceof Error ? ex.message : String(ex)).trim();\n\tconst carrier = ex as { status?: unknown; response?: { status?: unknown } } | null | undefined;\n\tconst status =\n\t\ttypeof carrier?.status === 'number'\n\t\t\t? carrier.status\n\t\t\t: typeof carrier?.response?.status === 'number'\n\t\t\t\t? carrier.response.status\n\t\t\t\t: undefined;\n\n\t// Gateways commonly answer API requests with a complete HTML error page. That payload is neither actionable\n\t// nor suitable for a warning DTO consumed by UIs and logs; preserve the status without leaking the document.\n\tif (/<(?:!doctype\\s+html|html|head|body)\\b/i.test(raw)) {\n\t\treturn status != null ? `Provider request failed with status ${status}.` : 'Provider request failed.';\n\t}\n\n\tif (!raw) {\n\t\treturn status != null ? `Provider request failed with status ${status}.` : 'Provider request failed.';\n\t}\n\treturn raw.length > maxProviderWarningMessageLength\n\t\t? `${raw.slice(0, maxProviderWarningMessageLength - 3)}...`\n\t\t: raw;\n}\n\n/**\n * Normalized org/workspace/group shape returned by the provider facade.\n * `name` is the provider identifier to pass to follow-up reads (a Jira project's key), while `org` is an optional\n * display label of the parent. Where that label can't address the parent, `orgId` is the parent's stable id to pass\n * as `org` instead (a Jira site's id, or a Data Center instance's domain).\n *\n * Jira site rows (`listOrgs`) are the exception: their `name` is the site's label, and a Jira read selects a site only\n * by its `id` (or a project row's `orgId`).\n */\nexport interface ProviderOrganization {\n\tid: string;\n\tproviderId: IntegrationIds;\n\tname: string;\n\torg?: string;\n\torgId?: string;\n\turl: string;\n}\n\n/**\n * A Linear Project (Linear's own `Project` entity, which can span several teams), as `listLinearProjects` lists it.\n * Distinct from the teams `listProjects` publishes for Linear: pass its `id` to `listIssueTrackerIssuesPage`'s\n * `linearProjects` to narrow an issue read to it.\n */\nexport interface ProviderLinearProjectShape {\n\tid: string;\n\tproviderId: IntegrationIds;\n\tname: string;\n\t/** Linear's project state, as Linear names it (e.g. `planned`, `started`, `paused`, `completed`, `canceled`). */\n\tstate: string;\n\turl: string;\n}\n\n/** Normalized repository shape returned by the provider facade. */\nexport interface ProviderRepositoryShape {\n\tid: string;\n\tnamespace: string;\n\tname: string;\n\t/** Azure DevOps project; `undefined` for hosts without a project layer. */\n\tproject?: string;\n\t/** Web (browser) URL, when the provider exposes it. */\n\turl?: string;\n\t/** HTTPS clone URL, when available. */\n\tcloneUrlHttps?: string;\n\t/** SSH clone URL, when available. */\n\tcloneUrlSsh?: string;\n\t/** Default branch name, when the provider reports it. */\n\tdefaultBranch?: string;\n}\n\n/**\n * Classifies a caught provider exception into a neutral {@link ProviderWarning}. Ordering matters:\n * `instanceof` is checked most-specific-first so a rate-limit isn't mislabeled as a generic error and\n * a 404 isn't mislabeled as auth.\n */\nexport function toProviderWarning(\n\tproviderId: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tex: unknown,\n): ProviderWarning {\n\tlet kind: ProviderWarning['kind'];\n\tif (ex instanceof AuthenticationError) {\n\t\tkind = 'auth';\n\t} else if (ex instanceof RequestRateLimitError) {\n\t\tkind = 'rate-limit';\n\t} else if (ex instanceof RequestNotFoundError) {\n\t\tkind = 'not-found';\n\t} else if (isUnsupportedSortError(ex)) {\n\t\tkind = 'unsupported';\n\t} else {\n\t\tkind = 'other';\n\t}\n\n\tconst cause = kind === 'other' ? getRequestFailureCause(ex) : undefined;\n\treturn {\n\t\tproviderId: providerId,\n\t\tdomain: domain,\n\t\tconnectionId: connectionId,\n\t\tmessage: providerWarningMessage(ex),\n\t\tkind: kind,\n\t\tisAuth: kind === 'auth',\n\t\t...(cause != null ? { cause: cause } : {}),\n\t};\n}\n\n/**\n * The cause a failed request's error names by itself, one that isn't the credential's (`unexpected-response` or\n * `unreachable`), for a failure classified as no more specific than that.\n */\nexport function getRequestFailureCause(ex: unknown): ProviderWarningCause | undefined {\n\tif (UnexpectedResponseError.is(ex)) return { reason: 'unexpected-response' };\n\tif (isProviderUnreachableError(ex)) return { reason: 'unreachable' };\n\treturn undefined;\n}\n\n/**\n * A scope's identity, as the stable string that keys it.\n *\n * The single definition of what \"the same scope\" means, shared by the failure and omission dedup keys in\n * `providerPaging.ts` and by {@link providerWarningKey} below, so a scope gaining a field is one edit rather\n * than three. `providerId` is included: the same repository ID under two providers is two scopes.\n *\n * The parameter is structural rather than the SDK's `CollectionScope` so this module keeps naming no\n * `@gitkraken/provider-apis` types (see the export block in `index.ts`) while still serving its SDK-facing\n * callers, which pass that type in unchanged.\n */\nexport function collectionScopeKey(scope: ProviderWarningOmissionScope | undefined): string {\n\treturn [scope?.providerId ?? '', scope?.resourceId ?? '', scope?.projectId ?? '', scope?.repositoryId ?? ''].join(\n\t\t' ',\n\t);\n}\n\n/**\n * The omission's contribution to a warning's identity — empty when there is none, so a warning without an\n * omission keeps deduping exactly as it did before the field existed.\n *\n * Two omissions of different kinds do produce different `message` values today, so message alone would still\n * separate them. That is incidental: the premise of `omission` is that consumers must not depend on prose\n * carrying the distinguishing fact, and this key must not either.\n */\nfunction providerWarningOmissionKey(omission: ProviderWarningOmission | undefined): string {\n\tif (omission == null) return '';\n\n\treturn [\n\t\tomission.kind,\n\t\tomission.recovery,\n\t\tomission.limit ?? '',\n\t\tomission.totalCount ?? '',\n\t\tcollectionScopeKey(omission.scope),\n\t].join(' ');\n}\n\n/**\n * Strips the omission from every warning in `warnings` when the read as a whole failed.\n *\n * An omission asserts the request SUCCEEDED, and a drain only learns it failed AFTER it may have emitted one:\n * an early page can report its own truncation and then a later page can die. Deciding per warning, at the\n * moment each is built, cannot see that future — so the aggregate is reconciled once, here, where\n * `fetchFailed` is final. Call it at the point a read returns its `fetchFailed`.\n *\n * Re-dedupes as it goes: the omission is part of a warning's identity, so two warnings that differed only\n * there become identical once it is gone, and the array's contract is that no two entries are equal.\n *\n * Mutates in place: the warning array is the one being returned, and callers accumulate into it across pages.\n */\nexport function reconcileOmissionsWithFailure(warnings: ProviderWarning[], fetchFailed: boolean): void {\n\tif (!fetchFailed || !warnings.some(w => w.omission != null)) return;\n\n\tconst reconciled: ProviderWarning[] = [];\n\tfor (const warning of warnings) {\n\t\tif (warning.omission == null) {\n\t\t\tappendDedupedWarning(reconciled, warning);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst { omission: _omission, ...rest } = warning;\n\t\tappendDedupedWarning(reconciled, rest);\n\t}\n\n\twarnings.splice(0, warnings.length, ...reconciled);\n}\n\n/**\n * A stable key for deduplicating warnings accumulated across drained pages / fan-out scopes.\n *\n * The scope and the cause are keyed for the reason the omission is (see {@link providerWarningOmissionKey}): today's\n * failure messages happen to spell them out, but two failures that differ in either must stay two warnings even if\n * they don't.\n *\n * `message` stays LAST. It is the only free-form segment — provider prose, spaces and all — so anything\n * appended after it could be impersonated by a message that happens to end in the same text.\n */\nfunction providerWarningKey(warning: ProviderWarning): string {\n\treturn [\n\t\twarning.providerId,\n\t\twarning.connectionId ?? '',\n\t\twarning.domain ?? '',\n\t\twarning.kind,\n\t\tcollectionScopeKey(warning.scope),\n\t\twarning.cause?.reason ?? '',\n\t\tproviderWarningOmissionKey(warning.omission),\n\t\twarning.selector != null ? JSON.stringify([warning.selector.kind, warning.selector.value]) : '',\n\t\twarning.message,\n\t].join(' ');\n}\n\n/**\n * Appends `warning` to `into` only when an equal warning (by provider/connection/domain/kind/message, plus the\n * structured scope, cause, omission and selector when present) is absent. An equal warning already there merges its\n * {@link ProviderWarning.targetKeys} with the new one's instead: the union of both, or none when either concerns\n * the whole call. Its {@link ProviderWarning.unreadableItems} merge too (see {@link mergeUnreadableItems}): two reads\n * can report the same text for different items. The existing warning is replaced, never mutated, since it may be\n * shared.\n */\nexport function appendDedupedWarning(into: ProviderWarning[], warning: ProviderWarning): void {\n\tconst key = providerWarningKey(warning);\n\tconst index = into.findIndex(existing => providerWarningKey(existing) === key);\n\tif (index === -1) {\n\t\tinto.push(warning);\n\t\treturn;\n\t}\n\n\tconst existing = into[index];\n\tconst targetKeys = mergeTargetKeys(existing.targetKeys, warning.targetKeys);\n\tconst unreadableItems =\n\t\texisting.unreadableItems != null && warning.unreadableItems != null\n\t\t\t? mergeUnreadableItems(existing.unreadableItems, warning.unreadableItems)\n\t\t\t: (existing.unreadableItems ?? warning.unreadableItems);\n\tif (targetKeys === existing.targetKeys && unreadableItems === existing.unreadableItems) return;\n\n\tconst { targetKeys: _, ...rest } = existing;\n\tinto[index] = {\n\t\t...rest,\n\t\t...(targetKeys != null ? { targetKeys: targetKeys } : {}),\n\t\t...(unreadableItems != null ? { unreadableItems: unreadableItems } : {}),\n\t};\n}\n\n/** The union of two warnings' target keys, or none when either concerns the whole call. `existing` when unchanged. */\nfunction mergeTargetKeys(existing: string[] | undefined, next: string[] | undefined): string[] | undefined {\n\t// Either concerns the whole call.\n\tif (existing == null || next == null) return undefined;\n\n\tconst added = next.filter(k => !existing.includes(k));\n\tif (!added.length) return existing;\n\n\treturn [...existing, ...new Set(added)];\n}\n\n/**\n * The items two reports of unreadable items left out together: the union of their keys, complete only when both are.\n * `existing` when `next` adds nothing to it.\n */\nexport function mergeUnreadableItems(\n\texisting: ProviderWarningUnreadableItems,\n\tnext: ProviderWarningUnreadableItems,\n): ProviderWarningUnreadableItems {\n\tconst known = new Set(existing.keys);\n\tconst added = next.keys.filter(k => !known.has(k));\n\tconst complete = existing.complete && next.complete;\n\tif (!added.length && complete === existing.complete) return existing;\n\n\treturn { keys: [...existing.keys, ...new Set(added)], complete: complete };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"results.js","names":[],"sources":["../../../../plus/integrations/src/results.ts"],"sourcesContent":["import { isUnsupportedSortError } from '@gitkraken/provider-apis';\nimport { AuthenticationError, RequestNotFoundError, RequestRateLimitError } from '@gitlens/git/errors.js';\nimport type { IssueSorting } from '@gitlens/git/models/issue.js';\nimport type { PullRequestSorting } from '@gitlens/git/models/pullRequest.js';\nimport type { IntegrationIds } from './constants.js';\nimport { isProviderUnreachableError } from './errors.js';\nimport { UnexpectedResponseError } from './providers/providerErrors.js';\n\nexport interface ConnectionStateChangeEvent {\n\tkey: string;\n\treason: 'connected' | 'disconnected';\n}\n\n/**\n * A per-provider, per-connection warning surfaced alongside (partial) read results. Consumers use\n * these to drive auth recovery, retry, or truncation messaging without the read itself throwing.\n *\n * `kind` is the programmatic discriminant (a rate-limit is retryable, a 404 is not, an unsupported capability\n * never succeeds on retry, an auth failure needs re-connection unless {@link ProviderWarning.scope} confines\n * it); `isAuth` is retained as a convenience mirror of `kind === 'auth'`.\n *\n * `'unsupported'`: the provider — or this integration of it — lacks the capability the request asked for: a\n * surface (issues on Bitbucket, pull requests on an issue tracker, a batch read, org/repo discovery,\n * current-account lookup, project-scoped or account-wide issue reads), or a filter, sort, state, search\n * criterion or option combination it cannot express. Retrying the same request never succeeds; a different\n * provider, scope or request may. NOT unsupported (these stay `'other'`): malformed or contradictory caller\n * input the caller fixes in the request itself (a duplicate batch key, invalid target numbers, an empty owner,\n * an unusable scope), incompleteness or omission warnings, and unclassified errors.\n *\n * `'unreadable-items'`: the provider returned items this read could not read (a Jira issue whose fields the SDK cannot\n * parse), so it left them out and read everything else in the warning's {@link ProviderWarning.scope}. It still fails\n * the read (`fetchFailed`), since those items are missing, but retrying returns the same gap. On the reads that publish\n * it (an issue tracker's project reads) it never hides a read that was cut short: a cut always comes with a warning of\n * another kind. {@link ProviderWarning.unreadableItems} names the items left out.\n */\nexport type ProviderWarningKind =\n\t| 'auth'\n\t| 'rate-limit'\n\t| 'not-found'\n\t| 'no-connection'\n\t| 'unsupported'\n\t| 'unreadable-items'\n\t| 'other';\n\n/** The items an `'unreadable-items'` warning left out: see {@link ProviderWarning.unreadableItems}. */\nexport interface ProviderWarningUnreadableItems {\n\t/**\n\t * The provider's identifiers of the items left out (a Jira issue's key), without duplicates, in reported order. Each\n\t * one is an item the provider reported as skipped, so an identifier present is one that was left out, and one absent\n\t * was read when {@link complete}.\n\t *\n\t * Both hold only for the spelling the provider returned, which is also how a published issue's id spells its key: `keys`\n\t * doesn't answer a lookup by another spelling, such as a different case or the key an issue had before it moved. A\n\t * consumer comparing a key from elsewhere (one parsed from a branch name, say) must normalize the case of both sides,\n\t * and can't take an issue under a key it once had as read.\n\t */\n\tkeys: string[];\n\t/**\n\t * Whether {@link keys} names every item left out. `false` when an item left out had no key to report, when the\n\t * provider's report of them was malformed (no list of keys, or more than keys in it), or when it said items were left\n\t * out and named none, so an identifier absent from `keys` may still be one of them.\n\t */\n\tcomplete: boolean;\n}\n\n/**\n * Why a read that SUCCEEDED still withheld results:\n * - `provider-limit`: the provider refuses to serve past a cap it enforces (GitHub search's 1,000-result\n * ceiling, Trello's `cards_limit`), so the excess is unreachable through that query.\n * - `recovery-budget`: the internal partitioned recovery stopped spending upstream requests before it ran\n * out of partitions to visit.\n * - `pagination-incomplete`: pages were left unread — a sub-scope of a multi-scope read that was not drained,\n * a drain that stopped at its own page backstop, or a provider that advertised another page and gave no way\n * to reach it. These have DIFFERENT remedies, so the kind alone does not say whether more can be fetched;\n * read {@link ProviderWarningOmission.recovery} for that.\n *\n * Mirrors the vocabulary the SDK reports. Declared here rather than imported so this module stays free of\n * `@gitkraken/provider-apis` types (see the export block in `index.ts`); `collectionMetadata.ts` holds the\n * compile-time link, so an SDK bump that adds a kind fails the build at that boundary.\n */\nexport type ProviderWarningOmissionKind = 'provider-limit' | 'recovery-budget' | 'pagination-incomplete';\n\n/**\n * How a consumer can recover omitted results. `page-budget` reruns with a larger budget; `narrow-scope`\n * requests a smaller server-side scope; `none` promises neither. Switch on the exact value because only\n * `page-budget` is a fetch-more action.\n */\nexport type ProviderWarningOmissionRecovery = 'none' | 'page-budget' | 'narrow-scope';\n\n/**\n * The part of a provider a warning is confined to: one resource (organization, workspace, site), project or\n * repository. All fields optional; see {@link ProviderWarning.scope} for when one is forwarded.\n */\nexport interface ProviderWarningScope {\n\t/**\n\t * The resource as the read addressed it: its id on most reads, its name on the few that address it by name\n\t * (Azure DevOps' repo-scoped reads, Bitbucket's workspace reads). Match it against both\n\t * `ProviderOrganization.id` and `name`, and a project's `orgId` where it has one.\n\t */\n\tresourceId?: string;\n\t/**\n\t * The project as the read addressed it. For a Jira project read, the key, on Jira Cloud and Jira Data Center alike\n\t * (the project's `name` in `listProjects`).\n\t */\n\tprojectId?: string;\n\trepositoryId?: string;\n}\n\n/**\n * The selector of an issue-tracker project read that matched nothing: see {@link ProviderWarning.selector}. `kind`\n * names which one missed, and `value` is that selector exactly as the caller passed it. `linearProject` is one of a\n * Linear read's `linearProjects`, an id the workspace's Projects no longer list.\n */\nexport interface ProviderWarningSelector {\n\tkind: 'org' | 'project' | 'linearProject';\n\tvalue: string;\n}\n\n/**\n * Why a provider request failed, when the failure itself says so:\n * - `unreachable`: a transport failure or server-side error prevented the provider from answering. The connection\n * still exists; this accompanies `kind: 'other'`, never an authentication warning.\n * - `oauth-app-not-allowed`: the organization does not let third-party OAuth apps in. On Azure DevOps this is the\n * organization policy \"Third-party application access via OAuth\", off by default for new organizations. An\n * organization admin enables it, or the user connects with a personal access token instead, which the policy\n * does not govern.\n * - `access-denied`: the account has no access to that organization or project (not a member, or lacking the\n * permission). Someone who administers it has to grant access.\n * - `conditional-access`: a Microsoft Entra Conditional Access policy blocked the request (Azure DevOps\n * `VS403463`). Its admin has to exempt the request, e.g. by location or device.\n * - `unexpected-response`: the provider answered, but not with data in the shape the read expects (a gateway's JSON\n * error on a 2xx, an empty object, `null`), so the read could not use it. Accompanies `kind: 'other'`, like\n * `unreachable`; a retry may succeed once the provider, or whatever answered in front of it, answers properly. A\n * page served instead of data keeps its own message, without this cause.\n *\n * None of these is healed by reconnecting, which is the point of naming them.\n */\nexport type ProviderWarningCauseReason =\n\t| 'oauth-app-not-allowed'\n\t| 'access-denied'\n\t| 'conditional-access'\n\t| 'unreachable'\n\t| 'unexpected-response';\n\n/** See {@link ProviderWarning.cause}. */\nexport interface ProviderWarningCause {\n\treason: ProviderWarningCauseReason;\n\t/** The provider's own error code, when it reports one (Azure DevOps `TF400813`, `VS403463`). */\n\tcode?: string;\n\t/** Where the setting behind the refusal is changed, when this layer can address it. */\n\tremedyUrl?: string;\n}\n\n/** Which repository / project / resource an omission is attributed to. All fields optional; a scope may name none. */\nexport interface ProviderWarningOmissionScope extends ProviderWarningScope {\n\tproviderId?: string;\n}\n\nexport interface ProviderWarningOmission {\n\tkind: ProviderWarningOmissionKind;\n\t/**\n\t * Whether anything would fetch the missing items, and what. Gate a \"load more\" affordance on this rather\n\t * than on `kind` — see {@link ProviderWarningOmissionRecovery}.\n\t */\n\trecovery: ProviderWarningOmissionRecovery;\n\t/**\n\t * The cap the provider enforces, when it reports one. NOT a result count for every kind: on\n\t * `recovery-budget` this is a REQUEST budget and must not be shown to a user as a number of results.\n\t */\n\tlimit?: number;\n\t/** Total matches the provider reported, when it reports one. Only GitHub's search cap does today. */\n\ttotalCount?: number;\n\t/**\n\t * The order the reachable window was selected under, as `field:direction`, when the omission is a result\n\t * ceiling.\n\t *\n\t * Present so a consumer can word its own \"N matched, showing the first M\" without assuming WHICH M: the\n\t * provider serves the top M under this key, so the sentence is only true if it names it. Absent for every\n\t * omission that isn't a ceiling, where no window was selected by an order.\n\t *\n\t * A union of both vocabularies rather than the issue one alone: `PullRequestSorting` happens to be a subset\n\t * today, so narrowing this to {@link IssueSorting} would still compile while describing only half the reads\n\t * that set it — and would break the day the PR search gains a key the issue search has no name for.\n\t */\n\tsort?: IssueSorting | PullRequestSorting;\n\t/** Which repository / project / resource was affected, when one is attributed. */\n\tscope?: ProviderWarningOmissionScope;\n}\n\nexport interface ProviderWarning {\n\tproviderId: IntegrationIds;\n\t/** Disambiguates connections on self-managed hosts (where one provider id spans multiple domains). */\n\tdomain?: string;\n\t/** The specific token/connection the read was pinned to, when a `connectionId` was supplied. */\n\tconnectionId?: string;\n\tmessage: string;\n\tkind: ProviderWarningKind;\n\t/** Convenience mirror of `kind === 'auth'`, scoped or not; read {@link scope} before prompting to reconnect. */\n\tisAuth: boolean;\n\t/**\n\t * Present when the failure was recorded against one sub-scope of the read (an organization, project or\n\t * repository) rather than against the connection. A scoped `auth` failure is that one scope refusing this\n\t * credential, e.g. an Azure DevOps organization that disallows third-party OAuth apps, one in another Entra\n\t * tenant, or a Conditional Access policy. Reconnecting cannot fix those, so this warning alone is no reason\n\t * to prompt for it.\n\t *\n\t * `kind` and `isAuth` are unaffected: a scoped 401 is still an authentication failure, and this field says\n\t * how far it reaches. It names at least one of its IDs, and is never set on an omission (which carries its\n\t * own {@link ProviderWarningOmission.scope}) or on a warning derived from a caught exception (see\n\t * {@link toProviderWarning}), even when that call targeted a single organization. The exception is a batch\n\t * read's refused target, which names its scope: the Azure DevOps organization and project, the repository on\n\t * other git hosts, or the tracker's resource.\n\t *\n\t * Its ABSENCE means account-wide or unattributed, so existing handling stays correct without it.\n\t *\n\t * A scoped `auth` failure also means the credential itself was accepted. A dead token can come back as nothing\n\t * but scoped refusals wherever a read reaches its scopes without an uncached request to the connection first:\n\t * discovery served from a per-token cache (Azure DevOps, Bitbucket, Jira Cloud) or an SDK fan-out across the\n\t * requested repositories (Bitbucket Data Center). So when a read's only auth failures are scoped, those providers\n\t * confirm the credential with one uncached check first, and a refused credential fails the whole read with an\n\t * unscoped `auth` warning. A batch read checks it only when every target was refused, since a target that\n\t * answered already proved it. A refusal the provider attributes to the credential itself, like Bitbucket's or Jira\n\t * Cloud's for a token missing the OAuth scopes a read needs, which a reconnect fixes, is published unscoped too,\n\t * once for the connection, while the scopes that answered keep their results.\n\t *\n\t * So the promise holds for a read that carries no unscoped `auth` warning of its own; one that does already asks\n\t * for a reconnect, and its other scoped refusals are not checked. Two exceptions stay scoped even then. A check\n\t * that could not complete or was denied (a network error, a throttle, a `403`, Bitbucket Data Center refusing a\n\t * credential it authenticated, or a Bitbucket Data Center project access token, whose user the check cannot\n\t * find) proves nothing, so the warnings are published unconfirmed, with no {@link cause}, and the next read\n\t * checks again. And a credential confirmed within the last minute is not probed again, so a revocation can take\n\t * up to a minute to surface.\n\t */\n\tscope?: ProviderWarningScope;\n\t/**\n\t * Why the provider request failed, when the failure itself says so: see {@link ProviderWarningCauseReason}. Switch on\n\t * `reason` to recommend a fix instead of a reconnect; `message` then carries the same explanation as prose.\n\t *\n\t * `unreachable` accompanies a transport or server failure with `kind: 'other'`, and `unexpected-response` a response\n\t * the read could not use, also with `kind: 'other'`. Credential refusal causes are set only on a scoped `auth`\n\t * warning whose credential was confirmed first (see {@link scope}), because the\n\t * refusals they name are indistinguishable from a dead credential until then: Azure DevOps answers a\n\t * third-party OAuth app its organization disallows exactly as it answers an expired token. Only Azure DevOps\n\t * reports one today. Its absence proves nothing about the cause.\n\t */\n\tcause?: ProviderWarningCause;\n\t/**\n\t * Present when this warning describes results the read could not return even though the request itself\n\t * SUCCEEDED — a provider-enforced cap, an exhausted recovery budget, a page budget, or a sub-scope the read\n\t * did not drain.\n\t *\n\t * Its presence is the signal: an omission is not a failure. Message it as incompleteness rather than as a\n\t * failed read, and do NOT derive that from `message`, which is English prose and subject to rewording.\n\t * `kind` stays `'other'` for these, so the failure discriminant keeps meaning exactly what it meant before\n\t * this field existed.\n\t *\n\t * Whether anything would fetch the rest is a SEPARATE question — read {@link ProviderWarningOmission.recovery},\n\t * not `kind`, and never assume a retry of the same request returns more.\n\t *\n\t * `limit` / `totalCount` / `scope` are forwarded only when they are reported; most omissions carry none of\n\t * the three, so render correctly without them — and see {@link ProviderWarningOmission.limit} before\n\t * showing that figure as a result count.\n\t *\n\t * Its ABSENCE proves nothing. It is never set on a warning derived from a caught exception (see\n\t * {@link toProviderWarning}) or from a structured scope failure — those are failures, and the field would\n\t * be a lie there. But it is also absent whenever incompleteness was reported without naming what was left\n\t * out, so treat a bare `kind: 'other'` warning as unclassified rather than as a proven failure.\n\t */\n\tomission?: ProviderWarningOmission;\n\t/**\n\t * Present exactly when {@link kind} is `'unreadable-items'`: which items the read left out because it could not read\n\t * them. A consumer deciding whether an identifier is absent from the provider, rather than merely unread, must find\n\t * it in no warning's `keys` and see `complete` on every one.\n\t */\n\tunreadableItems?: ProviderWarningUnreadableItems;\n\t/**\n\t * Present when an issue-tracker project read's `org` or `project` selector matched nothing the connection\n\t * discovered: the selection was removed, renamed, or is no longer visible to this connection. `kind` is\n\t * `'not-found'`, the page is empty and not `fetchFailed`, and it carries no cursor, since a retry finds the same.\n\t * A `linearProject` miss is diagnostic: the requested Project ids still filter the read, preserving issues in\n\t * other selected Projects. It therefore does not force an empty page or remove its continuation.\n\t *\n\t * Set only when discovery was complete. A read whose discovery failed or was truncated cannot tell a missing\n\t * selection from one it did not reach, so it keeps reporting that failure instead, without this field. A miss is\n\t * confirmed against the connection's lists as the provider holds them now, read without the cache, since a cached\n\t * one predates what was created since. A cached project list can still name a project deleted since: the tracker\n\t * refuses it (Jira's \"does not exist for the field 'project'\"), it reads as empty, and the refusal drops that list,\n\t * so the next read reports the miss. Jira Data Center's refusal counts only when it names the user it\n\t * authenticated: it answers a rejected token the same way, as the anonymous user. The miss waits on an account\n\t * lookup on a resource those lists name, which confirms the\n\t * connection's credential, and a failed lookup is reported as the read's failure instead; an account that lists no\n\t * resource at all has none to look up, and is judged on its lists alone.\n\t *\n\t * It is the discriminant: a `not-found` without it is a request that failed with a 404, 410 or 422.\n\t */\n\tselector?: ProviderWarningSelector;\n\t/**\n\t * The keys of the batch targets this warning concerns: the target a per-target failure was refused or failed\n\t * for, or every target of an integration call that failed as a whole. So a caller that sends several callers'\n\t * targets in one call can hand a failure only to the targets it belongs to.\n\t *\n\t * Its ABSENCE means the whole call. It is absent on every read that is not a batch read, and on a batch read's\n\t * refusal of the whole call before any target is read (a duplicate key, an invalid target, an unknown etag\n\t * include, no resolvable connection, the wrong surface), which concerns every target alike.\n\t *\n\t * It is not part of a warning's identity: two warnings that differ only here are one warning, for the union of\n\t * their targets (see {@link appendDedupedWarning}).\n\t */\n\ttargetKeys?: string[];\n}\n\nexport interface ProviderPageInfo {\n\t/**\n\t * The 1-based POSITION of the returned page. One convention across every paged read, page-number and\n\t * cursor paging alike, so a `getNextPageParam`-style consumer can key off it uniformly:\n\t * - the page number the provider itself reported, when it reports one (authoritative);\n\t * - else the position the caller addressed: the page encoded in a threaded page cursor, or the `page`\n\t * supplied alongside an opaque cursor (1 when the caller supplied no `page`);\n\t * - else, for a read that can be advanced by page number, the requested `page` — the provider either\n\t * honored it or the internal drain reached it (see the paging contract on `IntegrationManager`);\n\t * - else 1: a cursor-only read with no cursor threaded back ignores a requested page number and answers\n\t * with its first page, so echoing the request would mislabel page 1 as page N.\n\t *\n\t * A page past the provider's terminal cursor still reports the requested `page` with empty `items` — an\n\t * empty page N, never page N-1 relabeled.\n\t *\n\t * Continue a cursor-paged read from `cursor`, NOT from `currentPage + 1`: a cursor-only host can't be\n\t * addressed by number, so this is a position label and not always a resumable request parameter.\n\t */\n\tcurrentPage: number;\n\t/**\n\t * The page size IN EFFECT, which is not always the one requested — it is whichever of these the read can\n\t * actually vouch for:\n\t * - the size the provider reported for the page it served (numbered-page hosts), else\n\t * - the `itemsPerPage` that was applied to the request (repo-scoped reads, which forward it), else\n\t * - `items.length`, for a read that takes no page size at all: the account-wide PR/issue reads, `listRepos`\n\t * (cursor-only), and `listIssueTrackerIssuesPage`, whose `itemsPerPage` counts PROJECTS rather than issues.\n\t *\n\t * So it is a description of the page, not an echo of the request: don't derive a total from\n\t * `currentPage * itemsPerPage`, and don't read `items.length < itemsPerPage` as \"last page\" — `hasMore` /\n\t * `cursor` are the only continuation signals.\n\t */\n\titemsPerPage: number;\n\t/**\n\t * A COMPLETENESS assertion, not a mode flag: `true` only when a sweep drained every page of every target\n\t * without truncating or failing (`!truncated && !fetchFailed`). Set exclusively by `sweepPullRequests` /\n\t * `sweepClosedPullRequests` and absent on every paged read, so `allPages !== true` does NOT by itself mean\n\t * \"this was a single page\".\n\t *\n\t * This is the signal to gate on before caching a sweep as authoritative: unlike `truncated` it is `false`\n\t * for BOTH kinds of incompleteness, so it can't be misread as a benign cap.\n\t */\n\tallPages?: boolean;\n\t/**\n\t * True when the read stopped before it could confirm it had everything. Deliberately does NOT distinguish\n\t * WHY, and a consumer must not assume it means a provider ceiling — a sweep sets it from three different\n\t * origins:\n\t * - a provider-side ceiling or per-scope backstop propagated from below (e.g. GitHub Search's 1000-result\n\t * cap): the missing items are NOT reachable, so this really is \"complete to the provider's limit\";\n\t * - the sweep's OWN `maxPages` cap (default 100) with a usable cursor still in hand: the missing items ARE\n\t * reachable and were simply not fetched — raise `maxPages` to get them;\n\t * - the provider reporting another page but handing back no usable cursor: not reachable.\n\t *\n\t * Only the second is recoverable, and a sweep exposes no continuation cursor, so treating a truncated sweep\n\t * as a complete set silently drops reachable data in that case. Prefer `allPages` for the cache decision.\n\t */\n\ttruncated?: boolean;\n}\n\nexport interface ProviderResult<T> {\n\titems: T[];\n\twarnings: ProviderWarning[];\n\t/**\n\t * True when `items` is incomplete — distinguishes a failed or truncated flat read from a genuinely empty\n\t * result. Paged results additionally use `page.truncated` when a completed page could not confirm it\n\t * drained everything.\n\t */\n\tfetchFailed?: boolean;\n}\n\nexport interface ProviderPagedResult<T> extends ProviderResult<T> {\n\t/**\n\t * True when a request failed. Unlike `fetchFailed`, a collection read successfully that only left some of its items\n\t * unreadable doesn't set it; those items are named by their own warnings.\n\t */\n\trequestFailed?: boolean;\n\t/** True when the provider advertised continuation without a usable cursor or page position, or repeated one. */\n\tunexpectedGap?: boolean;\n\tpage: ProviderPageInfo;\n\thasMore: boolean;\n\t/**\n\t * Opaque cursor for cursor-based paging (GraphQL, per-repo/per-project cursors, etc.). For hosts that\n\t * require the previous page's cursor, consumers should pass this value back to the next `cursor` option.\n\t * A retry-only cursor may remain when `hasMore` is false: `hasMore` means automatic forward progress,\n\t * while explicitly reusing such a cursor retries failed work without creating an infinite paging loop.\n\t */\n\tcursor?: string;\n}\n\n/**\n * One sweep target, named the way {@link ProviderSweepTargetEvent} names it, so a consumer can join the two.\n * `domain` is the resolved self-managed host (always `undefined` for a cloud provider).\n */\nexport interface ProviderSweepTargetRef {\n\tproviderId: IntegrationIds;\n\tconnectionId: string | undefined;\n\tdomain: string | undefined;\n}\n\n/**\n * A sweep may carry several targets of one provider (one per connection), so the provider arrays below can't say\n * which connection failed: `failedTargets` and `incompleteTargets` are the authoritative attribution, and\n * `failedProviderIds`/`incompleteProviderIds` summarize them. With one target per provider the two agree exactly.\n */\nexport interface ProviderSweepResult<T> extends ProviderResult<T> {\n\tpage: ProviderPageInfo;\n\thasMore: boolean;\n\t/**\n\t * Providers whose top-level read failed before returning any usable page, for EVERY attributed target of the\n\t * provider. A provider with a failed target and a surviving sibling is in `incompleteProviderIds` instead.\n\t * Partial per-scope failures stay represented by `warnings` + `fetchFailed` and are intentionally excluded.\n\t */\n\tfailedProviderIds: IntegrationIds[];\n\t/**\n\t * Providers whose read started successfully but could not produce an authoritative complete slice (for\n\t * example, a later page failed, collection metadata reported a partial scope, or a paging backstop was hit),\n\t * or that have a failed target beside one that returned something.\n\t * Kept separate from\n\t * `failedProviderIds` so consumers can accept healthy sibling providers while preserving an older snapshot\n\t * for a provider whose returned slice has a gap.\n\t */\n\tincompleteProviderIds: IntegrationIds[];\n\t/** Targets whose top-level read failed before returning any usable page. A skipped target is in neither list. */\n\tfailedTargets: ProviderSweepTargetRef[];\n\t/** Targets that returned a slice with a gap: a later page failed, or pages remain unread. Never also in `failedTargets`. */\n\tincompleteTargets: ProviderSweepTargetRef[];\n}\n\nexport interface ProviderBroadenResult<T> extends ProviderPagedResult<T> {\n\tbroadenedProviderIds: IntegrationIds[];\n\t/** Providers for which no requested org produced a usable issue result. */\n\tfailedProviderIds: IntegrationIds[];\n\t/** Providers with both a usable org result and at least one failed or truncated org slice. */\n\tincompleteProviderIds: IntegrationIds[];\n\tfanOutCount: number;\n}\n\nexport type RepositoryResolutionStatus =\n\t| 'resolved'\n\t| 'not-found'\n\t| 'unauthorized'\n\t| 'unsupported-provider'\n\t| 'invalid-remote-url'\n\t| 'host-mismatch'\n\t| 'undetermined';\n\nexport interface RepositoryIdentity {\n\tproviderId: IntegrationIds;\n\tdomain: string;\n\t/** The provider's canonical owner/namespace (follows renames), falling back to the parsed remote when omitted. */\n\towner: string;\n\t/** The provider's canonical repo name (follows renames), falling back to the parsed remote when omitted. */\n\tname: string;\n\tproject?: string;\n\t/** The original input remote URL, so the caller can key the resolution back to the remote it asked about. */\n\tremoteUrl: string;\n\t/**\n\t * True when the provider's canonical owner/name differ from what the input remote URL carried — i.e. the\n\t * repo was renamed/moved host-side and the local remote is stale. Case-insensitive, mirroring gkcli's\n\t * `EqualFold` compare so hosts that echo the input casing (e.g. Bitbucket Server/Azure) aren't flagged.\n\t */\n\trenamed: boolean;\n}\n\nexport interface RepositoryResolution {\n\tstatus: RepositoryResolutionStatus;\n\tidentity?: RepositoryIdentity;\n\twarning?: ProviderWarning;\n}\n\nexport interface ResolveRepositoryResult {\n\tresolution: RepositoryResolution;\n}\n\nconst maxProviderWarningMessageLength = 500;\n\n/** A caught error's message as a warning can carry it: an HTML page becomes its status, and prose is capped. */\nexport function providerWarningMessage(ex: unknown): string {\n\tconst raw = (ex instanceof Error ? ex.message : String(ex)).trim();\n\tconst carrier = ex as { status?: unknown; response?: { status?: unknown } } | null | undefined;\n\tconst status =\n\t\ttypeof carrier?.status === 'number'\n\t\t\t? carrier.status\n\t\t\t: typeof carrier?.response?.status === 'number'\n\t\t\t\t? carrier.response.status\n\t\t\t\t: undefined;\n\n\t// Gateways commonly answer API requests with a complete HTML error page. That payload is neither actionable\n\t// nor suitable for a warning DTO consumed by UIs and logs; preserve the status without leaking the document.\n\tif (/<(?:!doctype\\s+html|html|head|body)\\b/i.test(raw)) {\n\t\treturn status != null ? `Provider request failed with status ${status}.` : 'Provider request failed.';\n\t}\n\n\tif (!raw) {\n\t\treturn status != null ? `Provider request failed with status ${status}.` : 'Provider request failed.';\n\t}\n\treturn raw.length > maxProviderWarningMessageLength\n\t\t? `${raw.slice(0, maxProviderWarningMessageLength - 3)}...`\n\t\t: raw;\n}\n\n/**\n * Normalized org/workspace/group shape returned by the provider facade.\n * `name` is the provider identifier to pass to follow-up reads (a Jira project's key), while `org` is an optional\n * display label of the parent. Where that label can't address the parent, `orgId` is the parent's stable id to pass\n * as `org` instead (a Jira site's id, or a Data Center instance's domain).\n *\n * Jira site rows (`listOrgs`) are the exception: their `name` is the site's label, and a Jira read selects a site only\n * by its `id` (or a project row's `orgId`).\n */\nexport interface ProviderOrganization {\n\tid: string;\n\tproviderId: IntegrationIds;\n\tname: string;\n\torg?: string;\n\torgId?: string;\n\turl: string;\n}\n\n/**\n * A Linear Project (Linear's own `Project` entity, which can span several teams), as `listLinearProjects` lists it.\n * Distinct from the teams `listProjects` publishes for Linear: pass its `id` to `listIssueTrackerIssuesPage`'s\n * `linearProjects` to narrow an issue read to it.\n */\nexport interface ProviderLinearProjectShape {\n\tid: string;\n\tproviderId: IntegrationIds;\n\tname: string;\n\t/** Linear's project state, as Linear names it (e.g. `planned`, `started`, `paused`, `completed`, `canceled`). */\n\tstate: string;\n\turl: string;\n}\n\n/** Normalized repository shape returned by the provider facade. */\nexport interface ProviderRepositoryShape {\n\tid: string;\n\tnamespace: string;\n\tname: string;\n\t/** Azure DevOps project; `undefined` for hosts without a project layer. */\n\tproject?: string;\n\t/** Web (browser) URL, when the provider exposes it. */\n\turl?: string;\n\t/** HTTPS clone URL, when available. */\n\tcloneUrlHttps?: string;\n\t/** SSH clone URL, when available. */\n\tcloneUrlSsh?: string;\n\t/** Default branch name, when the provider reports it. */\n\tdefaultBranch?: string;\n}\n\n/**\n * Classifies a caught provider exception into a neutral {@link ProviderWarning}. Ordering matters:\n * `instanceof` is checked most-specific-first so a rate-limit isn't mislabeled as a generic error and\n * a 404 isn't mislabeled as auth.\n */\nexport function toProviderWarning(\n\tproviderId: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tex: unknown,\n): ProviderWarning {\n\tlet kind: ProviderWarning['kind'];\n\tif (ex instanceof AuthenticationError) {\n\t\tkind = 'auth';\n\t} else if (ex instanceof RequestRateLimitError) {\n\t\tkind = 'rate-limit';\n\t} else if (ex instanceof RequestNotFoundError) {\n\t\tkind = 'not-found';\n\t} else if (isUnsupportedSortError(ex)) {\n\t\tkind = 'unsupported';\n\t} else {\n\t\tkind = 'other';\n\t}\n\n\tconst cause = kind === 'other' ? getRequestFailureCause(ex) : undefined;\n\treturn {\n\t\tproviderId: providerId,\n\t\tdomain: domain,\n\t\tconnectionId: connectionId,\n\t\tmessage: providerWarningMessage(ex),\n\t\tkind: kind,\n\t\tisAuth: kind === 'auth',\n\t\t...(cause != null ? { cause: cause } : {}),\n\t};\n}\n\n/**\n * The cause a failed request's error names by itself, one that isn't the credential's (`unexpected-response` or\n * `unreachable`), for a failure classified as no more specific than that.\n */\nexport function getRequestFailureCause(ex: unknown): ProviderWarningCause | undefined {\n\tif (UnexpectedResponseError.is(ex)) return { reason: 'unexpected-response' };\n\tif (isProviderUnreachableError(ex)) return { reason: 'unreachable' };\n\treturn undefined;\n}\n\n/**\n * A scope's identity, as the stable string that keys it.\n *\n * The single definition of what \"the same scope\" means, shared by the failure and omission dedup keys in\n * `providerPaging.ts` and by {@link providerWarningKey} below, so a scope gaining a field is one edit rather\n * than three. `providerId` is included: the same repository ID under two providers is two scopes.\n *\n * The parameter is structural rather than the SDK's `CollectionScope` so this module keeps naming no\n * `@gitkraken/provider-apis` types (see the export block in `index.ts`) while still serving its SDK-facing\n * callers, which pass that type in unchanged.\n */\nexport function collectionScopeKey(scope: ProviderWarningOmissionScope | undefined): string {\n\treturn [scope?.providerId ?? '', scope?.resourceId ?? '', scope?.projectId ?? '', scope?.repositoryId ?? ''].join(\n\t\t' ',\n\t);\n}\n\n/**\n * The omission's contribution to a warning's identity — empty when there is none, so a warning without an\n * omission keeps deduping exactly as it did before the field existed.\n *\n * Two omissions of different kinds do produce different `message` values today, so message alone would still\n * separate them. That is incidental: the premise of `omission` is that consumers must not depend on prose\n * carrying the distinguishing fact, and this key must not either.\n */\nfunction providerWarningOmissionKey(omission: ProviderWarningOmission | undefined): string {\n\tif (omission == null) return '';\n\n\treturn [\n\t\tomission.kind,\n\t\tomission.recovery,\n\t\tomission.limit ?? '',\n\t\tomission.totalCount ?? '',\n\t\tcollectionScopeKey(omission.scope),\n\t].join(' ');\n}\n\n/**\n * Strips the omission from every warning in `warnings` when the read as a whole failed.\n *\n * An omission asserts the request SUCCEEDED, and a drain only learns it failed AFTER it may have emitted one:\n * an early page can report its own truncation and then a later page can die. Deciding per warning, at the\n * moment each is built, cannot see that future — so the aggregate is reconciled once, here, where\n * `fetchFailed` is final. Call it at the point a read returns its `fetchFailed`.\n *\n * Re-dedupes as it goes: the omission is part of a warning's identity, so two warnings that differed only\n * there become identical once it is gone, and the array's contract is that no two entries are equal.\n *\n * Mutates in place: the warning array is the one being returned, and callers accumulate into it across pages.\n */\nexport function reconcileOmissionsWithFailure(warnings: ProviderWarning[], fetchFailed: boolean): void {\n\tif (!fetchFailed || !warnings.some(w => w.omission != null)) return;\n\n\tconst reconciled: ProviderWarning[] = [];\n\tfor (const warning of warnings) {\n\t\tif (warning.omission == null) {\n\t\t\tappendDedupedWarning(reconciled, warning);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst { omission: _omission, ...rest } = warning;\n\t\tappendDedupedWarning(reconciled, rest);\n\t}\n\n\twarnings.splice(0, warnings.length, ...reconciled);\n}\n\n/**\n * A stable key for deduplicating warnings accumulated across drained pages / fan-out scopes.\n *\n * The scope and the cause are keyed for the reason the omission is (see {@link providerWarningOmissionKey}): today's\n * failure messages happen to spell them out, but two failures that differ in either must stay two warnings even if\n * they don't.\n *\n * `message` stays LAST. It is the only free-form segment — provider prose, spaces and all — so anything\n * appended after it could be impersonated by a message that happens to end in the same text.\n */\nfunction providerWarningKey(warning: ProviderWarning): string {\n\treturn [\n\t\twarning.providerId,\n\t\twarning.connectionId ?? '',\n\t\twarning.domain ?? '',\n\t\twarning.kind,\n\t\tcollectionScopeKey(warning.scope),\n\t\twarning.cause?.reason ?? '',\n\t\tproviderWarningOmissionKey(warning.omission),\n\t\twarning.selector != null ? JSON.stringify([warning.selector.kind, warning.selector.value]) : '',\n\t\twarning.message,\n\t].join(' ');\n}\n\n/**\n * Appends `warning` to `into` only when an equal warning (by provider/connection/domain/kind/message, plus the\n * structured scope, cause, omission and selector when present) is absent. An equal warning already there merges its\n * {@link ProviderWarning.targetKeys} with the new one's instead: the union of both, or none when either concerns\n * the whole call. Its {@link ProviderWarning.unreadableItems} merge too (see {@link mergeUnreadableItems}): two reads\n * can report the same text for different items. The existing warning is replaced, never mutated, since it may be\n * shared.\n */\nexport function appendDedupedWarning(into: ProviderWarning[], warning: ProviderWarning): void {\n\tconst key = providerWarningKey(warning);\n\tconst index = into.findIndex(existing => providerWarningKey(existing) === key);\n\tif (index === -1) {\n\t\tinto.push(warning);\n\t\treturn;\n\t}\n\n\tconst existing = into[index];\n\tconst targetKeys = mergeTargetKeys(existing.targetKeys, warning.targetKeys);\n\tconst unreadableItems =\n\t\texisting.unreadableItems != null && warning.unreadableItems != null\n\t\t\t? mergeUnreadableItems(existing.unreadableItems, warning.unreadableItems)\n\t\t\t: (existing.unreadableItems ?? warning.unreadableItems);\n\tif (targetKeys === existing.targetKeys && unreadableItems === existing.unreadableItems) return;\n\n\tconst { targetKeys: _, ...rest } = existing;\n\tinto[index] = {\n\t\t...rest,\n\t\t...(targetKeys != null ? { targetKeys: targetKeys } : {}),\n\t\t...(unreadableItems != null ? { unreadableItems: unreadableItems } : {}),\n\t};\n}\n\n/** The union of two warnings' target keys, or none when either concerns the whole call. `existing` when unchanged. */\nfunction mergeTargetKeys(existing: string[] | undefined, next: string[] | undefined): string[] | undefined {\n\t// Either concerns the whole call.\n\tif (existing == null || next == null) return undefined;\n\n\tconst added = next.filter(k => !existing.includes(k));\n\tif (!added.length) return existing;\n\n\treturn [...existing, ...new Set(added)];\n}\n\n/**\n * The items two reports of unreadable items left out together: the union of their keys, complete only when both are.\n * `existing` when `next` adds nothing to it.\n */\nexport function mergeUnreadableItems(\n\texisting: ProviderWarningUnreadableItems,\n\tnext: ProviderWarningUnreadableItems,\n): ProviderWarningUnreadableItems {\n\tconst known = new Set(existing.keys);\n\tconst added = next.keys.filter(k => !known.has(k));\n\tconst complete = existing.complete && next.complete;\n\tif (!added.length && complete === existing.complete) return existing;\n\n\treturn { keys: [...existing.keys, ...new Set(added)], complete: complete };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { NotificationsContext } from "./context.js";
|
|
2
|
+
import { NotificationSettings, NotificationsPage } from "./models.js";
|
|
3
|
+
//#region ../plus/notifications/src/api.d.ts
|
|
4
|
+
export declare const notificationsPageSize = 50;
|
|
5
|
+
/** A thin client over the `notifications-service-v2` v1 API. */
|
|
6
|
+
export declare class NotificationsApi {
|
|
7
|
+
private readonly context;
|
|
8
|
+
constructor(context: NotificationsContext);
|
|
9
|
+
/** Loads one page (1-based), newest first. Invalid notifications are dropped (and logged). */
|
|
10
|
+
list(page: number, pageSize?: number): Promise<NotificationsPage>;
|
|
11
|
+
markRead(ids: readonly string[]): Promise<void>;
|
|
12
|
+
markUnread(ids: readonly string[]): Promise<void>;
|
|
13
|
+
delete(ids: readonly string[]): Promise<void>;
|
|
14
|
+
getSettings(): Promise<NotificationSettings>;
|
|
15
|
+
updateSettings(settings: NotificationSettings): Promise<void>;
|
|
16
|
+
/** Sends the request and returns the unwrapped `data` envelope (or `undefined` for an empty body). */
|
|
17
|
+
private request;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
//# sourceMappingURL=api.d.ts.map
|