limit 1.0.8
limit: ^1.0.8 copied to clipboard
Persistent across sessions, no manual storage needed. Easily manage cooldowns and rate limits with one-line.
Persistent limits. Effortlessly automated.
One line. No boilerplate. No setup. The limit package gives you instant, persistent control over cooldowns and rate limits — across sessions, isolates, and app restarts. Define once, automate forever.
Table of Contents
- ⏲ Cooldown — automatically manage cooldown periods (e.g. daily rewards, retry delays)
- 📊 Rate Limiter — control rates using a token bucket (e.g. 1000 actions per 15 minutes)
💥 Why Use limit
? #
Working with cooldowns and rate limits usually means:
- Manual
DateTime
comparisons - Writing timers or expiration logic
- Saving timestamps or counters
- Handling null, casting, and cleanup
limit removes all that: you just define, call, and trust it.
- ✅ Lets you define, control, and forget — the system handles everything in the background
- ✅ One-line setup, no manual storage or timers
- ✅ Persisted across app restarts and isolates
- ✅ Async-safe and cache-friendly
- ✅ Works great for daily rewards, retry delays, API limits, chat quotas, and more
🚀 Choosing the Right Limiter #
Each limiter is tailored for a specific pattern of time-based control.
Goal | Use |
---|---|
"Only once every X time" | Cooldown |
"Allow N actions per Y minutes" | RateLimiter |
"Only once every 24 hours"
→ Fixed cooldown timer from last activation
→ Great for claim buttons, retry delays, or cooldown locks
"Allow 100 actions per 15 minutes (rolling refill)"
→ Token bucket algorithm
→ Replenishes tokens over time (not per action)
→ Great for APIs, messaging, or hard quota control
⏲ Cooldown
Persistent Cooldown Service #
⤴️ Back -> Table of Contents
Cooldown
is a plug-and-play utility service for managing cooldown windows (e.g. daily rewards, button lockouts, retry delays) that persist across sessions and isolates — no timers, no manual bookkeeping, no re-implementation every time.
It handles:
- Cooldown timing (
DateTime.now()
+ duration) - Persistent storage (with caching and async-safety)
- Activation tracking and expiration logic
- Usage statistics (activation count, expiry progress, etc.)
🔧 How to Use #
isCooldownActive()
— Returnstrue
if the cooldown is still activeisExpired()
— Returnstrue
if the cooldown has expired or was never startedactivateCooldown()
— Starts the cooldown using the configured durationtryActivate()
— Starts cooldown only if it's not active — returns whether it was triggeredreset()
— Clears the cooldown timer, but keeps the activation countcompleteReset()
— Fully resets both the cooldown and its usage countertimeRemaining()
— Returns remaining time as aDuration
secondsRemaining()
— Same as above, in secondspercentRemaining()
— Progress indicator between0.0
and1.0
getLastActivationTime()
— ReturnsDateTime?
of last activationgetEndTime()
— Returns when the cooldown will endwhenExpires()
— Returns aFuture
that completes when the cooldown endsgetActivationCount()
— Returns the total number of activationsremoveAll()
— Deletes all stored values (for testing/debugging)anyStateExists()
— Returnstrue
if any cooldown data exists in storage
✅ Define a Cooldown
final cooldown = Cooldown('daily_reward', duration: Duration(hours: 24));
This creates a persistent cooldown that lasts 24 hours. It uses the prefix 'daily_reward'
to store:
- Last activation timestamp
- Activation count
🔍 Check If Cooldown Is Active
if (await cooldown.isCooldownActive()) {
print('Wait before trying again!');
}
⏱ Activate the Cooldown
await cooldown.activateCooldown();
This sets the cooldown to now and begins the countdown. The activation count is automatically incremented.
⚡ Try Activating Only If Expired
if (await cooldown.tryActivate()) {
print('Action allowed and cooldown started');
} else {
print('Still cooling down...');
}
Use this for one-line cooldown triggers (e.g. claiming a daily gift or retrying a network call).
🧼 Reset or Fully Clear Cooldown
await cooldown.reset(); // Clears only the time
await cooldown.completeReset(); // Clears time and resets usage counter
🕓 Check Time Remaining
final remaining = await cooldown.timeRemaining();
print('Still ${remaining.inMinutes} minutes left');
You can also use:
await cooldown.secondsRemaining(); // int
await cooldown.percentRemaining(); // double between 0.0–1.0
📅 View Timing Info
final lastUsed = await cooldown.getLastActivationTime();
final endsAt = await cooldown.getEndTime();
⏳ Wait for Expiry (e.g. for auto-retry)
await cooldown.whenExpires(); // Completes only when cooldown is over
📊 Get Activation Count
final count = await cooldown.getActivationCount();
print('Used $count times');
🧪 Test Utilities
await cooldown.removeAll(); // Clears all stored cooldown state
final exists = await cooldown.anyStateExists(); // Returns true if anything is stored
You can create as many cooldowns as you need — each with a unique prefix. All state is persisted, isolate-safe, and instantly reusable.
⚡ Optional useCache
Parameter #
Each limiter accepts a useCache
flag:
final cooldown = Cooldown(
'name_key',
duration: Duration(minutes: 5),
useCache: true // false by default
);
-
useCache: false
(default):- Fully isolate-safe
- Reads directly from storage every time
- Best when multiple isolates might read/write the same data
-
useCache: true
:- Uses memory caching for faster access
- Not isolate-safe — may lead to stale or out-of-sync data across isolates
- Best when used in single-isolate environments (most apps)
⚠️ Warning: Enabling
useCache
disables isolate safety. Use only when you're sure no other isolate accesses the same key.
📊 RateLimiter
Token Bucket Rate Limiter #
⤴️ Back -> Table of Contents
RateLimiter
is a high-performance, plug-and-play utility that implements a token bucket algorithm to enforce rate limits — like “100 actions per 15 minutes” — across sessions, isolates, and app restarts.
It handles:
- Token-based rate limiting
- Automatic time-based token refill
- Persistent state using
prf
types (PrfIso<double>
,PrfIso<DateTime>
) - Async-safe, isolate-compatible behavior
Perfect for chat limits, API quotas, retry windows, or any action frequency cap — all stored locally.
🔧 How to Use #
tryConsume()
— Tries to use 1 token; returnstrue
if allowed, orfalse
if rate-limitedisLimitedNow()
— Returnstrue
if no tokens are currently availableisReady()
— Returnstrue
if at least one token is availablegetAvailableTokens()
— Returns the current number of usable tokens (calculated live)timeUntilNextToken()
— Returns aDuration
until at least one token will be availablenextAllowedTime()
— Returns the exactDateTime
when a token will be availablereset()
— Resets to full token count and updates last refill to nowremoveAll()
— Deletes all limiter state (for testing/debugging)anyStateExists()
— Returnstrue
if limiter data exists in storagerunIfAllowed(action)
— Runs a callback if allowed, otherwise returnsnull
debugStats()
— Returns detailed internal stats for logging and debugging
The limiter uses fractional tokens internally to maintain precise refill rates, even across app restarts. No timers or background services required — it just works.
✅ RateLimiter
Basic Setup
Create a limiter with a key, a maximum number of actions, and a refill duration:
final limiter = RateLimiter(
'chat_send',
maxTokens: 100,
refillDuration: Duration(minutes: 15),
);
This example allows up to 100 actions per 15 minutes. The token count is automatically replenished over time — even after app restarts.
🚀 Check & Consume
To attempt an action:
final canSend = await limiter.tryConsume();
if (canSend) {
// Allowed – proceed with the action
} else {
// Blocked – too many actions, rate limit hit
}
Returns true
if a token was available and consumed, or false
if the limit was exceeded.
🧮 Get Available Tokens
To check how many tokens are usable at the moment:
final tokens = await limiter.getAvailableTokens();
print('Tokens left: ${tokens.toStringAsFixed(2)}');
Useful for debugging, showing rate limit progress, or enabling/disabling UI actions.
⏳ Time Until Next Token
To wait or show feedback until the next token becomes available:
final waitTime = await limiter.timeUntilNextToken();
print('Try again in: ${waitTime.inSeconds}s');
You can also get the actual time point:
final nextTime = await limiter.nextAllowedTime();
🔁 Reset the Limiter
To fully refill the bucket and reset the refill clock:
await limiter.reset();
Use this after manual overrides, feature unlocks, or privileged user actions.
🧼 Clear All Stored State
To wipe all saved token/refill data (for debugging or tests):
await limiter.removeAll();
To check if the limiter has any stored state:
final exists = await limiter.anyStateExists();
⚡ Optional useCache
Parameter #
Each limiter accepts a useCache
flag:
final limiter = RateLimiter(
'key',
maxTokens: 10,
refillDuration: Duration(minutes: 5),
useCache: true // false by default
);
-
useCache: false
(default):- Fully isolate-safe
- Reads directly from storage every time
- Best when multiple isolates might read/write the same data
-
useCache: true
:- Uses memory caching for faster access
- Not isolate-safe — may lead to stale or out-of-sync data across isolates
- Best when used in single-isolate environments (most apps)
⚠️ Warning: Enabling
useCache
disables isolate safety. Use only when you're sure no other isolate accesses the same key.