Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,129 @@
# @suaveplan/drive-plugin-cache - Read-through cache middleware for @suaveplan/drive. Accepts a caller-supplied AbstractCache<Uint8Array> 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<Uint8Array>` (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`.
<!-- begin:auto-docs -->
## 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…
<!-- end:auto-docs -->
## Modules
- [`plugin`](./src/plugin/plugin.md) — `driveCachePlugin(options): DrivePlugin<DriveCacheHandle>` 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<Uint8Array>();
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<Uint8Array>({ 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<Uint8Array>({
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<Uint8Array>({
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.