Skip to content

Commit e31068b

Browse files
committed
♻️ add stream methods for real-time value updates in storage containers and improve documentation
1 parent a47cd26 commit e31068b

File tree

10 files changed

+211
-361
lines changed

10 files changed

+211
-361
lines changed

packages/hyper_storage/lib/src/api/backend.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright © 2025 Hyperdesigned. All rights reserved.
22
// Use of this source code is governed by a BSD license that can be
33
// found in the LICENSE file.
4+
/// @docImport 'storage_container.dart';
5+
library;
46

57
import 'dart:convert';
68

@@ -17,6 +19,7 @@ import 'api.dart';
1719
/// It includes default implementations for common operations like handling JSON,
1820
/// lists, and date/time objects, reducing the boilerplate needed when implementing
1921
/// a new backend.
22+
/// {@category Backends}
2023
abstract class StorageBackend with GenericStorageOperationsMixin implements StorageOperationsApi {
2124
/// Initializes the storage backend.
2225
///

packages/hyper_storage/lib/src/api/listenable.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ mixin ListenableStorage {
232232
///
233233
/// This mixin implements the observer pattern, allowing components to be
234234
/// notified when data changes.
235+
/// {@category Reactivity}
235236
@protected
236237
mixin BaseListenable {
237238
/// A storage for keeping registry of listeners;

packages/hyper_storage/lib/src/api/serializable_container.dart

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Use of this source code is governed by a BSD license that can be
33
// found in the LICENSE file.
44

5+
import 'dart:async';
56
import 'dart:math';
67

78
import 'package:meta/meta.dart';
@@ -157,9 +158,6 @@ abstract class SerializableStorageContainer<E> extends StorageContainer implemen
157158
/// format is up to the implementation (JSON, XML, binary encoding, etc.),
158159
/// but it must be reversible by the [deserialize] method.
159160
///
160-
/// The [@protected] annotation indicates this method is for internal use
161-
/// by the container and shouldn't be called directly by external code.
162-
///
163161
/// Parameters:
164162
/// * [value] - The object to serialize.
165163
///
@@ -179,9 +177,6 @@ abstract class SerializableStorageContainer<E> extends StorageContainer implemen
179177
/// strings from storage are converted back to objects. The implementation
180178
/// must be able to reverse the serialization performed by [serialize].
181179
///
182-
/// The [@protected] annotation indicates this method is for internal use
183-
/// by the container and shouldn't be called directly by external code.
184-
///
185180
/// Parameters:
186181
/// * [value] - The string to deserialize, as returned by [serialize].
187182
///
@@ -201,9 +196,6 @@ abstract class SerializableStorageContainer<E> extends StorageContainer implemen
201196
/// number generator. The generated IDs are guaranteed to be valid storage
202197
/// keys (non-empty and not containing delimiters).
203198
///
204-
/// The [@protected] annotation indicates this method is for internal use
205-
/// by the container and shouldn't be called directly by external code.
206-
///
207199
/// Returns:
208200
/// A newly generated unique ID string.
209201
///
@@ -711,4 +703,66 @@ abstract class SerializableStorageContainer<E> extends StorageContainer implemen
711703
removeAllListeners();
712704
await backend.close();
713705
}
706+
707+
/// Provides a [Stream] of values for the given [key].
708+
/// The stream will emit the current value of the key and will update
709+
/// whenever the value changes.
710+
///
711+
/// It is important to close the stream when it is no longer needed
712+
/// to avoid memory leaks.
713+
///
714+
/// This can be done by cancelling the subscription to the stream.
715+
Stream<E?> stream(String key) async* {
716+
final E? itemValue = await get(key);
717+
yield itemValue;
718+
719+
late final void Function() retrieveAndAdd;
720+
final controller = StreamController<E?>(
721+
onCancel: () => removeKeyListener(key, retrieveAndAdd),
722+
);
723+
724+
retrieveAndAdd = () async {
725+
if (controller.isClosed) return;
726+
final E? value = await get(key);
727+
if (!controller.isClosed) {
728+
controller.add(value);
729+
}
730+
};
731+
732+
addKeyListener(key, retrieveAndAdd);
733+
734+
yield* controller.stream;
735+
await controller.close();
736+
}
737+
738+
/// Provides a [Stream] of values for all items in the container.
739+
/// The stream will emit the current values and will update
740+
/// whenever any value changes.
741+
///
742+
/// It is important to close the stream when it is no longer needed
743+
/// to avoid memory leaks.
744+
///
745+
/// This can be done by cancelling the subscription to the stream.
746+
Stream<List<E>> streamAll() async* {
747+
final List<E> values = await getValues();
748+
yield values;
749+
750+
late final void Function() retrieveAndAdd;
751+
final controller = StreamController<List<E>>(
752+
onCancel: () => removeListener(retrieveAndAdd),
753+
);
754+
755+
retrieveAndAdd = () async {
756+
if (controller.isClosed) return;
757+
final List<E> values = await getValues();
758+
if (!controller.isClosed) {
759+
controller.add(values);
760+
}
761+
};
762+
763+
addListener(retrieveAndAdd);
764+
765+
yield* controller.stream;
766+
await controller.close();
767+
}
714768
}

packages/hyper_storage/lib/src/api/storage_container.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
// Use of this source code is governed by a BSD license that can be
33
// found in the LICENSE file.
44

5+
/// @docImport 'serializable_container.dart';
6+
library;
7+
58
import 'package:meta/meta.dart';
69

710
import 'api.dart';

packages/hyper_storage/lib/src/hyper_storage.dart

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
// Use of this source code is governed by a BSD license that can be
33
// found in the LICENSE file.
44

5+
import 'dart:async';
56
import 'dart:math';
67

78
import 'package:meta/meta.dart';
89

910
import '../hyper_storage.dart';
10-
import 'api/api.dart';
1111
import 'api/backend.dart';
1212
import 'api/storage_container.dart';
1313

@@ -43,6 +43,7 @@ part 'storage_base.dart';
4343
/// - [JsonStorageContainer] for JSON serialization
4444
/// - [SerializableStorageContainer] for custom object storage
4545
/// - [StorageBackend] for implementing custom backends
46+
/// {@category Getting Started}
4647
class HyperStorage extends _HyperStorageImpl {
4748
/// Cache of basic storage containers indexed by name.
4849
///
@@ -218,7 +219,7 @@ class HyperStorage extends _HyperStorageImpl {
218219
/// * [fromJson] - A function that converts a JSON map back to an object of
219220
/// type [E]. This is called when retrieving objects.
220221
/// * [idGetter] - Optional. A function that extracts the ID from an object.
221-
/// If provided, this ID is used when adding objects with [add]. If not
222+
/// If provided, this ID is used when adding objects with add methods. If not
222223
/// provided, IDs are automatically generated.
223224
/// * [random] - Optional. A custom random number generator for ID generation.
224225
/// Useful for testing or when specific randomness characteristics are needed.
@@ -380,4 +381,47 @@ class HyperStorage extends _HyperStorageImpl {
380381
await backend.close();
381382
await super.close();
382383
}
384+
385+
/// Provides a [Stream] of values for the given [key].
386+
/// The stream will emit the current value of the key and will update
387+
/// whenever the value changes.
388+
///
389+
/// It is important to close the stream when it is no longer needed
390+
/// to avoid memory leaks.
391+
///
392+
/// This can be done by cancelling the subscription to the stream.
393+
///
394+
/// Note that only supported types are allowed for [E].
395+
/// Supported types are:
396+
/// - String
397+
/// - int
398+
/// - double
399+
/// - bool
400+
/// - List of String
401+
/// - JSON Map
402+
/// - List of JSON Map
403+
/// - DateTime
404+
/// - Duration
405+
Stream<E?> stream<E extends Object>(String key) async* {
406+
final E? itemValue = await get<E>(key);
407+
yield itemValue;
408+
409+
late final void Function() retrieveAndAdd;
410+
final controller = StreamController<E?>(
411+
onCancel: () => removeKeyListener(key, retrieveAndAdd),
412+
);
413+
414+
retrieveAndAdd = () async {
415+
if (controller.isClosed) return;
416+
final E? value = await get<E>(key);
417+
if (!controller.isClosed) {
418+
controller.add(value);
419+
}
420+
};
421+
422+
addKeyListener(key, retrieveAndAdd);
423+
424+
yield* controller.stream;
425+
await controller.close();
426+
}
383427
}

packages/hyper_storage/lib/src/hyper_storage_container.dart

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Use of this source code is governed by a BSD license that can be
33
// found in the LICENSE file.
44

5+
import 'dart:async';
56
import 'dart:convert';
67

78
import 'package:meta/meta.dart' show protected;
@@ -27,14 +28,10 @@ import 'api/storage_container.dart';
2728
///
2829
/// See also:
2930
/// - [StorageContainer] for the base class and abstract interface
31+
/// {@category Containers}
3032
final class HyperStorageContainer extends StorageContainer with ItemHolderMixin, GenericStorageOperationsMixin {
3133
/// Creates a new [HyperStorageContainer] instance.
3234
///
33-
/// This constructor is marked [@protected] because containers should
34-
/// typically be created through [HyperStorage.container] rather than
35-
/// directly instantiated. Direct instantiation bypasses the container
36-
/// caching system.
37-
///
3835
/// Parameters:
3936
/// * [backend] - The storage backend that handles actual persistence.
4037
/// * [name] - The name of the container, used as a prefix for all keys.
@@ -245,4 +242,47 @@ final class HyperStorageContainer extends StorageContainer with ItemHolderMixin,
245242
notifyListeners();
246243
removeAllListeners();
247244
}
245+
246+
/// Provides a [Stream] of values for the given [key].
247+
/// The stream will emit the current value of the key and will update
248+
/// whenever the value changes.
249+
///
250+
/// It is important to close the stream when it is no longer needed
251+
/// to avoid memory leaks.
252+
///
253+
/// This can be done by cancelling the subscription to the stream.
254+
///
255+
/// Note that only supported types are allowed for [E].
256+
/// Supported types are:
257+
/// - String
258+
/// - int
259+
/// - double
260+
/// - bool
261+
/// - List of String
262+
/// - JSON Map
263+
/// - List of JSON Map
264+
/// - DateTime
265+
/// - Duration
266+
Stream<E?> stream<E extends Object>(String key) async* {
267+
final E? itemValue = await get<E>(key);
268+
yield itemValue;
269+
270+
late final void Function() retrieveAndAdd;
271+
final controller = StreamController<E?>(
272+
onCancel: () => removeKeyListener(key, retrieveAndAdd),
273+
);
274+
275+
retrieveAndAdd = () async {
276+
if (controller.isClosed) return;
277+
final E? value = await get<E>(key);
278+
if (!controller.isClosed) {
279+
controller.add(value);
280+
}
281+
};
282+
283+
addKeyListener(key, retrieveAndAdd);
284+
285+
yield* controller.stream;
286+
await controller.close();
287+
}
248288
}

packages/hyper_storage/lib/src/in_memory_backend.dart

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
// found in the LICENSE file.
44

55
import 'api/backend.dart';
6-
import 'hyper_storage_container.dart';
76

87
/// A volatile storage backend that stores all data in memory.
98
///
@@ -40,13 +39,6 @@ class InMemoryBackend extends StorageBackend {
4039
@override
4140
Future<InMemoryBackend> init() async => this;
4241

43-
@override
44-
Future<HyperStorageContainer> container(String name) async {
45-
final backend = InMemoryBackend();
46-
await backend.init();
47-
return HyperStorageContainer(backend: backend, name: name);
48-
}
49-
5042
@override
5143
Future<void> setString(String key, String value) async => _data[key] = value;
5244

0 commit comments

Comments
 (0)