1.2.0-beta.3 • Published 5 years ago

firestore-schema v1.2.0-beta.3

Weekly downloads
206
License
MIT
Repository
-
Last release
5 years ago

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 or update
  • 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 or Promise<Boolean>. This functions are run on create and update.
    • 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.
  • convertTo: Function that receives a value and returns another one. This function is run on create or update. 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

https://codesandbox.io/s/z3yr8525k4

1.2.0-beta.3

5 years ago

1.2.0-beta.2

5 years ago

1.2.0-beta.1

5 years ago

1.2.0-beta.0

5 years ago

1.1.2

5 years ago

1.1.1

5 years ago

1.1.0

5 years ago

1.0.76

5 years ago

1.0.75

5 years ago

1.0.74

5 years ago

1.0.73

5 years ago

1.0.72

5 years ago

1.0.71-beta.0

5 years ago

1.0.70

5 years ago

1.0.69

5 years ago

1.0.68

5 years ago

1.0.67

5 years ago

1.0.66

5 years ago

1.0.66-beta.5

5 years ago

1.0.66-beta.4

5 years ago

1.0.66-beta.3

5 years ago

1.0.66-beta.2

5 years ago

1.0.66-beta.1

5 years ago

1.0.66-beta.0

5 years ago

1.0.65

5 years ago

1.0.64-beta.1

5 years ago

1.0.64-beta.0

5 years ago

1.0.63

5 years ago

1.0.62-a

5 years ago

1.0.61

5 years ago

1.0.60

5 years ago

1.0.58

5 years ago

1.0.57

5 years ago

1.0.56

5 years ago

1.0.55

5 years ago

1.0.54

5 years ago

1.0.53

5 years ago

1.0.52

5 years ago

1.0.51

5 years ago

1.0.50

5 years ago

1.0.49

5 years ago

1.0.48

5 years ago

1.0.47

5 years ago

1.0.46

5 years ago

1.0.45

5 years ago

1.0.44

5 years ago

1.0.43

5 years ago

1.0.42

5 years ago

1.0.41

5 years ago

1.0.40

5 years ago

1.0.39

5 years ago

1.0.38

5 years ago

1.0.37

5 years ago

1.0.36

5 years ago

1.0.35

5 years ago

1.0.34

5 years ago

1.0.33

5 years ago

1.0.32

5 years ago

1.0.31

5 years ago

1.0.30

5 years ago

1.0.29

5 years ago

1.0.28

5 years ago

1.0.27

5 years ago

1.0.26

5 years ago

1.0.25

5 years ago

1.0.24

5 years ago

1.0.23

5 years ago

1.0.21

5 years ago

1.0.20

5 years ago

1.0.19

5 years ago

1.0.18

5 years ago

1.0.17

5 years ago

1.0.16

5 years ago

1.0.15

5 years ago

1.0.14

5 years ago

1.0.13

5 years ago

1.0.12

5 years ago

1.0.11

5 years ago

1.0.10

5 years ago

1.0.9

5 years ago

1.0.8

5 years ago

1.0.7

5 years ago

1.0.6

5 years ago

1.0.5

5 years ago

1.0.4

5 years ago

1.0.3

5 years ago

1.0.2

5 years ago

1.0.1

5 years ago

1.0.0

5 years ago