cr-drip
cr-drip
A lightweight, thread-safe sliding window rate limiting and throttling shard for Crystal with fluent builder, wait budgets, and declarative macro-wrapped instance methods. Zero external dependencies.
Features
- Includes-Based Declarative API: Wrap instance methods with
driporrate_limitmacros including sliding window durations, wait budgets, fallbacks, and lifecycle hooks. - Fluent Builder API: Chain
.key(),.allow(),.per(),.wait(),.count(),.on_limited(),.on_throttled(), and.work { ... }. - Direct Function API: Call
Drip.drip(key, ...)orDrip.consume(key, ...)directly. - First-Class Crystal Durations: Native support for
Time::Span(e.g.2.seconds,500.milliseconds,1.day), duration strings ("500ms","1s","1m","1h","1d"), or numeric milliseconds. - Fail-Fast or Wait Budget: Reject immediately with
Drip::RateLimitErroror configurewait:to throttle/queue callers until the next token arrives. - Lifecycle Hooks: Observe
:limitedand:throttledevents with custom handlers. - Thread-Safe: Monotonic timing (
Time.instant) withMutexsynchronization across concurrent fibers.
Installation
Add it to your shard.yml:
dependencies:
cr-drip:
github: Gwash3189/cr-drip
version: ~> 0.1.0
Then run:
shards install
And require it:
require "cr-drip"
Usage
1. Includes-Based API (Declarative Method Wrapping)
Include Drip into your class to wrap instance methods declaratively:
class ApiClient
include Drip
drip :fetch_user,
allow: 10,
per: 1.second,
wait: 500.milliseconds,
key: ->(id : String) { "user:#{id}" },
fallback: :rate_limit_fallback,
on_limited: :alert_limited,
on_throttled: :log_throttled
def fetch_user(id : String) : String
# Remote HTTP call or database operation
"user_#{id}"
end
def rate_limit_fallback(error : Drip::RateLimitError) : String
"cached_fallback"
end
def alert_limited(error : Drip::RateLimitError, key : String)
Log.warn { "Rate limited on #{key}, retry after #{error.retry_after_ms}ms" }
end
def log_throttled(wait : Time::Span, key : String)
Log.info { "Throttling #{key} for #{wait.total_milliseconds}ms" }
end
end
The macro rate_limit is also available as an alias for drip.
2. Fluent Builder API
result = Drip.key("api:org-42")
.allow(100)
.per(1.minute) # or "1m", 60_000
.wait(5.seconds)
.on_throttled { |wait, key| Log.info { "Throttled #{key} for #{wait}" } }
.on_limited { |err, key| Log.warn { "Exceeded #{key}: #{err.message}" } }
.work do
call_external_api
end
3. Direct Function API
# With a work block:
result = Drip.drip("api:org-42", allow: 10, per: 1.second, wait: 500.milliseconds) do
call_external_api
end
# Alias RateLimit also supported:
RateLimit.rate_limit("api:org-42", allow: 10, per: 1.second) do
call_external_api
end
4. Consuming Tokens Without a Block
# Via builder
status = Drip.key("user:123").allow(10).per(1.minute).consume(1)
puts status.remaining # 9
# Direct module call
next_status = Drip.consume("user:123", allow: 10, per: 1.minute, count: 2)
puts next_status.remaining # 7
5. Resetting State
# Reset a specific key
Drip.reset("api:org-42")
Drip.key("api:org-42").reset
# Reset all rate limit buckets
Drip.reset_all
Options Reference
| Option | Type | Default | Description |
|---|---|---|---|
allow |
Int32 |
10 |
Maximum number of allowed tokens within the window. |
per |
Time::Span | Number | String |
1.second |
Duration of the sliding window (2.seconds, 500.milliseconds, "1s", 1000). |
wait |
Time::Span | Number | String |
0.seconds |
Maximum time to wait for a token before rejecting. |
count |
Int32 |
1 |
Token cost per invocation. |
key |
Proc | Symbol | String |
ClassName#method |
Custom rate limit key or key generator. |
fallback |
Symbol | Proc |
none | Method or proc invoked when rate limited instead of raising. |
on_limited |
Symbol | Proc |
none | Callback invoked when rate limit is exceeded. |
on_throttled |
Symbol | Proc |
none | Callback invoked when execution is delayed within wait budget. |
Handlers and Fallbacks
Lifecycle handlers (on_limited, on_throttled) and fallbacks can be declared with flexible argument signatures:
# on_limited: (error, key), (error), or 0 args
def alert_limited(error : Drip::RateLimitError, key : String); end
def alert_limited(error : Drip::RateLimitError); end
def alert_limited; end
# on_throttled: (wait, key), (wait), or 0 args
def log_throttled(wait : Time::Span, key : String); end
def log_throttled(wait : Time::Span); end
def log_throttled; end
# fallback: (error) or 0 args
def rate_limit_fallback(error : Drip::RateLimitError); end
def rate_limit_fallback; end
Supported Method Shapes
The includes-based macro wraps methods while completely preserving:
- Positional, optional, and default arguments
- Named arguments and keyword arguments
- Splat (
*args) and double-splat (**options) parameters - Yield-based blocks and typed captured blocks
- Private and protected method visibilities
- Method names ending in
?or!
Development
shards install
crystal spec --error-on-warnings
crystal tool format --check src spec
License
MIT License. Copyright (c) 2026 Adam Beck.
Repository
cr-drip
Owner
Statistic
- 0
- 0
- 0
- 0
- 0
- about 6 hours ago
- November 3, 2026
License
MIT License
Links
Synced at
Tue, 03 Nov 2026 09:00:00 GMT
Languages