zema_firestore 0.1.0
zema_firestore: ^0.1.0 copied to clipboard
Zema schema validation integration for Cloud Firestore. Type-safe document parsing via withConverter.
zema_firestore #
Zema schema validation integration for Cloud Firestore.
Hooks into Firestore's native withConverter API so every document read is automatically validated against a Zema schema. One line to wire up a collection: all reads, streams, and queries return validated data.
Installation #
dependencies:
zema: ^0.4.0
zema_firestore: ^0.1.0
cloud_firestore: ^5.0.0
Quick start #
import 'package:zema/zema.dart';
import 'package:zema_firestore/zema_firestore.dart';
final userSchema = z.object({
'id': z.string(),
'name': z.string().min(1),
'email': z.string().email(),
'createdAt': zTimestamp(),
});
final usersRef = FirebaseFirestore.instance
.collection('users')
.withZema(userSchema);
// Read: validated automatically
final snapshot = await usersRef.doc('abc').get();
final user = snapshot.data(); // Map<String, dynamic>, always valid
// Stream: each document validated as it arrives
usersRef.snapshots().listen((snap) {
for (final doc in snap.docs) {
print(doc.data()['email']); // safe
}
});
// Write
await usersRef.doc('abc').set({
'name': 'Alice',
'email': 'alice@example.com',
'createdAt': DateTime.now(), // written as Timestamp automatically
});
How it works #
withZema(schema) calls Firestore's withConverter with a ZemaFirestoreConverter<T> that:
- Injects the document ID into the data map under
'id'(configurable). - Runs
schema.safeParse(data)on every read. - Converts
DateTimefields toTimestampon every write. - Throws
ZemaFirestoreExceptionon schema mismatch, or callsonParseErrorif provided.
Firebase-specific schemas #
Use these instead of standard Zema primitives for Firestore-specific types:
final schema = z.object({
'createdAt': zTimestamp(), // Timestamp | DateTime → DateTime
'location': zGeoPoint(), // GeoPoint
'authorRef': zDocumentRef(), // DocumentReference
'avatar': zBlob(), // Blob
});
zTimestamp() accepts both Timestamp (from Firestore) and DateTime (from app code) and always produces a DateTime.
Error handling #
final usersRef = FirebaseFirestore.instance
.collection('users')
.withZema(
userSchema,
onParseError: (snapshot, error, stackTrace) {
// Log to your error tracker
Sentry.captureException(error, stackTrace: stackTrace);
// Return a fallback or null to rethrow
return {'id': snapshot.id, 'name': 'Unknown', 'email': 'unknown@example.com', 'createdAt': DateTime(2000)};
},
);
When no onParseError is provided, a ZemaFirestoreException is thrown with:
path: Firestore document pathdocumentId: document IDissues:List<ZemaIssue>from ZemareceivedData: raw document data for debugging
Configuration #
| Parameter | Type | Default | Description |
|---|---|---|---|
schema |
ZemaSchema<_, T> |
required | Schema used to validate each document |
validateWrites |
bool |
false |
Validate the map through schema before writing |
injectDocumentId |
bool |
true |
Inject document ID into the data map before parsing |
documentIdField |
String |
'id' |
Key used for the injected document ID |
onParseError |
OnParseError<T>? |
null |
Fallback callback on parse failure |
Works with Query and DocumentReference #
// Query
final active = FirebaseFirestore.instance
.collection('users')
.where('isActive', isEqualTo: true)
.withZema(userSchema);
final snap = await active.get();
for (final doc in snap.docs) {
print(doc.data()['name']); // validated
}
// DocumentReference
final ref = FirebaseFirestore.instance
.collection('users')
.doc('abc')
.withZema(userSchema);
await ref.set({'name': 'Alice', 'email': 'alice@example.com', 'createdAt': DateTime.now()});
Related packages #
zema: core schema libraryzema_forms: Flutter form integrationzema_hive: Hive integration