first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,93 @@
# `adaptive-cache` — `navigator.storage.estimate()`-driven cache sizing
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`createAdaptiveCacheSizer` periodically resizes a `PackedResourceCache`
from a `navigator.storage.estimate()`-style quota report so the LRU
ceiling tracks the actual storage the origin has available rather than a
static guess. The sizer is the production implementation of
PACKER-BROWSER-17 — adaptive cache sizing — and is what enables a
packer-browser deployment to ride the upstream quota without manually
re-tuning the cache on every device.
The module is **Clock-free**: it relies on the configured `intervalMs`
through an injectable timer (default `setInterval`), so deterministic
tests substitute a manual tick implementation rather than mocking time.
Cancellation is propagated through an `AbortSignal`; pre-aborted signals
short-circuit construction so the sizer never schedules work.
## Features
- Initial estimate fired synchronously on construction
- Recurring refresh on a configurable interval (default `60_000` ms)
- Pluggable `storageEstimate` injection (defaults to
`() => navigator.storage.estimate()` when available)
- Graceful no-op fallback when neither the injection nor the global API
is present
- `AbortSignal`-aware: aborting cancels the timer and stops further
estimate calls
- Disposer is idempotent and can be called from a manager's
shutdown path
## Usage: Basic
```ts
import { createAdaptiveCacheSizer } from "@suaveplan/packer-browser/adaptive-cache";
import { PackedResourceCache } from "@suaveplan/packer-browser";
const cache = new PackedResourceCache({ runtime, observability });
const sizer = createAdaptiveCacheSizer({ cache });
// On teardown:
sizer.dispose();
```
## Usage: Advanced
```ts
const controller = new AbortController();
const sizer = createAdaptiveCacheSizer({
cache,
fraction: 0.1, // 10 % of quota
intervalMs: 30_000,
storageEstimate: async () => ({ quota: 8 * 1024 * 1024 * 1024 }),
signal: controller.signal,
});
await sizer.refresh(); // one-shot refresh
console.log(sizer.getLastApplied()); // 858_993_459
controller.abort(); // stops the recurring updater
```
## API Reference
- `createAdaptiveCacheSizer(opts: AdaptiveCacheSizerOptions): AdaptiveCacheSizer`
- `opts.cache` — `PackedResourceCache` to size. Required.
- `opts.fraction` — clamped to `(0, 1]`. Default `0.25`.
- `opts.intervalMs` — refresh interval. Default `60_000`.
- `opts.storageEstimate` — pluggable estimate function.
- `opts.signal` — cancellation `AbortSignal`.
- `opts.setIntervalImpl` — test-only timer injection.
- Returns: `{ dispose, refresh, getLastApplied }`.
- `AdaptiveCacheSizer.dispose()` — idempotent stop.
- `AdaptiveCacheSizer.refresh(): Promise<void>` — one-shot refresh.
- `AdaptiveCacheSizer.getLastApplied(): number | undefined` — most recent
ceiling applied, or `undefined` before the first refresh.
## Implementation Notes
- The initial estimate is fired with `void refresh()` so construction
is synchronous and the call returns the disposer immediately. Callers
who need deterministic timing can `await sizer.refresh()`.
- When `quota` is missing or non-positive the sizer is a defensive no-op
for that tick — the cache's previous ceiling is preserved.
- The default `setInterval` adapter calls `.unref()` when present so a
Node test process is not held open by the recurring updater.
- The sizer never reads the cache; it only writes via
`cache.setMaxCacheSize(n)`, which is itself a runtime mutator that
performs eviction in-place.