-
-
Notifications
You must be signed in to change notification settings - Fork 66
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(storage): Add support for EncoderBackedStore (#129)
- Loading branch information
1 parent
699c090
commit 85bb319
Showing
4 changed files
with
137 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import 'base.dart'; | ||
|
||
/// {@template encoder_backed_store} | ||
/// Wraps a store with key and value encoders/decoders. | ||
/// | ||
/// This is useful for stores that only support certain types of keys and | ||
/// values. You can wrap the store with an encoder that converts the keys and | ||
/// values to the supported types. | ||
/// {@endtemplate} | ||
class EncoderBackedStore<K, V, EK, EV> implements BaseStore<K, V> { | ||
/// {@macro encoder_backed_store} | ||
EncoderBackedStore({ | ||
required this.store, | ||
required this.encoder, | ||
}); | ||
|
||
/// The underlying store. | ||
final BaseStore<EK, EV> store; | ||
|
||
/// The encoder/decoder for keys and values. | ||
final Encoder<K, V, EK, EV> encoder; | ||
|
||
@override | ||
Future<List<V?>> get(final List<K> keys) async { | ||
final encodedKeys = keys.map(encoder.encodeKey).toList(growable: false); | ||
final encodedValues = await store.get(encodedKeys); | ||
return encodedValues | ||
.map((final value) => value == null ? null : encoder.decodeValue(value)) | ||
.toList(growable: false); | ||
} | ||
|
||
@override | ||
Future<void> set(final List<(K, V)> keyValuePairs) async { | ||
final encodedKeyValuePairs = keyValuePairs | ||
.map( | ||
(final pair) => ( | ||
encoder.encodeKey(pair.$1), | ||
encoder.encodeValue(pair.$2), | ||
), | ||
) | ||
.toList(growable: false); | ||
await store.set(encodedKeyValuePairs); | ||
} | ||
|
||
@override | ||
Future<void> delete(final List<K> keys) async { | ||
final encodedKeys = keys.map(encoder.encodeKey).toList(growable: false); | ||
await store.delete(encodedKeys); | ||
} | ||
|
||
@override | ||
Stream<K> yieldKeys({final String? prefix}) async* { | ||
final encodedKeys = store.yieldKeys(prefix: prefix); | ||
await for (final encodedKey in encodedKeys) { | ||
yield encoder.decodeKey(encodedKey); | ||
} | ||
} | ||
} | ||
|
||
/// {@template encoder} | ||
/// Encoder/decoder for keys and values. | ||
/// {@endtemplate} | ||
abstract class Encoder<K, V, EK, EV> { | ||
/// {@macro encoder} | ||
const Encoder(); | ||
|
||
/// Encodes a key. | ||
EK encodeKey(final K key); | ||
|
||
/// Encodes a value. | ||
EV encodeValue(final V value); | ||
|
||
/// Decodes a key. | ||
K decodeKey(final EK key); | ||
|
||
/// Decodes a value. | ||
V decodeValue(final EV value); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export 'base.dart'; | ||
export 'encoder_backed.dart'; | ||
export 'in_memory.dart'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import 'package:langchain/langchain.dart'; | ||
import 'package:test/test.dart'; | ||
|
||
void main() { | ||
group('EncoderBackedStore tests', () { | ||
test( | ||
'EncoderBackedStore should encode and decode key-value pairs using InMemoryStore', | ||
() async { | ||
final encoderBackedStore = EncoderBackedStore( | ||
store: InMemoryStore<String, String>(), | ||
encoder: SampleEncoder(), | ||
); | ||
|
||
final keyValuePairs = [(1, 'One'), (2, 'Two'), (3, 'Three')]; | ||
|
||
await encoderBackedStore.set(keyValuePairs); | ||
|
||
// Verify if the key-value pairs are properly encoded and stored | ||
final keys = keyValuePairs.map((final pair) => pair.$1).toList(); | ||
final values = await encoderBackedStore.get(keys); | ||
|
||
for (int i = 0; i < keyValuePairs.length; i++) { | ||
expect(values[i], equals(keyValuePairs[i].$2)); | ||
} | ||
|
||
// Test for delete function | ||
await encoderBackedStore.delete([keyValuePairs[0].$1]); | ||
|
||
// Check if the first key is deleted | ||
final deletedValues = await encoderBackedStore.get(keys); | ||
expect(deletedValues[0], equals(null)); | ||
|
||
// Test yieldKeys | ||
final stream = encoderBackedStore.yieldKeys(); | ||
await for (final key in stream) { | ||
expect(key, isNot(equals(keyValuePairs[0].$1))); | ||
} | ||
}); | ||
}); | ||
} | ||
|
||
class SampleEncoder extends Encoder<int, String, String, String> { | ||
@override | ||
String encodeKey(final int key) => '$key'; | ||
|
||
@override | ||
String encodeValue(final String value) => "'$value'"; | ||
|
||
@override | ||
int decodeKey(final String encodedKey) => int.parse(encodedKey); | ||
|
||
@override | ||
String decodeValue(final String encodedValue) => encodedValue.substring( | ||
1, | ||
encodedValue.length - 1, | ||
); | ||
} |