import { CrossFamilyMiddleware } from "@prisma-next/framework-components/runtime"; //#region src/cache-annotation.d.ts /** * Payload accepted when calling the `cacheAnnotation` handle. * * - `ttl` — Time-to-live for the cached entry, in milliseconds. When * omitted, the cache middleware passes the query through untouched — * presence of the annotation alone is not sufficient to enable caching. * This makes the cache strictly opt-in per query. * - `skip` — When `true`, the cache middleware passes the query through * untouched even if a `ttl` is set. Useful for selectively bypassing * the cache on a per-call basis without removing the annotation * entirely (e.g. a "force refresh" knob in user code). * - `key` — Per-query override of the cache key. When supplied, replaces * the default `RuntimeMiddlewareContext.contentHash(exec)` digest. * The supplied string is stored as-is — the cache middleware does * **not** rehash it, so the caller is responsible for ensuring the * string is bounded in size and free of sensitive data they do not * want flowing into logs / Redis `KEYS` / persistence dumps. */ interface CachePayload { readonly ttl?: number; readonly skip?: boolean; readonly key?: string; } /** * Read-only annotation handle for the cache middleware. * * Declared with `applicableTo: ['read']`. Write terminals supply * `K = 'write'` to the type-level `ValidAnnotations<'write', As>` gate * (and the runtime `assertAnnotationsApplicable(annotations, 'write', ...)` * check); the join `K extends Kinds` fails for this annotation, making * "cache a mutation" structurally impossible without an `as any` cast * bypass at both type *and* runtime levels. * * Stored under namespace `'cache'` in `plan.meta.annotations`. The cache * middleware reads it via `cacheAnnotation.read(plan)`. * * @example * ```typescript * import { cacheAnnotation } from '@prisma-next/middleware-cache'; * * // ORM read terminal — accepts the read-only annotation via the meta callback. * const user = await db.User.first( * { id }, * (meta) => meta.annotate(cacheAnnotation({ ttl: 60_000 })), * ); * * // SQL DSL select builder — chainable. * const plan = db.sql * .from(tables.user) * .annotate(cacheAnnotation({ ttl: 60_000 })) * .select({ id: tables.user.columns.id }) * .build(); * ``` */ declare const cacheAnnotation: import("@prisma-next/framework-components/runtime").AnnotationHandle; //#endregion //#region src/cache-store.d.ts /** * A cached set of rows produced by a single execution. * * - `rows` are stored raw (undecoded). The SQL runtime's `decodeRow` pass * wraps the orchestrator output, so intercepted rows go through the * same codec decoding as driver rows on the way to the consumer. The * cache stores wire-format values; decoding happens once per consumer * read regardless of where the rows came from. * - `storedAt` is the clock value at the moment the entry was committed * to the store. It is informational metadata for callers (debugging, * telemetry) and is **not** used by the in-memory store itself for * expiry — TTL is driven by the store's own clock plus the `ttlMs` * passed to `set`. Custom stores may use it differently. */ interface CachedEntry { readonly rows: readonly Record[]; readonly storedAt: number; } /** * Pluggable cache backend used by the cache middleware. * * The default implementation is an in-memory LRU with TTL produced by * `createInMemoryCacheStore`. Users can supply Redis, Memcached, or any * other backend by implementing this interface. * * The interface is intentionally minimal: * * - `get` returns the entry if it exists and has not expired, or * `undefined` otherwise. Implementations that gate on TTL should * treat an expired entry as absent (return `undefined`) and may * evict it as a side effect. * - `set` writes the entry under the key with an associated TTL in * milliseconds. Implementations may evict other entries to make * room (LRU, LFU, etc.) and may treat the operation as fire-and- * forget at scale; the cache middleware does not rely on `set` * completing before subsequent `get`s. * * Both methods are async to leave the door open for I/O-backed stores * (Redis, S3, etc.). The default in-memory store completes * synchronously and wraps the result in `Promise.resolve` for type * conformance. */ interface CacheStore { get(key: string): Promise; set(key: string, entry: CachedEntry, ttlMs: number): Promise; } /** * Options accepted by `createInMemoryCacheStore`. * * - `maxEntries` — hard cap on the number of live entries. Once the cap * is exceeded, the least recently used entry is evicted. Reads and * writes both count as "uses" for ordering purposes. * - `clock` — injectable time source for TTL math. Defaults to * `Date.now`. Tests inject a controlled clock to verify expiry without * real-time waits. */ interface InMemoryCacheStoreOptions { readonly maxEntries: number; readonly clock?: () => number; } /** * Default cache backend. An LRU with per-entry TTL, backed by a `Map`. * * Eviction policy: * * - On `set` of a fresh key whose insertion would push the live count * above `maxEntries`, the least recently used entry is evicted. * Setting an existing key updates the entry in place and refreshes its * recency without changing the live count. * - On `get` of an existing key, recency is bumped (so the entry is no * longer the LRU candidate). * - On `get` of an expired entry, the entry is removed from the map and * `undefined` is returned. The slot becomes available for new writes * without counting against `maxEntries`. * * `Map` insertion order is the LRU order: the first key is the LRU * candidate; the last key is the most recently used. Bumping recency is * a delete-then-set on the underlying map. * * The default store is **not** coherent across processes or replicas — * each process holds its own Map. Users who need a shared cache supply * their own `CacheStore` (Redis, Memcached, etc.). */ declare function createInMemoryCacheStore(options: InMemoryCacheStoreOptions): CacheStore; //#endregion //#region src/cache-middleware.d.ts /** * Options accepted by `createCacheMiddleware`. * * - `store` — pluggable cache backend. Defaults to an in-process LRU * produced by `createInMemoryCacheStore`. Users supply Redis, * Memcached, or any other backend by implementing the `CacheStore` * interface. * - `maxEntries` — only consulted when `store` is omitted. Sets the * `maxEntries` cap on the default in-memory store. Defaults to 1000. * - `clock` — injectable time source for `storedAt` stamping on * committed entries. Defaults to `Date.now`. Tests inject a controlled * clock to make commit-time observable. Note: TTL math lives inside * the store, not the middleware — supplying a clock here only affects * the `storedAt` field on committed `CachedEntry` values. */ interface CacheMiddlewareOptions { readonly store?: CacheStore; readonly maxEntries?: number; readonly clock?: () => number; } /** * Creates a family-agnostic caching middleware. * * The middleware uses three hooks: * * - `intercept` — on each execution, checks the cache. On a hit, returns * the cached raw rows; the runtime skips `runDriver` and `onRow` * (`beforeExecute` is not affected — it has already run for every * middleware before any `intercept` is consulted) and yields the * cached rows to the consumer (which, in the SQL runtime, sees them * after the standard `decodeRow` pass — i.e. the cache stores * wire-format values). On a miss, records a pending buffer keyed on * the `exec` object identity and returns `undefined` (passthrough). * - `onRow` — on the miss path, appends each row yielded by the driver * to the pending buffer. * - `afterExecute` — on the miss path, commits the buffer to the store * if and only if `result.completed === true && result.source === 'driver'`. * Failed executions and middleware-served executions never populate * the cache. The pending buffer is cleared in all branches so a stale * `WeakMap` entry cannot leak between executions sharing an `exec`. * * The middleware bypasses the cache entirely when: * - the plan has no `cache` annotation, or * - the annotation has `skip: true`, or * - the annotation has no `ttl`, or * - `ctx.scope !== 'runtime'` (connection / transaction scopes opt out). * * Returns a cross-family `RuntimeMiddleware` (no `familyId` / * `targetId`). The package depends only on * `@prisma-next/framework-components/runtime`; cache keys come from * `ctx.contentHash(exec)`, populated by the family runtime, so SQL and * Mongo runtimes both work out of the box. * * @example * ```typescript * import { createCacheMiddleware, cacheAnnotation } from '@prisma-next/middleware-cache'; * * const db = postgres({ * contractJson, * url: process.env['DATABASE_URL']!, * middleware: [createCacheMiddleware({ maxEntries: 1000 })], * }); * * const user = await db.User.first( * { id }, * (meta) => meta.annotate(cacheAnnotation({ ttl: 60_000 })), * ); * ``` */ declare function createCacheMiddleware(options?: CacheMiddlewareOptions): CrossFamilyMiddleware; //#endregion export { type CacheMiddlewareOptions, type CachePayload, type CacheStore, type CachedEntry, type InMemoryCacheStoreOptions, cacheAnnotation, createCacheMiddleware, createInMemoryCacheStore }; //# sourceMappingURL=index.d.mts.map