Using with Firestore Triggers
Map the raw snapshot a trigger cloud function receives back to your repository’s read type with
fromSnapshot.
Firestore trigger functions (onDocumentCreated, onDocumentUpdated, onDocumentDeleted, …) hand
your handler a raw QueryDocumentSnapshot — not the shape a repository read produces. Two
things make a bare snapshot.data() as User cast unsafe:
- The converter is not applied. The Admin SDK only runs a
FirestoreDataConverter’sfromFirestorefor references built withwithConverter(...). A trigger snapshot is not one of those, so converter-transformed fields are still their raw stored type — e.g. a field thatcreateMillisTimestampConverterexposes as anumberis still a FirestoreTimestampon the trigger snapshot. - There is no
id.snapshot.data()never contains the document id; it lives onsnapshot.id.
A cast silently mislabels both. See Core Concepts → Firestore Converters for the underlying read semantics.
repo.fromSnapshot(snapshot)
Section titled “repo.fromSnapshot(snapshot)”Import your already-configured repository and hand it the snapshot. fromSnapshot mirrors a normal
read: it applies the repository’s converter fromFirestore (when one is configured), then overlays
id from snapshot.id.
fromSnapshot(snapshot: DocumentSnapshot): (T & { id: ID }) | null;- Returns the read model
T & { id }(never the write modelW). - Returns
nullwhen the snapshot does not exist. - Does no Firestore I/O — it operates purely on the snapshot you pass in.
Example (firebase-functions v2)
Section titled “Example (firebase-functions v2)”import { onDocumentCreated, onDocumentUpdated } from 'firebase-functions/v2/firestore';import { userRepo } from './repositories'; // your configured FirestoreRepository<User>
export const onUserCreated = onDocumentCreated('users/{userId}', event => { const user = event.data && userRepo.fromSnapshot(event.data); if (!user) return; // `user` is a fully reconstructed `User & { id }` — converter applied, id overlaid.});
export const onUserUpdated = onDocumentUpdated('users/{userId}', event => { const before = event.data?.before && userRepo.fromSnapshot(event.data.before); const after = event.data?.after && userRepo.fromSnapshot(event.data.after); if (!after) return; // Compare `before` and `after` as typed `User` values.});For onDocumentUpdated you get event.data.before and event.data.after (both snapshots); map
each with fromSnapshot.
Validating at the boundary
Section titled “Validating at the boundary”Like every other read, fromSnapshot does not run schema validation — it reconstructs and
casts. A trigger is an external input boundary, so if you want a runtime guarantee that the stored
document still matches your schema, parse the result with the repository’s read schema:
const user = event.data && userRepo.schemas?.read.parse(userRepo.fromSnapshot(event.data));repo.schemas?.read is the canonical schema you supplied to withSchema(...) (it includes the
required top-level id). See Schema Validation.