FireRecord
🔥 ActiveRecord-style Object-Relational Mapper for Cloud Firestore. Inspired by TypeORM and pring.ts.
Installation
$ yarn add firerecord
Then initialize FireRecord with the Firestore instance:
;; admin.initializeApp;initializeadmin.firestore;
TypeScript configuration
Enable experimentalDecorators
option in tsconfig.json
.
"experimentalDecorators": true
Quick Start
With FireRecord, your model looks like this:
;
And your domain logic looks like this:
await User.create; // Alternatively, you can use User.find(id) if you know id.; if !user await user.update; await user.destroy;
Usage
In this section, we'll see 4 decorators in FireRecord and how to manage data.
Defining models
Every FireRecord model should extend the Entity
class. FireRecord automatically detects a Firestore collection name from the model class name like ActiveRecord. (We use pluralize package)
;
Field Decorator
To add Firestore Document property, you simply need to decorate an entity's property with a @Field
decorator.
;
You can set the property's default value when using the @Field
decorator.
;
Embedded decorator
Since there is no JOIN query in Firestore, it's a good idea to embed data from other models unless you need strong consistency. Use @Embedded
decorator to embed data from other models, and just put the property names you want to embed in the second parameter.
;
The article entity's data will looks like this:
id: 'Mbfc08V59aQCY4yWnz75' title: 'My Great Post' content: 'Awesome Content' user: id: 'OOFh7DiDuEoSGSMu1OfQ' name: 'mu29'
You can use the cascade
option with the @Embedded
decorator. In the example above, let's say you want update an article's user information.
; if !article article.update;
This will updates the user's name embedded in the article, but does not change the original user model's name. Putting the cascade option will make it works as expected.
It is also possible to embed an array of entities.
;
There is a difference between updating an embedded entity list and an embedded entity, but we will see it later in update section.
Filtery` decorator
How do you deal with complex queries, such as all posts from one user sorted by creation date? Firestore docs suggested a decent solution: Encode all data about the query into a single map field. It looks like this:
id: 'Mbfc08V59aQCY4yWnz75' title: 'My Great Post' content: 'Awesome Content' user: id: 'OOFh7DiDuEoSGSMu1OfQ' name: 'mu29' createdAt: 1569903714364 updatedAt: 1569903714364 filterBy: user: OOFh7DiDuEoSGSMu1OfQ: 1569903714364
So we implemented it, the @FilterBy
decorator.
;
It will automatically create filterBy
property with entity's createdAt
value.
id: 'Mbfc08V59aQCY4yWnz75' title: 'My Great Post' content: 'Awesome Content' user: id: 'OOFh7DiDuEoSGSMu1OfQ' name: 'mu29' tags: id: 'c2OTYSydklJoQBNLL9TJ' name: 'hashtag' id: 'g4taIbpA79N5ECAcilZo' name: 'test' createdAt: 1569903714364 updatedAt: 1569903714364 filterBy: user: OOFh7DiDuEoSGSMu1OfQ: 1569903714364 tags: c2OTYSydklJoQBNLL9TJ: 1569903714364 g4taIbpA79N5ECAcilZo: 1569903714364
Nested decorator
Firestore sub-collections can be represented by @Nested
decorator.
;
Create
If you've used ActiveRecord, you will be familiar with it.
; await user.save; ; ; ;
Note that nested entities must be created separately.
;
Find
Finding an Entity is also similar to ActiveRecord, but applying range filters came from TypeORM.
; ;
There are 6 operators: Equal
, LessThan
, LessThanOrEqual
, MoreThan
, MoreThanOrEqual
, and ArrayContains
. Here's the use case:
; ;
Also, you can query the properties of an embedded entity.
;
lazy
option
If you do not need to read the data, such as just updating an attributes, you can use the lazy option.
;
Where
There is also a where
function that allows you to retrieve a list of entities. There is a condition
option that we used before on find
and findBy
function.
; ;
Filter option is supported on the property that is decorated with @FilterBy
. Note that you can only use one filter.
;
Pagination is supported by the cursor
option. It will returns the entities after the cursor using Firestore's startAfter
function.
;
Finally, we also support the order
option, which is a bit complicated. Normally, it takes an array of strings where the first element is an property name and the second element is an direction type. (asc or desc)
;
However, if we use the filter
option, it only accepts the direction type.
;
Update
Since we use the merge option in Firestore when updating entity, all data (including the embedded entity) will be merged, not replaced.
await Article.update'Mbfc08V59aQCY4yWnz75', ; // or ; if !article article.update;
However, an embedded entity list will be replaced, not merged.
Destroy
For now, deleting an entity does not erase the embedded data.
await User.destroy'OOFh7DiDuEoSGSMu1OfQ'; // or ;await user.destroy; // Caution: the user data embedded in the article entity remains.
Nested Entities
Nested Entities can also use the create
, update
, find
, findBy
, where
, and destroy
functions we've seen so far!
Transaction
Pass an array of Entity#save
functions that you what to be processed at once as an argument to the runTransaction function.
; ; ; user.name = 'New fancy name'; await runTransaction;
Contributing
Bug reports and pull requests are welcome on GitHub.
License
The package is available as open source under the terms of the MIT License.