@gitkraken/core-gitlens 0.5.100 → 0.5.102

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 (87) hide show
  1. package/CHANGELOG.md +36 -1
  2. package/dist/git/models/issue.d.ts +4 -3
  3. package/dist/git/models/issue.d.ts.map +1 -1
  4. package/dist/git/models/issue.js.map +1 -1
  5. package/dist/git/utils/issue.utils.d.ts.map +1 -1
  6. package/dist/git/utils/issue.utils.js +8 -6
  7. package/dist/git/utils/issue.utils.js.map +1 -1
  8. package/dist/git/utils/remote.utils.d.ts +3 -0
  9. package/dist/git/utils/remote.utils.d.ts.map +1 -1
  10. package/dist/git/utils/remote.utils.js +5 -0
  11. package/dist/git/utils/remote.utils.js.map +1 -1
  12. package/dist/plus/git-github/api/github.d.ts.map +1 -1
  13. package/dist/plus/git-github/api/github.js +18 -5
  14. package/dist/plus/git-github/api/github.js.map +1 -1
  15. package/dist/plus/git-github/models.d.ts +6 -4
  16. package/dist/plus/git-github/models.d.ts.map +1 -1
  17. package/dist/plus/git-github/models.js +19 -26
  18. package/dist/plus/git-github/models.js.map +1 -1
  19. package/dist/plus/integrations/collectionMetadata.d.ts +12 -0
  20. package/dist/plus/integrations/collectionMetadata.d.ts.map +1 -1
  21. package/dist/plus/integrations/collectionMetadata.js +111 -6
  22. package/dist/plus/integrations/collectionMetadata.js.map +1 -1
  23. package/dist/plus/integrations/index.d.ts +1 -1
  24. package/dist/plus/integrations/index.d.ts.map +1 -1
  25. package/dist/plus/integrations/index.js.map +1 -1
  26. package/dist/plus/integrations/integrationService.d.ts +3 -3
  27. package/dist/plus/integrations/integrationService.d.ts.map +1 -1
  28. package/dist/plus/integrations/integrationService.js +19 -3
  29. package/dist/plus/integrations/integrationService.js.map +1 -1
  30. package/dist/plus/integrations/models/integration.d.ts.map +1 -1
  31. package/dist/plus/integrations/models/integration.js +3 -2
  32. package/dist/plus/integrations/models/integration.js.map +1 -1
  33. package/dist/plus/integrations/providers/models.d.ts +1 -0
  34. package/dist/plus/integrations/providers/models.d.ts.map +1 -1
  35. package/dist/plus/integrations/providers/models.js.map +1 -1
  36. package/dist/plus/integrations/providers/providersApi.d.ts.map +1 -1
  37. package/dist/plus/integrations/providers/providersApi.js +4 -2
  38. package/dist/plus/integrations/providers/providersApi.js.map +1 -1
  39. package/dist/plus/integrations/providers/trello.d.ts +2 -0
  40. package/dist/plus/integrations/providers/trello.d.ts.map +1 -1
  41. package/dist/plus/integrations/providers/trello.js +1 -1
  42. package/dist/plus/integrations/providers/trello.js.map +1 -1
  43. package/dist/plus/integrations/providers/utils/providerPaging.d.ts +4 -2
  44. package/dist/plus/integrations/providers/utils/providerPaging.d.ts.map +1 -1
  45. package/dist/plus/integrations/providers/utils/providerPaging.js +34 -21
  46. package/dist/plus/integrations/providers/utils/providerPaging.js.map +1 -1
  47. package/dist/plus/integrations/reads/drains.d.ts.map +1 -1
  48. package/dist/plus/integrations/reads/drains.js +59 -20
  49. package/dist/plus/integrations/reads/drains.js.map +1 -1
  50. package/dist/plus/integrations/reads/issueTracker.d.ts.map +1 -1
  51. package/dist/plus/integrations/reads/issueTracker.js +8 -2
  52. package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
  53. package/dist/plus/integrations/reads/issues.d.ts.map +1 -1
  54. package/dist/plus/integrations/reads/issues.js +13 -3
  55. package/dist/plus/integrations/reads/issues.js.map +1 -1
  56. package/dist/plus/integrations/reads/pullRequests.d.ts.map +1 -1
  57. package/dist/plus/integrations/reads/pullRequests.js +7 -1
  58. package/dist/plus/integrations/reads/pullRequests.js.map +1 -1
  59. package/dist/plus/integrations/reads/warnings.d.ts +41 -6
  60. package/dist/plus/integrations/reads/warnings.d.ts.map +1 -1
  61. package/dist/plus/integrations/reads/warnings.js +54 -7
  62. package/dist/plus/integrations/reads/warnings.js.map +1 -1
  63. package/dist/plus/integrations/results.d.ts +115 -1
  64. package/dist/plus/integrations/results.d.ts.map +1 -1
  65. package/dist/plus/integrations/results.js +78 -3
  66. package/dist/plus/integrations/results.js.map +1 -1
  67. package/docs/integrations.md +81 -7
  68. package/package.json +2 -2
  69. package/src/git/models/issue.ts +3 -2
  70. package/src/git/utils/issue.utils.ts +9 -6
  71. package/src/git/utils/remote.utils.ts +6 -0
  72. package/src/plus/git-github/api/github.ts +19 -9
  73. package/src/plus/git-github/models.ts +27 -31
  74. package/src/plus/integrations/collectionMetadata.ts +125 -8
  75. package/src/plus/integrations/index.ts +4 -0
  76. package/src/plus/integrations/integrationService.ts +37 -9
  77. package/src/plus/integrations/models/integration.ts +3 -2
  78. package/src/plus/integrations/providers/models.ts +4 -1
  79. package/src/plus/integrations/providers/providersApi.ts +6 -10
  80. package/src/plus/integrations/providers/trello.ts +3 -2
  81. package/src/plus/integrations/providers/utils/providerPaging.ts +46 -23
  82. package/src/plus/integrations/reads/drains.ts +84 -20
  83. package/src/plus/integrations/reads/issueTracker.ts +8 -2
  84. package/src/plus/integrations/reads/issues.ts +21 -4
  85. package/src/plus/integrations/reads/pullRequests.ts +15 -1
  86. package/src/plus/integrations/reads/warnings.ts +82 -12
  87. package/src/plus/integrations/results.ts +173 -4
@@ -11,6 +11,68 @@ export interface ConnectionStateChangeEvent {
11
11
  * needs re-connection); `isAuth` is retained as a convenience mirror of `kind === 'auth'`.
12
12
  */
13
13
  export type ProviderWarningKind = 'auth' | 'rate-limit' | 'not-found' | 'no-connection' | 'other';
14
+ /**
15
+ * Why a read that SUCCEEDED still withheld results:
16
+ * - `provider-limit`: the provider refuses to serve past a cap it enforces (GitHub search's 1,000-result
17
+ * ceiling, Trello's `cards_limit`), so the excess is unreachable through that query.
18
+ * - `recovery-budget`: the internal partitioned recovery stopped spending upstream requests before it ran
19
+ * out of partitions to visit.
20
+ * - `pagination-incomplete`: pages were left unread — a sub-scope of a multi-scope read that was not drained,
21
+ * a drain that stopped at its own page backstop, or a provider that advertised another page and gave no way
22
+ * to reach it. These have DIFFERENT remedies, so the kind alone does not say whether more can be fetched;
23
+ * read {@link ProviderWarningOmission.recovery} for that.
24
+ *
25
+ * Mirrors the vocabulary the SDK reports. Declared here rather than imported so this module stays free of
26
+ * `@gitkraken/provider-apis` types (see the export block in `index.ts`); `collectionMetadata.ts` holds the
27
+ * compile-time link, so an SDK bump that adds a kind fails the build at that boundary.
28
+ */
29
+ export type ProviderWarningOmissionKind = 'provider-limit' | 'recovery-budget' | 'pagination-incomplete';
30
+ /**
31
+ * What, if anything, would fetch the withheld results — the question `kind` cannot answer.
32
+ *
33
+ * `kind` says WHY results are missing, and two omissions of the same kind can need opposite handling: a drain
34
+ * that stopped at a caller-settable page budget and a provider that advertised another page without a usable
35
+ * cursor are both `pagination-incomplete`, but only the first can be fetched. A consumer offering a "load
36
+ * more" affordance gates it on this, never on `kind`.
37
+ *
38
+ * - `none`: nothing the consumer can call returns the missing items — a provider-enforced cap, an internal
39
+ * budget it does not control, or a continuation the provider refused to hand back. Say the results are
40
+ * capped; do not offer to fetch more.
41
+ * - `page-budget`: re-run the SAME read with a higher page budget (`maxPages` on the sweep options). Note this
42
+ * re-reads from the start rather than continuing — a sweep exposes no cursor — so it is a deliberate,
43
+ * user-initiated action, not something to retry automatically.
44
+ *
45
+ * Required, not optional: an absent value would be indistinguishable from `none` while actually meaning "this
46
+ * producer didn't say", which is the ambiguity {@link ProviderWarning.omission} exists to remove. And a
47
+ * conservative union on purpose — it names only what a producer can PROVE. A value is added when some layer
48
+ * can vouch for it, never so that a plausible-looking case has something to map to; see
49
+ * `collectionMetadata.ts` for the SDK shape that looks recoverable and is not.
50
+ */
51
+ export type ProviderWarningOmissionRecovery = 'none' | 'page-budget';
52
+ /** Which repository / project / resource an omission is attributed to. All fields optional; a scope may name none. */
53
+ export interface ProviderWarningOmissionScope {
54
+ providerId?: string;
55
+ resourceId?: string;
56
+ projectId?: string;
57
+ repositoryId?: string;
58
+ }
59
+ export interface ProviderWarningOmission {
60
+ kind: ProviderWarningOmissionKind;
61
+ /**
62
+ * Whether anything would fetch the missing items, and what. Gate a "load more" affordance on this rather
63
+ * than on `kind` — see {@link ProviderWarningOmissionRecovery}.
64
+ */
65
+ recovery: ProviderWarningOmissionRecovery;
66
+ /**
67
+ * The cap the provider enforces, when it reports one. NOT a result count for every kind: on
68
+ * `recovery-budget` this is a REQUEST budget and must not be shown to a user as a number of results.
69
+ */
70
+ limit?: number;
71
+ /** Total matches the provider reported, when it reports one. Only GitHub's search cap does today. */
72
+ totalCount?: number;
73
+ /** Which repository / project / resource was affected, when one is attributed. */
74
+ scope?: ProviderWarningOmissionScope;
75
+ }
14
76
  export interface ProviderWarning {
15
77
  providerId: IntegrationIds;
16
78
  /** Disambiguates connections on self-managed hosts (where one provider id spans multiple domains). */
@@ -21,6 +83,29 @@ export interface ProviderWarning {
21
83
  kind: ProviderWarningKind;
22
84
  /** Convenience mirror of `kind === 'auth'`. */
23
85
  isAuth: boolean;
86
+ /**
87
+ * Present when this warning describes results the read could not return even though the request itself
88
+ * SUCCEEDED — a provider-enforced cap, an exhausted recovery budget, a page budget, or a sub-scope the read
89
+ * did not drain.
90
+ *
91
+ * Its presence is the signal: an omission is not a failure. Message it as incompleteness rather than as a
92
+ * failed read, and do NOT derive that from `message`, which is English prose and subject to rewording.
93
+ * `kind` stays `'other'` for these, so the failure discriminant keeps meaning exactly what it meant before
94
+ * this field existed.
95
+ *
96
+ * Whether anything would fetch the rest is a SEPARATE question — read {@link ProviderWarningOmission.recovery},
97
+ * not `kind`, and never assume a retry of the same request returns more.
98
+ *
99
+ * `limit` / `totalCount` / `scope` are forwarded only when they are reported; most omissions carry none of
100
+ * the three, so render correctly without them — and see {@link ProviderWarningOmission.limit} before
101
+ * showing that figure as a result count.
102
+ *
103
+ * Its ABSENCE proves nothing. It is never set on a warning derived from a caught exception (see
104
+ * {@link toProviderWarning}) or from a structured scope failure — those are failures, and the field would
105
+ * be a lie there. But it is also absent whenever incompleteness was reported without naming what was left
106
+ * out, so treat a bare `kind: 'other'` warning as unclassified rather than as a proven failure.
107
+ */
108
+ omission?: ProviderWarningOmission;
24
109
  }
25
110
  export interface ProviderPageInfo {
26
111
  /**
@@ -184,6 +269,35 @@ export interface ProviderRepositoryShape {
184
269
  * a 404 isn't mislabeled as auth.
185
270
  */
186
271
  export declare function toProviderWarning(providerId: IntegrationIds, domain: string | undefined, connectionId: string | undefined, ex: unknown): ProviderWarning;
187
- /** Appends `warning` to `into` only when an equal warning (by provider/connection/domain/kind/message) is absent. */
272
+ /**
273
+ * A scope's identity, as the stable string that keys it.
274
+ *
275
+ * The single definition of what "the same scope" means, shared by the failure and omission dedup keys in
276
+ * `providerPaging.ts` and by {@link providerWarningKey} below, so a scope gaining a field is one edit rather
277
+ * than three. `providerId` is included: the same repository ID under two providers is two scopes.
278
+ *
279
+ * The parameter is structural rather than the SDK's `CollectionScope` so this module keeps naming no
280
+ * `@gitkraken/provider-apis` types (see the export block in `index.ts`) while still serving its SDK-facing
281
+ * callers, which pass that type in unchanged.
282
+ */
283
+ export declare function collectionScopeKey(scope: ProviderWarningOmissionScope | undefined): string;
284
+ /**
285
+ * Strips the omission from every warning in `warnings` when the read as a whole failed.
286
+ *
287
+ * An omission asserts the request SUCCEEDED, and a drain only learns it failed AFTER it may have emitted one:
288
+ * an early page can report its own truncation and then a later page can die. Deciding per warning, at the
289
+ * moment each is built, cannot see that future — so the aggregate is reconciled once, here, where
290
+ * `fetchFailed` is final. Call it at the point a read returns its `fetchFailed`.
291
+ *
292
+ * Re-dedupes as it goes: the omission is part of a warning's identity, so two warnings that differed only
293
+ * there become identical once it is gone, and the array's contract is that no two entries are equal.
294
+ *
295
+ * Mutates in place: the warning array is the one being returned, and callers accumulate into it across pages.
296
+ */
297
+ export declare function reconcileOmissionsWithFailure(warnings: ProviderWarning[], fetchFailed: boolean): void;
298
+ /**
299
+ * Appends `warning` to `into` only when an equal warning (by provider/connection/domain/kind/message, plus the
300
+ * structured omission when one is present) is absent.
301
+ */
188
302
  export declare function appendDedupedWarning(into: ProviderWarning[], warning: ProviderWarning): void;
189
303
  //# sourceMappingURL=results.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"results.d.ts","sourceRoot":"","sources":["../../../src/plus/integrations/results.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"results.d.ts","sourceRoot":"","sources":["../../../src/plus/integrations/results.ts"],"names":[],"mappings":"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"}
@@ -48,11 +48,86 @@ export function toProviderWarning(providerId, domain, connectionId, ex) {
48
48
  isAuth: kind === 'auth',
49
49
  };
50
50
  }
51
- /** A stable key for deduplicating warnings accumulated across drained pages / fan-out scopes. */
51
+ /**
52
+ * A scope's identity, as the stable string that keys it.
53
+ *
54
+ * The single definition of what "the same scope" means, shared by the failure and omission dedup keys in
55
+ * `providerPaging.ts` and by {@link providerWarningKey} below, so a scope gaining a field is one edit rather
56
+ * than three. `providerId` is included: the same repository ID under two providers is two scopes.
57
+ *
58
+ * The parameter is structural rather than the SDK's `CollectionScope` so this module keeps naming no
59
+ * `@gitkraken/provider-apis` types (see the export block in `index.ts`) while still serving its SDK-facing
60
+ * callers, which pass that type in unchanged.
61
+ */
62
+ export function collectionScopeKey(scope) {
63
+ return [scope?.providerId ?? '', scope?.resourceId ?? '', scope?.projectId ?? '', scope?.repositoryId ?? ''].join(' ');
64
+ }
65
+ /**
66
+ * The omission's contribution to a warning's identity — empty when there is none, so a warning without an
67
+ * omission keeps deduping exactly as it did before the field existed.
68
+ *
69
+ * Two omissions of different kinds do produce different `message` values today, so message alone would still
70
+ * separate them. That is incidental: the premise of `omission` is that consumers must not depend on prose
71
+ * carrying the distinguishing fact, and this key must not either.
72
+ */
73
+ function providerWarningOmissionKey(omission) {
74
+ if (omission == null)
75
+ return '';
76
+ return [
77
+ omission.kind,
78
+ omission.recovery,
79
+ omission.limit ?? '',
80
+ omission.totalCount ?? '',
81
+ collectionScopeKey(omission.scope),
82
+ ].join(' ');
83
+ }
84
+ /**
85
+ * Strips the omission from every warning in `warnings` when the read as a whole failed.
86
+ *
87
+ * An omission asserts the request SUCCEEDED, and a drain only learns it failed AFTER it may have emitted one:
88
+ * an early page can report its own truncation and then a later page can die. Deciding per warning, at the
89
+ * moment each is built, cannot see that future — so the aggregate is reconciled once, here, where
90
+ * `fetchFailed` is final. Call it at the point a read returns its `fetchFailed`.
91
+ *
92
+ * Re-dedupes as it goes: the omission is part of a warning's identity, so two warnings that differed only
93
+ * there become identical once it is gone, and the array's contract is that no two entries are equal.
94
+ *
95
+ * Mutates in place: the warning array is the one being returned, and callers accumulate into it across pages.
96
+ */
97
+ export function reconcileOmissionsWithFailure(warnings, fetchFailed) {
98
+ if (!fetchFailed || !warnings.some(w => w.omission != null))
99
+ return;
100
+ const reconciled = [];
101
+ for (const warning of warnings) {
102
+ if (warning.omission == null) {
103
+ appendDedupedWarning(reconciled, warning);
104
+ continue;
105
+ }
106
+ const { omission: _omission, ...rest } = warning;
107
+ appendDedupedWarning(reconciled, rest);
108
+ }
109
+ warnings.splice(0, warnings.length, ...reconciled);
110
+ }
111
+ /**
112
+ * A stable key for deduplicating warnings accumulated across drained pages / fan-out scopes.
113
+ *
114
+ * `message` stays LAST. It is the only free-form segment — provider prose, spaces and all — so anything
115
+ * appended after it could be impersonated by a message that happens to end in the same text.
116
+ */
52
117
  function providerWarningKey(warning) {
53
- return [warning.providerId, warning.connectionId ?? '', warning.domain ?? '', warning.kind, warning.message].join(' ');
118
+ return [
119
+ warning.providerId,
120
+ warning.connectionId ?? '',
121
+ warning.domain ?? '',
122
+ warning.kind,
123
+ providerWarningOmissionKey(warning.omission),
124
+ warning.message,
125
+ ].join(' ');
54
126
  }
55
- /** Appends `warning` to `into` only when an equal warning (by provider/connection/domain/kind/message) is absent. */
127
+ /**
128
+ * Appends `warning` to `into` only when an equal warning (by provider/connection/domain/kind/message, plus the
129
+ * structured omission when one is present) is absent.
130
+ */
56
131
  export function appendDedupedWarning(into, warning) {
57
132
  const key = providerWarningKey(warning);
58
133
  if (into.some(existing => providerWarningKey(existing) === key))
@@ -1 +1 @@
1
- {"version":3,"file":"results.js","sourceRoot":"","sources":["../../../src/plus/integrations/results.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AA8K1G,MAAM,+BAA+B,GAAG,GAAG,CAAC;AAE5C,SAAS,sBAAsB,CAAC,EAAW;IAC1C,MAAM,GAAG,GAAG,CAAC,EAAE,YAAY,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACnE,MAAM,OAAO,GAAG,EAA8E,CAAC;IAC/F,MAAM,MAAM,GACX,OAAO,OAAO,EAAE,MAAM,KAAK,QAAQ;QAClC,CAAC,CAAC,OAAO,CAAC,MAAM;QAChB,CAAC,CAAC,OAAO,OAAO,EAAE,QAAQ,EAAE,MAAM,KAAK,QAAQ;YAC9C,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM;YACzB,CAAC,CAAC,SAAS,CAAC;IAEf,4GAA4G;IAC5G,6GAA6G;IAC7G,IAAI,wCAAwC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACxD,OAAO,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,uCAAuC,MAAM,GAAG,CAAC,CAAC,CAAC,0BAA0B,CAAC;IACvG,CAAC;IAED,IAAI,CAAC,GAAG,EAAE,CAAC;QACV,OAAO,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,uCAAuC,MAAM,GAAG,CAAC,CAAC,CAAC,0BAA0B,CAAC;IACvG,CAAC;IACD,OAAO,GAAG,CAAC,MAAM,GAAG,+BAA+B;QAClD,CAAC,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,+BAA+B,GAAG,CAAC,CAAC,KAAK;QAC3D,CAAC,CAAC,GAAG,CAAC;AACR,CAAC;AA+BD;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAChC,UAA0B,EAC1B,MAA0B,EAC1B,YAAgC,EAChC,EAAW;IAEX,IAAI,IAA6B,CAAC;IAClC,IAAI,EAAE,YAAY,mBAAmB,EAAE,CAAC;QACvC,IAAI,GAAG,MAAM,CAAC;IACf,CAAC;SAAM,IAAI,EAAE,YAAY,qBAAqB,EAAE,CAAC;QAChD,IAAI,GAAG,YAAY,CAAC;IACrB,CAAC;SAAM,IAAI,EAAE,YAAY,oBAAoB,EAAE,CAAC;QAC/C,IAAI,GAAG,WAAW,CAAC;IACpB,CAAC;SAAM,CAAC;QACP,IAAI,GAAG,OAAO,CAAC;IAChB,CAAC;IAED,OAAO;QACN,UAAU,EAAE,UAAU;QACtB,MAAM,EAAE,MAAM;QACd,YAAY,EAAE,YAAY;QAC1B,OAAO,EAAE,sBAAsB,CAAC,EAAE,CAAC;QACnC,IAAI,EAAE,IAAI;QACV,MAAM,EAAE,IAAI,KAAK,MAAM;KACvB,CAAC;AACH,CAAC;AAED,iGAAiG;AACjG,SAAS,kBAAkB,CAAC,OAAwB;IACnD,OAAO,CAAC,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC,YAAY,IAAI,EAAE,EAAE,OAAO,CAAC,MAAM,IAAI,EAAE,EAAE,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAChH,GAAG,CACH,CAAC;AACH,CAAC;AAED,qHAAqH;AACrH,MAAM,UAAU,oBAAoB,CAAC,IAAuB,EAAE,OAAwB;IACrF,MAAM,GAAG,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;IACxC,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,kBAAkB,CAAC,QAAQ,CAAC,KAAK,GAAG,CAAC;QAAE,OAAO;IAExE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AACpB,CAAC"}
1
+ {"version":3,"file":"results.js","sourceRoot":"","sources":["../../../src/plus/integrations/results.ts"],"names":[],"mappings":"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"}
@@ -177,13 +177,13 @@ result** instead of rejecting the call. One provider's expired token never blank
177
177
  `ProviderWarning.kind` (also exported as `ProviderWarningKind`) carries the classifications the facade can
178
178
  prove from structured errors:
179
179
 
180
- | `kind` | Meaning | Reasonable response |
181
- | --------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
182
- | `auth` | Token rejected (401/403 that isn't a throttle). | Prompt to reconnect that connection. |
183
- | `rate-limit` | Throttled (429, or a 403 whose body says so). | Back off and retry; keep the last snapshot. |
184
- | `not-found` | 404/410/422 on the requested scope. | Drop that scope; don't reconnect. |
185
- | `no-connection` | The requested `connectionId`/`domain` doesn't resolve. | Re-resolve the target or re-authenticate. |
186
- | `other` | Catch-all: unsupported input, truncation, upstream/network failure, or an unclassified error. | Preserve the warning and use the result flags; do not assume it is benign or non-retryable. |
180
+ | `kind` | Meaning | Reasonable response |
181
+ | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
182
+ | `auth` | Token rejected (401/403 that isn't a throttle). | Prompt to reconnect that connection. |
183
+ | `rate-limit` | Throttled (429, or a 403 whose body says so). | Back off and retry; keep the last snapshot. |
184
+ | `not-found` | 404/410/422 on the requested scope. | Drop that scope; don't reconnect. |
185
+ | `no-connection` | The requested `connectionId`/`domain` doesn't resolve. | Re-resolve the target or re-authenticate. |
186
+ | `other` | Catch-all: unsupported input, truncation, upstream/network failure, or an unclassified error. Read `omission` before treating one as a failure. | Preserve the warning and use the result flags; do not assume it is benign or non-retryable. |
187
187
 
188
188
  `isAuth` is a convenience mirror of `kind === 'auth'`. **Collapsing `kind` into that boolean loses the
189
189
  rate-limit and not-found distinctions**, which then have to be re-derived from raw provider prose.
@@ -191,6 +191,70 @@ Conversely, `other` is intentionally not a complete failure taxonomy. Treat `mes
191
191
  text rather than a stable protocol; use `fetchFailed`, `page.truncated`, and `page.allPages` for completeness
192
192
  and keep unknown failures conservative.
193
193
 
194
+ ### `omission` — succeeded, but withheld results
195
+
196
+ `other` covers two facts with **opposite remedies**: a request that failed, and a request that succeeded while
197
+ part of the answer was withheld. `ProviderWarning.omission` is set only for the second, so a consumer can act
198
+ on it without parsing `message`:
199
+
200
+ ```ts
201
+ if (warning.omission != null) {
202
+ // The read SUCCEEDED — message it as incompleteness, not failure.
203
+ // Whether anything would fetch the rest is a separate question; see `recovery` below.
204
+ if (warning.omission.recovery !== 'none') offerLoadMore(warning.omission);
205
+ }
206
+ ```
207
+
208
+ `kind` stays `'other'` for these on purpose: it is the discriminant derived from a caught exception's type, and
209
+ adding a member would silently change what `'other'` means for every existing build.
210
+
211
+ **Its absence proves nothing.** It is never set on a failure — an exception or a structured scope failure —
212
+ where it would be a lie. But it is also absent whenever incompleteness was reported without naming what was
213
+ left out, so treat a bare `kind: 'other'` warning as unclassified rather than as a proven failure.
214
+
215
+ The line that matters is whether the request **succeeded**, not whether a tail was left unread. A drain that
216
+ stopped on its own accounting succeeded and is capped, so it carries the omission; a drain that was interrupted
217
+ mid-read left an unread tail too, but a retry may complete it — that one carries no omission and sets
218
+ `fetchFailed`.
219
+
220
+ `kind` says **why** results are missing:
221
+
222
+ | `omission.kind` | What happened |
223
+ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
224
+ | `provider-limit` | The provider refuses to serve past a cap (GitHub search's 1,000, Trello's `cards_limit`). |
225
+ | `recovery-budget` | The internal partitioned recovery stopped before visiting every partition. |
226
+ | `pagination-incomplete` | Pages were left unread: an undrained sub-scope, a page budget, or a provider that advertised another page without a usable cursor. |
227
+
228
+ #### `recovery` — what, if anything, would fetch the rest
229
+
230
+ **`kind` does not answer that**, and `pagination-incomplete` is why: a drain that stopped at a page budget and
231
+ a provider that gave no usable cursor are the same kind, but only the first can be fetched. Gate a "load more"
232
+ affordance on `recovery`, never on `kind`:
233
+
234
+ | `omission.recovery` | Means | What a consumer does |
235
+ | ------------------- | -------------------------------------------------------------- | -------------------------------------------------------------------------- |
236
+ | `none` | Nothing you can call returns the missing items. | Say the results are capped. Do not offer to fetch more. |
237
+ | `page-budget` | Re-run the same read with a higher `maxPages` (sweep options). | Offer it — but note it re-reads from the start, so make it user-initiated. |
238
+
239
+ `recovery` is **required** — unlike `limit`, `totalCount` and `scope`, it is never absent. An absent value
240
+ would be indistinguishable from `none` while actually meaning "this producer didn't say", which is the
241
+ ambiguity `omission` exists to remove.
242
+
243
+ It is also **conservative**: it names only what a producer can prove, so `none` means "not known to be
244
+ recoverable", not "proven unrecoverable". Today only a sweep that spent its own page budget reports
245
+ `page-budget`; everything else — every provider cap, every exhausted internal budget, and every omission
246
+ derived from SDK metadata — is `none`. A `scope` does not change that. It attributes where results were
247
+ withheld, and the SDK reports the same scoped shape both for a scope it merely sampled and for one whose
248
+ cursor stalled, so re-reading it is not something this layer can promise.
249
+
250
+ `limit`, `totalCount` and `scope` are forwarded only when reported; **most omissions carry none of the three**,
251
+ so render correctly without them. Two traps: `totalCount` is `number | undefined` and never `null` (the SDK's
252
+ `null` is normalized to absent at the boundary), and `limit` on `recovery-budget` is a **request** budget — do
253
+ not show it to a user as a number of results.
254
+
255
+ Warnings dedup on their structure, `omission` included, so two omissions that differ only in kind, recovery or
256
+ scope stay two warnings even if their messages ever converge.
257
+
194
258
  | Flag | Says |
195
259
  | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
196
260
  | `fetchFailed` | `items` is incomplete because a scope failed, or because a flat hierarchy read was truncated. Distinguishes this from a genuine empty result. |
@@ -199,6 +263,16 @@ and keep unknown failures conservative.
199
263
  | `failedProviderIds` | Sweeps/broadens: providers whose requested scopes produced no usable result. |
200
264
  | `incompleteProviderIds` | Sweeps/broadens: providers with a usable result plus a failed, partial, or truncated sibling scope. |
201
265
 
266
+ An omission pairs with `page.truncated: true` — results are missing — and typically with `fetchFailed: false`,
267
+ since nothing failed. But the flags are **per result** and `omission` is **per warning**, so the two can differ
268
+ on a fan-out: a sweep where provider A was capped and provider B failed outright reports `fetchFailed: true`
269
+ while A's omission stays true for A. Read `omission` on the warning that carries it — its `providerId`,
270
+ `domain` and `connectionId` say who it is about — rather than inferring it from the aggregate.
271
+
272
+ What is guaranteed is the narrower thing: a warning never claims its own read succeeded when it didn't. A drain
273
+ that dies mid-read publishes its unread tail with no omission, even if an earlier page had already reported
274
+ one.
275
+
202
276
  `resolveRepository` reports through `resolution.status` instead: `resolved` · `not-found` · `unauthorized` ·
203
277
  `unsupported-provider` · `invalid-remote-url` · `host-mismatch` · `undetermined`. A `resolved` identity
204
278
  carries the provider's **canonical** owner/name plus `renamed: true` when the local remote is stale.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gitkraken/core-gitlens",
3
3
  "description": "GitLens core — shared Git / AI / GitHub primitives for internal GitKraken consumption",
4
- "version": "0.5.100",
4
+ "version": "0.5.102",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": {
7
7
  "name": "GitKraken",
@@ -220,7 +220,7 @@
220
220
  }
221
221
  },
222
222
  "dependencies": {
223
- "@gitkraken/provider-apis": "0.54.0",
223
+ "@gitkraken/provider-apis": "0.55.0",
224
224
  "@octokit/graphql": "9.0.3",
225
225
  "@octokit/request": "10.0.11",
226
226
  "@octokit/request-error": "7.1.0",
@@ -8,7 +8,8 @@ import type { RepositoryIdentityDescriptor } from './repositoryIdentities.js';
8
8
  export type IssueStateFilter = 'open' | 'closed' | 'all';
9
9
 
10
10
  export interface IssueShape extends IssueOrPullRequest {
11
- author: IssueMember;
11
+ /** `undefined` when the provider can't resolve the author, e.g. a deleted GitHub account */
12
+ author: IssueMember | undefined;
12
13
  assignees: IssueMember[];
13
14
  repository?: IssueRepository;
14
15
  labels?: IssueLabel[];
@@ -31,7 +32,7 @@ export class Issue implements IssueShape {
31
32
  public readonly updatedDate: Date,
32
33
  public readonly closed: boolean,
33
34
  public readonly state: IssueOrPullRequestState,
34
- public readonly author: IssueMember,
35
+ public readonly author: IssueMember | undefined,
35
36
  public readonly assignees: IssueMember[],
36
37
  public readonly repository?: IssueRepository,
37
38
  public readonly closedDate?: Date,
@@ -38,12 +38,15 @@ export function serializeIssue(value: IssueShape): IssueShape {
38
38
  closedDate: value.closedDate,
39
39
  closed: value.closed,
40
40
  state: value.state,
41
- author: {
42
- id: value.author.id,
43
- name: value.author.name,
44
- avatarUrl: value.author.avatarUrl,
45
- url: value.author.url,
46
- },
41
+ author:
42
+ value.author == null
43
+ ? undefined
44
+ : {
45
+ id: value.author.id,
46
+ name: value.author.name,
47
+ avatarUrl: value.author.avatarUrl,
48
+ url: value.author.url,
49
+ },
47
50
  repository:
48
51
  value.repository == null
49
52
  ? undefined
@@ -7,6 +7,12 @@ export function getDefaultRemoteOrHighlander<T extends GitRemote>(remotes: T[]):
7
7
  return remotes.length === 1 ? remotes[0] : remotes.find(r => r.default);
8
8
  }
9
9
 
10
+ /** Like {@link getDefaultRemoteOrHighlander}, but falls back to `origin` when no remote is designated
11
+ * default — otherwise features keyed on "the default remote" silently do nothing on multi-remote repos */
12
+ export function getDefaultRemoteOrOrigin<T extends GitRemote>(remotes: T[]): T | undefined {
13
+ return getDefaultRemoteOrHighlander(remotes) ?? remotes.find(r => r.name === 'origin');
14
+ }
15
+
10
16
  export function getHighlanderProviderName(remotes: GitRemote<RemoteProvider>[]): string | undefined {
11
17
  if (remotes.length === 0) return undefined;
12
18
 
@@ -3638,7 +3638,7 @@ export class GitHubApi {
3638
3638
  endCursor?: string | null;
3639
3639
  hasNextPage: boolean;
3640
3640
  };
3641
- nodes: GitHubIssue[];
3641
+ nodes: (GitHubIssue | null)[] | null;
3642
3642
  };
3643
3643
  interface SearchResult {
3644
3644
  authored?: SearchCategory;
@@ -3777,16 +3777,26 @@ export class GitHubApi {
3777
3777
  cancellation,
3778
3778
  );
3779
3779
 
3780
- function toQueryResult(issue: GitHubIssue): IssueShape {
3781
- return fromGitHubIssue(issue, provider);
3782
- }
3783
-
3784
3780
  if (rsp == null) return { values: [], hasMore: false, page: page, truncated: false };
3785
3781
 
3782
+ // Map node-by-node so one unmappable issue can't discard the whole result set
3783
+ const issues: IssueShape[] = [];
3784
+ for (const node of [
3785
+ ...(rsp.assigned?.nodes ?? []),
3786
+ ...(rsp.mentioned?.nodes ?? []),
3787
+ ...(rsp.authored?.nodes ?? []),
3788
+ ]) {
3789
+ if (node?.id == null) continue;
3790
+
3791
+ try {
3792
+ issues.push(fromGitHubIssue(node, provider));
3793
+ } catch (ex) {
3794
+ scope?.warn(`skipped unmappable issue; id=${node.id}, url=${node.url}, ex=${ex}`);
3795
+ }
3796
+ }
3797
+
3786
3798
  const results: IterableIterator<IssueShape> = uniqueBy(
3787
- [...(rsp.assigned?.nodes ?? []), ...(rsp.mentioned?.nodes ?? []), ...(rsp.authored?.nodes ?? [])].map(
3788
- toQueryResult,
3789
- ),
3799
+ issues,
3790
3800
  r => r.url,
3791
3801
  (original, _current) => original,
3792
3802
  );
@@ -3910,7 +3920,7 @@ export class GitHubApi {
3910
3920
  scope,
3911
3921
  cancellation,
3912
3922
  );
3913
- if (rsp == null) return [];
3923
+ if (rsp == null) return results;
3914
3924
 
3915
3925
  const pageResults = filterPullRequestsBySearchState(
3916
3926
  rsp.search.nodes.map(pr => fromGitHubPullRequest(pr, provider)),
@@ -2,7 +2,7 @@ import type { Endpoints } from '@octokit/types';
2
2
  import { GitFileIndexStatus } from '../../git/models/fileStatus.js';
3
3
  import type { IssueLabel } from '../../git/models/issue.js';
4
4
  import { Issue, RepositoryAccessLevel } from '../../git/models/issue.js';
5
- import type { PullRequestState } from '../../git/models/pullRequest.js';
5
+ import type { PullRequestMember, PullRequestState } from '../../git/models/pullRequest.js';
6
6
  import {
7
7
  PullRequest,
8
8
  PullRequestMergeableState,
@@ -131,8 +131,9 @@ export type GitHubPullRequestState = 'OPEN' | 'CLOSED' | 'MERGED';
131
131
  export type GitHubIssueOrPullRequestState = GitHubIssueState | GitHubPullRequestState;
132
132
 
133
133
  export interface GitHubPullRequestLite extends Omit<GitHubIssueOrPullRequest, '__typename'> {
134
- author: GitHubMember;
135
- body: string;
134
+ /** `Actor` is nullable in GitHub's schema — `null` once the author's account is deleted */
135
+ author: GitHubMember | null;
136
+ body: string | null;
136
137
 
137
138
  baseRefName: string;
138
139
  baseRefOid: string;
@@ -167,7 +168,8 @@ export interface GitHubPullRequestLite extends Omit<GitHubIssueOrPullRequest, '_
167
168
  }
168
169
 
169
170
  export interface GitHubIssue extends Omit<GitHubIssueOrPullRequest, '__typename'> {
170
- author: GitHubMember;
171
+ /** `Actor` is nullable in GitHub's schema — `null` once the author's account is deleted */
172
+ author: GitHubMember | null;
171
173
  assignees: { nodes: GitHubMember[] };
172
174
  comments?: {
173
175
  totalCount: number;
@@ -203,7 +205,7 @@ export interface GitHubPullRequest extends GitHubPullRequestLite {
203
205
  reviewDecision: GitHubPullRequestReviewDecision;
204
206
  latestReviews: {
205
207
  nodes: {
206
- author: GitHubMember;
208
+ author: GitHubMember | null;
207
209
  state: GitHubPullRequestReviewState;
208
210
  }[];
209
211
  };
@@ -234,15 +236,17 @@ export type GitHubViewerPermission =
234
236
  | 'READ' // Can read and clone this repository. Can also open and comment on issues and pull requests
235
237
  | 'NONE';
236
238
 
239
+ /** `ghost` is how github.com renders an actor whose account was deleted */
240
+ function fromGitHubMemberOrGhost(member: GitHubMember | null | undefined): PullRequestMember {
241
+ if (member == null) return { id: 'ghost', name: 'ghost' };
242
+
243
+ return { id: member.login, name: member.login, avatarUrl: member.avatarUrl, url: member.url };
244
+ }
245
+
237
246
  export function fromGitHubPullRequestLite(pr: GitHubPullRequestLite, provider: Provider): PullRequest {
238
247
  return new PullRequest(
239
248
  provider,
240
- {
241
- id: pr.author.login,
242
- name: pr.author.login,
243
- avatarUrl: pr.author.avatarUrl,
244
- url: pr.author.url,
245
- },
249
+ fromGitHubMemberOrGhost(pr.author),
246
250
  String(pr.number),
247
251
  pr.id,
248
252
  pr.title,
@@ -296,7 +300,7 @@ export function fromGitHubPullRequestLite(pr: GitHubPullRequestLite, provider: P
296
300
  undefined, // statusCheckRollupState
297
301
  undefined, // project
298
302
  undefined, // version
299
- pr.body,
303
+ pr.body ?? undefined,
300
304
  );
301
305
  }
302
306
 
@@ -399,12 +403,7 @@ export function fromGitHubPullRequestStatusCheckRollupState(
399
403
  export function fromGitHubPullRequest(pr: GitHubPullRequest, provider: Provider): PullRequest {
400
404
  return new PullRequest(
401
405
  provider,
402
- {
403
- id: pr.author.login,
404
- name: pr.author.login,
405
- avatarUrl: pr.author.avatarUrl,
406
- url: pr.author.url,
407
- },
406
+ fromGitHubMemberOrGhost(pr.author),
408
407
  String(pr.number),
409
408
  pr.id,
410
409
  pr.title,
@@ -469,12 +468,7 @@ export function fromGitHubPullRequest(pr: GitHubPullRequest, provider: Provider)
469
468
  )
470
469
  .filter(<T>(r?: T): r is T => Boolean(r)),
471
470
  pr.latestReviews.nodes.map(r => ({
472
- reviewer: {
473
- id: r.author.login,
474
- name: r.author.login,
475
- avatarUrl: r.author.avatarUrl,
476
- url: r.author.url,
477
- },
471
+ reviewer: fromGitHubMemberOrGhost(r.author),
478
472
  state: fromGitHubPullRequestReviewState(r.state),
479
473
  })),
480
474
  pr.assignees.nodes.map(r => ({
@@ -486,7 +480,7 @@ export function fromGitHubPullRequest(pr: GitHubPullRequest, provider: Provider)
486
480
  fromGitHubPullRequestStatusCheckRollupState(pr.commits.nodes?.[0]?.commit.statusCheckRollup?.state),
487
481
  undefined, // project
488
482
  undefined, // version
489
- pr.body,
483
+ pr.body ?? undefined,
490
484
  );
491
485
  }
492
486
 
@@ -506,12 +500,14 @@ export function fromGitHubIssue(value: GitHubIssue, provider: Provider): Issue {
506
500
  new Date(value.updatedAt),
507
501
  value.closed,
508
502
  fromGitHubIssueOrPullRequestState(value.state),
509
- {
510
- id: value.author.login,
511
- name: value.author.login,
512
- avatarUrl: value.author.avatarUrl,
513
- url: value.author.url,
514
- },
503
+ value.author == null
504
+ ? undefined
505
+ : {
506
+ id: value.author.login,
507
+ name: value.author.login,
508
+ avatarUrl: value.author.avatarUrl,
509
+ url: value.author.url,
510
+ },
515
511
  value.assignees.nodes.map(assignee => ({
516
512
  id: assignee.login,
517
513
  name: assignee.login,