# @suaveplan/drive-plugin-cache - Read-through cache middleware for @suaveplan/drive. Accepts a caller-supplied AbstractCache and intercepts Drive.read to short-circuit hot paths; write/delete invalidate post-success. Cache backend, eviction policy, TTL, and namespacing are entirely caller-owned. Read-through cache middleware for [`@suaveplan/drive`](../drive). Accepts a caller-supplied `AbstractCache` (from [`@suaveplan/cache`](../../cache/cache)) and intercepts every `drive.read(path)` to return cached plaintext bytes instead of re-running the storage / mesh / decryption stack. `write` and `delete` invalidate the corresponding entry post-success. The cache itself is opaque to the plugin — consumers pick the backend (`MemoryCache`, `StorageCache` over OPFS / file / S3 / CDN, `RedisCache`), the eviction policy, the TTL, the namespacing, the codec, the redis/storage instance. The plugin just talks `AbstractCache`. ## Documentation tree - [error-codes](./src/error-codes/error-codes.md) — `error-codes.ts` defines the structured error surface for `@suaveplan/drive-plugin-cache`. It exports the `DRIVE_CACHE_… - [error-codes](./src/error-codes/error-codes.md) — `error-codes.ts` defines the structured error surface for `@suaveplan/drive-plugin-cache`. It exports the `DRIVE_CACHE_… - [handle](./src/handle/handle.md) — `handle.ts` builds the `DriveCacheHandle` returned by `driveCachePlugin.install(drive)`. The handle exposes a stats sna… - [handle](./src/handle/handle.md) — `handle.ts` builds the `DriveCacheHandle` returned by `driveCachePlugin.install(drive)`. The handle exposes a stats sna… - [middleware](./src/middleware/middleware.md) — `middleware.ts` builds the `DriveMiddleware` registered on a `Drive` by `driveCachePlugin`. The middleware intercepts `… - [middleware.invalidation](./src/middleware/middleware.invalidation.md) — `middleware.invalidation.ts` provides the `write` and `delete` hooks of the `DriveMiddleware` built in `middleware.ts`.… - [middleware](./src/middleware/middleware.md) — `middleware.ts` builds the `DriveMiddleware` registered on a `Drive` by `driveCachePlugin`. The middleware intercepts `… - [middleware.signal](./src/middleware/middleware.signal.md) — `middleware.signal.ts` provides two tiny `AbortSignal` helpers used by the drive-cache middleware: `getSignal(ctx)` ext… - [otel](./src/otel/otel.md) — `otel.ts` wires the `@suaveplan/drive-cache` meter and tracer used by every middleware intercept in `@suaveplan/drive-p… - [otel](./src/otel/otel.md) — `otel.ts` wires the `@suaveplan/drive-cache` meter and tracer used by every middleware intercept in `@suaveplan/drive-p… - [plugin](./src/plugin/plugin.md) — `plugin.ts` is the public entry point of `@suaveplan/drive-plugin-cache`. It exports the `driveCachePlugin(options): Dr… - [plugin](./src/plugin/plugin.md) — `plugin.ts` is the public entry point of `@suaveplan/drive-plugin-cache`. It exports the `driveCachePlugin(options): Dr… ## Modules - [`plugin`](./src/plugin/plugin.md) — `driveCachePlugin(options): DrivePlugin` factory. - [`middleware`](./src/middleware/middleware.md) — the read intercept (hit short-circuit + populate-on-miss). - [`middleware.invalidation`](./src/middleware/middleware.invalidation.md) — write/delete invalidation hooks. - [`middleware.signal`](./src/middleware/middleware.signal.md) — `AbortSignal` helpers. - [`handle`](./src/handle/handle.md) — `DriveCacheHandle` builder. - [`otel`](./src/otel/otel.md) — `@suaveplan/drive-cache` meter + tracer wiring. - [`error-codes`](./src/error-codes/error-codes.md) — `DRIVE_CACHE_CODES` registry and `DriveCacheError`. - [`types`](./src/types/options.types.md) — public type surface. ## Quickstart ```ts import { Drive } from "@suaveplan/drive"; import { MemoryCache } from "@suaveplan/cache"; import { driveCachePlugin } from "@suaveplan/drive-plugin-cache"; const cache = new MemoryCache(); const drive = new Drive(storage); await drive.initialize(); const handle = await drive.usePlugin(driveCachePlugin({ cache })); await drive.write("clip.mov", payload); const bytes1 = await drive.read("clip.mov"); // miss → populate const bytes2 = await drive.read("clip.mov"); // hit → cached console.log(handle.getStats()); // { hits: 1, misses: 1, ... } ``` ## Composition recipes ### 1. Memory cache (zero deps, ephemeral, fastest) ```ts import { MemoryCache } from "@suaveplan/cache"; const cache = new MemoryCache({ maxSize: 10_000 }); await drive.usePlugin(driveCachePlugin({ cache })); ``` ### 2. OPFS-backed cache (browser, survives reloads) ```ts import { createOpfsStorage } from "@suaveplan/storage-browser-opfs"; import { StorageCache, IdentityCodec } from "@suaveplan/cache-storage"; const cache = new StorageCache({ storage: await createOpfsStorage("drive-cache"), codec: new IdentityCodec(), }); await drive.usePlugin(driveCachePlugin({ cache, namespace: "media" })); ``` ### 3. File-backed cache (server, survives process restart) ```ts import { createNodeFsStorage } from "@suaveplan/storage-file"; import { StorageCache, IdentityCodec } from "@suaveplan/cache-storage"; const cache = new StorageCache({ storage: createNodeFsStorage({ rootDir: "/var/cache/drive", mkdirp: true }), codec: new IdentityCodec(), }); await drive.usePlugin(driveCachePlugin({ cache })); ``` ### 4. Redis-backed cache (server, cross-process shared) ```ts import { createRedisCache } from "@suaveplan/cache-redis"; const cache = createRedisCache({ client, prefix: "drive-cache:" }); await drive.usePlugin(driveCachePlugin({ cache, namespace: "media" })); ``` ### 5. Stale-while-revalidate via `cache.wrap()` ```ts const handle = await drive.usePlugin(driveCachePlugin({ cache })); const bytes = await handle.cache.wrap( "drive::clip.mov", () => drive.read("clip.mov"), { staleAge: 60_000 } ); ``` Recipe 5 illustrates why the plugin exposes `handle.cache`: stale-while-revalidate, lock-and-load, refresh-ahead, and every other policy `cache.wrap()` already supports stays one composition step away — without the plugin growing options for each. ## Defaults | Option | Default | Rationale | |---|---|---| | `namespace` | `"drive"` | Single-drive consumers don't need to pick. Multi-drive consumers pass distinct strings and share one cache. | | `invalidateOnWrite` | `true` | Cached bytes go stale the moment storage accepts new bytes. Post-success deletion is the safe default. | | `invalidateOnDelete` | `true` | Symmetric with `invalidateOnWrite`. | | `maxEntryBytes` | `16 * 1024 * 1024` (16 MiB) | Matches `drive-mesh-protocol`'s `MAX_FRAME_BYTES`. Empirically safe in every consumer already running drive. Bypass via a custom `shouldCache` for large-blob workflows. | | `shouldCache` | `bytes.byteLength <= maxEntryBytes` | The byte-ceiling check is the only failure mode for the default predicate. | ## Out of scope - Caching `Drive.stat` / `Drive.list` results. - Range / streaming reads (`Drive.createReadStream`) — bypass middleware by design. - Per-tenant key partitioning (`keyFn(ctx)` extension). - Built-in `wrap()` integration — consumers compose via `handle.cache.wrap(...)`. See the design proposal for the full design rationale.