@break-limits/mongoose-cache 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/plugins/createCache.ts","../src/cache/RedisCacheStore.ts","../src/cache/Serializer.ts","../src/invalidation/RedisDependencyIndex.ts","../src/cache/fingerprint.ts","../src/invalidation/PredicateMatcher.ts","../src/utils/id.ts","../src/invalidation/InvalidationEngine.ts","../src/utils/hash.ts","../src/cache/CacheKey.ts","../src/cache/CacheManager.ts","../src/invalidation/TierClassifier.ts","../src/cache/CacheStore.ts","../src/invalidation/DependencyIndex.ts"],"sourcesContent":["// Public surface (Phase 1 core).\n\nexport {\n createCache,\n type Cache,\n type CreateCacheOptions,\n type ModelConfig,\n} from \"./plugins/createCache.js\";\nexport { RedisCacheStore } from \"./cache/RedisCacheStore.js\";\nexport { RedisDependencyIndex } from \"./invalidation/RedisDependencyIndex.js\";\n\nexport { normalizeQuery } from \"./cache/fingerprint.js\";\nexport { stableHash } from \"./utils/hash.js\";\nexport {\n buildQueryKey,\n buildDocKey,\n type CacheKeyInput,\n} from \"./cache/CacheKey.js\";\nexport { serialize, deserialize } from \"./cache/Serializer.js\";\nexport {\n CacheManager,\n type CachedQuery,\n} from \"./cache/CacheManager.js\";\nexport {\n type CacheStore,\n InMemoryCacheStore,\n} from \"./cache/CacheStore.js\";\n\nexport {\n matches,\n isSupportedPredicate,\n type Filter,\n type Doc,\n} from \"./invalidation/PredicateMatcher.js\";\nexport {\n classifyQuery,\n type Tier,\n type ClassifyInput,\n} from \"./invalidation/TierClassifier.js\";\nexport {\n InvalidationEngine,\n type WriteReport,\n} from \"./invalidation/InvalidationEngine.js\";\nexport {\n type DependencyIndex,\n InMemoryDependencyIndex,\n type QueryMeta,\n type RegisteredQuery,\n} from \"./invalidation/DependencyIndex.js\";\n","import { EventEmitter } from \"node:events\";\nimport type { Mongoose, Model } from \"mongoose\";\nimport type { Redis } from \"ioredis\";\nimport { RedisCacheStore } from \"../cache/RedisCacheStore.js\";\nimport { RedisDependencyIndex } from \"../invalidation/RedisDependencyIndex.js\";\nimport { InvalidationEngine } from \"../invalidation/InvalidationEngine.js\";\nimport { CacheManager, type CachedQuery } from \"../cache/CacheManager.js\";\nimport type { CacheStore } from \"../cache/CacheStore.js\";\nimport { classifyQuery } from \"../invalidation/TierClassifier.js\";\nimport { buildQueryKey, buildAggregateKey } from \"../cache/CacheKey.js\";\nimport type { Doc, Filter } from \"../invalidation/PredicateMatcher.js\";\nimport { idToString } from \"../utils/id.js\";\n\nexport interface ModelConfig {\n ttlMs?: number;\n enabled?: boolean;\n}\n\nexport interface CreateCacheOptions {\n mongoose: Mongoose;\n redis: Redis;\n /** Override the storage backend (e.g. for testing). Defaults to Redis. */\n store?: CacheStore;\n /** Opt-in model map. Omit to cache every currently-registered model. */\n models?: Record<string, ModelConfig>;\n defaults?: { ttlMs?: number };\n /** Resolve the current tenant id for keyspace isolation. */\n tenant?: () => string | undefined;\n}\n\nexport interface Cache extends EventEmitter {\n /** Restore all patched Mongoose methods. */\n close(): void;\n}\n\n// Read ops we cache. Point/predicate reads (find/findOne/countDocuments) are\n// invalidated precisely; distinct/estimatedDocumentCount conservatively.\nconst CACHE_OPS = new Set([\n \"find\",\n \"findOne\",\n \"countDocuments\",\n \"distinct\",\n \"estimatedDocumentCount\",\n]);\nconst UPDATE_OPS = new Set([\n \"updateOne\",\n \"updateMany\",\n \"findOneAndUpdate\",\n \"replaceOne\",\n \"findOneAndReplace\",\n]);\nconst DELETE_OPS = new Set([\n \"deleteOne\",\n \"deleteMany\",\n \"findOneAndDelete\",\n]);\n\nexport function createCache(options: CreateCacheOptions): Cache {\n const { mongoose } = options;\n const emitter = new EventEmitter() as Cache;\n\n const store = options.store ?? new RedisCacheStore(options.redis);\n const index = new RedisDependencyIndex(options.redis);\n const engine = new InvalidationEngine(index);\n const manager = new CacheManager(store, engine, emitter);\n\n const registry = new Map<string, ModelConfig>();\n const modelNames = options.models\n ? Object.keys(options.models)\n : mongoose.modelNames();\n for (const name of modelNames) {\n registry.set(name, options.models?.[name] ?? {});\n }\n\n // Queries we issue internally (before/after image fetches) skip the cache.\n const bypass = new WeakSet<object>();\n const tenant = () => options.tenant?.();\n const ttlFor = (config: ModelConfig) => config.ttlMs ?? options.defaults?.ttlMs;\n const collectionOf = (model: Model<unknown>) => model.collection.collectionName;\n\n // ----- internal image fetches ---------------------------------------------\n async function fetchMatching(model: Model<unknown>, filter: Filter) {\n const q = model.find(filter).lean();\n bypass.add(q);\n return (await q.exec()) as Doc[];\n }\n async function fetchById(model: Model<unknown>, id: unknown) {\n const q = model.findOne({ _id: id }).lean();\n bypass.add(q);\n return (await q.exec()) as Doc | null;\n }\n\n // ----- write invalidation (precise + conservative) ------------------------\n async function invalidateWrite(\n model: Model<unknown>,\n pairs: Array<{ before: Doc | null; after: Doc | null }>,\n opts: { flushPrecise?: boolean } = {},\n ) {\n const modelName = model.modelName;\n if (pairs.length === 0) {\n // No imaged docs — still bump the version to close the read race window.\n await manager.onWrite(modelName, null, null);\n } else {\n for (const { before, after } of pairs) {\n await manager.onWrite(modelName, before, after);\n }\n }\n if (opts.flushPrecise) await manager.flushModelPrecise(modelName);\n // Conservative: drain everything tagged with this collection (T2/T3/etc.).\n await manager.invalidateCollection(collectionOf(model));\n }\n\n // ----- read handling ------------------------------------------------------\n async function handleRead(query: any, op: string, config: ModelConfig) {\n const filter = (query.getFilter() ?? {}) as Filter;\n const opts = query.getOptions() ?? {};\n const tier = classifyQuery({ op, filter, hasSession: !!opts.session });\n if (tier === \"T4\") return originalExec.apply(query, []);\n\n const model = query.model as Model<unknown>;\n const collection = collectionOf(model);\n const populatePaths = Object.keys(query._mongooseOptions?.populate ?? {});\n const hasPopulate = populatePaths.length > 0;\n\n // distinct depends on field values beyond set membership, and populate /\n // T2 filters can't be evaluated precisely — all use conservative tagging.\n const conservative =\n op === \"distinct\" ||\n op === \"estimatedDocumentCount\" ||\n hasPopulate ||\n tier === \"T2\";\n\n let tags: string[] | undefined;\n if (conservative) {\n tags = [collection];\n if (hasPopulate) {\n const popCols = resolvePopulateCollections(query, populatePaths);\n if (popCols === null) return originalExec.apply(query, []); // unresolved → don't cache\n tags = [collection, ...popCols];\n }\n }\n\n const lean = !!query._mongooseOptions?.lean;\n const key = buildQueryKey({\n model: model.modelName,\n op,\n filter,\n projection: query.projection?.() ?? null,\n sort: opts.sort ?? null,\n skip: opts.skip,\n limit: opts.limit,\n populate: hasPopulate ? populatePaths : null,\n distinct: query._distinct ?? null,\n tenant: tenant(),\n });\n\n const cached: CachedQuery = {\n key,\n model: model.modelName,\n tier,\n predicate: conservative ? undefined : filter,\n limited: opts.limit !== undefined && opts.limit !== null,\n tags,\n ttlMs: ttlFor(config),\n };\n\n const plain = await manager.getOrLoad(\n cached,\n async () => toPlain(await originalExec.apply(query, [])),\n extractIds,\n );\n\n return lean || hasPopulate ? plain : rehydrate(model, op, plain);\n }\n\n // ----- query write handling -----------------------------------------------\n async function handleWrite(query: any, op: string) {\n const model = query.model as Model<unknown>;\n const filter = (query.getFilter() ?? {}) as Filter;\n const opts = query.getOptions() ?? {};\n\n const beforeDocs = await fetchMatching(model, filter);\n const result = await originalExec.apply(query, []);\n\n const isDelete = DELETE_OPS.has(op);\n const pairs: Array<{ before: Doc | null; after: Doc | null }> = [];\n for (const before of beforeDocs) {\n const after = isDelete ? null : await fetchById(model, before._id);\n pairs.push({ before, after });\n }\n\n // An upsert may have created a document we never imaged — flush precisely.\n const flushPrecise = !!opts.upsert && beforeDocs.length === 0;\n await invalidateWrite(model, pairs, { flushPrecise });\n\n return result;\n }\n\n // ----- exec wrapper (queries) ---------------------------------------------\n const queryProto = (mongoose as unknown as { Query: { prototype: any } }).Query\n .prototype;\n const originalExec: (...args: unknown[]) => Promise<unknown> = queryProto.exec;\n\n function wrappedExec(this: any, ...args: unknown[]): Promise<unknown> {\n if (bypass.has(this)) return originalExec.apply(this, args);\n const config = this.model ? registry.get(this.model.modelName) : undefined;\n if (!config || config.enabled === false) return originalExec.apply(this, args);\n\n const op: string = this.op;\n if (CACHE_OPS.has(op)) return handleRead(this, op, config);\n if (UPDATE_OPS.has(op) || DELETE_OPS.has(op)) return handleWrite(this, op);\n return originalExec.apply(this, args);\n }\n queryProto.exec = wrappedExec;\n\n const restores: Array<() => void> = [\n () => {\n queryProto.exec = originalExec;\n },\n ];\n\n // ----- aggregate interceptor ----------------------------------------------\n const aggProto = (mongoose as unknown as { Aggregate: { prototype: any } })\n .Aggregate.prototype;\n const originalAggExec: (...args: unknown[]) => Promise<unknown> = aggProto.exec;\n\n async function handleAggregate(agg: any, config: ModelConfig) {\n const model = agg._model as Model<unknown>;\n const pipeline: any[] = typeof agg.pipeline === \"function\" ? agg.pipeline() : agg._pipeline ?? [];\n\n // Write stages ($out/$merge) and session-bound aggregations are never cached.\n if (hasWriteStage(pipeline) || agg.options?.session) {\n return originalAggExec.apply(agg, []);\n }\n\n const tags = collectionsInPipeline(model, pipeline);\n const cached: CachedQuery = {\n key: buildAggregateKey(model.modelName, pipeline, tenant()),\n model: model.modelName,\n tier: \"T3\",\n tags,\n ttlMs: ttlFor(config),\n };\n return manager.getOrLoad(cached, async () => originalAggExec.apply(agg, []));\n }\n\n aggProto.exec = function wrappedAggExec(this: any, ...args: unknown[]) {\n const config = this._model ? registry.get(this._model.modelName) : undefined;\n if (!config || config.enabled === false) return originalAggExec.apply(this, args);\n return handleAggregate(this, config);\n };\n restores.push(() => {\n aggProto.exec = originalAggExec;\n });\n\n // ----- save / insertMany / bulkWrite interception -------------------------\n for (const name of registry.keys()) {\n const model = mongoose.models[name] as Model<unknown> | undefined;\n if (!model) continue;\n\n // `Model.create` routes through `$save`; `doc.save()` through `save`\n // (which delegates to `$save`). Patch both with a re-entrancy guard so\n // invalidation runs once per logical save.\n const proto = model.prototype as any;\n const saving = new WeakSet<object>();\n const makePatched =\n (original: (...args: unknown[]) => Promise<unknown>) =>\n async function patchedSave(this: any, ...a: unknown[]) {\n if (saving.has(this)) return original.apply(this, a);\n saving.add(this);\n try {\n const isNew = this.isNew;\n const before = isNew ? null : await fetchById(model, this._id);\n const res = await original.apply(this, a);\n const after = this.toObject() as Doc;\n await invalidateWrite(model, [{ before, after }]);\n return res;\n } finally {\n saving.delete(this);\n }\n };\n\n const originalSave = proto.save;\n proto.save = makePatched(originalSave);\n restores.push(() => {\n proto.save = originalSave;\n });\n if (typeof proto.$save === \"function\") {\n const original$save = proto.$save;\n proto.$save = makePatched(original$save);\n restores.push(() => {\n proto.$save = original$save;\n });\n }\n\n const originalInsertMany = model.insertMany.bind(model);\n (model as any).insertMany = async function patchedInsertMany(...a: unknown[]) {\n const res: any = await (originalInsertMany as any)(...a);\n const docs = Array.isArray(res) ? res : [res];\n await invalidateWrite(\n model,\n docs.map((d: any) => ({\n before: null,\n after: (typeof d?.toObject === \"function\" ? d.toObject() : d) as Doc,\n })),\n );\n return res;\n };\n restores.push(() => {\n (model as any).insertMany = originalInsertMany;\n });\n\n // bulkWrite mixes arbitrary ops we cannot image — flush the model's precise\n // registry and drain its collection tag (coarse but never stale).\n const originalBulkWrite = model.bulkWrite.bind(model);\n (model as any).bulkWrite = async function patchedBulkWrite(...a: unknown[]) {\n const res = await (originalBulkWrite as any)(...a);\n await invalidateWrite(model, [], { flushPrecise: true });\n return res;\n };\n restores.push(() => {\n (model as any).bulkWrite = originalBulkWrite;\n });\n }\n\n emitter.close = () => {\n for (const restore of restores) restore();\n };\n return emitter;\n}\n\n// ---------------------------------------------------------------------------\n// helpers\n// ---------------------------------------------------------------------------\n\nfunction hasWriteStage(pipeline: any[]): boolean {\n return pipeline.some((s) => s && (s.$out !== undefined || s.$merge !== undefined));\n}\n\nfunction collectionsInPipeline(model: Model<unknown>, pipeline: any[]): string[] {\n const cols = new Set<string>([model.collection.collectionName]);\n for (const stage of pipeline) {\n if (!stage) continue;\n if (stage.$lookup?.from) cols.add(stage.$lookup.from);\n if (stage.$graphLookup?.from) cols.add(stage.$graphLookup.from);\n if (stage.$unionWith) {\n cols.add(\n typeof stage.$unionWith === \"string\" ? stage.$unionWith : stage.$unionWith.coll,\n );\n }\n }\n return [...cols];\n}\n\nfunction resolvePopulateCollections(query: any, paths: string[]): string[] | null {\n const pop = query._mongooseOptions?.populate ?? {};\n const cols: string[] = [];\n for (const path of paths) {\n const opt = pop[path] ?? {};\n let ref: unknown = opt.model;\n if (typeof ref !== \"string\") {\n const sp = query.model.schema.path(path);\n ref = sp?.options?.ref ?? sp?.caster?.options?.ref;\n }\n if (typeof ref !== \"string\") return null; // dynamic/unresolvable ref → don't cache\n const refModel = query.model.db.models[ref];\n if (!refModel) return null;\n cols.push(refModel.collection.collectionName);\n }\n return cols;\n}\n\nfunction toPlain(result: unknown): unknown {\n if (Array.isArray(result)) return result.map((d) => toPlainDoc(d));\n return toPlainDoc(result);\n}\n\nfunction toPlainDoc(doc: unknown): unknown {\n if (doc && typeof (doc as { toObject?: unknown }).toObject === \"function\") {\n return (doc as { toObject(): unknown }).toObject();\n }\n return doc;\n}\n\nfunction rehydrate(model: any, op: string, plain: unknown): unknown {\n if (op === \"find\") return (plain as unknown[]).map((o) => model.hydrate(o));\n if (op === \"findOne\") return plain ? model.hydrate(plain) : null;\n return plain;\n}\n\nfunction extractIds(plain: unknown): string[] {\n const collect = (d: unknown): string | undefined =>\n d && typeof d === \"object\" && \"_id\" in d\n ? idToString((d as { _id: unknown })._id)\n : undefined;\n if (Array.isArray(plain)) {\n return plain.map(collect).filter((s): s is string => s !== undefined);\n }\n const single = collect(plain);\n return single ? [single] : [];\n}\n","import type { Redis } from \"ioredis\";\nimport type { CacheStore } from \"./CacheStore.js\";\n\n/**\n * Redis-backed {@link CacheStore}. Implements the same contract proven against\n * the in-memory store, so all CacheManager correctness logic carries over.\n *\n * Phase 1 targets single-node correctness (in-process single-flight + a\n * per-model version counter). Phase 2 hardens the version guard into an atomic\n * Lua compare-and-set for multi-node deployments.\n */\nexport class RedisCacheStore implements CacheStore {\n constructor(\n private readonly redis: Redis,\n private readonly versionPrefix = \"ver:\",\n ) {}\n\n async get(key: string): Promise<Buffer | null> {\n // getBuffer preserves raw bytes — required for binary BSON payloads.\n return this.redis.getBuffer(key);\n }\n\n async set(key: string, value: Buffer, ttlMs?: number): Promise<void> {\n if (ttlMs !== undefined) {\n await this.redis.set(key, value, \"PX\", ttlMs);\n } else {\n await this.redis.set(key, value);\n }\n }\n\n async del(keys: string[]): Promise<void> {\n if (keys.length === 0) return;\n await this.redis.del(...keys);\n }\n\n async getVersion(model: string): Promise<number> {\n const raw = await this.redis.get(this.versionKey(model));\n return raw === null ? 0 : Number(raw);\n }\n\n async bumpVersion(model: string): Promise<number> {\n return this.redis.incr(this.versionKey(model));\n }\n\n async addToSet(setKey: string, member: string): Promise<void> {\n await this.redis.sadd(setKey, member);\n }\n\n async drainSet(setKey: string): Promise<string[]> {\n const members = await this.redis.smembers(setKey);\n if (members.length > 0) await this.redis.del(setKey);\n return members;\n }\n\n private versionKey(model: string): string {\n return `${this.versionPrefix}${model}`;\n }\n}\n","import { BSON } from \"bson\";\n\n/**\n * Lossless serialization of cache values. We never store hydrated Mongoose\n * documents — only plain (lean) data — but that data still contains BSON types\n * (ObjectId, Date, Decimal128, Buffer) that must survive a Redis round-trip.\n *\n * Values are wrapped (`{ v: value }`) before BSON encoding so that arrays and\n * primitives — not just documents — can be stored.\n */\nexport function serialize(value: unknown): Buffer {\n const encoded = BSON.serialize({ v: value });\n return Buffer.from(encoded);\n}\n\nexport function deserialize(buffer: Buffer): unknown {\n const decoded = BSON.deserialize(buffer, {\n promoteBuffers: true,\n promoteValues: true,\n });\n return (decoded as { v: unknown }).v;\n}\n","import type { Redis } from \"ioredis\";\nimport type { DependencyIndex, QueryMeta, RegisteredQuery } from \"./DependencyIndex.js\";\nimport { serialize, deserialize } from \"../cache/Serializer.js\";\n\n/**\n * Redis-backed {@link DependencyIndex}. Persisting the registry alongside the\n * cached entries themselves is a correctness requirement: if the registry were\n * only in-process, a restart would orphan still-cached query results — a later\n * write could no longer find them to invalidate, serving stale data.\n *\n * Predicate metadata is stored as BSON (not JSON) so ObjectId/Date values\n * inside a predicate survive the round-trip and the matcher stays accurate.\n */\nexport class RedisDependencyIndex implements DependencyIndex {\n constructor(\n private readonly redis: Redis,\n private readonly prefix = \"idx:\",\n ) {}\n\n async addQuery(model: string, queryKey: string, meta: QueryMeta): Promise<void> {\n await this.redis.sadd(this.setKey(model), queryKey);\n await this.redis.set(this.metaKey(model, queryKey), serialize(meta));\n }\n\n async removeQuery(model: string, queryKey: string): Promise<void> {\n await this.redis.srem(this.setKey(model), queryKey);\n await this.redis.del(this.metaKey(model, queryKey));\n }\n\n async getQueries(model: string): Promise<RegisteredQuery[]> {\n const queryKeys = await this.redis.smembers(this.setKey(model));\n const queries: RegisteredQuery[] = [];\n for (const queryKey of queryKeys) {\n const raw = await this.redis.getBuffer(this.metaKey(model, queryKey));\n if (raw === null) continue; // meta expired/removed — skip\n const meta = deserialize(raw) as QueryMeta;\n queries.push({ queryKey, ...meta });\n }\n return queries;\n }\n\n async clearModel(model: string): Promise<string[]> {\n const queryKeys = await this.redis.smembers(this.setKey(model));\n if (queryKeys.length === 0) return [];\n await this.redis.del(\n this.setKey(model),\n ...queryKeys.map((qk) => this.metaKey(model, qk)),\n );\n return queryKeys;\n }\n\n private setKey(model: string): string {\n return `${this.prefix}qset:${model}`;\n }\n\n private metaKey(model: string, queryKey: string): string {\n return `${this.prefix}qmeta:${model}:${queryKey}`;\n }\n}\n","/**\n * Query fingerprinting: convert an arbitrary Mongoose query shape into a\n * deterministic, canonical structure suitable for hashing.\n *\n * Two semantically-equal queries (e.g. `{a:1,b:2}` and `{b:2,a:1}`) must\n * produce identical fingerprints. Special BSON-ish types (ObjectId, Date,\n * RegExp) are normalized to stable tagged forms.\n */\nexport function normalizeQuery(value: unknown): unknown {\n // Primitives and null/undefined pass through unchanged.\n if (value === null || typeof value !== \"object\") {\n return value;\n }\n\n // ObjectId (duck-typed so it works across bson copies bundled by mongoose).\n if (isObjectIdLike(value)) {\n return { $oid: value.toHexString() };\n }\n\n if (value instanceof Date) {\n return { $date: value.toISOString() };\n }\n\n if (value instanceof RegExp) {\n return { $regex: value.source, $options: value.flags };\n }\n\n // Arrays are ordered semantically — preserve order, normalize each element.\n if (Array.isArray(value)) {\n return value.map((el) => normalizeQuery(el));\n }\n\n // Plain objects: sort keys for determinism, normalize values recursively.\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(value as Record<string, unknown>).sort()) {\n out[key] = normalizeQuery((value as Record<string, unknown>)[key]);\n }\n return out;\n}\n\ninterface ObjectIdLike {\n _bsontype: \"ObjectId\";\n toHexString(): string;\n}\n\nfunction isObjectIdLike(value: object): value is ObjectIdLike {\n return (\n (value as { _bsontype?: unknown })._bsontype === \"ObjectId\" &&\n typeof (value as { toHexString?: unknown }).toHexString === \"function\"\n );\n}\n","import { normalizeQuery } from \"../cache/fingerprint.js\";\n\n/**\n * In-memory evaluation of a MongoDB filter against a single plain document.\n *\n * This is the engine that powers precise (T1) invalidation: on a write we\n * evaluate whether a document entered or left a cached query's result set.\n *\n * CRITICAL CORRECTNESS CONTRACT: `matches` must agree with MongoDB's matching\n * semantics for every operator that {@link isSupportedPredicate} accepts. A\n * predicate using any operator we cannot faithfully evaluate must be rejected\n * by `isSupportedPredicate` so the tier classifier downgrades it to T2\n * (collection-tag invalidation) rather than risk a missed invalidation.\n */\nexport type Filter = Record<string, unknown>;\nexport type Doc = Record<string, unknown>;\n\nconst LOGICAL_OPERATORS = new Set([\"$and\", \"$or\", \"$nor\"]);\nconst FIELD_OPERATORS = new Set([\n \"$eq\",\n \"$ne\",\n \"$gt\",\n \"$gte\",\n \"$lt\",\n \"$lte\",\n \"$in\",\n \"$nin\",\n \"$exists\",\n \"$not\",\n]);\n\n// ---------------------------------------------------------------------------\n// matches\n// ---------------------------------------------------------------------------\n\nexport function matches(filter: Filter, doc: Doc): boolean {\n for (const [key, condition] of Object.entries(filter)) {\n if (key === \"$and\") {\n if (!(condition as Filter[]).every((sub) => matches(sub, doc))) return false;\n } else if (key === \"$or\") {\n if (!(condition as Filter[]).some((sub) => matches(sub, doc))) return false;\n } else if (key === \"$nor\") {\n if ((condition as Filter[]).some((sub) => matches(sub, doc))) return false;\n } else {\n const resolved = resolvePath(doc, key);\n if (!fieldMatches(resolved, condition)) return false;\n }\n }\n return true;\n}\n\ninterface Resolved {\n exists: boolean;\n value: unknown;\n}\n\nfunction resolvePath(doc: unknown, path: string): Resolved {\n const segments = path.split(\".\");\n let current: unknown = doc;\n for (const seg of segments) {\n if (current === null || typeof current !== \"object\" || Array.isArray(current)) {\n return { exists: false, value: undefined };\n }\n if (!(seg in (current as Record<string, unknown>))) {\n return { exists: false, value: undefined };\n }\n current = (current as Record<string, unknown>)[seg];\n }\n return { exists: true, value: current };\n}\n\nfunction isOperatorObject(value: unknown): value is Record<string, unknown> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value) &&\n !(value instanceof Date) &&\n Object.keys(value).length > 0 &&\n Object.keys(value).every((k) => k.startsWith(\"$\"))\n );\n}\n\nfunction fieldMatches(resolved: Resolved, condition: unknown): boolean {\n if (isOperatorObject(condition)) {\n return Object.entries(condition).every(([op, operand]) =>\n operatorMatches(resolved, op, operand),\n );\n }\n return equals(resolved, condition);\n}\n\nfunction operatorMatches(resolved: Resolved, op: string, operand: unknown): boolean {\n switch (op) {\n case \"$eq\":\n return equals(resolved, operand);\n case \"$ne\":\n return !equals(resolved, operand);\n case \"$in\":\n return (operand as unknown[]).some((o) => equals(resolved, o));\n case \"$nin\":\n return !(operand as unknown[]).some((o) => equals(resolved, o));\n case \"$exists\":\n return resolved.exists === (operand as boolean);\n case \"$not\":\n return !fieldMatches(resolved, operand);\n case \"$gt\":\n case \"$gte\":\n case \"$lt\":\n case \"$lte\":\n return compare(resolved.value, op, operand);\n default:\n // Unreachable for supported predicates; conservative non-match.\n return false;\n }\n}\n\n/** Mongo equality, including array-contains and null/missing semantics. */\nfunction equals(resolved: Resolved, target: unknown): boolean {\n if (target === null) {\n // $eq null matches an explicit null OR a missing field.\n return !resolved.exists || resolved.value === null;\n }\n if (!resolved.exists) return false;\n const fv = resolved.value;\n if (Array.isArray(fv) && fv.some((el) => scalarEquals(el, target))) {\n return true;\n }\n return scalarEquals(fv, target);\n}\n\nfunction scalarEquals(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n return canonical(a) === canonical(b);\n}\n\nfunction canonical(value: unknown): string {\n return JSON.stringify(normalizeQuery(value));\n}\n\n/** Ordered comparison for numbers, Dates, and strings; array-aware. */\nfunction compare(fieldValue: unknown, op: string, operand: unknown): boolean {\n if (Array.isArray(fieldValue)) {\n return fieldValue.some((el) => compareScalar(el, op, operand));\n }\n return compareScalar(fieldValue, op, operand);\n}\n\nfunction compareScalar(a: unknown, op: string, b: unknown): boolean {\n const left = toComparable(a);\n const right = toComparable(b);\n if (left === null || right === null || typeof left !== typeof right) {\n // Cross-type or non-orderable: no match (Mongo would treat differently,\n // but such filters are out of the supported T1 boundary in practice).\n return false;\n }\n switch (op) {\n case \"$gt\":\n return left > right;\n case \"$gte\":\n return left >= right;\n case \"$lt\":\n return left < right;\n case \"$lte\":\n return left <= right;\n default:\n return false;\n }\n}\n\nfunction toComparable(value: unknown): number | string | null {\n if (typeof value === \"number\" || typeof value === \"string\") return value;\n if (value instanceof Date) return value.getTime();\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// isSupportedPredicate — the T1 soundness boundary\n// ---------------------------------------------------------------------------\n\nexport function isSupportedPredicate(filter: unknown): boolean {\n if (typeof filter !== \"object\" || filter === null || Array.isArray(filter)) {\n return false;\n }\n for (const [key, condition] of Object.entries(filter)) {\n if (LOGICAL_OPERATORS.has(key)) {\n if (!Array.isArray(condition)) return false;\n if (!condition.every((sub) => isSupportedPredicate(sub))) return false;\n } else if (key.startsWith(\"$\")) {\n // Any other top-level $operator ($where, $text, $expr, ...) is unsupported.\n return false;\n } else if (!isSupportedCondition(condition)) {\n return false;\n }\n }\n return true;\n}\n\nfunction isSupportedCondition(condition: unknown): boolean {\n if (condition instanceof RegExp) return false;\n if (isOperatorObject(condition)) {\n return Object.entries(condition).every(([op, operand]) => {\n if (!FIELD_OPERATORS.has(op)) return false;\n if (op === \"$not\") return isOperatorObject(operand) && isSupportedCondition(operand);\n if (op === \"$in\" || op === \"$nin\") {\n // A RegExp element means pattern matching, which our equality-based\n // evaluation cannot reproduce — reject so the query downgrades to T2.\n return (\n Array.isArray(operand) && !operand.some((el) => el instanceof RegExp)\n );\n }\n if (op === \"$exists\") return typeof operand === \"boolean\";\n return true;\n });\n }\n // Plain equality to a non-regex literal is always supported.\n return true;\n}\n","/** Canonicalize a document id to a stable string for keys and set comparison. */\nexport function idToString(id: unknown): string | undefined {\n if (id === null || id === undefined) return undefined;\n if (typeof id === \"string\") return id;\n if (typeof id === \"number\") return String(id);\n if (id instanceof Date) return id.toISOString();\n if (\n typeof id === \"object\" &&\n (id as { _bsontype?: unknown })._bsontype === \"ObjectId\" &&\n typeof (id as { toHexString?: unknown }).toHexString === \"function\"\n ) {\n return (id as { toHexString(): string }).toHexString();\n }\n return String(id);\n}\n","import type { DependencyIndex, QueryMeta } from \"./DependencyIndex.js\";\nimport { matches, type Doc } from \"./PredicateMatcher.js\";\nimport { idToString } from \"../utils/id.js\";\n\nexport interface WriteReport {\n invalidatedQueryKeys: string[];\n}\n\n/**\n * Drives precise (T1) invalidation via the membership-transition algorithm\n * (see plan.md \"On write\"). Given the before- and after-images of a changed\n * document, it determines exactly which cached queries could have changed.\n */\nexport class InvalidationEngine {\n constructor(private readonly index: DependencyIndex) {}\n\n async registerQuery(\n model: string,\n queryKey: string,\n meta: QueryMeta,\n ): Promise<void> {\n await this.index.addQuery(model, queryKey, meta);\n }\n\n async onWrite(\n model: string,\n before: Doc | null,\n after: Doc | null,\n ): Promise<WriteReport> {\n const docId = idToString((after ?? before)?._id);\n const queries = await this.index.getQueries(model);\n const invalidated: string[] = [];\n\n for (const q of queries) {\n // Step 2 — direct membership: the changed doc was in this result set.\n const directHit = docId !== undefined && q.resultDocIds.includes(docId);\n\n // Steps 3/4 — entering/leaving the predicate's result set.\n const wasMatch = before !== null && matches(q.predicate, before);\n const nowMatch = after !== null && matches(q.predicate, after);\n const transition = wasMatch !== nowMatch;\n\n // Step 5 — top-N safety: for a limited query we cannot cheaply know rank,\n // so any match (before or after) is a potential change to the window.\n const limitedHit = q.limited && (wasMatch || nowMatch);\n\n if (directHit || transition || limitedHit) {\n invalidated.push(q.queryKey);\n await this.index.removeQuery(model, q.queryKey);\n }\n }\n\n return { invalidatedQueryKeys: invalidated };\n }\n\n /**\n * Remove every registered query for a model and return their cache keys.\n * Used as a conservative fallback (bulkWrite, upserts) where per-document\n * before/after images aren't available.\n */\n async clearQueries(model: string): Promise<string[]> {\n return this.index.clearModel(model);\n }\n}\n","import { createHash } from \"node:crypto\";\nimport { normalizeQuery } from \"../cache/fingerprint.js\";\n\n/**\n * Deterministic hash of an arbitrary value. The value is first canonicalized\n * via {@link normalizeQuery} so semantically-equal inputs hash identically,\n * then hashed with SHA-256 and truncated for compactness.\n */\nexport function stableHash(value: unknown): string {\n const canonical = JSON.stringify(normalizeQuery(value));\n return createHash(\"sha256\")\n .update(canonical ?? \"undefined\")\n .digest(\"hex\")\n .slice(0, 32);\n}\n","import { stableHash } from \"../utils/hash.js\";\n\n/**\n * Deterministic cache-key construction. Keys are namespaced by tenant (for\n * isolation), kind (`q` for query results, `doc` for point reads), and model.\n */\nexport interface CacheKeyInput {\n model: string;\n op: string;\n filter?: unknown;\n projection?: unknown;\n sort?: unknown;\n skip?: number | undefined;\n limit?: number | undefined;\n populate?: unknown;\n collation?: unknown;\n /** The field name for a `distinct` query (so distinct('a') ≠ distinct('b')). */\n distinct?: unknown;\n tenant?: string | undefined;\n schemaVersion?: string | undefined;\n}\n\nfunction tenantPrefix(tenant?: string): string {\n return tenant ? `tenant:${tenant}:` : \"\";\n}\n\nexport function buildQueryKey(input: CacheKeyInput): string {\n // Everything that can change the result set feeds the hash.\n const hash = stableHash({\n op: input.op,\n filter: input.filter ?? null,\n projection: input.projection ?? null,\n sort: input.sort ?? null,\n skip: input.skip ?? null,\n limit: input.limit ?? null,\n populate: input.populate ?? null,\n collation: input.collation ?? null,\n distinct: input.distinct ?? null,\n schemaVersion: input.schemaVersion ?? null,\n });\n return `${tenantPrefix(input.tenant)}q:${input.model}:${hash}`;\n}\n\nexport function buildDocKey(model: string, id: string, tenant?: string): string {\n return `${tenantPrefix(tenant)}doc:${model}:${id}`;\n}\n\n/**\n * Collection tag key. Tags are intentionally NOT tenant-scoped: a write to a\n * collection must invalidate the conservative (T2/T3) entries of every tenant\n * that touched it, so all tenants share one tag per collection.\n */\nexport function buildTagKey(collection: string): string {\n return `tag:coll:${collection}`;\n}\n\nexport function buildAggregateKey(\n model: string,\n pipeline: unknown,\n tenant?: string,\n): string {\n return `${tenantPrefix(tenant)}agg:${model}:${stableHash(pipeline)}`;\n}\n","import type { EventEmitter } from \"node:events\";\nimport type { CacheStore } from \"./CacheStore.js\";\nimport { serialize, deserialize } from \"./Serializer.js\";\nimport { buildTagKey } from \"./CacheKey.js\";\nimport type { InvalidationEngine } from \"../invalidation/InvalidationEngine.js\";\nimport type { Filter, Doc } from \"../invalidation/PredicateMatcher.js\";\nimport type { Tier } from \"../invalidation/TierClassifier.js\";\n\nexport interface CachedQuery {\n key: string;\n model: string;\n tier: Tier;\n predicate?: Filter | undefined;\n limited?: boolean | undefined;\n ttlMs?: number | undefined;\n /** Collection names to tag this entry with (conservative T2/T3 invalidation). */\n tags?: string[] | undefined;\n}\n\n/**\n * Ties together the store, serializer, and invalidation engine into the\n * read/write caching brain. Guarantees the no-stale-read invariant via a\n * version-token race guard and prevents stampedes via in-process single-flight.\n */\nexport class CacheManager {\n private readonly inflight = new Map<string, Promise<unknown>>();\n\n constructor(\n private readonly store: CacheStore,\n private readonly engine: InvalidationEngine,\n private readonly events?: EventEmitter,\n ) {}\n\n /**\n * Return the cached value for a query, or load it via `loader`, cache it\n * (when safe), and register it for precise invalidation.\n *\n * @param extractIds maps a loaded result to the stable string ids of the\n * documents it contains — required for T1 direct-membership invalidation.\n */\n async getOrLoad<T>(\n query: CachedQuery,\n loader: () => Promise<T>,\n extractIds: (result: T) => string[] = () => [],\n ): Promise<T> {\n if (query.tier === \"T4\") return loader();\n\n // Degrade, never fail: if the cache read throws (Redis down), fall through\n // to the loader rather than surfacing the error to the application.\n let cached: Buffer | null = null;\n try {\n cached = await this.store.get(query.key);\n } catch (err) {\n this.emitError(err);\n return loader();\n }\n if (cached !== null) {\n this.events?.emit(\"hit\", { key: query.key, model: query.model });\n return deserialize(cached) as T;\n }\n this.events?.emit(\"miss\", { key: query.key, model: query.model });\n\n // Single-flight: collapse concurrent misses for the same key.\n const existing = this.inflight.get(query.key);\n if (existing) return existing as Promise<T>;\n\n const work = this.loadAndCache(query, loader, extractIds);\n this.inflight.set(query.key, work);\n try {\n return await work;\n } finally {\n this.inflight.delete(query.key);\n }\n }\n\n private async loadAndCache<T>(\n query: CachedQuery,\n loader: () => Promise<T>,\n extractIds: (result: T) => string[],\n ): Promise<T> {\n // Capture the version BEFORE reading Mongo so a concurrent write that\n // bumps it mid-load causes us to skip caching a possibly-stale value.\n // If we cannot read the version, we cannot prove freshness — so we load\n // but do not cache (correctness over hit-rate).\n let versionBefore: number;\n try {\n versionBefore = await this.store.getVersion(query.model);\n } catch (err) {\n this.emitError(err);\n return loader();\n }\n\n const result = await loader();\n\n let versionAfter: number;\n try {\n versionAfter = await this.store.getVersion(query.model);\n } catch (err) {\n this.emitError(err);\n return result;\n }\n\n if (versionBefore !== versionAfter) {\n // A write landed during our load — do not cache (no-stale-read guard).\n return result;\n }\n\n try {\n await this.store.set(query.key, serialize(result), query.ttlMs);\n\n // A predicate is only supplied for precisely-invalidatable tiers (T0/T1),\n // so its presence is the signal to register for membership tracking.\n if (query.predicate) {\n await this.engine.registerQuery(query.model, query.key, {\n predicate: query.predicate,\n limited: query.limited ?? false,\n resultDocIds: extractIds(result),\n });\n }\n\n // Conservative tiers (T2/T3) tag the entry by collection so that any\n // write to a tagged collection drains it.\n for (const tag of query.tags ?? []) {\n await this.store.addToSet(buildTagKey(tag), query.key);\n }\n } catch (err) {\n this.emitError(err);\n }\n\n return result;\n }\n\n /**\n * Apply a write: bump the model version (closing the race window), run the\n * invalidation engine, and delete every affected cache key.\n */\n async onWrite(\n model: string,\n before: Doc | null,\n after: Doc | null,\n extraKeys: string[] = [],\n ): Promise<string[]> {\n // The Mongo write has already committed by the time we get here, so a\n // Redis failure must never throw back into the application path.\n try {\n await this.store.bumpVersion(model);\n const report = await this.engine.onWrite(model, before, after);\n const keys = [...report.invalidatedQueryKeys, ...extraKeys];\n if (keys.length > 0) {\n await this.store.del(keys);\n this.events?.emit(\"invalidate\", { model, keys });\n }\n return keys;\n } catch (err) {\n this.emitError(err);\n return [];\n }\n }\n\n /**\n * Conservative invalidation: drain a collection's tag and delete every\n * entry tagged with it (T2/T3/distinct/populate). Degrade-safe.\n */\n async invalidateCollection(collection: string): Promise<string[]> {\n try {\n const keys = await this.store.drainSet(buildTagKey(collection));\n if (keys.length > 0) {\n await this.store.del(keys);\n this.events?.emit(\"invalidate\", { collection, keys });\n }\n return keys;\n } catch (err) {\n this.emitError(err);\n return [];\n }\n }\n\n /**\n * Nuclear precise fallback: remove every registered T1 query for a model and\n * delete its cache keys. Used for writes we cannot image (bulkWrite, upserts).\n */\n async flushModelPrecise(model: string): Promise<string[]> {\n try {\n const keys = await this.engine.clearQueries(model);\n if (keys.length > 0) {\n await this.store.del(keys);\n this.events?.emit(\"invalidate\", { model, keys });\n }\n return keys;\n } catch (err) {\n this.emitError(err);\n return [];\n }\n }\n\n /**\n * Emit an `error` event without crashing: a bare `error` emit on an\n * EventEmitter with no listeners would itself throw.\n */\n private emitError(err: unknown): void {\n if (this.events && this.events.listenerCount(\"error\") > 0) {\n this.events.emit(\"error\", err);\n }\n }\n}\n","import { isSupportedPredicate, type Filter } from \"./PredicateMatcher.js\";\n\n/**\n * Cacheability tiers. See plan.md \"Cacheability Tiers\".\n *\n * - T0: point read keyed by document id — surgical invalidation.\n * - T1: predicate query we can evaluate in-memory — precise invalidation.\n * - T2: bounded but unpredicatable — conservative collection-tag invalidation.\n * - T3: aggregation — conservative by touched collection(s).\n * - T4: never cache (active session/transaction, cursor/streaming).\n */\nexport type Tier = \"T0\" | \"T1\" | \"T2\" | \"T3\" | \"T4\";\n\nexport interface ClassifyInput {\n op: string;\n filter?: Filter;\n hasSession?: boolean;\n isCursor?: boolean;\n}\n\nexport function classifyQuery(input: ClassifyInput): Tier {\n // Never cache reads bound to a session/transaction or streamed via cursor.\n if (input.hasSession || input.isCursor) return \"T4\";\n\n if (input.op === \"aggregate\") return \"T3\";\n if (input.op === \"estimatedDocumentCount\") return \"T2\";\n if (input.op === \"findById\") return \"T0\";\n\n const filter = input.filter ?? {};\n\n if ((input.op === \"find\" || input.op === \"findOne\") && isPointRead(filter)) {\n return \"T0\";\n }\n\n return isSupportedPredicate(filter) ? \"T1\" : \"T2\";\n}\n\nfunction isPointRead(filter: Filter): boolean {\n const keys = Object.keys(filter);\n if (keys.length !== 1 || keys[0] !== \"_id\") return false;\n\n const value = filter._id;\n if (isOperatorObject(value)) {\n const opKeys = Object.keys(value);\n return opKeys.length === 1 && opKeys[0] === \"$in\" && Array.isArray(value.$in);\n }\n // Scalar / ObjectId / Date equality on _id.\n return true;\n}\n\nfunction isOperatorObject(value: unknown): value is Record<string, unknown> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value) &&\n !(value instanceof Date) &&\n Object.keys(value).length > 0 &&\n Object.keys(value).every((k) => k.startsWith(\"$\"))\n );\n}\n","/**\n * Storage contract used by the CacheManager. The in-memory implementation is\n * the reference (and powers unit tests); a Redis-backed adapter (Phase 2)\n * implements the same contract with Lua scripts for the atomic version guard.\n *\n * The per-model version counter underpins the write-after-read race guard:\n * a reader captures the version before loading from Mongo and only writes the\n * value to cache if no write bumped the version meanwhile.\n */\nexport interface CacheStore {\n get(key: string): Promise<Buffer | null>;\n set(key: string, value: Buffer, ttlMs?: number): Promise<void>;\n del(keys: string[]): Promise<void>;\n getVersion(model: string): Promise<number>;\n bumpVersion(model: string): Promise<number>;\n /** Tag membership — backs conservative (collection-tag) invalidation. */\n addToSet(setKey: string, member: string): Promise<void>;\n /** Return all members of a set and delete the set (atomic drain). */\n drainSet(setKey: string): Promise<string[]>;\n}\n\nexport class InMemoryCacheStore implements CacheStore {\n private readonly data = new Map<string, Buffer>();\n private readonly versions = new Map<string, number>();\n private readonly sets = new Map<string, Set<string>>();\n\n async get(key: string): Promise<Buffer | null> {\n return this.data.get(key) ?? null;\n }\n\n async set(key: string, value: Buffer, _ttlMs?: number): Promise<void> {\n this.data.set(key, value);\n }\n\n async del(keys: string[]): Promise<void> {\n for (const key of keys) this.data.delete(key);\n }\n\n async getVersion(model: string): Promise<number> {\n return this.versions.get(model) ?? 0;\n }\n\n async bumpVersion(model: string): Promise<number> {\n const next = (this.versions.get(model) ?? 0) + 1;\n this.versions.set(model, next);\n return next;\n }\n\n async addToSet(setKey: string, member: string): Promise<void> {\n let set = this.sets.get(setKey);\n if (!set) {\n set = new Set();\n this.sets.set(setKey, set);\n }\n set.add(member);\n }\n\n async drainSet(setKey: string): Promise<string[]> {\n const set = this.sets.get(setKey);\n if (!set) return [];\n this.sets.delete(setKey);\n return [...set];\n }\n}\n","import type { Filter } from \"./PredicateMatcher.js\";\n\n/**\n * Metadata recorded for every cached T1 query so that a later write can decide,\n * precisely, whether the query's result set could have changed.\n */\nexport interface QueryMeta {\n predicate: Filter;\n /** True when the query has a limit (top-N) — see InvalidationEngine step 5. */\n limited: boolean;\n /** Stable string ids of the documents currently in the cached result. */\n resultDocIds: string[];\n}\n\nexport interface RegisteredQuery extends QueryMeta {\n queryKey: string;\n}\n\n/**\n * Index of active T1 queries, keyed by model. The in-memory implementation is\n * the source of truth for unit tests and single-process use; a Redis-backed\n * implementation (Phase 2) provides the same contract across nodes.\n */\nexport interface DependencyIndex {\n addQuery(model: string, queryKey: string, meta: QueryMeta): Promise<void>;\n removeQuery(model: string, queryKey: string): Promise<void>;\n getQueries(model: string): Promise<RegisteredQuery[]>;\n /** Remove every registered query for a model, returning the keys removed. */\n clearModel(model: string): Promise<string[]>;\n}\n\nexport class InMemoryDependencyIndex implements DependencyIndex {\n private readonly byModel = new Map<string, Map<string, QueryMeta>>();\n\n async addQuery(model: string, queryKey: string, meta: QueryMeta): Promise<void> {\n let queries = this.byModel.get(model);\n if (!queries) {\n queries = new Map();\n this.byModel.set(model, queries);\n }\n queries.set(queryKey, meta);\n }\n\n async removeQuery(model: string, queryKey: string): Promise<void> {\n this.byModel.get(model)?.delete(queryKey);\n }\n\n async getQueries(model: string): Promise<RegisteredQuery[]> {\n const queries = this.byModel.get(model);\n if (!queries) return [];\n return [...queries.entries()].map(([queryKey, meta]) => ({\n queryKey,\n ...meta,\n }));\n }\n\n async clearModel(model: string): Promise<string[]> {\n const queries = this.byModel.get(model);\n if (!queries) return [];\n const keys = [...queries.keys()];\n this.byModel.delete(model);\n return keys;\n }\n}\n"],"mappings":"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","names":["canonical","extractIds","isOperatorObject"]}
@@ -0,0 +1,296 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import { Mongoose } from 'mongoose';
3
+ import { Redis } from 'ioredis';
4
+
5
+ /**
6
+ * Storage contract used by the CacheManager. The in-memory implementation is
7
+ * the reference (and powers unit tests); a Redis-backed adapter (Phase 2)
8
+ * implements the same contract with Lua scripts for the atomic version guard.
9
+ *
10
+ * The per-model version counter underpins the write-after-read race guard:
11
+ * a reader captures the version before loading from Mongo and only writes the
12
+ * value to cache if no write bumped the version meanwhile.
13
+ */
14
+ interface CacheStore {
15
+ get(key: string): Promise<Buffer | null>;
16
+ set(key: string, value: Buffer, ttlMs?: number): Promise<void>;
17
+ del(keys: string[]): Promise<void>;
18
+ getVersion(model: string): Promise<number>;
19
+ bumpVersion(model: string): Promise<number>;
20
+ /** Tag membership — backs conservative (collection-tag) invalidation. */
21
+ addToSet(setKey: string, member: string): Promise<void>;
22
+ /** Return all members of a set and delete the set (atomic drain). */
23
+ drainSet(setKey: string): Promise<string[]>;
24
+ }
25
+ declare class InMemoryCacheStore implements CacheStore {
26
+ private readonly data;
27
+ private readonly versions;
28
+ private readonly sets;
29
+ get(key: string): Promise<Buffer | null>;
30
+ set(key: string, value: Buffer, _ttlMs?: number): Promise<void>;
31
+ del(keys: string[]): Promise<void>;
32
+ getVersion(model: string): Promise<number>;
33
+ bumpVersion(model: string): Promise<number>;
34
+ addToSet(setKey: string, member: string): Promise<void>;
35
+ drainSet(setKey: string): Promise<string[]>;
36
+ }
37
+
38
+ interface ModelConfig {
39
+ ttlMs?: number;
40
+ enabled?: boolean;
41
+ }
42
+ interface CreateCacheOptions {
43
+ mongoose: Mongoose;
44
+ redis: Redis;
45
+ /** Override the storage backend (e.g. for testing). Defaults to Redis. */
46
+ store?: CacheStore;
47
+ /** Opt-in model map. Omit to cache every currently-registered model. */
48
+ models?: Record<string, ModelConfig>;
49
+ defaults?: {
50
+ ttlMs?: number;
51
+ };
52
+ /** Resolve the current tenant id for keyspace isolation. */
53
+ tenant?: () => string | undefined;
54
+ }
55
+ interface Cache extends EventEmitter {
56
+ /** Restore all patched Mongoose methods. */
57
+ close(): void;
58
+ }
59
+ declare function createCache(options: CreateCacheOptions): Cache;
60
+
61
+ /**
62
+ * Redis-backed {@link CacheStore}. Implements the same contract proven against
63
+ * the in-memory store, so all CacheManager correctness logic carries over.
64
+ *
65
+ * Phase 1 targets single-node correctness (in-process single-flight + a
66
+ * per-model version counter). Phase 2 hardens the version guard into an atomic
67
+ * Lua compare-and-set for multi-node deployments.
68
+ */
69
+ declare class RedisCacheStore implements CacheStore {
70
+ private readonly redis;
71
+ private readonly versionPrefix;
72
+ constructor(redis: Redis, versionPrefix?: string);
73
+ get(key: string): Promise<Buffer | null>;
74
+ set(key: string, value: Buffer, ttlMs?: number): Promise<void>;
75
+ del(keys: string[]): Promise<void>;
76
+ getVersion(model: string): Promise<number>;
77
+ bumpVersion(model: string): Promise<number>;
78
+ addToSet(setKey: string, member: string): Promise<void>;
79
+ drainSet(setKey: string): Promise<string[]>;
80
+ private versionKey;
81
+ }
82
+
83
+ /**
84
+ * In-memory evaluation of a MongoDB filter against a single plain document.
85
+ *
86
+ * This is the engine that powers precise (T1) invalidation: on a write we
87
+ * evaluate whether a document entered or left a cached query's result set.
88
+ *
89
+ * CRITICAL CORRECTNESS CONTRACT: `matches` must agree with MongoDB's matching
90
+ * semantics for every operator that {@link isSupportedPredicate} accepts. A
91
+ * predicate using any operator we cannot faithfully evaluate must be rejected
92
+ * by `isSupportedPredicate` so the tier classifier downgrades it to T2
93
+ * (collection-tag invalidation) rather than risk a missed invalidation.
94
+ */
95
+ type Filter = Record<string, unknown>;
96
+ type Doc = Record<string, unknown>;
97
+ declare function matches(filter: Filter, doc: Doc): boolean;
98
+ declare function isSupportedPredicate(filter: unknown): boolean;
99
+
100
+ /**
101
+ * Metadata recorded for every cached T1 query so that a later write can decide,
102
+ * precisely, whether the query's result set could have changed.
103
+ */
104
+ interface QueryMeta {
105
+ predicate: Filter;
106
+ /** True when the query has a limit (top-N) — see InvalidationEngine step 5. */
107
+ limited: boolean;
108
+ /** Stable string ids of the documents currently in the cached result. */
109
+ resultDocIds: string[];
110
+ }
111
+ interface RegisteredQuery extends QueryMeta {
112
+ queryKey: string;
113
+ }
114
+ /**
115
+ * Index of active T1 queries, keyed by model. The in-memory implementation is
116
+ * the source of truth for unit tests and single-process use; a Redis-backed
117
+ * implementation (Phase 2) provides the same contract across nodes.
118
+ */
119
+ interface DependencyIndex {
120
+ addQuery(model: string, queryKey: string, meta: QueryMeta): Promise<void>;
121
+ removeQuery(model: string, queryKey: string): Promise<void>;
122
+ getQueries(model: string): Promise<RegisteredQuery[]>;
123
+ /** Remove every registered query for a model, returning the keys removed. */
124
+ clearModel(model: string): Promise<string[]>;
125
+ }
126
+ declare class InMemoryDependencyIndex implements DependencyIndex {
127
+ private readonly byModel;
128
+ addQuery(model: string, queryKey: string, meta: QueryMeta): Promise<void>;
129
+ removeQuery(model: string, queryKey: string): Promise<void>;
130
+ getQueries(model: string): Promise<RegisteredQuery[]>;
131
+ clearModel(model: string): Promise<string[]>;
132
+ }
133
+
134
+ /**
135
+ * Redis-backed {@link DependencyIndex}. Persisting the registry alongside the
136
+ * cached entries themselves is a correctness requirement: if the registry were
137
+ * only in-process, a restart would orphan still-cached query results — a later
138
+ * write could no longer find them to invalidate, serving stale data.
139
+ *
140
+ * Predicate metadata is stored as BSON (not JSON) so ObjectId/Date values
141
+ * inside a predicate survive the round-trip and the matcher stays accurate.
142
+ */
143
+ declare class RedisDependencyIndex implements DependencyIndex {
144
+ private readonly redis;
145
+ private readonly prefix;
146
+ constructor(redis: Redis, prefix?: string);
147
+ addQuery(model: string, queryKey: string, meta: QueryMeta): Promise<void>;
148
+ removeQuery(model: string, queryKey: string): Promise<void>;
149
+ getQueries(model: string): Promise<RegisteredQuery[]>;
150
+ clearModel(model: string): Promise<string[]>;
151
+ private setKey;
152
+ private metaKey;
153
+ }
154
+
155
+ /**
156
+ * Query fingerprinting: convert an arbitrary Mongoose query shape into a
157
+ * deterministic, canonical structure suitable for hashing.
158
+ *
159
+ * Two semantically-equal queries (e.g. `{a:1,b:2}` and `{b:2,a:1}`) must
160
+ * produce identical fingerprints. Special BSON-ish types (ObjectId, Date,
161
+ * RegExp) are normalized to stable tagged forms.
162
+ */
163
+ declare function normalizeQuery(value: unknown): unknown;
164
+
165
+ /**
166
+ * Deterministic hash of an arbitrary value. The value is first canonicalized
167
+ * via {@link normalizeQuery} so semantically-equal inputs hash identically,
168
+ * then hashed with SHA-256 and truncated for compactness.
169
+ */
170
+ declare function stableHash(value: unknown): string;
171
+
172
+ /**
173
+ * Deterministic cache-key construction. Keys are namespaced by tenant (for
174
+ * isolation), kind (`q` for query results, `doc` for point reads), and model.
175
+ */
176
+ interface CacheKeyInput {
177
+ model: string;
178
+ op: string;
179
+ filter?: unknown;
180
+ projection?: unknown;
181
+ sort?: unknown;
182
+ skip?: number | undefined;
183
+ limit?: number | undefined;
184
+ populate?: unknown;
185
+ collation?: unknown;
186
+ /** The field name for a `distinct` query (so distinct('a') ≠ distinct('b')). */
187
+ distinct?: unknown;
188
+ tenant?: string | undefined;
189
+ schemaVersion?: string | undefined;
190
+ }
191
+ declare function buildQueryKey(input: CacheKeyInput): string;
192
+ declare function buildDocKey(model: string, id: string, tenant?: string): string;
193
+
194
+ /**
195
+ * Lossless serialization of cache values. We never store hydrated Mongoose
196
+ * documents — only plain (lean) data — but that data still contains BSON types
197
+ * (ObjectId, Date, Decimal128, Buffer) that must survive a Redis round-trip.
198
+ *
199
+ * Values are wrapped (`{ v: value }`) before BSON encoding so that arrays and
200
+ * primitives — not just documents — can be stored.
201
+ */
202
+ declare function serialize(value: unknown): Buffer;
203
+ declare function deserialize(buffer: Buffer): unknown;
204
+
205
+ interface WriteReport {
206
+ invalidatedQueryKeys: string[];
207
+ }
208
+ /**
209
+ * Drives precise (T1) invalidation via the membership-transition algorithm
210
+ * (see plan.md "On write"). Given the before- and after-images of a changed
211
+ * document, it determines exactly which cached queries could have changed.
212
+ */
213
+ declare class InvalidationEngine {
214
+ private readonly index;
215
+ constructor(index: DependencyIndex);
216
+ registerQuery(model: string, queryKey: string, meta: QueryMeta): Promise<void>;
217
+ onWrite(model: string, before: Doc | null, after: Doc | null): Promise<WriteReport>;
218
+ /**
219
+ * Remove every registered query for a model and return their cache keys.
220
+ * Used as a conservative fallback (bulkWrite, upserts) where per-document
221
+ * before/after images aren't available.
222
+ */
223
+ clearQueries(model: string): Promise<string[]>;
224
+ }
225
+
226
+ /**
227
+ * Cacheability tiers. See plan.md "Cacheability Tiers".
228
+ *
229
+ * - T0: point read keyed by document id — surgical invalidation.
230
+ * - T1: predicate query we can evaluate in-memory — precise invalidation.
231
+ * - T2: bounded but unpredicatable — conservative collection-tag invalidation.
232
+ * - T3: aggregation — conservative by touched collection(s).
233
+ * - T4: never cache (active session/transaction, cursor/streaming).
234
+ */
235
+ type Tier = "T0" | "T1" | "T2" | "T3" | "T4";
236
+ interface ClassifyInput {
237
+ op: string;
238
+ filter?: Filter;
239
+ hasSession?: boolean;
240
+ isCursor?: boolean;
241
+ }
242
+ declare function classifyQuery(input: ClassifyInput): Tier;
243
+
244
+ interface CachedQuery {
245
+ key: string;
246
+ model: string;
247
+ tier: Tier;
248
+ predicate?: Filter | undefined;
249
+ limited?: boolean | undefined;
250
+ ttlMs?: number | undefined;
251
+ /** Collection names to tag this entry with (conservative T2/T3 invalidation). */
252
+ tags?: string[] | undefined;
253
+ }
254
+ /**
255
+ * Ties together the store, serializer, and invalidation engine into the
256
+ * read/write caching brain. Guarantees the no-stale-read invariant via a
257
+ * version-token race guard and prevents stampedes via in-process single-flight.
258
+ */
259
+ declare class CacheManager {
260
+ private readonly store;
261
+ private readonly engine;
262
+ private readonly events?;
263
+ private readonly inflight;
264
+ constructor(store: CacheStore, engine: InvalidationEngine, events?: EventEmitter | undefined);
265
+ /**
266
+ * Return the cached value for a query, or load it via `loader`, cache it
267
+ * (when safe), and register it for precise invalidation.
268
+ *
269
+ * @param extractIds maps a loaded result to the stable string ids of the
270
+ * documents it contains — required for T1 direct-membership invalidation.
271
+ */
272
+ getOrLoad<T>(query: CachedQuery, loader: () => Promise<T>, extractIds?: (result: T) => string[]): Promise<T>;
273
+ private loadAndCache;
274
+ /**
275
+ * Apply a write: bump the model version (closing the race window), run the
276
+ * invalidation engine, and delete every affected cache key.
277
+ */
278
+ onWrite(model: string, before: Doc | null, after: Doc | null, extraKeys?: string[]): Promise<string[]>;
279
+ /**
280
+ * Conservative invalidation: drain a collection's tag and delete every
281
+ * entry tagged with it (T2/T3/distinct/populate). Degrade-safe.
282
+ */
283
+ invalidateCollection(collection: string): Promise<string[]>;
284
+ /**
285
+ * Nuclear precise fallback: remove every registered T1 query for a model and
286
+ * delete its cache keys. Used for writes we cannot image (bulkWrite, upserts).
287
+ */
288
+ flushModelPrecise(model: string): Promise<string[]>;
289
+ /**
290
+ * Emit an `error` event without crashing: a bare `error` emit on an
291
+ * EventEmitter with no listeners would itself throw.
292
+ */
293
+ private emitError;
294
+ }
295
+
296
+ export { type Cache, type CacheKeyInput, CacheManager, type CacheStore, type CachedQuery, type ClassifyInput, type CreateCacheOptions, type DependencyIndex, type Doc, type Filter, InMemoryCacheStore, InMemoryDependencyIndex, InvalidationEngine, type ModelConfig, type QueryMeta, RedisCacheStore, RedisDependencyIndex, type RegisteredQuery, type Tier, type WriteReport, buildDocKey, buildQueryKey, classifyQuery, createCache, deserialize, isSupportedPredicate, matches, normalizeQuery, serialize, stableHash };