@gitkraken/core-gitlens 0.11.1 → 0.12.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +28 -1
  2. package/dist/git/models/issue.d.ts +3 -1
  3. package/dist/git/models/issue.js.map +1 -1
  4. package/dist/git/utils/issue.utils.d.ts +3 -1
  5. package/dist/git/utils/issue.utils.js +5 -1
  6. package/dist/git/utils/issue.utils.js.map +1 -1
  7. package/dist/plus/integrations/models/gitHostIntegration.js +3 -1
  8. package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
  9. package/dist/plus/integrations/models/integration.js +1 -1
  10. package/dist/plus/integrations/providers/azure/azure.js +3 -2
  11. package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
  12. package/dist/plus/integrations/providers/azure/models.d.ts +27 -10
  13. package/dist/plus/integrations/providers/azure/models.js +39 -19
  14. package/dist/plus/integrations/providers/azure/models.js.map +1 -1
  15. package/dist/plus/integrations/providers/azure/workItemStateCategories.d.ts +19 -0
  16. package/dist/plus/integrations/providers/azure/workItemStateCategories.js +32 -0
  17. package/dist/plus/integrations/providers/azure/workItemStateCategories.js.map +1 -0
  18. package/dist/plus/integrations/providers/azureDevOps.js +5 -5
  19. package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
  20. package/dist/plus/integrations/providers/issueSorts.d.ts +0 -1
  21. package/dist/plus/integrations/providers/jira-server.js +11 -5
  22. package/dist/plus/integrations/providers/jira-server.js.map +1 -1
  23. package/dist/plus/integrations/providers/jira.d.ts +5 -2
  24. package/dist/plus/integrations/providers/jira.js +45 -30
  25. package/dist/plus/integrations/providers/jira.js.map +1 -1
  26. package/dist/plus/integrations/providers/jiraIssueByKey.d.ts +3 -4
  27. package/dist/plus/integrations/providers/jiraIssueByKey.js +3 -4
  28. package/dist/plus/integrations/providers/jiraIssueByKey.js.map +1 -1
  29. package/dist/plus/integrations/providers/linearIssuesEtag.d.ts +5 -5
  30. package/dist/plus/integrations/providers/linearIssuesEtag.js +6 -6
  31. package/dist/plus/integrations/providers/linearIssuesEtag.js.map +1 -1
  32. package/dist/plus/integrations/providers/models.d.ts +11 -1
  33. package/dist/plus/integrations/providers/models.js +9 -9
  34. package/dist/plus/integrations/providers/models.js.map +1 -1
  35. package/dist/plus/integrations/providers/providersApi.d.ts +30 -32
  36. package/dist/plus/integrations/providers/providersApi.js +149 -46
  37. package/dist/plus/integrations/providers/providersApi.js.map +1 -1
  38. package/dist/plus/integrations/providers/utils/jiraIssueDrains.d.ts +27 -15
  39. package/dist/plus/integrations/providers/utils/jiraIssueDrains.js +72 -10
  40. package/dist/plus/integrations/providers/utils/jiraIssueDrains.js.map +1 -1
  41. package/dist/plus/integrations/reads/cursors.js +1 -1
  42. package/dist/plus/integrations/reads/drains.d.ts +0 -1
  43. package/dist/plus/integrations/reads/hierarchy.js +2 -2
  44. package/dist/plus/integrations/reads/hierarchy.js.map +1 -1
  45. package/dist/plus/integrations/reads/hierarchy.utils.d.ts +13 -0
  46. package/dist/plus/integrations/reads/hierarchy.utils.js +26 -1
  47. package/dist/plus/integrations/reads/hierarchy.utils.js.map +1 -1
  48. package/dist/plus/integrations/reads/issueTracker.js +4 -4
  49. package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
  50. package/dist/plus/integrations/reads/issues.js +1 -1
  51. package/dist/plus/integrations/results.d.ts +12 -2
  52. package/dist/plus/integrations/results.js.map +1 -1
  53. package/dist/plus/integrations/utils/integration.utils.d.ts +6 -1
  54. package/dist/plus/integrations/utils/integration.utils.js +8 -1
  55. package/dist/plus/integrations/utils/integration.utils.js.map +1 -1
  56. package/docs/integrations.md +49 -19
  57. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"hierarchy.utils.js","names":[],"sources":["../../../../../plus/integrations/src/reads/hierarchy.utils.ts"],"sourcesContent":["import type { IssueResourceDescriptor, ResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport { IssuesCloudHostIntegrationId } from '../constants.js';\nimport type { IntegrationIds } from '../constants.js';\nimport type { ProviderOrganization, ProviderResult, ProviderWarning } from '../results.js';\nimport { appendDedupedWarning } from '../results.js';\n\n/**\n * Mapping of a provider's own hierarchy vocabulary onto the single {@link ProviderOrganization} shape the facade\n * publishes.\n *\n * Providers disagree about what sits above a repo or an issue: git hosts have orgs (and Azure DevOps a project\n * tier below that), while issue trackers have \"resources\" (Jira sites, Linear orgs, Trello workspaces) with\n * projects beneath them. `listOrgs`/`listProjects` unify all of it, so these helpers exist to keep the\n * per-provider shape-guessing — which field is the id, which is the display label, which resource is a\n * project's parent — in one place instead of inline in those reads.\n *\n * Everything here reads through the base {@link ResourceDescriptor} (which only guarantees `key`) and casts to\n * the concrete {@link IssueResourceDescriptor} for the optional `id`/`name`. That cast is why the fallbacks are\n * spelled out rather than assumed.\n */\n\n/** The label to show for a resource: its name, else its id, else the key that is always present. */\nexport function resourceLabel(resource: ResourceDescriptor): string {\n\tconst typed = resource as IssueResourceDescriptor;\n\treturn typed.name ?? typed.id ?? resource.key;\n}\n\n/** Whether `org` addresses this resource by any of the identifiers a caller could reasonably have. */\nexport function resourceMatchesOrg(resource: ResourceDescriptor, org: string): boolean {\n\tconst typed = resource as IssueResourceDescriptor;\n\treturn resource.key === org || typed.id === org || typed.name === org;\n}\n\n/**\n * The id of the RESOURCE a project belongs to, used to look up which account (and therefore which user) that\n * project's issues should be scoped to. Falls back through the project's own identifiers because some providers\n * (and test doubles) return project descriptors without naming their parent.\n */\nexport function resourceIdForProject(project: ResourceDescriptor): string | undefined {\n\tconst typed = project as IssueResourceDescriptor & { resourceId?: string };\n\treturn typed.resourceId ?? typed.id ?? project.key;\n}\n\n/**\n * A stable identity for one project across facade pages, for the retry/completed bookkeeping an issue-tracker\n * cursor carries. All three identifiers are included rather than one preferred value: two trackers can reuse the\n * same project id, so keying on a single field would collapse them into one entry.\n */\nexport function projectKey(project: ResourceDescriptor): string {\n\tconst typed = project as IssueResourceDescriptor & { resourceId?: string };\n\treturn JSON.stringify([typed.resourceId ?? '', typed.id ?? '', project.key]);\n}\n\n/**\n * Maps an issue-tracker resource descriptor to the unified {@link ProviderOrganization} org shape. `url` is\n * synthesized when the descriptor carries none, rather than widening the shared `ProviderOrganization.url` to\n * optional for the one provider family that may omit it.\n */\nexport function resourceToOrg(\n\tproviderId: IntegrationIds,\n\tresource: ResourceDescriptor,\n\torg?: string,\n): ProviderOrganization {\n\tconst typed = resource as IssueResourceDescriptor & { url?: string };\n\treturn {\n\t\tid: typed.id ?? resource.key,\n\t\tproviderId: providerId,\n\t\tname: resourceLabel(resource),\n\t\t...(org != null ? { org: org } : {}),\n\t\turl: typed.url ?? '',\n\t};\n}\n\n/**\n * The resource a project belongs under, as a display label — the `org` of the project's unified org shape.\n *\n * A project doesn't reliably name its parent, so try each identifier it does carry against the resources that\n * were read alongside it. When none matches but only ONE resource was in scope, that resource is the parent by\n * construction. Trello is excluded: its boards have no enclosing resource to name.\n */\nexport function orgForProject(\n\tproviderId: IntegrationIds,\n\tproject: ResourceDescriptor,\n\tresources: ResourceDescriptor[],\n): string | undefined {\n\tif (providerId === IssuesCloudHostIntegrationId.Trello) return undefined;\n\n\tconst typedProject = project as IssueResourceDescriptor & { resourceId?: string };\n\tconst parentMatch = [typedProject.resourceId, typedProject.id, project.key]\n\t\t.filter((value): value is string => value != null)\n\t\t.map(candidate => resources.find(resource => resourceMatchesOrg(resource, candidate)))\n\t\t.find((resource): resource is ResourceDescriptor => resource != null);\n\n\treturn parentMatch != null\n\t\t? resourceLabel(parentMatch)\n\t\t: resources.length === 1\n\t\t\t? resourceLabel(resources[0])\n\t\t\t: undefined;\n}\n\n/**\n * Stamps the provider onto an org the provider's own client produced. A git host's client fills the rest of the\n * shape but doesn't know which integration id it was reached through — and on a self-managed host the same\n * client serves several — so the fan-out attributes it here.\n */\nexport function withProviderContext(providerId: IntegrationIds, item: ProviderOrganization): ProviderOrganization {\n\treturn {\n\t\t...item,\n\t\tproviderId: providerId,\n\t\t...(item.org != null ? { org: item.org } : {}),\n\t};\n}\n\n/**\n * Merges the per-provider slices of a hierarchy fan-out into one flat result.\n *\n * `ProviderResult` has no page object, so `fetchFailed` is its only non-authoritative signal — any slice that\n * failed or came back incomplete has to set it, or a caller would treat a short list as the whole account.\n * Warnings are deduped because the same account can surface under two ids in one fan-out.\n */\nexport function mergeHierarchyResults<T>(\n\tresults: readonly ({ items: T[]; warnings: ProviderWarning[]; fetchFailed: boolean } | undefined)[],\n): ProviderResult<T> {\n\tconst items: T[] = [];\n\tconst warnings: ProviderWarning[] = [];\n\tlet fetchFailed = false;\n\tfor (const result of results) {\n\t\tif (result == null) continue;\n\n\t\titems.push(...result.items);\n\t\tfor (const warning of result.warnings) {\n\t\t\tappendDedupedWarning(warnings, warning);\n\t\t}\n\t\tif (result.fetchFailed) {\n\t\t\tfetchFailed = true;\n\t\t}\n\t}\n\n\treturn { items: items, warnings: warnings, fetchFailed: fetchFailed || undefined };\n}\n\n/**\n * Rejects a hierarchy read whose scoped selectors have no provider to apply to. `connectionId` and `domain`\n * both select among the connections OF a provider, so either one without `providerId` is ambiguous rather than\n * merely redundant — the fan-out would have to guess which provider the caller meant, and silently picking one\n * would read that connection's data under another provider's name.\n */\nexport function assertHierarchyReadTarget(\n\tmethod: 'listOrgs' | 'listProjects',\n\toptions: { providerId?: IntegrationIds; connectionId?: string; domain?: string } | undefined,\n): void {\n\tif (options?.providerId == null && (options?.connectionId != null || options?.domain != null)) {\n\t\tthrow new TypeError(`'${method}' requires 'providerId' when 'connectionId' or 'domain' is supplied`);\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAsBA,SAAgB,cAAc,UAAsC;CACnE,MAAM,QAAQ;CACd,OAAO,MAAM,QAAQ,MAAM,MAAM,SAAS;AAC3C;;AAGA,SAAgB,mBAAmB,UAA8B,KAAsB;CACtF,MAAM,QAAQ;CACd,OAAO,SAAS,QAAQ,OAAO,MAAM,OAAO,OAAO,MAAM,SAAS;AACnE;;;;;;AAOA,SAAgB,qBAAqB,SAAiD;CACrF,MAAM,QAAQ;CACd,OAAO,MAAM,cAAc,MAAM,MAAM,QAAQ;AAChD;;;;;;AAOA,SAAgB,WAAW,SAAqC;CAC/D,MAAM,QAAQ;CACd,OAAO,KAAK,UAAU;EAAC,MAAM,cAAc;EAAI,MAAM,MAAM;EAAI,QAAQ;CAAG,CAAC;AAC5E;;;;;;AAOA,SAAgB,cACf,YACA,UACA,KACuB;CACvB,MAAM,QAAQ;CACd,OAAO;EACN,IAAI,MAAM,MAAM,SAAS;EACb;EACZ,MAAM,cAAc,QAAQ;EAC5B,GAAI,OAAO,OAAO,EAAO,IAAI,IAAI,CAAC;EAClC,KAAK,MAAM,OAAO;CACnB;AACD;;;;;;;;AASA,SAAgB,cACf,YACA,SACA,WACqB;CACrB,IAAI,eAAA,UAAoD,OAAO,KAAA;CAE/D,MAAM,eAAe;CACrB,MAAM,cAAc;EAAC,aAAa;EAAY,aAAa;EAAI,QAAQ;CAAG,CAAC,CACzE,QAAQ,UAA2B,SAAS,IAAI,CAAC,CACjD,KAAI,cAAa,UAAU,MAAK,aAAY,mBAAmB,UAAU,SAAS,CAAC,CAAC,CAAC,CACrF,MAAM,aAA6C,YAAY,IAAI;CAErE,OAAO,eAAe,OACnB,cAAc,WAAW,IACzB,UAAU,WAAW,IACpB,cAAc,UAAU,EAAE,IAC1B,KAAA;AACL;;;;;;AAOA,SAAgB,oBAAoB,YAA4B,MAAkD;CACjH,OAAO;EACN,GAAG;EACS;EACZ,GAAI,KAAK,OAAO,OAAO,EAAE,KAAK,KAAK,IAAI,IAAI,CAAC;CAC7C;AACD;;;;;;;;AASA,SAAgB,sBACf,SACoB;CACpB,MAAM,QAAa,CAAC;CACpB,MAAM,WAA8B,CAAC;CACrC,IAAI,cAAc;CAClB,KAAK,MAAM,UAAU,SAAS;EAC7B,IAAI,UAAU,MAAM;EAEpB,MAAM,KAAK,GAAG,OAAO,KAAK;EAC1B,KAAK,MAAM,WAAW,OAAO,UAC5B,qBAAqB,UAAU,OAAO;EAEvC,IAAI,OAAO,aACV,cAAc;CAEhB;CAEA,OAAO;EAAS;EAAiB;EAAU,aAAa,eAAe,KAAA;CAAU;AAClF;;;;;;;AAQA,SAAgB,0BACf,QACA,SACO;CACP,IAAI,SAAS,cAAc,SAAS,SAAS,gBAAgB,QAAQ,SAAS,UAAU,OACvF,MAAM,IAAI,UAAU,IAAI,OAAO,oEAAoE;AAErG"}
1
+ {"version":3,"file":"hierarchy.utils.js","names":[],"sources":["../../../../../plus/integrations/src/reads/hierarchy.utils.ts"],"sourcesContent":["import type { IssueResourceDescriptor, ResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport { IssuesCloudHostIntegrationId } from '../constants.js';\nimport type { IntegrationIds } from '../constants.js';\nimport type { ProviderOrganization, ProviderResult, ProviderWarning } from '../results.js';\nimport { appendDedupedWarning } from '../results.js';\nimport { isJiraIntegrationId } from '../utils/integration.utils.js';\n\n/**\n * Mapping of a provider's own hierarchy vocabulary onto the single {@link ProviderOrganization} shape the facade\n * publishes.\n *\n * Providers disagree about what sits above a repo or an issue: git hosts have orgs (and Azure DevOps a project\n * tier below that), while issue trackers have \"resources\" (Jira sites, Linear orgs, Trello workspaces) with\n * projects beneath them. `listOrgs`/`listProjects` unify all of it, so these helpers exist to keep the\n * per-provider shape-guessing — which field is the id, which is the display label, which resource is a\n * project's parent — in one place instead of inline in those reads.\n *\n * Everything here reads through the base {@link ResourceDescriptor} (which only guarantees `key`) and casts to\n * the concrete {@link IssueResourceDescriptor} for the optional `id`/`name`. That cast is why the fallbacks are\n * spelled out rather than assumed.\n */\n\n/** The label to show for a resource: its name, else its id, else the key that is always present. */\nexport function resourceLabel(resource: ResourceDescriptor): string {\n\tconst typed = resource as IssueResourceDescriptor;\n\treturn typed.name ?? typed.id ?? resource.key;\n}\n\n/** Whether `org` addresses this resource by any of the identifiers a caller could reasonably have. */\nexport function resourceMatchesOrg(resource: ResourceDescriptor, org: string): boolean {\n\tconst typed = resource as IssueResourceDescriptor;\n\treturn resource.key === org || typed.id === org || typed.name === org;\n}\n\n/**\n * Whether a caller's `org`/`project` selector addresses this resource or project, for the reads that act on a\n * remembered selection. Jira's only by its canonical key (a site's id, a project's key): a site or project that was\n * removed must select nothing, not resolve through its old name or id to another one that happens to carry it as a\n * name, id or key. Every other provider by any identifier, as {@link resourceMatchesOrg}.\n */\nexport function selectorMatches(providerId: IntegrationIds, resource: ResourceDescriptor, selector: string): boolean {\n\treturn isJiraIntegrationId(providerId) ? resource.key === selector : resourceMatchesOrg(resource, selector);\n}\n\n/**\n * The id of the RESOURCE a project belongs to, used to look up which account (and therefore which user) that\n * project's issues should be scoped to. Falls back through the project's own identifiers because some providers\n * (and test doubles) return project descriptors without naming their parent.\n */\nexport function resourceIdForProject(project: ResourceDescriptor): string | undefined {\n\tconst typed = project as IssueResourceDescriptor & { resourceId?: string };\n\treturn typed.resourceId ?? typed.id ?? project.key;\n}\n\n/**\n * A stable identity for one project across facade pages, for the retry/completed bookkeeping an issue-tracker\n * cursor carries. All three identifiers are included rather than one preferred value: two trackers can reuse the\n * same project id, so keying on a single field would collapse them into one entry.\n */\nexport function projectKey(project: ResourceDescriptor): string {\n\tconst typed = project as IssueResourceDescriptor & { resourceId?: string };\n\treturn JSON.stringify([typed.resourceId ?? '', typed.id ?? '', project.key]);\n}\n\n/**\n * Maps an issue-tracker resource descriptor to the unified {@link ProviderOrganization} org shape. `url` is\n * synthesized when the descriptor carries none, rather than widening the shared `ProviderOrganization.url` to\n * optional for the one provider family that may omit it.\n */\nexport function resourceToOrg(\n\tproviderId: IntegrationIds,\n\tresource: ResourceDescriptor,\n\torg?: string,\n): ProviderOrganization {\n\tconst typed = resource as IssueResourceDescriptor & { url?: string };\n\treturn {\n\t\tid: typed.id ?? resource.key,\n\t\tproviderId: providerId,\n\t\tname: resourceLabel(resource),\n\t\t...(org != null ? { org: org } : {}),\n\t\turl: typed.url ?? '',\n\t};\n}\n\n/**\n * Maps an issue-tracker project descriptor to the unified org shape, as {@link resourceToOrg} does with its parent's\n * label as `org`. A Jira project is published by what its reads take: `name` is its key (its display name can collide\n * with another project's key), and `orgId` its site's id, since the label in `org` can't address the site.\n */\nexport function projectToOrg(\n\tproviderId: IntegrationIds,\n\tproject: ResourceDescriptor,\n\tresources: ResourceDescriptor[],\n): ProviderOrganization {\n\tconst item = resourceToOrg(providerId, project, orgForProject(providerId, project, resources));\n\tif (!isJiraIntegrationId(providerId)) return item;\n\n\t// The descriptor's own parent id only: `resourceIdForProject` would fall back to the project's id or key.\n\tconst { resourceId } = project;\n\treturn { ...item, name: project.key, ...(typeof resourceId === 'string' ? { orgId: resourceId } : {}) };\n}\n\n/**\n * The resource a project belongs under, as a display label — the `org` of the project's unified org shape.\n *\n * A project doesn't reliably name its parent, so try each identifier it does carry against the resources that\n * were read alongside it. When none matches but only ONE resource was in scope, that resource is the parent by\n * construction. Trello is excluded: its boards have no enclosing resource to name.\n */\nexport function orgForProject(\n\tproviderId: IntegrationIds,\n\tproject: ResourceDescriptor,\n\tresources: ResourceDescriptor[],\n): string | undefined {\n\tif (providerId === IssuesCloudHostIntegrationId.Trello) return undefined;\n\n\tconst typedProject = project as IssueResourceDescriptor & { resourceId?: string };\n\tconst parentMatch = [typedProject.resourceId, typedProject.id, project.key]\n\t\t.filter((value): value is string => value != null)\n\t\t.map(candidate => resources.find(resource => resourceMatchesOrg(resource, candidate)))\n\t\t.find((resource): resource is ResourceDescriptor => resource != null);\n\n\treturn parentMatch != null\n\t\t? resourceLabel(parentMatch)\n\t\t: resources.length === 1\n\t\t\t? resourceLabel(resources[0])\n\t\t\t: undefined;\n}\n\n/**\n * Stamps the provider onto an org the provider's own client produced. A git host's client fills the rest of the\n * shape but doesn't know which integration id it was reached through — and on a self-managed host the same\n * client serves several — so the fan-out attributes it here.\n */\nexport function withProviderContext(providerId: IntegrationIds, item: ProviderOrganization): ProviderOrganization {\n\treturn {\n\t\t...item,\n\t\tproviderId: providerId,\n\t\t...(item.org != null ? { org: item.org } : {}),\n\t};\n}\n\n/**\n * Merges the per-provider slices of a hierarchy fan-out into one flat result.\n *\n * `ProviderResult` has no page object, so `fetchFailed` is its only non-authoritative signal — any slice that\n * failed or came back incomplete has to set it, or a caller would treat a short list as the whole account.\n * Warnings are deduped because the same account can surface under two ids in one fan-out.\n */\nexport function mergeHierarchyResults<T>(\n\tresults: readonly ({ items: T[]; warnings: ProviderWarning[]; fetchFailed: boolean } | undefined)[],\n): ProviderResult<T> {\n\tconst items: T[] = [];\n\tconst warnings: ProviderWarning[] = [];\n\tlet fetchFailed = false;\n\tfor (const result of results) {\n\t\tif (result == null) continue;\n\n\t\titems.push(...result.items);\n\t\tfor (const warning of result.warnings) {\n\t\t\tappendDedupedWarning(warnings, warning);\n\t\t}\n\t\tif (result.fetchFailed) {\n\t\t\tfetchFailed = true;\n\t\t}\n\t}\n\n\treturn { items: items, warnings: warnings, fetchFailed: fetchFailed || undefined };\n}\n\n/**\n * Rejects a hierarchy read whose scoped selectors have no provider to apply to. `connectionId` and `domain`\n * both select among the connections OF a provider, so either one without `providerId` is ambiguous rather than\n * merely redundant — the fan-out would have to guess which provider the caller meant, and silently picking one\n * would read that connection's data under another provider's name.\n */\nexport function assertHierarchyReadTarget(\n\tmethod: 'listOrgs' | 'listProjects',\n\toptions: { providerId?: IntegrationIds; connectionId?: string; domain?: string } | undefined,\n): void {\n\tif (options?.providerId == null && (options?.connectionId != null || options?.domain != null)) {\n\t\tthrow new TypeError(`'${method}' requires 'providerId' when 'connectionId' or 'domain' is supplied`);\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,cAAc,UAAsC;CACnE,MAAM,QAAQ;CACd,OAAO,MAAM,QAAQ,MAAM,MAAM,SAAS;AAC3C;;AAGA,SAAgB,mBAAmB,UAA8B,KAAsB;CACtF,MAAM,QAAQ;CACd,OAAO,SAAS,QAAQ,OAAO,MAAM,OAAO,OAAO,MAAM,SAAS;AACnE;;;;;;;AAQA,SAAgB,gBAAgB,YAA4B,UAA8B,UAA2B;CACpH,OAAO,oBAAoB,UAAU,IAAI,SAAS,QAAQ,WAAW,mBAAmB,UAAU,QAAQ;AAC3G;;;;;;AAOA,SAAgB,qBAAqB,SAAiD;CACrF,MAAM,QAAQ;CACd,OAAO,MAAM,cAAc,MAAM,MAAM,QAAQ;AAChD;;;;;;AAOA,SAAgB,WAAW,SAAqC;CAC/D,MAAM,QAAQ;CACd,OAAO,KAAK,UAAU;EAAC,MAAM,cAAc;EAAI,MAAM,MAAM;EAAI,QAAQ;CAAG,CAAC;AAC5E;;;;;;AAOA,SAAgB,cACf,YACA,UACA,KACuB;CACvB,MAAM,QAAQ;CACd,OAAO;EACN,IAAI,MAAM,MAAM,SAAS;EACb;EACZ,MAAM,cAAc,QAAQ;EAC5B,GAAI,OAAO,OAAO,EAAO,IAAI,IAAI,CAAC;EAClC,KAAK,MAAM,OAAO;CACnB;AACD;;;;;;AAOA,SAAgB,aACf,YACA,SACA,WACuB;CACvB,MAAM,OAAO,cAAc,YAAY,SAAS,cAAc,YAAY,SAAS,SAAS,CAAC;CAC7F,IAAI,CAAC,oBAAoB,UAAU,GAAG,OAAO;CAG7C,MAAM,EAAE,eAAe;CACvB,OAAO;EAAE,GAAG;EAAM,MAAM,QAAQ;EAAK,GAAI,OAAO,eAAe,WAAW,EAAE,OAAO,WAAW,IAAI,CAAC;CAAG;AACvG;;;;;;;;AASA,SAAgB,cACf,YACA,SACA,WACqB;CACrB,IAAI,eAAA,UAAoD,OAAO,KAAA;CAE/D,MAAM,eAAe;CACrB,MAAM,cAAc;EAAC,aAAa;EAAY,aAAa;EAAI,QAAQ;CAAG,CAAC,CACzE,QAAQ,UAA2B,SAAS,IAAI,CAAC,CACjD,KAAI,cAAa,UAAU,MAAK,aAAY,mBAAmB,UAAU,SAAS,CAAC,CAAC,CAAC,CACrF,MAAM,aAA6C,YAAY,IAAI;CAErE,OAAO,eAAe,OACnB,cAAc,WAAW,IACzB,UAAU,WAAW,IACpB,cAAc,UAAU,EAAE,IAC1B,KAAA;AACL;;;;;;AAOA,SAAgB,oBAAoB,YAA4B,MAAkD;CACjH,OAAO;EACN,GAAG;EACS;EACZ,GAAI,KAAK,OAAO,OAAO,EAAE,KAAK,KAAK,IAAI,IAAI,CAAC;CAC7C;AACD;;;;;;;;AASA,SAAgB,sBACf,SACoB;CACpB,MAAM,QAAa,CAAC;CACpB,MAAM,WAA8B,CAAC;CACrC,IAAI,cAAc;CAClB,KAAK,MAAM,UAAU,SAAS;EAC7B,IAAI,UAAU,MAAM;EAEpB,MAAM,KAAK,GAAG,OAAO,KAAK;EAC1B,KAAK,MAAM,WAAW,OAAO,UAC5B,qBAAqB,UAAU,OAAO;EAEvC,IAAI,OAAO,aACV,cAAc;CAEhB;CAEA,OAAO;EAAS;EAAiB;EAAU,aAAa,eAAe,KAAA;CAAU;AAClF;;;;;;;AAQA,SAAgB,0BACf,QACA,SACO;CACP,IAAI,SAAS,cAAc,SAAS,SAAS,gBAAgB,QAAQ,SAAS,UAAU,OACvF,MAAM,IAAI,UAAU,IAAI,OAAO,oEAAoE;AAErG"}
@@ -4,14 +4,14 @@ import "../constants.js";
4
4
  import { appendDedupedWarning, reconcileOmissionsWithFailure } from "../results.js";
5
5
  import { assessCollectionMetadata, mergeAssessmentInto } from "../collectionMetadata.js";
6
6
  import "../providerFilters.js";
7
- import { mergeCollectionMetadata, parsePageCursor } from "../providers/utils/providerPaging.js";
8
7
  import { isIssuesHostIntegrationId } from "../utils/integration.utils.js";
8
+ import { mergeCollectionMetadata, parsePageCursor } from "../providers/utils/providerPaging.js";
9
9
  import { providersMetadata } from "../providers/models.js";
10
10
  import { parseIssueTrackerPageCursor, toIssueTrackerPageCursor } from "./cursors.js";
11
11
  import { incompleteReadWarning, issueTrackerOnlySurfaceWarning, otherWarning, unmergeableIssueSortWarning, unsupportedIssueSortWarning, unsupportedWarning } from "./warnings.js";
12
12
  import { runCaptured } from "./drains.js";
13
13
  import { isIssuesIntegration } from "../models/issuesIntegration.js";
14
- import { projectKey, resourceIdForProject, resourceLabel, resourceMatchesOrg } from "./hierarchy.utils.js";
14
+ import { projectKey, resourceIdForProject, resourceLabel, selectorMatches } from "./hierarchy.utils.js";
15
15
  import { resolveIssueSort, toIssueOrdering } from "./ordering.js";
16
16
  //#region ../plus/integrations/src/reads/issueTracker.ts
17
17
  const issueStateFilters = [
@@ -98,7 +98,7 @@ async function listIssueTrackerIssuesPage(ctx, options) {
98
98
  const fetchFailed = resourcesWarning != null && resources == null;
99
99
  return emptyPage(fetchFailed, false, fetchFailed ? discoveryFailureRetry() : void 0);
100
100
  }
101
- const scopedResources = options.org != null ? resources.filter((r) => resourceMatchesOrg(r, options.org)) : resources;
101
+ const scopedResources = options.org != null ? resources.filter((r) => selectorMatches(options.providerId, r, options.org)) : resources;
102
102
  if (scopedResources.length === 0) return emptyPage();
103
103
  const { value: projectsResult, warning: projectsWarning } = await runCaptured(options.providerId, domain, options.connectionId, () => integration.getProjectsForResourcesWithMetadataResult(scopedResources, options.connectionId));
104
104
  if (projectsWarning != null) warnings.push(projectsWarning);
@@ -110,7 +110,7 @@ async function listIssueTrackerIssuesPage(ctx, options) {
110
110
  const fetchFailed = projectsWarning != null && projectsResult == null || projectDiscoveryFailed;
111
111
  return emptyPage(fetchFailed, projectDiscoveryTruncated, fetchFailed || projectDiscoveryTruncated ? discoveryFailureRetry() : void 0);
112
112
  }
113
- const matchedProjects = options.project != null ? projects.filter((p) => resourceMatchesOrg(p, options.project)) : projects;
113
+ const matchedProjects = options.project != null ? projects.filter((p) => selectorMatches(options.providerId, p, options.project)) : projects;
114
114
  if (options.filters?.length) {
115
115
  const supported = providersMetadata[options.providerId]?.supportedIssueFilters;
116
116
  if (!(supported != null && options.filters.every((f) => supported.includes(f)))) {
@@ -1 +1 @@
1
- {"version":3,"file":"issueTracker.js","names":[],"sources":["../../../../../plus/integrations/src/reads/issueTracker.ts"],"sourcesContent":["import type { CollectionMetadata } from '@gitkraken/provider-apis';\nimport type { IssueShape, IssueSorting, IssueStateFilter } from '@gitlens/git/models/issue.js';\nimport type { ResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport { mapBounded } from '@gitlens/utils/promise.js';\nimport { assessCollectionMetadata, mergeAssessmentInto } from '../collectionMetadata.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { providerFanOutConcurrency } from '../constants.js';\nimport { toError } from '../errors.js';\nimport type { IntegrationResult } from '../models/integration.js';\nimport type { ProjectIssuesDrain } from '../models/issueReads.js';\nimport { isIssuesIntegration } from '../models/issuesIntegration.js';\nimport { IssueFilter, providersMetadata } from '../providers/models.js';\nimport { mergeCollectionMetadata, parsePageCursor } from '../providers/utils/providerPaging.js';\nimport type { ProviderPagedResult, ProviderWarning } from '../results.js';\nimport { appendDedupedWarning, reconcileOmissionsWithFailure } from '../results.js';\nimport { isIssuesHostIntegrationId } from '../utils/integration.utils.js';\nimport type { ProviderReadContext } from './context.js';\nimport { parseIssueTrackerPageCursor, toIssueTrackerPageCursor } from './cursors.js';\nimport { runCaptured } from './drains.js';\nimport { projectKey, resourceIdForProject, resourceLabel, resourceMatchesOrg } from './hierarchy.utils.js';\nimport { resolveIssueSort, toIssueOrdering } from './ordering.js';\nimport {\n\tincompleteReadWarning,\n\tissueTrackerOnlySurfaceWarning,\n\totherWarning,\n\tunmergeableIssueSortWarning,\n\tunsupportedIssueSortWarning,\n\tunsupportedWarning,\n} from './warnings.js';\n\nconst issueStateFilters: readonly IssueStateFilter[] = ['open', 'closed', 'all'];\n\nexport async function listIssueTrackerIssuesPage(\n\tctx: ProviderReadContext,\n\toptions: {\n\t\tproviderId: IntegrationIds;\n\t\torg?: string;\n\t\tproject?: string;\n\t\tfilters?: IssueFilter[];\n\t\t/** Broadens the read to every assignee. Scopes to user-assigned issues when omitted. */\n\t\tincludeAllAssignees?: boolean;\n\t\t/**\n\t\t * How to order the issues, as `field:direction`. Omitted orders most-recently-updated-first where the\n\t\t * tracker can express it.\n\t\t *\n\t\t * Validated against `getSupportedFilters().issues`' sibling `issueSorts` — a tracker reports there, not\n\t\t * under the account-wide table, because resource -> project IS its only issue surface. A key it can't\n\t\t * express refuses the read rather than serving a differently-ordered list.\n\t\t *\n\t\t * One page can span SEVERAL projects, whose issues are merged here, so a key no normalized issue carries\n\t\t * (`priority`, `dueDate`, `resolved`) is refused for a multi-project page even though the tracker orders by\n\t\t * it perfectly well within one project.\n\t\t *\n\t\t * Unlike the git-host reads this one is safe to change mid-pagination: its cursor windows PROJECTS and\n\t\t * drains each to exhaustion, so which projects a round covers doesn't depend on how their issues are\n\t\t * ordered. Only the order within a page changes.\n\t\t */\n\t\tsort?: IssueSorting;\n\t\t/**\n\t\t * Which issue states to read. Omitted reads open issues only.\n\t\t *\n\t\t * Applied server-side by every project read, so it narrows each project's drain rather than the merged page.\n\t\t * Only a tracker declaring `ProviderMetadata.supportsIssueStates` can express anything but `'open'`; asking\n\t\t * another for `'closed'`/`'all'` refuses the read rather than serving its open issues.\n\t\t */\n\t\tstate?: IssueStateFilter;\n\t\tforceSync?: boolean;\n\t\tpage?: number;\n\t\tcursor?: string;\n\t\titemsPerPage?: number;\n\t\tconnectionId?: string;\n\t\t/**\n\t\t * Explicit self-managed tracker host. Used to select the instance when the requested connection has no\n\t\t * configured domain; it must come from the trusted authentication configuration, never from repository\n\t\t * or remote data. Ignored for the cloud trackers.\n\t\t */\n\t\tdomain?: string;\n\t},\n): Promise<ProviderPagedResult<IssueShape>> {\n\t// Pagination is opt-in: only window the projects when the caller actually asked to page. A caller that\n\t// passes none of page/cursor/itemsPerPage keeps the \"aggregate every matched project\" contract, so an\n\t// existing consumer that doesn't inspect `hasMore` never silently loses projects past a default window.\n\tconst compositeCursor = parseIssueTrackerPageCursor(options.cursor);\n\tconst paginated =\n\t\tcompositeCursor?.unpaged !== true &&\n\t\t(options.page != null || options.cursor != null || options.itemsPerPage != null);\n\t// A composite cursor can carry retries from earlier project windows alongside the next untouched window.\n\t// `currentPage` remains the caller-facing position; `nextPage` is the window this round should advance.\n\tconst page = Math.max(\n\t\t1,\n\t\tMath.trunc(compositeCursor?.currentPage ?? parsePageCursor(options.cursor) ?? options.page ?? 1),\n\t);\n\tconst projectsPerPage = Math.max(1, Math.trunc(options.itemsPerPage ?? 20));\n\n\tconst items: IssueShape[] = [];\n\tconst warnings: ProviderWarning[] = [];\n\tconst emptyPage = (\n\t\tfetchFailed?: boolean,\n\t\ttruncated?: boolean,\n\t\tretry?: {\n\t\t\tnextPage?: number;\n\t\t\tpages?: readonly number[];\n\t\t\tprojects?: readonly string[];\n\t\t\tcompletedProjects?: readonly string[];\n\t\t},\n\t): ProviderPagedResult<IssueShape> => {\n\t\tconst cursor =\n\t\t\tretry != null\n\t\t\t\t? toIssueTrackerPageCursor({\n\t\t\t\t\t\tcurrentPage: page + 1,\n\t\t\t\t\t\tunpaged: !paginated,\n\t\t\t\t\t\tnextPage: retry.nextPage,\n\t\t\t\t\t\tretryPages: retry.pages ?? [],\n\t\t\t\t\t\tretryProjects: retry.projects ?? [],\n\t\t\t\t\t\tcompletedProjects: retry.completedProjects ?? [],\n\t\t\t\t\t\tstate: options.state,\n\t\t\t\t\t})\n\t\t\t\t: undefined;\n\t\treturn {\n\t\t\titems: items,\n\t\t\twarnings: warnings,\n\t\t\tpage: { currentPage: page, itemsPerPage: items.length, truncated: truncated || undefined },\n\t\t\t// Retry-only state is deliberately manual: advertising it as forward progress would make a\n\t\t\t// persistent provider failure spin forever in a normal `while (hasMore)` consumer. An untouched\n\t\t\t// project window remains normal forward progress even when this window failed completely.\n\t\t\thasMore: retry?.nextPage != null && cursor != null,\n\t\t\tcursor: cursor,\n\t\t\tfetchFailed: fetchFailed || undefined,\n\t\t};\n\t};\n\tconst normalWindowPage = paginated\n\t\t? (compositeCursor?.nextPage ?? (compositeCursor == null ? page : undefined))\n\t\t: undefined;\n\tconst priorRetryPages = compositeCursor?.retryPages ?? [];\n\tconst priorRetryProjects = compositeCursor?.retryProjects ?? [];\n\tconst priorCompletedProjects = compositeCursor?.completedProjects ?? [];\n\tconst trackCompletedProjects =\n\t\tpriorCompletedProjects.length > 0 || priorRetryPages.length > 0 || compositeCursor?.completedProjects != null;\n\tconst retryPagesForDiscoveryFailure = (): number[] => [\n\t\t...priorRetryPages,\n\t\t...(normalWindowPage != null ? [normalWindowPage] : compositeCursor == null ? [1] : []),\n\t];\n\tconst discoveryFailureRetry = (): {\n\t\tpages: readonly number[];\n\t\tprojects: readonly string[];\n\t\tcompletedProjects: readonly string[];\n\t} => ({\n\t\tpages: retryPagesForDiscoveryFailure(),\n\t\tprojects: priorRetryProjects,\n\t\tcompletedProjects: priorCompletedProjects,\n\t});\n\n\tif (!isIssuesHostIntegrationId(options.providerId)) {\n\t\twarnings.push(\n\t\t\tissueTrackerOnlySurfaceWarning(options.providerId, options.connectionId, 'Issue-tracker project reads'),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(options.providerId, options.connectionId, options.domain);\n\tif (integration == null) {\n\t\t// A supplied connectionId or domain that no longer resolves is a broken target, not an empty account.\n\t\tconst early = ctx.earlyReturnConnectionWarnings(options.providerId, options.connectionId, options.domain);\n\t\twarnings.push(...early.warnings);\n\t\treturn emptyPage(early.fetchFailed);\n\t}\n\tif (!isIssuesIntegration(integration)) {\n\t\twarnings.push(\n\t\t\tissueTrackerOnlySurfaceWarning(options.providerId, options.connectionId, 'Issue-tracker project reads'),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\tconst domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);\n\n\t// Before any upstream request: the discovery fan-outs below (resources, projects, per-resource accounts) are\n\t// three round trips, and an order this tracker can't express refuses the read whatever they return. Placed after\n\t// `domain` only because every warning carries it.\n\tconst resolvedSort = resolveIssueSort(providersMetadata[options.providerId]?.supportedIssueSorts, options.sort);\n\tif (resolvedSort.rejection != null) {\n\t\twarnings.push(\n\t\t\tunsupportedIssueSortWarning(options.providerId, domain, options.connectionId, resolvedSort.rejection),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\tconst sort = resolvedSort.sort;\n\n\t// Refused before any request for the same reason as the sort: a state the tracker can't express would otherwise\n\t// be dropped by its project reads, and their open issues published as the requested state. A value outside the\n\t// vocabulary is refused too, since `toProviderIssueStates` would read it as omitted, which means open.\n\tif (\n\t\toptions.state != null &&\n\t\t(!issueStateFilters.includes(options.state) ||\n\t\t\t(options.state !== 'open' && !providersMetadata[options.providerId]?.supportsIssueStates))\n\t) {\n\t\t// A known state this tracker can't express is an unsupported capability; a value outside the vocabulary is\n\t\t// caller input the caller fixes in the request.\n\t\twarnings.push(\n\t\t\tissueStateFilters.includes(options.state)\n\t\t\t\t? unsupportedWarning(\n\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\tdomain,\n\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t`Issue state '${options.state}' is not supported by '${options.providerId}'.`,\n\t\t\t\t\t)\n\t\t\t\t: otherWarning(\n\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\tdomain,\n\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t`Unknown issue state; expected one of ${issueStateFilters.join(', ')}.`,\n\t\t\t\t\t),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\t// A cursor resumed under another state than it was minted for skips what it already covered, whose other-state\n\t// issues were never read. A narrowed read only mints composite cursors, which record their state, so any other\n\t// cursor came from an open read.\n\tconst cursorState =\n\t\tcompositeCursor != null ? (compositeCursor.state ?? 'open') : options.cursor != null ? 'open' : undefined;\n\tif (cursorState != null && cursorState !== (options.state ?? 'open')) {\n\t\twarnings.push(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'The cursor belongs to a read of another issue state; start again without it.',\n\t\t\t),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\tawait ctx.forceRefreshIfRequested(integration, options.forceSync, options.connectionId);\n\n\tawait integration.getCurrentAccount({ connectionId: options.connectionId }).catch(() => undefined);\n\n\tconst { value: resources, warning: resourcesWarning } = await runCaptured(\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\t() => integration.getResourcesForUserResult(options.connectionId),\n\t);\n\tif (resourcesWarning != null) {\n\t\twarnings.push(resourcesWarning);\n\t}\n\tif (resources == null || resources.length === 0) {\n\t\tconst fetchFailed = resourcesWarning != null && resources == null;\n\t\treturn emptyPage(fetchFailed, false, fetchFailed ? discoveryFailureRetry() : undefined);\n\t}\n\n\tconst scopedResources =\n\t\toptions.org != null ? resources.filter(r => resourceMatchesOrg(r, options.org!)) : resources;\n\tif (scopedResources.length === 0) {\n\t\treturn emptyPage();\n\t}\n\n\tconst { value: projectsResult, warning: projectsWarning } = await runCaptured(\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\t() => integration.getProjectsForResourcesWithMetadataResult(scopedResources, options.connectionId),\n\t);\n\tif (projectsWarning != null) {\n\t\twarnings.push(projectsWarning);\n\t}\n\t// Partial project discovery: continue with the resources that succeeded, but surface per-resource\n\t// failures as warnings and remember to mark the page fetchFailed so the caller knows some issues may be\n\t// missing. `projectDiscoveryFailed`/`projectDiscoveryTruncated` are OR-ed into the page's\n\t// fetchFailed/truncated at every return below (a truncated-but-not-failed discovery, e.g. a paging\n\t// backstop, still means the project set is incomplete).\n\tconst projectDiscoveryAssessment = mergeAssessmentInto(\n\t\twarnings,\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\tprojectsResult?.metadata,\n\t);\n\tconst projectDiscoveryFailed = projectDiscoveryAssessment.fetchFailed;\n\tconst projectDiscoveryTruncated = projectDiscoveryAssessment.truncated;\n\tconst projects = projectsResult?.values;\n\tif (projects == null || projects.length === 0) {\n\t\tconst fetchFailed = (projectsWarning != null && projectsResult == null) || projectDiscoveryFailed;\n\t\treturn emptyPage(\n\t\t\tfetchFailed,\n\t\t\tprojectDiscoveryTruncated,\n\t\t\tfetchFailed || projectDiscoveryTruncated ? discoveryFailureRetry() : undefined,\n\t\t);\n\t}\n\n\tconst matchedProjects =\n\t\toptions.project != null ? projects.filter(p => resourceMatchesOrg(p, options.project!)) : projects;\n\n\t// Validate the requested filters against what this provider supports (e.g. Linear/Trello support only\n\t// Assignee). An unsupported filter must not silently degrade — Linear/Trello ignore the requested type\n\t// and apply Assignee regardless — so warn + fetchFailed instead of returning a differently-scoped set.\n\tif (options.filters?.length) {\n\t\tconst supported = providersMetadata[options.providerId]?.supportedIssueFilters;\n\t\tconst allSupported = supported != null && options.filters.every(f => supported.includes(f));\n\t\tif (!allSupported) {\n\t\t\twarnings.push(\n\t\t\t\tunsupportedWarning(\n\t\t\t\t\toptions.providerId,\n\t\t\t\t\tdomain,\n\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t`One or more requested issue filters are not supported by '${options.providerId}'.`,\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn emptyPage(true);\n\t\t}\n\t}\n\n\t// `includeAllAssignees` drops the user scope, but a user-relative filter (Author/Mention) is meaningless\n\t// without a user. Passing both to the provider degrades silently: Jira, seeing no user, falls through to\n\t// an unscoped project fetch and returns EVERY issue instead of the requested author's/mentions. Reject\n\t// the incompatible combination up front rather than publishing a differently-scoped set as the result.\n\tif (options.includeAllAssignees === true && options.filters?.some(f => f !== IssueFilter.Assignee)) {\n\t\twarnings.push(\n\t\t\tunsupportedWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`\\`includeAllAssignees\\` cannot be combined with an author/mention filter for '${options.providerId}' (those filters require a user scope).`,\n\t\t\t),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\t// Scope to the current user's assigned issues unless the caller broadens to all assignees. Resolve the\n\t// handle from each resource's own account (multi-account safe), capturing any error so its kind\n\t// (e.g. auth) is preserved rather than collapsed to a generic warning.\n\tlet usersByResourceId: Map<string, string> | undefined;\n\t// The same accounts keyed by the same resource, holding the provider's stable id rather than the handle. A\n\t// separate map rather than a widened value because the two are resolved and consumed independently: a\n\t// provider may carry an account with no id, and `scopedProjectsWithUsers` gates on the HANDLE alone, so\n\t// folding them together would drop a resource whose id happened to be missing.\n\tlet userIdsByResourceId: Map<string, string> | undefined;\n\tlet accountLookupFailed = false;\n\tif (options.includeAllAssignees !== true) {\n\t\tusersByResourceId = new Map<string, string>();\n\t\tuserIdsByResourceId = new Map<string, string>();\n\t\tconst accounts = await mapBounded(scopedResources, providerFanOutConcurrency, async resource => ({\n\t\t\tresource: resource,\n\t\t\t...(await runCaptured(options.providerId, domain, options.connectionId, () =>\n\t\t\t\tintegration.getAccountForResourceResult(resource, options.connectionId),\n\t\t\t)),\n\t\t}));\n\n\t\tfor (const { resource, value: account, warning: accountWarning } of accounts) {\n\t\t\tconst user = account?.username ?? account?.name ?? undefined;\n\t\t\tif (user != null) {\n\t\t\t\tconst resourceKey = resourceIdForProject(resource) ?? resource.key;\n\t\t\t\tusersByResourceId.set(resourceKey, user);\n\t\t\t\t// Empty-checked, not just null-checked: an account shape declares `id` as a plain string, and a\n\t\t\t\t// provider with nothing to put there sets `''` rather than omitting it — which would otherwise\n\t\t\t\t// reach a user-field query as an empty identity and match nothing.\n\t\t\t\tif (account?.id != null && account.id !== '') {\n\t\t\t\t\tuserIdsByResourceId.set(resourceKey, account.id);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Deduped: a captured `accountWarning` carries only the provider error, with no resource in its\n\t\t\t// message, so one expired token across N resources would otherwise repeat verbatim N times. The\n\t\t\t// synthesized fallback names the resource, so those stay distinct on their own.\n\t\t\tappendDedupedWarning(\n\t\t\t\twarnings,\n\t\t\t\taccountWarning ??\n\t\t\t\t\totherWarning(\n\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\tdomain,\n\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t`Could not resolve the current user for '${resourceLabel(resource)}'; skipping that resource to avoid returning issues assigned to others.`,\n\t\t\t\t\t),\n\t\t\t);\n\t\t\taccountLookupFailed = true;\n\t\t}\n\t}\n\n\tconst fallbackUserForUnscopedProject =\n\t\tscopedResources.length === 1 && usersByResourceId?.size === 1\n\t\t\t? usersByResourceId.values().next().value\n\t\t\t: undefined;\n\tconst userForProject = (project: ResourceDescriptor): string | undefined => {\n\t\tconst resourceId = resourceIdForProject(project);\n\t\tif (resourceId != null) {\n\t\t\tconst user = usersByResourceId?.get(resourceId);\n\t\t\tif (user != null) {\n\t\t\t\treturn user;\n\t\t\t}\n\t\t}\n\n\t\t// Some providers/tests return project descriptors without their parent resource id. When we have only\n\t\t// one scoped resource, re-use that sole resolved user rather than silently dropping every project.\n\t\treturn fallbackUserForUnscopedProject;\n\t};\n\t// Deliberately NOT falling back the way `userForProject` does. That fallback exists so a project with no\n\t// parent resource id still gets scoped rather than dropped, and the handle is what the drop is judged on;\n\t// the id is optional everywhere it is read, so a miss degrades to the handle instead of widening the read.\n\tconst userIdForProject = (project: ResourceDescriptor): string | undefined => {\n\t\tconst resourceId = resourceIdForProject(project);\n\t\treturn resourceId != null ? userIdsByResourceId?.get(resourceId) : undefined;\n\t};\n\n\tconst retryProjectKeys = new Set<string>();\n\tconst completedProjectKeys = new Set(priorCompletedProjects);\n\tfor (const retryProject of priorRetryProjects) {\n\t\tcompletedProjectKeys.delete(retryProject);\n\t}\n\tconst scopedProjectsWithUsers =\n\t\tusersByResourceId != null\n\t\t\t? matchedProjects.filter(project => {\n\t\t\t\t\tif (userForProject(project) != null) return true;\n\n\t\t\t\t\tconst key = projectKey(project);\n\t\t\t\t\tretryProjectKeys.add(key);\n\t\t\t\t\tcompletedProjectKeys.delete(key);\n\t\t\t\t\treturn false;\n\t\t\t\t})\n\t\t\t: matchedProjects;\n\n\t// Select the untouched window plus any earlier windows/projects that explicitly need retrying. Keys\n\t// include the parent resource so two trackers can reuse the same project id without collapsing.\n\tconst projectsByRetryKey = new Map(scopedProjectsWithUsers.map(project => [projectKey(project), project]));\n\tconst scopedProjectsByKey = new Map<string, ResourceDescriptor>();\n\tconst addScopedProject = (project: ResourceDescriptor): void => {\n\t\tconst key = projectKey(project);\n\t\tif (completedProjectKeys.has(key)) return;\n\n\t\tscopedProjectsByKey.set(key, project);\n\t};\n\tfor (const retryProject of priorRetryProjects) {\n\t\tconst project = projectsByRetryKey.get(retryProject);\n\t\tif (project != null) {\n\t\t\taddScopedProject(project);\n\t\t} else if (projectDiscoveryFailed || projectDiscoveryTruncated) {\n\t\t\tretryProjectKeys.add(retryProject);\n\t\t}\n\t}\n\tconst windowPages = new Set<number>(priorRetryPages);\n\tif (normalWindowPage != null) {\n\t\twindowPages.add(normalWindowPage);\n\t}\n\tif (!paginated && (projectDiscoveryFailed || projectDiscoveryTruncated)) {\n\t\twindowPages.add(1);\n\t}\n\tif (paginated) {\n\t\tfor (const windowPage of windowPages) {\n\t\t\tconst windowStart = (windowPage - 1) * projectsPerPage;\n\t\t\tfor (const project of scopedProjectsWithUsers.slice(windowStart, windowStart + projectsPerPage)) {\n\t\t\t\taddScopedProject(project);\n\t\t\t}\n\t\t}\n\t} else if (compositeCursor == null || priorRetryPages.length > 0) {\n\t\t// An unpaged retry-only cursor should re-read only the explicitly failed projects already added\n\t\t// above. Discovery retries still rescan the aggregate set so newly recovered projects are included.\n\t\tfor (const project of scopedProjectsWithUsers) {\n\t\t\taddScopedProject(project);\n\t\t}\n\t}\n\tconst scopedProjects = [...scopedProjectsByKey.values()];\n\tconst furthestWindowPage = windowPages.size > 0 ? Math.max(...windowPages) : normalWindowPage;\n\tconst normalWindowEnd =\n\t\tfurthestWindowPage != null ? furthestWindowPage * projectsPerPage : scopedProjectsWithUsers.length;\n\tconst nextPage =\n\t\tpaginated && furthestWindowPage != null && scopedProjectsWithUsers.length > normalWindowEnd\n\t\t\t? furthestWindowPage + 1\n\t\t\t: undefined;\n\tconst retryWindowPages = (): number[] => {\n\t\tif (!projectDiscoveryFailed && !projectDiscoveryTruncated && retryProjectKeys.size === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst pages = new Set(windowPages);\n\t\tif (pages.size === 0) {\n\t\t\t// A terminal retry has no untouched next window to identify its origin. Keep a manual window\n\t\t\t// marker so a later partial discovery can reconcile against the projects already emitted.\n\t\t\tpages.add(paginated ? Math.max(1, page - 1) : 1);\n\t\t}\n\t\treturn [...pages];\n\t};\n\tif (scopedProjects.length === 0) {\n\t\t// The discovered projects didn't intersect the requested filter/window, or every matching resource\n\t\t// failed user resolution. If discovery or account lookup was partial, the empty result is not a\n\t\t// proven-empty account — carry `fetchFailed` so the caller knows issues may be missing.\n\t\treturn emptyPage(projectDiscoveryFailed || accountLookupFailed, projectDiscoveryTruncated, {\n\t\t\tnextPage: nextPage,\n\t\t\tpages: retryWindowPages(),\n\t\t\tprojects: [...retryProjectKeys],\n\t\t\tcompletedProjects: [...completedProjectKeys],\n\t\t});\n\t}\n\n\t// A read spanning several projects is a merge, so it can only honor a key a normalized issue carries. Bound here\n\t// rather than next to the key's validation above, because the project count is only known now — while still\n\t// before any project read, so a refusal costs no request. Refused rather than served as concatenated\n\t// per-project runs, which would look ordered without being so.\n\t//\n\t// Counted over every project the READ covers, not the `scopedProjects` window this page happens to hold: the\n\t// window is `itemsPerPage` projects wide, so counting it would refuse a 20-project page and then serve the\n\t// final 1-project page of the same paginated call — the same key answered two ways, with the earlier pages'\n\t// issues missing from a read that reported success.\n\t//\n\t// Projects are not the only thing a tracker read merges: a user-scoped read asking for several relationships\n\t// issues one provider query PER relationship and unions them within each project (Jira runs one JQL drain per\n\t// filter and folds them into one map, in filter order), so a single-project page of two filters is just as\n\t// concatenated as a two-project one. Counting only projects published those runs under the requested key.\n\t// Not gated on the provider: the two trackers that don't fan out (Linear, Trello) declare `Assignee` as their\n\t// only supported filter, so a multi-filter read is already refused above and can never reach this.\n\tconst mergesFilters = options.includeAllAssignees !== true && (options.filters?.length ?? 0) > 1;\n\tconst ordering = toIssueOrdering(sort, scopedProjectsWithUsers.length > 1 || mergesFilters);\n\tif (ordering.unmergeable != null) {\n\t\twarnings.push(\n\t\t\tunmergeableIssueSortWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\tordering.unmergeable,\n\t\t\t\tscopedProjectsWithUsers.length > 1 ? 'projects' : 'filters',\n\t\t\t),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\t// One call for the whole window, so a tracker that can search several projects at once (Jira) does, instead\n\t// of paying one drain per project. Results still come back per project, which the accounting below needs.\n\tlet projectReads: IntegrationResult<ProjectIssuesDrain | undefined>[];\n\ttry {\n\t\tprojectReads = await integration.getIssuesForProjectsWithTruncationResult(\n\t\t\tscopedProjects.map(project => ({\n\t\t\t\tproject: project,\n\t\t\t\toptions: {\n\t\t\t\t\tuser: userForProject(project),\n\t\t\t\t\tuserId: userIdForProject(project),\n\t\t\t\t\tfilters: options.filters,\n\t\t\t\t\tsort: sort,\n\t\t\t\t\tstate: options.state,\n\t\t\t\t},\n\t\t\t})),\n\t\t\toptions.connectionId,\n\t\t);\n\t} catch (ex) {\n\t\tconst error = toError(ex);\n\t\tprojectReads = scopedProjects.map(() => ({ error: error }));\n\t}\n\tconst perProject = await Promise.all(\n\t\tscopedProjects.map(async (project, index) => ({\n\t\t\tproject: project,\n\t\t\t...(await runCaptured(options.providerId, domain, options.connectionId, () =>\n\t\t\t\tPromise.resolve(projectReads[index]),\n\t\t\t)),\n\t\t})),\n\t);\n\n\t// Partial project discovery means some projects' issues are missing from this page; propagate it so the\n\t// page reports fetchFailed even when every discovered project's own read succeeded.\n\tlet fetchFailed = projectDiscoveryFailed || accountLookupFailed;\n\tlet truncationRecovery: 'narrow-scope' | 'none' | undefined = projectDiscoveryTruncated ? 'none' : undefined;\n\tlet drainMetadata: CollectionMetadata | undefined;\n\tfor (const { project, value: result, warning } of perProject) {\n\t\tconst key = projectKey(project);\n\t\tconst retryProject = result == null;\n\t\tif (warning != null) {\n\t\t\t// Deduped: the per-project warning is built from the provider error alone and names no project, so a\n\t\t\t// single failing token repeats verbatim once per project in the window.\n\t\t\tappendDedupedWarning(warnings, warning);\n\t\t\tfetchFailed = true;\n\t\t\ttruncationRecovery = 'none';\n\t\t}\n\t\t// A thrown/unsupported read (e.g. Linear not-implemented) surfaces as a warning with no value;\n\t\t// mark the aggregate as fetchFailed so an empty result isn't mistaken for \"no issues\".\n\t\tif (result == null) {\n\t\t\tfetchFailed = true;\n\t\t}\n\t\tif (result != null) {\n\t\t\titems.push(...result.values);\n\t\t\tif (result.truncated) {\n\t\t\t\tconst recovery = result.recovery;\n\t\t\t\ttruncationRecovery = truncationRecovery == null || truncationRecovery === recovery ? recovery : 'none';\n\t\t\t}\n\t\t\tif (result.metadata != null) {\n\t\t\t\tdrainMetadata = mergeCollectionMetadata(drainMetadata, result.metadata);\n\t\t\t}\n\t\t}\n\t\tif (retryProject) {\n\t\t\tretryProjectKeys.add(key);\n\t\t\tcompletedProjectKeys.delete(key);\n\t\t} else {\n\t\t\t// A usable partial/truncated project is emitted once and remains structurally incomplete.\n\t\t\t// Re-running the same project cursor would normally return the same prefix and duplicate it\n\t\t\t// across facade pages; only a project that returned no value is safe to retry automatically.\n\t\t\tcompletedProjectKeys.add(key);\n\t\t}\n\t}\n\n\t// Each provider query arrived ordered by the tracker; the union of them — across projects, and across the\n\t// per-relationship queries a multi-filter read fans out into — is not, and the union is what this read\n\t// publishes. A no-op when nothing merged, since the tracker already ordered that single run.\n\tconst orderedItems = ordering.order(items);\n\n\tconst projectReadReportedTruncation = truncationRecovery != null;\n\tconst drainAssessment = assessCollectionMetadata(options.providerId, domain, options.connectionId, drainMetadata);\n\tfor (const warning of drainAssessment.warnings) {\n\t\t// A project result already supplies the recovery semantics. Replace metadata's unstructured fallback with\n\t\t// the structured warning below, while retaining specific failures and omissions.\n\t\tif (projectReadReportedTruncation && !drainAssessment.fetchFailed && warning.omission == null) continue;\n\n\t\tappendDedupedWarning(warnings, warning);\n\t}\n\tfetchFailed = fetchFailed || drainAssessment.fetchFailed;\n\tif (drainAssessment.truncated) {\n\t\ttruncationRecovery = 'none';\n\t}\n\n\tconst projectTruncated = truncationRecovery != null;\n\tconst scopeTooLarge = !fetchFailed && truncationRecovery === 'narrow-scope';\n\tif (scopeTooLarge || (!fetchFailed && projectTruncated && !warnings.some(warning => warning.omission != null))) {\n\t\tappendDedupedWarning(\n\t\t\twarnings,\n\t\t\tincompleteReadWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\tscopeTooLarge\n\t\t\t\t\t? 'Some projects hold more issues than one read can return; narrow the scope to read the rest.'\n\t\t\t\t\t: 'Some issues were omitted; the provider returned an incomplete result.',\n\t\t\t\tscopeTooLarge ? 'scope-too-large' : 'exhausted',\n\t\t\t),\n\t\t);\n\t}\n\t// A metadata omission from an earlier project asserts the read succeeded; a later one may since have failed.\n\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\n\tconst retryPages = retryWindowPages();\n\tconst cursor = toIssueTrackerPageCursor({\n\t\tcurrentPage: page + 1,\n\t\tunpaged: !paginated,\n\t\tnextPage: nextPage,\n\t\tretryPages: retryPages,\n\t\tretryProjects: [...retryProjectKeys],\n\t\tcompletedProjects:\n\t\t\t(trackCompletedProjects ||\n\t\t\t\tprojectDiscoveryFailed ||\n\t\t\t\tprojectDiscoveryTruncated ||\n\t\t\t\tretryProjectKeys.size > 0) &&\n\t\t\t(retryPages.length > 0 || nextPage != null)\n\t\t\t\t? [...completedProjectKeys]\n\t\t\t\t: [],\n\t\tstate: options.state,\n\t});\n\treturn {\n\t\titems: orderedItems,\n\t\twarnings: warnings,\n\t\tpage: { currentPage: page, itemsPerPage: orderedItems.length, truncated: projectTruncated || undefined },\n\t\t// Failed-project retries alone are manual. Only an untouched project window is automatic progress.\n\t\thasMore: nextPage != null && cursor != null,\n\t\tcursor: cursor,\n\t\tfetchFailed: fetchFailed || undefined,\n\t};\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"issueTracker.js","names":[],"sources":["../../../../../plus/integrations/src/reads/issueTracker.ts"],"sourcesContent":["import type { CollectionMetadata } from '@gitkraken/provider-apis';\nimport type { IssueShape, IssueSorting, IssueStateFilter } from '@gitlens/git/models/issue.js';\nimport type { ResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport { mapBounded } from '@gitlens/utils/promise.js';\nimport { assessCollectionMetadata, mergeAssessmentInto } from '../collectionMetadata.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { providerFanOutConcurrency } from '../constants.js';\nimport { toError } from '../errors.js';\nimport type { IntegrationResult } from '../models/integration.js';\nimport type { ProjectIssuesDrain } from '../models/issueReads.js';\nimport { isIssuesIntegration } from '../models/issuesIntegration.js';\nimport { IssueFilter, providersMetadata } from '../providers/models.js';\nimport { mergeCollectionMetadata, parsePageCursor } from '../providers/utils/providerPaging.js';\nimport type { ProviderPagedResult, ProviderWarning } from '../results.js';\nimport { appendDedupedWarning, reconcileOmissionsWithFailure } from '../results.js';\nimport { isIssuesHostIntegrationId } from '../utils/integration.utils.js';\nimport type { ProviderReadContext } from './context.js';\nimport { parseIssueTrackerPageCursor, toIssueTrackerPageCursor } from './cursors.js';\nimport { runCaptured } from './drains.js';\nimport { projectKey, resourceIdForProject, resourceLabel, selectorMatches } from './hierarchy.utils.js';\nimport { resolveIssueSort, toIssueOrdering } from './ordering.js';\nimport {\n\tincompleteReadWarning,\n\tissueTrackerOnlySurfaceWarning,\n\totherWarning,\n\tunmergeableIssueSortWarning,\n\tunsupportedIssueSortWarning,\n\tunsupportedWarning,\n} from './warnings.js';\n\nconst issueStateFilters: readonly IssueStateFilter[] = ['open', 'closed', 'all'];\n\nexport async function listIssueTrackerIssuesPage(\n\tctx: ProviderReadContext,\n\toptions: {\n\t\tproviderId: IntegrationIds;\n\t\torg?: string;\n\t\tproject?: string;\n\t\tfilters?: IssueFilter[];\n\t\t/** Broadens the read to every assignee. Scopes to user-assigned issues when omitted. */\n\t\tincludeAllAssignees?: boolean;\n\t\t/**\n\t\t * How to order the issues, as `field:direction`. Omitted orders most-recently-updated-first where the\n\t\t * tracker can express it.\n\t\t *\n\t\t * Validated against `getSupportedFilters().issues`' sibling `issueSorts` — a tracker reports there, not\n\t\t * under the account-wide table, because resource -> project IS its only issue surface. A key it can't\n\t\t * express refuses the read rather than serving a differently-ordered list.\n\t\t *\n\t\t * One page can span SEVERAL projects, whose issues are merged here, so a key no normalized issue carries\n\t\t * (`priority`, `dueDate`, `resolved`) is refused for a multi-project page even though the tracker orders by\n\t\t * it perfectly well within one project.\n\t\t *\n\t\t * Unlike the git-host reads this one is safe to change mid-pagination: its cursor windows PROJECTS and\n\t\t * drains each to exhaustion, so which projects a round covers doesn't depend on how their issues are\n\t\t * ordered. Only the order within a page changes.\n\t\t */\n\t\tsort?: IssueSorting;\n\t\t/**\n\t\t * Which issue states to read. Omitted reads open issues only.\n\t\t *\n\t\t * Applied server-side by every project read, so it narrows each project's drain rather than the merged page.\n\t\t * Only a tracker declaring `ProviderMetadata.supportsIssueStates` can express anything but `'open'`; asking\n\t\t * another for `'closed'`/`'all'` refuses the read rather than serving its open issues.\n\t\t */\n\t\tstate?: IssueStateFilter;\n\t\tforceSync?: boolean;\n\t\tpage?: number;\n\t\tcursor?: string;\n\t\titemsPerPage?: number;\n\t\tconnectionId?: string;\n\t\t/**\n\t\t * Explicit self-managed tracker host. Used to select the instance when the requested connection has no\n\t\t * configured domain; it must come from the trusted authentication configuration, never from repository\n\t\t * or remote data. Ignored for the cloud trackers.\n\t\t */\n\t\tdomain?: string;\n\t},\n): Promise<ProviderPagedResult<IssueShape>> {\n\t// Pagination is opt-in: only window the projects when the caller actually asked to page. A caller that\n\t// passes none of page/cursor/itemsPerPage keeps the \"aggregate every matched project\" contract, so an\n\t// existing consumer that doesn't inspect `hasMore` never silently loses projects past a default window.\n\tconst compositeCursor = parseIssueTrackerPageCursor(options.cursor);\n\tconst paginated =\n\t\tcompositeCursor?.unpaged !== true &&\n\t\t(options.page != null || options.cursor != null || options.itemsPerPage != null);\n\t// A composite cursor can carry retries from earlier project windows alongside the next untouched window.\n\t// `currentPage` remains the caller-facing position; `nextPage` is the window this round should advance.\n\tconst page = Math.max(\n\t\t1,\n\t\tMath.trunc(compositeCursor?.currentPage ?? parsePageCursor(options.cursor) ?? options.page ?? 1),\n\t);\n\tconst projectsPerPage = Math.max(1, Math.trunc(options.itemsPerPage ?? 20));\n\n\tconst items: IssueShape[] = [];\n\tconst warnings: ProviderWarning[] = [];\n\tconst emptyPage = (\n\t\tfetchFailed?: boolean,\n\t\ttruncated?: boolean,\n\t\tretry?: {\n\t\t\tnextPage?: number;\n\t\t\tpages?: readonly number[];\n\t\t\tprojects?: readonly string[];\n\t\t\tcompletedProjects?: readonly string[];\n\t\t},\n\t): ProviderPagedResult<IssueShape> => {\n\t\tconst cursor =\n\t\t\tretry != null\n\t\t\t\t? toIssueTrackerPageCursor({\n\t\t\t\t\t\tcurrentPage: page + 1,\n\t\t\t\t\t\tunpaged: !paginated,\n\t\t\t\t\t\tnextPage: retry.nextPage,\n\t\t\t\t\t\tretryPages: retry.pages ?? [],\n\t\t\t\t\t\tretryProjects: retry.projects ?? [],\n\t\t\t\t\t\tcompletedProjects: retry.completedProjects ?? [],\n\t\t\t\t\t\tstate: options.state,\n\t\t\t\t\t})\n\t\t\t\t: undefined;\n\t\treturn {\n\t\t\titems: items,\n\t\t\twarnings: warnings,\n\t\t\tpage: { currentPage: page, itemsPerPage: items.length, truncated: truncated || undefined },\n\t\t\t// Retry-only state is deliberately manual: advertising it as forward progress would make a\n\t\t\t// persistent provider failure spin forever in a normal `while (hasMore)` consumer. An untouched\n\t\t\t// project window remains normal forward progress even when this window failed completely.\n\t\t\thasMore: retry?.nextPage != null && cursor != null,\n\t\t\tcursor: cursor,\n\t\t\tfetchFailed: fetchFailed || undefined,\n\t\t};\n\t};\n\tconst normalWindowPage = paginated\n\t\t? (compositeCursor?.nextPage ?? (compositeCursor == null ? page : undefined))\n\t\t: undefined;\n\tconst priorRetryPages = compositeCursor?.retryPages ?? [];\n\tconst priorRetryProjects = compositeCursor?.retryProjects ?? [];\n\tconst priorCompletedProjects = compositeCursor?.completedProjects ?? [];\n\tconst trackCompletedProjects =\n\t\tpriorCompletedProjects.length > 0 || priorRetryPages.length > 0 || compositeCursor?.completedProjects != null;\n\tconst retryPagesForDiscoveryFailure = (): number[] => [\n\t\t...priorRetryPages,\n\t\t...(normalWindowPage != null ? [normalWindowPage] : compositeCursor == null ? [1] : []),\n\t];\n\tconst discoveryFailureRetry = (): {\n\t\tpages: readonly number[];\n\t\tprojects: readonly string[];\n\t\tcompletedProjects: readonly string[];\n\t} => ({\n\t\tpages: retryPagesForDiscoveryFailure(),\n\t\tprojects: priorRetryProjects,\n\t\tcompletedProjects: priorCompletedProjects,\n\t});\n\n\tif (!isIssuesHostIntegrationId(options.providerId)) {\n\t\twarnings.push(\n\t\t\tissueTrackerOnlySurfaceWarning(options.providerId, options.connectionId, 'Issue-tracker project reads'),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(options.providerId, options.connectionId, options.domain);\n\tif (integration == null) {\n\t\t// A supplied connectionId or domain that no longer resolves is a broken target, not an empty account.\n\t\tconst early = ctx.earlyReturnConnectionWarnings(options.providerId, options.connectionId, options.domain);\n\t\twarnings.push(...early.warnings);\n\t\treturn emptyPage(early.fetchFailed);\n\t}\n\tif (!isIssuesIntegration(integration)) {\n\t\twarnings.push(\n\t\t\tissueTrackerOnlySurfaceWarning(options.providerId, options.connectionId, 'Issue-tracker project reads'),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\tconst domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);\n\n\t// Before any upstream request: the discovery fan-outs below (resources, projects, per-resource accounts) are\n\t// three round trips, and an order this tracker can't express refuses the read whatever they return. Placed after\n\t// `domain` only because every warning carries it.\n\tconst resolvedSort = resolveIssueSort(providersMetadata[options.providerId]?.supportedIssueSorts, options.sort);\n\tif (resolvedSort.rejection != null) {\n\t\twarnings.push(\n\t\t\tunsupportedIssueSortWarning(options.providerId, domain, options.connectionId, resolvedSort.rejection),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\tconst sort = resolvedSort.sort;\n\n\t// Refused before any request for the same reason as the sort: a state the tracker can't express would otherwise\n\t// be dropped by its project reads, and their open issues published as the requested state. A value outside the\n\t// vocabulary is refused too, since `toProviderIssueStates` would read it as omitted, which means open.\n\tif (\n\t\toptions.state != null &&\n\t\t(!issueStateFilters.includes(options.state) ||\n\t\t\t(options.state !== 'open' && !providersMetadata[options.providerId]?.supportsIssueStates))\n\t) {\n\t\t// A known state this tracker can't express is an unsupported capability; a value outside the vocabulary is\n\t\t// caller input the caller fixes in the request.\n\t\twarnings.push(\n\t\t\tissueStateFilters.includes(options.state)\n\t\t\t\t? unsupportedWarning(\n\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\tdomain,\n\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t`Issue state '${options.state}' is not supported by '${options.providerId}'.`,\n\t\t\t\t\t)\n\t\t\t\t: otherWarning(\n\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\tdomain,\n\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t`Unknown issue state; expected one of ${issueStateFilters.join(', ')}.`,\n\t\t\t\t\t),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\t// A cursor resumed under another state than it was minted for skips what it already covered, whose other-state\n\t// issues were never read. A narrowed read only mints composite cursors, which record their state, so any other\n\t// cursor came from an open read.\n\tconst cursorState =\n\t\tcompositeCursor != null ? (compositeCursor.state ?? 'open') : options.cursor != null ? 'open' : undefined;\n\tif (cursorState != null && cursorState !== (options.state ?? 'open')) {\n\t\twarnings.push(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'The cursor belongs to a read of another issue state; start again without it.',\n\t\t\t),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\tawait ctx.forceRefreshIfRequested(integration, options.forceSync, options.connectionId);\n\n\tawait integration.getCurrentAccount({ connectionId: options.connectionId }).catch(() => undefined);\n\n\tconst { value: resources, warning: resourcesWarning } = await runCaptured(\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\t() => integration.getResourcesForUserResult(options.connectionId),\n\t);\n\tif (resourcesWarning != null) {\n\t\twarnings.push(resourcesWarning);\n\t}\n\tif (resources == null || resources.length === 0) {\n\t\tconst fetchFailed = resourcesWarning != null && resources == null;\n\t\treturn emptyPage(fetchFailed, false, fetchFailed ? discoveryFailureRetry() : undefined);\n\t}\n\n\tconst scopedResources =\n\t\toptions.org != null ? resources.filter(r => selectorMatches(options.providerId, r, options.org!)) : resources;\n\tif (scopedResources.length === 0) {\n\t\treturn emptyPage();\n\t}\n\n\tconst { value: projectsResult, warning: projectsWarning } = await runCaptured(\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\t() => integration.getProjectsForResourcesWithMetadataResult(scopedResources, options.connectionId),\n\t);\n\tif (projectsWarning != null) {\n\t\twarnings.push(projectsWarning);\n\t}\n\t// Partial project discovery: continue with the resources that succeeded, but surface per-resource\n\t// failures as warnings and remember to mark the page fetchFailed so the caller knows some issues may be\n\t// missing. `projectDiscoveryFailed`/`projectDiscoveryTruncated` are OR-ed into the page's\n\t// fetchFailed/truncated at every return below (a truncated-but-not-failed discovery, e.g. a paging\n\t// backstop, still means the project set is incomplete).\n\tconst projectDiscoveryAssessment = mergeAssessmentInto(\n\t\twarnings,\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\tprojectsResult?.metadata,\n\t);\n\tconst projectDiscoveryFailed = projectDiscoveryAssessment.fetchFailed;\n\tconst projectDiscoveryTruncated = projectDiscoveryAssessment.truncated;\n\tconst projects = projectsResult?.values;\n\tif (projects == null || projects.length === 0) {\n\t\tconst fetchFailed = (projectsWarning != null && projectsResult == null) || projectDiscoveryFailed;\n\t\treturn emptyPage(\n\t\t\tfetchFailed,\n\t\t\tprojectDiscoveryTruncated,\n\t\t\tfetchFailed || projectDiscoveryTruncated ? discoveryFailureRetry() : undefined,\n\t\t);\n\t}\n\n\tconst matchedProjects =\n\t\toptions.project != null\n\t\t\t? projects.filter(p => selectorMatches(options.providerId, p, options.project!))\n\t\t\t: projects;\n\n\t// Validate the requested filters against what this provider supports (e.g. Linear/Trello support only\n\t// Assignee). An unsupported filter must not silently degrade — Linear/Trello ignore the requested type\n\t// and apply Assignee regardless — so warn + fetchFailed instead of returning a differently-scoped set.\n\tif (options.filters?.length) {\n\t\tconst supported = providersMetadata[options.providerId]?.supportedIssueFilters;\n\t\tconst allSupported = supported != null && options.filters.every(f => supported.includes(f));\n\t\tif (!allSupported) {\n\t\t\twarnings.push(\n\t\t\t\tunsupportedWarning(\n\t\t\t\t\toptions.providerId,\n\t\t\t\t\tdomain,\n\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t`One or more requested issue filters are not supported by '${options.providerId}'.`,\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn emptyPage(true);\n\t\t}\n\t}\n\n\t// `includeAllAssignees` drops the user scope, but a user-relative filter (Author/Mention) is meaningless\n\t// without a user. Passing both to the provider degrades silently: Jira, seeing no user, falls through to\n\t// an unscoped project fetch and returns EVERY issue instead of the requested author's/mentions. Reject\n\t// the incompatible combination up front rather than publishing a differently-scoped set as the result.\n\tif (options.includeAllAssignees === true && options.filters?.some(f => f !== IssueFilter.Assignee)) {\n\t\twarnings.push(\n\t\t\tunsupportedWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`\\`includeAllAssignees\\` cannot be combined with an author/mention filter for '${options.providerId}' (those filters require a user scope).`,\n\t\t\t),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\t// Scope to the current user's assigned issues unless the caller broadens to all assignees. Resolve the\n\t// handle from each resource's own account (multi-account safe), capturing any error so its kind\n\t// (e.g. auth) is preserved rather than collapsed to a generic warning.\n\tlet usersByResourceId: Map<string, string> | undefined;\n\t// The same accounts keyed by the same resource, holding the provider's stable id rather than the handle. A\n\t// separate map rather than a widened value because the two are resolved and consumed independently: a\n\t// provider may carry an account with no id, and `scopedProjectsWithUsers` gates on the HANDLE alone, so\n\t// folding them together would drop a resource whose id happened to be missing.\n\tlet userIdsByResourceId: Map<string, string> | undefined;\n\tlet accountLookupFailed = false;\n\tif (options.includeAllAssignees !== true) {\n\t\tusersByResourceId = new Map<string, string>();\n\t\tuserIdsByResourceId = new Map<string, string>();\n\t\tconst accounts = await mapBounded(scopedResources, providerFanOutConcurrency, async resource => ({\n\t\t\tresource: resource,\n\t\t\t...(await runCaptured(options.providerId, domain, options.connectionId, () =>\n\t\t\t\tintegration.getAccountForResourceResult(resource, options.connectionId),\n\t\t\t)),\n\t\t}));\n\n\t\tfor (const { resource, value: account, warning: accountWarning } of accounts) {\n\t\t\tconst user = account?.username ?? account?.name ?? undefined;\n\t\t\tif (user != null) {\n\t\t\t\tconst resourceKey = resourceIdForProject(resource) ?? resource.key;\n\t\t\t\tusersByResourceId.set(resourceKey, user);\n\t\t\t\t// Empty-checked, not just null-checked: an account shape declares `id` as a plain string, and a\n\t\t\t\t// provider with nothing to put there sets `''` rather than omitting it — which would otherwise\n\t\t\t\t// reach a user-field query as an empty identity and match nothing.\n\t\t\t\tif (account?.id != null && account.id !== '') {\n\t\t\t\t\tuserIdsByResourceId.set(resourceKey, account.id);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Deduped: a captured `accountWarning` carries only the provider error, with no resource in its\n\t\t\t// message, so one expired token across N resources would otherwise repeat verbatim N times. The\n\t\t\t// synthesized fallback names the resource, so those stay distinct on their own.\n\t\t\tappendDedupedWarning(\n\t\t\t\twarnings,\n\t\t\t\taccountWarning ??\n\t\t\t\t\totherWarning(\n\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\tdomain,\n\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t`Could not resolve the current user for '${resourceLabel(resource)}'; skipping that resource to avoid returning issues assigned to others.`,\n\t\t\t\t\t),\n\t\t\t);\n\t\t\taccountLookupFailed = true;\n\t\t}\n\t}\n\n\tconst fallbackUserForUnscopedProject =\n\t\tscopedResources.length === 1 && usersByResourceId?.size === 1\n\t\t\t? usersByResourceId.values().next().value\n\t\t\t: undefined;\n\tconst userForProject = (project: ResourceDescriptor): string | undefined => {\n\t\tconst resourceId = resourceIdForProject(project);\n\t\tif (resourceId != null) {\n\t\t\tconst user = usersByResourceId?.get(resourceId);\n\t\t\tif (user != null) {\n\t\t\t\treturn user;\n\t\t\t}\n\t\t}\n\n\t\t// Some providers/tests return project descriptors without their parent resource id. When we have only\n\t\t// one scoped resource, re-use that sole resolved user rather than silently dropping every project.\n\t\treturn fallbackUserForUnscopedProject;\n\t};\n\t// Deliberately NOT falling back the way `userForProject` does. That fallback exists so a project with no\n\t// parent resource id still gets scoped rather than dropped, and the handle is what the drop is judged on;\n\t// the id is optional everywhere it is read, so a miss degrades to the handle instead of widening the read.\n\tconst userIdForProject = (project: ResourceDescriptor): string | undefined => {\n\t\tconst resourceId = resourceIdForProject(project);\n\t\treturn resourceId != null ? userIdsByResourceId?.get(resourceId) : undefined;\n\t};\n\n\tconst retryProjectKeys = new Set<string>();\n\tconst completedProjectKeys = new Set(priorCompletedProjects);\n\tfor (const retryProject of priorRetryProjects) {\n\t\tcompletedProjectKeys.delete(retryProject);\n\t}\n\tconst scopedProjectsWithUsers =\n\t\tusersByResourceId != null\n\t\t\t? matchedProjects.filter(project => {\n\t\t\t\t\tif (userForProject(project) != null) return true;\n\n\t\t\t\t\tconst key = projectKey(project);\n\t\t\t\t\tretryProjectKeys.add(key);\n\t\t\t\t\tcompletedProjectKeys.delete(key);\n\t\t\t\t\treturn false;\n\t\t\t\t})\n\t\t\t: matchedProjects;\n\n\t// Select the untouched window plus any earlier windows/projects that explicitly need retrying. Keys\n\t// include the parent resource so two trackers can reuse the same project id without collapsing.\n\tconst projectsByRetryKey = new Map(scopedProjectsWithUsers.map(project => [projectKey(project), project]));\n\tconst scopedProjectsByKey = new Map<string, ResourceDescriptor>();\n\tconst addScopedProject = (project: ResourceDescriptor): void => {\n\t\tconst key = projectKey(project);\n\t\tif (completedProjectKeys.has(key)) return;\n\n\t\tscopedProjectsByKey.set(key, project);\n\t};\n\tfor (const retryProject of priorRetryProjects) {\n\t\tconst project = projectsByRetryKey.get(retryProject);\n\t\tif (project != null) {\n\t\t\taddScopedProject(project);\n\t\t} else if (projectDiscoveryFailed || projectDiscoveryTruncated) {\n\t\t\tretryProjectKeys.add(retryProject);\n\t\t}\n\t}\n\tconst windowPages = new Set<number>(priorRetryPages);\n\tif (normalWindowPage != null) {\n\t\twindowPages.add(normalWindowPage);\n\t}\n\tif (!paginated && (projectDiscoveryFailed || projectDiscoveryTruncated)) {\n\t\twindowPages.add(1);\n\t}\n\tif (paginated) {\n\t\tfor (const windowPage of windowPages) {\n\t\t\tconst windowStart = (windowPage - 1) * projectsPerPage;\n\t\t\tfor (const project of scopedProjectsWithUsers.slice(windowStart, windowStart + projectsPerPage)) {\n\t\t\t\taddScopedProject(project);\n\t\t\t}\n\t\t}\n\t} else if (compositeCursor == null || priorRetryPages.length > 0) {\n\t\t// An unpaged retry-only cursor should re-read only the explicitly failed projects already added\n\t\t// above. Discovery retries still rescan the aggregate set so newly recovered projects are included.\n\t\tfor (const project of scopedProjectsWithUsers) {\n\t\t\taddScopedProject(project);\n\t\t}\n\t}\n\tconst scopedProjects = [...scopedProjectsByKey.values()];\n\tconst furthestWindowPage = windowPages.size > 0 ? Math.max(...windowPages) : normalWindowPage;\n\tconst normalWindowEnd =\n\t\tfurthestWindowPage != null ? furthestWindowPage * projectsPerPage : scopedProjectsWithUsers.length;\n\tconst nextPage =\n\t\tpaginated && furthestWindowPage != null && scopedProjectsWithUsers.length > normalWindowEnd\n\t\t\t? furthestWindowPage + 1\n\t\t\t: undefined;\n\tconst retryWindowPages = (): number[] => {\n\t\tif (!projectDiscoveryFailed && !projectDiscoveryTruncated && retryProjectKeys.size === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst pages = new Set(windowPages);\n\t\tif (pages.size === 0) {\n\t\t\t// A terminal retry has no untouched next window to identify its origin. Keep a manual window\n\t\t\t// marker so a later partial discovery can reconcile against the projects already emitted.\n\t\t\tpages.add(paginated ? Math.max(1, page - 1) : 1);\n\t\t}\n\t\treturn [...pages];\n\t};\n\tif (scopedProjects.length === 0) {\n\t\t// The discovered projects didn't intersect the requested filter/window, or every matching resource\n\t\t// failed user resolution. If discovery or account lookup was partial, the empty result is not a\n\t\t// proven-empty account — carry `fetchFailed` so the caller knows issues may be missing.\n\t\treturn emptyPage(projectDiscoveryFailed || accountLookupFailed, projectDiscoveryTruncated, {\n\t\t\tnextPage: nextPage,\n\t\t\tpages: retryWindowPages(),\n\t\t\tprojects: [...retryProjectKeys],\n\t\t\tcompletedProjects: [...completedProjectKeys],\n\t\t});\n\t}\n\n\t// A read spanning several projects is a merge, so it can only honor a key a normalized issue carries. Bound here\n\t// rather than next to the key's validation above, because the project count is only known now — while still\n\t// before any project read, so a refusal costs no request. Refused rather than served as concatenated\n\t// per-project runs, which would look ordered without being so.\n\t//\n\t// Counted over every project the READ covers, not the `scopedProjects` window this page happens to hold: the\n\t// window is `itemsPerPage` projects wide, so counting it would refuse a 20-project page and then serve the\n\t// final 1-project page of the same paginated call — the same key answered two ways, with the earlier pages'\n\t// issues missing from a read that reported success.\n\t//\n\t// Projects are not the only thing a tracker read merges: a user-scoped read asking for several relationships\n\t// issues one provider query PER relationship and unions them within each project (Jira runs one JQL drain per\n\t// filter and folds them into one map, in filter order), so a single-project page of two filters is just as\n\t// concatenated as a two-project one. Counting only projects published those runs under the requested key.\n\t// Not gated on the provider: the two trackers that don't fan out (Linear, Trello) declare `Assignee` as their\n\t// only supported filter, so a multi-filter read is already refused above and can never reach this.\n\tconst mergesFilters = options.includeAllAssignees !== true && (options.filters?.length ?? 0) > 1;\n\tconst ordering = toIssueOrdering(sort, scopedProjectsWithUsers.length > 1 || mergesFilters);\n\tif (ordering.unmergeable != null) {\n\t\twarnings.push(\n\t\t\tunmergeableIssueSortWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\tordering.unmergeable,\n\t\t\t\tscopedProjectsWithUsers.length > 1 ? 'projects' : 'filters',\n\t\t\t),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\t// One call for the whole window, so a tracker that can search several projects at once (Jira) does, instead\n\t// of paying one drain per project. Results still come back per project, which the accounting below needs.\n\tlet projectReads: IntegrationResult<ProjectIssuesDrain | undefined>[];\n\ttry {\n\t\tprojectReads = await integration.getIssuesForProjectsWithTruncationResult(\n\t\t\tscopedProjects.map(project => ({\n\t\t\t\tproject: project,\n\t\t\t\toptions: {\n\t\t\t\t\tuser: userForProject(project),\n\t\t\t\t\tuserId: userIdForProject(project),\n\t\t\t\t\tfilters: options.filters,\n\t\t\t\t\tsort: sort,\n\t\t\t\t\tstate: options.state,\n\t\t\t\t},\n\t\t\t})),\n\t\t\toptions.connectionId,\n\t\t);\n\t} catch (ex) {\n\t\tconst error = toError(ex);\n\t\tprojectReads = scopedProjects.map(() => ({ error: error }));\n\t}\n\tconst perProject = await Promise.all(\n\t\tscopedProjects.map(async (project, index) => ({\n\t\t\tproject: project,\n\t\t\t...(await runCaptured(options.providerId, domain, options.connectionId, () =>\n\t\t\t\tPromise.resolve(projectReads[index]),\n\t\t\t)),\n\t\t})),\n\t);\n\n\t// Partial project discovery means some projects' issues are missing from this page; propagate it so the\n\t// page reports fetchFailed even when every discovered project's own read succeeded.\n\tlet fetchFailed = projectDiscoveryFailed || accountLookupFailed;\n\tlet truncationRecovery: 'narrow-scope' | 'none' | undefined = projectDiscoveryTruncated ? 'none' : undefined;\n\tlet drainMetadata: CollectionMetadata | undefined;\n\tfor (const { project, value: result, warning } of perProject) {\n\t\tconst key = projectKey(project);\n\t\tconst retryProject = result == null;\n\t\tif (warning != null) {\n\t\t\t// Deduped: the per-project warning is built from the provider error alone and names no project, so a\n\t\t\t// single failing token repeats verbatim once per project in the window.\n\t\t\tappendDedupedWarning(warnings, warning);\n\t\t\tfetchFailed = true;\n\t\t\ttruncationRecovery = 'none';\n\t\t}\n\t\t// A thrown/unsupported read (e.g. Linear not-implemented) surfaces as a warning with no value;\n\t\t// mark the aggregate as fetchFailed so an empty result isn't mistaken for \"no issues\".\n\t\tif (result == null) {\n\t\t\tfetchFailed = true;\n\t\t}\n\t\tif (result != null) {\n\t\t\titems.push(...result.values);\n\t\t\tif (result.truncated) {\n\t\t\t\tconst recovery = result.recovery;\n\t\t\t\ttruncationRecovery = truncationRecovery == null || truncationRecovery === recovery ? recovery : 'none';\n\t\t\t}\n\t\t\tif (result.metadata != null) {\n\t\t\t\tdrainMetadata = mergeCollectionMetadata(drainMetadata, result.metadata);\n\t\t\t}\n\t\t}\n\t\tif (retryProject) {\n\t\t\tretryProjectKeys.add(key);\n\t\t\tcompletedProjectKeys.delete(key);\n\t\t} else {\n\t\t\t// A usable partial/truncated project is emitted once and remains structurally incomplete.\n\t\t\t// Re-running the same project cursor would normally return the same prefix and duplicate it\n\t\t\t// across facade pages; only a project that returned no value is safe to retry automatically.\n\t\t\tcompletedProjectKeys.add(key);\n\t\t}\n\t}\n\n\t// Each provider query arrived ordered by the tracker; the union of them — across projects, and across the\n\t// per-relationship queries a multi-filter read fans out into — is not, and the union is what this read\n\t// publishes. A no-op when nothing merged, since the tracker already ordered that single run.\n\tconst orderedItems = ordering.order(items);\n\n\tconst projectReadReportedTruncation = truncationRecovery != null;\n\tconst drainAssessment = assessCollectionMetadata(options.providerId, domain, options.connectionId, drainMetadata);\n\tfor (const warning of drainAssessment.warnings) {\n\t\t// A project result already supplies the recovery semantics. Replace metadata's unstructured fallback with\n\t\t// the structured warning below, while retaining specific failures and omissions.\n\t\tif (projectReadReportedTruncation && !drainAssessment.fetchFailed && warning.omission == null) continue;\n\n\t\tappendDedupedWarning(warnings, warning);\n\t}\n\tfetchFailed = fetchFailed || drainAssessment.fetchFailed;\n\tif (drainAssessment.truncated) {\n\t\ttruncationRecovery = 'none';\n\t}\n\n\tconst projectTruncated = truncationRecovery != null;\n\tconst scopeTooLarge = !fetchFailed && truncationRecovery === 'narrow-scope';\n\tif (scopeTooLarge || (!fetchFailed && projectTruncated && !warnings.some(warning => warning.omission != null))) {\n\t\tappendDedupedWarning(\n\t\t\twarnings,\n\t\t\tincompleteReadWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\tscopeTooLarge\n\t\t\t\t\t? 'Some projects hold more issues than one read can return; narrow the scope to read the rest.'\n\t\t\t\t\t: 'Some issues were omitted; the provider returned an incomplete result.',\n\t\t\t\tscopeTooLarge ? 'scope-too-large' : 'exhausted',\n\t\t\t),\n\t\t);\n\t}\n\t// A metadata omission from an earlier project asserts the read succeeded; a later one may since have failed.\n\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\n\tconst retryPages = retryWindowPages();\n\tconst cursor = toIssueTrackerPageCursor({\n\t\tcurrentPage: page + 1,\n\t\tunpaged: !paginated,\n\t\tnextPage: nextPage,\n\t\tretryPages: retryPages,\n\t\tretryProjects: [...retryProjectKeys],\n\t\tcompletedProjects:\n\t\t\t(trackCompletedProjects ||\n\t\t\t\tprojectDiscoveryFailed ||\n\t\t\t\tprojectDiscoveryTruncated ||\n\t\t\t\tretryProjectKeys.size > 0) &&\n\t\t\t(retryPages.length > 0 || nextPage != null)\n\t\t\t\t? [...completedProjectKeys]\n\t\t\t\t: [],\n\t\tstate: options.state,\n\t});\n\treturn {\n\t\titems: orderedItems,\n\t\twarnings: warnings,\n\t\tpage: { currentPage: page, itemsPerPage: orderedItems.length, truncated: projectTruncated || undefined },\n\t\t// Failed-project retries alone are manual. Only an untouched project window is automatic progress.\n\t\thasMore: nextPage != null && cursor != null,\n\t\tcursor: cursor,\n\t\tfetchFailed: fetchFailed || undefined,\n\t};\n}\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import "../constants.js";
2
2
  import { reconcileOmissionsWithFailure } from "../results.js";
3
3
  import { mergeAssessmentInto } from "../collectionMetadata.js";
4
- import { mergeCollectionMetadata } from "../providers/utils/providerPaging.js";
5
4
  import { isGitHostIntegration, isIssuesHostIntegrationId, warnOnMissingSessionForDomain } from "../utils/integration.utils.js";
5
+ import { mergeCollectionMetadata } from "../providers/utils/providerPaging.js";
6
6
  import { isRepoIdsInput, providersMetadata } from "../providers/models.js";
7
7
  import { drainFlatPagesToRequestedPage, drainToRequestedPage, isPageNumberAdvanceable, pageToCursor, refusedPage, resolveContinuation, resolveCurrentPage, toProviderPageInfo, usableCursor } from "./paging.js";
8
8
  import { gitHostOnlySurfaceWarning, issuesUnsupportedWarning, otherWarning, truncationWarning, unmergeableIssueSortWarning, unsupportedAccountWideIssueFiltersWarning, unsupportedIssueSortWarning, unsupportedWarning } from "./warnings.js";
@@ -53,9 +53,13 @@ export interface ProviderWarningScope {
53
53
  /**
54
54
  * The resource as the read addressed it: its id on most reads, its name on the few that address it by name
55
55
  * (Azure DevOps' repo-scoped reads, Bitbucket's workspace reads). Match it against both
56
- * `ProviderOrganization.id` and `name`.
56
+ * `ProviderOrganization.id` and `name`, and a project's `orgId` where it has one.
57
57
  */
58
58
  resourceId?: string;
59
+ /**
60
+ * The project as the read addressed it. For a Jira project read, the key on Jira Cloud (the project's `name` in
61
+ * `listProjects`) and the numeric id on Jira Data Center.
62
+ */
59
63
  projectId?: string;
60
64
  repositoryId?: string;
61
65
  }
@@ -343,13 +347,19 @@ export interface ResolveRepositoryResult {
343
347
  export declare function providerWarningMessage(ex: unknown): string;
344
348
  /**
345
349
  * Normalized org/workspace/group shape returned by the provider facade.
346
- * `name` is the provider identifier to pass to follow-up reads, while `org` is an optional display label.
350
+ * `name` is the provider identifier to pass to follow-up reads (a Jira project's key), while `org` is an optional
351
+ * display label of the parent. Where that label can't address the parent, `orgId` is the parent's stable id to pass
352
+ * as `org` instead (a Jira site's id, or a Data Center instance's domain).
353
+ *
354
+ * Jira site rows (`listOrgs`) are the exception: their `name` is the site's label, and a Jira read selects a site only
355
+ * by its `id` (or a project row's `orgId`).
347
356
  */
348
357
  export interface ProviderOrganization {
349
358
  id: string;
350
359
  providerId: IntegrationIds;
351
360
  name: string;
352
361
  org?: string;
362
+ orgId?: string;
353
363
  url: string;
354
364
  }
355
365
  /** Normalized repository shape returned by the provider facade. */
@@ -1 +1 @@
1
- {"version":3,"file":"results.js","names":[],"sources":["../../../../plus/integrations/src/results.ts"],"sourcesContent":["import { isUnsupportedSortError } from '@gitkraken/provider-apis';\nimport { AuthenticationError, RequestNotFoundError, RequestRateLimitError } from '@gitlens/git/errors.js';\nimport type { IssueSorting } from '@gitlens/git/models/issue.js';\nimport type { PullRequestSorting } from '@gitlens/git/models/pullRequest.js';\nimport type { IntegrationIds } from './constants.js';\nimport { isProviderUnreachableError } from './errors.js';\n\nexport interface ConnectionStateChangeEvent {\n\tkey: string;\n\treason: 'connected' | 'disconnected';\n}\n\n/**\n * A per-provider, per-connection warning surfaced alongside (partial) read results. Consumers use\n * these to drive auth recovery, retry, or truncation messaging without the read itself throwing.\n *\n * `kind` is the programmatic discriminant (a rate-limit is retryable, a 404 is not, an unsupported capability\n * never succeeds on retry, an auth failure needs re-connection unless {@link ProviderWarning.scope} confines\n * it); `isAuth` is retained as a convenience mirror of `kind === 'auth'`.\n *\n * `'unsupported'`: the provider — or this integration of it — lacks the capability the request asked for: a\n * surface (issues on Bitbucket, pull requests on an issue tracker, a batch read, org/repo discovery,\n * current-account lookup, project-scoped or account-wide issue reads), or a filter, sort, state, search\n * criterion or option combination it cannot express. Retrying the same request never succeeds; a different\n * provider, scope or request may. NOT unsupported (these stay `'other'`): malformed or contradictory caller\n * input the caller fixes in the request itself (a duplicate batch key, invalid target numbers, an empty owner,\n * an unusable scope), incompleteness or omission warnings, and unclassified errors.\n */\nexport type ProviderWarningKind = 'auth' | 'rate-limit' | 'not-found' | 'no-connection' | 'unsupported' | 'other';\n\n/**\n * Why a read that SUCCEEDED still withheld results:\n * - `provider-limit`: the provider refuses to serve past a cap it enforces (GitHub search's 1,000-result\n * ceiling, Trello's `cards_limit`), so the excess is unreachable through that query.\n * - `recovery-budget`: the internal partitioned recovery stopped spending upstream requests before it ran\n * out of partitions to visit.\n * - `pagination-incomplete`: pages were left unread — a sub-scope of a multi-scope read that was not drained,\n * a drain that stopped at its own page backstop, or a provider that advertised another page and gave no way\n * to reach it. These have DIFFERENT remedies, so the kind alone does not say whether more can be fetched;\n * read {@link ProviderWarningOmission.recovery} for that.\n *\n * Mirrors the vocabulary the SDK reports. Declared here rather than imported so this module stays free of\n * `@gitkraken/provider-apis` types (see the export block in `index.ts`); `collectionMetadata.ts` holds the\n * compile-time link, so an SDK bump that adds a kind fails the build at that boundary.\n */\nexport type ProviderWarningOmissionKind = 'provider-limit' | 'recovery-budget' | 'pagination-incomplete';\n\n/**\n * How a consumer can recover omitted results. `page-budget` reruns with a larger budget; `narrow-scope`\n * requests a smaller server-side scope; `none` promises neither. Switch on the exact value because only\n * `page-budget` is a fetch-more action.\n */\nexport type ProviderWarningOmissionRecovery = 'none' | 'page-budget' | 'narrow-scope';\n\n/**\n * The part of a provider a warning is confined to: one resource (organization, workspace, site), project or\n * repository. All fields optional; see {@link ProviderWarning.scope} for when one is forwarded.\n */\nexport interface ProviderWarningScope {\n\t/**\n\t * The resource as the read addressed it: its id on most reads, its name on the few that address it by name\n\t * (Azure DevOps' repo-scoped reads, Bitbucket's workspace reads). Match it against both\n\t * `ProviderOrganization.id` and `name`.\n\t */\n\tresourceId?: string;\n\tprojectId?: string;\n\trepositoryId?: string;\n}\n\n/**\n * Why a provider request failed, when the failure itself says so:\n * - `unreachable`: a transport failure or server-side error prevented the provider from answering. The connection\n * still exists; this accompanies `kind: 'other'`, never an authentication warning.\n * - `oauth-app-not-allowed`: the organization does not let third-party OAuth apps in. On Azure DevOps this is the\n * organization policy \"Third-party application access via OAuth\", off by default for new organizations. An\n * organization admin enables it, or the user connects with a personal access token instead, which the policy\n * does not govern.\n * - `access-denied`: the account has no access to that organization or project (not a member, or lacking the\n * permission). Someone who administers it has to grant access.\n * - `conditional-access`: a Microsoft Entra Conditional Access policy blocked the request (Azure DevOps\n * `VS403463`). Its admin has to exempt the request, e.g. by location or device.\n *\n * None of these is healed by reconnecting, which is the point of naming them.\n */\nexport type ProviderWarningCauseReason =\n\t| 'oauth-app-not-allowed'\n\t| 'access-denied'\n\t| 'conditional-access'\n\t| 'unreachable';\n\n/** See {@link ProviderWarning.cause}. */\nexport interface ProviderWarningCause {\n\treason: ProviderWarningCauseReason;\n\t/** The provider's own error code, when it reports one (Azure DevOps `TF400813`, `VS403463`). */\n\tcode?: string;\n\t/** Where the setting behind the refusal is changed, when this layer can address it. */\n\tremedyUrl?: string;\n}\n\n/** Which repository / project / resource an omission is attributed to. All fields optional; a scope may name none. */\nexport interface ProviderWarningOmissionScope extends ProviderWarningScope {\n\tproviderId?: string;\n}\n\nexport interface ProviderWarningOmission {\n\tkind: ProviderWarningOmissionKind;\n\t/**\n\t * Whether anything would fetch the missing items, and what. Gate a \"load more\" affordance on this rather\n\t * than on `kind` — see {@link ProviderWarningOmissionRecovery}.\n\t */\n\trecovery: ProviderWarningOmissionRecovery;\n\t/**\n\t * The cap the provider enforces, when it reports one. NOT a result count for every kind: on\n\t * `recovery-budget` this is a REQUEST budget and must not be shown to a user as a number of results.\n\t */\n\tlimit?: number;\n\t/** Total matches the provider reported, when it reports one. Only GitHub's search cap does today. */\n\ttotalCount?: number;\n\t/**\n\t * The order the reachable window was selected under, as `field:direction`, when the omission is a result\n\t * ceiling.\n\t *\n\t * Present so a consumer can word its own \"N matched, showing the first M\" without assuming WHICH M: the\n\t * provider serves the top M under this key, so the sentence is only true if it names it. Absent for every\n\t * omission that isn't a ceiling, where no window was selected by an order.\n\t *\n\t * A union of both vocabularies rather than the issue one alone: `PullRequestSorting` happens to be a subset\n\t * today, so narrowing this to {@link IssueSorting} would still compile while describing only half the reads\n\t * that set it — and would break the day the PR search gains a key the issue search has no name for.\n\t */\n\tsort?: IssueSorting | PullRequestSorting;\n\t/** Which repository / project / resource was affected, when one is attributed. */\n\tscope?: ProviderWarningOmissionScope;\n}\n\nexport interface ProviderWarning {\n\tproviderId: IntegrationIds;\n\t/** Disambiguates connections on self-managed hosts (where one provider id spans multiple domains). */\n\tdomain?: string;\n\t/** The specific token/connection the read was pinned to, when a `connectionId` was supplied. */\n\tconnectionId?: string;\n\tmessage: string;\n\tkind: ProviderWarningKind;\n\t/** Convenience mirror of `kind === 'auth'`, scoped or not; read {@link scope} before prompting to reconnect. */\n\tisAuth: boolean;\n\t/**\n\t * Present when the failure was recorded against one sub-scope of the read (an organization, project or\n\t * repository) rather than against the connection. A scoped `auth` failure is that one scope refusing this\n\t * credential, e.g. an Azure DevOps organization that disallows third-party OAuth apps, one in another Entra\n\t * tenant, or a Conditional Access policy. Reconnecting cannot fix those, so this warning alone is no reason\n\t * to prompt for it.\n\t *\n\t * `kind` and `isAuth` are unaffected: a scoped 401 is still an authentication failure, and this field says\n\t * how far it reaches. It names at least one of its IDs, and is never set on an omission (which carries its\n\t * own {@link ProviderWarningOmission.scope}) or on a warning derived from a caught exception (see\n\t * {@link toProviderWarning}), even when that call targeted a single organization. The exception is a batch\n\t * read's refused target, which names its scope: the Azure DevOps organization and project, the repository on\n\t * other git hosts, or the tracker's resource.\n\t *\n\t * Its ABSENCE means account-wide or unattributed, so existing handling stays correct without it.\n\t *\n\t * A scoped `auth` failure also means the credential itself was accepted. A dead token can come back as nothing\n\t * but scoped refusals wherever a read reaches its scopes without an uncached request to the connection first:\n\t * discovery served from a per-token cache (Azure DevOps, Bitbucket, Jira Cloud) or an SDK fan-out across the\n\t * requested repositories (Bitbucket Data Center). So when a read's only auth failures are scoped, those providers\n\t * confirm the credential with one uncached check first, and a refused credential fails the whole read with an\n\t * unscoped `auth` warning. A batch read checks it only when every target was refused, since a target that\n\t * answered already proved it. A refusal the provider attributes to the credential itself, like Bitbucket's or Jira\n\t * Cloud's for a token missing the OAuth scopes a read needs, which a reconnect fixes, is published unscoped too,\n\t * once for the connection, while the scopes that answered keep their results.\n\t *\n\t * So the promise holds for a read that carries no unscoped `auth` warning of its own; one that does already asks\n\t * for a reconnect, and its other scoped refusals are not checked. Two exceptions stay scoped even then. A check\n\t * that could not complete or was denied (a network error, a throttle, a `403`, Bitbucket Data Center refusing a\n\t * credential it authenticated, or a Bitbucket Data Center project access token, whose user the check cannot\n\t * find) proves nothing, so the warnings are published unconfirmed, with no {@link cause}, and the next read\n\t * checks again. And a credential confirmed within the last minute is not probed again, so a revocation can take\n\t * up to a minute to surface.\n\t */\n\tscope?: ProviderWarningScope;\n\t/**\n\t * Why the provider request failed, when the failure itself says so: see {@link ProviderWarningCauseReason}. Switch on\n\t * `reason` to recommend a fix instead of a reconnect; `message` then carries the same explanation as prose.\n\t *\n\t * `unreachable` accompanies a transport or server failure with `kind: 'other'`. Credential refusal causes are\n\t * set only on a scoped `auth` warning whose credential was confirmed first (see {@link scope}), because the\n\t * refusals they name are indistinguishable from a dead credential until then: Azure DevOps answers a\n\t * third-party OAuth app its organization disallows exactly as it answers an expired token. Only Azure DevOps\n\t * reports one today. Its absence proves nothing about the cause.\n\t */\n\tcause?: ProviderWarningCause;\n\t/**\n\t * Present when this warning describes results the read could not return even though the request itself\n\t * SUCCEEDED — a provider-enforced cap, an exhausted recovery budget, a page budget, or a sub-scope the read\n\t * did not drain.\n\t *\n\t * Its presence is the signal: an omission is not a failure. Message it as incompleteness rather than as a\n\t * failed read, and do NOT derive that from `message`, which is English prose and subject to rewording.\n\t * `kind` stays `'other'` for these, so the failure discriminant keeps meaning exactly what it meant before\n\t * this field existed.\n\t *\n\t * Whether anything would fetch the rest is a SEPARATE question — read {@link ProviderWarningOmission.recovery},\n\t * not `kind`, and never assume a retry of the same request returns more.\n\t *\n\t * `limit` / `totalCount` / `scope` are forwarded only when they are reported; most omissions carry none of\n\t * the three, so render correctly without them — and see {@link ProviderWarningOmission.limit} before\n\t * showing that figure as a result count.\n\t *\n\t * Its ABSENCE proves nothing. It is never set on a warning derived from a caught exception (see\n\t * {@link toProviderWarning}) or from a structured scope failure — those are failures, and the field would\n\t * be a lie there. But it is also absent whenever incompleteness was reported without naming what was left\n\t * out, so treat a bare `kind: 'other'` warning as unclassified rather than as a proven failure.\n\t */\n\tomission?: ProviderWarningOmission;\n\t/**\n\t * The keys of the batch targets this warning concerns: the target a per-target failure was refused or failed\n\t * for, or every target of an integration call that failed as a whole. So a caller that sends several callers'\n\t * targets in one call can hand a failure only to the targets it belongs to.\n\t *\n\t * Its ABSENCE means the whole call. It is absent on every read that is not a batch read, and on a batch read's\n\t * refusal of the whole call before any target is read (a duplicate key, an invalid target, an unknown etag\n\t * include, no resolvable connection, the wrong surface), which concerns every target alike.\n\t *\n\t * It is not part of a warning's identity: two warnings that differ only here are one warning, for the union of\n\t * their targets (see {@link appendDedupedWarning}).\n\t */\n\ttargetKeys?: string[];\n}\n\nexport interface ProviderPageInfo {\n\t/**\n\t * The 1-based POSITION of the returned page. One convention across every paged read, page-number and\n\t * cursor paging alike, so a `getNextPageParam`-style consumer can key off it uniformly:\n\t * - the page number the provider itself reported, when it reports one (authoritative);\n\t * - else the position the caller addressed: the page encoded in a threaded page cursor, or the `page`\n\t * supplied alongside an opaque cursor (1 when the caller supplied no `page`);\n\t * - else, for a read that can be advanced by page number, the requested `page` — the provider either\n\t * honored it or the internal drain reached it (see the paging contract on `IntegrationManager`);\n\t * - else 1: a cursor-only read with no cursor threaded back ignores a requested page number and answers\n\t * with its first page, so echoing the request would mislabel page 1 as page N.\n\t *\n\t * A page past the provider's terminal cursor still reports the requested `page` with empty `items` — an\n\t * empty page N, never page N-1 relabeled.\n\t *\n\t * Continue a cursor-paged read from `cursor`, NOT from `currentPage + 1`: a cursor-only host can't be\n\t * addressed by number, so this is a position label and not always a resumable request parameter.\n\t */\n\tcurrentPage: number;\n\t/**\n\t * The page size IN EFFECT, which is not always the one requested — it is whichever of these the read can\n\t * actually vouch for:\n\t * - the size the provider reported for the page it served (numbered-page hosts), else\n\t * - the `itemsPerPage` that was applied to the request (repo-scoped reads, which forward it), else\n\t * - `items.length`, for a read that takes no page size at all: the account-wide PR/issue reads, `listRepos`\n\t * (cursor-only), and `listIssueTrackerIssuesPage`, whose `itemsPerPage` counts PROJECTS rather than issues.\n\t *\n\t * So it is a description of the page, not an echo of the request: don't derive a total from\n\t * `currentPage * itemsPerPage`, and don't read `items.length < itemsPerPage` as \"last page\" — `hasMore` /\n\t * `cursor` are the only continuation signals.\n\t */\n\titemsPerPage: number;\n\t/**\n\t * A COMPLETENESS assertion, not a mode flag: `true` only when a sweep drained every page of every target\n\t * without truncating or failing (`!truncated && !fetchFailed`). Set exclusively by `sweepPullRequests` /\n\t * `sweepClosedPullRequests` and absent on every paged read, so `allPages !== true` does NOT by itself mean\n\t * \"this was a single page\".\n\t *\n\t * This is the signal to gate on before caching a sweep as authoritative: unlike `truncated` it is `false`\n\t * for BOTH kinds of incompleteness, so it can't be misread as a benign cap.\n\t */\n\tallPages?: boolean;\n\t/**\n\t * True when the read stopped before it could confirm it had everything. Deliberately does NOT distinguish\n\t * WHY, and a consumer must not assume it means a provider ceiling — a sweep sets it from three different\n\t * origins:\n\t * - a provider-side ceiling or per-scope backstop propagated from below (e.g. GitHub Search's 1000-result\n\t * cap): the missing items are NOT reachable, so this really is \"complete to the provider's limit\";\n\t * - the sweep's OWN `maxPages` cap (default 100) with a usable cursor still in hand: the missing items ARE\n\t * reachable and were simply not fetched — raise `maxPages` to get them;\n\t * - the provider reporting another page but handing back no usable cursor: not reachable.\n\t *\n\t * Only the second is recoverable, and a sweep exposes no continuation cursor, so treating a truncated sweep\n\t * as a complete set silently drops reachable data in that case. Prefer `allPages` for the cache decision.\n\t */\n\ttruncated?: boolean;\n}\n\nexport interface ProviderResult<T> {\n\titems: T[];\n\twarnings: ProviderWarning[];\n\t/**\n\t * True when `items` is incomplete — distinguishes a failed or truncated flat read from a genuinely empty\n\t * result. Paged results additionally use `page.truncated` when a completed page could not confirm it\n\t * drained everything.\n\t */\n\tfetchFailed?: boolean;\n}\n\nexport interface ProviderPagedResult<T> extends ProviderResult<T> {\n\tpage: ProviderPageInfo;\n\thasMore: boolean;\n\t/**\n\t * Opaque cursor for cursor-based paging (GraphQL, per-repo/per-project cursors, etc.). For hosts that\n\t * require the previous page's cursor, consumers should pass this value back to the next `cursor` option.\n\t * A retry-only cursor may remain when `hasMore` is false: `hasMore` means automatic forward progress,\n\t * while explicitly reusing such a cursor retries failed work without creating an infinite paging loop.\n\t */\n\tcursor?: string;\n}\n\nexport interface ProviderSweepResult<T> extends ProviderResult<T> {\n\tpage: ProviderPageInfo;\n\thasMore: boolean;\n\t/**\n\t * Providers whose top-level read failed before returning any usable page. Partial per-scope failures stay\n\t * represented by `warnings` + `fetchFailed` and are intentionally excluded.\n\t */\n\tfailedProviderIds: IntegrationIds[];\n\t/**\n\t * Providers whose read started successfully but could not produce an authoritative complete slice (for\n\t * example, a later page failed, collection metadata reported a partial scope, or a paging backstop was hit).\n\t * Kept separate from\n\t * `failedProviderIds` so consumers can accept healthy sibling providers while preserving an older snapshot\n\t * for a provider whose returned slice has a gap.\n\t */\n\tincompleteProviderIds: IntegrationIds[];\n}\n\nexport interface ProviderBroadenResult<T> extends ProviderPagedResult<T> {\n\tbroadenedProviderIds: IntegrationIds[];\n\t/** Providers for which no requested org produced a usable issue result. */\n\tfailedProviderIds: IntegrationIds[];\n\t/** Providers with both a usable org result and at least one failed or truncated org slice. */\n\tincompleteProviderIds: IntegrationIds[];\n\tfanOutCount: number;\n}\n\nexport type RepositoryResolutionStatus =\n\t| 'resolved'\n\t| 'not-found'\n\t| 'unauthorized'\n\t| 'unsupported-provider'\n\t| 'invalid-remote-url'\n\t| 'host-mismatch'\n\t| 'undetermined';\n\nexport interface RepositoryIdentity {\n\tproviderId: IntegrationIds;\n\tdomain: string;\n\t/** The provider's canonical owner/namespace (follows renames), falling back to the parsed remote when omitted. */\n\towner: string;\n\t/** The provider's canonical repo name (follows renames), falling back to the parsed remote when omitted. */\n\tname: string;\n\tproject?: string;\n\t/** The original input remote URL, so the caller can key the resolution back to the remote it asked about. */\n\tremoteUrl: string;\n\t/**\n\t * True when the provider's canonical owner/name differ from what the input remote URL carried — i.e. the\n\t * repo was renamed/moved host-side and the local remote is stale. Case-insensitive, mirroring gkcli's\n\t * `EqualFold` compare so hosts that echo the input casing (e.g. Bitbucket Server/Azure) aren't flagged.\n\t */\n\trenamed: boolean;\n}\n\nexport interface RepositoryResolution {\n\tstatus: RepositoryResolutionStatus;\n\tidentity?: RepositoryIdentity;\n\twarning?: ProviderWarning;\n}\n\nexport interface ResolveRepositoryResult {\n\tresolution: RepositoryResolution;\n}\n\nconst maxProviderWarningMessageLength = 500;\n\n/** A caught error's message as a warning can carry it: an HTML page becomes its status, and prose is capped. */\nexport function providerWarningMessage(ex: unknown): string {\n\tconst raw = (ex instanceof Error ? ex.message : String(ex)).trim();\n\tconst carrier = ex as { status?: unknown; response?: { status?: unknown } } | null | undefined;\n\tconst status =\n\t\ttypeof carrier?.status === 'number'\n\t\t\t? carrier.status\n\t\t\t: typeof carrier?.response?.status === 'number'\n\t\t\t\t? carrier.response.status\n\t\t\t\t: undefined;\n\n\t// Gateways commonly answer API requests with a complete HTML error page. That payload is neither actionable\n\t// nor suitable for a warning DTO consumed by UIs and logs; preserve the status without leaking the document.\n\tif (/<(?:!doctype\\s+html|html|head|body)\\b/i.test(raw)) {\n\t\treturn status != null ? `Provider request failed with status ${status}.` : 'Provider request failed.';\n\t}\n\n\tif (!raw) {\n\t\treturn status != null ? `Provider request failed with status ${status}.` : 'Provider request failed.';\n\t}\n\treturn raw.length > maxProviderWarningMessageLength\n\t\t? `${raw.slice(0, maxProviderWarningMessageLength - 3)}...`\n\t\t: raw;\n}\n\n/**\n * Normalized org/workspace/group shape returned by the provider facade.\n * `name` is the provider identifier to pass to follow-up reads, while `org` is an optional display label.\n */\nexport interface ProviderOrganization {\n\tid: string;\n\tproviderId: IntegrationIds;\n\tname: string;\n\torg?: string;\n\turl: string;\n}\n\n/** Normalized repository shape returned by the provider facade. */\nexport interface ProviderRepositoryShape {\n\tid: string;\n\tnamespace: string;\n\tname: string;\n\t/** Azure DevOps project; `undefined` for hosts without a project layer. */\n\tproject?: string;\n\t/** Web (browser) URL, when the provider exposes it. */\n\turl?: string;\n\t/** HTTPS clone URL, when available. */\n\tcloneUrlHttps?: string;\n\t/** SSH clone URL, when available. */\n\tcloneUrlSsh?: string;\n\t/** Default branch name, when the provider reports it. */\n\tdefaultBranch?: string;\n}\n\n/**\n * Classifies a caught provider exception into a neutral {@link ProviderWarning}. Ordering matters:\n * `instanceof` is checked most-specific-first so a rate-limit isn't mislabeled as a generic error and\n * a 404 isn't mislabeled as auth.\n */\nexport function toProviderWarning(\n\tproviderId: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tex: unknown,\n): ProviderWarning {\n\tlet kind: ProviderWarning['kind'];\n\tif (ex instanceof AuthenticationError) {\n\t\tkind = 'auth';\n\t} else if (ex instanceof RequestRateLimitError) {\n\t\tkind = 'rate-limit';\n\t} else if (ex instanceof RequestNotFoundError) {\n\t\tkind = 'not-found';\n\t} else if (isUnsupportedSortError(ex)) {\n\t\tkind = 'unsupported';\n\t} else {\n\t\tkind = 'other';\n\t}\n\n\treturn {\n\t\tproviderId: providerId,\n\t\tdomain: domain,\n\t\tconnectionId: connectionId,\n\t\tmessage: providerWarningMessage(ex),\n\t\tkind: kind,\n\t\tisAuth: kind === 'auth',\n\t\t...(kind === 'other' && isProviderUnreachableError(ex) ? { cause: { reason: 'unreachable' as const } } : {}),\n\t};\n}\n\n/**\n * A scope's identity, as the stable string that keys it.\n *\n * The single definition of what \"the same scope\" means, shared by the failure and omission dedup keys in\n * `providerPaging.ts` and by {@link providerWarningKey} below, so a scope gaining a field is one edit rather\n * than three. `providerId` is included: the same repository ID under two providers is two scopes.\n *\n * The parameter is structural rather than the SDK's `CollectionScope` so this module keeps naming no\n * `@gitkraken/provider-apis` types (see the export block in `index.ts`) while still serving its SDK-facing\n * callers, which pass that type in unchanged.\n */\nexport function collectionScopeKey(scope: ProviderWarningOmissionScope | undefined): string {\n\treturn [scope?.providerId ?? '', scope?.resourceId ?? '', scope?.projectId ?? '', scope?.repositoryId ?? ''].join(\n\t\t' ',\n\t);\n}\n\n/**\n * The omission's contribution to a warning's identity — empty when there is none, so a warning without an\n * omission keeps deduping exactly as it did before the field existed.\n *\n * Two omissions of different kinds do produce different `message` values today, so message alone would still\n * separate them. That is incidental: the premise of `omission` is that consumers must not depend on prose\n * carrying the distinguishing fact, and this key must not either.\n */\nfunction providerWarningOmissionKey(omission: ProviderWarningOmission | undefined): string {\n\tif (omission == null) return '';\n\n\treturn [\n\t\tomission.kind,\n\t\tomission.recovery,\n\t\tomission.limit ?? '',\n\t\tomission.totalCount ?? '',\n\t\tcollectionScopeKey(omission.scope),\n\t].join(' ');\n}\n\n/**\n * Strips the omission from every warning in `warnings` when the read as a whole failed.\n *\n * An omission asserts the request SUCCEEDED, and a drain only learns it failed AFTER it may have emitted one:\n * an early page can report its own truncation and then a later page can die. Deciding per warning, at the\n * moment each is built, cannot see that future — so the aggregate is reconciled once, here, where\n * `fetchFailed` is final. Call it at the point a read returns its `fetchFailed`.\n *\n * Re-dedupes as it goes: the omission is part of a warning's identity, so two warnings that differed only\n * there become identical once it is gone, and the array's contract is that no two entries are equal.\n *\n * Mutates in place: the warning array is the one being returned, and callers accumulate into it across pages.\n */\nexport function reconcileOmissionsWithFailure(warnings: ProviderWarning[], fetchFailed: boolean): void {\n\tif (!fetchFailed || !warnings.some(w => w.omission != null)) return;\n\n\tconst reconciled: ProviderWarning[] = [];\n\tfor (const warning of warnings) {\n\t\tif (warning.omission == null) {\n\t\t\tappendDedupedWarning(reconciled, warning);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst { omission: _omission, ...rest } = warning;\n\t\tappendDedupedWarning(reconciled, rest);\n\t}\n\n\twarnings.splice(0, warnings.length, ...reconciled);\n}\n\n/**\n * A stable key for deduplicating warnings accumulated across drained pages / fan-out scopes.\n *\n * The scope and the cause are keyed for the reason the omission is (see {@link providerWarningOmissionKey}): today's\n * failure messages happen to spell them out, but two failures that differ in either must stay two warnings even if\n * they don't.\n *\n * `message` stays LAST. It is the only free-form segment — provider prose, spaces and all — so anything\n * appended after it could be impersonated by a message that happens to end in the same text.\n */\nfunction providerWarningKey(warning: ProviderWarning): string {\n\treturn [\n\t\twarning.providerId,\n\t\twarning.connectionId ?? '',\n\t\twarning.domain ?? '',\n\t\twarning.kind,\n\t\tcollectionScopeKey(warning.scope),\n\t\twarning.cause?.reason ?? '',\n\t\tproviderWarningOmissionKey(warning.omission),\n\t\twarning.message,\n\t].join(' ');\n}\n\n/**\n * Appends `warning` to `into` only when an equal warning (by provider/connection/domain/kind/message, plus the\n * structured scope, cause and omission when present) is absent. An equal warning already there merges its\n * {@link ProviderWarning.targetKeys} with the new one's instead: the union of both, or none when either concerns\n * the whole call. The existing warning is replaced, never mutated, since it may be shared.\n */\nexport function appendDedupedWarning(into: ProviderWarning[], warning: ProviderWarning): void {\n\tconst key = providerWarningKey(warning);\n\tconst index = into.findIndex(existing => providerWarningKey(existing) === key);\n\tif (index === -1) {\n\t\tinto.push(warning);\n\t\treturn;\n\t}\n\n\tconst existing = into[index];\n\tconst existingKeys = existing.targetKeys;\n\t// Already covers the whole call.\n\tif (existingKeys == null) return;\n\n\tif (warning.targetKeys == null) {\n\t\tconst { targetKeys: _, ...wholeCall } = existing;\n\t\tinto[index] = wholeCall;\n\t\treturn;\n\t}\n\n\tconst added = warning.targetKeys.filter(k => !existingKeys.includes(k));\n\tif (!added.length) return;\n\n\tinto[index] = { ...existing, targetKeys: [...existingKeys, ...new Set(added)] };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"results.js","names":[],"sources":["../../../../plus/integrations/src/results.ts"],"sourcesContent":["import { isUnsupportedSortError } from '@gitkraken/provider-apis';\nimport { AuthenticationError, RequestNotFoundError, RequestRateLimitError } from '@gitlens/git/errors.js';\nimport type { IssueSorting } from '@gitlens/git/models/issue.js';\nimport type { PullRequestSorting } from '@gitlens/git/models/pullRequest.js';\nimport type { IntegrationIds } from './constants.js';\nimport { isProviderUnreachableError } from './errors.js';\n\nexport interface ConnectionStateChangeEvent {\n\tkey: string;\n\treason: 'connected' | 'disconnected';\n}\n\n/**\n * A per-provider, per-connection warning surfaced alongside (partial) read results. Consumers use\n * these to drive auth recovery, retry, or truncation messaging without the read itself throwing.\n *\n * `kind` is the programmatic discriminant (a rate-limit is retryable, a 404 is not, an unsupported capability\n * never succeeds on retry, an auth failure needs re-connection unless {@link ProviderWarning.scope} confines\n * it); `isAuth` is retained as a convenience mirror of `kind === 'auth'`.\n *\n * `'unsupported'`: the provider — or this integration of it — lacks the capability the request asked for: a\n * surface (issues on Bitbucket, pull requests on an issue tracker, a batch read, org/repo discovery,\n * current-account lookup, project-scoped or account-wide issue reads), or a filter, sort, state, search\n * criterion or option combination it cannot express. Retrying the same request never succeeds; a different\n * provider, scope or request may. NOT unsupported (these stay `'other'`): malformed or contradictory caller\n * input the caller fixes in the request itself (a duplicate batch key, invalid target numbers, an empty owner,\n * an unusable scope), incompleteness or omission warnings, and unclassified errors.\n */\nexport type ProviderWarningKind = 'auth' | 'rate-limit' | 'not-found' | 'no-connection' | 'unsupported' | 'other';\n\n/**\n * Why a read that SUCCEEDED still withheld results:\n * - `provider-limit`: the provider refuses to serve past a cap it enforces (GitHub search's 1,000-result\n * ceiling, Trello's `cards_limit`), so the excess is unreachable through that query.\n * - `recovery-budget`: the internal partitioned recovery stopped spending upstream requests before it ran\n * out of partitions to visit.\n * - `pagination-incomplete`: pages were left unread — a sub-scope of a multi-scope read that was not drained,\n * a drain that stopped at its own page backstop, or a provider that advertised another page and gave no way\n * to reach it. These have DIFFERENT remedies, so the kind alone does not say whether more can be fetched;\n * read {@link ProviderWarningOmission.recovery} for that.\n *\n * Mirrors the vocabulary the SDK reports. Declared here rather than imported so this module stays free of\n * `@gitkraken/provider-apis` types (see the export block in `index.ts`); `collectionMetadata.ts` holds the\n * compile-time link, so an SDK bump that adds a kind fails the build at that boundary.\n */\nexport type ProviderWarningOmissionKind = 'provider-limit' | 'recovery-budget' | 'pagination-incomplete';\n\n/**\n * How a consumer can recover omitted results. `page-budget` reruns with a larger budget; `narrow-scope`\n * requests a smaller server-side scope; `none` promises neither. Switch on the exact value because only\n * `page-budget` is a fetch-more action.\n */\nexport type ProviderWarningOmissionRecovery = 'none' | 'page-budget' | 'narrow-scope';\n\n/**\n * The part of a provider a warning is confined to: one resource (organization, workspace, site), project or\n * repository. All fields optional; see {@link ProviderWarning.scope} for when one is forwarded.\n */\nexport interface ProviderWarningScope {\n\t/**\n\t * The resource as the read addressed it: its id on most reads, its name on the few that address it by name\n\t * (Azure DevOps' repo-scoped reads, Bitbucket's workspace reads). Match it against both\n\t * `ProviderOrganization.id` and `name`, and a project's `orgId` where it has one.\n\t */\n\tresourceId?: string;\n\t/**\n\t * The project as the read addressed it. For a Jira project read, the key on Jira Cloud (the project's `name` in\n\t * `listProjects`) and the numeric id on Jira Data Center.\n\t */\n\tprojectId?: string;\n\trepositoryId?: string;\n}\n\n/**\n * Why a provider request failed, when the failure itself says so:\n * - `unreachable`: a transport failure or server-side error prevented the provider from answering. The connection\n * still exists; this accompanies `kind: 'other'`, never an authentication warning.\n * - `oauth-app-not-allowed`: the organization does not let third-party OAuth apps in. On Azure DevOps this is the\n * organization policy \"Third-party application access via OAuth\", off by default for new organizations. An\n * organization admin enables it, or the user connects with a personal access token instead, which the policy\n * does not govern.\n * - `access-denied`: the account has no access to that organization or project (not a member, or lacking the\n * permission). Someone who administers it has to grant access.\n * - `conditional-access`: a Microsoft Entra Conditional Access policy blocked the request (Azure DevOps\n * `VS403463`). Its admin has to exempt the request, e.g. by location or device.\n *\n * None of these is healed by reconnecting, which is the point of naming them.\n */\nexport type ProviderWarningCauseReason =\n\t| 'oauth-app-not-allowed'\n\t| 'access-denied'\n\t| 'conditional-access'\n\t| 'unreachable';\n\n/** See {@link ProviderWarning.cause}. */\nexport interface ProviderWarningCause {\n\treason: ProviderWarningCauseReason;\n\t/** The provider's own error code, when it reports one (Azure DevOps `TF400813`, `VS403463`). */\n\tcode?: string;\n\t/** Where the setting behind the refusal is changed, when this layer can address it. */\n\tremedyUrl?: string;\n}\n\n/** Which repository / project / resource an omission is attributed to. All fields optional; a scope may name none. */\nexport interface ProviderWarningOmissionScope extends ProviderWarningScope {\n\tproviderId?: string;\n}\n\nexport interface ProviderWarningOmission {\n\tkind: ProviderWarningOmissionKind;\n\t/**\n\t * Whether anything would fetch the missing items, and what. Gate a \"load more\" affordance on this rather\n\t * than on `kind` — see {@link ProviderWarningOmissionRecovery}.\n\t */\n\trecovery: ProviderWarningOmissionRecovery;\n\t/**\n\t * The cap the provider enforces, when it reports one. NOT a result count for every kind: on\n\t * `recovery-budget` this is a REQUEST budget and must not be shown to a user as a number of results.\n\t */\n\tlimit?: number;\n\t/** Total matches the provider reported, when it reports one. Only GitHub's search cap does today. */\n\ttotalCount?: number;\n\t/**\n\t * The order the reachable window was selected under, as `field:direction`, when the omission is a result\n\t * ceiling.\n\t *\n\t * Present so a consumer can word its own \"N matched, showing the first M\" without assuming WHICH M: the\n\t * provider serves the top M under this key, so the sentence is only true if it names it. Absent for every\n\t * omission that isn't a ceiling, where no window was selected by an order.\n\t *\n\t * A union of both vocabularies rather than the issue one alone: `PullRequestSorting` happens to be a subset\n\t * today, so narrowing this to {@link IssueSorting} would still compile while describing only half the reads\n\t * that set it — and would break the day the PR search gains a key the issue search has no name for.\n\t */\n\tsort?: IssueSorting | PullRequestSorting;\n\t/** Which repository / project / resource was affected, when one is attributed. */\n\tscope?: ProviderWarningOmissionScope;\n}\n\nexport interface ProviderWarning {\n\tproviderId: IntegrationIds;\n\t/** Disambiguates connections on self-managed hosts (where one provider id spans multiple domains). */\n\tdomain?: string;\n\t/** The specific token/connection the read was pinned to, when a `connectionId` was supplied. */\n\tconnectionId?: string;\n\tmessage: string;\n\tkind: ProviderWarningKind;\n\t/** Convenience mirror of `kind === 'auth'`, scoped or not; read {@link scope} before prompting to reconnect. */\n\tisAuth: boolean;\n\t/**\n\t * Present when the failure was recorded against one sub-scope of the read (an organization, project or\n\t * repository) rather than against the connection. A scoped `auth` failure is that one scope refusing this\n\t * credential, e.g. an Azure DevOps organization that disallows third-party OAuth apps, one in another Entra\n\t * tenant, or a Conditional Access policy. Reconnecting cannot fix those, so this warning alone is no reason\n\t * to prompt for it.\n\t *\n\t * `kind` and `isAuth` are unaffected: a scoped 401 is still an authentication failure, and this field says\n\t * how far it reaches. It names at least one of its IDs, and is never set on an omission (which carries its\n\t * own {@link ProviderWarningOmission.scope}) or on a warning derived from a caught exception (see\n\t * {@link toProviderWarning}), even when that call targeted a single organization. The exception is a batch\n\t * read's refused target, which names its scope: the Azure DevOps organization and project, the repository on\n\t * other git hosts, or the tracker's resource.\n\t *\n\t * Its ABSENCE means account-wide or unattributed, so existing handling stays correct without it.\n\t *\n\t * A scoped `auth` failure also means the credential itself was accepted. A dead token can come back as nothing\n\t * but scoped refusals wherever a read reaches its scopes without an uncached request to the connection first:\n\t * discovery served from a per-token cache (Azure DevOps, Bitbucket, Jira Cloud) or an SDK fan-out across the\n\t * requested repositories (Bitbucket Data Center). So when a read's only auth failures are scoped, those providers\n\t * confirm the credential with one uncached check first, and a refused credential fails the whole read with an\n\t * unscoped `auth` warning. A batch read checks it only when every target was refused, since a target that\n\t * answered already proved it. A refusal the provider attributes to the credential itself, like Bitbucket's or Jira\n\t * Cloud's for a token missing the OAuth scopes a read needs, which a reconnect fixes, is published unscoped too,\n\t * once for the connection, while the scopes that answered keep their results.\n\t *\n\t * So the promise holds for a read that carries no unscoped `auth` warning of its own; one that does already asks\n\t * for a reconnect, and its other scoped refusals are not checked. Two exceptions stay scoped even then. A check\n\t * that could not complete or was denied (a network error, a throttle, a `403`, Bitbucket Data Center refusing a\n\t * credential it authenticated, or a Bitbucket Data Center project access token, whose user the check cannot\n\t * find) proves nothing, so the warnings are published unconfirmed, with no {@link cause}, and the next read\n\t * checks again. And a credential confirmed within the last minute is not probed again, so a revocation can take\n\t * up to a minute to surface.\n\t */\n\tscope?: ProviderWarningScope;\n\t/**\n\t * Why the provider request failed, when the failure itself says so: see {@link ProviderWarningCauseReason}. Switch on\n\t * `reason` to recommend a fix instead of a reconnect; `message` then carries the same explanation as prose.\n\t *\n\t * `unreachable` accompanies a transport or server failure with `kind: 'other'`. Credential refusal causes are\n\t * set only on a scoped `auth` warning whose credential was confirmed first (see {@link scope}), because the\n\t * refusals they name are indistinguishable from a dead credential until then: Azure DevOps answers a\n\t * third-party OAuth app its organization disallows exactly as it answers an expired token. Only Azure DevOps\n\t * reports one today. Its absence proves nothing about the cause.\n\t */\n\tcause?: ProviderWarningCause;\n\t/**\n\t * Present when this warning describes results the read could not return even though the request itself\n\t * SUCCEEDED — a provider-enforced cap, an exhausted recovery budget, a page budget, or a sub-scope the read\n\t * did not drain.\n\t *\n\t * Its presence is the signal: an omission is not a failure. Message it as incompleteness rather than as a\n\t * failed read, and do NOT derive that from `message`, which is English prose and subject to rewording.\n\t * `kind` stays `'other'` for these, so the failure discriminant keeps meaning exactly what it meant before\n\t * this field existed.\n\t *\n\t * Whether anything would fetch the rest is a SEPARATE question — read {@link ProviderWarningOmission.recovery},\n\t * not `kind`, and never assume a retry of the same request returns more.\n\t *\n\t * `limit` / `totalCount` / `scope` are forwarded only when they are reported; most omissions carry none of\n\t * the three, so render correctly without them — and see {@link ProviderWarningOmission.limit} before\n\t * showing that figure as a result count.\n\t *\n\t * Its ABSENCE proves nothing. It is never set on a warning derived from a caught exception (see\n\t * {@link toProviderWarning}) or from a structured scope failure — those are failures, and the field would\n\t * be a lie there. But it is also absent whenever incompleteness was reported without naming what was left\n\t * out, so treat a bare `kind: 'other'` warning as unclassified rather than as a proven failure.\n\t */\n\tomission?: ProviderWarningOmission;\n\t/**\n\t * The keys of the batch targets this warning concerns: the target a per-target failure was refused or failed\n\t * for, or every target of an integration call that failed as a whole. So a caller that sends several callers'\n\t * targets in one call can hand a failure only to the targets it belongs to.\n\t *\n\t * Its ABSENCE means the whole call. It is absent on every read that is not a batch read, and on a batch read's\n\t * refusal of the whole call before any target is read (a duplicate key, an invalid target, an unknown etag\n\t * include, no resolvable connection, the wrong surface), which concerns every target alike.\n\t *\n\t * It is not part of a warning's identity: two warnings that differ only here are one warning, for the union of\n\t * their targets (see {@link appendDedupedWarning}).\n\t */\n\ttargetKeys?: string[];\n}\n\nexport interface ProviderPageInfo {\n\t/**\n\t * The 1-based POSITION of the returned page. One convention across every paged read, page-number and\n\t * cursor paging alike, so a `getNextPageParam`-style consumer can key off it uniformly:\n\t * - the page number the provider itself reported, when it reports one (authoritative);\n\t * - else the position the caller addressed: the page encoded in a threaded page cursor, or the `page`\n\t * supplied alongside an opaque cursor (1 when the caller supplied no `page`);\n\t * - else, for a read that can be advanced by page number, the requested `page` — the provider either\n\t * honored it or the internal drain reached it (see the paging contract on `IntegrationManager`);\n\t * - else 1: a cursor-only read with no cursor threaded back ignores a requested page number and answers\n\t * with its first page, so echoing the request would mislabel page 1 as page N.\n\t *\n\t * A page past the provider's terminal cursor still reports the requested `page` with empty `items` — an\n\t * empty page N, never page N-1 relabeled.\n\t *\n\t * Continue a cursor-paged read from `cursor`, NOT from `currentPage + 1`: a cursor-only host can't be\n\t * addressed by number, so this is a position label and not always a resumable request parameter.\n\t */\n\tcurrentPage: number;\n\t/**\n\t * The page size IN EFFECT, which is not always the one requested — it is whichever of these the read can\n\t * actually vouch for:\n\t * - the size the provider reported for the page it served (numbered-page hosts), else\n\t * - the `itemsPerPage` that was applied to the request (repo-scoped reads, which forward it), else\n\t * - `items.length`, for a read that takes no page size at all: the account-wide PR/issue reads, `listRepos`\n\t * (cursor-only), and `listIssueTrackerIssuesPage`, whose `itemsPerPage` counts PROJECTS rather than issues.\n\t *\n\t * So it is a description of the page, not an echo of the request: don't derive a total from\n\t * `currentPage * itemsPerPage`, and don't read `items.length < itemsPerPage` as \"last page\" — `hasMore` /\n\t * `cursor` are the only continuation signals.\n\t */\n\titemsPerPage: number;\n\t/**\n\t * A COMPLETENESS assertion, not a mode flag: `true` only when a sweep drained every page of every target\n\t * without truncating or failing (`!truncated && !fetchFailed`). Set exclusively by `sweepPullRequests` /\n\t * `sweepClosedPullRequests` and absent on every paged read, so `allPages !== true` does NOT by itself mean\n\t * \"this was a single page\".\n\t *\n\t * This is the signal to gate on before caching a sweep as authoritative: unlike `truncated` it is `false`\n\t * for BOTH kinds of incompleteness, so it can't be misread as a benign cap.\n\t */\n\tallPages?: boolean;\n\t/**\n\t * True when the read stopped before it could confirm it had everything. Deliberately does NOT distinguish\n\t * WHY, and a consumer must not assume it means a provider ceiling — a sweep sets it from three different\n\t * origins:\n\t * - a provider-side ceiling or per-scope backstop propagated from below (e.g. GitHub Search's 1000-result\n\t * cap): the missing items are NOT reachable, so this really is \"complete to the provider's limit\";\n\t * - the sweep's OWN `maxPages` cap (default 100) with a usable cursor still in hand: the missing items ARE\n\t * reachable and were simply not fetched — raise `maxPages` to get them;\n\t * - the provider reporting another page but handing back no usable cursor: not reachable.\n\t *\n\t * Only the second is recoverable, and a sweep exposes no continuation cursor, so treating a truncated sweep\n\t * as a complete set silently drops reachable data in that case. Prefer `allPages` for the cache decision.\n\t */\n\ttruncated?: boolean;\n}\n\nexport interface ProviderResult<T> {\n\titems: T[];\n\twarnings: ProviderWarning[];\n\t/**\n\t * True when `items` is incomplete — distinguishes a failed or truncated flat read from a genuinely empty\n\t * result. Paged results additionally use `page.truncated` when a completed page could not confirm it\n\t * drained everything.\n\t */\n\tfetchFailed?: boolean;\n}\n\nexport interface ProviderPagedResult<T> extends ProviderResult<T> {\n\tpage: ProviderPageInfo;\n\thasMore: boolean;\n\t/**\n\t * Opaque cursor for cursor-based paging (GraphQL, per-repo/per-project cursors, etc.). For hosts that\n\t * require the previous page's cursor, consumers should pass this value back to the next `cursor` option.\n\t * A retry-only cursor may remain when `hasMore` is false: `hasMore` means automatic forward progress,\n\t * while explicitly reusing such a cursor retries failed work without creating an infinite paging loop.\n\t */\n\tcursor?: string;\n}\n\nexport interface ProviderSweepResult<T> extends ProviderResult<T> {\n\tpage: ProviderPageInfo;\n\thasMore: boolean;\n\t/**\n\t * Providers whose top-level read failed before returning any usable page. Partial per-scope failures stay\n\t * represented by `warnings` + `fetchFailed` and are intentionally excluded.\n\t */\n\tfailedProviderIds: IntegrationIds[];\n\t/**\n\t * Providers whose read started successfully but could not produce an authoritative complete slice (for\n\t * example, a later page failed, collection metadata reported a partial scope, or a paging backstop was hit).\n\t * Kept separate from\n\t * `failedProviderIds` so consumers can accept healthy sibling providers while preserving an older snapshot\n\t * for a provider whose returned slice has a gap.\n\t */\n\tincompleteProviderIds: IntegrationIds[];\n}\n\nexport interface ProviderBroadenResult<T> extends ProviderPagedResult<T> {\n\tbroadenedProviderIds: IntegrationIds[];\n\t/** Providers for which no requested org produced a usable issue result. */\n\tfailedProviderIds: IntegrationIds[];\n\t/** Providers with both a usable org result and at least one failed or truncated org slice. */\n\tincompleteProviderIds: IntegrationIds[];\n\tfanOutCount: number;\n}\n\nexport type RepositoryResolutionStatus =\n\t| 'resolved'\n\t| 'not-found'\n\t| 'unauthorized'\n\t| 'unsupported-provider'\n\t| 'invalid-remote-url'\n\t| 'host-mismatch'\n\t| 'undetermined';\n\nexport interface RepositoryIdentity {\n\tproviderId: IntegrationIds;\n\tdomain: string;\n\t/** The provider's canonical owner/namespace (follows renames), falling back to the parsed remote when omitted. */\n\towner: string;\n\t/** The provider's canonical repo name (follows renames), falling back to the parsed remote when omitted. */\n\tname: string;\n\tproject?: string;\n\t/** The original input remote URL, so the caller can key the resolution back to the remote it asked about. */\n\tremoteUrl: string;\n\t/**\n\t * True when the provider's canonical owner/name differ from what the input remote URL carried — i.e. the\n\t * repo was renamed/moved host-side and the local remote is stale. Case-insensitive, mirroring gkcli's\n\t * `EqualFold` compare so hosts that echo the input casing (e.g. Bitbucket Server/Azure) aren't flagged.\n\t */\n\trenamed: boolean;\n}\n\nexport interface RepositoryResolution {\n\tstatus: RepositoryResolutionStatus;\n\tidentity?: RepositoryIdentity;\n\twarning?: ProviderWarning;\n}\n\nexport interface ResolveRepositoryResult {\n\tresolution: RepositoryResolution;\n}\n\nconst maxProviderWarningMessageLength = 500;\n\n/** A caught error's message as a warning can carry it: an HTML page becomes its status, and prose is capped. */\nexport function providerWarningMessage(ex: unknown): string {\n\tconst raw = (ex instanceof Error ? ex.message : String(ex)).trim();\n\tconst carrier = ex as { status?: unknown; response?: { status?: unknown } } | null | undefined;\n\tconst status =\n\t\ttypeof carrier?.status === 'number'\n\t\t\t? carrier.status\n\t\t\t: typeof carrier?.response?.status === 'number'\n\t\t\t\t? carrier.response.status\n\t\t\t\t: undefined;\n\n\t// Gateways commonly answer API requests with a complete HTML error page. That payload is neither actionable\n\t// nor suitable for a warning DTO consumed by UIs and logs; preserve the status without leaking the document.\n\tif (/<(?:!doctype\\s+html|html|head|body)\\b/i.test(raw)) {\n\t\treturn status != null ? `Provider request failed with status ${status}.` : 'Provider request failed.';\n\t}\n\n\tif (!raw) {\n\t\treturn status != null ? `Provider request failed with status ${status}.` : 'Provider request failed.';\n\t}\n\treturn raw.length > maxProviderWarningMessageLength\n\t\t? `${raw.slice(0, maxProviderWarningMessageLength - 3)}...`\n\t\t: raw;\n}\n\n/**\n * Normalized org/workspace/group shape returned by the provider facade.\n * `name` is the provider identifier to pass to follow-up reads (a Jira project's key), while `org` is an optional\n * display label of the parent. Where that label can't address the parent, `orgId` is the parent's stable id to pass\n * as `org` instead (a Jira site's id, or a Data Center instance's domain).\n *\n * Jira site rows (`listOrgs`) are the exception: their `name` is the site's label, and a Jira read selects a site only\n * by its `id` (or a project row's `orgId`).\n */\nexport interface ProviderOrganization {\n\tid: string;\n\tproviderId: IntegrationIds;\n\tname: string;\n\torg?: string;\n\torgId?: string;\n\turl: string;\n}\n\n/** Normalized repository shape returned by the provider facade. */\nexport interface ProviderRepositoryShape {\n\tid: string;\n\tnamespace: string;\n\tname: string;\n\t/** Azure DevOps project; `undefined` for hosts without a project layer. */\n\tproject?: string;\n\t/** Web (browser) URL, when the provider exposes it. */\n\turl?: string;\n\t/** HTTPS clone URL, when available. */\n\tcloneUrlHttps?: string;\n\t/** SSH clone URL, when available. */\n\tcloneUrlSsh?: string;\n\t/** Default branch name, when the provider reports it. */\n\tdefaultBranch?: string;\n}\n\n/**\n * Classifies a caught provider exception into a neutral {@link ProviderWarning}. Ordering matters:\n * `instanceof` is checked most-specific-first so a rate-limit isn't mislabeled as a generic error and\n * a 404 isn't mislabeled as auth.\n */\nexport function toProviderWarning(\n\tproviderId: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tex: unknown,\n): ProviderWarning {\n\tlet kind: ProviderWarning['kind'];\n\tif (ex instanceof AuthenticationError) {\n\t\tkind = 'auth';\n\t} else if (ex instanceof RequestRateLimitError) {\n\t\tkind = 'rate-limit';\n\t} else if (ex instanceof RequestNotFoundError) {\n\t\tkind = 'not-found';\n\t} else if (isUnsupportedSortError(ex)) {\n\t\tkind = 'unsupported';\n\t} else {\n\t\tkind = 'other';\n\t}\n\n\treturn {\n\t\tproviderId: providerId,\n\t\tdomain: domain,\n\t\tconnectionId: connectionId,\n\t\tmessage: providerWarningMessage(ex),\n\t\tkind: kind,\n\t\tisAuth: kind === 'auth',\n\t\t...(kind === 'other' && isProviderUnreachableError(ex) ? { cause: { reason: 'unreachable' as const } } : {}),\n\t};\n}\n\n/**\n * A scope's identity, as the stable string that keys it.\n *\n * The single definition of what \"the same scope\" means, shared by the failure and omission dedup keys in\n * `providerPaging.ts` and by {@link providerWarningKey} below, so a scope gaining a field is one edit rather\n * than three. `providerId` is included: the same repository ID under two providers is two scopes.\n *\n * The parameter is structural rather than the SDK's `CollectionScope` so this module keeps naming no\n * `@gitkraken/provider-apis` types (see the export block in `index.ts`) while still serving its SDK-facing\n * callers, which pass that type in unchanged.\n */\nexport function collectionScopeKey(scope: ProviderWarningOmissionScope | undefined): string {\n\treturn [scope?.providerId ?? '', scope?.resourceId ?? '', scope?.projectId ?? '', scope?.repositoryId ?? ''].join(\n\t\t' ',\n\t);\n}\n\n/**\n * The omission's contribution to a warning's identity — empty when there is none, so a warning without an\n * omission keeps deduping exactly as it did before the field existed.\n *\n * Two omissions of different kinds do produce different `message` values today, so message alone would still\n * separate them. That is incidental: the premise of `omission` is that consumers must not depend on prose\n * carrying the distinguishing fact, and this key must not either.\n */\nfunction providerWarningOmissionKey(omission: ProviderWarningOmission | undefined): string {\n\tif (omission == null) return '';\n\n\treturn [\n\t\tomission.kind,\n\t\tomission.recovery,\n\t\tomission.limit ?? '',\n\t\tomission.totalCount ?? '',\n\t\tcollectionScopeKey(omission.scope),\n\t].join(' ');\n}\n\n/**\n * Strips the omission from every warning in `warnings` when the read as a whole failed.\n *\n * An omission asserts the request SUCCEEDED, and a drain only learns it failed AFTER it may have emitted one:\n * an early page can report its own truncation and then a later page can die. Deciding per warning, at the\n * moment each is built, cannot see that future — so the aggregate is reconciled once, here, where\n * `fetchFailed` is final. Call it at the point a read returns its `fetchFailed`.\n *\n * Re-dedupes as it goes: the omission is part of a warning's identity, so two warnings that differed only\n * there become identical once it is gone, and the array's contract is that no two entries are equal.\n *\n * Mutates in place: the warning array is the one being returned, and callers accumulate into it across pages.\n */\nexport function reconcileOmissionsWithFailure(warnings: ProviderWarning[], fetchFailed: boolean): void {\n\tif (!fetchFailed || !warnings.some(w => w.omission != null)) return;\n\n\tconst reconciled: ProviderWarning[] = [];\n\tfor (const warning of warnings) {\n\t\tif (warning.omission == null) {\n\t\t\tappendDedupedWarning(reconciled, warning);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst { omission: _omission, ...rest } = warning;\n\t\tappendDedupedWarning(reconciled, rest);\n\t}\n\n\twarnings.splice(0, warnings.length, ...reconciled);\n}\n\n/**\n * A stable key for deduplicating warnings accumulated across drained pages / fan-out scopes.\n *\n * The scope and the cause are keyed for the reason the omission is (see {@link providerWarningOmissionKey}): today's\n * failure messages happen to spell them out, but two failures that differ in either must stay two warnings even if\n * they don't.\n *\n * `message` stays LAST. It is the only free-form segment — provider prose, spaces and all — so anything\n * appended after it could be impersonated by a message that happens to end in the same text.\n */\nfunction providerWarningKey(warning: ProviderWarning): string {\n\treturn [\n\t\twarning.providerId,\n\t\twarning.connectionId ?? '',\n\t\twarning.domain ?? '',\n\t\twarning.kind,\n\t\tcollectionScopeKey(warning.scope),\n\t\twarning.cause?.reason ?? '',\n\t\tproviderWarningOmissionKey(warning.omission),\n\t\twarning.message,\n\t].join(' ');\n}\n\n/**\n * Appends `warning` to `into` only when an equal warning (by provider/connection/domain/kind/message, plus the\n * structured scope, cause and omission when present) is absent. An equal warning already there merges its\n * {@link ProviderWarning.targetKeys} with the new one's instead: the union of both, or none when either concerns\n * the whole call. The existing warning is replaced, never mutated, since it may be shared.\n */\nexport function appendDedupedWarning(into: ProviderWarning[], warning: ProviderWarning): void {\n\tconst key = providerWarningKey(warning);\n\tconst index = into.findIndex(existing => providerWarningKey(existing) === key);\n\tif (index === -1) {\n\t\tinto.push(warning);\n\t\treturn;\n\t}\n\n\tconst existing = into[index];\n\tconst existingKeys = existing.targetKeys;\n\t// Already covers the whole call.\n\tif (existingKeys == null) return;\n\n\tif (warning.targetKeys == null) {\n\t\tconst { targetKeys: _, ...wholeCall } = existing;\n\t\tinto[index] = wholeCall;\n\t\treturn;\n\t}\n\n\tconst added = warning.targetKeys.filter(k => !existingKeys.includes(k));\n\tif (!added.length) return;\n\n\tinto[index] = { ...existing, targetKeys: [...existingKeys, ...new Set(added)] };\n}\n"],"mappings":";;;;AA0XA,MAAM,kCAAkC;;AAGxC,SAAgB,uBAAuB,IAAqB;CAC3D,MAAM,OAAO,cAAc,QAAQ,GAAG,UAAU,OAAO,EAAE,EAAA,CAAG,KAAK;CACjE,MAAM,UAAU;CAChB,MAAM,SACL,OAAO,SAAS,WAAW,WACxB,QAAQ,SACR,OAAO,SAAS,UAAU,WAAW,WACpC,QAAQ,SAAS,SACjB,KAAA;CAIL,IAAI,yCAAyC,KAAK,GAAG,GACpD,OAAO,UAAU,OAAO,uCAAuC,OAAO,KAAK;CAG5E,IAAI,CAAC,KACJ,OAAO,UAAU,OAAO,uCAAuC,OAAO,KAAK;CAE5E,OAAO,IAAI,SAAS,kCACjB,GAAG,IAAI,MAAM,GAAG,GAAmC,EAAE,OACrD;AACJ;;;;;;AA0CA,SAAgB,kBACf,YACA,QACA,cACA,IACkB;CAClB,IAAI;CACJ,IAAI,cAAc,qBACjB,OAAO;MACD,IAAI,cAAc,uBACxB,OAAO;MACD,IAAI,cAAc,sBACxB,OAAO;MACD,IAAI,uBAAuB,EAAE,GACnC,OAAO;MAEP,OAAO;CAGR,OAAO;EACM;EACJ;EACM;EACd,SAAS,uBAAuB,EAAE;EAC5B;EACN,QAAQ,SAAS;EACjB,GAAI,SAAS,WAAW,2BAA2B,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,cAAuB,EAAE,IAAI,CAAC;CAC3G;AACD;;;;;;;;;;;;AAaA,SAAgB,mBAAmB,OAAyD;CAC3F,OAAO;EAAC,OAAO,cAAc;EAAI,OAAO,cAAc;EAAI,OAAO,aAAa;EAAI,OAAO,gBAAgB;CAAE,CAAC,CAAC,KAC5G,GACD;AACD;;;;;;;;;AAUA,SAAS,2BAA2B,UAAuD;CAC1F,IAAI,YAAY,MAAM,OAAO;CAE7B,OAAO;EACN,SAAS;EACT,SAAS;EACT,SAAS,SAAS;EAClB,SAAS,cAAc;EACvB,mBAAmB,SAAS,KAAK;CAClC,CAAC,CAAC,KAAK,GAAG;AACX;;;;;;;;;;;;;;AAeA,SAAgB,8BAA8B,UAA6B,aAA4B;CACtG,IAAI,CAAC,eAAe,CAAC,SAAS,MAAK,MAAK,EAAE,YAAY,IAAI,GAAG;CAE7D,MAAM,aAAgC,CAAC;CACvC,KAAK,MAAM,WAAW,UAAU;EAC/B,IAAI,QAAQ,YAAY,MAAM;GAC7B,qBAAqB,YAAY,OAAO;GACxC;EACD;EAEA,MAAM,EAAE,UAAU,WAAW,GAAG,SAAS;EACzC,qBAAqB,YAAY,IAAI;CACtC;CAEA,SAAS,OAAO,GAAG,SAAS,QAAQ,GAAG,UAAU;AAClD;;;;;;;;;;;AAYA,SAAS,mBAAmB,SAAkC;CAC7D,OAAO;EACN,QAAQ;EACR,QAAQ,gBAAgB;EACxB,QAAQ,UAAU;EAClB,QAAQ;EACR,mBAAmB,QAAQ,KAAK;EAChC,QAAQ,OAAO,UAAU;EACzB,2BAA2B,QAAQ,QAAQ;EAC3C,QAAQ;CACT,CAAC,CAAC,KAAK,GAAG;AACX;;;;;;;AAQA,SAAgB,qBAAqB,MAAyB,SAAgC;CAC7F,MAAM,MAAM,mBAAmB,OAAO;CACtC,MAAM,QAAQ,KAAK,WAAU,aAAY,mBAAmB,QAAQ,MAAM,GAAG;CAC7E,IAAI,UAAU,IAAI;EACjB,KAAK,KAAK,OAAO;EACjB;CACD;CAEA,MAAM,WAAW,KAAK;CACtB,MAAM,eAAe,SAAS;CAE9B,IAAI,gBAAgB,MAAM;CAE1B,IAAI,QAAQ,cAAc,MAAM;EAC/B,MAAM,EAAE,YAAY,GAAG,GAAG,cAAc;EACxC,KAAK,SAAS;EACd;CACD;CAEA,MAAM,QAAQ,QAAQ,WAAW,QAAO,MAAK,CAAC,aAAa,SAAS,CAAC,CAAC;CACtE,IAAI,CAAC,MAAM,QAAQ;CAEnB,KAAK,SAAS;EAAE,GAAG;EAAU,YAAY,CAAC,GAAG,cAAc,GAAG,IAAI,IAAI,KAAK,CAAC;CAAE;AAC/E"}
@@ -1,5 +1,5 @@
1
1
  import { RemoteProvider, RemoteProviderId } from "../../../git/models/remoteProvider.js";
2
- import { CloudGitSelfManagedHostIntegrationIds, CloudSelfManagedHostIntegrationIds, GitCloudHostIntegrationId, GitSelfManagedHostIntegrationId, IntegrationIds, IssuesHostIntegrationIds, IssuesSelfManagedHostIntegrationId, SelfManagedHostIntegrationIds } from "../constants.js";
2
+ import { CloudGitSelfManagedHostIntegrationIds, CloudSelfManagedHostIntegrationIds, GitCloudHostIntegrationId, GitSelfManagedHostIntegrationId, IntegrationIds, IssuesCloudHostIntegrationId, IssuesHostIntegrationIds, IssuesSelfManagedHostIntegrationId, SelfManagedHostIntegrationIds } from "../constants.js";
3
3
  import { GitHostIntegration } from "../models/gitHostIntegration.js";
4
4
  import { Integration, IntegrationConnectedKey } from "../models/integration.js";
5
5
  //#region ../plus/integrations/src/utils/integration.utils.d.ts
@@ -41,6 +41,11 @@ export declare function isIssuesSelfManagedHostIntegrationId(id: IntegrationIds)
41
41
  * domain, collapsing every host of it into one bucket.
42
42
  */
43
43
  export declare function isSelfManagedHostIntegrationId(id: IntegrationIds): id is SelfManagedHostIntegrationIds;
44
+ /**
45
+ * Whether this id is Jira, Cloud or Data Center: the two share their project and site identity rules and the SDK's
46
+ * status mapper. Takes any string, as a provider reference's `id` is one.
47
+ */
48
+ export declare function isJiraIntegrationId(id: string): id is IssuesCloudHostIntegrationId.Jira | IssuesSelfManagedHostIntegrationId.JiraServer;
44
49
  /**
45
50
  * Whether this id belongs to a dedicated issue tracker (resource → project) rather than a git host.
46
51
  *
@@ -117,6 +117,13 @@ function isSelfManagedHostIntegrationId(id) {
117
117
  return isGitSelfManagedHostIntegrationId(id) || isIssuesSelfManagedHostIntegrationId(id);
118
118
  }
119
119
  /**
120
+ * Whether this id is Jira, Cloud or Data Center: the two share their project and site identity rules and the SDK's
121
+ * status mapper. Takes any string, as a provider reference's `id` is one.
122
+ */
123
+ function isJiraIntegrationId(id) {
124
+ return id === "jira" || id === "jira-server";
125
+ }
126
+ /**
120
127
  * Whether this id belongs to a dedicated issue tracker (resource → project) rather than a git host.
121
128
  *
122
129
  * Decided from the id alone, so a read can refuse a mismatched surface — a repo/PR read asked of Jira, or an
@@ -181,6 +188,6 @@ function getSelfManagedBaseUrl(id, domain, protocol) {
181
188
  }
182
189
  }
183
190
  //#endregion
184
- export { convertRemoteProviderIdToIntegrationId, getIntegrationConnectedKey, getIntegrationIdForRemote, getSelfManagedBaseUrl, isCloudGitSelfManagedHostIntegrationId, isCloudSelfManagedHostIntegrationId, isGitCloudHostIntegrationId, isGitHostIntegration, isGitSelfManagedHostIntegrationId, isIssuesHostIntegrationId, isIssuesSelfManagedHostIntegrationId, isNonExpiringZeroTokenIntegrationId, isSelfManagedHostIntegrationId, remoteProviderTypeForConfig, remoteProviderTypeForIntegration, supportedIntegrationIds, warnOnMissingSessionForDomain };
191
+ export { convertRemoteProviderIdToIntegrationId, getIntegrationConnectedKey, getIntegrationIdForRemote, getSelfManagedBaseUrl, isCloudGitSelfManagedHostIntegrationId, isCloudSelfManagedHostIntegrationId, isGitCloudHostIntegrationId, isGitHostIntegration, isGitSelfManagedHostIntegrationId, isIssuesHostIntegrationId, isIssuesSelfManagedHostIntegrationId, isJiraIntegrationId, isNonExpiringZeroTokenIntegrationId, isSelfManagedHostIntegrationId, remoteProviderTypeForConfig, remoteProviderTypeForIntegration, supportedIntegrationIds, warnOnMissingSessionForDomain };
185
192
 
186
193
  //# sourceMappingURL=integration.utils.js.map