
Security News
Risky Biz Podcast: Making Reachability Analysis Work in Real-World Codebases
This episode explores the hard problem of reachability analysis, from static analysis limits to handling dynamic languages and massive dependency trees.
TypedCache is a lightweight, type-safe façade around your favourite Ruby cache stores. It adds three things on top of the raw back-end implementation:
Namespace
helpers prevent key collisions. You can create nested namespaces easily, like Namespace.at("users", "profiles", "avatars")
.Either
, Maybe
, and Snapshot
wrap cache results so you always know whether you have a value, an error, or a cache-miss.TL;DR – Think Faraday or Rack middlewares, but for caching.
bundle add typed_cache && bundle install
# or
gem install typed_cache
This gem does is also cryptographically signed, if you want to ensure the gem was not tampered with, make sure to use these commands:
bundle add typed_cache && bundle install --trust-policy=HighSecurity
# or
gem install typed_cache -P HighSecurity
If there are issues with unsigned gems, use MediumSecurity
instead.
require "typed_cache"
# 1. Build a store
store = TypedCache.builder
.with_backend(:memory, shared: true)
.with_instrumentation(:rails) # e.g. using ActiveSupport
.build
.value # unwrap Either for brevity
# 2. Get a reference to a key
user_ref = store.ref("users:123") # => CacheRef
# 3. Fetch and compute if absent
user_snapshot = user_ref.fetch do
puts "Cache miss! Computing..."
{ id: 123, name: "Jane" }
end.value # => Snapshot
puts "Found: #{user_snapshot.value} (from_cache?=#{user_snapshot.from_cache?})"
Step | Purpose |
---|---|
with_backend(:name, **opts) | Mandatory. Configure the concrete Backend and its options. |
with_decorator(:key) | Optional. Add a decorator by registry key. |
with_instrumentation(:source) | Optional. Add instrumentation, e.g. :rails or :dry . |
build | Returns Either[Error, Store] . |
TypedCache::Backend
) – persists data (Memory, Redis, etc.).TypedCache::Decorator
) – wraps an existing store to add
behaviour (Instrumentation, Logging, Circuit-Breaker …).Both include the same public Store
interface, so they can be composed
freely. Registries keep them separate:
TypedCache::Backends.available # => [:memory, :active_support]
TypedCache::Decorators.available # => [:instrumented]
class RedisBackend
include TypedCache::Backend
# … implement #read, #write, etc.
end
TypedCache::Backends.register(:redis, RedisBackend)
class LogDecorator
include TypedCache::Decorator
def initialize(store) = @store = store
def write(key, value)
puts "[cache] WRITE #{key}"
@store.write(key, value)
end
# delegate the rest …
end
TypedCache::Decorators.register(:logger, LogDecorator)
All operations return one of:
Either.right(Snapshot)
– successEither.left(CacheMissError)
– key not presentEither.left(StoreError)
– transport / serialization failureUse the monad directly or pattern-match:
result.fold(
->(err) { warn err.message },
->(snapshot) { puts snapshot.value },
)
CacheRef
and Store
APIsWhile you can call read
, write
, and fetch
directly on the store
, the more powerful way to work with TypedCache is via the CacheRef
object. It provides a rich, monadic API for a single cache key. The Store
also provides fetch_all
for batch operations.
You get a CacheRef
by calling store.ref(key)
:
user_ref = store.ref("users:123") # => #<TypedCache::CacheRef ...>
Now you can operate on it:
# Fetch a value, computing it if it's missing
snapshot_either = user_ref.fetch do
{ id: 123, name: "Jane Doe" }
end
# The result is always an Either[Error, Snapshot]
snapshot_either.fold(
->(err) { warn "Something went wrong: #{err.message}" },
->(snapshot) { puts "Got value: #{snapshot.value} (from cache: #{snapshot.from_cache?})" }
)
# You can also map over values
name_either = user_ref.map { |user| user[:name] }
puts "User name is: #{name_either.value.value}" # unwrap Either, then Snapshot
fetch_all
For retrieving multiple keys at once, the Store
provides a fetch_all
method. This is more efficient than fetching keys one by one, especially with remote back-ends like Redis.
It takes a list of keys and a block to compute the values for any missing keys.
user_refs = store.fetch_all("users:123", "users:456") do |missing_key|
# This block is called for each cache miss
user_id = missing_key.split(":").last
puts "Cache miss for #{missing_key}! Computing..."
{ id: user_id, name: "Fetched User #{user_id}" }
end
user_refs.each do |key, snapshot_either|
snapshot_either.fold(
->(err) { warn "Error for #{key}: #{err.message}" },
->(snapshot) { puts "Got value for #{key}: #{snapshot.value}" }
)
end
The CacheRef
API encourages a functional style and makes composing cache operations safe and predictable.
TypedCache can publish events about cache operations using different instrumenters. To enable it, use the with_instrumentation
method on the builder, specifying an instrumentation backend:
# For ActiveSupport::Notifications (e.g. in Rails)
store = TypedCache.builder
.with_backend(:memory)
.with_instrumentation(:rails)
.build.value
# For Dry::Monitor
store = TypedCache.builder
.with_backend(:memory)
.with_instrumentation(:dry)
.build.value
Events are published to a topic like typed_cache.<operation>
(e.g., typed_cache.write
). The topic namespace can be configured.
Payload keys include: :namespace, :key, :operation, :duration
, and cache_hit
.
You can subscribe to these events like so:
# Example for ActiveSupport
ActiveSupport::Notifications.subscribe("typed_cache.write") do |name, start, finish, id, payload|
# Or you can subscribe via the store object itself
instrumenter = store.instrumenter
instrumenter.subscribe("write") do |event|
payload = event.payload
puts "Cache WRITE for key #{payload[:key]} took #{payload[:duration]}ms. Hit? #{payload[:cache_hit]}"
end
If you call with_instrumentation
with no arguments, it uses a Null
instrumenter, which has no overhead.
Just like with back-ends and decorators, you can write and register your own instrumenters. An instrumenter must implement an instrument
and a subscribe
method.
class MyCustomInstrumenter
include TypedCache::Instrumenter
def instrument(operation, key, **payload, &block)
# ... your logic ...
end
def subscribe(event_name, **filters, &block)
# ... your logic ...
end
end
# Register it
TypedCache::Instrumenters.register(:custom, MyCustomInstrumenter)
# Use it
store = TypedCache.builder
.with_instrumentation(:custom)
# ...
For more advanced scenarios—including Rails integration, pattern matching, custom back-ends, and testing—see examples.md.
This work is licensed under the Apache-2.0 license.
FAQs
Unknown package
We found that typed_cache demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
This episode explores the hard problem of reachability analysis, from static analysis limits to handling dynamic languages and massive dependency trees.
Security News
/Research
Malicious Nx npm versions stole secrets and wallet info using AI CLI tools; Socket’s AI scanner detected the supply chain attack and flagged the malware.
Security News
CISA’s 2025 draft SBOM guidance adds new fields like hashes, licenses, and tool metadata to make software inventories more actionable.