Decorators
Firestore is schemaless. Two documents in the same collection can hold completely different fields with completely different types, and the database will not object — which is convenient until a field that was always a string turns up as a number in six documents written by an old build. There is no migration system and no ALTER TABLE, so the schema only exists wherever you decide to write it down.
These decorators are that place. They record the shape of each collection in a registry keyed by class constructor, which the runtime reads to validate writes and the CLI generators read to emit security rules, indexes, and Cloud Functions. One consequence of the TC39 Stage 3 decorator semantics is worth knowing: field decorators run before class decorators and never see the constructor, so their metadata is queued and drained by @Collection. A model class without @Collection is not registered at all, and its fields go nowhere.
@Collection(name)
Registers a class as a Firestore collection model.
@Collection('users')
class User extends BaseModel {
// fields...
}The name is the Firestore collection path, and it is required — an empty or non-string name throws immediately at class definition time rather than failing on the first query. An options object accepts idStrategy, for choosing between Firestore-generated and caller-assigned document IDs, and parentCollection, which marks the model as a subcollection. That second option matters beyond bookkeeping: subcollections need a different match path in security rules and a different wildcard path in Cloud Function triggers, and the generators use this metadata to produce both correctly.
@Field(options)
Defines field metadata including type, required status, and defaults.
// Available types:
// 'string' | 'number' | 'boolean' | 'timestamp'
// 'geopoint' | 'reference' | 'array' | 'map' | 'bytes'
@Field({ type: 'string' })
name!: string;
@Field({ type: 'number', default: 0 })
score!: number;
@Field({ type: 'timestamp' })
createdAt!: Date;The nine types mirror Firestore's own value types rather than TypeScript's, which is why timestamp, geopoint and reference appear alongside the primitives. The runtime check is structural and deliberately permissive about representation: a timestamp accepts either a JavaScript Date or a Firestore Timestamp, so values that came back from a read can be written straight back without conversion.
Beyond type, the options object takes required, default, description, indexed and, for arrays, arrayElementType. A default is applied on create when the key is absent from the data you pass — it is not a database-level default, so documents written outside Firemap will not get it. For queries spanning more than one field you want an explicit composite index rather than a per-field flag.
@Required
Shorthand to mark a field as required. Creates validation errors on create/update if missing.
@Required
@Field({ type: 'string' })
email!: string;
// Throws FiremapValidationError if missingThis is equivalent to required: true on @Field, and its effect reaches further than the runtime. The rules generator turns every required field on a collection into a keys().hasAll([...]) assertion inside the create rule, so the constraint is enforced by the database for clients that never touch your code. Presence is what is checked, not truthiness — an empty string or a zero satisfies it.
@SoftDelete
Marks the collection for soft deletes. Instead of removing documents, sets a deletedAt timestamp.
@SoftDelete
@Collection('posts')
class Post extends BaseModel {
// delete() sets deletedAt instead of removing
}Firestore deletes are irreversible and do not cascade to subcollections, which is the usual reason teams reach for soft deletes in the first place. The trade-off is that a soft-deleted document is still a document: it occupies storage, it still matches queries, and it is still readable by anyone your security rules allow. Nothing filters it out for you, so every query against a soft-deleting collection needs its own deletedAt condition — see the model API for how that interacts with find.