firestore-schema v1.2.0-beta.3
Firestore Schema
- Implement CRUD operations for your collection in just a couple of lines of code.
create
,delete
,update
,get
,get several
,query
- Get each field of a document validated when trying to
create
orupdate
- Define a field that reference a document on another collection and get all the behaviors that you expect from that
- Define events to occur when a schema is created, updated or deleted
- Define custom types to describe how you want to treat certain fields
- Use easy tools to implement the most common relations
- Use typescript's interfaces to get a better intellisense
Usage
First you will need to pass a valid firebase app instance to FirestoreSchema to work
import firebase from "@firebase/app";
import "@firebase/firestore";
import "@firebase/auth";
import FirestoreSchema from "firestore-schema";
const firebaseAppInstance = firebase.initializeApp(FIREBASE_CONFIG_OBJECT);
FirestoreSchema.setup(firebaseAppInstance); // <-- We need this. !important
Now that we have FirestoreSchema setted up.. Define the schema of your collections and firestore-schema will provide you an object with all the CRUD methods for that collection.
All the documents when retrieved will have an id
, updated_at
and created_at
field.
import FirestoreSchema, { type } from "firestore-schema";
const Article = new FirestoreSchema("Comments", {
title: type.String,
content: type.String,
tags: type.List({ child: type.String, defaultValue: [] })
});
Now if we want to create a document, we can use the create
method of the schema and it will validate all fields according to the Article schema
Article.create({
title: "Hello World",
content: "Lorem ipsum dolor sit amet",
tags: ["firestore"]
});
if you try to:
Article.create({
title: 123, // <- this will fail, it should be of type string
content: "Lorem ipsum dolor sit amet",
tags: ["firestore"]
});
Create relations
Firestore Schema also offers more complex manipulation.
Lets say now we want to add a field author
to our Article schema
import FirestoreSchema, { type } from "firestore-schema";
const Users = new FirestoreSchema("Users", {
name: type.String
});
const Article = new FirestoreSchema("Articles", {
title: type.String,
content: type.String,
tags: type.List({ child: type.String }),
author: type.LookUp({ relation: "Users" }) // <- This should do the work
});
// Now to create a document we just:
Article.create({
author: "User-ID",
title: "Hello World",
content: "Lorem ipsum dolor sit amet",
tags: ["firestore"]
});
// The author field will be validated and made sure that the document id passed actually exists
// Also, if you run
Article.get("Article-ID");
// the `author` field will be resolved, and instead of the user's id you will get the user document in place
One-to-many relation
createRelation
will return two fields that when implemented on your schemas they will add some events and connections between them./
This will use firestore sub-collections and this comes with some performance improvements when retrieving multiple user's articles at once.
import FirestoreSchema, { type, createRelation } from "firestore-schema";
const USERS = "users",
ARTICLES = "articles";
/**
* createRelation will return a tuple
* First value = type.List({ child: type.LookUp });
* Second value = type.LookUp
*
* But why not just to use those fields directly on the schema?
* Because by using `createRelation()` FirestoreSchema will handle this scenario in a smart way.
* By creating document copies and sub-collections to improve performance, and mantaining all the copies in sync without having you to worry about anything.
*/
const [userArticles, articleAuthor] = createRelation();
const Users = new FirestoreSchema(USERS, {
name: type.String,
articles: userArticles // <- Here's one
});
const Article = new FirestoreSchema(ARTICLES, {
title: type.String,
content: type.String,
tags: type.List({ child: type.String }),
author: articleAuthor // <- Aaand the other
});
Query
All your schemas will have a method query
that will retrieve only the data you are interested in.\
First arg is the document id, second arg is a query object.
Query Object:
- Use
false
if you dont want to resolve a field and just get the raw value from the document\ - Use
true
if you want to resolve the field. - Use an object if the field is of type
LookUp
and you want to only retrieve specific fields
Article.query("Article-ID", {
content: true,
author: {
name: true,
// You can even do this if you want
articles: {
content: true
}
// You get the idea
// you can retrieve whatever you want
}
});
Events
Sometimes you need to do something after a document is created/updated/deleted, you can do that by using schema events. When defining a new schema theres a third optional argument that accepts an object with the following interface:
interface SchemaEvents {
onCreate: (newRecord, transaction?) => void | Promise<void>;
onDelete: (recordId, transaction?) => void | Promise<void>;
onUpdate: (getOldRecord, fieldsUpdated, transaction?) => void | Promise<void>;
}
- All events will run before the operation is executed.
- You can see how the record created will look like by accessing the
newRecord
argument - You must only use the write methods of the Firestore.Transaction argument. This allows you to increase the performance by using the same transaction for all operations related)
Types
Types are functions that describe how to treat a field. Firestore Schema provides 8 types out of the box:
String
Number
Boolean
DateTime
Any
List
OneOf
(Like an enum)LookUp
(A reference to another document)
To use a type you can just pass the function without calling it,
or you can use the function with an object as argument.
The argument object contains at least 2 properties: required
and defaultValue
, but they can have more.
const Article = new FirestoreSchema("Articles", {
title: type.String({ required: true }), // Here we call the function with some arguments
content: type.String, // Here we pass just the function
tags: type.List({ child: type.String, defaultValue: [] }),
author: type.LookUp({ relation: "Users", required: true })
});
Custom Types
Sometimes, you need a field to be treated different than what the default types offer.
For those cases, you can create a custom type by using the createType
method.
When creating a custom type, you will be able to set the following:
- validations: Array of functions that receive a value and return
Boolean
orPromise<Boolean>
. This functions are run oncreate
andupdate
.- Return
true
if the value is valid. - Return
false
if the value is invalid and should be ommited. - Throw an error if the value is invalid and should avoid the document creation/update.
- Return
- convertTo: Function that receives a value and returns another one. This function is run on
create
orupdate
. You should use it if you want the value to change when storing it. - resolve: Function that receives the value of the field and should return a new value. This function is run when retrieving a document
- args: Arguments that you will need to do all of the above ^
const JsonType = createType("json", (store, getArgs) => {
const convertTo = (value: Object) => JSON.stringify(value);
const resolve = (value: string) => JSON.parse(value);
return {
convertTo,
resolve
};
});
const Foo = new FirestoreSchema("Foo", {
data: JsonType, // <- Just like the default types, you don't need to pass any args if you don't want to
metadata: JsonType({ required: true })
});
CodeSandbox example
You will need to provide a firebase config for this to work.
The files that may be of your interest are: schema.setup.ts
and actions.ts
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago