From aba549b5f6441b412997cb68d9450754ab673efd Mon Sep 17 00:00:00 2001 From: Leo <58687994+1-leo@users.noreply.github.com> Date: Mon, 11 May 2026 11:24:18 +0200 Subject: [PATCH 1/3] chore: debug profiler --- packages/ndk/lib/simple_profiler.dart | 52 +++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 packages/ndk/lib/simple_profiler.dart diff --git a/packages/ndk/lib/simple_profiler.dart b/packages/ndk/lib/simple_profiler.dart new file mode 100644 index 000000000..296cbb17e --- /dev/null +++ b/packages/ndk/lib/simple_profiler.dart @@ -0,0 +1,52 @@ +/// DO NOT USE IN PRODUCTION CODE! Only for debugging and testing purposes. +class SimpleProfiler { + final String name; + final DateTime startTime; + DateTime _lastCheckpoint; + + SimpleProfiler(this.name) + : startTime = DateTime.now(), + _lastCheckpoint = DateTime.now() { + // ignore: avoid_print + print('Starting $name at $startTime'); + } + + void checkpoint(String description) { + final now = DateTime.now(); + final totalDuration = now.difference(startTime); + final checkpointDuration = now.difference(_lastCheckpoint); + + // ignore: avoid_print + print('$name - $description:' + '\n\t$name Total: ${totalDuration.inMilliseconds}ms' + '\n\t$name Since last checkpoint: ${checkpointDuration.inMilliseconds}ms'); + + _lastCheckpoint = now; + } + + void end() { + final now = DateTime.now(); + final totalDuration = now.difference(startTime); + final checkpointDuration = now.difference(_lastCheckpoint); + + // ignore: avoid_print + print('Ended $name:' + '\n\t$name Total time: ${totalDuration.inMilliseconds}ms' + '\n\t$name Since last checkpoint: ${checkpointDuration.inMilliseconds}ms'); + } +} + +/** +// Usage: +Future someFunction() async { + final profiler = SimpleProfiler('MyOperation'); + + await step1(); + profiler.checkpoint('Step 1 completed'); + + await step2(); + profiler.checkpoint('Step 2 completed'); + + profiler.end(); +} + */ From 0097248b4adef862efcb65f664e4da1f2627e2af Mon Sep 17 00:00:00 2001 From: Leo <58687994+1-leo@users.noreply.github.com> Date: Mon, 11 May 2026 11:24:40 +0200 Subject: [PATCH 2/3] fix: async write objextbox --- .../db/object_box/db_object_box.dart | 29 +- packages/objectbox/lib/objectbox.g.dart | 275 ++++++++++-------- 2 files changed, 159 insertions(+), 145 deletions(-) diff --git a/packages/objectbox/lib/data_layer/db/object_box/db_object_box.dart b/packages/objectbox/lib/data_layer/db/object_box/db_object_box.dart index dd0a3046b..14ad12ea2 100644 --- a/packages/objectbox/lib/data_layer/db/object_box/db_object_box.dart +++ b/packages/objectbox/lib/data_layer/db/object_box/db_object_box.dart @@ -270,21 +270,15 @@ class DbObjectBox extends WalletsRepo implements CacheManager { Future saveEvent(Nip01Event event) async { await dbRdy; final eventBox = _objectBox.store.box(); - final existingEvent = eventBox - .query(DbNip01Event_.nostrId.equals(event.id)) - .build() - .findFirst(); - if (existingEvent != null) { - eventBox.remove(existingEvent.dbId); - } - eventBox.put(DbNip01Event.fromNdk(event)); + await eventBox.putAsync(DbNip01Event.fromNdk(event), mode: PutMode.put); } @override Future saveEvents(List events) async { await dbRdy; final eventBox = _objectBox.store.box(); - eventBox.putMany(events.map((e) => DbNip01Event.fromNdk(e)).toList()); + await eventBox + .putManyAsync(events.map((e) => DbNip01Event.fromNdk(e)).toList()); } @override @@ -303,7 +297,7 @@ class DbObjectBox extends WalletsRepo implements CacheManager { metadata.updatedAt! < existingMetadatas[0].updatedAt!) { return; } - metadataBox.put(DbMetadata.fromNdk(metadata)); + metadataBox.putAsync(DbMetadata.fromNdk(metadata)); } @override @@ -608,7 +602,7 @@ class DbObjectBox extends WalletsRepo implements CacheManager { nip05.networkFetchTime! < existing[0].networkFetchTime!) { return; } - box.put(DbNip05.fromNdk(nip05)); + await box.putAsync(DbNip05.fromNdk(nip05)); } @override @@ -628,7 +622,7 @@ class DbObjectBox extends WalletsRepo implements CacheManager { if (existing != null) { box.remove(existing.dbId); } - box.put(DbRelaySet.fromNdk(relaySet)); + await box.putAsync(DbRelaySet.fromNdk(relaySet)); } @override @@ -643,11 +637,12 @@ class DbObjectBox extends WalletsRepo implements CacheManager { if (existingUserRelayList != null) { userRelayListBox.remove(existingUserRelayList.dbId); } - userRelayListBox.put(DbUserRelayList.fromNdk(userRelayList)); + await userRelayListBox.putAsync(DbUserRelayList.fromNdk(userRelayList)); } @override Future saveUserRelayLists(List userRelayLists) async { + await dbRdy; final wait = []; for (final userRelayList in userRelayLists) { wait.add(saveUserRelayList(userRelayList)); @@ -714,7 +709,7 @@ class DbObjectBox extends WalletsRepo implements CacheManager { FilterFetchedRangeRecord record) async { await dbRdy; final box = _objectBox.store.box(); - box.put(DbFilterFetchedRangeRecord.fromNdk(record)); + await box.putAsync(DbFilterFetchedRangeRecord.fromNdk(record)); } @override @@ -722,7 +717,7 @@ class DbObjectBox extends WalletsRepo implements CacheManager { List records) async { await dbRdy; final box = _objectBox.store.box(); - box.putMany( + await box.putManyAsync( records.map((r) => DbFilterFetchedRangeRecord.fromNdk(r)).toList()); } @@ -810,7 +805,7 @@ class DbObjectBox extends WalletsRepo implements CacheManager { Future removeAllFilterFetchedRangeRecords() async { await dbRdy; final box = _objectBox.store.box(); - box.removeAll(); + await box.removeAll(); } @override @@ -911,7 +906,7 @@ class DbObjectBox extends WalletsRepo implements CacheManager { @override Future saveKeyset(CahsuKeyset keyset) async { - _objectBox.store.box().put( + await _objectBox.store.box().putAsync( DbWalletCahsuKeyset.fromNdk(keyset), ); return Future.value(); diff --git a/packages/objectbox/lib/objectbox.g.dart b/packages/objectbox/lib/objectbox.g.dart index ad804c93e..fac383914 100644 --- a/packages/objectbox/lib/objectbox.g.dart +++ b/packages/objectbox/lib/objectbox.g.dart @@ -923,10 +923,12 @@ obx_int.ModelDefinition getObjectBoxModel() { object.dbId = id; }, objectToFB: (DbCashuMintInfo object, fb.Builder fbb) { - final nameOffset = - object.name == null ? null : fbb.writeString(object.name!); - final versionOffset = - object.version == null ? null : fbb.writeString(object.version!); + final nameOffset = object.name == null + ? null + : fbb.writeString(object.name!); + final versionOffset = object.version == null + ? null + : fbb.writeString(object.version!); final descriptionOffset = object.description == null ? null : fbb.writeString(object.description!); @@ -934,15 +936,18 @@ obx_int.ModelDefinition getObjectBoxModel() { ? null : fbb.writeString(object.descriptionLong!); final contactJsonOffset = fbb.writeString(object.contactJson); - final motdOffset = - object.motd == null ? null : fbb.writeString(object.motd!); - final iconUrlOffset = - object.iconUrl == null ? null : fbb.writeString(object.iconUrl!); + final motdOffset = object.motd == null + ? null + : fbb.writeString(object.motd!); + final iconUrlOffset = object.iconUrl == null + ? null + : fbb.writeString(object.iconUrl!); final urlsOffset = fbb.writeList( object.urls.map(fbb.writeString).toList(growable: false), ); - final tosUrlOffset = - object.tosUrl == null ? null : fbb.writeString(object.tosUrl!); + final tosUrlOffset = object.tosUrl == null + ? null + : fbb.writeString(object.tosUrl!); final nutsJsonOffset = fbb.writeString(object.nutsJson); fbb.startTable(13); fbb.addInt64(0, object.dbId); @@ -1166,61 +1171,63 @@ obx_int.ModelDefinition getObjectBoxModel() { ), DbFilterFetchedRangeRecord: obx_int.EntityDefinition( - model: _entities[3], - toOneRelations: (DbFilterFetchedRangeRecord object) => [], - toManyRelations: (DbFilterFetchedRangeRecord object) => {}, - getId: (DbFilterFetchedRangeRecord object) => object.dbId, - setId: (DbFilterFetchedRangeRecord object, int id) { - object.dbId = id; - }, - objectToFB: (DbFilterFetchedRangeRecord object, fb.Builder fbb) { - final filterHashOffset = fbb.writeString(object.filterHash); - final relayUrlOffset = fbb.writeString(object.relayUrl); - fbb.startTable(6); - fbb.addInt64(0, object.dbId); - fbb.addOffset(1, filterHashOffset); - fbb.addOffset(2, relayUrlOffset); - fbb.addInt64(3, object.rangeStart); - fbb.addInt64(4, object.rangeEnd); - fbb.finish(fbb.endTable()); - return object.dbId; - }, - objectFromFB: (obx.Store store, ByteData fbData) { - final buffer = fb.BufferContext(fbData); - final rootOffset = buffer.derefObject(0); - final filterHashParam = const fb.StringReader( - asciiOptimization: true, - ).vTableGet(buffer, rootOffset, 6, ''); - final relayUrlParam = const fb.StringReader( - asciiOptimization: true, - ).vTableGet(buffer, rootOffset, 8, ''); - final rangeStartParam = const fb.Int64Reader().vTableGet( - buffer, - rootOffset, - 10, - 0, - ); - final rangeEndParam = const fb.Int64Reader().vTableGet( - buffer, - rootOffset, - 12, - 0, - ); - final object = DbFilterFetchedRangeRecord( - filterHash: filterHashParam, - relayUrl: relayUrlParam, - rangeStart: rangeStartParam, - rangeEnd: rangeEndParam, - )..dbId = const fb.Int64Reader().vTableGet( - buffer, - rootOffset, - 4, - 0, - ); - - return object; - }, - ), + model: _entities[3], + toOneRelations: (DbFilterFetchedRangeRecord object) => [], + toManyRelations: (DbFilterFetchedRangeRecord object) => {}, + getId: (DbFilterFetchedRangeRecord object) => object.dbId, + setId: (DbFilterFetchedRangeRecord object, int id) { + object.dbId = id; + }, + objectToFB: (DbFilterFetchedRangeRecord object, fb.Builder fbb) { + final filterHashOffset = fbb.writeString(object.filterHash); + final relayUrlOffset = fbb.writeString(object.relayUrl); + fbb.startTable(6); + fbb.addInt64(0, object.dbId); + fbb.addOffset(1, filterHashOffset); + fbb.addOffset(2, relayUrlOffset); + fbb.addInt64(3, object.rangeStart); + fbb.addInt64(4, object.rangeEnd); + fbb.finish(fbb.endTable()); + return object.dbId; + }, + objectFromFB: (obx.Store store, ByteData fbData) { + final buffer = fb.BufferContext(fbData); + final rootOffset = buffer.derefObject(0); + final filterHashParam = const fb.StringReader( + asciiOptimization: true, + ).vTableGet(buffer, rootOffset, 6, ''); + final relayUrlParam = const fb.StringReader( + asciiOptimization: true, + ).vTableGet(buffer, rootOffset, 8, ''); + final rangeStartParam = const fb.Int64Reader().vTableGet( + buffer, + rootOffset, + 10, + 0, + ); + final rangeEndParam = const fb.Int64Reader().vTableGet( + buffer, + rootOffset, + 12, + 0, + ); + final object = + DbFilterFetchedRangeRecord( + filterHash: filterHashParam, + relayUrl: relayUrlParam, + rangeStart: rangeStartParam, + rangeEnd: rangeEndParam, + ) + ..dbId = const fb.Int64Reader().vTableGet( + buffer, + rootOffset, + 4, + 0, + ); + + return object; + }, + ), DbMetadata: obx_int.EntityDefinition( model: _entities[4], toOneRelations: (DbMetadata object) => [], @@ -1231,25 +1238,33 @@ obx_int.ModelDefinition getObjectBoxModel() { }, objectToFB: (DbMetadata object, fb.Builder fbb) { final pubKeyOffset = fbb.writeString(object.pubKey); - final nameOffset = - object.name == null ? null : fbb.writeString(object.name!); + final nameOffset = object.name == null + ? null + : fbb.writeString(object.name!); final displayNameOffset = object.displayName == null ? null : fbb.writeString(object.displayName!); - final pictureOffset = - object.picture == null ? null : fbb.writeString(object.picture!); - final bannerOffset = - object.banner == null ? null : fbb.writeString(object.banner!); - final websiteOffset = - object.website == null ? null : fbb.writeString(object.website!); - final aboutOffset = - object.about == null ? null : fbb.writeString(object.about!); - final nip05Offset = - object.nip05 == null ? null : fbb.writeString(object.nip05!); - final lud16Offset = - object.lud16 == null ? null : fbb.writeString(object.lud16!); - final lud06Offset = - object.lud06 == null ? null : fbb.writeString(object.lud06!); + final pictureOffset = object.picture == null + ? null + : fbb.writeString(object.picture!); + final bannerOffset = object.banner == null + ? null + : fbb.writeString(object.banner!); + final websiteOffset = object.website == null + ? null + : fbb.writeString(object.website!); + final aboutOffset = object.about == null + ? null + : fbb.writeString(object.about!); + final nip05Offset = object.nip05 == null + ? null + : fbb.writeString(object.nip05!); + final lud16Offset = object.lud16 == null + ? null + : fbb.writeString(object.lud16!); + final lud06Offset = object.lud06 == null + ? null + : fbb.writeString(object.lud06!); final splitDisplayNameWordsOffset = object.splitDisplayNameWords == null ? null : fbb.writeList( @@ -1264,8 +1279,9 @@ obx_int.ModelDefinition getObjectBoxModel() { .map(fbb.writeString) .toList(growable: false), ); - final tagsJsonOffset = - object.tagsJson == null ? null : fbb.writeString(object.tagsJson!); + final tagsJsonOffset = object.tagsJson == null + ? null + : fbb.writeString(object.tagsJson!); final rawContentJsonOffset = object.rawContentJson == null ? null : fbb.writeString(object.rawContentJson!); @@ -1336,8 +1352,8 @@ obx_int.ModelDefinition getObjectBoxModel() { rootOffset, 26, ); - final refreshedTimestampParam = - const fb.Int64Reader().vTableGetNullable(buffer, rootOffset, 28); + final refreshedTimestampParam = const fb.Int64Reader() + .vTableGetNullable(buffer, rootOffset, 28); final tagsJsonParam = const fb.StringReader( asciiOptimization: true, ).vTableGetNullable(buffer, rootOffset, 34); @@ -1378,8 +1394,9 @@ obx_int.ModelDefinition getObjectBoxModel() { final nostrIdOffset = fbb.writeString(object.nostrId); final pubKeyOffset = fbb.writeString(object.pubKey); final contentOffset = fbb.writeString(object.content); - final sigOffset = - object.sig == null ? null : fbb.writeString(object.sig!); + final sigOffset = object.sig == null + ? null + : fbb.writeString(object.sig!); final sourcesOffset = fbb.writeList( object.sources.map(fbb.writeString).toList(growable: false), ); @@ -1428,39 +1445,40 @@ obx_int.ModelDefinition getObjectBoxModel() { 10, 0, ); - final object = DbNip01Event( - pubKey: pubKeyParam, - kind: kindParam, - content: contentParam, - nostrId: nostrIdParam, - createdAt: createdAtParam, - ) - ..dbId = const fb.Int64Reader().vTableGet( - buffer, - rootOffset, - 4, - 0, - ) - ..sig = const fb.StringReader( - asciiOptimization: true, - ).vTableGetNullable(buffer, rootOffset, 16) - ..validSig = const fb.BoolReader().vTableGetNullable( - buffer, - rootOffset, - 18, - ) - ..sources = const fb.ListReader( - fb.StringReader(asciiOptimization: true), - lazy: false, - ).vTableGet(buffer, rootOffset, 20, []) - ..tagsPacked = const fb.ListReader( - fb.StringReader(asciiOptimization: true), - lazy: false, - ).vTableGet(buffer, rootOffset, 22, []) - ..tagsIndex = const fb.ListReader( - fb.StringReader(asciiOptimization: true), - lazy: false, - ).vTableGet(buffer, rootOffset, 24, []); + final object = + DbNip01Event( + pubKey: pubKeyParam, + kind: kindParam, + content: contentParam, + nostrId: nostrIdParam, + createdAt: createdAtParam, + ) + ..dbId = const fb.Int64Reader().vTableGet( + buffer, + rootOffset, + 4, + 0, + ) + ..sig = const fb.StringReader( + asciiOptimization: true, + ).vTableGetNullable(buffer, rootOffset, 16) + ..validSig = const fb.BoolReader().vTableGetNullable( + buffer, + rootOffset, + 18, + ) + ..sources = const fb.ListReader( + fb.StringReader(asciiOptimization: true), + lazy: false, + ).vTableGet(buffer, rootOffset, 20, []) + ..tagsPacked = const fb.ListReader( + fb.StringReader(asciiOptimization: true), + lazy: false, + ).vTableGet(buffer, rootOffset, 22, []) + ..tagsIndex = const fb.ListReader( + fb.StringReader(asciiOptimization: true), + lazy: false, + ).vTableGet(buffer, rootOffset, 24, []); return object; }, @@ -1931,8 +1949,9 @@ obx_int.ModelDefinition getObjectBoxModel() { }, objectToFB: (DbKeyValue object, fb.Builder fbb) { final keyOffset = fbb.writeString(object.key); - final valueOffset = - object.value == null ? null : fbb.writeString(object.value!); + final valueOffset = object.value == null + ? null + : fbb.writeString(object.value!); fbb.startTable(4); fbb.addInt64(0, object.dbId); fbb.addOffset(1, keyOffset); @@ -2123,8 +2142,8 @@ class DbFilterFetchedRangeRecord_ { /// See [DbFilterFetchedRangeRecord.rangeStart]. static final rangeStart = obx.QueryIntegerProperty( - _entities[3].properties[3], - ); + _entities[3].properties[3], + ); /// See [DbFilterFetchedRangeRecord.rangeEnd]. static final rangeEnd = obx.QueryIntegerProperty( @@ -2449,8 +2468,8 @@ class DbWalletCahsuKeyset_ { /// See [DbWalletCahsuKeyset.mintKeyPairs]. static final mintKeyPairs = obx.QueryStringVectorProperty( - _entities[10].properties[6], - ); + _entities[10].properties[6], + ); /// See [DbWalletCahsuKeyset.fetchedAt]. static final fetchedAt = obx.QueryIntegerProperty( @@ -2546,8 +2565,8 @@ class DbWalletTransaction_ { /// See [DbWalletTransaction.metadataJsonString]. static final metadataJsonString = obx.QueryStringProperty( - _entities[12].properties[10], - ); + _entities[12].properties[10], + ); } /// [DbKeyValue] entity fields to define ObjectBox queries. From 974728d31f6b9846de1799f125e5319c6af5a9c2 Mon Sep 17 00:00:00 2001 From: Leo <58687994+1-leo@users.noreply.github.com> Date: Mon, 11 May 2026 11:25:13 +0200 Subject: [PATCH 3/3] fix: write cache buffered --- .../usecases/cache_write/cache_write.dart | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/packages/ndk/lib/domain_layer/usecases/cache_write/cache_write.dart b/packages/ndk/lib/domain_layer/usecases/cache_write/cache_write.dart index 096409e33..735dbdb38 100644 --- a/packages/ndk/lib/domain_layer/usecases/cache_write/cache_write.dart +++ b/packages/ndk/lib/domain_layer/usecases/cache_write/cache_write.dart @@ -1,5 +1,8 @@ import 'dart:async'; +import 'package:rxdart/rxdart.dart'; + +import '../../../shared/isolates/isolate_manager_io.dart'; import '../../../shared/logger/logger.dart'; import '../../entities/nip_01_event.dart'; import '../../repositories/cache_manager.dart'; @@ -7,24 +10,36 @@ import '../../repositories/cache_manager.dart'; /// class to handle writes to cache/db with business logic class CacheWrite { final CacheManager cacheManager; + final Duration writeBufferDuration; - CacheWrite(this.cacheManager); + CacheWrite( + this.cacheManager, { + this.writeBufferDuration = const Duration(seconds: 5), + }); /// saves network responses in db and then write to response stream if not already in db (useful to avoid duplicates) void saveNetworkResponse({ required bool writeToCache, required Stream inputStream, }) { - inputStream.listen((event) async { - Logger.log.t(() => "⛁ got event from network $event "); - - if (writeToCache) { - await cacheManager.saveEvent(event); - } - }, onDone: () { - //? cannot be implemented as stack insert when the stream closes, because it would screw up subscriptions. - }, onError: (error) { - Logger.log.e(() => "⛔ $error "); - }); + inputStream + .doOnData((event) { + Logger.log.t(() => "⛁ got event from network $event "); + }) + .bufferTime(writeBufferDuration) + .where((events) => writeToCache && events.isNotEmpty) + .listen( + (events) { + Logger.log.t(() => + "CacheWrite - 💾 Saving batch of ${events.length} events"); + cacheManager.saveEvents(events); + }, + onDone: () { + Logger.log.t(() => "CacheWrite - ✓ Stream completed"); + }, + onError: (error) { + Logger.log.e(() => "CacheWrite - ⛔ $error "); + }, + ); } }