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)
- ๐ฆ More Packages โ other packages by Jozz
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.
โคด๏ธ Back -> Table of Contents
๐ฆ More jozz
Packages
โคด๏ธ Back โ Table of Contents
Iโm Jozz โ and my packages share a simple philosophy: developer experience first. I try to avoid boilerplate wherever possible, and most of these packages were born out of real needs in my own projects. Each one comes with clear documentation, minimal setup, and APIs that are easy to pick up without surprises.
Theyโre built to be lightweight, reliable, and ready for production, always with simplicity in mind. There are more packages in the works, following the same approach. If you find them useful and feel like supporting, youโre welcome to do so (:
- shrink โ Compress Anything in One Line
- track โ Persistent Streaks, Counters & Records
- hivez โ Hive, but Safer & Smarter
- time_plus โ Smarter DateTime & Duration Extensions
- prf โ SharedPreferences, Without the Pain
- exui โ Supercharge Your Flutter UI
- jozz_events โ Strongly-Typed Events for Clean Architecture
๐ฝ shrink โ Compress Anything in One Line
Because every byte counts. shrink
makes data compression effortless with a one-line API and fully lossless results. It auto-detects the best method, often cutting size by 5ร to 40ร (and up to 1,000ร+ for structured data). Perfect for Firestore, local storage, or bandwidth-sensitive apps. Backed by clear docs and real-world benchmarks.
๐ track โ Persistent Streaks, Counters & Records
Define once, track forever. track
gives you plug-and-play tools for streaks, counters, activity logs, and records โ all persisted safely across sessions and isolates. From daily streaks to rolling counters to best-ever records, it handles resets, history, and storage automatically. Clean APIs, zero boilerplate, and deeply detailed documentation.
๐ hivez โ Hive, but Safer & Smarter
hivez
is a production-ready layer on top of Hive CE that keeps its raw speed but makes it safer and easier to use. It auto-initializes boxes, enforces type safety, and gives you a single unified API for Box, LazyBox, and IsolatedBox. Concurrency issues are handled with built-in locks, and you also get extras like backup/restore, search, and crash recovery. Backed by clear, detailed documentation, hivez
is designed for real-world apps where you want Hiveโs performance without the boilerplate or pitfalls.
โฑ time_plus โ Smarter DateTime & Duration Extensions
Stop wrestling with DateTime
and Duration
. time_plus
adds the missing tools you wish Dart had built in: add and subtract time units, start/end of day/week/month, compare by precision, yesterday/tomorrow, fractional durations, and more. Built with 128+ extensions, 700+ tests, and zero dependencies, itโs faster, more precise, and more reliable than the classic time
package โ while keeping APIs clear and intuitive. Ideal for scheduling, analytics, or any app where every microsecond counts.
โก prf โ SharedPreferences, Without the Pain
No strings, no boilerplate, no setup. prf
lets you define variables once, then get()
and set()
them anywhere with a type-safe API. It fully replaces raw SharedPreferences
with support for 20+ built-in types (including DateTime
, Duration
, Uint8List
, JSON, and enums). Every variable is cached, test-friendly, and isolate-safe with a .isolated
mode. Designed for clarity, scale, and zero friction, with docs that make local persistence finally headache-free.
๐จ exui โ Supercharge Your Flutter UI
Everything your widgets wish they had. exui
is a zero-dependency extension library for Flutter with 200+ chainable utilities for padding, margin, centering, gaps, visibility, constraints, gestures, buttons, text styling, and more โ all while keeping your widget tree fully native.
No wrappers. No boilerplate. Just concise, expressive methods that feel built into Flutter itself. Backed by hundreds of unit tests and exceptional documentation, exui
makes UI code cleaner, faster, and easier to maintain.
๐ข jozz_events โ Strongly-Typed Events for Clean Architecture
A domain-first, framework-agnostic event bus built for scalable apps. jozz_events
enables decoupled, strongly-typed communication between features and layers โ without the spaghetti. Itโs lightweight, dependency-free, lifecycle-aware, and integrates naturally with Clean Architecture. Ideal for Flutter or pure Dart projects where modularity, testability, and clarity matter most.
๐ License MIT ยฉ Jozz
Libraries
- limit
limit
Package Barrel File