@gitkraken/core-gitlens 0.11.1 → 0.13.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 +84 -1
- package/dist/git/cache.d.ts +84 -29
- package/dist/git/cache.js +188 -54
- package/dist/git/cache.js.map +1 -1
- package/dist/git/features.d.ts +1 -1
- package/dist/git/features.js +4 -0
- package/dist/git/features.js.map +1 -1
- package/dist/git/models/branch.d.ts +15 -2
- package/dist/git/models/branch.js +27 -5
- package/dist/git/models/branch.js.map +1 -1
- package/dist/git/models/issue.d.ts +3 -1
- package/dist/git/models/issue.js.map +1 -1
- package/dist/git/models/status.js +1 -1
- package/dist/git/models/worktree.d.ts +4 -1
- package/dist/git/models/worktree.js +4 -2
- package/dist/git/models/worktree.js.map +1 -1
- package/dist/git/providers/branches.d.ts +73 -8
- package/dist/git/providers/pausedOperations.d.ts +6 -0
- package/dist/git/providers/provider.d.ts +34 -2
- package/dist/git/providers/status.d.ts +8 -2
- package/dist/git/remotes/azure-devops.js +61 -53
- package/dist/git/remotes/azure-devops.js.map +1 -1
- package/dist/git/service.d.ts +8 -5
- package/dist/git/service.js +16 -8
- package/dist/git/service.js.map +1 -1
- package/dist/git/utils/branch.utils.d.ts +8 -1
- package/dist/git/utils/branch.utils.js +30 -1
- package/dist/git/utils/branch.utils.js.map +1 -1
- package/dist/git/utils/issue.utils.d.ts +3 -1
- package/dist/git/utils/issue.utils.js +5 -1
- package/dist/git/utils/issue.utils.js.map +1 -1
- package/dist/git/watching/changeEvent.d.ts +13 -2
- package/dist/git/watching/changeEvent.js +17 -3
- package/dist/git/watching/changeEvent.js.map +1 -1
- package/dist/git/watching/gitDirPoller.d.ts +111 -0
- package/dist/git/watching/gitDirPoller.js +266 -0
- package/dist/git/watching/gitDirPoller.js.map +1 -0
- package/dist/git/watching/provider.d.ts +16 -0
- package/dist/git/watching/watchGroup.d.ts +40 -4
- package/dist/git/watching/watchGroup.js +143 -17
- package/dist/git/watching/watchGroup.js.map +1 -1
- package/dist/git/watching/watchService.d.ts +70 -1
- package/dist/git/watching/watchService.js +158 -13
- package/dist/git/watching/watchService.js.map +1 -1
- package/dist/git/watching/watchSession.d.ts +44 -0
- package/dist/git/watching/watchSession.js +91 -6
- package/dist/git/watching/watchSession.js.map +1 -1
- package/dist/git/watching/watcherPatterns.d.ts +21 -0
- package/dist/git/watching/watcherPatterns.js +67 -1
- package/dist/git/watching/watcherPatterns.js.map +1 -1
- package/dist/git/watching/worktreeSummary.d.ts +34 -7
- package/dist/git/watching/worktreeSummary.js +39 -12
- package/dist/git/watching/worktreeSummary.js.map +1 -1
- package/dist/git-cli/cliGitProvider.d.ts +34 -2
- package/dist/git-cli/cliGitProvider.js +119 -17
- package/dist/git-cli/cliGitProvider.js.map +1 -1
- package/dist/git-cli/exec/commandClassifier.d.ts +15 -0
- package/dist/git-cli/exec/commandClassifier.js +48 -2
- package/dist/git-cli/exec/commandClassifier.js.map +1 -1
- package/dist/git-cli/exec/git.d.ts +15 -3
- package/dist/git-cli/exec/git.js +38 -17
- package/dist/git-cli/exec/git.js.map +1 -1
- package/dist/git-cli/exec/gitQueue.d.ts +2 -0
- package/dist/git-cli/exec/gitQueue.js +2 -1
- package/dist/git-cli/exec/gitQueue.js.map +1 -1
- package/dist/git-cli/exec/indexLock.d.ts +38 -0
- package/dist/git-cli/exec/indexLock.js +150 -0
- package/dist/git-cli/exec/indexLock.js.map +1 -0
- package/dist/git-cli/parsers/worktreeParser.js +42 -2
- package/dist/git-cli/parsers/worktreeParser.js.map +1 -1
- package/dist/git-cli/providers/branches.d.ts +109 -10
- package/dist/git-cli/providers/branches.js +624 -131
- package/dist/git-cli/providers/branches.js.map +1 -1
- package/dist/git-cli/providers/config.d.ts +9 -5
- package/dist/git-cli/providers/config.js +60 -27
- package/dist/git-cli/providers/config.js.map +1 -1
- package/dist/git-cli/providers/operations.d.ts +17 -0
- package/dist/git-cli/providers/operations.js +153 -96
- package/dist/git-cli/providers/operations.js.map +1 -1
- package/dist/git-cli/providers/pausedOperations.d.ts +8 -0
- package/dist/git-cli/providers/pausedOperations.js +60 -40
- package/dist/git-cli/providers/pausedOperations.js.map +1 -1
- package/dist/git-cli/providers/refs.js +11 -8
- package/dist/git-cli/providers/refs.js.map +1 -1
- package/dist/git-cli/providers/remotes.js +19 -10
- package/dist/git-cli/providers/remotes.js.map +1 -1
- package/dist/git-cli/providers/staging.js +40 -15
- package/dist/git-cli/providers/staging.js.map +1 -1
- package/dist/git-cli/providers/stash.d.ts +2 -0
- package/dist/git-cli/providers/stash.js +33 -18
- package/dist/git-cli/providers/stash.js.map +1 -1
- package/dist/git-cli/providers/status.d.ts +3 -1
- package/dist/git-cli/providers/status.js +38 -20
- package/dist/git-cli/providers/status.js.map +1 -1
- package/dist/git-cli/providers/tags.js +6 -4
- package/dist/git-cli/providers/tags.js.map +1 -1
- package/dist/git-cli/providers/worktrees.d.ts +1 -0
- package/dist/git-cli/providers/worktrees.js +60 -31
- package/dist/git-cli/providers/worktrees.js.map +1 -1
- package/dist/git-cli/service.d.ts +6 -0
- package/dist/git-cli/service.js +5 -2
- package/dist/git-cli/service.js.map +1 -1
- package/dist/ipc/discovery.js +1 -1
- package/dist/plus/agents/providers/claudeCodeTranscript.js +6 -6
- package/dist/plus/agents/providers/claudeCodeTranscript.js.map +1 -1
- package/dist/plus/ai/providers/openAICompatibleProviderBase.js +1 -1
- package/dist/plus/git-github/providers/github/status.d.ts +3 -1
- package/dist/plus/git-github/providers/github/status.js +1 -1
- package/dist/plus/git-github/providers/github/status.js.map +1 -1
- package/dist/plus/integrations/collectionMetadata.d.ts +13 -2
- package/dist/plus/integrations/collectionMetadata.js +14 -2
- package/dist/plus/integrations/collectionMetadata.js.map +1 -1
- package/dist/plus/integrations/index.d.ts +2 -2
- package/dist/plus/integrations/index.js.map +1 -1
- package/dist/plus/integrations/integrationService.d.ts +1 -0
- package/dist/plus/integrations/integrationService.js +1 -1
- package/dist/plus/integrations/integrationService.js.map +1 -1
- package/dist/plus/integrations/manager.d.ts +1 -0
- package/dist/plus/integrations/models/gitHostIntegration.d.ts +41 -5
- package/dist/plus/integrations/models/gitHostIntegration.js +109 -30
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
- package/dist/plus/integrations/models/integration.js +1 -1
- package/dist/plus/integrations/models/issueReads.d.ts +17 -4
- package/dist/plus/integrations/providers/apiClients.d.ts +2 -0
- package/dist/plus/integrations/providers/apiClients.js +7 -1
- package/dist/plus/integrations/providers/apiClients.js.map +1 -1
- package/dist/plus/integrations/providers/azure/azure.d.ts +5 -4
- package/dist/plus/integrations/providers/azure/azure.js +38 -77
- package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
- package/dist/plus/integrations/providers/azure/azure.utils.js +20 -17
- package/dist/plus/integrations/providers/azure/azure.utils.js.map +1 -1
- package/dist/plus/integrations/providers/azure/models.d.ts +27 -10
- package/dist/plus/integrations/providers/azure/models.js +40 -20
- package/dist/plus/integrations/providers/azure/models.js.map +1 -1
- package/dist/plus/integrations/providers/azure/workItemStateCategories.d.ts +23 -0
- package/dist/plus/integrations/providers/azure/workItemStateCategories.js +84 -0
- package/dist/plus/integrations/providers/azure/workItemStateCategories.js.map +1 -0
- package/dist/plus/integrations/providers/azureDevOps.d.ts +44 -0
- package/dist/plus/integrations/providers/azureDevOps.js +113 -22
- package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOpsServer.d.ts +45 -1
- package/dist/plus/integrations/providers/azureDevOpsServer.js +121 -6
- package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -1
- package/dist/plus/integrations/providers/issueSorts.d.ts +0 -1
- package/dist/plus/integrations/providers/jira-server.js +29 -19
- package/dist/plus/integrations/providers/jira-server.js.map +1 -1
- package/dist/plus/integrations/providers/jira.d.ts +5 -2
- package/dist/plus/integrations/providers/jira.js +63 -44
- package/dist/plus/integrations/providers/jira.js.map +1 -1
- package/dist/plus/integrations/providers/jiraIssueByKey.d.ts +3 -4
- package/dist/plus/integrations/providers/jiraIssueByKey.js +3 -4
- package/dist/plus/integrations/providers/jiraIssueByKey.js.map +1 -1
- package/dist/plus/integrations/providers/linearIssuesEtag.d.ts +5 -5
- package/dist/plus/integrations/providers/linearIssuesEtag.js +6 -6
- package/dist/plus/integrations/providers/linearIssuesEtag.js.map +1 -1
- package/dist/plus/integrations/providers/models.d.ts +12 -3
- package/dist/plus/integrations/providers/models.js +16 -9
- package/dist/plus/integrations/providers/models.js.map +1 -1
- package/dist/plus/integrations/providers/providersApi.d.ts +21 -33
- package/dist/plus/integrations/providers/providersApi.js +159 -49
- package/dist/plus/integrations/providers/providersApi.js.map +1 -1
- package/dist/plus/integrations/providers/utils/jiraIssueDrains.d.ts +49 -15
- package/dist/plus/integrations/providers/utils/jiraIssueDrains.js +132 -10
- package/dist/plus/integrations/providers/utils/jiraIssueDrains.js.map +1 -1
- package/dist/plus/integrations/providers/utils/providerPaging.js +8 -2
- package/dist/plus/integrations/providers/utils/providerPaging.js.map +1 -1
- package/dist/plus/integrations/reads/cursors.js +1 -1
- package/dist/plus/integrations/reads/drains.d.ts +0 -1
- package/dist/plus/integrations/reads/filters.d.ts +3 -3
- package/dist/plus/integrations/reads/filters.js.map +1 -1
- package/dist/plus/integrations/reads/hierarchy.js +2 -2
- package/dist/plus/integrations/reads/hierarchy.js.map +1 -1
- package/dist/plus/integrations/reads/hierarchy.utils.d.ts +13 -0
- package/dist/plus/integrations/reads/hierarchy.utils.js +26 -1
- package/dist/plus/integrations/reads/hierarchy.utils.js.map +1 -1
- package/dist/plus/integrations/reads/issueBatch.d.ts +2 -1
- package/dist/plus/integrations/reads/issueBatch.js +1 -1
- package/dist/plus/integrations/reads/issueBatch.js.map +1 -1
- package/dist/plus/integrations/reads/issueTracker.js +27 -18
- package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
- package/dist/plus/integrations/reads/issues.d.ts +2 -1
- package/dist/plus/integrations/reads/issues.js +19 -4
- package/dist/plus/integrations/reads/issues.js.map +1 -1
- package/dist/plus/integrations/reads/pullRequestBatch.js +1 -1
- package/dist/plus/integrations/reads/resolveRepository.js +12 -3
- package/dist/plus/integrations/reads/resolveRepository.js.map +1 -1
- package/dist/plus/integrations/reads/warnings.d.ts +11 -4
- package/dist/plus/integrations/reads/warnings.js +15 -5
- package/dist/plus/integrations/reads/warnings.js.map +1 -1
- package/dist/plus/integrations/results.d.ts +48 -4
- package/dist/plus/integrations/results.js +33 -13
- package/dist/plus/integrations/results.js.map +1 -1
- package/dist/plus/integrations/utils/integration.utils.d.ts +6 -1
- package/dist/plus/integrations/utils/integration.utils.js +8 -1
- package/dist/plus/integrations/utils/integration.utils.js.map +1 -1
- package/dist/utils/env/node/exec.js +2 -2
- package/dist/utils/promiseCache.d.ts +10 -0
- package/dist/utils/promiseCache.js +17 -2
- package/dist/utils/promiseCache.js.map +1 -1
- package/docs/integrations.md +93 -31
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integration.utils.js","names":[],"sources":["../../../../../plus/integrations/src/utils/integration.utils.ts"],"sourcesContent":["import type { RemoteProvider, RemoteProviderId } from '@gitlens/git/models/remoteProvider.js';\nimport type {\n\tCloudGitSelfManagedHostIntegrationIds,\n\tCloudSelfManagedHostIntegrationIds,\n\tIntegrationIds,\n\tIssuesHostIntegrationIds,\n\tSelfManagedHostIntegrationIds,\n} from '../constants.js';\nimport {\n\tGitCloudHostIntegrationId,\n\tGitSelfManagedHostIntegrationId,\n\tIssuesCloudHostIntegrationId,\n\tIssuesSelfManagedHostIntegrationId,\n} from '../constants.js';\nimport type { GitHostIntegration } from '../models/gitHostIntegration.js';\nimport type { Integration, IntegrationConnectedKey } from '../models/integration.js';\n// These domain checks come from `providers/models.ts` (already imported here) rather than the individual\n// provider modules: those read `providersMetadata[...]` synchronously at module init, so importing from them\n// while `providers/models.ts` is still loading triggers a TDZ on `providersMetadata`. Importing the shared\n// predicates keeps one definition of each regex — duplicating them let a fix land in only one place.\nimport { isAzureCloudDomain, isBitbucketCloudDomain, isGitHubDotCom, isGitLabDotCom } from '../providers/models.js';\nimport { baseUrlFromDomain } from './domain.utils.js';\n\nconst selfHostedIntegrationIds: GitSelfManagedHostIntegrationId[] = [\n\tGitSelfManagedHostIntegrationId.CloudGitHubEnterprise,\n\tGitSelfManagedHostIntegrationId.CloudGitLabSelfHosted,\n\tGitSelfManagedHostIntegrationId.BitbucketServer,\n\tGitSelfManagedHostIntegrationId.AzureDevOpsServer,\n] as const;\n\nconst selfHostedIssuesIntegrationIds: IssuesSelfManagedHostIntegrationId[] = [\n\tIssuesSelfManagedHostIntegrationId.JiraServer,\n] as const;\n\nexport const supportedIntegrationIds: IntegrationIds[] = [\n\tGitCloudHostIntegrationId.GitHub,\n\tGitCloudHostIntegrationId.GitLab,\n\tGitCloudHostIntegrationId.Bitbucket,\n\tGitCloudHostIntegrationId.AzureDevOps,\n\tIssuesCloudHostIntegrationId.Jira,\n\tIssuesCloudHostIntegrationId.Trello,\n\t...selfHostedIntegrationIds,\n\t...selfHostedIssuesIntegrationIds,\n] as const;\n\nexport function convertRemoteProviderIdToIntegrationId(\n\tremoteProviderId: RemoteProviderId,\n): GitCloudHostIntegrationId | GitSelfManagedHostIntegrationId | undefined {\n\tswitch (remoteProviderId) {\n\t\tcase 'azure-devops':\n\t\t\treturn GitCloudHostIntegrationId.AzureDevOps;\n\t\tcase 'bitbucket':\n\t\t\treturn GitCloudHostIntegrationId.Bitbucket;\n\t\tcase 'github':\n\t\t\treturn GitCloudHostIntegrationId.GitHub;\n\t\tcase 'gitlab':\n\t\t\treturn GitCloudHostIntegrationId.GitLab;\n\t\tcase 'bitbucket-server':\n\t\t\treturn GitSelfManagedHostIntegrationId.BitbucketServer;\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\nexport function getIntegrationConnectedKey<T extends IntegrationIds>(\n\tid: T,\n\tdomain?: string,\n): IntegrationConnectedKey<T> {\n\tif (isSelfManagedHostIntegrationId(id)) {\n\t\tif (!domain) {\n\t\t\tthrow new Error(`Domain is required for self-managed integration ID: ${id}`);\n\t\t}\n\t\treturn `connected:${id}:${domain}` as IntegrationConnectedKey<T>;\n\t}\n\n\treturn `connected:${id}` as IntegrationConnectedKey<T>;\n}\n\nexport function getIntegrationIdForRemote(\n\tprovider: RemoteProvider | undefined,\n): GitCloudHostIntegrationId | GitSelfManagedHostIntegrationId | undefined {\n\tswitch (provider?.id) {\n\t\tcase 'azure-devops':\n\t\t\tif (isAzureCloudDomain(provider.domain)) {\n\t\t\t\treturn GitCloudHostIntegrationId.AzureDevOps;\n\t\t\t}\n\t\t\treturn provider.custom ? undefined : GitSelfManagedHostIntegrationId.AzureDevOpsServer;\n\t\tcase 'bitbucket':\n\t\tcase 'bitbucket-server':\n\t\t\tif (isBitbucketCloudDomain(provider.domain)) {\n\t\t\t\treturn GitCloudHostIntegrationId.Bitbucket;\n\t\t\t}\n\t\t\treturn GitSelfManagedHostIntegrationId.BitbucketServer;\n\t\tcase 'github':\n\t\t\tif (provider.domain != null && !isGitHubDotCom(provider.domain)) {\n\t\t\t\treturn GitSelfManagedHostIntegrationId.CloudGitHubEnterprise;\n\t\t\t}\n\t\t\treturn GitCloudHostIntegrationId.GitHub;\n\t\tcase 'gitlab':\n\t\t\tif (provider.domain != null && !isGitLabDotCom(provider.domain)) {\n\t\t\t\treturn GitSelfManagedHostIntegrationId.CloudGitLabSelfHosted;\n\t\t\t}\n\t\t\treturn GitCloudHostIntegrationId.GitLab;\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\nexport function isCloudGitSelfManagedHostIntegrationId(\n\tid: IntegrationIds,\n): id is CloudGitSelfManagedHostIntegrationIds {\n\tswitch (id) {\n\t\tcase GitSelfManagedHostIntegrationId.CloudGitHubEnterprise:\n\t\tcase GitSelfManagedHostIntegrationId.CloudGitLabSelfHosted:\n\t\tcase GitSelfManagedHostIntegrationId.BitbucketServer:\n\t\tcase GitSelfManagedHostIntegrationId.AzureDevOpsServer:\n\t\t\treturn true;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\n/**\n * Whether this id is a self-managed host whose token comes from the GK cloud backend — the check behind\n * \"expand this provider to one instance per configured host\". Distinct from\n * {@link isCloudGitSelfManagedHostIntegrationId}, which stays git-only because its callers go on to ask the\n * instance for repositories.\n */\nexport function isCloudSelfManagedHostIntegrationId(id: IntegrationIds): id is CloudSelfManagedHostIntegrationIds {\n\treturn isCloudGitSelfManagedHostIntegrationId(id) || isIssuesSelfManagedHostIntegrationId(id);\n}\n\n/**\n * Whether a provider's cloud token uses `expiresIn: 0` to mean \"never expires\" (rather than \"already\n * expired\"). GitHub and the cloud self-managed hosts always return 0 for their non-expiring tokens; Trello\n * is issued with `expiration: never` (identity-service), so its cloud token comes back as 0 too. Callers\n * must map 0 → a far-future expiry for these, or the session is immediately treated as expired.\n *\n * Every backend-managed self-managed host qualifies, not just the git ones: a self-hosted tracker's token is\n * a PAT the backend stores as-is and cannot refresh, so reading 0 as \"already expired\" would re-resolve the\n * session on every single read.\n */\nexport function isNonExpiringZeroTokenIntegrationId(id: IntegrationIds): boolean {\n\treturn (\n\t\tid === GitCloudHostIntegrationId.GitHub ||\n\t\tid === IssuesCloudHostIntegrationId.Trello ||\n\t\tisCloudSelfManagedHostIntegrationId(id)\n\t);\n}\n\nexport function isGitHostIntegration(integration: Integration): integration is GitHostIntegration {\n\treturn integration.type === 'git';\n}\n\nexport function isGitCloudHostIntegrationId(id: IntegrationIds): id is GitCloudHostIntegrationId {\n\tswitch (id) {\n\t\tcase GitCloudHostIntegrationId.GitHub:\n\t\tcase GitCloudHostIntegrationId.GitLab:\n\t\tcase GitCloudHostIntegrationId.Bitbucket:\n\t\tcase GitCloudHostIntegrationId.AzureDevOps:\n\t\t\treturn true;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\nexport function isGitSelfManagedHostIntegrationId(id: IntegrationIds): id is GitSelfManagedHostIntegrationId {\n\treturn selfHostedIntegrationIds.includes(id as GitSelfManagedHostIntegrationId);\n}\n\nexport function isIssuesSelfManagedHostIntegrationId(id: IntegrationIds): id is IssuesSelfManagedHostIntegrationId {\n\treturn selfHostedIssuesIntegrationIds.includes(id as IssuesSelfManagedHostIntegrationId);\n}\n\n/**\n * Whether this id addresses ONE customer-run host, whatever it hosts — the predicate the domain-keyed\n * machinery runs on: the integration cache key, the secret key, the `connected:${id}:${domain}` flag, domain\n * normalization, per-host primary selection, and multi-account reconcile.\n *\n * Deliberately wider than {@link isGitSelfManagedHostIntegrationId}, which is reserved for reads that go on to\n * ask for repositories, pull requests, or a git remote. Widening THOSE to a tracker would attribute a Jira\n * host's remotes to Jira; narrowing THESE to git hosts would key a tracker's connections under an empty\n * domain, collapsing every host of it into one bucket.\n */\nexport function isSelfManagedHostIntegrationId(id: IntegrationIds): id is SelfManagedHostIntegrationIds {\n\treturn isGitSelfManagedHostIntegrationId(id) || isIssuesSelfManagedHostIntegrationId(id);\n}\n\n/**\n * Whether this id belongs to a dedicated issue tracker (resource → project) rather than a git host.\n *\n * Decided from the id alone, so a read can refuse a mismatched surface — a repo/PR read asked of Jira, or an\n * issue-tracker project read asked of GitHub — before resolving a connection. The instance-level\n * {@link isIssuesIntegration} answers the same question once an integration is in hand.\n */\nexport function isIssuesHostIntegrationId(id: IntegrationIds): id is IssuesHostIntegrationIds {\n\tswitch (id) {\n\t\tcase IssuesCloudHostIntegrationId.Jira:\n\t\tcase IssuesCloudHostIntegrationId.Linear:\n\t\tcase IssuesCloudHostIntegrationId.Trello:\n\t\tcase IssuesSelfManagedHostIntegrationId.JiraServer:\n\t\t\treturn true;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\n/**\n * Whether a read targeted only by an explicit self-managed `domain` (no `connectionId`) must treat a\n * session-less core result as a broken target instead of an empty account. Without this, a self-managed host\n * addressed only by domain — the manual-token/external-auth case `domain` exists to cover — returns an empty\n * success with no warning and no `fetchFailed`, indistinguishable from \"this host has nothing\".\n */\nexport function warnOnMissingSessionForDomain(id: IntegrationIds, domain: string | undefined): boolean {\n\treturn domain != null && isSelfManagedHostIntegrationId(id);\n}\n\n/** Maps an integration id to the git-remote provider type used by the remote-URL matcher. */\nexport function remoteProviderTypeForIntegration(id: IntegrationIds): RemoteProviderId | undefined {\n\tswitch (id) {\n\t\tcase GitCloudHostIntegrationId.GitHub:\n\t\tcase GitSelfManagedHostIntegrationId.CloudGitHubEnterprise:\n\t\t\treturn 'github';\n\t\tcase GitCloudHostIntegrationId.GitLab:\n\t\tcase GitSelfManagedHostIntegrationId.CloudGitLabSelfHosted:\n\t\t\treturn 'gitlab';\n\t\tcase GitCloudHostIntegrationId.Bitbucket:\n\t\t\treturn 'bitbucket';\n\t\tcase GitSelfManagedHostIntegrationId.BitbucketServer:\n\t\t\treturn 'bitbucket-server';\n\t\tcase GitCloudHostIntegrationId.AzureDevOps:\n\t\tcase GitSelfManagedHostIntegrationId.AzureDevOpsServer:\n\t\t\treturn 'azure-devops';\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\n/** Normalizes a host remote-config `type` string (e.g. `'GitHub'`) to a git-remote provider type. */\nexport function remoteProviderTypeForConfig(type: string): RemoteProviderId | undefined {\n\tswitch (type.toLowerCase()) {\n\t\tcase 'github':\n\t\t\treturn 'github';\n\t\tcase 'gitlab':\n\t\t\treturn 'gitlab';\n\t\tcase 'bitbucket':\n\t\t\treturn 'bitbucket';\n\t\tcase 'bitbucket-server':\n\t\tcase 'bitbucketserver':\n\t\t\treturn 'bitbucket-server';\n\t\tcase 'azuredevops':\n\t\tcase 'azure-devops':\n\t\t\treturn 'azure-devops';\n\t\tcase 'gitea':\n\t\t\treturn 'gitea';\n\t\tcase 'gerrit':\n\t\t\treturn 'gerrit';\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\nexport function getSelfManagedBaseUrl(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tprotocol?: string,\n): string | undefined {\n\tconst baseUrl = baseUrlFromDomain(domain, protocol);\n\tif (baseUrl == null) return undefined;\n\n\tswitch (id) {\n\t\tcase GitSelfManagedHostIntegrationId.CloudGitHubEnterprise:\n\t\tcase GitSelfManagedHostIntegrationId.CloudGitLabSelfHosted:\n\t\t\treturn baseUrl.replace(/\\/api(?:\\/v\\d+)?$/, '');\n\t\tcase GitSelfManagedHostIntegrationId.BitbucketServer:\n\t\t\treturn baseUrl.replace(/\\/rest\\/api\\/1\\.0$/, '');\n\t\tdefault:\n\t\t\treturn baseUrl;\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"integration.utils.js","names":[],"sources":["../../../../../plus/integrations/src/utils/integration.utils.ts"],"sourcesContent":["import type { RemoteProvider, RemoteProviderId } from '@gitlens/git/models/remoteProvider.js';\nimport type {\n\tCloudGitSelfManagedHostIntegrationIds,\n\tCloudSelfManagedHostIntegrationIds,\n\tIntegrationIds,\n\tIssuesHostIntegrationIds,\n\tSelfManagedHostIntegrationIds,\n} from '../constants.js';\nimport {\n\tGitCloudHostIntegrationId,\n\tGitSelfManagedHostIntegrationId,\n\tIssuesCloudHostIntegrationId,\n\tIssuesSelfManagedHostIntegrationId,\n} from '../constants.js';\nimport type { GitHostIntegration } from '../models/gitHostIntegration.js';\nimport type { Integration, IntegrationConnectedKey } from '../models/integration.js';\n// These domain checks come from `providers/models.ts` (already imported here) rather than the individual\n// provider modules: those read `providersMetadata[...]` synchronously at module init, so importing from them\n// while `providers/models.ts` is still loading triggers a TDZ on `providersMetadata`. Importing the shared\n// predicates keeps one definition of each regex — duplicating them let a fix land in only one place.\nimport { isAzureCloudDomain, isBitbucketCloudDomain, isGitHubDotCom, isGitLabDotCom } from '../providers/models.js';\nimport { baseUrlFromDomain } from './domain.utils.js';\n\nconst selfHostedIntegrationIds: GitSelfManagedHostIntegrationId[] = [\n\tGitSelfManagedHostIntegrationId.CloudGitHubEnterprise,\n\tGitSelfManagedHostIntegrationId.CloudGitLabSelfHosted,\n\tGitSelfManagedHostIntegrationId.BitbucketServer,\n\tGitSelfManagedHostIntegrationId.AzureDevOpsServer,\n] as const;\n\nconst selfHostedIssuesIntegrationIds: IssuesSelfManagedHostIntegrationId[] = [\n\tIssuesSelfManagedHostIntegrationId.JiraServer,\n] as const;\n\nexport const supportedIntegrationIds: IntegrationIds[] = [\n\tGitCloudHostIntegrationId.GitHub,\n\tGitCloudHostIntegrationId.GitLab,\n\tGitCloudHostIntegrationId.Bitbucket,\n\tGitCloudHostIntegrationId.AzureDevOps,\n\tIssuesCloudHostIntegrationId.Jira,\n\tIssuesCloudHostIntegrationId.Trello,\n\t...selfHostedIntegrationIds,\n\t...selfHostedIssuesIntegrationIds,\n] as const;\n\nexport function convertRemoteProviderIdToIntegrationId(\n\tremoteProviderId: RemoteProviderId,\n): GitCloudHostIntegrationId | GitSelfManagedHostIntegrationId | undefined {\n\tswitch (remoteProviderId) {\n\t\tcase 'azure-devops':\n\t\t\treturn GitCloudHostIntegrationId.AzureDevOps;\n\t\tcase 'bitbucket':\n\t\t\treturn GitCloudHostIntegrationId.Bitbucket;\n\t\tcase 'github':\n\t\t\treturn GitCloudHostIntegrationId.GitHub;\n\t\tcase 'gitlab':\n\t\t\treturn GitCloudHostIntegrationId.GitLab;\n\t\tcase 'bitbucket-server':\n\t\t\treturn GitSelfManagedHostIntegrationId.BitbucketServer;\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\nexport function getIntegrationConnectedKey<T extends IntegrationIds>(\n\tid: T,\n\tdomain?: string,\n): IntegrationConnectedKey<T> {\n\tif (isSelfManagedHostIntegrationId(id)) {\n\t\tif (!domain) {\n\t\t\tthrow new Error(`Domain is required for self-managed integration ID: ${id}`);\n\t\t}\n\t\treturn `connected:${id}:${domain}` as IntegrationConnectedKey<T>;\n\t}\n\n\treturn `connected:${id}` as IntegrationConnectedKey<T>;\n}\n\nexport function getIntegrationIdForRemote(\n\tprovider: RemoteProvider | undefined,\n): GitCloudHostIntegrationId | GitSelfManagedHostIntegrationId | undefined {\n\tswitch (provider?.id) {\n\t\tcase 'azure-devops':\n\t\t\tif (isAzureCloudDomain(provider.domain)) {\n\t\t\t\treturn GitCloudHostIntegrationId.AzureDevOps;\n\t\t\t}\n\t\t\treturn provider.custom ? undefined : GitSelfManagedHostIntegrationId.AzureDevOpsServer;\n\t\tcase 'bitbucket':\n\t\tcase 'bitbucket-server':\n\t\t\tif (isBitbucketCloudDomain(provider.domain)) {\n\t\t\t\treturn GitCloudHostIntegrationId.Bitbucket;\n\t\t\t}\n\t\t\treturn GitSelfManagedHostIntegrationId.BitbucketServer;\n\t\tcase 'github':\n\t\t\tif (provider.domain != null && !isGitHubDotCom(provider.domain)) {\n\t\t\t\treturn GitSelfManagedHostIntegrationId.CloudGitHubEnterprise;\n\t\t\t}\n\t\t\treturn GitCloudHostIntegrationId.GitHub;\n\t\tcase 'gitlab':\n\t\t\tif (provider.domain != null && !isGitLabDotCom(provider.domain)) {\n\t\t\t\treturn GitSelfManagedHostIntegrationId.CloudGitLabSelfHosted;\n\t\t\t}\n\t\t\treturn GitCloudHostIntegrationId.GitLab;\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\nexport function isCloudGitSelfManagedHostIntegrationId(\n\tid: IntegrationIds,\n): id is CloudGitSelfManagedHostIntegrationIds {\n\tswitch (id) {\n\t\tcase GitSelfManagedHostIntegrationId.CloudGitHubEnterprise:\n\t\tcase GitSelfManagedHostIntegrationId.CloudGitLabSelfHosted:\n\t\tcase GitSelfManagedHostIntegrationId.BitbucketServer:\n\t\tcase GitSelfManagedHostIntegrationId.AzureDevOpsServer:\n\t\t\treturn true;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\n/**\n * Whether this id is a self-managed host whose token comes from the GK cloud backend — the check behind\n * \"expand this provider to one instance per configured host\". Distinct from\n * {@link isCloudGitSelfManagedHostIntegrationId}, which stays git-only because its callers go on to ask the\n * instance for repositories.\n */\nexport function isCloudSelfManagedHostIntegrationId(id: IntegrationIds): id is CloudSelfManagedHostIntegrationIds {\n\treturn isCloudGitSelfManagedHostIntegrationId(id) || isIssuesSelfManagedHostIntegrationId(id);\n}\n\n/**\n * Whether a provider's cloud token uses `expiresIn: 0` to mean \"never expires\" (rather than \"already\n * expired\"). GitHub and the cloud self-managed hosts always return 0 for their non-expiring tokens; Trello\n * is issued with `expiration: never` (identity-service), so its cloud token comes back as 0 too. Callers\n * must map 0 → a far-future expiry for these, or the session is immediately treated as expired.\n *\n * Every backend-managed self-managed host qualifies, not just the git ones: a self-hosted tracker's token is\n * a PAT the backend stores as-is and cannot refresh, so reading 0 as \"already expired\" would re-resolve the\n * session on every single read.\n */\nexport function isNonExpiringZeroTokenIntegrationId(id: IntegrationIds): boolean {\n\treturn (\n\t\tid === GitCloudHostIntegrationId.GitHub ||\n\t\tid === IssuesCloudHostIntegrationId.Trello ||\n\t\tisCloudSelfManagedHostIntegrationId(id)\n\t);\n}\n\nexport function isGitHostIntegration(integration: Integration): integration is GitHostIntegration {\n\treturn integration.type === 'git';\n}\n\nexport function isGitCloudHostIntegrationId(id: IntegrationIds): id is GitCloudHostIntegrationId {\n\tswitch (id) {\n\t\tcase GitCloudHostIntegrationId.GitHub:\n\t\tcase GitCloudHostIntegrationId.GitLab:\n\t\tcase GitCloudHostIntegrationId.Bitbucket:\n\t\tcase GitCloudHostIntegrationId.AzureDevOps:\n\t\t\treturn true;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\nexport function isGitSelfManagedHostIntegrationId(id: IntegrationIds): id is GitSelfManagedHostIntegrationId {\n\treturn selfHostedIntegrationIds.includes(id as GitSelfManagedHostIntegrationId);\n}\n\nexport function isIssuesSelfManagedHostIntegrationId(id: IntegrationIds): id is IssuesSelfManagedHostIntegrationId {\n\treturn selfHostedIssuesIntegrationIds.includes(id as IssuesSelfManagedHostIntegrationId);\n}\n\n/**\n * Whether this id addresses ONE customer-run host, whatever it hosts — the predicate the domain-keyed\n * machinery runs on: the integration cache key, the secret key, the `connected:${id}:${domain}` flag, domain\n * normalization, per-host primary selection, and multi-account reconcile.\n *\n * Deliberately wider than {@link isGitSelfManagedHostIntegrationId}, which is reserved for reads that go on to\n * ask for repositories, pull requests, or a git remote. Widening THOSE to a tracker would attribute a Jira\n * host's remotes to Jira; narrowing THESE to git hosts would key a tracker's connections under an empty\n * domain, collapsing every host of it into one bucket.\n */\nexport function isSelfManagedHostIntegrationId(id: IntegrationIds): id is SelfManagedHostIntegrationIds {\n\treturn isGitSelfManagedHostIntegrationId(id) || isIssuesSelfManagedHostIntegrationId(id);\n}\n\n/**\n * Whether this id is Jira, Cloud or Data Center: the two share their project and site identity rules and the SDK's\n * status mapper. Takes any string, as a provider reference's `id` is one.\n */\nexport function isJiraIntegrationId(\n\tid: string,\n): id is IssuesCloudHostIntegrationId.Jira | IssuesSelfManagedHostIntegrationId.JiraServer {\n\treturn id === IssuesCloudHostIntegrationId.Jira || id === IssuesSelfManagedHostIntegrationId.JiraServer;\n}\n\n/**\n * Whether this id belongs to a dedicated issue tracker (resource → project) rather than a git host.\n *\n * Decided from the id alone, so a read can refuse a mismatched surface — a repo/PR read asked of Jira, or an\n * issue-tracker project read asked of GitHub — before resolving a connection. The instance-level\n * {@link isIssuesIntegration} answers the same question once an integration is in hand.\n */\nexport function isIssuesHostIntegrationId(id: IntegrationIds): id is IssuesHostIntegrationIds {\n\tswitch (id) {\n\t\tcase IssuesCloudHostIntegrationId.Jira:\n\t\tcase IssuesCloudHostIntegrationId.Linear:\n\t\tcase IssuesCloudHostIntegrationId.Trello:\n\t\tcase IssuesSelfManagedHostIntegrationId.JiraServer:\n\t\t\treturn true;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\n/**\n * Whether a read targeted only by an explicit self-managed `domain` (no `connectionId`) must treat a\n * session-less core result as a broken target instead of an empty account. Without this, a self-managed host\n * addressed only by domain — the manual-token/external-auth case `domain` exists to cover — returns an empty\n * success with no warning and no `fetchFailed`, indistinguishable from \"this host has nothing\".\n */\nexport function warnOnMissingSessionForDomain(id: IntegrationIds, domain: string | undefined): boolean {\n\treturn domain != null && isSelfManagedHostIntegrationId(id);\n}\n\n/** Maps an integration id to the git-remote provider type used by the remote-URL matcher. */\nexport function remoteProviderTypeForIntegration(id: IntegrationIds): RemoteProviderId | undefined {\n\tswitch (id) {\n\t\tcase GitCloudHostIntegrationId.GitHub:\n\t\tcase GitSelfManagedHostIntegrationId.CloudGitHubEnterprise:\n\t\t\treturn 'github';\n\t\tcase GitCloudHostIntegrationId.GitLab:\n\t\tcase GitSelfManagedHostIntegrationId.CloudGitLabSelfHosted:\n\t\t\treturn 'gitlab';\n\t\tcase GitCloudHostIntegrationId.Bitbucket:\n\t\t\treturn 'bitbucket';\n\t\tcase GitSelfManagedHostIntegrationId.BitbucketServer:\n\t\t\treturn 'bitbucket-server';\n\t\tcase GitCloudHostIntegrationId.AzureDevOps:\n\t\tcase GitSelfManagedHostIntegrationId.AzureDevOpsServer:\n\t\t\treturn 'azure-devops';\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\n/** Normalizes a host remote-config `type` string (e.g. `'GitHub'`) to a git-remote provider type. */\nexport function remoteProviderTypeForConfig(type: string): RemoteProviderId | undefined {\n\tswitch (type.toLowerCase()) {\n\t\tcase 'github':\n\t\t\treturn 'github';\n\t\tcase 'gitlab':\n\t\t\treturn 'gitlab';\n\t\tcase 'bitbucket':\n\t\t\treturn 'bitbucket';\n\t\tcase 'bitbucket-server':\n\t\tcase 'bitbucketserver':\n\t\t\treturn 'bitbucket-server';\n\t\tcase 'azuredevops':\n\t\tcase 'azure-devops':\n\t\t\treturn 'azure-devops';\n\t\tcase 'gitea':\n\t\t\treturn 'gitea';\n\t\tcase 'gerrit':\n\t\t\treturn 'gerrit';\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\nexport function getSelfManagedBaseUrl(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tprotocol?: string,\n): string | undefined {\n\tconst baseUrl = baseUrlFromDomain(domain, protocol);\n\tif (baseUrl == null) return undefined;\n\n\tswitch (id) {\n\t\tcase GitSelfManagedHostIntegrationId.CloudGitHubEnterprise:\n\t\tcase GitSelfManagedHostIntegrationId.CloudGitLabSelfHosted:\n\t\t\treturn baseUrl.replace(/\\/api(?:\\/v\\d+)?$/, '');\n\t\tcase GitSelfManagedHostIntegrationId.BitbucketServer:\n\t\t\treturn baseUrl.replace(/\\/rest\\/api\\/1\\.0$/, '');\n\t\tdefault:\n\t\t\treturn baseUrl;\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -2,10 +2,10 @@ import { getScopedLogger, maybeStartScopedLogger } from "../../logger.scoped.js"
|
|
|
2
2
|
import { normalizePath } from "../../path.js";
|
|
3
3
|
import { isWindows } from "./platform.js";
|
|
4
4
|
import { join } from "path";
|
|
5
|
-
import { exec, execFile, spawn } from "child_process";
|
|
6
|
-
import { access, constants } from "fs";
|
|
7
5
|
import { stat } from "fs/promises";
|
|
8
6
|
import * as process from "process";
|
|
7
|
+
import { exec, execFile, spawn } from "child_process";
|
|
8
|
+
import { access, constants } from "fs";
|
|
9
9
|
//#region ../utils/src/env/node/exec.ts
|
|
10
10
|
var RunError = class extends Error {
|
|
11
11
|
cmd;
|
|
@@ -130,6 +130,8 @@ export declare class PromiseMap<K, V> {
|
|
|
130
130
|
private readonly cache;
|
|
131
131
|
private readonly aborts;
|
|
132
132
|
private readonly controllers;
|
|
133
|
+
/** Each cached promise's value once it fulfills, for {@link getSettled} */
|
|
134
|
+
private readonly settled;
|
|
133
135
|
get [Symbol.toStringTag](): string;
|
|
134
136
|
/**
|
|
135
137
|
* Gets a promise from the cache or creates a new one using the factory function.
|
|
@@ -165,6 +167,14 @@ export declare class PromiseMap<K, V> {
|
|
|
165
167
|
* Sets a promise in the cache. The promise will be automatically removed if it fails.
|
|
166
168
|
*/
|
|
167
169
|
set(key: K, promise: Promise<V>): this;
|
|
170
|
+
/**
|
|
171
|
+
* The value `key`'s cached promise fulfilled with, or `undefined` while it is pending (or nothing is cached), so a
|
|
172
|
+
* caller can tell a settled entry from a read still in flight without joining it.
|
|
173
|
+
*/
|
|
174
|
+
getSettled(key: K): {
|
|
175
|
+
value: V;
|
|
176
|
+
} | undefined;
|
|
177
|
+
private track;
|
|
168
178
|
/**
|
|
169
179
|
* Checks if a key exists in the cache.
|
|
170
180
|
*/
|
|
@@ -247,6 +247,8 @@ var PromiseMap = class {
|
|
|
247
247
|
cache = /* @__PURE__ */ new Map();
|
|
248
248
|
aborts = /* @__PURE__ */ new Map();
|
|
249
249
|
controllers = /* @__PURE__ */ new Map();
|
|
250
|
+
/** Each cached promise's value once it fulfills, for {@link getSettled} */
|
|
251
|
+
settled = /* @__PURE__ */ new WeakMap();
|
|
250
252
|
get [Symbol.toStringTag]() {
|
|
251
253
|
return "PromiseMap";
|
|
252
254
|
}
|
|
@@ -279,7 +281,7 @@ var PromiseMap = class {
|
|
|
279
281
|
this.aborts.set(key, aborts);
|
|
280
282
|
this.controllers.set(key, cacheable);
|
|
281
283
|
const promise = factory(cacheable, aborts.signal);
|
|
282
|
-
|
|
284
|
+
this.track(key, promise);
|
|
283
285
|
promise.finally(() => {
|
|
284
286
|
if (cacheable.invalidated) deleteIfOwned(this.cache, key, promise);
|
|
285
287
|
aborts.dispose();
|
|
@@ -319,10 +321,23 @@ var PromiseMap = class {
|
|
|
319
321
|
*/
|
|
320
322
|
set(key, promise) {
|
|
321
323
|
this.cache.set(key, promise);
|
|
322
|
-
|
|
324
|
+
this.track(key, promise);
|
|
323
325
|
return this;
|
|
324
326
|
}
|
|
325
327
|
/**
|
|
328
|
+
* The value `key`'s cached promise fulfilled with, or `undefined` while it is pending (or nothing is cached), so a
|
|
329
|
+
* caller can tell a settled entry from a read still in flight without joining it.
|
|
330
|
+
*/
|
|
331
|
+
getSettled(key) {
|
|
332
|
+
const promise = this.cache.get(key);
|
|
333
|
+
return promise != null ? this.settled.get(promise) : void 0;
|
|
334
|
+
}
|
|
335
|
+
track(key, promise) {
|
|
336
|
+
promise.then((value) => {
|
|
337
|
+
this.settled.set(promise, { value });
|
|
338
|
+
}, () => deleteIfOwned(this.cache, key, promise));
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
326
341
|
* Checks if a key exists in the cache.
|
|
327
342
|
*/
|
|
328
343
|
has(key) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"promiseCache.js","names":[],"sources":["../../../utils/src/promiseCache.ts"],"sourcesContent":["import { AbortAggregate, CancellationError, raceWithSignal } from './cancellation.js';\n\n/** Deletes `key` from `map` only if it still maps to `owner`. A `delete(key)` + fresh `getOrCreate` can install\n * a successor under the same key while our factory is in flight; our late settle must not evict the successor. */\nfunction deleteIfOwned<K, V>(map: Map<K, V>, key: K, owner: V): void {\n\tif (map.get(key) === owner) {\n\t\tmap.delete(key);\n\t}\n}\n\ninterface CacheEntry<V> {\n\tpromise: Promise<V>;\n\taccessed: number;\n\tcreated: number;\n\n\tcreateTTL: number | undefined;\n\taccessTTL: number | undefined;\n}\n\nexport class CacheController {\n\t#invalidated = false;\n\tget invalidated(): boolean {\n\t\treturn this.#invalidated;\n\t}\n\n\tinvalidate(): void {\n\t\tthis.#invalidated = true;\n\t}\n}\n\ninterface PromiseCacheOptions {\n\t/** TTL (time-to-live) in milliseconds since creation */\n\tcreateTTL?: number;\n\t/** TTL (time-to-live) in milliseconds since last access */\n\taccessTTL?: number;\n\t/** Whether to expire the entry if the promise fails (default: true) */\n\texpireOnError?: boolean;\n\t/**\n\t * TTL (time-to-live) in milliseconds for caching errors. When the factory rejects and `errorTTL` is set,\n\t * the cache entry is replaced with `Promise.resolve(undefined)` for subsequent callers, and the error\n\t * is re-thrown to the first caller. After `errorTTL` expires, the next call retries.\n\t * Only used when `onError` is not provided. Mutually exclusive with `expireOnError`.\n\t */\n\terrorTTL?: number;\n\t/** Maximum number of entries in the cache (LRU eviction when exceeded) */\n\tcapacity?: number;\n}\n\nexport class PromiseCache<K, V> {\n\tprivate readonly cache = new Map<K, CacheEntry<V>>();\n\tprivate readonly aborts = new Map<K, AbortAggregate>();\n\tprivate readonly controllers = new Map<K, CacheController>();\n\tprivate readonly options: PromiseCacheOptions;\n\n\tconstructor(options?: PromiseCacheOptions) {\n\t\tthis.options = { expireOnError: true, ...options };\n\t}\n\n\t/** Clears all entries. Like `delete`, does NOT dispose in-flight abort aggregates — that would detach a running\n\t * op's cancellation (a later caller-abort couldn't cancel it); each factory's settle handler disposes its own. */\n\tclear(): void {\n\t\tthis.aborts.clear();\n\t\tthis.controllers.clear();\n\t\tthis.cache.clear();\n\t}\n\n\t/**\n\t * Removes a promise from the cache.\n\t * Does NOT dispose the abort aggregate: while a factory is in flight, disposing would detach\n\t * its callers' cancellation and orphan the underlying op (a later caller-abort could no longer cancel the\n\t * running `git status`). The factory's own settle handler disposes the aggregate (see `getOrCreate`). */\n\tdelete(key: K): void {\n\t\tthis.aborts.delete(key);\n\t\tthis.controllers.delete(key);\n\t\tthis.cache.delete(key);\n\t}\n\n\t/**\n\t * Marks a cached entry as invalidated.\n\t * - If the entry has a `CacheController` (created via `getOrCreate`), it is soft-invalidated:\n\t * existing waiters complete normally, new callers joining during the remainder of the\n\t * in-flight operation share the same promise, and the entry self-evicts on settle.\n\t * - If the entry was set via plain `.set()` (no controller), it is hard-deleted — there is\n\t * no in-flight factory to ride along with.\n\t *\n\t * No-op if the key is not cached.\n\t */\n\tinvalidate(key: K): void {\n\t\tif (this.controllers.has(key)) {\n\t\t\tthis.controllers.get(key)!.invalidate();\n\t\t} else {\n\t\t\tthis.delete(key);\n\t\t}\n\t}\n\n\tkeys(): IterableIterator<K> {\n\t\treturn this.cache.keys();\n\t}\n\n\t/**\n\t * Returns the number of entries in the cache, including expired entries that haven't been evicted yet\n\t * (they are only evicted on access), so this reflects what's actually retained in memory.\n\t */\n\tget size(): number {\n\t\treturn this.cache.size;\n\t}\n\n\t/**\n\t * Gets a promise from the cache without creating it and updates accessed time\n\t * @param key - The cache key\n\t * @returns The cached promise, or undefined if not cached or expired\n\t */\n\tget(key: K): Promise<V> | undefined {\n\t\tconst now = Date.now();\n\t\tconst entry = this.cache.get(key);\n\t\tif (entry == null || this.expired(entry, now)) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\t// Update accessed time\n\t\tentry.accessed = now;\n\t\treturn entry.promise;\n\t}\n\n\t/**\n\t * Gets a promise from the cache or creates a new one using the factory function.\n\t *\n\t * When `cancellation` is provided:\n\t * - The factory receives an aggregate `AbortSignal` that only fires when **all** callers cancel\n\t * - The returned promise rejects with `CancellationError` if this caller's signal fires\n\t * - Other callers sharing the same cached promise are unaffected\n\t *\n\t * @param key - The cache key\n\t * @param factory - Factory function to create the value if not cached\n\t * @param cancellation - Optional per-caller AbortSignal\n\t * @param options - Optional TTL and error handling options that override the defaults\n\t */\n\tasync getOrCreate(\n\t\tkey: K,\n\t\tfactory: (cacheable: CacheController, cancellation?: AbortSignal) => Promise<V>,\n\t\toptions?: {\n\t\t\t/** Per-caller AbortSignal — the factory receives an aggregate signal that only fires when all callers cancel */\n\t\t\tcancellation?: AbortSignal;\n\t\t\t/** TTL (time-to-live) in milliseconds since creation */\n\t\t\tcreateTTL?: number;\n\t\t\t/** TTL (time-to-live) in milliseconds since last access */\n\t\t\taccessTTL?: number;\n\t\t\t/** Whether to expire the entry if the promise fails */\n\t\t\texpireOnError?: boolean;\n\t\t\t/**\n\t\t\t * Called after the factory resolves. If the predicate resolves to true, the entry is evicted while\n\t\t\t * the current caller still receives the resolved value.\n\t\t\t */\n\t\t\tevictWhen?: (value: V) => boolean | Promise<boolean>;\n\t\t\t/**\n\t\t\t * TTL (time-to-live) in milliseconds for caching errors. When the factory rejects and `errorTTL` is set,\n\t\t\t * the cache entry is replaced with `Promise.resolve(undefined)` for subsequent callers, and the error\n\t\t\t * is re-thrown to the first caller. After `errorTTL` expires, the next call retries.\n\t\t\t * Only used when `onError` is not provided.\n\t\t\t */\n\t\t\terrorTTL?: number;\n\t\t\t/**\n\t\t\t * Called when the factory promise rejects. Return a fallback result to cache\n\t\t\t * instead of expiring. The fallback value is cached with the specified `createTTL`\n\t\t\t * (defaults to the cache's `createTTL`). By default, the error is re-thrown to the\n\t\t\t * first caller after caching the fallback. Set `suppress: true` to return the fallback\n\t\t\t * value to the first caller instead. Return `undefined` to expire normally and re-throw.\n\t\t\t */\n\t\t\tonError?: (error: unknown) => { value: V; createTTL?: number; suppress?: boolean } | undefined;\n\t\t},\n\t): Promise<V> {\n\t\tconst cancellation = options?.cancellation;\n\t\tif (cancellation?.aborted) return Promise.reject(new CancellationError());\n\n\t\tconst now = Date.now();\n\n\t\toptions = { ...this.options, ...options };\n\n\t\tlet entry = this.cache.get(key);\n\t\tif (entry != null && !this.expired(entry, now)) {\n\t\t\t// Cache hit — but don't join an entry whose aggregate has already permanently aborted (all prior\n\t\t\t// callers cancelled, so the shared run is failing): that would hand this caller a doomed promise.\n\t\t\t// Fall through to create a fresh entry; the doomed entry's late settle is `deleteIfOwned`-guarded.\n\t\t\tconst existingAborts = this.aborts.get(key);\n\t\t\tif (existingAborts == null || !existingAborts.signal.aborted) {\n\t\t\t\tentry.accessed = now;\n\t\t\t\tconst cleanup = existingAborts != null ? existingAborts.add(cancellation) : undefined;\n\t\t\t\treturn this.attachCallerLifecycle(entry.promise, cancellation, cleanup);\n\t\t\t}\n\t\t}\n\n\t\tconst cacheable = new CacheController();\n\t\t// Always create the aggregate so subsequent callers (with or without signals) can contribute,\n\t\t// and so delete/invalidate has a controller to interact with\n\t\tconst abortAgg = new AbortAggregate();\n\t\tconst cleanup = abortAgg.add(cancellation);\n\t\tthis.aborts.set(key, abortAgg);\n\t\tthis.controllers.set(key, cacheable);\n\n\t\tconst promise = factory(cacheable, abortAgg.signal);\n\t\tvoid promise\n\t\t\t.finally(() => {\n\t\t\t\t// All three deletes are ownership-guarded — a hard `delete(key)` + fresh `getOrCreate` while our\n\t\t\t\t// factory is in flight installs a successor under `key`; our late settle must not evict it.\n\t\t\t\tif (cacheable.invalidated) {\n\t\t\t\t\tdeleteIfOwned(this.cache, key, entry!);\n\t\t\t\t}\n\t\t\t\tabortAgg.dispose();\n\t\t\t\tdeleteIfOwned(this.aborts, key, abortAgg);\n\t\t\t\tdeleteIfOwned(this.controllers, key, cacheable);\n\t\t\t})\n\t\t\t// Swallow the cleanup chain's rejection so it doesn't surface as an unhandled rejection\n\t\t\t// separate from the caller-facing promise's own handling (e.g. on cancellation).\n\t\t\t.catch(() => {});\n\n\t\tif (options?.onError != null) {\n\t\t\t// Wrap the promise to handle errors with the onError callback\n\t\t\tconst errorHandled = promise.catch((ex: unknown) => {\n\t\t\t\tconst result = options.onError!(ex);\n\t\t\t\t// A hard `delete(key)` + fresh `getOrCreate` while we were in flight installs a successor;\n\t\t\t\t// don't clobber it. But an EMPTY key (our entry already evicted, e.g. by the invalidated-finally)\n\t\t\t\t// still takes the fallback — preserving base behavior of caching the error fallback.\n\t\t\t\tconst occupant = this.cache.get(key);\n\t\t\t\tconst clobbersSuccessor = occupant != null && occupant !== entry;\n\t\t\t\tif (result != null) {\n\t\t\t\t\t// Replace the entry with a resolved fallback value for subsequent callers\n\t\t\t\t\tif (!clobbersSuccessor) {\n\t\t\t\t\t\tconst errorNow = Date.now();\n\t\t\t\t\t\tthis.cache.set(key, {\n\t\t\t\t\t\t\tpromise: Promise.resolve(result.value),\n\t\t\t\t\t\t\tcreated: errorNow,\n\t\t\t\t\t\t\taccessed: errorNow,\n\t\t\t\t\t\t\tcreateTTL: result.createTTL ?? options.createTTL,\n\t\t\t\t\t\t\taccessTTL: options.accessTTL,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\t// suppress: return fallback to first caller; otherwise re-throw\n\t\t\t\t\tif (result.suppress) return result.value;\n\t\t\t\t} else {\n\t\t\t\t\tdeleteIfOwned(this.cache, key, entry!);\n\t\t\t\t}\n\t\t\t\tthrow ex;\n\t\t\t});\n\n\t\t\tentry = {\n\t\t\t\tpromise: errorHandled,\n\t\t\t\tcreated: now,\n\t\t\t\taccessed: now,\n\t\t\t\tcreateTTL: options.createTTL,\n\t\t\t\taccessTTL: options.accessTTL,\n\t\t\t};\n\t\t\tthis.cache.set(key, entry);\n\t\t} else if (options?.errorTTL != null) {\n\t\t\t// Cache resolved undefined for subsequent callers, re-throw to first caller\n\t\t\tconst errorTTL = options.errorTTL;\n\t\t\tconst errorHandled = promise.catch((ex: unknown) => {\n\t\t\t\t// Overwrite unless a successor now owns the key (empty key still takes it — see onError above).\n\t\t\t\tconst occupant = this.cache.get(key);\n\t\t\t\tif (occupant == null || occupant === entry) {\n\t\t\t\t\tconst errorNow = Date.now();\n\t\t\t\t\tthis.cache.set(key, {\n\t\t\t\t\t\tpromise: Promise.resolve(undefined as V),\n\t\t\t\t\t\tcreated: errorNow,\n\t\t\t\t\t\taccessed: errorNow,\n\t\t\t\t\t\tcreateTTL: errorTTL,\n\t\t\t\t\t\taccessTTL: options.accessTTL,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tthrow ex;\n\t\t\t});\n\n\t\t\tentry = {\n\t\t\t\tpromise: errorHandled,\n\t\t\t\tcreated: now,\n\t\t\t\taccessed: now,\n\t\t\t\tcreateTTL: options.createTTL,\n\t\t\t\taccessTTL: options.accessTTL,\n\t\t\t};\n\t\t\tthis.cache.set(key, entry);\n\t\t} else {\n\t\t\tentry = {\n\t\t\t\tpromise: promise,\n\t\t\t\tcreated: now,\n\t\t\t\taccessed: now,\n\t\t\t\tcreateTTL: options.createTTL,\n\t\t\t\taccessTTL: options.accessTTL,\n\t\t\t};\n\t\t\tthis.cache.set(key, entry);\n\n\t\t\tif (options?.expireOnError ?? true) {\n\t\t\t\t// Ownership-guarded: a hard `delete(key)` + fresh `getOrCreate` replacing us while in flight must\n\t\t\t\t// not have its entry evicted by our rejection.\n\t\t\t\tconst ourEntry = entry;\n\t\t\t\tpromise.catch(() => deleteIfOwned(this.cache, key, ourEntry));\n\t\t\t}\n\t\t}\n\n\t\tconst evictWhen = options?.evictWhen;\n\t\tif (evictWhen != null) {\n\t\t\tconst candidate = entry;\n\t\t\tvoid candidate.promise\n\t\t\t\t.then(value => evictWhen(value))\n\t\t\t\t.then(evict => {\n\t\t\t\t\tif (evict) {\n\t\t\t\t\t\tdeleteIfOwned(this.cache, key, candidate);\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t// A retention policy must not surface a rejection independently of the caller-facing operation.\n\t\t\t\t.catch(() => {});\n\t\t}\n\n\t\t// Clean up expired entries and enforce capacity limit in one pass\n\t\tif (this.cache.size > 1) {\n\t\t\tqueueMicrotask(() => this.cleanup());\n\t\t}\n\n\t\treturn this.attachCallerLifecycle(entry.promise, cancellation, cleanup);\n\t}\n\n\tprivate attachCallerLifecycle(\n\t\tpromise: Promise<V>,\n\t\tcancellation: AbortSignal | undefined,\n\t\tcleanup: (() => void) | undefined,\n\t): Promise<V> {\n\t\tconst returned = cancellation != null ? raceWithSignal(promise, cancellation) : promise;\n\t\tif (cleanup != null) {\n\t\t\t// Release this caller's aggregate slot when their wait ends.\n\t\t\t// Attach .catch to swallow any rejection in the cleanup chain so it doesn't surface\n\t\t\t// as an unhandled rejection separate from the caller's own handling of `returned`.\n\t\t\tvoid returned.finally(cleanup).catch(() => {});\n\t\t}\n\t\treturn returned;\n\t}\n\n\tprivate cleanup(): void {\n\t\tconst now = Date.now();\n\t\tconst capacity = this.options.capacity;\n\n\t\t// If no capacity limit, just remove expired entries\n\t\tif (capacity == null) {\n\t\t\tfor (const [key, entry] of this.cache.entries()) {\n\t\t\t\tif (this.expired(entry, now)) {\n\t\t\t\t\tthis.cache.delete(key);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Single pass: collect non-expired entries and find LRU candidates\n\t\tconst entries: Array<[K, CacheEntry<V>]> = [];\n\t\tfor (const [key, entry] of this.cache.entries()) {\n\t\t\tif (!this.expired(entry, now)) {\n\t\t\t\tentries.push([key, entry]);\n\t\t\t} else {\n\t\t\t\tthis.cache.delete(key);\n\t\t\t}\n\t\t}\n\n\t\t// If still over capacity, remove LRU entries\n\t\tconst excess = entries.length - capacity;\n\t\tif (excess > 0) {\n\t\t\t// Sort by accessed time (oldest first) and remove the excess\n\t\t\t// Note: O(N log N) sort — acceptable at current cache sizes; replace with a linked-list LRU if capacity grows\n\t\t\tentries.sort((a, b) => a[1].accessed - b[1].accessed);\n\t\t\tfor (let i = 0; i < excess; i++) {\n\t\t\t\tthis.cache.delete(entries[i][0]);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate expired(entry: CacheEntry<V>, now: number): boolean {\n\t\treturn (\n\t\t\t(entry.createTTL != null && now - entry.created >= entry.createTTL) ||\n\t\t\t(entry.accessTTL != null && now - entry.accessed >= entry.accessTTL)\n\t\t);\n\t}\n\n\t/**\n\t * Sets a promise in the cache.\n\t * The promise will use the default options from the cache constructor, unless overridden.\n\t *\n\t * @param key - The cache key\n\t * @param promise - The promise to cache\n\t * @param options - Optional TTL options that override the defaults\n\t */\n\tset(key: K, promise: Promise<V>, options?: { accessTTL?: number; createTTL?: number }): void {\n\t\tconst now = Date.now();\n\t\tconst entry: CacheEntry<V> = {\n\t\t\tpromise: promise,\n\t\t\tcreated: now,\n\t\t\taccessed: now,\n\t\t\tcreateTTL: options?.createTTL ?? this.options.createTTL,\n\t\t\taccessTTL: options?.accessTTL ?? this.options.accessTTL,\n\t\t};\n\t\tthis.cache.set(key, entry);\n\n\t\tif (this.options.expireOnError ?? true) {\n\t\t\tpromise.catch(() => deleteIfOwned(this.cache, key, entry));\n\t\t}\n\t}\n}\n\n/**\n * A Map-like wrapper for promises that automatically removes failed promises from the cache.\n * This provides a drop-in replacement for Map<K, Promise<V>> with automatic error cleanup.\n */\nexport class PromiseMap<K, V> {\n\tprivate readonly cache = new Map<K, Promise<V>>();\n\tprivate readonly aborts = new Map<K, AbortAggregate>();\n\tprivate readonly controllers = new Map<K, CacheController>();\n\n\tget [Symbol.toStringTag](): string {\n\t\treturn 'PromiseMap';\n\t}\n\n\t/**\n\t * Gets a promise from the cache or creates a new one using the factory function.\n\t * Failed promises are automatically removed from the cache.\n\t *\n\t * When `cancellation` is provided:\n\t * - The factory receives an aggregate `AbortSignal` that only fires when **all** callers cancel\n\t * - The returned promise rejects with `CancellationError` if this caller's signal fires\n\t * - Other callers sharing the same cached promise are unaffected\n\t *\n\t * Callers without a `cancellation` signal count as \"permanent\" subscribers — the aggregate\n\t * cannot fire while at least one is active. The slot is released when the caller's returned\n\t * promise settles.\n\t */\n\tgetOrCreate(\n\t\tkey: K,\n\t\tfactory: (cacheable: CacheController, cancellation?: AbortSignal) => Promise<V>,\n\t\tcancellation?: AbortSignal,\n\t): Promise<V> {\n\t\tif (cancellation?.aborted) return Promise.reject(new CancellationError());\n\n\t\tconst cached = this.cache.get(key);\n\t\tif (cached != null) {\n\t\t\t// Cache hit — but don't join an entry whose aggregate has already permanently aborted (all prior\n\t\t\t// callers cancelled, so the shared run is failing): joining hands this caller a doomed promise.\n\t\t\t// Fall through to a fresh entry; the doomed entry's late settle is `deleteIfOwned`-guarded.\n\t\t\tconst existingAborts = this.aborts.get(key);\n\t\t\tif (existingAborts == null || !existingAborts.signal.aborted) {\n\t\t\t\tconst cleanup = existingAborts != null ? existingAborts.add(cancellation) : undefined;\n\t\t\t\treturn this.attachCallerLifecycle(cached, cancellation, cleanup);\n\t\t\t}\n\t\t}\n\n\t\t// Cache miss — always create the aggregate so subsequent callers (with or without signals)\n\t\t// can contribute, and so delete/invalidate has a controller to interact with\n\t\tconst cacheable = new CacheController();\n\t\tconst aborts = new AbortAggregate();\n\t\tconst cleanup = aborts.add(cancellation);\n\t\tthis.aborts.set(key, aborts);\n\t\tthis.controllers.set(key, cacheable);\n\n\t\tconst promise = factory(cacheable, aborts.signal);\n\n\t\t// Automatically remove failed promises from the cache (ownership-guarded — see `deleteIfOwned`).\n\t\tpromise.catch(() => deleteIfOwned(this.cache, key, promise));\n\t\tvoid promise\n\t\t\t.finally(() => {\n\t\t\t\tif (cacheable.invalidated) {\n\t\t\t\t\tdeleteIfOwned(this.cache, key, promise);\n\t\t\t\t}\n\t\t\t\taborts.dispose();\n\t\t\t\tdeleteIfOwned(this.aborts, key, aborts);\n\t\t\t\tdeleteIfOwned(this.controllers, key, cacheable);\n\t\t\t})\n\t\t\t// Swallow the cleanup chain's rejection so it doesn't surface as an unhandled rejection\n\t\t\t// separate from the caller-facing promise's own handling (e.g. on cancellation).\n\t\t\t.catch(() => {});\n\n\t\tthis.cache.set(key, promise);\n\n\t\treturn this.attachCallerLifecycle(promise, cancellation, cleanup);\n\t}\n\n\tprivate attachCallerLifecycle(\n\t\tpromise: Promise<V>,\n\t\tcancellation: AbortSignal | undefined,\n\t\tcleanup: (() => void) | undefined,\n\t): Promise<V> {\n\t\tconst returned = cancellation != null ? raceWithSignal(promise, cancellation) : promise;\n\t\tif (cleanup != null) {\n\t\t\t// Release this caller's aggregate slot when their wait ends.\n\t\t\t// Attach .catch to swallow any rejection in the cleanup chain so it doesn't surface\n\t\t\t// as an unhandled rejection separate from the caller's own handling of `returned`.\n\t\t\tvoid returned.finally(cleanup).catch(() => {});\n\t\t}\n\t\treturn returned;\n\t}\n\n\t/**\n\t * Marks a cached entry as invalidated.\n\t * - If the entry has a `CacheController` (created via `getOrCreate`), it is soft-invalidated:\n\t * existing waiters complete normally, new callers joining during the remainder of the\n\t * in-flight operation share the same promise, and the entry self-evicts on settle.\n\t * - If the entry was set via plain `.set()` (no controller), it is hard-deleted — there is\n\t * no in-flight factory to ride along with.\n\t *\n\t * No-op if the key is not cached.\n\t */\n\tinvalidate(key: K): void {\n\t\tif (this.controllers.has(key)) {\n\t\t\tthis.controllers.get(key)!.invalidate();\n\t\t} else {\n\t\t\tthis.delete(key);\n\t\t}\n\t}\n\n\t/**\n\t * Gets a promise from the cache, or undefined if not found.\n\t */\n\tget(key: K): Promise<V> | undefined {\n\t\treturn this.cache.get(key);\n\t}\n\n\t/**\n\t * Sets a promise in the cache. The promise will be automatically removed if it fails.\n\t */\n\tset(key: K, promise: Promise<V>): this {\n\t\tthis.cache.set(key, promise);\n\n\t\t// Automatically remove failed promises from the cache (ownership-guarded — see `deleteIfOwned`).\n\t\tpromise.catch(() => deleteIfOwned(this.cache, key, promise));\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Checks if a key exists in the cache.\n\t */\n\thas(key: K): boolean {\n\t\treturn this.cache.has(key);\n\t}\n\n\t/**\n\t * Removes a key from the cache.\n\t * Does NOT dispose the abort aggregate: while a factory is in flight, disposing would detach\n\t * its callers' cancellation and orphan the underlying op (a later caller-abort could no longer cancel the\n\t * running `git status`). The factory's own settle handler disposes the aggregate (see `getOrCreate`). */\n\tdelete(key: K): boolean {\n\t\tthis.aborts.delete(key);\n\t\tthis.controllers.delete(key);\n\t\treturn this.cache.delete(key);\n\t}\n\n\t/**\n\t * Clears all entries. Like `delete`, does NOT dispose in-flight abort aggregates — that would detach a running\n\t * op's cancellation (a later caller-abort couldn't cancel it); each factory's settle handler disposes its own.\n\t */\n\tclear(): void {\n\t\tthis.aborts.clear();\n\t\tthis.controllers.clear();\n\t\tthis.cache.clear();\n\t}\n\n\t/**\n\t * Returns the number of entries in the cache.\n\t */\n\tget size(): number {\n\t\treturn this.cache.size;\n\t}\n\n\t/**\n\t * Returns an iterator for the cache keys.\n\t */\n\tkeys(): IterableIterator<K> {\n\t\treturn this.cache.keys();\n\t}\n\n\t/**\n\t * Returns an iterator for the cache values.\n\t */\n\tvalues(): IterableIterator<Promise<V>> {\n\t\treturn this.cache.values();\n\t}\n\n\t/**\n\t * Returns an iterator for the cache entries.\n\t */\n\tentries(): IterableIterator<[K, Promise<V>]> {\n\t\treturn this.cache.entries();\n\t}\n\n\t/**\n\t * Executes a provided function once for each cache entry.\n\t */\n\tforEach(callbackfn: (value: Promise<V>, key: K, map: Map<K, Promise<V>>) => void, thisArg?: any): void {\n\t\tthis.cache.forEach(callbackfn, thisArg);\n\t}\n\n\t[Symbol.iterator](): IterableIterator<[K, Promise<V>]> {\n\t\treturn this.cache[Symbol.iterator]();\n\t}\n}\n\n/**\n * A two-level cache that organizes PromiseCaches by repository path.\n * Automatically creates and manages inner PromiseCaches per repository.\n *\n * This is useful for caching data that varies by both repository and some other key,\n * with TTL and capacity management provided by PromiseCache.\n */\nexport class RepoPromiseCacheMap<K, V> {\n\tprivate readonly cache = new Map<string, PromiseCache<K, V>>();\n\tprivate readonly options: PromiseCacheOptions;\n\n\tconstructor(options?: PromiseCacheOptions) {\n\t\tthis.options = { expireOnError: true, ...options };\n\t}\n\n\tget [Symbol.toStringTag](): string {\n\t\treturn 'RepoPromiseCacheMap';\n\t}\n\n\t/**\n\t * Gets a promise from the cache or creates a new one using the factory function.\n\t * Automatically creates the inner PromiseCache for the repository if it doesn't exist.\n\t *\n\t * @param repoPath - The repository path (outer key)\n\t * @param key - The cache key within the repository (inner key)\n\t * @param factory - Factory function to create the value if not cached\n\t * @param options - Optional TTL and error handling options that override the defaults\n\t */\n\tgetOrCreate(\n\t\trepoPath: string,\n\t\tkey: K,\n\t\tfactory: (cacheable: CacheController, cancellation?: AbortSignal) => Promise<V>,\n\t\toptions?: {\n\t\t\t/** Per-caller AbortSignal — the factory receives an aggregate signal that only fires when all callers cancel */\n\t\t\tcancellation?: AbortSignal;\n\t\t\t/** TTL (time-to-live) in milliseconds since creation */\n\t\t\tcreateTTL?: number;\n\t\t\t/** TTL (time-to-live) in milliseconds since last access */\n\t\t\taccessTTL?: number;\n\t\t\t/** Whether to expire the entry if the promise fails */\n\t\t\texpireOnError?: boolean;\n\t\t\t/**\n\t\t\t * Called after the factory resolves. If the predicate resolves to true, the entry is evicted while\n\t\t\t * the current caller still receives the resolved value.\n\t\t\t */\n\t\t\tevictWhen?: (value: V) => boolean | Promise<boolean>;\n\t\t\t/**\n\t\t\t * TTL (time-to-live) in milliseconds for caching errors. When the factory rejects and `errorTTL` is set,\n\t\t\t * the cache entry is replaced with `Promise.resolve(undefined)` for subsequent callers, and the error\n\t\t\t * is re-thrown to the first caller. After `errorTTL` expires, the next call retries.\n\t\t\t * Only used when `onError` is not provided.\n\t\t\t */\n\t\t\terrorTTL?: number;\n\t\t\t/**\n\t\t\t * Called when the factory promise rejects. Return a fallback result to cache\n\t\t\t * instead of expiring. The fallback value is cached with the specified `createTTL`\n\t\t\t * (defaults to the cache's `createTTL`). By default, the error is re-thrown to the\n\t\t\t * first caller after caching the fallback. Set `suppress: true` to return the fallback\n\t\t\t * value to the first caller instead. Return `undefined` to expire normally and re-throw.\n\t\t\t */\n\t\t\tonError?: (error: unknown) => { value: V; createTTL?: number; suppress?: boolean } | undefined;\n\t\t},\n\t): Promise<V> {\n\t\tlet repoCache = this.cache.get(repoPath);\n\t\tif (repoCache == null) {\n\t\t\trepoCache = new PromiseCache<K, V>(this.options);\n\t\t\tthis.cache.set(repoPath, repoCache);\n\t\t}\n\n\t\treturn repoCache.getOrCreate(key, factory, options);\n\t}\n\n\t/**\n\t * Gets a promise from the cache without creating it.\n\t *\n\t * @param repoPath - The repository path (outer key)\n\t * @param key - The cache key within the repository (inner key)\n\t * @returns The cached promise, or undefined if not cached\n\t */\n\tget(repoPath: string, key: K): Promise<V> | undefined {\n\t\tconst repoCache = this.cache.get(repoPath);\n\t\tif (repoCache == null) return undefined;\n\n\t\treturn repoCache.get(key);\n\t}\n\n\t/**\n\t * Sets a promise in the cache.\n\t *\n\t * @param repoPath - The repository path (outer key)\n\t * @param key - The cache key within the repository (inner key)\n\t * @param promise - The promise to cache\n\t * @param options - Optional TTL options that override the defaults\n\t */\n\tset(repoPath: string, key: K, promise: Promise<V>, options?: { accessTTL?: number; createTTL?: number }): void {\n\t\tlet repoCache = this.cache.get(repoPath);\n\t\tif (repoCache == null) {\n\t\t\trepoCache = new PromiseCache<K, V>(this.options);\n\t\t\tthis.cache.set(repoPath, repoCache);\n\t\t}\n\n\t\trepoCache.set(key, promise, options);\n\t}\n\n\t/**\n\t * Deletes a specific key from a repository's cache, or all keys for a repository.\n\t *\n\t * @param repoPath - The repository path\n\t * @param key - Optional specific key to delete. If omitted, deletes the entire repository cache.\n\t * @returns true if something was deleted, false otherwise\n\t */\n\tdelete(repoPath: string, key?: K): boolean {\n\t\tif (key === undefined) {\n\t\t\treturn this.cache.delete(repoPath);\n\t\t}\n\n\t\tconst repoCache = this.cache.get(repoPath);\n\t\tif (repoCache == null) return false;\n\n\t\trepoCache.delete(key);\n\t\treturn true;\n\t}\n\n\t/**\n\t * Marks a cached entry as invalidated. See `PromiseCache.invalidate` for semantics.\n\t *\n\t * @param repoPath - The repository path\n\t * @param key - Optional specific key to invalidate. If omitted, invalidates every entry for the repository.\n\t */\n\tinvalidate(repoPath: string, key?: K): void {\n\t\tconst repoCache = this.cache.get(repoPath);\n\t\tif (repoCache == null) return;\n\n\t\tif (key === undefined) {\n\t\t\tfor (const k of repoCache.keys()) {\n\t\t\t\trepoCache.invalidate(k);\n\t\t\t}\n\t\t} else {\n\t\t\trepoCache.invalidate(key);\n\t\t}\n\t}\n\n\t/**\n\t * Deletes all keys matching a prefix from a repository's cache.\n\t * @param repoPath - The repository path\n\t * @param prefix - Key prefix to match\n\t * @returns true if something was deleted\n\t */\n\tdeleteByKeyPrefix(this: RepoPromiseCacheMap<string, V>, repoPath: string, prefix: string): boolean {\n\t\tconst repoCache = this.cache.get(repoPath);\n\t\tif (repoCache == null) return false;\n\n\t\tlet deleted = false;\n\t\tfor (const key of repoCache.keys()) {\n\t\t\tif (key.startsWith(prefix)) {\n\t\t\t\trepoCache.delete(key);\n\t\t\t\tdeleted = true;\n\t\t\t}\n\t\t}\n\t\treturn deleted;\n\t}\n\n\t/**\n\t * Invalidates all keys matching a prefix from a repository's cache. See `PromiseCache.invalidate`\n\t * for semantics — in-flight entries are marked invalidated and shared with new callers; settled\n\t * entries are hard-deleted. Prefer over `deleteByKeyPrefix` when the eviction is a \"data has\n\t * changed\" event and in-flight factories' results would still be valid for current waiters.\n\t * @param repoPath - The repository path\n\t * @param prefix - Key prefix to match\n\t * @returns true if something was invalidated\n\t */\n\tinvalidateByKeyPrefix(this: RepoPromiseCacheMap<string, V>, repoPath: string, prefix: string): boolean {\n\t\tconst repoCache = this.cache.get(repoPath);\n\t\tif (repoCache == null) return false;\n\n\t\tlet invalidated = false;\n\t\tfor (const key of repoCache.keys()) {\n\t\t\tif (key.startsWith(prefix)) {\n\t\t\t\trepoCache.invalidate(key);\n\t\t\t\tinvalidated = true;\n\t\t\t}\n\t\t}\n\t\treturn invalidated;\n\t}\n\n\t/**\n\t * Clears all caches for all repositories.\n\t */\n\tclear(): void {\n\t\tthis.cache.clear();\n\t}\n\n\t/**\n\t * Returns the total number of entries across all repository caches.\n\t */\n\tget entryCount(): number {\n\t\tlet count = 0;\n\t\tfor (const repoCache of this.cache.values()) {\n\t\t\tcount += repoCache.size;\n\t\t}\n\t\treturn count;\n\t}\n\n\t/**\n\t * Returns the number of repositories in the cache.\n\t */\n\tget size(): number {\n\t\treturn this.cache.size;\n\t}\n}\n\n/**\n * A two-level cache that organizes PromiseMaps by repository path.\n * Automatically creates and manages inner PromiseMaps per repository.\n *\n * This is useful for caching data that varies by both repository and some other key,\n * with simple promise deduplication and automatic error cleanup.\n */\nexport class RepoPromiseMap<K, V> {\n\tprivate readonly cache = new Map<string, PromiseMap<K, V>>();\n\n\tget [Symbol.toStringTag](): string {\n\t\treturn 'RepoPromiseMap';\n\t}\n\n\t/**\n\t * Gets a promise from the cache or creates a new one using the factory function.\n\t * Automatically creates the inner PromiseMap for the repository if it doesn't exist.\n\t *\n\t * @param repoPath - The repository path (outer key)\n\t * @param key - The cache key within the repository (inner key)\n\t * @param factory - Factory function to create the value if not cached\n\t */\n\tgetOrCreate(\n\t\trepoPath: string,\n\t\tkey: K,\n\t\tfactory: (cacheable: CacheController, cancellation?: AbortSignal) => Promise<V>,\n\t\tcancellation?: AbortSignal,\n\t): Promise<V> {\n\t\tlet repoCache = this.cache.get(repoPath);\n\t\tif (repoCache == null) {\n\t\t\trepoCache = new PromiseMap<K, V>();\n\t\t\tthis.cache.set(repoPath, repoCache);\n\t\t}\n\n\t\treturn repoCache.getOrCreate(key, factory, cancellation);\n\t}\n\n\t/**\n\t * Gets a promise from the cache without creating it.\n\t *\n\t * @param repoPath - The repository path (outer key)\n\t * @param key - The cache key within the repository (inner key)\n\t * @returns The cached promise, or undefined if not found\n\t */\n\tget(repoPath: string, key: K): Promise<V> | undefined {\n\t\tconst repoCache = this.cache.get(repoPath);\n\t\treturn repoCache?.get(key);\n\t}\n\n\t/**\n\t * Deletes a specific key from a repository's cache, or all keys for a repository.\n\t *\n\t * @param repoPath - The repository path\n\t * @param key - Optional specific key to delete. If omitted, deletes the entire repository cache.\n\t * @returns true if something was deleted, false otherwise\n\t */\n\tdelete(repoPath: string, key?: K): boolean {\n\t\tif (key === undefined) {\n\t\t\treturn this.cache.delete(repoPath);\n\t\t}\n\n\t\tconst repoCache = this.cache.get(repoPath);\n\t\tif (repoCache == null) return false;\n\n\t\treturn repoCache.delete(key);\n\t}\n\n\t/**\n\t * Marks a cached entry as invalidated. See `PromiseMap.invalidate` for semantics.\n\t *\n\t * @param repoPath - The repository path\n\t * @param key - Optional specific key to invalidate. If omitted, invalidates every entry for the repository.\n\t */\n\tinvalidate(repoPath: string, key?: K): void {\n\t\tconst repoCache = this.cache.get(repoPath);\n\t\tif (repoCache == null) return;\n\n\t\tif (key === undefined) {\n\t\t\tfor (const k of repoCache.keys()) {\n\t\t\t\trepoCache.invalidate(k);\n\t\t\t}\n\t\t} else {\n\t\t\trepoCache.invalidate(key);\n\t\t}\n\t}\n\n\t/**\n\t * Clears all caches for all repositories.\n\t */\n\tclear(): void {\n\t\tthis.cache.clear();\n\t}\n\n\t/**\n\t * Returns the number of repositories in the cache.\n\t */\n\tget size(): number {\n\t\treturn this.cache.size;\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"promiseCache.js","names":[],"sources":["../../../utils/src/promiseCache.ts"],"sourcesContent":["import { AbortAggregate, CancellationError, raceWithSignal } from './cancellation.js';\n\n/** Deletes `key` from `map` only if it still maps to `owner`. A `delete(key)` + fresh `getOrCreate` can install\n * a successor under the same key while our factory is in flight; our late settle must not evict the successor. */\nfunction deleteIfOwned<K, V>(map: Map<K, V>, key: K, owner: V): void {\n\tif (map.get(key) === owner) {\n\t\tmap.delete(key);\n\t}\n}\n\ninterface CacheEntry<V> {\n\tpromise: Promise<V>;\n\taccessed: number;\n\tcreated: number;\n\n\tcreateTTL: number | undefined;\n\taccessTTL: number | undefined;\n}\n\nexport class CacheController {\n\t#invalidated = false;\n\tget invalidated(): boolean {\n\t\treturn this.#invalidated;\n\t}\n\n\tinvalidate(): void {\n\t\tthis.#invalidated = true;\n\t}\n}\n\ninterface PromiseCacheOptions {\n\t/** TTL (time-to-live) in milliseconds since creation */\n\tcreateTTL?: number;\n\t/** TTL (time-to-live) in milliseconds since last access */\n\taccessTTL?: number;\n\t/** Whether to expire the entry if the promise fails (default: true) */\n\texpireOnError?: boolean;\n\t/**\n\t * TTL (time-to-live) in milliseconds for caching errors. When the factory rejects and `errorTTL` is set,\n\t * the cache entry is replaced with `Promise.resolve(undefined)` for subsequent callers, and the error\n\t * is re-thrown to the first caller. After `errorTTL` expires, the next call retries.\n\t * Only used when `onError` is not provided. Mutually exclusive with `expireOnError`.\n\t */\n\terrorTTL?: number;\n\t/** Maximum number of entries in the cache (LRU eviction when exceeded) */\n\tcapacity?: number;\n}\n\nexport class PromiseCache<K, V> {\n\tprivate readonly cache = new Map<K, CacheEntry<V>>();\n\tprivate readonly aborts = new Map<K, AbortAggregate>();\n\tprivate readonly controllers = new Map<K, CacheController>();\n\tprivate readonly options: PromiseCacheOptions;\n\n\tconstructor(options?: PromiseCacheOptions) {\n\t\tthis.options = { expireOnError: true, ...options };\n\t}\n\n\t/** Clears all entries. Like `delete`, does NOT dispose in-flight abort aggregates — that would detach a running\n\t * op's cancellation (a later caller-abort couldn't cancel it); each factory's settle handler disposes its own. */\n\tclear(): void {\n\t\tthis.aborts.clear();\n\t\tthis.controllers.clear();\n\t\tthis.cache.clear();\n\t}\n\n\t/**\n\t * Removes a promise from the cache.\n\t * Does NOT dispose the abort aggregate: while a factory is in flight, disposing would detach\n\t * its callers' cancellation and orphan the underlying op (a later caller-abort could no longer cancel the\n\t * running `git status`). The factory's own settle handler disposes the aggregate (see `getOrCreate`). */\n\tdelete(key: K): void {\n\t\tthis.aborts.delete(key);\n\t\tthis.controllers.delete(key);\n\t\tthis.cache.delete(key);\n\t}\n\n\t/**\n\t * Marks a cached entry as invalidated.\n\t * - If the entry has a `CacheController` (created via `getOrCreate`), it is soft-invalidated:\n\t * existing waiters complete normally, new callers joining during the remainder of the\n\t * in-flight operation share the same promise, and the entry self-evicts on settle.\n\t * - If the entry was set via plain `.set()` (no controller), it is hard-deleted — there is\n\t * no in-flight factory to ride along with.\n\t *\n\t * No-op if the key is not cached.\n\t */\n\tinvalidate(key: K): void {\n\t\tif (this.controllers.has(key)) {\n\t\t\tthis.controllers.get(key)!.invalidate();\n\t\t} else {\n\t\t\tthis.delete(key);\n\t\t}\n\t}\n\n\tkeys(): IterableIterator<K> {\n\t\treturn this.cache.keys();\n\t}\n\n\t/**\n\t * Returns the number of entries in the cache, including expired entries that haven't been evicted yet\n\t * (they are only evicted on access), so this reflects what's actually retained in memory.\n\t */\n\tget size(): number {\n\t\treturn this.cache.size;\n\t}\n\n\t/**\n\t * Gets a promise from the cache without creating it and updates accessed time\n\t * @param key - The cache key\n\t * @returns The cached promise, or undefined if not cached or expired\n\t */\n\tget(key: K): Promise<V> | undefined {\n\t\tconst now = Date.now();\n\t\tconst entry = this.cache.get(key);\n\t\tif (entry == null || this.expired(entry, now)) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\t// Update accessed time\n\t\tentry.accessed = now;\n\t\treturn entry.promise;\n\t}\n\n\t/**\n\t * Gets a promise from the cache or creates a new one using the factory function.\n\t *\n\t * When `cancellation` is provided:\n\t * - The factory receives an aggregate `AbortSignal` that only fires when **all** callers cancel\n\t * - The returned promise rejects with `CancellationError` if this caller's signal fires\n\t * - Other callers sharing the same cached promise are unaffected\n\t *\n\t * @param key - The cache key\n\t * @param factory - Factory function to create the value if not cached\n\t * @param cancellation - Optional per-caller AbortSignal\n\t * @param options - Optional TTL and error handling options that override the defaults\n\t */\n\tasync getOrCreate(\n\t\tkey: K,\n\t\tfactory: (cacheable: CacheController, cancellation?: AbortSignal) => Promise<V>,\n\t\toptions?: {\n\t\t\t/** Per-caller AbortSignal — the factory receives an aggregate signal that only fires when all callers cancel */\n\t\t\tcancellation?: AbortSignal;\n\t\t\t/** TTL (time-to-live) in milliseconds since creation */\n\t\t\tcreateTTL?: number;\n\t\t\t/** TTL (time-to-live) in milliseconds since last access */\n\t\t\taccessTTL?: number;\n\t\t\t/** Whether to expire the entry if the promise fails */\n\t\t\texpireOnError?: boolean;\n\t\t\t/**\n\t\t\t * Called after the factory resolves. If the predicate resolves to true, the entry is evicted while\n\t\t\t * the current caller still receives the resolved value.\n\t\t\t */\n\t\t\tevictWhen?: (value: V) => boolean | Promise<boolean>;\n\t\t\t/**\n\t\t\t * TTL (time-to-live) in milliseconds for caching errors. When the factory rejects and `errorTTL` is set,\n\t\t\t * the cache entry is replaced with `Promise.resolve(undefined)` for subsequent callers, and the error\n\t\t\t * is re-thrown to the first caller. After `errorTTL` expires, the next call retries.\n\t\t\t * Only used when `onError` is not provided.\n\t\t\t */\n\t\t\terrorTTL?: number;\n\t\t\t/**\n\t\t\t * Called when the factory promise rejects. Return a fallback result to cache\n\t\t\t * instead of expiring. The fallback value is cached with the specified `createTTL`\n\t\t\t * (defaults to the cache's `createTTL`). By default, the error is re-thrown to the\n\t\t\t * first caller after caching the fallback. Set `suppress: true` to return the fallback\n\t\t\t * value to the first caller instead. Return `undefined` to expire normally and re-throw.\n\t\t\t */\n\t\t\tonError?: (error: unknown) => { value: V; createTTL?: number; suppress?: boolean } | undefined;\n\t\t},\n\t): Promise<V> {\n\t\tconst cancellation = options?.cancellation;\n\t\tif (cancellation?.aborted) return Promise.reject(new CancellationError());\n\n\t\tconst now = Date.now();\n\n\t\toptions = { ...this.options, ...options };\n\n\t\tlet entry = this.cache.get(key);\n\t\tif (entry != null && !this.expired(entry, now)) {\n\t\t\t// Cache hit — but don't join an entry whose aggregate has already permanently aborted (all prior\n\t\t\t// callers cancelled, so the shared run is failing): that would hand this caller a doomed promise.\n\t\t\t// Fall through to create a fresh entry; the doomed entry's late settle is `deleteIfOwned`-guarded.\n\t\t\tconst existingAborts = this.aborts.get(key);\n\t\t\tif (existingAborts == null || !existingAborts.signal.aborted) {\n\t\t\t\tentry.accessed = now;\n\t\t\t\tconst cleanup = existingAborts != null ? existingAborts.add(cancellation) : undefined;\n\t\t\t\treturn this.attachCallerLifecycle(entry.promise, cancellation, cleanup);\n\t\t\t}\n\t\t}\n\n\t\tconst cacheable = new CacheController();\n\t\t// Always create the aggregate so subsequent callers (with or without signals) can contribute,\n\t\t// and so delete/invalidate has a controller to interact with\n\t\tconst abortAgg = new AbortAggregate();\n\t\tconst cleanup = abortAgg.add(cancellation);\n\t\tthis.aborts.set(key, abortAgg);\n\t\tthis.controllers.set(key, cacheable);\n\n\t\tconst promise = factory(cacheable, abortAgg.signal);\n\t\tvoid promise\n\t\t\t.finally(() => {\n\t\t\t\t// All three deletes are ownership-guarded — a hard `delete(key)` + fresh `getOrCreate` while our\n\t\t\t\t// factory is in flight installs a successor under `key`; our late settle must not evict it.\n\t\t\t\tif (cacheable.invalidated) {\n\t\t\t\t\tdeleteIfOwned(this.cache, key, entry!);\n\t\t\t\t}\n\t\t\t\tabortAgg.dispose();\n\t\t\t\tdeleteIfOwned(this.aborts, key, abortAgg);\n\t\t\t\tdeleteIfOwned(this.controllers, key, cacheable);\n\t\t\t})\n\t\t\t// Swallow the cleanup chain's rejection so it doesn't surface as an unhandled rejection\n\t\t\t// separate from the caller-facing promise's own handling (e.g. on cancellation).\n\t\t\t.catch(() => {});\n\n\t\tif (options?.onError != null) {\n\t\t\t// Wrap the promise to handle errors with the onError callback\n\t\t\tconst errorHandled = promise.catch((ex: unknown) => {\n\t\t\t\tconst result = options.onError!(ex);\n\t\t\t\t// A hard `delete(key)` + fresh `getOrCreate` while we were in flight installs a successor;\n\t\t\t\t// don't clobber it. But an EMPTY key (our entry already evicted, e.g. by the invalidated-finally)\n\t\t\t\t// still takes the fallback — preserving base behavior of caching the error fallback.\n\t\t\t\tconst occupant = this.cache.get(key);\n\t\t\t\tconst clobbersSuccessor = occupant != null && occupant !== entry;\n\t\t\t\tif (result != null) {\n\t\t\t\t\t// Replace the entry with a resolved fallback value for subsequent callers\n\t\t\t\t\tif (!clobbersSuccessor) {\n\t\t\t\t\t\tconst errorNow = Date.now();\n\t\t\t\t\t\tthis.cache.set(key, {\n\t\t\t\t\t\t\tpromise: Promise.resolve(result.value),\n\t\t\t\t\t\t\tcreated: errorNow,\n\t\t\t\t\t\t\taccessed: errorNow,\n\t\t\t\t\t\t\tcreateTTL: result.createTTL ?? options.createTTL,\n\t\t\t\t\t\t\taccessTTL: options.accessTTL,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\t// suppress: return fallback to first caller; otherwise re-throw\n\t\t\t\t\tif (result.suppress) return result.value;\n\t\t\t\t} else {\n\t\t\t\t\tdeleteIfOwned(this.cache, key, entry!);\n\t\t\t\t}\n\t\t\t\tthrow ex;\n\t\t\t});\n\n\t\t\tentry = {\n\t\t\t\tpromise: errorHandled,\n\t\t\t\tcreated: now,\n\t\t\t\taccessed: now,\n\t\t\t\tcreateTTL: options.createTTL,\n\t\t\t\taccessTTL: options.accessTTL,\n\t\t\t};\n\t\t\tthis.cache.set(key, entry);\n\t\t} else if (options?.errorTTL != null) {\n\t\t\t// Cache resolved undefined for subsequent callers, re-throw to first caller\n\t\t\tconst errorTTL = options.errorTTL;\n\t\t\tconst errorHandled = promise.catch((ex: unknown) => {\n\t\t\t\t// Overwrite unless a successor now owns the key (empty key still takes it — see onError above).\n\t\t\t\tconst occupant = this.cache.get(key);\n\t\t\t\tif (occupant == null || occupant === entry) {\n\t\t\t\t\tconst errorNow = Date.now();\n\t\t\t\t\tthis.cache.set(key, {\n\t\t\t\t\t\tpromise: Promise.resolve(undefined as V),\n\t\t\t\t\t\tcreated: errorNow,\n\t\t\t\t\t\taccessed: errorNow,\n\t\t\t\t\t\tcreateTTL: errorTTL,\n\t\t\t\t\t\taccessTTL: options.accessTTL,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tthrow ex;\n\t\t\t});\n\n\t\t\tentry = {\n\t\t\t\tpromise: errorHandled,\n\t\t\t\tcreated: now,\n\t\t\t\taccessed: now,\n\t\t\t\tcreateTTL: options.createTTL,\n\t\t\t\taccessTTL: options.accessTTL,\n\t\t\t};\n\t\t\tthis.cache.set(key, entry);\n\t\t} else {\n\t\t\tentry = {\n\t\t\t\tpromise: promise,\n\t\t\t\tcreated: now,\n\t\t\t\taccessed: now,\n\t\t\t\tcreateTTL: options.createTTL,\n\t\t\t\taccessTTL: options.accessTTL,\n\t\t\t};\n\t\t\tthis.cache.set(key, entry);\n\n\t\t\tif (options?.expireOnError ?? true) {\n\t\t\t\t// Ownership-guarded: a hard `delete(key)` + fresh `getOrCreate` replacing us while in flight must\n\t\t\t\t// not have its entry evicted by our rejection.\n\t\t\t\tconst ourEntry = entry;\n\t\t\t\tpromise.catch(() => deleteIfOwned(this.cache, key, ourEntry));\n\t\t\t}\n\t\t}\n\n\t\tconst evictWhen = options?.evictWhen;\n\t\tif (evictWhen != null) {\n\t\t\tconst candidate = entry;\n\t\t\tvoid candidate.promise\n\t\t\t\t.then(value => evictWhen(value))\n\t\t\t\t.then(evict => {\n\t\t\t\t\tif (evict) {\n\t\t\t\t\t\tdeleteIfOwned(this.cache, key, candidate);\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t// A retention policy must not surface a rejection independently of the caller-facing operation.\n\t\t\t\t.catch(() => {});\n\t\t}\n\n\t\t// Clean up expired entries and enforce capacity limit in one pass\n\t\tif (this.cache.size > 1) {\n\t\t\tqueueMicrotask(() => this.cleanup());\n\t\t}\n\n\t\treturn this.attachCallerLifecycle(entry.promise, cancellation, cleanup);\n\t}\n\n\tprivate attachCallerLifecycle(\n\t\tpromise: Promise<V>,\n\t\tcancellation: AbortSignal | undefined,\n\t\tcleanup: (() => void) | undefined,\n\t): Promise<V> {\n\t\tconst returned = cancellation != null ? raceWithSignal(promise, cancellation) : promise;\n\t\tif (cleanup != null) {\n\t\t\t// Release this caller's aggregate slot when their wait ends.\n\t\t\t// Attach .catch to swallow any rejection in the cleanup chain so it doesn't surface\n\t\t\t// as an unhandled rejection separate from the caller's own handling of `returned`.\n\t\t\tvoid returned.finally(cleanup).catch(() => {});\n\t\t}\n\t\treturn returned;\n\t}\n\n\tprivate cleanup(): void {\n\t\tconst now = Date.now();\n\t\tconst capacity = this.options.capacity;\n\n\t\t// If no capacity limit, just remove expired entries\n\t\tif (capacity == null) {\n\t\t\tfor (const [key, entry] of this.cache.entries()) {\n\t\t\t\tif (this.expired(entry, now)) {\n\t\t\t\t\tthis.cache.delete(key);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Single pass: collect non-expired entries and find LRU candidates\n\t\tconst entries: Array<[K, CacheEntry<V>]> = [];\n\t\tfor (const [key, entry] of this.cache.entries()) {\n\t\t\tif (!this.expired(entry, now)) {\n\t\t\t\tentries.push([key, entry]);\n\t\t\t} else {\n\t\t\t\tthis.cache.delete(key);\n\t\t\t}\n\t\t}\n\n\t\t// If still over capacity, remove LRU entries\n\t\tconst excess = entries.length - capacity;\n\t\tif (excess > 0) {\n\t\t\t// Sort by accessed time (oldest first) and remove the excess\n\t\t\t// Note: O(N log N) sort — acceptable at current cache sizes; replace with a linked-list LRU if capacity grows\n\t\t\tentries.sort((a, b) => a[1].accessed - b[1].accessed);\n\t\t\tfor (let i = 0; i < excess; i++) {\n\t\t\t\tthis.cache.delete(entries[i][0]);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate expired(entry: CacheEntry<V>, now: number): boolean {\n\t\treturn (\n\t\t\t(entry.createTTL != null && now - entry.created >= entry.createTTL) ||\n\t\t\t(entry.accessTTL != null && now - entry.accessed >= entry.accessTTL)\n\t\t);\n\t}\n\n\t/**\n\t * Sets a promise in the cache.\n\t * The promise will use the default options from the cache constructor, unless overridden.\n\t *\n\t * @param key - The cache key\n\t * @param promise - The promise to cache\n\t * @param options - Optional TTL options that override the defaults\n\t */\n\tset(key: K, promise: Promise<V>, options?: { accessTTL?: number; createTTL?: number }): void {\n\t\tconst now = Date.now();\n\t\tconst entry: CacheEntry<V> = {\n\t\t\tpromise: promise,\n\t\t\tcreated: now,\n\t\t\taccessed: now,\n\t\t\tcreateTTL: options?.createTTL ?? this.options.createTTL,\n\t\t\taccessTTL: options?.accessTTL ?? this.options.accessTTL,\n\t\t};\n\t\tthis.cache.set(key, entry);\n\n\t\tif (this.options.expireOnError ?? true) {\n\t\t\tpromise.catch(() => deleteIfOwned(this.cache, key, entry));\n\t\t}\n\t}\n}\n\n/**\n * A Map-like wrapper for promises that automatically removes failed promises from the cache.\n * This provides a drop-in replacement for Map<K, Promise<V>> with automatic error cleanup.\n */\nexport class PromiseMap<K, V> {\n\tprivate readonly cache = new Map<K, Promise<V>>();\n\tprivate readonly aborts = new Map<K, AbortAggregate>();\n\tprivate readonly controllers = new Map<K, CacheController>();\n\t/** Each cached promise's value once it fulfills, for {@link getSettled} */\n\tprivate readonly settled = new WeakMap<Promise<V>, { value: V }>();\n\n\tget [Symbol.toStringTag](): string {\n\t\treturn 'PromiseMap';\n\t}\n\n\t/**\n\t * Gets a promise from the cache or creates a new one using the factory function.\n\t * Failed promises are automatically removed from the cache.\n\t *\n\t * When `cancellation` is provided:\n\t * - The factory receives an aggregate `AbortSignal` that only fires when **all** callers cancel\n\t * - The returned promise rejects with `CancellationError` if this caller's signal fires\n\t * - Other callers sharing the same cached promise are unaffected\n\t *\n\t * Callers without a `cancellation` signal count as \"permanent\" subscribers — the aggregate\n\t * cannot fire while at least one is active. The slot is released when the caller's returned\n\t * promise settles.\n\t */\n\tgetOrCreate(\n\t\tkey: K,\n\t\tfactory: (cacheable: CacheController, cancellation?: AbortSignal) => Promise<V>,\n\t\tcancellation?: AbortSignal,\n\t): Promise<V> {\n\t\tif (cancellation?.aborted) return Promise.reject(new CancellationError());\n\n\t\tconst cached = this.cache.get(key);\n\t\tif (cached != null) {\n\t\t\t// Cache hit — but don't join an entry whose aggregate has already permanently aborted (all prior\n\t\t\t// callers cancelled, so the shared run is failing): joining hands this caller a doomed promise.\n\t\t\t// Fall through to a fresh entry; the doomed entry's late settle is `deleteIfOwned`-guarded.\n\t\t\tconst existingAborts = this.aborts.get(key);\n\t\t\tif (existingAborts == null || !existingAborts.signal.aborted) {\n\t\t\t\tconst cleanup = existingAborts != null ? existingAborts.add(cancellation) : undefined;\n\t\t\t\treturn this.attachCallerLifecycle(cached, cancellation, cleanup);\n\t\t\t}\n\t\t}\n\n\t\t// Cache miss — always create the aggregate so subsequent callers (with or without signals)\n\t\t// can contribute, and so delete/invalidate has a controller to interact with\n\t\tconst cacheable = new CacheController();\n\t\tconst aborts = new AbortAggregate();\n\t\tconst cleanup = aborts.add(cancellation);\n\t\tthis.aborts.set(key, aborts);\n\t\tthis.controllers.set(key, cacheable);\n\n\t\tconst promise = factory(cacheable, aborts.signal);\n\t\tthis.track(key, promise);\n\t\tvoid promise\n\t\t\t.finally(() => {\n\t\t\t\tif (cacheable.invalidated) {\n\t\t\t\t\tdeleteIfOwned(this.cache, key, promise);\n\t\t\t\t}\n\t\t\t\taborts.dispose();\n\t\t\t\tdeleteIfOwned(this.aborts, key, aborts);\n\t\t\t\tdeleteIfOwned(this.controllers, key, cacheable);\n\t\t\t})\n\t\t\t// Swallow the cleanup chain's rejection so it doesn't surface as an unhandled rejection\n\t\t\t// separate from the caller-facing promise's own handling (e.g. on cancellation).\n\t\t\t.catch(() => {});\n\n\t\tthis.cache.set(key, promise);\n\n\t\treturn this.attachCallerLifecycle(promise, cancellation, cleanup);\n\t}\n\n\tprivate attachCallerLifecycle(\n\t\tpromise: Promise<V>,\n\t\tcancellation: AbortSignal | undefined,\n\t\tcleanup: (() => void) | undefined,\n\t): Promise<V> {\n\t\tconst returned = cancellation != null ? raceWithSignal(promise, cancellation) : promise;\n\t\tif (cleanup != null) {\n\t\t\t// Release this caller's aggregate slot when their wait ends.\n\t\t\t// Attach .catch to swallow any rejection in the cleanup chain so it doesn't surface\n\t\t\t// as an unhandled rejection separate from the caller's own handling of `returned`.\n\t\t\tvoid returned.finally(cleanup).catch(() => {});\n\t\t}\n\t\treturn returned;\n\t}\n\n\t/**\n\t * Marks a cached entry as invalidated.\n\t * - If the entry has a `CacheController` (created via `getOrCreate`), it is soft-invalidated:\n\t * existing waiters complete normally, new callers joining during the remainder of the\n\t * in-flight operation share the same promise, and the entry self-evicts on settle.\n\t * - If the entry was set via plain `.set()` (no controller), it is hard-deleted — there is\n\t * no in-flight factory to ride along with.\n\t *\n\t * No-op if the key is not cached.\n\t */\n\tinvalidate(key: K): void {\n\t\tif (this.controllers.has(key)) {\n\t\t\tthis.controllers.get(key)!.invalidate();\n\t\t} else {\n\t\t\tthis.delete(key);\n\t\t}\n\t}\n\n\t/**\n\t * Gets a promise from the cache, or undefined if not found.\n\t */\n\tget(key: K): Promise<V> | undefined {\n\t\treturn this.cache.get(key);\n\t}\n\n\t/**\n\t * Sets a promise in the cache. The promise will be automatically removed if it fails.\n\t */\n\tset(key: K, promise: Promise<V>): this {\n\t\tthis.cache.set(key, promise);\n\t\tthis.track(key, promise);\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * The value `key`'s cached promise fulfilled with, or `undefined` while it is pending (or nothing is cached), so a\n\t * caller can tell a settled entry from a read still in flight without joining it.\n\t */\n\tgetSettled(key: K): { value: V } | undefined {\n\t\tconst promise = this.cache.get(key);\n\t\treturn promise != null ? this.settled.get(promise) : undefined;\n\t}\n\n\tprivate track(key: K, promise: Promise<V>): void {\n\t\tpromise.then(\n\t\t\tvalue => {\n\t\t\t\tthis.settled.set(promise, { value: value });\n\t\t\t},\n\t\t\t// Automatically remove failed promises from the cache (ownership-guarded — see `deleteIfOwned`).\n\t\t\t() => deleteIfOwned(this.cache, key, promise),\n\t\t);\n\t}\n\n\t/**\n\t * Checks if a key exists in the cache.\n\t */\n\thas(key: K): boolean {\n\t\treturn this.cache.has(key);\n\t}\n\n\t/**\n\t * Removes a key from the cache.\n\t * Does NOT dispose the abort aggregate: while a factory is in flight, disposing would detach\n\t * its callers' cancellation and orphan the underlying op (a later caller-abort could no longer cancel the\n\t * running `git status`). The factory's own settle handler disposes the aggregate (see `getOrCreate`). */\n\tdelete(key: K): boolean {\n\t\tthis.aborts.delete(key);\n\t\tthis.controllers.delete(key);\n\t\treturn this.cache.delete(key);\n\t}\n\n\t/**\n\t * Clears all entries. Like `delete`, does NOT dispose in-flight abort aggregates — that would detach a running\n\t * op's cancellation (a later caller-abort couldn't cancel it); each factory's settle handler disposes its own.\n\t */\n\tclear(): void {\n\t\tthis.aborts.clear();\n\t\tthis.controllers.clear();\n\t\tthis.cache.clear();\n\t}\n\n\t/**\n\t * Returns the number of entries in the cache.\n\t */\n\tget size(): number {\n\t\treturn this.cache.size;\n\t}\n\n\t/**\n\t * Returns an iterator for the cache keys.\n\t */\n\tkeys(): IterableIterator<K> {\n\t\treturn this.cache.keys();\n\t}\n\n\t/**\n\t * Returns an iterator for the cache values.\n\t */\n\tvalues(): IterableIterator<Promise<V>> {\n\t\treturn this.cache.values();\n\t}\n\n\t/**\n\t * Returns an iterator for the cache entries.\n\t */\n\tentries(): IterableIterator<[K, Promise<V>]> {\n\t\treturn this.cache.entries();\n\t}\n\n\t/**\n\t * Executes a provided function once for each cache entry.\n\t */\n\tforEach(callbackfn: (value: Promise<V>, key: K, map: Map<K, Promise<V>>) => void, thisArg?: any): void {\n\t\tthis.cache.forEach(callbackfn, thisArg);\n\t}\n\n\t[Symbol.iterator](): IterableIterator<[K, Promise<V>]> {\n\t\treturn this.cache[Symbol.iterator]();\n\t}\n}\n\n/**\n * A two-level cache that organizes PromiseCaches by repository path.\n * Automatically creates and manages inner PromiseCaches per repository.\n *\n * This is useful for caching data that varies by both repository and some other key,\n * with TTL and capacity management provided by PromiseCache.\n */\nexport class RepoPromiseCacheMap<K, V> {\n\tprivate readonly cache = new Map<string, PromiseCache<K, V>>();\n\tprivate readonly options: PromiseCacheOptions;\n\n\tconstructor(options?: PromiseCacheOptions) {\n\t\tthis.options = { expireOnError: true, ...options };\n\t}\n\n\tget [Symbol.toStringTag](): string {\n\t\treturn 'RepoPromiseCacheMap';\n\t}\n\n\t/**\n\t * Gets a promise from the cache or creates a new one using the factory function.\n\t * Automatically creates the inner PromiseCache for the repository if it doesn't exist.\n\t *\n\t * @param repoPath - The repository path (outer key)\n\t * @param key - The cache key within the repository (inner key)\n\t * @param factory - Factory function to create the value if not cached\n\t * @param options - Optional TTL and error handling options that override the defaults\n\t */\n\tgetOrCreate(\n\t\trepoPath: string,\n\t\tkey: K,\n\t\tfactory: (cacheable: CacheController, cancellation?: AbortSignal) => Promise<V>,\n\t\toptions?: {\n\t\t\t/** Per-caller AbortSignal — the factory receives an aggregate signal that only fires when all callers cancel */\n\t\t\tcancellation?: AbortSignal;\n\t\t\t/** TTL (time-to-live) in milliseconds since creation */\n\t\t\tcreateTTL?: number;\n\t\t\t/** TTL (time-to-live) in milliseconds since last access */\n\t\t\taccessTTL?: number;\n\t\t\t/** Whether to expire the entry if the promise fails */\n\t\t\texpireOnError?: boolean;\n\t\t\t/**\n\t\t\t * Called after the factory resolves. If the predicate resolves to true, the entry is evicted while\n\t\t\t * the current caller still receives the resolved value.\n\t\t\t */\n\t\t\tevictWhen?: (value: V) => boolean | Promise<boolean>;\n\t\t\t/**\n\t\t\t * TTL (time-to-live) in milliseconds for caching errors. When the factory rejects and `errorTTL` is set,\n\t\t\t * the cache entry is replaced with `Promise.resolve(undefined)` for subsequent callers, and the error\n\t\t\t * is re-thrown to the first caller. After `errorTTL` expires, the next call retries.\n\t\t\t * Only used when `onError` is not provided.\n\t\t\t */\n\t\t\terrorTTL?: number;\n\t\t\t/**\n\t\t\t * Called when the factory promise rejects. Return a fallback result to cache\n\t\t\t * instead of expiring. The fallback value is cached with the specified `createTTL`\n\t\t\t * (defaults to the cache's `createTTL`). By default, the error is re-thrown to the\n\t\t\t * first caller after caching the fallback. Set `suppress: true` to return the fallback\n\t\t\t * value to the first caller instead. Return `undefined` to expire normally and re-throw.\n\t\t\t */\n\t\t\tonError?: (error: unknown) => { value: V; createTTL?: number; suppress?: boolean } | undefined;\n\t\t},\n\t): Promise<V> {\n\t\tlet repoCache = this.cache.get(repoPath);\n\t\tif (repoCache == null) {\n\t\t\trepoCache = new PromiseCache<K, V>(this.options);\n\t\t\tthis.cache.set(repoPath, repoCache);\n\t\t}\n\n\t\treturn repoCache.getOrCreate(key, factory, options);\n\t}\n\n\t/**\n\t * Gets a promise from the cache without creating it.\n\t *\n\t * @param repoPath - The repository path (outer key)\n\t * @param key - The cache key within the repository (inner key)\n\t * @returns The cached promise, or undefined if not cached\n\t */\n\tget(repoPath: string, key: K): Promise<V> | undefined {\n\t\tconst repoCache = this.cache.get(repoPath);\n\t\tif (repoCache == null) return undefined;\n\n\t\treturn repoCache.get(key);\n\t}\n\n\t/**\n\t * Sets a promise in the cache.\n\t *\n\t * @param repoPath - The repository path (outer key)\n\t * @param key - The cache key within the repository (inner key)\n\t * @param promise - The promise to cache\n\t * @param options - Optional TTL options that override the defaults\n\t */\n\tset(repoPath: string, key: K, promise: Promise<V>, options?: { accessTTL?: number; createTTL?: number }): void {\n\t\tlet repoCache = this.cache.get(repoPath);\n\t\tif (repoCache == null) {\n\t\t\trepoCache = new PromiseCache<K, V>(this.options);\n\t\t\tthis.cache.set(repoPath, repoCache);\n\t\t}\n\n\t\trepoCache.set(key, promise, options);\n\t}\n\n\t/**\n\t * Deletes a specific key from a repository's cache, or all keys for a repository.\n\t *\n\t * @param repoPath - The repository path\n\t * @param key - Optional specific key to delete. If omitted, deletes the entire repository cache.\n\t * @returns true if something was deleted, false otherwise\n\t */\n\tdelete(repoPath: string, key?: K): boolean {\n\t\tif (key === undefined) {\n\t\t\treturn this.cache.delete(repoPath);\n\t\t}\n\n\t\tconst repoCache = this.cache.get(repoPath);\n\t\tif (repoCache == null) return false;\n\n\t\trepoCache.delete(key);\n\t\treturn true;\n\t}\n\n\t/**\n\t * Marks a cached entry as invalidated. See `PromiseCache.invalidate` for semantics.\n\t *\n\t * @param repoPath - The repository path\n\t * @param key - Optional specific key to invalidate. If omitted, invalidates every entry for the repository.\n\t */\n\tinvalidate(repoPath: string, key?: K): void {\n\t\tconst repoCache = this.cache.get(repoPath);\n\t\tif (repoCache == null) return;\n\n\t\tif (key === undefined) {\n\t\t\tfor (const k of repoCache.keys()) {\n\t\t\t\trepoCache.invalidate(k);\n\t\t\t}\n\t\t} else {\n\t\t\trepoCache.invalidate(key);\n\t\t}\n\t}\n\n\t/**\n\t * Deletes all keys matching a prefix from a repository's cache.\n\t * @param repoPath - The repository path\n\t * @param prefix - Key prefix to match\n\t * @returns true if something was deleted\n\t */\n\tdeleteByKeyPrefix(this: RepoPromiseCacheMap<string, V>, repoPath: string, prefix: string): boolean {\n\t\tconst repoCache = this.cache.get(repoPath);\n\t\tif (repoCache == null) return false;\n\n\t\tlet deleted = false;\n\t\tfor (const key of repoCache.keys()) {\n\t\t\tif (key.startsWith(prefix)) {\n\t\t\t\trepoCache.delete(key);\n\t\t\t\tdeleted = true;\n\t\t\t}\n\t\t}\n\t\treturn deleted;\n\t}\n\n\t/**\n\t * Invalidates all keys matching a prefix from a repository's cache. See `PromiseCache.invalidate`\n\t * for semantics — in-flight entries are marked invalidated and shared with new callers; settled\n\t * entries are hard-deleted. Prefer over `deleteByKeyPrefix` when the eviction is a \"data has\n\t * changed\" event and in-flight factories' results would still be valid for current waiters.\n\t * @param repoPath - The repository path\n\t * @param prefix - Key prefix to match\n\t * @returns true if something was invalidated\n\t */\n\tinvalidateByKeyPrefix(this: RepoPromiseCacheMap<string, V>, repoPath: string, prefix: string): boolean {\n\t\tconst repoCache = this.cache.get(repoPath);\n\t\tif (repoCache == null) return false;\n\n\t\tlet invalidated = false;\n\t\tfor (const key of repoCache.keys()) {\n\t\t\tif (key.startsWith(prefix)) {\n\t\t\t\trepoCache.invalidate(key);\n\t\t\t\tinvalidated = true;\n\t\t\t}\n\t\t}\n\t\treturn invalidated;\n\t}\n\n\t/**\n\t * Clears all caches for all repositories.\n\t */\n\tclear(): void {\n\t\tthis.cache.clear();\n\t}\n\n\t/**\n\t * Returns the total number of entries across all repository caches.\n\t */\n\tget entryCount(): number {\n\t\tlet count = 0;\n\t\tfor (const repoCache of this.cache.values()) {\n\t\t\tcount += repoCache.size;\n\t\t}\n\t\treturn count;\n\t}\n\n\t/**\n\t * Returns the number of repositories in the cache.\n\t */\n\tget size(): number {\n\t\treturn this.cache.size;\n\t}\n}\n\n/**\n * A two-level cache that organizes PromiseMaps by repository path.\n * Automatically creates and manages inner PromiseMaps per repository.\n *\n * This is useful for caching data that varies by both repository and some other key,\n * with simple promise deduplication and automatic error cleanup.\n */\nexport class RepoPromiseMap<K, V> {\n\tprivate readonly cache = new Map<string, PromiseMap<K, V>>();\n\n\tget [Symbol.toStringTag](): string {\n\t\treturn 'RepoPromiseMap';\n\t}\n\n\t/**\n\t * Gets a promise from the cache or creates a new one using the factory function.\n\t * Automatically creates the inner PromiseMap for the repository if it doesn't exist.\n\t *\n\t * @param repoPath - The repository path (outer key)\n\t * @param key - The cache key within the repository (inner key)\n\t * @param factory - Factory function to create the value if not cached\n\t */\n\tgetOrCreate(\n\t\trepoPath: string,\n\t\tkey: K,\n\t\tfactory: (cacheable: CacheController, cancellation?: AbortSignal) => Promise<V>,\n\t\tcancellation?: AbortSignal,\n\t): Promise<V> {\n\t\tlet repoCache = this.cache.get(repoPath);\n\t\tif (repoCache == null) {\n\t\t\trepoCache = new PromiseMap<K, V>();\n\t\t\tthis.cache.set(repoPath, repoCache);\n\t\t}\n\n\t\treturn repoCache.getOrCreate(key, factory, cancellation);\n\t}\n\n\t/**\n\t * Gets a promise from the cache without creating it.\n\t *\n\t * @param repoPath - The repository path (outer key)\n\t * @param key - The cache key within the repository (inner key)\n\t * @returns The cached promise, or undefined if not found\n\t */\n\tget(repoPath: string, key: K): Promise<V> | undefined {\n\t\tconst repoCache = this.cache.get(repoPath);\n\t\treturn repoCache?.get(key);\n\t}\n\n\t/**\n\t * Deletes a specific key from a repository's cache, or all keys for a repository.\n\t *\n\t * @param repoPath - The repository path\n\t * @param key - Optional specific key to delete. If omitted, deletes the entire repository cache.\n\t * @returns true if something was deleted, false otherwise\n\t */\n\tdelete(repoPath: string, key?: K): boolean {\n\t\tif (key === undefined) {\n\t\t\treturn this.cache.delete(repoPath);\n\t\t}\n\n\t\tconst repoCache = this.cache.get(repoPath);\n\t\tif (repoCache == null) return false;\n\n\t\treturn repoCache.delete(key);\n\t}\n\n\t/**\n\t * Marks a cached entry as invalidated. See `PromiseMap.invalidate` for semantics.\n\t *\n\t * @param repoPath - The repository path\n\t * @param key - Optional specific key to invalidate. If omitted, invalidates every entry for the repository.\n\t */\n\tinvalidate(repoPath: string, key?: K): void {\n\t\tconst repoCache = this.cache.get(repoPath);\n\t\tif (repoCache == null) return;\n\n\t\tif (key === undefined) {\n\t\t\tfor (const k of repoCache.keys()) {\n\t\t\t\trepoCache.invalidate(k);\n\t\t\t}\n\t\t} else {\n\t\t\trepoCache.invalidate(key);\n\t\t}\n\t}\n\n\t/**\n\t * Clears all caches for all repositories.\n\t */\n\tclear(): void {\n\t\tthis.cache.clear();\n\t}\n\n\t/**\n\t * Returns the number of repositories in the cache.\n\t */\n\tget size(): number {\n\t\treturn this.cache.size;\n\t}\n}\n"],"mappings":"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"}
|
package/docs/integrations.md
CHANGED
|
@@ -204,8 +204,10 @@ The hosts with a cheap check select only the change state:
|
|
|
204
204
|
full read, more often than the merge request itself does.
|
|
205
205
|
|
|
206
206
|
- **Azure DevOps and Azure DevOps Server:** work items in one batch request per project per 200 ids; an id the batch
|
|
207
|
-
leaves out falls through to its own full read.
|
|
208
|
-
|
|
207
|
+
leaves out falls through to its own full read. Both reads classify the states they read through the same cached
|
|
208
|
+
work item type states (see [Tracker issue state](#tracker-issue-state)), so a check after a full read reads them
|
|
209
|
+
again only once that cache expires. Pull requests can't be batched by id, so the check is one request per pull
|
|
210
|
+
request instead of the full read's two. Azure DevOps keeps no update time on a pull request, so its etag
|
|
209
211
|
also carries a revision: a 64-bit hash of the title, description, target branch and every reviewer with their vote,
|
|
210
212
|
read off the same response at no extra cost. It sees state, draft, closing, new pushes and those edits, including
|
|
211
213
|
a reviewer added or removed and an optional reviewer's vote (plus mergeability when included). It can't see a
|
|
@@ -744,14 +746,22 @@ result** instead of rejecting the call. One provider's expired token never blank
|
|
|
744
746
|
`ProviderWarning.kind` (also exported as `ProviderWarningKind`) carries the classifications the facade can
|
|
745
747
|
prove from structured errors:
|
|
746
748
|
|
|
747
|
-
| `kind`
|
|
748
|
-
|
|
|
749
|
-
| `auth`
|
|
750
|
-
| `rate-limit`
|
|
751
|
-
| `not-found`
|
|
752
|
-
| `no-connection`
|
|
753
|
-
| `unsupported`
|
|
754
|
-
| `
|
|
749
|
+
| `kind` | Meaning | Reasonable response |
|
|
750
|
+
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
751
|
+
| `auth` | Token rejected (401/403 that isn't a throttle). | Prompt to reconnect that connection. A scoped one is narrower: see `scope` below. |
|
|
752
|
+
| `rate-limit` | Throttled (429, or a 403 whose body says so). | Back off and retry; keep the last snapshot. |
|
|
753
|
+
| `not-found` | 404/410/422 on the requested scope. | Drop that scope; don't reconnect. |
|
|
754
|
+
| `no-connection` | The requested `connectionId`/`domain` doesn't resolve. | Re-resolve the target or re-authenticate. |
|
|
755
|
+
| `unsupported` | The provider can't serve this capability: a surface, filter, sort, state, search criterion or option combination it can't express. | Hide or disable the feature for that provider; don't retry or reconnect. |
|
|
756
|
+
| `unreadable-items` | Items the provider returned that couldn't be read were left out (a Jira issue `@gitkraken/provider-apis` can't parse); the rest of its `scope` was read. `unreadableItems` names them. | Show the read as incomplete; retrying returns the same gap. A cut read always adds a warning of another kind. |
|
|
757
|
+
| `other` | Catch-all: malformed or contradictory input, truncation, upstream/network failure, or an unclassified error. Read `omission` before treating one as a failure. | Preserve the warning and use the result flags; do not assume it is benign or non-retryable. |
|
|
758
|
+
|
|
759
|
+
An `unreadable-items` warning carries `unreadableItems: { keys, complete }`: the identifiers of the items left out (Jira
|
|
760
|
+
issue keys), and whether `keys` names all of them. `complete` is `false` when the provider named only some (Jira names
|
|
761
|
+
at most ten per page) or named one by its position, so an identifier absent from `keys` is proven read only when every
|
|
762
|
+
such warning is `complete`. The read still sets `fetchFailed`, but it retracts no omission: no request failed. On an
|
|
763
|
+
issue tracker's project reads it never stands for a read that was also cut short: a project or relationship stopped
|
|
764
|
+
by the page backstop, a stalled continuation or a failed page always adds a warning of another kind for it.
|
|
755
765
|
|
|
756
766
|
`isAuth` is a convenience mirror of `kind === 'auth'`. **Collapsing `kind` into that boolean loses the
|
|
757
767
|
rate-limit and not-found distinctions**, which then have to be re-derived from raw provider prose.
|
|
@@ -813,7 +823,9 @@ one extra check a minute, and a revocation can take up to a minute to surface as
|
|
|
813
823
|
|
|
814
824
|
`scope.resourceId` is the resource as the read addressed it: its id on most reads, its name on the few that
|
|
815
825
|
address it by name (Azure DevOps' repo-scoped reads, Bitbucket's workspace reads). Match it against both
|
|
816
|
-
`ProviderOrganization.id` and `name
|
|
826
|
+
`ProviderOrganization.id` and `name`, and against a project's `orgId` where it has one. A Jira project read's
|
|
827
|
+
`scope.projectId` is the project as the read addressed it: the key on Jira Cloud (a project's `name` in
|
|
828
|
+
`listProjects`), the numeric id on Jira Data Center.
|
|
817
829
|
|
|
818
830
|
Warnings also dedup on `scope` and `cause`, so failures of two scopes stay two warnings.
|
|
819
831
|
|
|
@@ -1120,9 +1132,9 @@ HTTPS links are rebuilt from the configured URL and an SSH link, whose host and
|
|
|
1120
1132
|
Jira Data Center inherits Jira Cloud's row, except that it refuses `getCurrentAccount`. Its reads are addressed per host, so `domain` selects the instance and
|
|
1121
1133
|
every read below is scoped to that one connection; a paged read that omits it gets the primary configured host,
|
|
1122
1134
|
while `getIssuesBatch` refuses instead (see §4). `listOrgs` returns exactly one resource — the
|
|
1123
|
-
instance itself, synthesized from the configured host rather than fetched — and its projects carry the
|
|
1124
|
-
as `key
|
|
1125
|
-
|
|
1135
|
+
instance itself, synthesized from the configured host rather than fetched — and its projects carry the project key
|
|
1136
|
+
as `key` (the numeric id when the server reports none), which is what `listProjects` publishes as `name` and what
|
|
1137
|
+
`project` selects; reads address the project by id regardless. No autolinks are registered yet.
|
|
1126
1138
|
|
|
1127
1139
|
¹ Branches in the base repository only: a target whose `headOwner` names another owner is refused, since Bitbucket DC
|
|
1128
1140
|
finds a branch's pull requests only through the repository the branch lives in, and an Azure DevOps fork shares its
|
|
@@ -1182,28 +1194,52 @@ DevOps Services could express most of it, so the gap is unimplemented rather tha
|
|
|
1182
1194
|
carry the provider's own workflow state in the optional `providerState` field; it is display data and is not added to
|
|
1183
1195
|
`IssueOrPullRequest`, because pull requests' normalized `'opened' | 'closed' | 'merged'` state is complete.
|
|
1184
1196
|
|
|
1185
|
-
The field mirrors what `provider-apis` provides
|
|
1186
|
-
omitted when the provider returns `null`, and `category` is only
|
|
1197
|
+
The field mirrors what `provider-apis` provides, except for Azure DevOps' `category` (below). `name` is the
|
|
1198
|
+
provider's untranslated display name, `color` is omitted when the provider returns `null`, and `category` is only
|
|
1199
|
+
present where the provider supplies it:
|
|
1187
1200
|
|
|
1188
1201
|
| Provider | `name` | `color` | `category` |
|
|
1189
1202
|
| ------------ | ------------------- | ------- | ---------------------------------------- |
|
|
1190
1203
|
| Jira Cloud | Status name | Yes | Yes, from the stable status-category key |
|
|
1191
|
-
| Jira DC | Status name | Yes |
|
|
1204
|
+
| Jira DC | Status name | Yes | Yes, from the stable status-category key |
|
|
1192
1205
|
| Linear | Workflow state name | Yes | Yes, mapped from the Linear state type |
|
|
1193
1206
|
| Trello | Card list name | No | No |
|
|
1194
|
-
| Azure DevOps | Work-item state | No |
|
|
1207
|
+
| Azure DevOps | Work-item state | No | Yes, from the work item type's states |
|
|
1195
1208
|
| GitHub | `open` / `closed` | No | No |
|
|
1196
1209
|
| GitLab | `opened` / `closed` | No | No |
|
|
1197
1210
|
|
|
1198
|
-
For Jira,
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1211
|
+
For Jira, every read maps the category from the status category's `key` (`new`, `indeterminate`, `done`), which Jira
|
|
1212
|
+
does not localize, never from its translated `name`, so a Spanish `Listo` closes an issue and `Por hacer` doesn't.
|
|
1213
|
+
Jira's fourth category, "No Category" (`undefined`), and an unknown key read as `TO_DO`: an issue in one is open, as
|
|
1214
|
+
the open-state JQL (`statusCategory != Done`) lists it. Jira never reports `closedDate`, so `closed` is decided by the
|
|
1215
|
+
category alone.
|
|
1216
|
+
|
|
1217
|
+
`category` is `TO_DO`, `IN_PROGRESS`, `DONE` or `CANCELED`. `CANCELED` is abandoned work (canceled, duplicate,
|
|
1218
|
+
removed): it closes an issue as `DONE` does, and stays its own value so a consumer can tell the two outcomes apart.
|
|
1219
|
+
Linear reports its canceled and duplicate states as `CANCELED`.
|
|
1220
|
+
|
|
1221
|
+
Azure DevOps reports a state in its `Removed` category as `CANCELED`, `Completed` and `Resolved` as `DONE`,
|
|
1222
|
+
`InProgress` as `IN_PROGRESS` and `Proposed` as `TO_DO`. No work item read carries the category, so every one (by id,
|
|
1223
|
+
the cheap etag check and the project list, on Azure DevOps Server too) classifies its rows from their type's states
|
|
1224
|
+
(`GET …/_apis/wit/workItemTypes/{type}/states?api-version=6.0-preview.1`, the preview version Azure DevOps Server 2020
|
|
1225
|
+
requires), keyed on the raw `System.State`, and caches each read for five minutes per host, project, type and
|
|
1226
|
+
credential. The category, never the name, decides: a state named `Canceled` in the `Completed` category is `DONE`, and
|
|
1227
|
+
a state its type doesn't list has no category. When a states read fails, a project list keeps its rows (the failed
|
|
1228
|
+
type's without a category) and reports the project as a partial collection with a failure scoped to it; a read by id
|
|
1229
|
+
and the cheap etag check fail instead. List selection uses the same metadata before pagination, matching both
|
|
1230
|
+
work item type and state name. A closed read includes Removed, Completed and Resolved categories even without closed
|
|
1231
|
+
or resolved dates. Failed workflows fall back to dates with partial metadata; failures are cached for 30 seconds
|
|
1232
|
+
before retrying. List, identity and native presentation reads share one cache, including workflow expiry and HTTP
|
|
1233
|
+
proxy invalidation.
|
|
1234
|
+
|
|
1235
|
+
`listIssuesPage` accepts `state: 'open' | 'closed' | 'all'` for Azure DevOps Services and Server, on repository,
|
|
1236
|
+
project and account-wide reads. Omitted means open only. `getSupportedFilters(providerId).issueStates` declares this
|
|
1237
|
+
capability. Other git hosts retain their open-only list default and refuse non-open state requests on this facade.
|
|
1238
|
+
|
|
1239
|
+
A known Azure workflow category determines normalized `state` and `closed`, with `CANCELED` closing like `DONE`.
|
|
1240
|
+
A historical closed date remains available as data but does not close an issue whose category is now open.
|
|
1241
|
+
Identity and list reads fall back to the closed date only when the category is unknown. Other providers keep their
|
|
1242
|
+
existing derivation.
|
|
1207
1243
|
|
|
1208
1244
|
### Sprints and iterations
|
|
1209
1245
|
|
|
@@ -1281,14 +1317,27 @@ Markdown by this package.
|
|
|
1281
1317
|
- **Azure DevOps** — org + project scoped. Repo-scoped reads accept one org per call. Account-wide reads
|
|
1282
1318
|
drain every project of every org and return one aggregate page; a failed project becomes a scoped warning
|
|
1283
1319
|
while its siblings survive. Only Azure can narrow an account-wide issue read by `org`/`project`.
|
|
1284
|
-
`resolveRepository` needs a project in the remote URL
|
|
1320
|
+
`resolveRepository` needs a project in the remote URL; a repository named after its project is also served without
|
|
1321
|
+
it (`{collection}/_git/{repo}`, the clone URL TFS 2018 reports), which reads as that project. Azure DevOps Server uses the trusted connection's
|
|
1285
1322
|
`baseUrl`, including its installation path. A repository's virtual directory must match that path and is
|
|
1286
1323
|
applied once; a connection addressed at the host root takes the repository's own virtual directory. An address
|
|
1287
1324
|
that also names a collection (`https://server/tfs/DefaultCollection`) reads that collection without repeating it:
|
|
1288
1325
|
discovery reports it as the one collection visible there, since Azure only lists collections at the server level,
|
|
1289
1326
|
and another collection's repositories need an address without the collection. Installation paths match case-insensitively, as IIS serves them. An SSH remote
|
|
1290
1327
|
that names no virtual directory is read against the whole address, since it can't tell a virtual directory from
|
|
1291
|
-
another collection named there.
|
|
1328
|
+
another collection named there. Behind a virtual directory the project-less form, `{vdir}/{collection}/_git/{repo}`,
|
|
1329
|
+
is equally a full path whose collection is `{vdir}` and project `{collection}`. When the connection's address is that
|
|
1330
|
+
virtual directory (or its parent, for an address naming the collection), the integration settles it against the
|
|
1331
|
+
collections discovery reports, over HTTPS and SSH alike, reading it as the project-less form only when the segment
|
|
1332
|
+
before `_git` is a listed collection and the one before it isn't; any other path reads as parsed, without discovery.
|
|
1333
|
+
When the collections can't be read the path is read as parsed, and a read that finds nothing then fails rather than
|
|
1334
|
+
answer, so the miss isn't cached.
|
|
1335
|
+
A repository scope a consumer passes (`repos` on the repo-scoped, search and count reads, and the `owner`/`project`/`repo`
|
|
1336
|
+
of the batch and branch reads) is settled against the same collections when it splits the path as the remote parser
|
|
1337
|
+
does: collection `{vdir}` and project `{collection}`, with the connection addressed at `{vdir}` (or at
|
|
1338
|
+
`{vdir}/{collection}`). The repository name is required for it, since it names the project. A scope that names its
|
|
1339
|
+
collection and project is read as given. So is one whose collections can't be read, and a batch or branch read that
|
|
1340
|
+
then finds nothing for it fails that target rather than report it absent.
|
|
1292
1341
|
|
|
1293
1342
|
Azure DevOps Server also has the filtered searches and their counts, with the collection applied exactly once
|
|
1294
1343
|
whichever way the address is written. Scope names are encoded as URL segments (collections, projects,
|
|
@@ -1330,7 +1379,13 @@ Markdown by this package.
|
|
|
1330
1379
|
|
|
1331
1380
|
- **Jira (Cloud + Data Center) / Linear / Trello** — paged by **project**, not by issue: `itemsPerPage` counts projects (default
|
|
1332
1381
|
20), each drained in full. Passing none of `page`/`cursor`/`itemsPerPage` aggregates every matched project
|
|
1333
|
-
in one page. A single project exceeding its internal drain backstop shows up as `page.truncated`.
|
|
1382
|
+
in one page. A single project exceeding its internal drain backstop shows up as `page.truncated`. On Jira it gets a
|
|
1383
|
+
warning of its own, naming it in its message and its `omission.scope`, published even when another project failed
|
|
1384
|
+
(when a request of the read failed, the omission is retracted as on every read, and only the message names it);
|
|
1385
|
+
Linear and Trello report one warning for the page, only when nothing failed.
|
|
1386
|
+
A Jira issue `@gitkraken/provider-apis` can't read is dropped from its page and reported instead: its siblings are
|
|
1387
|
+
served, with `fetchFailed`, an `unreadable-items` warning scoped to its project and naming its key, and
|
|
1388
|
+
`page.truncated` (never `narrow-scope`); a project that dropped one is not retried.
|
|
1334
1389
|
Trello's issue `id` is the card's `idShort` — unique per board only, so correlate across boards by
|
|
1335
1390
|
`nodeId`. For Trello, boards are both the resource and the project, so `listOrgs` and `listProjects`
|
|
1336
1391
|
return the same set; Linear's resource is the organization and its projects are teams.
|
|
@@ -1338,6 +1393,12 @@ Markdown by this package.
|
|
|
1338
1393
|
retries, preserve a caller's aggregate-all mode, and suppress projects already emitted before discovery
|
|
1339
1394
|
recovered. `hasMore` reports only untouched forward progress. A cursor can therefore remain with
|
|
1340
1395
|
`hasMore: false`; reusing it is an explicit manual retry of failed work, not a normal paging loop.
|
|
1396
|
+
`org` and `project` select by identifier. Linear and Trello match any of a resource's or project's id, name or
|
|
1397
|
+
key. Jira (Cloud and Data Center) matches only the canonical key, so a selection whose site or project was removed
|
|
1398
|
+
selects nothing instead of resolving to another one that carries the old value as a name or id: `org` is the site's
|
|
1399
|
+
id (`listOrgs`' `id`, or `listProjects`' `orgId`), never its label, and `project` is the project key, which is
|
|
1400
|
+
`listProjects`' `name` for Jira. `listProjects({ org })` itself still matches any identifier, since it only lists
|
|
1401
|
+
what a caller can then select.
|
|
1341
1402
|
|
|
1342
1403
|
Self-managed descriptors keep `domain` as the normalized host and expose the configured installation address
|
|
1343
1404
|
in `baseUrl`. Provider requests use the selected connection's address, including during account discovery.
|
|
@@ -1387,7 +1448,8 @@ const metadata = await api.getRepositoryMetadata('gitkraken', 'vscode-gitlens');
|
|
|
1387
1448
|
Supports `getRepositoryMetadata` + `getDefaultBranch` for GitHub/GHE, GitLab/self-hosted, Bitbucket Cloud,
|
|
1388
1449
|
and Azure DevOps (+ Server). **Not** Bitbucket Data Center. A self-managed id requires `token.domain`
|
|
1389
1450
|
(it throws otherwise, rather than building a malformed base URL). Azure encodes the repo as
|
|
1390
|
-
`"{project}/_git/{repoName}"
|
|
1451
|
+
`"{project}/_git/{repoName}"`; a repository named after its project and addressed without the project is
|
|
1452
|
+
`"{repoName}/_git/{repoName}"`.
|
|
1391
1453
|
|
|
1392
1454
|
## 11. Checklist for a new consumer
|
|
1393
1455
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gitkraken/core-gitlens",
|
|
3
3
|
"description": "GitLens core — shared Git / AI / GitHub primitives for internal GitKraken consumption",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.13.0",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "GitKraken",
|
|
@@ -239,7 +239,7 @@
|
|
|
239
239
|
}
|
|
240
240
|
},
|
|
241
241
|
"dependencies": {
|
|
242
|
-
"@gitkraken/provider-apis": "0.
|
|
242
|
+
"@gitkraken/provider-apis": "0.65.3",
|
|
243
243
|
"@octokit/graphql": "9.0.4",
|
|
244
244
|
"@octokit/request": "10.0.14",
|
|
245
245
|
"@octokit/request-error": "7.1.1",
|