img

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

โฒ Cooldown

"Only once every 24 hours"
โ†’ Fixed cooldown timer from last activation
โ†’ Great for claim buttons, retry delays, or cooldown locks

๐Ÿ“Š RateLimiter

"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() โ€” Returns true if the cooldown is still active
  • isExpired() โ€” Returns true if the cooldown has expired or was never started
  • activateCooldown() โ€” Starts the cooldown using the configured duration
  • tryActivate() โ€” Starts cooldown only if it's not active โ€” returns whether it was triggered
  • reset() โ€” Clears the cooldown timer, but keeps the activation count
  • completeReset() โ€” Fully resets both the cooldown and its usage counter
  • timeRemaining() โ€” Returns remaining time as a Duration
  • secondsRemaining() โ€” Same as above, in seconds
  • percentRemaining() โ€” Progress indicator between 0.0 and 1.0
  • getLastActivationTime() โ€” Returns DateTime? of last activation
  • getEndTime() โ€” Returns when the cooldown will end
  • whenExpires() โ€” Returns a Future that completes when the cooldown ends
  • getActivationCount() โ€” Returns the total number of activations
  • removeAll() โ€” Deletes all stored values (for testing/debugging)
  • anyStateExists() โ€” Returns true 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; returns true if allowed, or false if rate-limited
  • isLimitedNow() โ€” Returns true if no tokens are currently available
  • isReady() โ€” Returns true if at least one token is available
  • getAvailableTokens() โ€” Returns the current number of usable tokens (calculated live)
  • timeUntilNextToken() โ€” Returns a Duration until at least one token will be available
  • nextAllowedTime() โ€” Returns the exact DateTime when a token will be available
  • reset() โ€” Resets to full token count and updates last refill to now
  • removeAll() โ€” Deletes all limiter state (for testing/debugging)
  • anyStateExists() โ€” Returns true if limiter data exists in storage
  • runIfAllowed(action) โ€” Runs a callback if allowed, otherwise returns null
  • 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.


๐Ÿ”— License MIT ยฉ Jozz

โ˜• Enjoying this package? You can support it here.

Libraries

limit
limit Package Barrel File
services/cooldown
services/rate_limiter