better dht record class

This commit is contained in:
Christien Rioux 2023-07-17 22:39:33 -04:00
parent 90fc2e5f85
commit bf813d7d0f
7 changed files with 300 additions and 132 deletions

View File

@ -1,3 +1,4 @@
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:veilid/veilid.dart';
@ -7,14 +8,15 @@ part 'identity.g.dart';
// AccountOwnerInfo is the key and owner info for the account dht key that is
// stored in the identity key
@freezed
class AccountOwnerInfo with _$AccountOwnerInfo {
const factory AccountOwnerInfo({
class AccountRecordInfo with _$AccountRecordInfo {
const factory AccountRecordInfo({
// Top level account keys and secrets
required Map<String, TypedKeyPair> accountKeyPairs,
}) = _AccountOwnerInfo;
required TypedKey key,
required KeyPair owner,
}) = _AccountRecordInfo;
factory AccountOwnerInfo.fromJson(Map<String, dynamic> json) =>
_$AccountOwnerInfoFromJson(json);
factory AccountRecordInfo.fromJson(Map<String, dynamic> json) =>
_$AccountRecordInfoFromJson(json);
}
// Identity Key points to accounts associated with this identity
@ -27,7 +29,7 @@ class AccountOwnerInfo with _$AccountOwnerInfo {
class Identity with _$Identity {
const factory Identity({
// Top level account keys and secrets
required Map<String, TypedKeyPair> accountKeyPairs,
required IMap<String, ISet<AccountRecordInfo>> accountRecords,
}) = _Identity;
factory Identity.fromJson(Map<String, dynamic> json) =>

View File

@ -14,35 +14,35 @@ T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
AccountOwnerInfo _$AccountOwnerInfoFromJson(Map<String, dynamic> json) {
return _AccountOwnerInfo.fromJson(json);
AccountRecordInfo _$AccountRecordInfoFromJson(Map<String, dynamic> json) {
return _AccountRecordInfo.fromJson(json);
}
/// @nodoc
mixin _$AccountOwnerInfo {
mixin _$AccountRecordInfo {
// Top level account keys and secrets
Map<String, TypedKeyPair> get accountKeyPairs =>
throw _privateConstructorUsedError;
Typed<FixedEncodedString43> get key => throw _privateConstructorUsedError;
KeyPair get owner => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$AccountOwnerInfoCopyWith<AccountOwnerInfo> get copyWith =>
$AccountRecordInfoCopyWith<AccountRecordInfo> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $AccountOwnerInfoCopyWith<$Res> {
factory $AccountOwnerInfoCopyWith(
AccountOwnerInfo value, $Res Function(AccountOwnerInfo) then) =
_$AccountOwnerInfoCopyWithImpl<$Res, AccountOwnerInfo>;
abstract class $AccountRecordInfoCopyWith<$Res> {
factory $AccountRecordInfoCopyWith(
AccountRecordInfo value, $Res Function(AccountRecordInfo) then) =
_$AccountRecordInfoCopyWithImpl<$Res, AccountRecordInfo>;
@useResult
$Res call({Map<String, TypedKeyPair> accountKeyPairs});
$Res call({Typed<FixedEncodedString43> key, KeyPair owner});
}
/// @nodoc
class _$AccountOwnerInfoCopyWithImpl<$Res, $Val extends AccountOwnerInfo>
implements $AccountOwnerInfoCopyWith<$Res> {
_$AccountOwnerInfoCopyWithImpl(this._value, this._then);
class _$AccountRecordInfoCopyWithImpl<$Res, $Val extends AccountRecordInfo>
implements $AccountRecordInfoCopyWith<$Res> {
_$AccountRecordInfoCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
@ -52,116 +52,122 @@ class _$AccountOwnerInfoCopyWithImpl<$Res, $Val extends AccountOwnerInfo>
@pragma('vm:prefer-inline')
@override
$Res call({
Object? accountKeyPairs = null,
Object? key = null,
Object? owner = null,
}) {
return _then(_value.copyWith(
accountKeyPairs: null == accountKeyPairs
? _value.accountKeyPairs
: accountKeyPairs // ignore: cast_nullable_to_non_nullable
as Map<String, TypedKeyPair>,
key: null == key
? _value.key
: key // ignore: cast_nullable_to_non_nullable
as Typed<FixedEncodedString43>,
owner: null == owner
? _value.owner
: owner // ignore: cast_nullable_to_non_nullable
as KeyPair,
) as $Val);
}
}
/// @nodoc
abstract class _$$_AccountOwnerInfoCopyWith<$Res>
implements $AccountOwnerInfoCopyWith<$Res> {
factory _$$_AccountOwnerInfoCopyWith(
_$_AccountOwnerInfo value, $Res Function(_$_AccountOwnerInfo) then) =
__$$_AccountOwnerInfoCopyWithImpl<$Res>;
abstract class _$$_AccountRecordInfoCopyWith<$Res>
implements $AccountRecordInfoCopyWith<$Res> {
factory _$$_AccountRecordInfoCopyWith(_$_AccountRecordInfo value,
$Res Function(_$_AccountRecordInfo) then) =
__$$_AccountRecordInfoCopyWithImpl<$Res>;
@override
@useResult
$Res call({Map<String, TypedKeyPair> accountKeyPairs});
$Res call({Typed<FixedEncodedString43> key, KeyPair owner});
}
/// @nodoc
class __$$_AccountOwnerInfoCopyWithImpl<$Res>
extends _$AccountOwnerInfoCopyWithImpl<$Res, _$_AccountOwnerInfo>
implements _$$_AccountOwnerInfoCopyWith<$Res> {
__$$_AccountOwnerInfoCopyWithImpl(
_$_AccountOwnerInfo _value, $Res Function(_$_AccountOwnerInfo) _then)
class __$$_AccountRecordInfoCopyWithImpl<$Res>
extends _$AccountRecordInfoCopyWithImpl<$Res, _$_AccountRecordInfo>
implements _$$_AccountRecordInfoCopyWith<$Res> {
__$$_AccountRecordInfoCopyWithImpl(
_$_AccountRecordInfo _value, $Res Function(_$_AccountRecordInfo) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? accountKeyPairs = null,
Object? key = null,
Object? owner = null,
}) {
return _then(_$_AccountOwnerInfo(
accountKeyPairs: null == accountKeyPairs
? _value._accountKeyPairs
: accountKeyPairs // ignore: cast_nullable_to_non_nullable
as Map<String, TypedKeyPair>,
return _then(_$_AccountRecordInfo(
key: null == key
? _value.key
: key // ignore: cast_nullable_to_non_nullable
as Typed<FixedEncodedString43>,
owner: null == owner
? _value.owner
: owner // ignore: cast_nullable_to_non_nullable
as KeyPair,
));
}
}
/// @nodoc
@JsonSerializable()
class _$_AccountOwnerInfo implements _AccountOwnerInfo {
const _$_AccountOwnerInfo(
{required final Map<String, TypedKeyPair> accountKeyPairs})
: _accountKeyPairs = accountKeyPairs;
class _$_AccountRecordInfo implements _AccountRecordInfo {
const _$_AccountRecordInfo({required this.key, required this.owner});
factory _$_AccountOwnerInfo.fromJson(Map<String, dynamic> json) =>
_$$_AccountOwnerInfoFromJson(json);
factory _$_AccountRecordInfo.fromJson(Map<String, dynamic> json) =>
_$$_AccountRecordInfoFromJson(json);
// Top level account keys and secrets
final Map<String, TypedKeyPair> _accountKeyPairs;
// Top level account keys and secrets
@override
Map<String, TypedKeyPair> get accountKeyPairs {
if (_accountKeyPairs is EqualUnmodifiableMapView) return _accountKeyPairs;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_accountKeyPairs);
}
final Typed<FixedEncodedString43> key;
@override
final KeyPair owner;
@override
String toString() {
return 'AccountOwnerInfo(accountKeyPairs: $accountKeyPairs)';
return 'AccountRecordInfo(key: $key, owner: $owner)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_AccountOwnerInfo &&
const DeepCollectionEquality()
.equals(other._accountKeyPairs, _accountKeyPairs));
other is _$_AccountRecordInfo &&
(identical(other.key, key) || other.key == key) &&
(identical(other.owner, owner) || other.owner == owner));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(
runtimeType, const DeepCollectionEquality().hash(_accountKeyPairs));
int get hashCode => Object.hash(runtimeType, key, owner);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$_AccountOwnerInfoCopyWith<_$_AccountOwnerInfo> get copyWith =>
__$$_AccountOwnerInfoCopyWithImpl<_$_AccountOwnerInfo>(this, _$identity);
_$$_AccountRecordInfoCopyWith<_$_AccountRecordInfo> get copyWith =>
__$$_AccountRecordInfoCopyWithImpl<_$_AccountRecordInfo>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$_AccountOwnerInfoToJson(
return _$$_AccountRecordInfoToJson(
this,
);
}
}
abstract class _AccountOwnerInfo implements AccountOwnerInfo {
const factory _AccountOwnerInfo(
{required final Map<String, TypedKeyPair> accountKeyPairs}) =
_$_AccountOwnerInfo;
abstract class _AccountRecordInfo implements AccountRecordInfo {
const factory _AccountRecordInfo(
{required final Typed<FixedEncodedString43> key,
required final KeyPair owner}) = _$_AccountRecordInfo;
factory _AccountOwnerInfo.fromJson(Map<String, dynamic> json) =
_$_AccountOwnerInfo.fromJson;
factory _AccountRecordInfo.fromJson(Map<String, dynamic> json) =
_$_AccountRecordInfo.fromJson;
@override // Top level account keys and secrets
Map<String, TypedKeyPair> get accountKeyPairs;
Typed<FixedEncodedString43> get key;
@override
KeyPair get owner;
@override
@JsonKey(ignore: true)
_$$_AccountOwnerInfoCopyWith<_$_AccountOwnerInfo> get copyWith =>
_$$_AccountRecordInfoCopyWith<_$_AccountRecordInfo> get copyWith =>
throw _privateConstructorUsedError;
}
@ -172,7 +178,7 @@ Identity _$IdentityFromJson(Map<String, dynamic> json) {
/// @nodoc
mixin _$Identity {
// Top level account keys and secrets
Map<String, TypedKeyPair> get accountKeyPairs =>
IMap<String, ISet<AccountRecordInfo>> get accountRecords =>
throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@ -186,7 +192,7 @@ abstract class $IdentityCopyWith<$Res> {
factory $IdentityCopyWith(Identity value, $Res Function(Identity) then) =
_$IdentityCopyWithImpl<$Res, Identity>;
@useResult
$Res call({Map<String, TypedKeyPair> accountKeyPairs});
$Res call({IMap<String, ISet<AccountRecordInfo>> accountRecords});
}
/// @nodoc
@ -202,13 +208,13 @@ class _$IdentityCopyWithImpl<$Res, $Val extends Identity>
@pragma('vm:prefer-inline')
@override
$Res call({
Object? accountKeyPairs = null,
Object? accountRecords = null,
}) {
return _then(_value.copyWith(
accountKeyPairs: null == accountKeyPairs
? _value.accountKeyPairs
: accountKeyPairs // ignore: cast_nullable_to_non_nullable
as Map<String, TypedKeyPair>,
accountRecords: null == accountRecords
? _value.accountRecords
: accountRecords // ignore: cast_nullable_to_non_nullable
as IMap<String, ISet<AccountRecordInfo>>,
) as $Val);
}
}
@ -220,7 +226,7 @@ abstract class _$$_IdentityCopyWith<$Res> implements $IdentityCopyWith<$Res> {
__$$_IdentityCopyWithImpl<$Res>;
@override
@useResult
$Res call({Map<String, TypedKeyPair> accountKeyPairs});
$Res call({IMap<String, ISet<AccountRecordInfo>> accountRecords});
}
/// @nodoc
@ -234,13 +240,13 @@ class __$$_IdentityCopyWithImpl<$Res>
@pragma('vm:prefer-inline')
@override
$Res call({
Object? accountKeyPairs = null,
Object? accountRecords = null,
}) {
return _then(_$_Identity(
accountKeyPairs: null == accountKeyPairs
? _value._accountKeyPairs
: accountKeyPairs // ignore: cast_nullable_to_non_nullable
as Map<String, TypedKeyPair>,
accountRecords: null == accountRecords
? _value.accountRecords
: accountRecords // ignore: cast_nullable_to_non_nullable
as IMap<String, ISet<AccountRecordInfo>>,
));
}
}
@ -248,25 +254,18 @@ class __$$_IdentityCopyWithImpl<$Res>
/// @nodoc
@JsonSerializable()
class _$_Identity implements _Identity {
const _$_Identity({required final Map<String, TypedKeyPair> accountKeyPairs})
: _accountKeyPairs = accountKeyPairs;
const _$_Identity({required this.accountRecords});
factory _$_Identity.fromJson(Map<String, dynamic> json) =>
_$$_IdentityFromJson(json);
// Top level account keys and secrets
final Map<String, TypedKeyPair> _accountKeyPairs;
// Top level account keys and secrets
@override
Map<String, TypedKeyPair> get accountKeyPairs {
if (_accountKeyPairs is EqualUnmodifiableMapView) return _accountKeyPairs;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_accountKeyPairs);
}
final IMap<String, ISet<AccountRecordInfo>> accountRecords;
@override
String toString() {
return 'Identity(accountKeyPairs: $accountKeyPairs)';
return 'Identity(accountRecords: $accountRecords)';
}
@override
@ -274,14 +273,13 @@ class _$_Identity implements _Identity {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_Identity &&
const DeepCollectionEquality()
.equals(other._accountKeyPairs, _accountKeyPairs));
(identical(other.accountRecords, accountRecords) ||
other.accountRecords == accountRecords));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(
runtimeType, const DeepCollectionEquality().hash(_accountKeyPairs));
int get hashCode => Object.hash(runtimeType, accountRecords);
@JsonKey(ignore: true)
@override
@ -299,12 +297,13 @@ class _$_Identity implements _Identity {
abstract class _Identity implements Identity {
const factory _Identity(
{required final Map<String, TypedKeyPair> accountKeyPairs}) = _$_Identity;
{required final IMap<String, ISet<AccountRecordInfo>>
accountRecords}) = _$_Identity;
factory _Identity.fromJson(Map<String, dynamic> json) = _$_Identity.fromJson;
@override // Top level account keys and secrets
Map<String, TypedKeyPair> get accountKeyPairs;
IMap<String, ISet<AccountRecordInfo>> get accountRecords;
@override
@JsonKey(ignore: true)
_$$_IdentityCopyWith<_$_Identity> get copyWith =>

View File

@ -6,29 +6,37 @@ part of 'identity.dart';
// JsonSerializableGenerator
// **************************************************************************
_$_AccountOwnerInfo _$$_AccountOwnerInfoFromJson(Map<String, dynamic> json) =>
_$_AccountOwnerInfo(
accountKeyPairs: (json['account_key_pairs'] as Map<String, dynamic>).map(
(k, e) => MapEntry(k, TypedKeyPair.fromJson(e)),
),
_$_AccountRecordInfo _$$_AccountRecordInfoFromJson(Map<String, dynamic> json) =>
_$_AccountRecordInfo(
key: Typed<FixedEncodedString43>.fromJson(json['key']),
owner: KeyPair.fromJson(json['owner']),
);
Map<String, dynamic> _$$_AccountOwnerInfoToJson(_$_AccountOwnerInfo instance) =>
Map<String, dynamic> _$$_AccountRecordInfoToJson(
_$_AccountRecordInfo instance) =>
<String, dynamic>{
'account_key_pairs':
instance.accountKeyPairs.map((k, e) => MapEntry(k, e.toJson())),
'key': instance.key.toJson(),
'owner': instance.owner.toJson(),
};
_$_Identity _$$_IdentityFromJson(Map<String, dynamic> json) => _$_Identity(
accountKeyPairs: (json['account_key_pairs'] as Map<String, dynamic>).map(
(k, e) => MapEntry(k, TypedKeyPair.fromJson(e)),
),
accountRecords: IMap<String, ISet<AccountRecordInfo>>.fromJson(
json['account_records'] as Map<String, dynamic>,
(value) => value as String,
(value) => ISet<AccountRecordInfo>.fromJson(
value,
(value) =>
AccountRecordInfo.fromJson(value as Map<String, dynamic>))),
);
Map<String, dynamic> _$$_IdentityToJson(_$_Identity instance) =>
<String, dynamic>{
'account_key_pairs':
instance.accountKeyPairs.map((k, e) => MapEntry(k, e.toJson())),
'account_records': instance.accountRecords.toJson(
(value) => value,
(value) => value.toJson(
(value) => value.toJson(),
),
),
};
_$_IdentityMaster _$$_IdentityMasterFromJson(Map<String, dynamic> json) =>

View File

@ -102,23 +102,48 @@ class LocalAccountManager {
}
}
/// Adds a 'VeilidChat' account record to an identity key
Future<void> updateIdentityKey(
VeilidRoutingContext dhtctx,
TypedKey identityRecordKey,
TypedKey accountRecordKey,
KeyPair accountRecordOwner) async {
// Account record to add
final accountRecordInfo =
AccountRecordInfo(key: accountRecordKey, owner: accountRecordOwner);
// Eventually update identity key
eventuallyConsistentUpdate(
dhtctx,
identityRecordKey,
0,
true,
jsonUpdate(Identity.fromJson, (oldObj) async {
final accountRecords = IMapOfSets.from(oldObj.accountRecords)
.add("VeilidChat", accountRecordInfo)
.asIMap();
return oldObj.copyWith(accountRecords: accountRecords);
}));
// Get existing identity key
ValueData? identityValueData =
await dhtctx.getDHTValue(identityRecordKey, 0, true);
if (identityValueData == null) {
throw const FormatException("Identity does not exist");
}
var identity = identityValueData.readJsonData(Identity.fromJson);
xxx make back to bytes function and do update loop and maybe make that a tool function too (consistentUpdate)
// Update identity key to include account
const identity = Identity(accountKeyPairs: {});
final identityBytes = Uint8List.fromList(utf8.encode(jsonEncode(identity)));
await dhtctx.setDHTValue(identityRec.key, 0, identityBytes);
do {
if (identityValueData == null) {
throw const FormatException("Identity does not exist");
}
var identity = identityValueData.readJsonData(Identity.fromJson);
// Update identity key to include account
final accountRecords = IMapOfSets.from(identity.accountRecords)
.add("VeilidChat", accountRecordInfo)
.asIMap();
identity = identity.copyWith(accountRecords: accountRecords);
final identityBytes = jsonEncodeBytes(identity);
identityValueData =
await dhtctx.setDHTValue(identityRecordKey, 0, identityBytes);
} while (identityValueData != null);
}
/// Creates a new account associated with master identity

109
lib/tools/dht_record.dart Normal file
View File

@ -0,0 +1,109 @@
import 'package:veilid/veilid.dart';
import 'dart:typed_data';
import 'tools.dart';
class DHTRecord {
final VeilidRoutingContext _dhtctx;
final DHTRecordDescriptor _recordDescriptor;
final int _defaultSubkey;
static Future<DHTRecord> create(VeilidRoutingContext dhtctx,
{DHTSchema schema = const DHTSchema.dflt(oCnt: 1),
int defaultSubkey = 0}) async {
DHTRecordDescriptor recordDescriptor = await dhtctx.createDHTRecord(schema);
return DHTRecord(
dhtctx: dhtctx,
recordDescriptor: recordDescriptor,
defaultSubkey: defaultSubkey);
}
static Future<DHTRecord> open(
VeilidRoutingContext dhtctx, TypedKey recordKey, KeyPair? writer,
{int defaultSubkey = 0}) async {
DHTRecordDescriptor recordDescriptor =
await dhtctx.openDHTRecord(recordKey, writer);
return DHTRecord(
dhtctx: dhtctx,
recordDescriptor: recordDescriptor,
defaultSubkey: defaultSubkey);
}
DHTRecord(
{required VeilidRoutingContext dhtctx,
required DHTRecordDescriptor recordDescriptor,
int defaultSubkey = 0})
: _dhtctx = dhtctx,
_recordDescriptor = recordDescriptor,
_defaultSubkey = defaultSubkey;
int _subkey(int subkey) => (subkey == -1) ? _defaultSubkey : subkey;
Future<Uint8List?> get({int subkey = -1, bool forceRefresh = false}) async {
ValueData? valueData = await _dhtctx.getDHTValue(
_recordDescriptor.key, _subkey(subkey), false);
if (valueData == null) {
return null;
}
return valueData.data;
}
Future<T?> getJson<T>(T Function(Map<String, dynamic>) fromJson,
{int subkey = -1, bool forceRefresh = false}) async {
ValueData? valueData = await _dhtctx.getDHTValue(
_recordDescriptor.key, _subkey(subkey), false);
if (valueData == null) {
return null;
}
return valueData.readJsonData(fromJson);
}
Future<void> eventualWriteBytes(Uint8List newValue, {int subkey = -1}) async {
// Get existing identity key
ValueData? valueData;
do {
// Ensure it exists already
if (valueData == null) {
throw const FormatException("value does not exist");
}
// Set the new data
valueData = await _dhtctx.setDHTValue(
_recordDescriptor.key, _subkey(subkey), newValue);
// Repeat if newer data on the network was found
} while (valueData != null);
}
Future<void> eventualUpdateBytes(
Future<Uint8List> Function(Uint8List oldValue) update,
{int subkey = -1}) async {
// Get existing identity key
ValueData? valueData = await _dhtctx.getDHTValue(
_recordDescriptor.key, _subkey(subkey), false);
do {
// Ensure it exists already
if (valueData == null) {
throw const FormatException("value does not exist");
}
// Update the data
final newData = await update(valueData.data);
// Set it back
valueData = await _dhtctx.setDHTValue(
_recordDescriptor.key, _subkey(subkey), newData);
// Repeat if newer data on the network was found
} while (valueData != null);
}
Future<void> eventualWriteJson<T>(T newValue, {int subkey = -1}) {
return eventualWriteBytes(jsonEncodeBytes(newValue), subkey: subkey);
}
Future<void> eventualUpdateJson<T>(
T Function(Map<String, dynamic>) fromJson, Future<T> Function(T) update,
{int subkey = -1}) {
return eventualUpdateBytes(jsonUpdate(fromJson, update), subkey: subkey);
}
}

30
lib/tools/json_tools.dart Normal file
View File

@ -0,0 +1,30 @@
// import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:veilid/veilid.dart';
import 'dart:typed_data';
import 'dart:convert';
extension FromValueDataJsonExt on ValueData {
T readJsonData<T>(T Function(Map<String, dynamic>) fromJson) {
return fromJson(jsonDecode(utf8.decode(data)));
}
}
Uint8List jsonEncodeBytes(Object? object,
{Object? Function(Object?)? toEncodable}) {
return Uint8List.fromList(
utf8.encode(jsonEncode(object, toEncodable: toEncodable)));
}
Future<Uint8List> jsonUpdateBytes<T>(T Function(Map<String, dynamic>) fromJson,
Uint8List oldBytes, Future<T> Function(T) update) async {
T oldObj = fromJson(jsonDecode(utf8.decode(oldBytes)));
T newObj = await update(oldObj);
return jsonEncodeBytes(newObj);
}
Future<Uint8List> Function(Uint8List) jsonUpdate<T>(
T Function(Map<String, dynamic>) fromJson, Future<T> Function(T) update) {
return (Uint8List oldBytes) {
return jsonUpdateBytes(fromJson, oldBytes, update);
};
}

View File

@ -1,9 +1,4 @@
export 'external_stream_state.dart';
import 'package:veilid/veilid.dart';
import 'dart:convert';
extension FromValueDataJsonExt on ValueData {
T readJsonData<T>(T Function(Map<String, dynamic>) fromJson) {
return fromJson(jsonDecode(utf8.decode(data)));
}
}
export 'dht_record.dart';
export 'json_tools.dart';
export 'phono_byte.dart';