Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
d0de5f1
feat(firestore): Web implementation for Pipeline APIs
SelaseKay Mar 9, 2026
9e0dd21
chore: fix ci
SelaseKay Mar 9, 2026
19e374f
chore: inject firestore pipelines script for web
SelaseKay Mar 9, 2026
9087862
chore: add comments for Firestore Pipelines script injection in web i…
SelaseKay Mar 9, 2026
d778f31
Merge branch 'firestore-pipelines-dart-api-v2' into firestore-pipelin…
SelaseKay Mar 10, 2026
97151e9
refactor: remove unecessary comment
SelaseKay Mar 10, 2026
248c694
chore: add support for missing Expressions and fix bugs
SelaseKay Mar 10, 2026
49b1737
chore: fix ci
SelaseKay Mar 10, 2026
214ea85
feat: enhance pipeline expression handling with new expressions and e…
SelaseKay Mar 11, 2026
5a68800
Merge branch 'firestore-pipelines-dart-api-v2' into firestore-pipelin…
SelaseKay Mar 13, 2026
d654f4d
refactor: rename 'replace' case to 'string_replace_all' in pipeline e…
SelaseKay Mar 13, 2026
474e951
Merge branch 'firestore-pipelines-dart-api-v2' into firestore-pipelin…
SelaseKay Mar 13, 2026
44fde63
Merge branch 'firestore-pipelines-dart-api-v2' into firestore-pipelin…
SelaseKay Mar 17, 2026
3ebb93d
chore: update Pipeline execution to accept options for index mode
SelaseKay Mar 17, 2026
9a6c7f9
Merge branch 'firestore-pipelines-dart-api-v2' into firestore-pipelin…
SelaseKay Mar 18, 2026
6e28d59
fix: resolve failing mapGet test on web
SelaseKay Mar 18, 2026
a9d58f6
fix: correctly parse 'not' expression arguments
SelaseKay Mar 19, 2026
46e00de
trigger CI
SelaseKay Mar 20, 2026
5f30720
Merge branch 'firestore-pipelines-dart-api-v2' into firestore-pipelin…
SelaseKay Mar 20, 2026
4601281
Merge branch 'firestore-pipelines-dart-api-v2' into firestore-pipelin…
SelaseKay Mar 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import 'src/collection_reference_web.dart';
import 'src/document_reference_web.dart';
import 'src/field_value_factory_web.dart';
import 'src/interop/firestore.dart' as firestore_interop;
import 'src/interop/firestore_interop.dart' as firestore_interop_js;
import 'src/pipeline_builder_web.dart';
import 'src/pipeline_web.dart';
import 'src/query_web.dart';
import 'src/transaction_web.dart';
import 'src/write_batch_web.dart';
Expand Down Expand Up @@ -271,4 +274,48 @@ class FirebaseFirestoreWeb extends FirebaseFirestorePlatform {
}
_delegate.setLoggingEnabled(value);
}

@override
PipelinePlatform pipeline(List<Map<String, dynamic>> initialStages) {
return PipelineWeb(this, _delegate, initialStages);
}

@override
Future<PipelineSnapshotPlatform> executePipeline(
List<Map<String, dynamic>> stages, {
Map<String, dynamic>? options,
}) async {
final jsFirestore = _delegate.jsObject;
firestore_interop_js.PipelineJsImpl jsPipeline;
try {
jsPipeline = buildPipelineFromStages(jsFirestore, stages);
} catch (e, stack) {
// Let our Dart FirebaseException (e.g. unsupported expression) propagate
// so the user sees a clear message; the guard would cast it to JSError.
if (e is FirebaseException) {
Error.throwWithStackTrace(e, stack);
}
// JS or other errors: run through the guard to convert to FirebaseException.
return convertWebExceptions(() async {
Error.throwWithStackTrace(e, stack);
});
}

final dartPipeline = firestore_interop.Pipeline.getInstance(jsPipeline);
return convertWebExceptions(() async {
String? executeOptions;
if (options != null) {
executeOptions = options['indexMode'] as String;
}
final snapshot = await dartPipeline.execute(executeOptions);

final results = snapshot.results
.map((r) => PipelineResultWeb(this, _delegate, r.jsObject))
.toList();

final executionTime = snapshot.executionTime ?? DateTime.now();

return PipelineSnapshotWeb(results, executionTime);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1089,3 +1089,122 @@ class AggregateQuerySnapshot
}
}
}

// =============================================================================
// Pipeline (global execute + snapshot/result wrappers)
// =============================================================================

/// Single result in a pipeline snapshot (document + data).
class PipelineResult
extends JsObjectWrapper<firestore_interop.PipelineResultJsImpl> {
static final _expando = Expando<PipelineResult>();

late final DocumentReference? _ref;
late final Map<String, dynamic>? _data;
late final DateTime? _createTime;
late final DateTime? _updateTime;

static PipelineResult getInstance(
firestore_interop.PipelineResultJsImpl jsObject) {
return _expando[jsObject] ??= PipelineResult._fromJsObject(jsObject);
}

PipelineResult._fromJsObject(firestore_interop.PipelineResultJsImpl jsObject)
: _ref = jsObject.ref != null
? DocumentReference.getInstance(jsObject.ref!)
: null,
_data = _dataFromResult(jsObject),
_createTime = _timestampToDateTime(jsObject.createTime),
_updateTime = _timestampToDateTime(jsObject.updateTime),
super.fromJsObject(jsObject);

static Map<String, dynamic>? _dataFromResult(
firestore_interop.PipelineResultJsImpl jsResult) {
final d = jsResult.data();
if (d == null) return null;
final parsed = dartify(d);
return parsed != null
? Map<String, dynamic>.from(parsed as Map<Object?, Object?>)
: null;
}

static DateTime? _timestampToDateTime(dynamic value) {
if (value == null) return null;
final d = dartify(value);
if (d == null) return null;
if (d is DateTime) return d;
if (d is Timestamp) return d.toDate();
if (d is int) return DateTime.fromMillisecondsSinceEpoch(d);
return null;
}

DocumentReference? get ref => _ref;
Map<String, dynamic>? get data => _data;
DateTime? get createTime => _createTime;
DateTime? get updateTime => _updateTime;
}

/// Snapshot of pipeline execution results.
class PipelineSnapshot
extends JsObjectWrapper<firestore_interop.PipelineSnapshotJsImpl> {
static final _expando = Expando<PipelineSnapshot>();

late final List<PipelineResult> _results;
late final DateTime? _executionTime;

static PipelineSnapshot getInstance(
firestore_interop.PipelineSnapshotJsImpl jsObject) {
return _expando[jsObject] ??= PipelineSnapshot._fromJsObject(jsObject);
}

static List<PipelineResult> _buildResults(
firestore_interop.PipelineSnapshotJsImpl jsObject) {
final rawResults = jsObject.results.toDart;
return rawResults
.cast<firestore_interop.PipelineResultJsImpl>()
.map(PipelineResult.getInstance)
.toList();
}

PipelineSnapshot._fromJsObject(
firestore_interop.PipelineSnapshotJsImpl jsObject)
: _results = _buildResults(jsObject),
_executionTime = _executionTimeFromJs(jsObject.executionTime),
super.fromJsObject(jsObject);

static DateTime? _executionTimeFromJs(dynamic value) {
if (value == null) return null;
final d = dartify(value);
if (d == null) return null;
if (d is DateTime) return d;
if (d is int) return DateTime.fromMillisecondsSinceEpoch(d);
return null;
}

List<PipelineResult> get results => _results;
DateTime? get executionTime => _executionTime;
}

/// Wraps a JS pipeline; use [execute] to run it via the global execute function.
class Pipeline extends JsObjectWrapper<firestore_interop.PipelineJsImpl> {
static final _expando = Expando<Pipeline>();

static Pipeline getInstance(firestore_interop.PipelineJsImpl jsObject) {
return _expando[jsObject] ??= Pipeline._fromJsObject(jsObject);
}

Pipeline._fromJsObject(firestore_interop.PipelineJsImpl jsObject)
: super.fromJsObject(jsObject);

/// Runs this pipeline using the global JS SDK execute function.
Future<PipelineSnapshot> execute(String? executeOptions) async {
final executeOptionsJs = firestore_interop.PipelineExecuteOptionsJsImpl();
if (executeOptions != null) {
executeOptionsJs.indexMode = executeOptions.toJS;
}
executeOptionsJs.pipeline = jsObject as JSAny;
final snapshot =
await firestore_interop.pipelines.execute(executeOptionsJs).toDart;
return PipelineSnapshot.getInstance(snapshot);
}
}
Loading
Loading