New:Socket for Asana Is Now Available.Learn more
Sign In

@ultimat3/cache

Package Overview
Dependencies
Maintainers
1
Versions
24
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ultimat3/cache - npm Package Compare versions

Comparing version
11.1.0
to
11.2.0
+25
-3
CLAUDE.md

@@ -141,2 +141,19 @@ # @ultimat3/cache — agent notes

stack, never a module-level map: two stacks are two ladders.
**The mechanism is `@ultimat3/core`'s since 2026-08-23** — this file's shape verbatim, one tier
down, because four packages each grew a deduper and only copies can drift. `single-flight.ts`
stays as the door, so `createSingleFlight`, `SingleFlight` and `FlightJoin` are still exported
from `@ultimat3/cache` unchanged; `single-flight.test.ts` pins the delegation by IDENTITY, since
behavioural parity is exactly what let the four copies drift in the first place.
- **A wedged `load()` no longer holds its key for ever** (`As of 2026-08-23`). It used to: every
later reader of that key joined a promise nothing would resolve, so a cache stopped damping an
outage and became one. `createCacheStack` passes `deadlineMs: DEFAULT_LOAD_DEADLINE_MS` (30s;
`loadDeadlineMs` overrides it, `schedule` injects the timer). The number is `@ultimat3/http`'s
`requestTimeoutMs` default written out — a `load()` still running at 30s has no reader left to
serve, because the request waiting on it was abandoned at the same instant — and it is a literal
because cache is tier 1 and http is tier 2. **Eviction frees the KEY and nothing else**: `load()`
is the app's function and this stack holds no signal to abort it, so the wedged load runs on and
its own readers still get its answer. The cost is one duplicate fill, which the ladder's
last-write-wins `set` already tolerates. Not an `app.config.ts` key on purpose — the ceiling
belongs to whoever wrote the `load()`, and `bun run scripts/config-readers.ts` refuses a leaf
key nothing reads.
- **A joiner shares the leader's WRITE, so it contributes to it** (`FlightJoin`, merged by

@@ -255,3 +272,8 @@ `mergeSetOptions` in `set-options.ts`). Keyed on `key` alone and read late, the entry used to land

- `retryable` on `X_CACHE_PURGE_FAILED` is derived, never guessed: 408/409/425/429 and 5xx, plus
any request that never got a status. That table lives in `purge-http.ts` and is edited there.
any request that never got a status. The table is **`@ultimat3/core`'s** `isRetryableStatus`
(`retryable-status.ts`), `As of 2026-08-23`, and is edited there — `purge-http.ts` re-exports it
so both drivers still read "what a failure means" off the shared HTTP half, the same door
`@ultimat3/auth`'s `tokens.ts` gives `timingSafeEqual`. It was a private `RETRYABLE_STATUSES`
here that was byte-identical to `packages/mail/src/driver-resend.ts`'s, in two packages that
cannot import each other, so one copy was always going to be edited alone.
**It reaches `error.retry` too, `As of 2026-08-23`** — `CachePurgeFailedError` passes

@@ -289,5 +311,5 @@ `retry: input.retryable ? 'retryable' : 'terminal'`. Without it the constructor fell back to

| `redis.ts` | `Bun.redis` tier, build-namespaced keys, hash-tagged buckets, one script call per tag |
| `single-flight.ts` | one in-flight `load()` per key, shared by every concurrent miss |
| `single-flight.ts` | the door onto `@ultimat3/core`'s `createSingleFlight` — one in-flight `load()` per key, shared by every concurrent miss. No implementation of its own since 2026-08-23 |
| `cdn.ts` | `Cache-Control`/`Surrogate-Key` emission, the `PurgeDriver` seam, `noopPurgeDriver` |
| `purge-http.ts` | the HTTP half both remote drivers share: one POST, retryable table, batching, key guard |
| `purge-http.ts` | the HTTP half both remote drivers share: one POST, batching, key guard, and core's retryable table re-exported |
| `purge-fastly.ts` | `fastlyPurgeDriver`: surrogate-key batch purge, `purge_all` |

@@ -294,0 +316,0 @@ | `purge-cloudflare.ts` | `cloudflarePurgeDriver`: cache-tag purge, `purge_everything` |

+2
-2
{
"name": "@ultimat3/cache",
"version": "11.1.0",
"version": "11.2.0",
"description": "Tagged caching: request memo, LRU, Redis, CDN — one invalidation graph",

@@ -34,4 +34,4 @@ "license": "MIT",

"dependencies": {
"@ultimat3/core": "11.1.0"
"@ultimat3/core": "11.2.0"
}
}

@@ -70,3 +70,10 @@ # @ultimat3/cache 🗂️

TTL boundary, because the write only lands after `load()` resolves. The primitive is
`createSingleFlight()` if you need it elsewhere; the stack holds one per stack. A joiner shares the
`createSingleFlight()` if you need it elsewhere — `@ultimat3/core`'s, re-exported here unchanged;
the stack holds one per stack.
A `load()` that never settles does **not** hold its key for ever: past `loadDeadlineMs`
(`DEFAULT_LOAD_DEADLINE_MS`, 30s — the point at which `http.requestTimeoutMs` already abandoned the
request that was waiting for it) the key is freed and the next reader loads for itself. Eviction
frees the key and never the work, so the readers already holding that load still get its answer,
and the cost is one duplicate fill — `createCacheStack(tiers, { loadDeadlineMs: 5_000 })` for a
tighter ceiling on a fast origin. A joiner shares the
leader's **write** as well as its load, so it contributes to it: tags union, TTLs take the shortest.

@@ -73,0 +80,0 @@ Without that the entry landed carrying only the leader's tags and the joiner's invalidation never

@@ -114,2 +114,3 @@ // Public API of @ultimat3/cache. Explicit, no `export *`.

createCacheStack,
DEFAULT_LOAD_DEADLINE_MS,
DEFAULT_TTL_JITTER_FRACTION,

@@ -116,0 +117,0 @@ isExpired,

@@ -18,8 +18,9 @@ // Single responsibility: the HTTP half both remote purge drivers share — one POST with a

// A 4xx here means the same request, unchanged, might land: a throttle or a momentary conflict.
// Every other 4xx is a credential or a plan, which no retry fixes.
const RETRYABLE_STATUSES = new Set([408, 409, 425, 429]);
// Every other 4xx is a credential or a plan, which no retry fixes. The table itself is
// `@ultimat3/core`'s — this line and `packages/mail/src/driver-resend.ts`'s were byte-identical in
// two packages that cannot import each other, so one of them was always going to be edited alone.
// Re-exported rather than imported twice, so both purge drivers still read "what a failure means"
// off the shared HTTP half — the same door `@ultimat3/auth`'s `tokens.ts` gives `timingSafeEqual`.
export { isRetryableStatus } from '@ultimat3/core';
export const isRetryableStatus = (status: number): boolean =>
status >= 500 || RETRYABLE_STATUSES.has(status);
/**

@@ -26,0 +27,0 @@ * A bare reference to `globalThis.fetch` risks "Illegal invocation" on some hosts; closing over

@@ -1,78 +0,8 @@

// N concurrent misses on one key are ONE origin load. Without this a cache is an outage
// amplifier: the write only lands after `load()` resolves, so every request that arrives inside
// that window misses too and every one of them queries the origin. The share is per load and
// never a second cache — the entry clears as it settles, rejection included.
// N concurrent misses on one key are ONE origin load. The mechanism is `@ultimat3/core`'s — this
// file's shape verbatim, one tier down, plus identity-checked eviction and an optional injected
// deadline — because four packages each grew their own deduper and only copies can drift. This
// file stays as the door `@ultimat3/cache` has always published it through, so no caller moves.
// `@ultimat3/realtime`'s `entry.reading` is the same shape one tier UP, adopting the same door.
/**
* What a joiner contributes to the load it joined. Without one a joiner is a free rider: it takes
* the leader's value AND the leader's write, so anything it declared about that write is dropped.
*/
export interface FlightJoin<C> {
readonly context: C;
/** Folds a joiner in. Called synchronously as it arrives, so the leader sees it before it writes. */
readonly merge: (current: C, joining: C) => C;
}
/** Shares one in-flight `work()` per key. `@ultimat3/realtime`'s `entry.reading`, one tier down. */
export interface SingleFlight {
/**
* `work` receives a reader for the merged context — read it LATE (after the load settles), or
* it answers with only what the leader brought.
*/
run<T, C = undefined>(
key: string,
work: (shared: () => C | undefined) => Promise<T>,
join?: FlightJoin<C>,
): Promise<T>;
/** In-flight loads right now. A number that does not fall back to `0` is a leak. */
readonly size: number;
}
/** The leader's promise, plus the box its merged context lives in — one identity for both. */
interface Flight {
readonly running: Promise<unknown>;
readonly shared: { context: unknown };
}
export function createSingleFlight(): SingleFlight {
const inflight = new Map<string, Flight>();
return {
get size(): number {
return inflight.size;
},
run<T, C = undefined>(
key: string,
work: (shared: () => C | undefined) => Promise<T>,
join?: FlightJoin<C>,
): Promise<T> {
const joined = inflight.get(key);
// Two readers of one key asking for two different `T` is an app bug the cache cannot see;
// the value they share is the same object either way, so the cast is the honest one.
if (joined !== undefined) {
if (join !== undefined) {
joined.shared.context = join.merge(joined.shared.context as C, join.context);
}
return joined.running as Promise<T>;
}
const shared: { context: unknown } = { context: join?.context };
// Wrapped so a `work()` that throws SYNCHRONOUSLY still rejects the joiners rather than
// escaping past the map and leaving no entry to clear.
const running: Promise<T> = (async () => await work(() => shared.context as C | undefined))();
const entry: Flight = { running, shared };
inflight.set(key, entry);
const settled = (): void => {
// Only the leader clears its own entry: a load started after this one settled must not be
// dropped by a late callback from the load it replaced.
if (inflight.get(key) === entry) inflight.delete(key);
};
// A rejected load MUST clear too, or one failure is cached as a permanent rejection.
void running.then(settled, settled);
return running;
},
};
}
export type { FlightJoin, SingleFlight } from '@ultimat3/core';
export { createSingleFlight } from '@ultimat3/core';

@@ -6,3 +6,3 @@ // The tier ladder: request-memo -> lru -> redis -> cdn. Reads walk DOWN until a hit, then

import type { CacheTierName, Clock } from '@ultimat3/core';
import type { CacheTierName, Clock, Scheduler } from '@ultimat3/core';
import { CACHE_TIERS, systemClock } from '@ultimat3/core';

@@ -166,2 +166,10 @@ import { CacheJitterInvalidError, CacheTtlInvalidError } from './errors';

/**
* A `load()` still running at 30s has no reader left to serve: `stack.read` is on the request path,
* and `@ultimat3/http` abandons the request that is waiting for it at the same 30s
* (`requestTimeoutMs`). Stated as a literal because cache is tier 1 and http is tier 2, so the
* number cannot be imported — deliberately NOT a JWKS fetch's bound, which is a transport's.
*/
export const DEFAULT_LOAD_DEADLINE_MS = 30_000;
/**
* Every tier call here goes through `bestEffort`: a tier that refuses is a tier that did not

@@ -174,2 +182,13 @@ * answer, never a failed business read. `load()` is the one call left unguarded — it *is* the

readonly clock?: Clock;
/**
* How long one `load()` may hold its key before a later reader is allowed to start its own,
* instead of joining a promise that may never resolve. Defaults to `DEFAULT_LOAD_DEADLINE_MS`.
*
* An option on the stack rather than an `app.config.ts` key on purpose: the ceiling belongs to
* whoever wrote the `load()`, not to the deployment, and a leaf key nothing reads is what
* `bun run scripts/config-readers.ts` exists to refuse.
*/
readonly loadDeadlineMs?: number;
/** Injected so the deadline is provable without a test waiting one out. */
readonly schedule?: Scheduler;
}

@@ -184,3 +203,12 @@

// Per stack, not per module: two stacks are two ladders and must not join each other's loads.
const flight = createSingleFlight();
//
// The deadline frees the KEY and nothing else — `load()` is the app's function and this stack
// holds no signal that could abort it, so the wedged load runs on and the readers already
// holding its promise still get whatever it eventually answers. What eviction buys is that the
// NEXT reader is allowed to try. So the worst case is one duplicate fill, which the ladder's
// last-write-wins `set` already tolerates, against a key pinned for the life of the process.
const flight = createSingleFlight({
deadlineMs: options.loadDeadlineMs ?? DEFAULT_LOAD_DEADLINE_MS,
schedule: options.schedule,
});

@@ -187,0 +215,0 @@ /** Take back what a fence refused mid-ladder: half a stale ladder is still a stale read. */