From 9b5660f947cfec8eef574f0e03bdb8f65f1bda57 Mon Sep 17 00:00:00 2001 From: Colin Ihlenfeldt Date: Tue, 19 May 2026 11:31:46 +0200 Subject: [PATCH] feat: support appendMissingElements (arrayUnion) in YustFieldTransform Adds `appendMissingElements` to `YustFieldTransform` so callers can ask Firestore to union values into an array field. Wired through both the Dart (`googleapis/firestore`) and Flutter (`cloud_firestore`) variants. Also makes `YustDatabaseServiceMocked.updateDocByTransform` honor the full transform set (`appendMissingElements`, `removeFromArray`, `setToServerTimestamp`, `delete`) instead of treating every transform as an `increment`. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 4 ++ .../yust_database_service_mocked.dart | 48 ++++++++++++++++--- lib/src/util/yust_field_transform_dart.dart | 7 +++ .../util/yust_field_transform_flutter.dart | 5 ++ pubspec.yaml | 2 +- 5 files changed, 58 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 438d19de..ecefa353 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.34.0 - 2026-05-19 + +- Add `appendMissingElements` to `YustFieldTransform` (Firestore `arrayUnion`) on Dart, Flutter, and mocked backends; mocked service now also honors `removeFromArray`, `setToServerTimestamp`, and `delete` + ## 3.33.2 - 2026-05-18 - Fix handling of empty maps in `YustDatabaseService` (dart-only) diff --git a/lib/src/services/yust_database_service_mocked.dart b/lib/src/services/yust_database_service_mocked.dart index 67fa406c..cf4f1491 100644 --- a/lib/src/services/yust_database_service_mocked.dart +++ b/lib/src/services/yust_database_service_mocked.dart @@ -512,13 +512,47 @@ class YustDatabaseServiceMocked extends YustDatabaseService for (final t in fieldTransforms) { final unescapedPath = t.fieldPath.replaceAll('`', ''); - final oldValue = - (_readValueInJsonDoc(jsonDocClone, unescapedPath) ?? 0) as num; - _changeValueInJsonDoc( - jsonDocClone, - oldValue + (t.increment ?? 0), - unescapedPath, - ); + if (t.appendMissingElements != null) { + final current = _readValueInJsonDoc(jsonDocClone, unescapedPath); + final list = (current is List) + ? List.from(current) + : []; + for (final element in t.appendMissingElements!) { + if (!list.any( + (e) => const DeepCollectionEquality().equals(e, element), + )) { + list.add(element); + } + } + _changeValueInJsonDoc(jsonDocClone, list, unescapedPath); + } else if (t.removeFromArray != null) { + final current = _readValueInJsonDoc(jsonDocClone, unescapedPath); + final list = (current is List) + ? List.from(current) + : []; + list.removeWhere( + (e) => t.removeFromArray!.any( + (r) => const DeepCollectionEquality().equals(e, r), + ), + ); + _changeValueInJsonDoc(jsonDocClone, list, unescapedPath); + } else if (t.delete == true) { + _changeValueInJsonDoc(jsonDocClone, null, unescapedPath); + } else if (t.setToServerTimestamp == true) { + _changeValueInJsonDoc( + jsonDocClone, + DateTime.now().toUtc().toIso8601String(), + unescapedPath, + ); + } else { + final oldValue = + (_readValueInJsonDoc(jsonDocClone, unescapedPath) ?? 0) as num; + _changeValueInJsonDoc( + jsonDocClone, + oldValue + (t.increment ?? 0), + unescapedPath, + ); + } jsonDocs[index] = jsonDocClone; await onChange?.call( _getParentPath(docSetup, id: id), diff --git a/lib/src/util/yust_field_transform_dart.dart b/lib/src/util/yust_field_transform_dart.dart index f3fbf9d9..e460930e 100644 --- a/lib/src/util/yust_field_transform_dart.dart +++ b/lib/src/util/yust_field_transform_dart.dart @@ -5,6 +5,7 @@ import '../../yust.dart'; class YustFieldTransform { String fieldPath; double? increment; + List? appendMissingElements; List? removeFromArray; bool? setToServerTimestamp; bool? delete; @@ -12,6 +13,7 @@ class YustFieldTransform { YustFieldTransform({ required String fieldPath, this.increment, + this.appendMissingElements, this.removeFromArray, this.setToServerTimestamp, this.delete, @@ -27,6 +29,11 @@ class YustFieldTransform { return FieldTransform( fieldPath: YustHelpers().toQuotedFieldPath(fieldPath), increment: increment != null ? Value(doubleValue: increment) : null, + appendMissingElements: appendMissingElements != null + ? ArrayValue.fromJson({ + 'values': appendMissingElements, + }) + : null, removeAllFromArray: removeFromArray != null ? ArrayValue.fromJson({'values': removeFromArray}) : null, diff --git a/lib/src/util/yust_field_transform_flutter.dart b/lib/src/util/yust_field_transform_flutter.dart index 457eab79..eba0693c 100644 --- a/lib/src/util/yust_field_transform_flutter.dart +++ b/lib/src/util/yust_field_transform_flutter.dart @@ -3,6 +3,7 @@ import 'package:cloud_firestore/cloud_firestore.dart'; class YustFieldTransform { String fieldPath; double? increment; + List? appendMissingElements; List? removeFromArray; bool? setToServerTimestamp; bool? delete; @@ -10,6 +11,7 @@ class YustFieldTransform { YustFieldTransform({ required String fieldPath, this.increment, + this.appendMissingElements, this.removeFromArray, this.setToServerTimestamp, this.delete, @@ -23,6 +25,9 @@ class YustFieldTransform { /// ... and for dart (googleapis/firestore) its a [FieldTransform] dynamic toNativeTransform() { if (increment != null) return FieldValue.increment(increment!); + if (appendMissingElements != null) { + return FieldValue.arrayUnion(appendMissingElements!); + } if (removeFromArray != null) { return FieldValue.arrayRemove(removeFromArray!); } diff --git a/pubspec.yaml b/pubspec.yaml index d1581341..4f9cc273 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: yust description: Build awesome apps with Flutter and Firebase - this package allows communication with Firebase to be a breeze. -version: 3.33.2 +version: 3.34.0 homepage: https://github.com/univelop/yust environment: