Runtime APIs
Cache API
Use caches.default and caches.open() with OpenOrange, backed by a local Rivet Actor.
Status: Partial. Runs on one Rivet Actor for each cache name.
Configuration
The Cache API needs no configuration. caches.default and caches.open(name)
store entries by default.
To get the workers.dev behaviour, where every call succeeds and nothing is
stored, set ORANGE_CACHE=off before orange dev:
ORANGE_CACHE=off orange dev
ORANGE_CACHE accepts on (the default) and off. Other values stop
orange dev with an error.
How it works
Each cache name is one internal Durable Object (openorange:cache), which runs
as a Rivet Actor with SQLite storage. caches.default uses the name default
and caches.open("x") uses named:x, as Miniflare does. Entries persist
across orange dev restarts in the project’s .open-orange directory.
The rules follow workerd and Miniflare’s cache plugin:
put()throws workerd’sTypeErrorfor a non-GET request, a206response, aVary: *header, an error response, or a WebSocket response. A304response is ignored.- Freshness comes from
Cache-Control(s-maxage, thenmax-age) andExpires, evaluated as a shared cache. The requestCache-Controlis ignored. no-store,private,no-cache, and anySet-Cookieheader mean the entry is not stored.put()still resolves, as in workerd.Cache-Control: private=Set-Cookiestores the response without itsSet-Cookieheader.match()returns the entry withcf-cache-status: HIT, orundefined. Expired entries miss.match()honoursIf-None-MatchandIf-Modified-Since(304), andRange(206, or416withContent-Range: bytes */<size>).match()anddelete()treat a non-GET request as a miss unlessignoreMethod: true.delete()returnstrueonly when a live entry was removed.request.cf.cacheKeyreplaces the URL as the key when set.
Orange does not copy Cloudflare’s 512 MB limit on a cached body. Bodies are
stored in 512 KiB chunks, one commit each, so Rivet’s commit limit does not
bound an entry either. A put commits its body atomically, so a
concurrent match() reads one whole body, never a mix of two puts.
fetch() with cf.cacheTtl or cf.cacheEverything
These options do not use the Cache API. Subrequests always go to the origin.
wrangler dev (Miniflare) does the same. On Cloudflare they control the zone’s
edge cache, which is separate from the Cache API.
Conformance
src/runtime/cache.test.ts drives the Cache API through a real orange dev
over HTTP: hits, expiry, uncacheable responses, 304, 206 and 416,
concurrent puts on one URL, restarts, and ORANGE_CACHE=off.
Limits and deviations
- There is one cache per project, not one per data center. Entries are not evicted before they expire.
Varyis ignored for matching. OnlyVary: *changes behaviour:put()throws.- A
Rangewith several ranges gets the whole body with200. Miniflare sendsmultipart/byteranges. - Orange serves
206for any stored entry. Cloudflare requires a storedContent-Length. - The per-request call limit (50 or 1,000) is not enforced.
cache.add()andcache.addAll()are not defined.- In a Worker with no Durable Objects or storage bindings, the first Cache API call starts the Rivet Engine and waits a few seconds.
Compatibility
From the latest conformance run. Scores on the compatibility page; every binding on the status page.
Cache API
Runs on Internal Rivet Actor per cache name, with Miniflare's freshness rules.
Entries persist locally. Vary is ignored, and several ranges return the whole body. ORANGE_CACHE=off gives the workers.dev no-op.
Scales by Cache name. One actor per cache name. caches.default is one actor, so busy traffic should use named caches.
Recorded results
WorksCache APIAvailable.caches.default and named caches work, on Miniflare's freshness rules. Set ORANGE_CACHE=off to turn it off. 1/1 pass
- Results
- example: cache-api Pass