parent
c0753b523d
commit
000178fdc4
@ -0,0 +1,123 @@
|
||||
import {
|
||||
CACHE_CONTEXT_REASON_FETCH_ERROR,
|
||||
CACHE_CONTEXT_REASON_GET,
|
||||
CACHE_DECISION_ACTION_DELETE_AND_FETCH,
|
||||
CACHE_DECISION_ACTION_SERVE,
|
||||
CACHE_DECISION_ACTION_SERVE_AND_REFRESH,
|
||||
CACHE_DECISION_REASON_BYPASS_CACHE,
|
||||
CACHE_DECISION_REASON_CACHE_MISS,
|
||||
CACHE_DECISION_REASON_NO_STORE,
|
||||
CACHE_EVENT_ADAPTER_ERROR,
|
||||
CACHE_EVENT_HIT,
|
||||
CACHE_EVENT_MISS,
|
||||
CACHE_EVENT_SCHEMA_MISMATCH,
|
||||
CACHE_EVENT_STALE_HIT,
|
||||
CACHE_EVENT_STALE_IF_ERROR,
|
||||
CACHE_READ_MODE_BYPASS_CACHE,
|
||||
CACHE_READ_MODE_NO_STORE
|
||||
} from './consts.ts';
|
||||
import { normalizeTags } from './key.ts';
|
||||
import type { CacheContextInput, ResolvedCacheContext } from './runtime-context.ts';
|
||||
import type { CacheRuntimeDelete } from './runtime-delete.ts';
|
||||
import { canServeCacheStaleIfError } from './runtime-evaluation.ts';
|
||||
import type { CacheRuntimeIo } from './runtime-io.ts';
|
||||
import type { Singleflight } from './singleflight.ts';
|
||||
import type {
|
||||
CacheAdapter,
|
||||
CacheDecision,
|
||||
CacheEnvelope,
|
||||
CacheEvent,
|
||||
CacheEventType,
|
||||
QueryOptions
|
||||
} from './types.ts';
|
||||
|
||||
interface CacheRuntimeQueryInput {
|
||||
readonly adapter: CacheAdapter;
|
||||
readonly deletion: CacheRuntimeDelete;
|
||||
readonly io: CacheRuntimeIo;
|
||||
readonly singleflight: Singleflight;
|
||||
now(): number;
|
||||
contextFor(input: CacheContextInput): Promise<ResolvedCacheContext>;
|
||||
evaluate<T>(entry: CacheEnvelope<T>, ctx: ResolvedCacheContext): Promise<CacheDecision>;
|
||||
backgroundRefresh<T>(ctx: ResolvedCacheContext, options: QueryOptions<T>): Promise<void>;
|
||||
emit(type: CacheEventType, patch?: Partial<CacheEvent>): void;
|
||||
emitForContext(
|
||||
type: CacheEventType,
|
||||
ctx: ResolvedCacheContext,
|
||||
patch?: Partial<CacheEvent>
|
||||
): void;
|
||||
}
|
||||
|
||||
export function createCacheRuntimeQuery(runtime: CacheRuntimeQueryInput) {
|
||||
return async function query<T>(options: QueryOptions<T>): Promise<T> {
|
||||
const ctx = await runtime.contextFor(options);
|
||||
|
||||
if (
|
||||
ctx.policy.mode === CACHE_READ_MODE_BYPASS_CACHE ||
|
||||
ctx.policy.mode === CACHE_READ_MODE_NO_STORE
|
||||
) {
|
||||
const reason =
|
||||
ctx.policy.mode === CACHE_READ_MODE_BYPASS_CACHE
|
||||
? CACHE_DECISION_REASON_BYPASS_CACHE
|
||||
: CACHE_DECISION_REASON_NO_STORE;
|
||||
runtime.emitForContext(CACHE_EVENT_MISS, ctx, { reason });
|
||||
const value = await options.fetcher();
|
||||
if (ctx.policy.mode === CACHE_READ_MODE_BYPASS_CACHE) {
|
||||
const envelope = await runtime.io.createEnvelope(value, ctx, normalizeTags(options.tags));
|
||||
await runtime.io.writeEnvelope(ctx, envelope);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
let cached: CacheEnvelope<T> | null = null;
|
||||
|
||||
try {
|
||||
cached = await runtime.adapter.get<T>(ctx.fullKey);
|
||||
} catch (error) {
|
||||
runtime.emit(CACHE_EVENT_ADAPTER_ERROR, {
|
||||
key: ctx.fullKey,
|
||||
keyHash: ctx.keyHash,
|
||||
error,
|
||||
reason: CACHE_CONTEXT_REASON_GET
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (cached) {
|
||||
const decision = await runtime.evaluate(cached, ctx);
|
||||
|
||||
if (decision.action === CACHE_DECISION_ACTION_SERVE) {
|
||||
runtime.emitForContext(CACHE_EVENT_HIT, ctx, { reason: decision.reason });
|
||||
return cached.value;
|
||||
}
|
||||
|
||||
if (decision.action === CACHE_DECISION_ACTION_SERVE_AND_REFRESH) {
|
||||
runtime.emitForContext(CACHE_EVENT_STALE_HIT, ctx, { reason: decision.reason });
|
||||
await runtime.backgroundRefresh(ctx, options);
|
||||
return cached.value;
|
||||
}
|
||||
|
||||
if (decision.action === CACHE_DECISION_ACTION_DELETE_AND_FETCH) {
|
||||
runtime.emitForContext(CACHE_EVENT_SCHEMA_MISMATCH, ctx, { reason: decision.reason });
|
||||
await runtime.deletion.safeDelete(ctx.fullKey, ctx.keyHash);
|
||||
}
|
||||
} else {
|
||||
runtime.emitForContext(CACHE_EVENT_MISS, ctx, { reason: CACHE_DECISION_REASON_CACHE_MISS });
|
||||
}
|
||||
|
||||
return runtime.singleflight.run(ctx.fullKey, async () => {
|
||||
try {
|
||||
return await runtime.io.fetchAndStore(ctx, options);
|
||||
} catch (error) {
|
||||
if (cached && canServeCacheStaleIfError(cached, runtime.now())) {
|
||||
runtime.emitForContext(CACHE_EVENT_STALE_IF_ERROR, ctx, {
|
||||
reason: CACHE_CONTEXT_REASON_FETCH_ERROR,
|
||||
error
|
||||
});
|
||||
return cached.value;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
Loading…
Reference in new issue