|
const run = async (...args: Parameters<Fn>): Promise<CachedValue<Fn>> => { |
|
// `fn`'s awaited result is the cached value by construction; the generic `Fn` erases it to `unknown`. |
|
const rawFallback = async (): Promise<CachedValue<Fn>> => (await fn(...args)) as CachedValue<Fn>; |
|
const noLayerLabels = { |
|
cacheNamespace: this.namespace, |
|
useCase: options.useCase, |
|
keyType: options.keyType, |
|
layer: NO_CACHE_LAYER, |
|
} as const; |
|
|
|
if (!this.isEnabled()) { |
|
this.metrics?.disabled({ ...noLayerLabels, reason: "context" }); |
|
return await rawFallback(); |
|
} |
|
|
|
const fallback = (): Promise<CachedValue<Fn>> => |
|
withFallbackTimeout(rawFallback, options.useCase, fallbackTimeoutMs); |
|
|
|
let key: DialCacheKey; |
|
try { |
|
key = this.buildKey(options, options.cacheKey(...args)); |
|
} catch (error) { |
|
this.logger.error("Could not construct DialCache key", error); |
|
this.metrics?.error({ |
|
...noLayerLabels, |
|
error: "key_construction", |
|
inFallback: false, |
|
}); |
|
return await this.callFallback(noLayerLabels, fallback); |
|
} |
|
|
|
let keyConfig: DialCacheKeyConfig | null; |
|
try { |
|
keyConfig = await fetchKeyConfig(this.configProvider, key); |
|
} catch (error) { |
|
// Provider failure: fail open and run uncached, mirroring the per-layer config_error path. |
|
this.logger.warn("Could not resolve DialCache key config", error); |
|
this.recordError(key, NO_CACHE_LAYER, "config_resolution"); |
|
this.metrics?.disabled({ ...noLayerLabels, reason: "config_error" }); |
|
return await this.callFallback(noLayerLabels, fallback); |
|
} |
|
|
|
// An unawaited child can inherit the async store after its outer enable() |
|
// callback settles. The closed holder turns that detached work back into |
|
// pass-through instead of allowing it to repopulate request state. |
|
if (!this.isEnabled()) { |
|
this.metrics?.disabled({ ...noLayerLabels, reason: "context" }); |
|
return await this.callFallback(noLayerLabels, fallback); |
|
} |
|
|
|
if (keyConfig?.requestLocal === true) { |
|
const requestLocalCache = getOrCreateRequestLocalCache(this.context); |
|
if (requestLocalCache !== null) { |
|
return await this.getThroughRequestLocal(requestLocalCache, key, keyConfig, fallback); |
|
} |
|
} |
|
|
|
return await this.getThroughSharedLayers(key, keyConfig, fallback, CacheLayer.LOCAL); |
Priority
P2 — Medium — remove built-in hot-path overhead without taking ownership of dynamic provider policy.
Current problem
Every enabled invocation awaits
cacheConfigProviderbefore request-local lookup and before exact-key process coalescing. That remains true for the built-in provider that only returnsnull. Repeated request-local hits therefore still cross the provider abstraction.Current evidence:
DialCache/src/dialcache.ts
Lines 252 to 309 in 34a45fa
DialCache/src/dialcache.ts
Lines 165 to 194 in 34a45fa
#69 confirms that provider settlement deadlines are application-owned;
fallbackTimeoutMsdoes not cover provider work. Open #68 preserves one provider resolution per enabled invocation while changing how sparse results overlay defaults.Recommended narrow scope — approval required
Acceptance criteria
config_errorhandling and the finite-settlement caller contract.