Skip to content

perf: scope-helper + caching#63

Open
CompN3rd wants to merge 3 commits into
mainfrom
perf/scope-helper-caching
Open

perf: scope-helper + caching#63
CompN3rd wants to merge 3 commits into
mainfrom
perf/scope-helper-caching

Conversation

@CompN3rd
Copy link
Copy Markdown
Owner

Changes

TtlCache utility (new)

  • Reusable \TtlCache\ class with TTL-based expiry
  • Used to cache \getWorkItemTypes\ results (5-min TTL)
  • Reduces redundant API calls for work item type lookups

forEachScope helper (new)

  • \ orEachScope()\ in src/utils/async.ts combines
    esolveProjectScopes\ + \mapWithConcurrencyLimit\ into one pattern
  • Returns { scopes, items }\ with scope-tagged results
  • Replaces 70 lines of boilerplate across 4 providers

Migrated to forEachScope

  • workItemProvider.ts — tree view children
  • pipelinesProvider.ts — pipeline run listing
  • pullRequestProvider.ts — PR bucket loading
  • planningProviders.ts — backlog provider

Removed

  • \MAX_CONCURRENT_SCOPE_REQUESTS\ constants from all 4 providers (now defaults in \ orEachScope)

  • esolveProjectScopes\ + \mapWithConcurrencyLimit\ imports from migrated files

Verification


  • pm run compile\ — all TypeScript checks pass

Marc Kassubeck and others added 2 commits May 20, 2026 22:18
Extract reusable TtlCache<T> class to src/utils/ttlCache.ts. Add 5-minute TTL cache to AdoClient.getWorkItemTypes to avoid redundant API calls when querying work item types for the same project within the cache window.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Create forEachScope<T>() in async.ts that combines resolveProjectScopes + mapWithConcurrencyLimit into a single reusable pattern returning { scopes, items }.

Migrate workItemProvider, pipelinesProvider, pullRequestProvider, and planningProviders to use the helper, removing the duplicate resolveProjectScopes + mapParallelLimit boilerplate and the MAX_CONCURRENT_SCOPE_REQUESTS constants from each file.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces reusable helpers to reduce redundant Azure DevOps API calls and remove repeated multi-scope concurrency boilerplate across several tree providers.

Changes:

  • Added a generic TtlCache<T> utility and used it to cache AdoClient.getWorkItemTypes() for 5 minutes.
  • Introduced forEachScope() to combine resolveProjectScopes() + mapWithConcurrencyLimit() into a single helper returning { scopes, items }.
  • Migrated Work Items, Pull Requests, Pipelines, and Planning providers to use forEachScope() and removed their per-file concurrency constants/loader helpers.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/utils/ttlCache.ts Adds a small TTL cache primitive used for API result caching.
src/utils/async.ts Adds forEachScope() helper alongside existing concurrency mapper.
src/providers/workItemProvider.ts Replaces per-scope loader boilerplate with forEachScope().
src/providers/pullRequestProvider.ts Uses forEachScope() for bucket PR loading and removes redundant helper.
src/providers/planningProviders.ts Uses forEachScope() in planning item loading path.
src/providers/pipelinesProvider.ts Uses forEachScope() for pipeline run listing and removes redundant helper.
src/api/adoClient.ts Adds a 5-minute TTL cache for getWorkItemTypes() and clears it on token changes/disconnect.

Comment thread src/utils/ttlCache.ts Outdated

has(key: string): boolean {
const entry = this._map.get(key);
return entry !== undefined && entry.expiresAt > Date.now();
Comment thread src/utils/async.ts Outdated
Comment on lines +1 to +16
import type { AdoClient } from '../api/adoClient';
import type { ConfigManager } from '../config/configManager';
import { resolveProjectScopes, type ProjectScope } from '../providers/projectScopes';

export async function forEachScope<T>(
client: AdoClient,
config: ConfigManager,
fetcher: (scope: ProjectScope) => Promise<T[]>,
concurrency = 4
): Promise<{ scopes: ProjectScope[]; items: T[] }> {
const scopes = await resolveProjectScopes(client, config);
if (scopes.length === 0) {
return { scopes, items: [] };
}
const nested = await mapWithConcurrencyLimit(scopes, concurrency, fetcher);
return { scopes, items: nested.flat() };
- TtlCache.has() now evicts expired entries (delegates to get())
- Move forEachScope from utils/async.ts to providers/projectScopes.ts
  so unrelated utils/async consumers don't transitively pull in the
  provider layer
- Capture activeWorkItemQuery.filter / pipelineRunsTop / pipelineRunsFilter
  before the concurrent fetcher to guard against config changes mid-load
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants