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
DateTimecomparisons - 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()— Returnstrueif the cooldown is still activeisExpired()— Returnstrueif 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 aDurationsecondsRemaining()— Same as above, in secondspercentRemaining()— Progress indicator between0.0and1.0getLastActivationTime()— ReturnsDateTime?of last activationgetEndTime()— Returns when the cooldown will endwhenExpires()— Returns aFuturethat completes when the cooldown endsgetActivationCount()— Returns the total number of activationsremoveAll()— Deletes all stored values (for testing/debugging)anyStateExists()— Returnstrueif 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
useCachedisables 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
prftypes (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; returnstrueif allowed, orfalseif rate-limitedisLimitedNow()— Returnstrueif no tokens are currently availableisReady()— Returnstrueif at least one token is availablegetAvailableTokens()— Returns the current number of usable tokens (calculated live)timeUntilNextToken()— Returns aDurationuntil at least one token will be availablenextAllowedTime()— Returns the exactDateTimewhen a token will be availablereset()— Resets to full token count and updates last refill to nowremoveAll()— Deletes all limiter state (for testing/debugging)anyStateExists()— Returnstrueif limiter data exists in storagerunIfAllowed(action)— Runs a callback if allowed, otherwise returnsnulldebugStats()— 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
useCachedisables isolate safety. Use only when you're sure no other isolate accesses the same key.