Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
ArunPrakashG committed Jul 6, 2024
2 parents 95234f8 + c5e1c66 commit d64a81d
Show file tree
Hide file tree
Showing 9 changed files with 179 additions and 90 deletions.
36 changes: 26 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,35 @@
# EverCache

A Dart package that provides advanced caching mechanisms with TTL (Time To Live), and events. Designed to enhance performance and resource management in Dart and Flutter applications by efficiently caching data.
<div align="center">
<h1>🚀 EverCache</h1>

<p align="center">
<a href="https://pub.dev/packages/wordpress_client">
<img src="https://img.shields.io/pub/v/ever_cache?color=blueviolet" alt="Pub Version"/>
</a>
<br>
<img src="https://img.shields.io/badge/dart-%230175C2.svg?style=for-the-badge&logo=dart&logoColor=white" alt="Dart" />
<img src="https://img.shields.io/badge/Flutter-%2302569B.svg?style=for-the-badge&logo=Flutter&logoColor=white" alt="Flutter" />
<br>
<p>
A simple dart package which extends the functionality of Dart's built-in `late` keyword to provide a more robust and flexible way to handle lazy initialization. It closesly resembles the `Lazy<T>` from C#.
</p>
</p>
</div>

## ✨ Key Features

- **⏳ TTL Support**: Say goodbye to stale data! Automatically purge cache entries after a set duration.
- **📡 Events**: Monitor the state of the cache based on delegates emitted from the instance.
- **🚀 Lazy Initialization**: Compute the cache entry only when it is accessed for the first time. (or trigger the compute manually!)
- **⏳ TTL Support**: Automatically purge cache entries after a set duration.
- **📡 Events**: Monitor the state of the cache based on delegates invoked from the instance.
- **🔧 Placeholder**: Provide placeholder data to be returned when cache is being computed.
- **🔍 Access Locking**: Control acess to the computed value by using `lock` functionality.

## 🚀 Getting Started

Integrate EverCache into your project effortlessly. Just sprinkle this into your `pubspec.yaml`:
Integrate `ever_cache` into your project effortlessly. Just sprinkle this into your `pubspec.yaml`:

```yaml
dependencies:
ever_cache: ^0.0.1
ever_cache: ^0.0.4
```
then run `pub get` or `flutter pub get`.
Expand Down Expand Up @@ -42,14 +58,14 @@ final cache = EverCache<String>(
),
// if you want the cache to be computed as soon as this constructor is called in the background
earlyCompute: true,
// if you want to meaningful debug logs in the console
debug: true,
);
```

### 📚 Additional Methods

- **`compute()`**: Manually compute the cache entry.
- **`compute()`**: Manually compute the cache entryin async.
- **`computeSync()`**: Manually compute the cache entry in sync.
- **`lock()`**: Lock the cache entry to prevent further access till the provided callback is executed.
- **`invalidate()`**: Invalidate the cache entry.
- **`dispose()`**: Dispose of the cache entry.

Expand Down
2 changes: 2 additions & 0 deletions lib/ever_cache.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ library;
export 'src/ever_cache_base.dart';
export 'src/ever_events.dart';
export 'src/ever_ttl.dart';
export 'src/ever_value_base.dart' show IEverValue;
export 'src/exceptions/ever_operation_cancelled_exception.dart';
export 'src/exceptions/ever_state_exception.dart';
export 'src/helpers.dart';
export 'src/lockable_base.dart';
30 changes: 15 additions & 15 deletions lib/src/cancellable_operation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,27 @@ class CancellableOperation<T> {
final Completer<T?> _completer = Completer<T?>();
bool _isCancelled = false;

void cancel() {
if (_isCancelled) {
return;
}

_isCancelled = true;
if (!_completer.isCompleted) {
_completer.completeError(
const EverOperationCancelledException(),
StackTrace.current,
);
}
}

Future<T?> execute(
Future<T> Function() fetch, {
void Function()? onComputing,
void Function()? onComputed,
void Function(Object error, StackTrace stackTrace)? onError,
}) async {
_isCancelled = false; // Reset the flag for each execution
_isCancelled = false;

onComputing?.call();

Expand All @@ -37,18 +51,4 @@ class CancellableOperation<T> {
return _completer.future;
}
}

void cancel() {
if (_isCancelled) {
return;
}

_isCancelled = true;
if (!_completer.isCompleted) {
_completer.completeError(
const EverOperationCancelledException(),
StackTrace.current,
);
}
}
}
56 changes: 29 additions & 27 deletions lib/src/ever_cache_base.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import 'dart:async';

import 'ever_events.dart';
import 'ever_ttl.dart';
import 'exceptions/ever_state_exception.dart';
import 'helpers.dart';
import '../ever_cache.dart';

/// A Dart class that manages caching of a value of type [T] with support for time-to-live (TTL),
/// placeholders, and custom events.
Expand All @@ -18,8 +15,7 @@ import 'helpers.dart';
/// - [placeholder]: An optional function that returns a placeholder value until the actual value is fetched.
/// - [ttl]: An optional instance of [EverTTL] to set a TTL for the cached value.
/// - [earlyCompute]: A boolean indicating whether to compute the value immediately upon instantiation.
/// - [debug]: A boolean indicating whether to log debug information.
final class EverCache<T> {
final class EverCache<T> extends ILockable<T> {
/// Constructor for creating an instance of [EverCache].
EverCache(
this._fetch, {
Expand All @@ -28,10 +24,9 @@ final class EverCache<T> {
this.ttl,
this.disposer,
bool earlyCompute = false,
this.debug = false,
}) {
if (earlyCompute) {
backgrounded(compute);
computeSync();
}

if (ttl != null) {
Expand All @@ -51,14 +46,11 @@ final class EverCache<T> {
/// An optional instance of [EverTTL] to set a TTL for the cached value.
final EverTTL? ttl;

/// A boolean indicating whether to log debug information.
final bool debug;

T? _value;

Timer? _timer;

bool _isComputing = false;
final bool _isComputing = false;

bool _isDisposed = false;

Expand Down Expand Up @@ -90,13 +82,14 @@ final class EverCache<T> {
/// Throws an [EverStateException] if the value has been disposed or is being evaluated.
///
/// If the value is not yet computed, it will be fetched in the background as soon as possible.
@override
T get value {
if (_isDisposed) {
throw const EverStateException('Value has been disposed.');
}

if (_value == null) {
backgrounded(compute, debug: debug);
computeSync();

if (placeholder != null) {
return placeholder!();
Expand Down Expand Up @@ -125,11 +118,11 @@ final class EverCache<T> {
///
/// If the value is already computed and not forced, it will return `true`.
Future<bool> compute({bool force = false}) async {
if (_isDisposed) {
if (disposed) {
throw const EverStateException('Value has been disposed.');
}

if (_isComputing) {
if (computing) {
if (placeholder != null) {
return false;
}
Expand All @@ -142,7 +135,7 @@ final class EverCache<T> {
}

_value = await guard(
() async => _fetch(),
() async, => _fetch(),
onError: (e, s) {
_isComputing = false;
events?.onError?.call(e, s);
Expand All @@ -154,22 +147,20 @@ final class EverCache<T> {
events?.onComputed?.call();
return value;
},
onError: (_, __) async => null,
);

_isComputing = false;
return computed;
}

/// Fetches and caches the value synchronously.
///
/// If the value is already computed and not forced, it will return `true`.
void computeSync({bool force = false}) {
if (_isDisposed) {
if (disposed) {
throw const EverStateException('Value has been disposed.');
}

if (_isComputing) {
if (computing) {
if (placeholder != null) {
return;
}
Expand All @@ -182,20 +173,17 @@ final class EverCache<T> {
}

return backgrounded(
() async => _fetch(),
() async, => _fetch(),
then: (object) => _value = object,
onStart: () {
_isComputing = true;
events?.onComputing?.call();
_value = await _fetch();
events?.onComputed?.call();
_isComputing = false;
},
debug: debug,
onError: (e, s) {
onEnd: () {
_isComputing = false;
events?.onError?.call(e, s);
events?.onComputed?.call();
},
onError: events?.onError,
);
}

Expand Down Expand Up @@ -237,4 +225,18 @@ final class EverCache<T> {

_timer = Timer(ttl!.value, invalidate);
}

/// Executes the [callback] function with the value of type [T] and returns the result.
///
/// If the value is locked, an [EverStateException] is thrown.
static Future<R?> synced<R, T>(
ILockable<T> lockable,
Future<R> Function(T value) callback, {
void Function(Object error, StackTrace stackTrace)? onError,
}) async {
return lockable.use<R>(
callback,
onError: onError,
);
}
}
20 changes: 10 additions & 10 deletions lib/src/ever_ttl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,25 @@ final class EverTTL {
/// Constructs a [EverTTL] instance with a given [Duration].
const EverTTL(this.value);

/// Creates a [EverTTL] instance representing a number of seconds.
factory EverTTL.seconds(int seconds) => EverTTL(Duration(seconds: seconds));

/// Creates a [EverTTL] instance representing a number of minutes.
factory EverTTL.minutes(int minutes) => EverTTL(Duration(minutes: minutes));
/// Creates a [EverTTL] instance representing a number of days.
factory EverTTL.days(int days) => EverTTL(Duration(days: days));

/// Creates a [EverTTL] instance representing a number of hours.
factory EverTTL.hours(int hours) => EverTTL(Duration(hours: hours));

/// Creates a [EverTTL] instance representing a number of days.
factory EverTTL.days(int days) => EverTTL(Duration(days: days));

/// Creates a [EverTTL] instance representing a number of weeks.
factory EverTTL.weeks(int weeks) => EverTTL(Duration(days: weeks * 7));
/// Creates a [EverTTL] instance representing a number of minutes.
factory EverTTL.minutes(int minutes) => EverTTL(Duration(minutes: minutes));

/// Creates a [EverTTL] instance representing a number of months.
/// Assumes an average month duration of 30 days.
factory EverTTL.months(int months) => EverTTL(Duration(days: months * 30));

/// Creates a [EverTTL] instance representing a number of seconds.
factory EverTTL.seconds(int seconds) => EverTTL(Duration(seconds: seconds));

/// Creates a [EverTTL] instance representing a number of weeks.
factory EverTTL.weeks(int weeks) => EverTTL(Duration(days: weeks * 7));

/// Creates a [EverTTL] instance representing a number of years.
/// Assumes a year duration of 365 days.
factory EverTTL.years(int years) => EverTTL(Duration(days: years * 365));
Expand Down
3 changes: 3 additions & 0 deletions lib/src/ever_value_base.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
abstract class IEverValue<T> {
T get value;
}
42 changes: 17 additions & 25 deletions lib/src/helpers.dart
Original file line number Diff line number Diff line change
@@ -1,51 +1,43 @@
import 'dart:async';
import 'dart:developer';

/// Runs a function in the background.
void backgrounded<T>(
FutureOr<T> Function() callback, {
FutureOr<void> Function(T object)? then,
bool debug = false,
void Function(Object error, StackTrace stackTrace)? onError,
void Function()? onStart,
void Function()? onEnd,
}) {
Timer.run(() async {
await guardAsync<void>(
function: () async {
await guard<void>(
() async {
final result = await callback();

if (then != null) {
await then(result);
}
},
onError: (error, stackTrace) async {
if (!debug) {
return;
}

log(
error.toString(),
time: DateTime.now(),
name: 'backgrounded()',
error: error,
stackTrace: stackTrace,
);

if (onError != null) {
onError(error, stackTrace);
}
},
onStart: onStart,
onEnd: onEnd,
onError: onError,
);
});
}

/// Guards an asynchronous function.
Future<T> guardAsync<T>({
required Future<T> Function() function,
required Future<T> Function(Object error, StackTrace stackTrace) onError,
FutureOr<T?> guard<T>(
FutureOr<T> Function() function, {
void Function(Object error, StackTrace stackTrace)? onError,
void Function()? onStart,
void Function()? onEnd,
}) async {
try {
onStart?.call();
return await function();
} catch (error, stackTrace) {
return onError(error, stackTrace);
onError?.call(error, stackTrace);
return null;
} finally {
onEnd?.call();
}
}
Loading

0 comments on commit d64a81d

Please sign in to comment.