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.
- โฒ 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
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
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 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.)
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
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
if (await cooldown.isCooldownActive()) {
print('Wait before trying again!');
}await cooldown.activateCooldown();This sets the cooldown to now and begins the countdown. The activation count is automatically incremented.
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).
await cooldown.reset(); // Clears only the time
await cooldown.completeReset(); // Clears time and resets usage counterfinal 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.0final lastUsed = await cooldown.getLastActivationTime();
final endsAt = await cooldown.getEndTime();await cooldown.whenExpires(); // Completes only when cooldown is overfinal count = await cooldown.getActivationCount();
print('Used $count times');await cooldown.removeAll(); // Clears all stored cooldown state
final exists = await cooldown.anyStateExists(); // Returns true if anything is storedYou can create as many cooldowns as you need โ each with a unique prefix. All state is persisted, isolate-safe, and instantly reusable.
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: EnablinguseCachedisables isolate safety. Use only when you're sure no other isolate accesses the same key.
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.
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.
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.
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.
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.
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();To fully refill the bucket and reset the refill clock:
await limiter.reset();Use this after manual overrides, feature unlocks, or privileged user actions.
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();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: EnablinguseCachedisables isolate safety. Use only when you're sure no other isolate accesses the same key.
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.
