/** * A {@link Preloader} subclass that transparently caches 1:1 (`belongsTo`/`hasOne`) * relation loads through an externally-supplied cache service, keyed canonically * by the related row's identity and tagged for write-through invalidation. * * Both relation-loading paths funnel through Lucid's `Preloader` * (`processRelation` for a single parent, `processRelationForMany` for eager * loads across many parents), so overriding them here covers `model.load()` and * `Model.query().preload()` alike (ADR-001). */ import { Preloader } from '@adonisjs/lucid/orm'; import type { LucidModel } from '@adonisjs/lucid/types/model'; import type { RelationCacheEntryOptions } from "./cache_service_resolver"; /** * Per-call cache controls recorded by `preloadCached(name, options, cb?)`. * @internal */ export interface RelationCacheCallOptions extends RelationCacheEntryOptions { /** Bypass the cache read but still write the fresh result back. */ forceLoad?: boolean; } /** * Caching extension of Lucid's `Preloader`. * @internal */ export declare class CachingPreloader extends Preloader { #private; constructor(model: LucidModel, relationsCache: Map, callOptions?: Map); /** * Eagerly registers every configured cacheable 1:1 relation's related-model * identity name as a relation-cache TARGET (and validates `belongsTo` key * consistency up front). Registering from the DECLARED config ensures the * write-through eviction gate knows the full target set. Before registering, it * validates (once per model) that every `relationsCache` key is a real relation * name (see {@link CachingPreloader.#assertRelationsCacheKeysAreRelations}). * * IMPORTANT: this is also invoked process-globally at host boot (via the * exported `registerRelationCacheTargetsForModel`), NOT only from a constructed * preloader — a preloader is built lazily on first `.query()`/`.load()` of the * declaring parent, which a credential/rate worker process may never call. Boot * registration makes the target set process-global so a "cold" writer (e.g. a * rates-sync command that only ever updates `Currency`) still evicts shared L2 * entries another process cached. * * @param model - The model declaring the relations (reads `$resourcefulRelationsCache`). */ static registerCacheTargets(model: LucidModel): void; /** * Records per-call cache controls for a relation (used by the builder method * `preloadCached`) so the next `processRelation`/`processRelationForMany` * applies them. * * @param name - The relation name. * @param options - The per-call cache controls. */ recordCallOptions(name: string, options: RelationCacheCallOptions): void; /** * Clones the preloader, preserving its concrete type and cache configuration * so cloned queries (e.g. pagination's count clone) keep caching by * construction (ADR-001). Uses `this.constructor` so further subclasses clone * to their own type rather than silently degrading to the base. * * @returns A new preloader of the same concrete type carrying the same state. */ clone(): Preloader; } /** * Registers a model's configured cacheable 1:1 relations as process-global cache * TARGETS, independent of ever constructing a {@link CachingPreloader} for it. * * The host application calls this at boot for every model that declares a * `relationsCache`, so the write-through eviction gate * (`isRelationCacheTarget`) is correct in EVERY process — including a "cold" * worker (e.g. a rates-sync command) that only writes a target row (`Currency`) * and never queries the declaring parent (`PaymentIntentTicket`). Without this, * such a process would skip `deleteByTag` and leave stale entries in a shared L2 * for other processes until the TTL elapsed. * * Booting the model's relations + validating belongsTo key consistency happens * here too, so a misconfiguration fails loud at boot rather than at first load. * * @param model - A model that may declare a `relationsCache`. * @public */ export declare const registerRelationCacheTargetsForModel: (model: LucidModel) => void;