stream_event_guard 0.1.0 copy "stream_event_guard: ^0.1.0" to clipboard
stream_event_guard: ^0.1.0 copied to clipboard

Keyed duplicate suppression and cooldown control for synchronous and asynchronous Dart events.

stream_event_guard #

Keyed duplicate suppression and cooldown control for synchronous and asynchronous Dart events.

EventGuard prevents the same logical event from running more than once at the same time or during a configurable post-success cooldown. Each key has independent state, so unrelated events can still run concurrently.

Features #

  • Drops duplicate work while the same key is running.
  • Applies an optional cooldown after successful work.
  • Keeps different keys independent.
  • Accepts synchronous and asynchronous actions.
  • Preserves nullable action results.
  • Reports whether work executed or why it was dropped.
  • Releases failed keys immediately for retry.
  • Removes per-key state when it is no longer needed.
  • Uses no runtime dependencies and works in Dart and Flutter applications.

Installation #

dart pub add stream_event_guard

For a Flutter application, use flutter pub add stream_event_guard.

Stream usage #

Create a guard with the type used to identify equivalent events. The following listener allows different QR codes to run concurrently while deduplicating bursts of the same code:

import 'package:stream_event_guard/stream_event_guard.dart';

final guard = EventGuard<String>(
  cooldown: const Duration(seconds: 2),
);

scannerStream.listen((code) {
  guard.run(
    key: code,
    action: () async {
      await processCode(code);
    },
  );
});

EventGuard does not transform or subscribe to the stream itself. It is used at the event-processing boundary, so the same API also works with QR/barcode scanners, NFC, BLE, sensors, button events, WebSockets, and direct method calls. The returned Future can be ignored when drop details are not relevant. As with any fire-and-forget asynchronous call, action failures should be handled inside the action or by the application's error zone.

For a given key, the lifecycle is:

absent -> running -> cooldown -> absent
              |
              +-- failure ----> absent

While running, another submission returns Dropped with DropReason.alreadyRunning. After a successful action, duplicates during the configured cooldown return DropReason.cooldown. The cooldown begins when the action completes, not when it starts.

The default cooldown is Duration.zero, which only protects against overlapping executions:

final submitGuard = EventGuard<int>();

Result handling #

run<R> returns Future<GuardResult<R>>:

  • Executed<R> contains the exact value returned by the action, including null when R is nullable.
  • Dropped<R> contains either DropReason.alreadyRunning or DropReason.cooldown.

The action can return R or Future<R>. If it throws synchronously or completes with an error, the original error is propagated. Failed actions do not start a cooldown, so the same key can be retried immediately.

Object events and keys #

The generic parameter in EventGuard<K> is the key type, not the event type. Objects do not need Equatable or custom equality when they expose a stable identifier:

final orderGuard = EventGuard<String>();

ordersStream.listen((order) {
  orderGuard.run(
    key: order.id,
    action: () => send(order),
  );
});

Different Order instances with the same id share guard state. Composite identifiers can use Dart records:

final orderGuard = EventGuard<({String accountId, String orderId})>();

orderGuard.run(
  key: (accountId: order.accountId, orderId: order.id),
  action: () => send(order),
);

You can use an object itself as the key with EventGuard<Order>. In that case, deduplication follows the object's normal == and hashCode behavior; Equatable is one optional way to define that behavior, but it is not required or included as a dependency.

Scope and limitations #

State is local to one EventGuard instance in one isolate. The package does not provide distributed idempotency, persistence, retries, cancellation, queueing, joining, or exactly-once delivery.

Keys use normal Dart == and hashCode semantics. Their equality and hash code must remain stable while an action or cooldown is active.

See example/stream_event_guard_example.dart for a complete runnable example.

Contributing #

Issues and pull requests are welcome in the GitHub repository.

3
likes
160
points
145
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Keyed duplicate suppression and cooldown control for synchronous and asynchronous Dart events.

Repository (GitHub)
View/report issues

Topics

#async #concurrency #deduplication #events

License

MIT (license)

More

Packages that depend on stream_event_guard