npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@cat-factory/caching

v0.20.87

Published

The app-level caching seam for the Agent Architecture Board (docs/initiatives/caching-layer.md): createAppCaches builds the named, typed in-memory read-through caches (layered-loader) the services consume via the kernel AppCaches port, with optional distr

Readme

@cat-factory/caching

The app-level caching seam (see docs/initiatives/caching-layer.md in the repo). createAppCaches(options) builds the named, typed read-through caches the services consume through the kernel AppCaches port, implemented on layered-loader.

Design rules

  • In-memory only. Each cache is a per-replica LRU (layered-loader GroupLoader over its in-memory tier). A replica always repopulates from its own data source on a miss. There is deliberately no Redis (or any async) data tier.
  • Redis is an invalidation bus, never a data tier. In a multi-node Node deployment the facade injects a notificationPairFactory (built from layered-loader's createGroupNotificationPair over dedicated ioredis clients, gated on REDIS_URL); a write on one node then broadcasts the invalidated key/group so every peer drops its in-memory entry. Only keys/groups travel on the wire, never values. Absent the factory (single replica, local mode, tests) the loaders are bare in-memory with zero extra dependency.
  • Invalidate after commit, at every write site. The consuming service calls invalidate/invalidateGroup (or the coarse invalidateAll for rare wide-blast writes) after the DB write commits; layered-loader publishes to peers automatically.
  • Staleness probes for git-backed caches. A profile with ttlLeftBeforeRefreshInMsecs turns on preemptive in-memory refresh (layered-loader ≥ 14.5.3): an entry hit inside the window runs the caller's per-read isStillCurrent probe (a sha/hash compare, strictly cheaper than the load) in the background: TTL bump when the source hasn't moved, full background reload otherwise. DB-backed invalidation-driven caches leave the window unset: a DB read as a probe saves nothing over the DB read as the load.
  • Deep imports keep ioredis out of every runtime but Node. layered-loader's root index eagerly loads its Redis modules (and ioredis), so this package deep-imports only the in-memory machinery. The Redis notification classes are loaded dynamically by the Node facade alone, behind REDIS_URL.

The Cloudflare Worker profiles

A Worker isolate has no cross-isolate invalidation bus and no Redis, so a bare TTL'd in-isolate cache over mutable cross-instance state would serve stale data after a write processed by another isolate: a correctness bug, not an optimization. Push is structurally unavailable there (an isolate holds no subscription between requests), so the Worker has two stances, selected by whether its CACHE_GENERATIONS Durable Object binding exists:

  • ISOLATE_SAFE_APP_CACHES_PROFILE (the fallback, and prior behaviour): caches of mutable state are pass-through (enabled: false; every read runs its load), and only caches of immutable or self-verifying entries (sha-pinned repo reads, external documents re-validated by a version probe) get real TTLs.
  • ISOLATE_COHERENT_APP_CACHES_PROFILE: the isolate-safe profile plus pull-coherent caches. A coherent cache keeps a real TTL, and its profile entry carries a coherencyWindowMsecs: a read whose group snapshot is older than the window re-reads the injected CacheGenerationStore (one monotonic counter per (cache, group), one round trip per group serving every coherent cache) and, on a moved counter, applies layered-loader 16.1's local, fencing, non-publishing applyRemoteInvalidation* primitives before serving. Every invalidation site bumps the directory right after its local invalidation, awaited by the write path. Cross-isolate staleness is bounded by the window (5s on the pilot), instead of indefinite (a bare TTL) or zero-at-the-cost-of-every-read (pass-through).

Error posture, deliberately asymmetric: a probe failure fails CLOSED (the read invalidates locally and loads fresh, so a directory outage degrades to pass-through performance, never staleness); a bump failure fails OPEN (the write and its local invalidation already happened; peers heal at the TTL, and cache.coherency_bump_failure is the visible trace). createAppCaches refuses a profile that sets a window on an enabled cache with no generationStore wired.

A coherent cache also declares whether it ever invalidates CACHE-WIDE, with cacheWideInvalidation. Only a cache that does needs the reserved '*' epoch counter probed beside its own group, and that shard is ONE globally placed Durable Object, so a cache with no invalidateAll call site would be paying a cross-colo round trip that structurally cannot return news. It is declared rather than inferred, and invalidateAll on a coherent cache that did not declare it THROWS: dropping the entries here while every peer keeps serving theirs to the TTL is exactly the hole the flag exists to close.

Isolate runtimes: nothing may cross an invocation

Two rules, both from the same fact: on Cloudflare, I/O is scoped to the invocation that created it, and an invocation that touches another's I/O is destroyed with "Cannot perform I/O on behalf of a different request", at the runtime level, where no catch in the joining code can see it.

  • Background work is adopted by the CURRENT invocation. Pass scheduleBackgroundWork and hand the promise to ctx.waitUntil, resolving the context at call time rather than closing over one (the Worker reads the ambient ExecutionContext off an AsyncLocalStorage; see runtimes/cloudflare/src/infrastructure/appCachesHost.ts).
  • In-flight promises are never shared between invocations. This is what currentInvocation is for. The bag is one per ISOLATE, which is what makes its entries caches rather than per-invocation memos, but it also puts layered-loader's in-flight load map (and the coherency probe's) in reach of a second concurrent invocation on every same-key miss. Supplying currentInvocation moves the MISS path onto per-invocation loads: reads still serve from the isolate-scoped in-memory tier and a hit still schedules its preemptive refresh, but a miss loads for itself rather than joining a promise it cannot safely await. Within one invocation the coalescing is kept, and an invocation the runtime cannot name (a Workflows step, which has no ExecutionContext) coalesces with nothing, because two loads that cannot be told apart must be assumed to be different contexts. Node supplies nothing here and keeps layered-loader's own load path unchanged.

Because a miss now publishes outside the loader, that publish is fenced locally: an invalidation landing while a load is in flight discards the late write instead of resurrecting the entry it dropped (the caller still receives the value it loaded).

fragmentDocumentBody is the first self-verifying cache that stays enabled on the Worker even without the directory: its entries are external Confluence/Notion/GitHub/… page content re-validated by the source's cheap version probe (ttlLeftBeforeRefreshInMsecs + isStillCurrent), so a peer isolate's cached body self-heals within the refresh window without an invalidation bus. workspaceSettings is the pull-coherency pilot (one invalidation site, no invalidateAll, hot on the Worker); further flips are one profile row each, in their own slice.

Named caches

| Cache | Value | Group / key | Profile | | ---------------------- | ----------------------------------------------- | ---------------------------------------------------- | --------------------------------------------- | | fragmentCatalog | merged per-workspace catalog | workspaceId / workspaceId | TTL + invalidation; pass-through on Worker | | fragmentDocumentBody | a document-backed fragment's live external body | viaWorkspaceId / <source>:<externalId> | TTL + version probe; enabled on both facades | | repoProjection | a workspace's whole GitHub repo projection | workspaceId / workspaceId | TTL + invalidation; pass-through on Worker | | repoFiles | checkout-free RepoFiles reads on a branch | <inst>:<owner>/<repo>@<branch> / f:|d: + path | TTL + head-sha probe; enabled on both facades |

Usage

import { createAppCaches } from '@cat-factory/caching'

// Node facade (multi-node): inject the Redis-backed notification pair factory.
const caches = createAppCaches({ notificationPairFactory, logger })

// Cloudflare Worker: the module-scope host picks the coherent profile when the
// CACHE_GENERATIONS Durable Object is bound, else the isolate-safe fallback
// (runtimes/cloudflare/src/infrastructure/appCachesHost.ts).
const caches = createAppCaches({
  profile: ISOLATE_COHERENT_APP_CACHES_PROFILE,
  generationStore, // DO-backed on the Worker; any CacheGenerationStore elsewhere
  scheduleBackgroundWork, // adopt detached refreshes onto ctx.waitUntil
})

// A consuming service reads through its named handle…
const catalog = await caches.fragmentCatalog.get(key, workspaceId, () => loadCatalog())
// …and every write path invalidates after commit.
await caches.fragmentCatalog.invalidateGroup(workspaceId)