From 000178fdc46a42bf9070d96f0c8d69bcfdad96d0 Mon Sep 17 00:00:00 2001 From: dev Date: Wed, 29 Apr 2026 20:20:27 +0200 Subject: [PATCH] Extract cache query runtime --- NEXT_STEPS.md | 1 + src/libs/cach/engine.ts | 110 ++++------------------------- src/libs/cach/runtime-query.ts | 123 +++++++++++++++++++++++++++++++++ 3 files changed, 139 insertions(+), 95 deletions(-) create mode 100644 src/libs/cach/runtime-query.ts diff --git a/NEXT_STEPS.md b/NEXT_STEPS.md index 3d75d6c..91b66a2 100644 --- a/NEXT_STEPS.md +++ b/NEXT_STEPS.md @@ -35,6 +35,7 @@ Estado al cierre: - `arts/sium/core/pipe.ts` queda centrado en composicion; factories `refine/transform/codec/meta` viven en `steps.ts`. - `libs/cach/engine.ts` delega lectura `get()` en `runtime-get.ts`. - `libs/cach/engine.ts` delega escritura `set()` en `runtime-set.ts`. + - `libs/cach/engine.ts` delega `query()` en `runtime-query.ts`; conserva composition root para context/io/delete/invalidate/mutate/explain. - `arts/conn/connection.ts` delega decode/routing de frames entrantes en `connection-message-router.ts`. - `arts/conn/connection.ts` delega el intento open/auth/flush/join en `connection-connect.ts`. - `arts/conn/connection.ts` delega current transport, attach/detach y close en `connection-transport-runtime.ts`. diff --git a/src/libs/cach/engine.ts b/src/libs/cach/engine.ts index 01f8e44..79e4669 100644 --- a/src/libs/cach/engine.ts +++ b/src/libs/cach/engine.ts @@ -1,43 +1,23 @@ import { - CACHE_CONTEXT_REASON_FETCH_ERROR, - CACHE_CONTEXT_REASON_GET, CACHE_CONTEXT_REASON_JOINED_EXISTING_FETCH, - 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_DEFAULT_NAMESPACE, CACHE_DEFAULT_VERSION, - CACHE_EVENT_ADAPTER_ERROR, - CACHE_EVENT_HIT, - CACHE_EVENT_MISS, - CACHE_EVENT_SCHEMA_MISMATCH, - CACHE_EVENT_SINGLEFLIGHT_JOIN, - CACHE_EVENT_STALE_HIT, - CACHE_EVENT_STALE_IF_ERROR, - CACHE_READ_MODE_BYPASS_CACHE, - CACHE_READ_MODE_NO_STORE, - CACHE_READ_MODE_STALE_WHILE_REVALIDATE + CACHE_EVENT_SINGLEFLIGHT_JOIN } from './consts.ts'; import { CacheEvents } from './events.ts'; -import { normalizeTags } from './key.ts'; import { resolveCacheContext, type CacheContextInput, type ResolvedCacheContext } from './runtime-context.ts'; import { createCacheRuntimeDelete } from './runtime-delete.ts'; -import { - canServeCacheStaleIfError, - evaluateCacheEntry -} from './runtime-evaluation.ts'; +import { evaluateCacheEntry } from './runtime-evaluation.ts'; import { readCacheCurrentEpochs } from './runtime-epochs.ts'; import { buildCacheHitExplain, buildCacheMissExplain } from './runtime-explain.ts'; import { createCacheRuntimeGet } from './runtime-get.ts'; import { invalidateCacheEntry } from './runtime-invalidation.ts'; import { createCacheRuntimeIo } from './runtime-io.ts'; +import { createCacheRuntimeQuery } from './runtime-query.ts'; import { createCacheRuntimeSet } from './runtime-set.ts'; import { systemCacheClock } from './runtime-helpers.ts'; import { runCacheMutation } from './runtime-mutation.ts'; @@ -135,78 +115,18 @@ export function createCacheRuntime(config: CacheRuntimeConfig): CacheRuntime { }); } - async function query(options: QueryOptions): Promise { - const ctx = await 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; - emitForContext(CACHE_EVENT_MISS, ctx, { reason }); - const value = await options.fetcher(); - if (ctx.policy.mode === CACHE_READ_MODE_BYPASS_CACHE) { - const envelope = await io.createEnvelope(value, ctx, normalizeTags(options.tags)); - await io.writeEnvelope(ctx, envelope); - } - return value; - } - - let cached: CacheEnvelope | null = null; - - try { - cached = await adapter.get(ctx.fullKey); - } catch (error) { - emit(CACHE_EVENT_ADAPTER_ERROR, { - key: ctx.fullKey, - keyHash: ctx.keyHash, - error, - reason: CACHE_CONTEXT_REASON_GET - }); - throw error; - } - - if (cached) { - const decision = await evaluate(cached, ctx); - - if (decision.action === CACHE_DECISION_ACTION_SERVE) { - emitForContext(CACHE_EVENT_HIT, ctx, { reason: decision.reason }); - return cached.value; - } - - if (decision.action === CACHE_DECISION_ACTION_SERVE_AND_REFRESH) { - emitForContext(CACHE_EVENT_STALE_HIT, ctx, { reason: decision.reason }); - await backgroundRefresh(ctx, options); - return cached.value; - } - - if (decision.action === CACHE_DECISION_ACTION_DELETE_AND_FETCH) { - emitForContext(CACHE_EVENT_SCHEMA_MISMATCH, ctx, { reason: decision.reason }); - await deletion.safeDelete(ctx.fullKey, ctx.keyHash); - } - } else { - emitForContext(CACHE_EVENT_MISS, ctx, { reason: CACHE_DECISION_REASON_CACHE_MISS }); - } - - return singleflight.run(ctx.fullKey, async () => { - try { - return await io.fetchAndStore(ctx, options); - } catch (error) { - if (cached && canServeCacheStaleIfError(cached, clock.now())) { - emitForContext(CACHE_EVENT_STALE_IF_ERROR, ctx, { - reason: CACHE_CONTEXT_REASON_FETCH_ERROR, - error - }); - return cached.value; - } - throw error; - } - }); - } - + const query = createCacheRuntimeQuery({ + adapter, + deletion, + io, + singleflight, + now: () => clock.now(), + contextFor, + evaluate, + backgroundRefresh, + emit, + emitForContext + }); const get = createCacheRuntimeGet({ adapter, deletion, contextFor, evaluate, emitForContext }); const set = createCacheRuntimeSet({ io, contextFor }); diff --git a/src/libs/cach/runtime-query.ts b/src/libs/cach/runtime-query.ts new file mode 100644 index 0000000..019df78 --- /dev/null +++ b/src/libs/cach/runtime-query.ts @@ -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; + evaluate(entry: CacheEnvelope, ctx: ResolvedCacheContext): Promise; + backgroundRefresh(ctx: ResolvedCacheContext, options: QueryOptions): Promise; + emit(type: CacheEventType, patch?: Partial): void; + emitForContext( + type: CacheEventType, + ctx: ResolvedCacheContext, + patch?: Partial + ): void; +} + +export function createCacheRuntimeQuery(runtime: CacheRuntimeQueryInput) { + return async function query(options: QueryOptions): Promise { + 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 | null = null; + + try { + cached = await runtime.adapter.get(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; + } + }); + }; +}