import { ConfigError } from 'effect'; import { Context } from 'effect'; import { Effect } from 'effect'; import { Layer } from 'effect'; import { Option } from 'effect'; import { Schema } from 'effect'; import { Scope } from 'effect'; import { Stream } from 'effect'; export declare class Cache extends Cache_base { } declare const Cache_base: Effect.Service.Class(key: string) => Effect.Effect, CacheError>; readonly set: (key: string, value: A, options?: CacheSetOptions) => Effect.Effect; readonly setWithTags: (key: string, value: A, options: CacheSetWithTagsOptions) => Effect.Effect; /** Write-through with the SAME storage shape `wrap` uses (incl. the * SWR envelope) — so a value written here is read back identically * by a later `wrap`. The dispatcher uses this to keep cached * snapshots warm from its live recompute. */ readonly put: (key: string, options: WrapOptions, value: A) => Effect.Effect; readonly remove: (key: string) => Effect.Effect; readonly has: (key: string) => Effect.Effect; readonly clear: () => Effect.Effect; readonly invalidateTag: (tag: string) => Effect.Effect; readonly wrap: (key: string, options: WrapOptions, compute: Effect.Effect) => Effect.Effect; /** A named, key-prefixed partition with its own default ttl/swr/tags. * Keys become `:`; per-call options override the defaults; * tags stay global (unprefixed) so bus invalidation still reaches * partitioned entries. */ readonly partition: (name: string, defaults?: PartitionDefaults) => CachePartition; /** Read the hit/miss counters + derived hit-rate (0–1). Drives the * dashboard cache panel. */ readonly stats: () => Effect.Effect<{ hits: number; misses: number; hitRate: number; }>; }, never, CacheStore>; }>; export declare class CacheError extends CacheError_base { } declare const CacheError_base: Schema.TaggedErrorClass; } & { /** Which op failed: 'get' | 'set' | 'setWithTags' | 'remove' | 'has' | * 'clear' | 'invalidateTag' | 'connect' | 'serialize' | 'deserialize'. */ operation: typeof Schema.String; /** The cache key (or tag) involved; '' for clear / connect. */ key: typeof Schema.String; /** `String(originalError)` — round-trip-safe diagnostic form. */ cause: typeof Schema.String; }>; export declare namespace CacheLayer { export { storeLayer, layer } } /** * A named, key-prefixed VIEW over the cache — one logical namespace per * concern (`sessions`, `reports`, …) with its own default TTL/SWR. Keys are * prefixed `:`, so partitions can't collide, and per-call options * override the partition defaults. * * Tags are NOT prefixed — they stay global on purpose, so a table-change * invalidation through the bus still drops matching entries in every * partition. A partition shares the parent cache's BACKEND; to put a concern * on a different backend/server, configure it as its own concern (e.g. the * durable `Kv` service, or a separately-provided cache) — the connection * registry lets each point at the same or a different Redis. */ export declare interface CachePartition { get(key: string): Effect.Effect, CacheError>; set(key: string, value: A, options?: CacheSetOptions): Effect.Effect; has(key: string): Effect.Effect; remove(key: string): Effect.Effect; /** Cache-aside within this partition. Partition defaults fill any option the * call omits; tags stay global (unprefixed). */ wrap(key: string, options: WrapOptions, compute: Effect.Effect): Effect.Effect; /** Invalidate a (global, unprefixed) tag — shared across partitions. */ invalidateTag(tag: string): Effect.Effect; } export declare interface CacheSetOptions { /** Time-to-live in milliseconds. Omit for no expiry (entry lives until * evicted, capacity-pressured, or invalidated by tag). */ readonly ttlMs?: number; } export declare interface CacheSetWithTagsOptions extends CacheSetOptions { /** Tags this entry belongs to. `invalidateTag(tag)` drops every entry * carrying that tag. */ readonly tags?: ReadonlyArray; } export declare class CacheStore extends CacheStore_base { } declare const CacheStore_base: Context.TagClass; export declare interface CacheStoreShape { /** Read a value. `None` on miss OR expiry (expired entries are lazily * evicted on read). */ readonly get: (key: string) => Effect.Effect, CacheError>; /** Write a value with an optional ms TTL. Overwrites any existing entry * (clearing its old tag memberships). */ readonly set: (key: string, value: unknown, options?: CacheSetOptions) => Effect.Effect; /** Write a value AND register it under the given tags. */ readonly setWithTags: (key: string, value: unknown, options: CacheSetWithTagsOptions) => Effect.Effect; /** Delete one key (and its tag memberships). Resolves `true` if it existed. */ readonly remove: (key: string) => Effect.Effect; /** Existence check honouring expiry, without deserializing the value. */ readonly has: (key: string) => Effect.Effect; /** Drop every entry in this cache's namespace. Scoped to the backend's * key-prefix — never a global flush on a shared server. */ readonly clear: () => Effect.Effect; /** Delete every key registered under `tag`, then drop the tag index. * Resolves the count of keys removed. */ readonly invalidateTag: (tag: string) => Effect.Effect; } /** A minimal, transport-agnostic change signal. Structurally a subset of the * runtime's `ChangeEvent` (`{ table, op, new/old }`) so a boot-site wire-up is * `store.onChange(e => cache.onSourceChange({ table: e.table, op: e.op, id: * (e.new ?? e.old)?.id }))` — no import of `@voltro/runtime` here. */ export declare interface ChangeSignal { readonly table: string; readonly op: 'insert' | 'update' | 'delete'; /** The changed row's primary key, when known. Absent for an aggregate/table * signal that only knows the table (e.g. the `InvalidationBus`, which * carries the table name only). */ readonly id?: string; } /** * Cosine similarity of two vectors, in [-1, 1]. Returns 0 when either vector * is zero-length or empty (no meaningful angle). Kept local — `@voltro/cache` * must not depend on `@voltro/ai` (the layering is ai → cache), so it can't * reuse the AI SDK's helper. */ export declare const cosineSimilarity: (a: ReadonlyArray, b: ReadonlyArray) => number; /** * The dependency tags a change should evict. * * - `update` / `delete`: the specific row (`row:t:id`, when the id is known) * AND the table (`table:t`) — so both a row-precise answer and a * table-coarse aggregate answer drop. * - `insert`: the table only. A brand-new row was never read by any cached * answer, so no `row:` tag could name it; only table-coarse answers (lists, * counts, "latest") can be stale, and those carry `table:t`. */ export declare const depsForChange: (c: ChangeSignal) => ReadonlyArray; export declare class InvalidationBus extends InvalidationBus_base { } declare const InvalidationBus_base: Effect.Service.Class Effect.Effect; readonly changes: Stream.Stream; readonly addSink: (handle: (table: string) => Effect.Effect) => Effect.Effect; }, never, Scope.Scope>; }>; /** * `Layer` whose backend is chosen from the environment. Provide * this in the app's layer stack so handlers can `yield* Cache`. */ declare const layer: Layer.Layer; /** `Layer` backed by an in-process `Ref`. */ export declare const layerMemory: (options?: MemoryCacheOptions) => Layer.Layer; /** `Layer` backed by a RESP server (Redis/Valkey/KeyDB/Dragonfly/Upstash). */ export declare const layerRedis: (options: RedisCacheOptions) => Layer.Layer; /** * Build a {@link SemanticCacheShape} over a resolved {@link CacheStoreShape}. * The store holds values + TTL + the tag index; the returned object adds the * in-process vector index + eviction logic on top. Exposed as a plain function * (in addition to the {@link SemanticCache} service) so it's unit-testable * without a Layer. */ export declare const makeSemanticCache: (store: CacheStoreShape, _options?: SemanticCacheOptions) => Effect.Effect; export declare interface MemoryCacheOptions { /** Hard cap on live entry count. When exceeded, oldest-by-insertion * entries are evicted — the backstop for entries that expired but were * never read (lazy eviction never fired). Omit for unbounded. */ readonly maxEntries?: number; /** Injectable clock — a plain `() => epoch-ms` used for TTL expiry instead * of Effect's ambient `Clock.currentTimeMillis`. Lets a caller drive * expiry off a controlled clock (e.g. a test harness whose own frozen * clock isn't Effect's `TestClock`) without standing up an Effect runtime * + custom `Clock` layer. Omit to use the ambient Effect clock (the * default — `TestClock` still drives it under Effect tests). */ readonly now?: () => number; } /** Per-partition default `WrapOptions`, applied when a call omits them. */ export declare interface PartitionDefaults { readonly ttlMs?: number; readonly swrMs?: number; readonly tags?: ReadonlyArray; } export declare interface RedisCacheOptions { readonly driver: 'resp' | 'http'; /** Connection URL (`redis://…` / `rediss://…` for RESP, the REST URL for http). */ readonly url: string; /** Auth token — http (Upstash REST) driver only. */ readonly token?: string; readonly keyPrefix?: string; } /** The dependency tag for one source row. An entry that read `docs#123` tags * itself `row:docs:123`; a change to that row evicts it precisely. */ export declare const rowDep: (table: string, id: string) => string; /** * The semantic cache as an Effect service, built over whatever `CacheStore` * layer (memory / RESP) is provided. Its `Default` layer requires a * `CacheStore` in context — mirror of how `Cache` is built on `CacheStore`. * * ```ts * const layer = SemanticCache.Default.pipe(Layer.provide(CacheLayer.layer)) * ``` */ export declare class SemanticCache extends SemanticCache_base { } declare const SemanticCache_base: Effect.Service.Class; }>; export declare interface SemanticCacheOptions { /** Default similarity threshold when a `lookup` omits one. Default 0.95. */ readonly defaultThreshold?: number; } /** The semantic-cache operations. Every method is Effect-first; failures are * the underlying `CacheStore`'s `CacheError` (the cache can be made * best-effort with `Effect.catchTag('CacheError', …)`). */ export declare interface SemanticCacheShape { /** * Find a cached value whose stored embedding is within `threshold` cosine * similarity of `embedding`. Returns the CLOSEST such value that is still * live in the store; `None` on a miss. Entries whose store value has expired * or been tag-evicted are pruned from the index as they're encountered. */ readonly lookup: (embedding: ReadonlyArray, options: SemanticLookupOptions) => Effect.Effect, CacheError>; /** Store `value` keyed by `embedding`, tagged with its dependency set. */ readonly put: (embedding: ReadonlyArray, value: A, options: SemanticPutOptions) => Effect.Effect; /** Evict every entry carrying any of `deps`. Returns the count removed from * the index. Drives the store side through the existing `invalidateTag`. */ readonly evict: (deps: ReadonlyArray) => Effect.Effect; /** Evict everything a data change invalidates — {@link depsForChange} applied * to the signal, then {@link evict}. This is the row-granular entrypoint the * boot site wires to `store.onChange`. */ readonly onSourceChange: (change: ChangeSignal) => Effect.Effect; /** Table-granular eviction — the entrypoint wireable to the EXISTING * `InvalidationBus` (which carries only the table name) with zero runtime * edits: `bus.addSink(table => semantic.onTableChange(table))`. */ readonly onTableChange: (table: string) => Effect.Effect; /** Drop every entry (index + store namespace). */ readonly clear: () => Effect.Effect; /** Hit/miss counters + live index size, for inspection and the COGS story * (each hit is one LLM generation — and its tokens — avoided). */ readonly stats: () => Effect.Effect; } export declare interface SemanticCacheStats { readonly hits: number; readonly misses: number; /** Live entries in the vector index. */ readonly size: number; } export declare interface SemanticLookupOptions { /** Minimum cosine similarity ([-1, 1]) for a hit. Typical: 0.9–0.98. A query * whose best-matching stored vector is below this MISSes. */ readonly threshold: number; } export declare interface SemanticPutOptions { /** The dependency set — the source rows/tables this answer read. Stored as * `CacheStore` tags; a change to any of them evicts this entry. Build them * with {@link rowDep} / {@link tableDep} (the AI wrapper does this from the * reads it records). */ readonly deps: ReadonlyArray; /** TTL floor in ms — the entry expires after this even if nothing it depends * on changes. Omit for "live until a dependency changes". */ readonly ttlMs?: number; /** Override the content key (defaults to a hash of the embedding). Pass a * stable prompt-derived id to make an identical prompt overwrite in place. */ readonly key?: string; } /** * `Layer` whose backend (memory | redis) + connection are chosen * from the environment. This is the STORE — value/TTL/tag storage — one level * below `Cache`. Exposed so a caller can build MORE than one service on ONE * store instance: `Layer.provide(storeLayer)` on a merged layer memoises a * SINGLE `CacheStore`, shared by every consumer in it (that is how the query * `Cache` and a `SemanticCache` end up over the same backend — see * `cli/src/cacheFacade.ts`). */ declare const storeLayer: Layer.Layer; /** The dependency tag for a whole table. Coarser than {@link rowDep}: it also * catches INSERTs (a new matching row the cached answer never read, so no * per-row tag could have named it) and list/aggregate answers. */ export declare const tableDep: (table: string) => string; export declare interface WrapOptions { /** Fresh window in ms. After this the entry is stale (or gone, if no swr). */ readonly ttlMs?: number; /** Stale-while-revalidate window in ms past `ttlMs`. While stale, the * cached value is served immediately AND a background refresh runs. */ readonly swrMs?: number; /** Tags for `invalidateTag` — typically the tables the compute reads. */ readonly tags?: ReadonlyArray; } export { }