Skip to content

Instantly share code, notes, and snippets.

@sma
Last active January 26, 2024 13:34
Show Gist options
  • Save sma/acddf5dd33b5fcfbd2a239504ad05b29 to your computer and use it in GitHub Desktop.
Save sma/acddf5dd33b5fcfbd2a239504ad05b29 to your computer and use it in GitHub Desktop.
A simple read-optimized in-memory key-value store w/persistent file-based redo log.
/// A simple read-optimized in-memory key-value store w/persistent file-based redo log.
class KV {
KV._(this._file, this._data);
final File _file;
final Map<String, dynamic> _data;
static Future<KV> open(File file) async {
final data = <String, dynamic>{};
if (!file.existsSync()) return KV._(file, data);
await for (final line in file //
.openRead()
.transform(utf8.decoder)
.transform(const LineSplitter())
.where((line) => line.isNotEmpty)
.map(json.decode)) {
if (line case [String key, String value]) {
data[key] = value;
} else if (line case [String key]) {
data.remove(key);
}
}
return KV._(file, data);
}
Future<Object?> get(String key) async => _data[key];
Future<Object?> set(String key, Object? value) async {
final old = _data[key];
if (old == value) return value;
if (value != null) {
await _append([key, value]);
_data[key] = value;
} else {
await _append([key]);
_data.remove(key);
}
return old;
}
Stream<(String, Object)> list(String prefix) {
return Stream.fromIterable(_data.entries.where((e) => e.key.startsWith(prefix)).map((e) => (e.key, e.value)));
}
Future<void> snapshot() async {
final file = File('${_file.path},$hashCode');
await file.writeAsString(_data.entries.map((e) => '${json.encode([e.key, e.value])}\n').join());
await file.rename(_file.path);
}
Future<void> _append(List<Object> values) async {
final sink = _file.openWrite(mode: FileMode.append);
sink.writeln(json.encode(values));
await sink.flush();
await sink.close();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment