Skip to content

Commit f7577d6

Browse files
authored
refactor(firestore): standalone googleapis_firestore package and multi-db support for firestore (#121)
* docs: update documentation link for sending messages to topic conditions * refactor: update constructors to use internal factory methods for AppCheck, Auth, Firestore, Messaging, and SecurityRules * update README * wip refactor google_cloud_firestore into its own package * wip refactor google_cloud_firestore files to streamline package structure * refactor: rename files and update imports for googleapis_firestore package * docs: add googleapis_firestore package documentation section to index.html * feat: add multi-database support to Firestore This change refactors the Firestore integration to support multiple databases within a single `FirebaseApp`, similar to the Node.js Admin SDK. Key changes: - `app.firestore()` now accepts an optional `databaseId` to get or create a named database instance. - Database instances are cached per `databaseId` to ensure the same instance is returned on subsequent calls. - The `Firestore` service wrapper now manages multiple `googleapis_firestore.Firestore` delegates, one for each database ID. - Settings are now passed during initialization via `app.firestore(settings: ...)`, and re-initialization with different settings is prevented. - Added comprehensive unit and integration tests for multi-database functionality, CRUD operations, and emulator support. - Refactored `EmulatorClient` into the `googleapis_firestore` package and removed redundant exception handling code. * docs: update README.md with Firestore usage examples and corrections * chore: fix lint errors * feat: enhance Firestore example with multi-database support and usage scenarios * feat: add googleapis_firestore dependency to pubspec.yaml * feat: add failedPrecondition error code and update error messages in Firestore * chore: comment out local Firestore emulator configuration in coverage script * refactor(firestore): simplify and improve firestore tests
1 parent 6d38423 commit f7577d6

File tree

104 files changed

+4712
-3731
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

104 files changed

+4712
-3731
lines changed

all_lint_rules.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,5 +219,4 @@ linter:
219219
- use_string_in_part_of_directives
220220
- use_super_parameters
221221
- use_test_throws_matchers
222-
- use_to_and_as_if_applicable
223222
- void_checks

doc/index.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ <h2>dart_firebase_admin</h2>
6060
<a href="api/dart_firebase_admin/index.html">View Documentation →</a>
6161
</li>
6262

63+
<li class="package-item">
64+
<h2>googleapis_firestore</h2>
65+
<p>Standalone Cloud Firestore client library for Dart. Provides direct access to Firestore database operations including documents, collections, queries, transactions, and batch writes.</p>
66+
<a href="api/googleapis_firestore/index.html">View Documentation →</a>
67+
</li>
68+
6369
<li class="package-item">
6470
<h2>googleapis_auth_utils</h2>
6571
<p>Google APIs authentication utilities used by the Firebase Admin SDK.</p>

packages/dart_firebase_admin/README.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,18 +114,21 @@ print("Token: ${result.token}");
114114
### Firestore
115115

116116
```dart
117+
import 'package:dart_firebase_admin/dart_firebase_admin.dart';
118+
import 'package:googleapis_firestore/googleapis_firestore.dart';
119+
117120
final app = FirebaseApp.initializeApp();
118121
119122
// Getting a document
120123
final snapshot = await app.firestore().collection("users").doc("<user-id>").get();
121124
print(snapshot.data());
122125
123126
// Querying a collection
124-
final snapshot = await app.firestore().collection("users")
125-
.where('age', .greaterThan, 18)
127+
final querySnapshot = await app.firestore().collection("users")
128+
.where('age', WhereFilter.greaterThan, 18)
126129
.orderBy('age', descending: true)
127130
.get();
128-
print(snapshot.docs())
131+
print(querySnapshot.docs);
129132
130133
// Running a transaction (e.g. adding credits to a balance)
131134
final balance = await app.firestore().runTransaction((tsx) async {
@@ -136,13 +139,13 @@ final balance = await app.firestore().runTransaction((tsx) async {
136139
final snapshot = await tsx.get(ref);
137140
138141
// Get the users current balance (or 0 if it doesn't exist)
139-
final currentBalance = snapshot.exists() ? snapshot.data()?['balanace'] ?? 0 : 0;
142+
final currentBalance = snapshot.exists ? snapshot.data()?['balance'] ?? 0 : 0;
140143
141144
// Add 10 credits to the users balance
142145
final newBalance = currentBalance + 10;
143146
144147
// Update the document within the transaction
145-
await tsx.update(ref, {
148+
tsx.update(ref, {
146149
'balance': newBalance,
147150
});
148151

packages/dart_firebase_admin/example/lib/main.dart

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Future<void> main() async {
1010
// await authExample(admin);
1111

1212
// Uncomment to run firestore example
13-
// await firestoreExample(admin);
13+
await firestoreExample(admin);
1414

1515
// Uncomment to run project config example
1616
// await projectConfigExample(admin);
@@ -61,6 +61,8 @@ Future<void> authExample(FirebaseApp admin) async {
6161
Future<void> firestoreExample(FirebaseApp admin) async {
6262
print('\n### Firestore Example ###\n');
6363

64+
// Example 1: Using the default database
65+
print('> Using default database...\n');
6466
final firestore = admin.firestore();
6567

6668
try {
@@ -73,6 +75,49 @@ Future<void> firestoreExample(FirebaseApp admin) async {
7375
} catch (e) {
7476
print('> Error setting document: $e');
7577
}
78+
79+
// Example 2: Using a named database (multi-database support)
80+
print('\n> Using named database "my-database"...\n');
81+
final namedFirestore = admin.firestore(databaseId: 'my-database');
82+
83+
try {
84+
final collection = namedFirestore.collection('products');
85+
await collection.doc('product-1').set({
86+
'name': 'Widget',
87+
'price': 19.99,
88+
'inStock': true,
89+
});
90+
print('> Document written to named database\n');
91+
92+
final doc = await collection.doc('product-1').get();
93+
if (doc.exists) {
94+
print('> Retrieved from named database: ${doc.data()}');
95+
}
96+
} catch (e) {
97+
print('> Error with named database: $e');
98+
}
99+
100+
// Example 3: Using multiple databases simultaneously
101+
print('\n> Demonstrating multiple database access...\n');
102+
try {
103+
final defaultDb = admin.firestore();
104+
final analyticsDb = admin.firestore(databaseId: 'analytics-db');
105+
106+
await defaultDb.collection('users').doc('user-1').set({
107+
'name': 'Alice',
108+
'email': 'alice@example.com',
109+
});
110+
111+
await analyticsDb.collection('events').doc('event-1').set({
112+
'type': 'page_view',
113+
'timestamp': DateTime.now().toIso8601String(),
114+
'userId': 'user-1',
115+
});
116+
117+
print('> Successfully wrote to multiple databases');
118+
} catch (e) {
119+
print('> Error with multiple databases: $e');
120+
}
76121
}
77122

78123
// ignore: unreachable_from_main

packages/dart_firebase_admin/example/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ environment:
77

88
dependencies:
99
dart_firebase_admin: any
10+
googleapis_firestore: any
Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1 @@
1-
export 'src/google_cloud_firestore/firestore.dart'
2-
hide
3-
$SettingsCopyWith,
4-
ApiMapValue,
5-
AggregateFieldInternal,
6-
FirestoreHttpClient;
1+
export 'src/firestore/firestore.dart';

packages/dart_firebase_admin/lib/src/app.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ import 'dart:io';
66
import 'dart:typed_data';
77

88
import 'package:equatable/equatable.dart';
9-
// import 'package:googleapis/cloudfunctions/v2.dart' as auth4;
109
import 'package:googleapis/identitytoolkit/v3.dart' as auth3;
1110
import 'package:googleapis_auth/auth_io.dart' as googleapis_auth;
1211
import 'package:googleapis_auth_utils/googleapis_auth_utils.dart'
1312
as googleapis_auth_utils;
13+
import 'package:googleapis_firestore/googleapis_firestore.dart'
14+
as googleapis_firestore;
1415
import 'package:http/http.dart';
1516
import 'package:meta/meta.dart';
1617

packages/dart_firebase_admin/lib/src/app/app_exception.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ enum AppErrorCode {
7070
/// No Firebase app exists with the given name.
7171
noApp(code: 'no-app', message: 'No Firebase app exists with the given name.'),
7272

73+
/// Operation failed because a precondition was not met.
74+
failedPrecondition(
75+
code: 'failed-precondition',
76+
message: 'The operation failed because a precondition was not met.',
77+
),
78+
7379
/// Unable to parse the server response.
7480
unableToParseResponse(
7581
code: 'unable-to-parse-response',

packages/dart_firebase_admin/lib/src/app/emulator_client.dart

Lines changed: 2 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class _RequestImpl extends BaseRequest {
2626
/// Firebase emulators expect this specific bearer token to grant full
2727
/// admin privileges for local development and testing.
2828
@internal
29-
class EmulatorClient implements googleapis_auth.AuthClient {
29+
class EmulatorClient extends BaseClient implements googleapis_auth.AuthClient {
3030
EmulatorClient(this.client);
3131

3232
final Client client;
@@ -49,57 +49,7 @@ class EmulatorClient implements googleapis_auth.AuthClient {
4949
}
5050

5151
@override
52-
void close() {
53-
client.close();
54-
}
55-
56-
@override
57-
Future<Response> head(Uri url, {Map<String, String>? headers}) =>
58-
client.head(url, headers: headers);
59-
60-
@override
61-
Future<Response> get(Uri url, {Map<String, String>? headers}) =>
62-
client.get(url, headers: headers);
63-
64-
@override
65-
Future<Response> post(
66-
Uri url, {
67-
Map<String, String>? headers,
68-
Object? body,
69-
Encoding? encoding,
70-
}) => client.post(url, headers: headers, body: body, encoding: encoding);
71-
72-
@override
73-
Future<Response> put(
74-
Uri url, {
75-
Map<String, String>? headers,
76-
Object? body,
77-
Encoding? encoding,
78-
}) => client.put(url, headers: headers, body: body, encoding: encoding);
79-
80-
@override
81-
Future<Response> patch(
82-
Uri url, {
83-
Map<String, String>? headers,
84-
Object? body,
85-
Encoding? encoding,
86-
}) => client.patch(url, headers: headers, body: body, encoding: encoding);
87-
88-
@override
89-
Future<Response> delete(
90-
Uri url, {
91-
Map<String, String>? headers,
92-
Object? body,
93-
Encoding? encoding,
94-
}) => client.delete(url, headers: headers, body: body, encoding: encoding);
95-
96-
@override
97-
Future<String> read(Uri url, {Map<String, String>? headers}) =>
98-
client.read(url, headers: headers);
99-
100-
@override
101-
Future<Uint8List> readBytes(Uri url, {Map<String, String>? headers}) =>
102-
client.readBytes(url, headers: headers);
52+
void close() => client.close();
10353
}
10454

10555
/// HTTP client for Cloud Tasks emulator that rewrites URLs.

packages/dart_firebase_admin/lib/src/app/firebase_app.dart

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -151,42 +151,46 @@ class FirebaseApp {
151151
/// Gets the App Check service instance for this app.
152152
///
153153
/// Returns a cached instance if one exists, otherwise creates a new one.
154-
AppCheck appCheck() =>
155-
getOrInitService(FirebaseServiceType.appCheck.name, AppCheck.internal);
154+
AppCheck appCheck() => AppCheck.internal(this);
156155

157156
/// Gets the Auth service instance for this app.
158157
///
159158
/// Returns a cached instance if one exists, otherwise creates a new one.
160-
Auth auth() => getOrInitService(FirebaseServiceType.auth.name, Auth.internal);
159+
Auth auth() => Auth.internal(this);
161160

162161
/// Gets the Firestore service instance for this app.
163162
///
164163
/// Returns a cached instance if one exists, otherwise creates a new one.
165164
/// Optional [settings] are only applied when creating a new instance.
166-
Firestore firestore({Settings? settings}) => getOrInitService(
167-
FirebaseServiceType.firestore.name,
168-
(app) => Firestore.internal(app, settings: settings),
169-
);
165+
///
166+
/// For multi-database support, use [databaseId] to specify a named database.
167+
/// Default is '(default)'.
168+
googleapis_firestore.Firestore firestore({
169+
googleapis_firestore.Settings? settings,
170+
String databaseId = kDefaultDatabaseId,
171+
}) {
172+
final service = Firestore.internal(this);
173+
174+
if (settings != null) {
175+
return service.initializeDatabase(databaseId, settings);
176+
}
177+
return service.getDatabase(databaseId);
178+
}
170179

171180
/// Gets the Messaging service instance for this app.
172181
///
173182
/// Returns a cached instance if one exists, otherwise creates a new one.
174-
Messaging messaging() =>
175-
getOrInitService(FirebaseServiceType.messaging.name, Messaging.internal);
183+
Messaging messaging() => Messaging.internal(this);
176184

177185
/// Gets the Security Rules service instance for this app.
178186
///
179187
/// Returns a cached instance if one exists, otherwise creates a new one.
180-
SecurityRules securityRules() => getOrInitService(
181-
FirebaseServiceType.securityRules.name,
182-
SecurityRules.internal,
183-
);
188+
SecurityRules securityRules() => SecurityRules.internal(this);
184189

185190
/// Gets the Functions service instance for this app.
186191
///
187192
/// Returns a cached instance if one exists, otherwise creates a new one.
188-
Functions functions() =>
189-
getOrInitService(FirebaseServiceType.functions.name, Functions.internal);
193+
Functions functions() => Functions.internal(this);
190194

191195
/// Closes this app and cleans up all associated resources.
192196
///

0 commit comments

Comments
 (0)