Prefer explicit, responsibility-aligned service APIs with well-defined contracts for (a) what callers should expect synchronously/async, (b) how caching vs live validation behaves, and (c) how events supersede in-flight work.
Concretely:
undefined with multiple meanings.Example shape (access-verdict contract):
type AccessVerdict =
| { kind: 'SignInRequired' }
| { kind: 'Denied' }
| { kind: 'Available'; manifest: IExtensionGalleryManifest } // only if this service owns fetching
| { kind: 'Misconfigured' }
| { kind: 'Unreachable' };
interface IMarketplaceAccessService {
// Returns a verdict based on durable cache + (optionally) live validation.
// If live validation is deferred, publish updates via onDidChangeAccess.
resolveAccess(serviceUrl: string): Promise<AccessVerdict>;
// Fires when a later validation supersedes the earlier verdict.
onDidChangeAccess: Event<AccessVerdict>;
// Clears cached verdicts + memoized indices so late results can’t repopulate.
reset(): void;
}
Applying this consistently reduces ambiguity for consumers, prevents stale-cache races, and keeps API ownership and security boundaries clear.