diff --git a/packages/ferry_cache/lib/src/cache.dart b/packages/ferry_cache/lib/src/cache.dart index 8b3f170e..03be1e19 100644 --- a/packages/ferry_cache/lib/src/cache.dart +++ b/packages/ferry_cache/lib/src/cache.dart @@ -310,12 +310,9 @@ class Cache { } } - void _evictField( - String entityId, - String fieldName, - Map args, - OperationRequest? optimisticRequest, - ) { + void _evictField(String entityId, String fieldName, Map args, + OperationRequest? optimisticRequest, + [bool eraseCompletely = false]) { if (optimisticRequest != null) { /// Set field to `null` in optimistic patch optimisticPatchesStream.add({ @@ -349,25 +346,57 @@ class Cache { final entity = store.get(entityId); if (entity != null) { - store.put( - entityId, - entity.map( - // NOTE: we need to set to null rather than removing altogether - // to ensure that denormalize doesn't throw a [PartialDataException] - (key, value) => _fieldMatch( - key, - fieldName, - args, - ) - ? MapEntry(key, null) - : MapEntry(key, value), - ), - ); + if (eraseCompletely) { + store.put( + entityId, + { + for (final key in entity.keys) + if (!_fieldMatch(key, fieldName, args)) key: entity[key], + }, + ); + } else { + store.put( + entityId, + entity.map( + // NOTE: we need to set to null rather than removing altogether + // to ensure that denormalize doesn't throw a [PartialDataException] + (key, value) => _fieldMatch( + key, + fieldName, + args, + ) + ? MapEntry(key, null) + : MapEntry(key, value), + ), + ); + } } _eventStream.add(null); } } + /// Evicts all top-level fields from that operation from the cache. + /// Consider calling after this gc() to completely remove orphaned entities. + void evictOperation(OperationRequest request) { + final operationDefinition = utils.getOperationDefinition( + request.operation.document, request.operation.operationName); + final rootTypeName = + utils.resolveRootTypename(operationDefinition, typePolicies); + + final fields = utils.operationFieldNames( + request.operation.document, + request.operation.operationName, + request.varsToJson(), + typePolicies, + possibleTypes, + ); + + for (final field in fields) { + final fieldKey = utils.FieldKey.parse(field); + _evictField(rootTypeName, fieldKey.fieldName, fieldKey.args, null, true); + } + } + bool _fieldMatch( String keyString, String fieldName, diff --git a/packages/ferry_cache/pubspec.yaml b/packages/ferry_cache/pubspec.yaml index 173374e3..d2763c79 100644 --- a/packages/ferry_cache/pubspec.yaml +++ b/packages/ferry_cache/pubspec.yaml @@ -19,7 +19,7 @@ dependencies: pedantic: ^1.11.0 dev_dependencies: test: ^1.16.8 - ferry_test_graphql2: ^0.4.0 + ferry_test_graphql2: gql_exec: ^1.0.0 gql: ^1.0.0 gql_tristate_value: ^1.0.0 \ No newline at end of file diff --git a/packages/ferry_cache/test/eviction_test.dart b/packages/ferry_cache/test/eviction_test.dart index 3a99101c..b9631532 100644 --- a/packages/ferry_cache/test/eviction_test.dart +++ b/packages/ferry_cache/test/eviction_test.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:test/test.dart'; import 'package:normalize/src/utils/field_key.dart'; @@ -122,6 +124,7 @@ void main() { hanReq.rebuild((b) => b..vars.friendsAfter = 'chewie'), hanData, ); + final entityId = cache.identify(hanData.human)!; final keyLuke = FieldKey.from('friendsConnection', {'first': 10, 'after': 'luke'}); @@ -170,4 +173,50 @@ void main() { expect(cache.store.get('Human:chewie'), isNull); }); }); + + group('evictOperation', () { + test('can evict Operation', () { + final cache = Cache(); + addTearDown(() { + cache.dispose(); + }); + cache.writeQuery( + hanReq, + hanData, + ); + + cache.evictOperation(hanReq); + + expect(cache.readQuery(hanReq), equals(null)); + + final gcResult = cache.gc(); + + expect(gcResult, equals({'Human:luke', 'Human:chewie', 'Human:han'})); + + expect(cache.store.get('Query'), equals({'__typename': 'Query'})); + + expect(cache.store.keys, equals({'Query'})); + }); + + test('only evicts given operation', () { + final cache = Cache(); + addTearDown(() { + cache.dispose(); + }); + cache.writeQuery( + hanReq, + hanData, + ); + + cache.writeQuery( + chewieReq, + chewieData, + ); + + cache.evictOperation(hanReq); + + expect(cache.readQuery(hanReq), equals(null)); + expect(cache.readQuery(chewieReq), equals(chewieData)); + }); + }); }