From c17950ce4f483a3ef6976d3a73ae4b25a309bd23 Mon Sep 17 00:00:00 2001 From: Gringo Date: Tue, 23 Dec 2025 13:47:10 +0100 Subject: [PATCH 1/2] feat: deprecation message for filters --- .../usecases/requests/requests.dart | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/packages/ndk/lib/domain_layer/usecases/requests/requests.dart b/packages/ndk/lib/domain_layer/usecases/requests/requests.dart index 20c2b18da..1f928b98d 100644 --- a/packages/ndk/lib/domain_layer/usecases/requests/requests.dart +++ b/packages/ndk/lib/domain_layer/usecases/requests/requests.dart @@ -58,7 +58,8 @@ class Requests { /// Performs a low-level Nostr query /// - /// [filters] A list of filters to apply to the query \ + /// [filter] The filter to apply to the query \ + /// [filters] @deprecated A list of filters to apply to the query. Use [filter] instead \ /// [name] An optional name used as an ID prefix \ /// [relaySet] An optional set of relays to query \ /// [cacheRead] Whether to read from cache \ @@ -71,7 +72,9 @@ class Requests { /// /// Returns an [NdkResponse] containing the query result stream, future NdkResponse query({ - required List filters, + Filter? filter, + @Deprecated('Use filter instead. Multiple filters support will be removed in a future version.') + List? filters, String name = '', RelaySet? relaySet, bool cacheRead = true, @@ -82,12 +85,16 @@ class Requests { Iterable? explicitRelays, int? desiredCoverage, }) { + if (filter == null && (filters == null || filters.isEmpty)) { + throw ArgumentError('Either filter or filters must be provided'); + } + final effectiveFilters = filter != null ? [filter] : filters!; timeout ??= _defaultQueryTimeout; return requestNostrEvent(NdkRequest.query( '$name-${Helpers.getRandomString(10)}', name: name, - filters: filters.map((e) => e.clone()).toList(), + filters: effectiveFilters.map((e) => e.clone()).toList(), relaySet: relaySet, cacheRead: cacheRead, cacheWrite: cacheWrite, @@ -102,7 +109,8 @@ class Requests { /// Creates a low-level Nostr subscription /// - /// [filters] A list of filters to apply to the subscription \ + /// [filter] The filter to apply to the subscription \ + /// [filters] @deprecated A list of filters to apply to the subscription. Use [filter] instead \ /// [name] An optional name for the subscription \ /// [id] An optional ID for the subscription, overriding name \ /// [relaySet] An optional set of relays to subscribe to \ @@ -113,7 +121,9 @@ class Requests { /// /// Returns an [NdkResponse] containing the subscription results as stream NdkResponse subscription({ - required List filters, + Filter? filter, + @Deprecated('Use filter instead. Multiple filters support will be removed in a future version.') + List? filters, String name = '', String? id, RelaySet? relaySet, @@ -122,10 +132,14 @@ class Requests { Iterable? explicitRelays, int? desiredCoverage, }) { + if (filter == null && (filters == null || filters.isEmpty)) { + throw ArgumentError('Either filter or filters must be provided'); + } + final effectiveFilters = filter != null ? [filter] : filters!; return requestNostrEvent(NdkRequest.subscription( id ?? "$name-${Helpers.getRandomString(10)}", name: name, - filters: filters.map((e) => e.clone()).toList(), + filters: effectiveFilters.map((e) => e.clone()).toList(), relaySet: relaySet, cacheRead: cacheRead, cacheWrite: cacheWrite, From 34fd9b257fdbb2b2e0eedebd463865f4d9eca76e Mon Sep 17 00:00:00 2001 From: Gringo Date: Tue, 23 Dec 2025 13:54:07 +0100 Subject: [PATCH 2/2] feat: add tests --- packages/ndk/test/relays/requests_test.dart | 63 +++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/packages/ndk/test/relays/requests_test.dart b/packages/ndk/test/relays/requests_test.dart index b0f710f1f..c3f8790c0 100644 --- a/packages/ndk/test/relays/requests_test.dart +++ b/packages/ndk/test/relays/requests_test.dart @@ -42,6 +42,33 @@ void main() async { }; group('Requests', () { + test('Request text note with single filter parameter', () async { + MockRelay relay1 = MockRelay(name: "relay 1", explicitPort: 6060); + await relay1.startServer(textNotes: textNotes); + + final ndk = Ndk(NdkConfig( + eventVerifier: MockEventVerifier(), + cache: MemCacheManager(), + engine: NdkEngine.RELAY_SETS, + bootstrapRelays: [relay1.url], + )); + ndk.accounts + .loginPrivateKey(pubkey: key1.publicKey, privkey: key1.privateKey!); + + final filter = + Filter(kinds: [Nip01Event.kTextNodeKind], authors: [key1.publicKey]); + + // Using the new single filter parameter + final query = ndk.requests.query(filter: filter); + + await expectLater( + query.stream, emitsInAnyOrder([textNotes.values.first])); + + await ndk.destroy(); + expect(ndk.relays.globalState.inFlightRequests.isEmpty, true); + await relay1.stopServer(); + }); + test('Request text note', () async { MockRelay relay1 = MockRelay(name: "relay 1", explicitPort: 6060); await relay1.startServer(textNotes: textNotes); @@ -131,6 +158,42 @@ void main() async { await relay1.stopServer(); }); + test('Subscription with single filter parameter', () async { + MockRelay relay1 = MockRelay(name: "relay 1", explicitPort: 6060); + await relay1.startServer(textNotes: textNotes); + + final ndk = Ndk(NdkConfig( + eventVerifier: MockEventVerifier(), + cache: MemCacheManager(), + engine: NdkEngine.RELAY_SETS, + bootstrapRelays: [relay1.url], + )); + ndk.accounts + .loginPrivateKey(pubkey: key1.publicKey, privkey: key1.privateKey!); + + final filter = + Filter(kinds: [Nip01Event.kTextNodeKind], authors: [key1.publicKey]); + + // Using the new single filter parameter + final subscription = ndk.requests.subscription(filter: filter); + + final receivedEvents = []; + final streamSubscription = subscription.stream.listen((event) { + receivedEvents.add(event); + }); + + await Future.delayed(Duration(milliseconds: 200)); + + expect(receivedEvents.length, equals(1)); + expect(receivedEvents[0].content, contains('key1')); + + await streamSubscription.cancel(); + await ndk.requests.closeSubscription(subscription.requestId); + await ndk.destroy(); + expect(ndk.relays.globalState.inFlightRequests.isEmpty, true); + await relay1.stopServer(); + }); + test('Subscription processes events immediately without stream closing', () async { // This test would FAIL with the previous VerifyEventStream implementation