Skip to content

Cache, counter & storage

Three small key-addressed contracts. The cache is read-through key/value with TTLs (Caching); the counter is an atomic monotonic sequence; object storage holds blobs by key. The ports-and-adapters model behind them is in Contracts.

Cache

ctx.cache(spec) returns a combined read/write CachePort. Keys are strings within the spec's namespace. Values must be JSON-serializable, or pre-encoded bytes (stored verbatim, returned for the caller to decode) — don't rely on key ordering or non-JSON types surviving a round trip.

CacheSpec — most fields tune document read-through; the deep behavior is in Caching:

Field Type Default Meaning
name str \| StrEnum required namespace for the keys
ttl timedelta 300s default entry lifetime
ttl_pointer timedelta 60s TTL for versioned-cache pointers
early_refresh_beta float \| None None probabilistic early refresh (XFetch)
early_refresh_background bool False run elected refreshes detached, off the read path
l1 L1Spec \| None None opt-in in-process L1 ahead of the backend
sliding_ttl timedelta \| None None expire-after-access for versioned entries (capped by ttl)
age_ttl AgeBasedTtl \| None None age-proportional per-entry lifetime
Method Signature Notes
get get(key) value or None on miss
get_many get_many(keys) (found_mapping, missing_keys)
exists exists(key) presence check without payload transfer
set set(key, value, *, ttl=None) store
set_many set_many(mapping, *, ttl=None) bulk store
set_versioned set_versioned(key, version, value, *, ttl=None) store tagged with a version
set_many_versioned set_many_versioned(mapping, *, ttl=None) keyed by (key, version)
delete delete(key, *, hard) hard is required
delete_many delete_many(keys, *, hard) hard is required

TTLs default from the CacheSpec; the per-entry ttl= overrides that entry's lifetime alone (the seam the adaptive lifetimes write through). See Cache reads with Redis.

Counter

ctx.counter(spec) returns a CounterPort — an atomic, monotonic sequence within the spec's namespace (CounterSpec carries only a name). suffix partitions counters under one spec.

Method Signature Notes
incr incr(by=1, *, suffix=None) increment, return the new value
incr_batch incr_batch(size=2, *, suffix=None) allocate size sequential values at once
decr decr(by=1, *, suffix=None) decrement, return the new value
reset reset(value=1, *, suffix=None) reset, return value

Storage

Object storage splits into query and command ports — ctx.storage.query(spec) / ctx.storage.command(spec) (StorageSpec carries only a name). Objects are addressed by string key.

Port Method Signature Notes
query download download(key) the whole object, buffered — a DownloadedObject (bytes + metadata)
query download_stream download_stream(key) a StreamedDownload — single-use async chunk iterator, never buffers the whole object; encrypted objects decrypt chunk by chunk
query download_range download_range(key, *, start, end=None) inclusive byte range as a RangedDownload (HTTP Range semantics); a start beyond the object is a precondition error (the 416 equivalent)
query download_if_changed download_if_changed(key, *, if_none_match=None, if_modified_since=None) None when unchanged (the 304 equivalent); at least one condition required
query head head(key, *, include_tags=False) an ObjectHead — size, content type, ETag, last-modified — without the body; works for raw presigned uploads too
query presign_download presign_download(key, *, expires_in) time-limited direct-GET URL — a bearer credential, never log it
query list list(limit, offset, *, prefix=None, include_tags=False) (objects, total_count)
command upload upload(obj) takes an UploadedObject, returns StoredObject metadata
command upload_stream upload_stream(chunks, *, filename, ...) bounded-memory multipart upload from an async chunk iterator; an encrypting route seals it chunk by chunk
command overwrite_stream overwrite_stream(key, chunks, *, ...) replace an existing key in place, in bounded memory (the re-encryption seam)
command presign_upload presign_upload(key, *, expires_in, content_type=None) time-limited direct-PUT URL — a write grant, so it lives on the command port
command delete delete(key) remove by key
command copy / move copy(src_key, dst_key) server-side, same-bucket; move is copy-then-delete (non-atomic)
command put_object_tags put_object_tags(key, tags) full tag replacement

A third port, StorageUploadSessionPort, drives resumable multipart uploads (begin / presign part / list parts / complete / abort) — its HTTP projection is on the FastAPI route generators page. Ranged reads work over chunked-AEAD encrypted objects (only the covering chunks are fetched and decrypted); a legacy whole-payload envelope cannot be sliced and refuses with core.storage.range_whole_payload_unsupported.

Implemented by

Contract Backend Integration
Cache Redis Redis
Counter Redis Redis
Object storage S3, GCS S3 · GCS

The in-memory mock implements all three for tests.