Skip to content

Commit d6a945e

Browse files
authored
feat(firestore, web): Web implementation for Pipeline APIs (#18100)
* feat(firestore): Web implementation for Pipeline APIs * chore: fix ci * chore: inject firestore pipelines script for web * chore: add comments for Firestore Pipelines script injection in web implementation * refactor: remove unecessary comment * chore: add support for missing Expressions and fix bugs * chore: fix ci * feat: enhance pipeline expression handling with new expressions and error handling * refactor: rename 'replace' case to 'string_replace_all' in pipeline expression parser * chore: update Pipeline execution to accept options for index mode * fix: resolve failing mapGet test on web * fix: correctly parse 'not' expression arguments * trigger CI * fix tests * fix tests * fix: update type casting for boolean expression in PipelineExpressionParserWeb * fix ci
1 parent 0f6e2ab commit d6a945e

File tree

7 files changed

+1444
-5
lines changed

7 files changed

+1444
-5
lines changed

packages/cloud_firestore/cloud_firestore_web/lib/cloud_firestore_web.dart

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ import 'src/collection_reference_web.dart';
2121
import 'src/document_reference_web.dart';
2222
import 'src/field_value_factory_web.dart';
2323
import 'src/interop/firestore.dart' as firestore_interop;
24+
import 'src/interop/firestore_interop.dart' as firestore_interop_js;
25+
import 'src/pipeline_builder_web.dart';
26+
import 'src/pipeline_web.dart';
2427
import 'src/query_web.dart';
2528
import 'src/transaction_web.dart';
2629
import 'src/write_batch_web.dart';
@@ -271,4 +274,48 @@ class FirebaseFirestoreWeb extends FirebaseFirestorePlatform {
271274
}
272275
_delegate.setLoggingEnabled(value);
273276
}
277+
278+
@override
279+
PipelinePlatform pipeline(List<Map<String, dynamic>> initialStages) {
280+
return PipelineWeb(this, _delegate, initialStages);
281+
}
282+
283+
@override
284+
Future<PipelineSnapshotPlatform> executePipeline(
285+
List<Map<String, dynamic>> stages, {
286+
Map<String, dynamic>? options,
287+
}) async {
288+
final jsFirestore = _delegate.jsObject;
289+
firestore_interop_js.PipelineJsImpl jsPipeline;
290+
try {
291+
jsPipeline = buildPipelineFromStages(jsFirestore, stages);
292+
} catch (e, stack) {
293+
// Let our Dart FirebaseException (e.g. unsupported expression) propagate
294+
// so the user sees a clear message; the guard would cast it to JSError.
295+
if (e is FirebaseException) {
296+
Error.throwWithStackTrace(e, stack);
297+
}
298+
// JS or other errors: run through the guard to convert to FirebaseException.
299+
return convertWebExceptions(() async {
300+
Error.throwWithStackTrace(e, stack);
301+
});
302+
}
303+
304+
final dartPipeline = firestore_interop.Pipeline.getInstance(jsPipeline);
305+
return convertWebExceptions(() async {
306+
String? executeOptions;
307+
if (options != null) {
308+
executeOptions = options['indexMode'] as String;
309+
}
310+
final snapshot = await dartPipeline.execute(executeOptions);
311+
312+
final results = snapshot.results
313+
.map((r) => PipelineResultWeb(this, _delegate, r.jsObject))
314+
.toList();
315+
316+
final executionTime = snapshot.executionTime ?? DateTime.now();
317+
318+
return PipelineSnapshotWeb(results, executionTime);
319+
});
320+
}
274321
}

packages/cloud_firestore/cloud_firestore_web/lib/src/interop/firestore.dart

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,3 +1089,122 @@ class AggregateQuerySnapshot
10891089
}
10901090
}
10911091
}
1092+
1093+
// =============================================================================
1094+
// Pipeline (global execute + snapshot/result wrappers)
1095+
// =============================================================================
1096+
1097+
/// Single result in a pipeline snapshot (document + data).
1098+
class PipelineResult
1099+
extends JsObjectWrapper<firestore_interop.PipelineResultJsImpl> {
1100+
static final _expando = Expando<PipelineResult>();
1101+
1102+
late final DocumentReference? _ref;
1103+
late final Map<String, dynamic>? _data;
1104+
late final DateTime? _createTime;
1105+
late final DateTime? _updateTime;
1106+
1107+
static PipelineResult getInstance(
1108+
firestore_interop.PipelineResultJsImpl jsObject) {
1109+
return _expando[jsObject] ??= PipelineResult._fromJsObject(jsObject);
1110+
}
1111+
1112+
PipelineResult._fromJsObject(firestore_interop.PipelineResultJsImpl jsObject)
1113+
: _ref = jsObject.ref != null
1114+
? DocumentReference.getInstance(jsObject.ref!)
1115+
: null,
1116+
_data = _dataFromResult(jsObject),
1117+
_createTime = _timestampToDateTime(jsObject.createTime),
1118+
_updateTime = _timestampToDateTime(jsObject.updateTime),
1119+
super.fromJsObject(jsObject);
1120+
1121+
static Map<String, dynamic>? _dataFromResult(
1122+
firestore_interop.PipelineResultJsImpl jsResult) {
1123+
final d = jsResult.data();
1124+
if (d == null) return null;
1125+
final parsed = dartify(d);
1126+
return parsed != null
1127+
? Map<String, dynamic>.from(parsed as Map<Object?, Object?>)
1128+
: null;
1129+
}
1130+
1131+
static DateTime? _timestampToDateTime(dynamic value) {
1132+
if (value == null) return null;
1133+
final d = dartify(value);
1134+
if (d == null) return null;
1135+
if (d is DateTime) return d;
1136+
if (d is Timestamp) return d.toDate();
1137+
if (d is int) return DateTime.fromMillisecondsSinceEpoch(d);
1138+
return null;
1139+
}
1140+
1141+
DocumentReference? get ref => _ref;
1142+
Map<String, dynamic>? get data => _data;
1143+
DateTime? get createTime => _createTime;
1144+
DateTime? get updateTime => _updateTime;
1145+
}
1146+
1147+
/// Snapshot of pipeline execution results.
1148+
class PipelineSnapshot
1149+
extends JsObjectWrapper<firestore_interop.PipelineSnapshotJsImpl> {
1150+
static final _expando = Expando<PipelineSnapshot>();
1151+
1152+
late final List<PipelineResult> _results;
1153+
late final DateTime? _executionTime;
1154+
1155+
static PipelineSnapshot getInstance(
1156+
firestore_interop.PipelineSnapshotJsImpl jsObject) {
1157+
return _expando[jsObject] ??= PipelineSnapshot._fromJsObject(jsObject);
1158+
}
1159+
1160+
static List<PipelineResult> _buildResults(
1161+
firestore_interop.PipelineSnapshotJsImpl jsObject) {
1162+
final rawResults = jsObject.results.toDart;
1163+
return rawResults
1164+
.cast<firestore_interop.PipelineResultJsImpl>()
1165+
.map(PipelineResult.getInstance)
1166+
.toList();
1167+
}
1168+
1169+
PipelineSnapshot._fromJsObject(
1170+
firestore_interop.PipelineSnapshotJsImpl jsObject)
1171+
: _results = _buildResults(jsObject),
1172+
_executionTime = _executionTimeFromJs(jsObject.executionTime),
1173+
super.fromJsObject(jsObject);
1174+
1175+
static DateTime? _executionTimeFromJs(dynamic value) {
1176+
if (value == null) return null;
1177+
final d = dartify(value);
1178+
if (d == null) return null;
1179+
if (d is DateTime) return d;
1180+
if (d is int) return DateTime.fromMillisecondsSinceEpoch(d);
1181+
return null;
1182+
}
1183+
1184+
List<PipelineResult> get results => _results;
1185+
DateTime? get executionTime => _executionTime;
1186+
}
1187+
1188+
/// Wraps a JS pipeline; use [execute] to run it via the global execute function.
1189+
class Pipeline extends JsObjectWrapper<firestore_interop.PipelineJsImpl> {
1190+
static final _expando = Expando<Pipeline>();
1191+
1192+
static Pipeline getInstance(firestore_interop.PipelineJsImpl jsObject) {
1193+
return _expando[jsObject] ??= Pipeline._fromJsObject(jsObject);
1194+
}
1195+
1196+
Pipeline._fromJsObject(firestore_interop.PipelineJsImpl jsObject)
1197+
: super.fromJsObject(jsObject);
1198+
1199+
/// Runs this pipeline using the global JS SDK execute function.
1200+
Future<PipelineSnapshot> execute(String? executeOptions) async {
1201+
final executeOptionsJs = firestore_interop.PipelineExecuteOptionsJsImpl();
1202+
if (executeOptions != null) {
1203+
executeOptionsJs.indexMode = executeOptions.toJS;
1204+
}
1205+
executeOptionsJs.pipeline = jsObject as JSAny;
1206+
final snapshot =
1207+
await firestore_interop.pipelines.execute(executeOptionsJs).toDart;
1208+
return PipelineSnapshot.getInstance(snapshot);
1209+
}
1210+
}

0 commit comments

Comments
 (0)